Share host-aware completion across runtimes

This commit is contained in:
2026-08-13 18:55:03 +09:00
parent e862e53f3e
commit d28edcf041
25 changed files with 2836 additions and 578 deletions
@@ -3,6 +3,73 @@ import { resolve } from 'node:path';
const packageDir = resolve(import.meta.dirname, '../../../packages/decodal-wasm');
const declarationPath = resolve(packageDir, 'decodal_wasm.d.ts');
let declarations = readFileSync(declarationPath, 'utf8');
const hostTypes = `
/** A concrete JavaScript value or Decodal schema descriptor supplied by the host. */
export type DecodalHostValue =
| string
| number
| boolean
| DecodalHostValue[]
| { [field: string]: DecodalHostValue }
| DecodalPrimitiveDescriptor
| DecodalArrayDescriptor
| DecodalAbstractDescriptor;
export type DecodalPrimitiveType = 'String' | 'Int' | 'Float' | 'Bool';
export type DecodalConstraint =
| { kind: 'type'; value: DecodalPrimitiveType }
| { kind: 'compare'; op: '>' | '>=' | '<' | '<='; value: string | number | boolean }
| { kind: 'regex'; value: string }
| { kind: 'predicate'; value: string };
export interface DecodalPrimitiveDescriptor {
$decodal: DecodalPrimitiveType;
constraints?: DecodalConstraint[];
default?: DecodalHostValue;
}
export interface DecodalArrayDescriptor {
$decodal: 'Array';
item: DecodalHostValue;
constraints?: DecodalConstraint[];
default?: DecodalHostValue;
}
export interface DecodalAbstractDescriptor {
$decodal: 'Abstract';
constraints?: DecodalConstraint[];
default?: DecodalHostValue;
}
export type DecodalLoadedImport =
| { kind: 'source'; key: string; name?: string; source: string }
| { kind: 'value'; key: string; value: DecodalHostValue };
export type DecodalImportCandidate =
| string
| { specifier: string; detail?: string };
/** Host-owned environment shared by evaluation and language tooling. */
export interface DecodalEnvironment {
globals?: Record<string, DecodalHostValue>;
/** Synchronous import callback. Preload or cache asynchronous resources first. */
loadImport?: (currentKey: string | null, specifier: string) => DecodalLoadedImport;
/** Synchronous import completion callback. */
completeImport?: (currentKey: string | null, prefix: string) => DecodalImportCandidate[];
}
`;
if (!declarations.includes('export interface DecodalEnvironment')) {
declarations = declarations.replace('/* eslint-disable */\n', `/* eslint-disable */\n${hostTypes}`);
}
declarations = declarations.replace(
'constructor(options: any);',
'constructor(options?: DecodalEnvironment);',
);
writeFileSync(declarationPath, declarations);
writeFileSync(
resolve(packageDir, '.gitignore'),
'# wasm-pack output is committed for the browser runtime package.\n',
@@ -19,7 +86,7 @@ writeFileSync(
WebAssembly runtime package for Decodal.
This package exposes the Decodal evaluator to browsers and other JavaScript runtimes that can load WebAssembly modules.
This package exposes the Decodal evaluator and shared language service to browsers and other JavaScript runtimes that can load WebAssembly modules.
It is generated from the Rust crate in \`crates/decodal-wasm\` with \`wasm-pack\` and is used by the official playground.
Use \`decodal-codemirror\` separately when you need CodeMirror 6 language support.
@@ -39,18 +106,51 @@ deno add jsr:@hare/decodal-wasm
## Usage
\`\`\`js
import init, { evaluateProject } from 'decodal-wasm';
import init, { DecodalLanguageService } from 'decodal-wasm';
await init();
const result = evaluateProject('main.dcdl', JSON.stringify({
'main.dcdl': 'Server = { port = Int default 8080; };',
}));
const files = {
'main.dcdl': 'let schema = import "./schema.dcdl"; in schema.Server',
'schema.dcdl': 'Server = App.Server & { port = 8080; };',
};
const service = new DecodalLanguageService({
globals: {
App: {
Server: {
port: { $decodal: 'Int' },
},
},
},
loadImport(_currentKey, specifier) {
const key = specifier.startsWith('./') ? specifier.slice(2) : specifier;
return { kind: 'source', key, name: key, source: files[key] };
},
completeImport() {
return ['./schema.dcdl'];
},
});
const result = service.evaluate('main.dcdl', 'main.dcdl', files['main.dcdl']);
console.log(JSON.parse(result));
const completion = service.complete(
'main.dcdl',
'App.Server.po',
'App.Server.po'.length,
false,
);
console.log(JSON.parse(completion));
\`\`\`
The exported functions return JSON strings so callers can handle diagnostics and successful results without depending on Rust data structures.
\`globals\`, \`loadImport\`, and \`completeImport\` are host-owned. The package does not assume a filesystem or virtual project model. Import callbacks are synchronous; preload or cache remote content before evaluation.
Plain strings, numbers, booleans, arrays, and objects are concrete host values. Use \`{ $decodal: 'String' | 'Int' | 'Float' | 'Bool' }\` for primitive schemas and \`{ $decodal: 'Array', item: value }\` for array schemas. Descriptors also accept \`constraints\` and \`default\`.
Methods return JSON strings so callers can handle diagnostics and successful results without depending on Rust data structures.
`,
);