config: migrate workspace evaluation to Decodal 0.4
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
"@codemirror/autocomplete": "npm:@codemirror/autocomplete@6.20.0",
|
||||
"@codemirror/state": "npm:@codemirror/state@6.7.1",
|
||||
"@codemirror/view": "npm:@codemirror/view@6.43.8",
|
||||
"decodal-codemirror": "npm:decodal-codemirror@0.1.6",
|
||||
"decodal-codemirror": "npm:decodal-codemirror@0.3.0",
|
||||
"clsx": "npm:clsx@2.1.1",
|
||||
"cookie": "npm:cookie@0.6.0",
|
||||
"devalue": "npm:devalue@5.6.4",
|
||||
|
||||
Generated
+4
-4
@@ -12,7 +12,7 @@
|
||||
"npm:@sveltejs/vite-plugin-svelte@6.2.1": "6.2.1_svelte@5.45.6_vite@7.2.7",
|
||||
"npm:clsx@2.1.1": "2.1.1",
|
||||
"npm:cookie@0.6.0": "0.6.0",
|
||||
"npm:decodal-codemirror@0.1.6": "0.1.6_@codemirror+view@6.43.8",
|
||||
"npm:decodal-codemirror@0.3.0": "0.3.0_@codemirror+language@6.12.4_@codemirror+view@6.43.8_@lezer+highlight@1.2.3_@lezer+lr@1.4.10",
|
||||
"npm:devalue@5.6.4": "5.6.4",
|
||||
"npm:gen-interface-jp@0.8.0": "0.8.0",
|
||||
"npm:set-cookie-parser@2.7.2": "2.7.2",
|
||||
@@ -554,8 +554,8 @@
|
||||
"ms"
|
||||
]
|
||||
},
|
||||
"decodal-codemirror@0.1.6_@codemirror+view@6.43.8": {
|
||||
"integrity": "sha512-XTS5vAY+vTb/yEg5n+1yORtBPuhozG4KO0YGbYEFR8oZX0KghZuHyFEsq/CJMXF223YMC/FQt3SC19NVYGWKMw==",
|
||||
"decodal-codemirror@0.3.0_@codemirror+language@6.12.4_@codemirror+view@6.43.8_@lezer+highlight@1.2.3_@lezer+lr@1.4.10": {
|
||||
"integrity": "sha512-M+Iod3UAZigpt46TmuLJIlSEBhL6KOOksyVxUOGGdlfKYCMN9oN6YG5G9tjkr3/r+Eq8ig1RipC7VZX5iiACTQ==",
|
||||
"dependencies": [
|
||||
"@codemirror/language",
|
||||
"@codemirror/view",
|
||||
@@ -1007,7 +1007,7 @@
|
||||
"npm:@sveltejs/vite-plugin-svelte@6.2.1",
|
||||
"npm:clsx@2.1.1",
|
||||
"npm:cookie@0.6.0",
|
||||
"npm:decodal-codemirror@0.1.6",
|
||||
"npm:decodal-codemirror@0.3.0",
|
||||
"npm:devalue@5.6.4",
|
||||
"npm:set-cookie-parser@2.7.2",
|
||||
"npm:shiki@3.13.0",
|
||||
|
||||
+3
@@ -9,6 +9,8 @@ export function changes_between(base: any, candidate: any): any;
|
||||
|
||||
export function complete_current(entrypoint: string, source: string, utf16_offset: number, explicit: boolean): any;
|
||||
|
||||
export function compose_schema_bundle(contributions: any): any;
|
||||
|
||||
export function evaluate_current(contract: any): any;
|
||||
|
||||
export function evaluate_snapshot(snapshot: any, contract: any): any;
|
||||
@@ -27,6 +29,7 @@ export interface InitOutput {
|
||||
readonly apply_changes: (a: any) => [number, number, number];
|
||||
readonly changes_between: (a: any, b: any) => [number, number, number];
|
||||
readonly complete_current: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
|
||||
readonly compose_schema_bundle: (a: any) => [number, number, number];
|
||||
readonly evaluate_current: (a: any) => [number, number, number];
|
||||
readonly evaluate_snapshot: (a: any, b: any) => [number, number, number];
|
||||
readonly format_source: (a: number, b: number) => [number, number, number, number];
|
||||
|
||||
@@ -62,6 +62,18 @@ export function complete_current(entrypoint, source, utf16_offset, explicit) {
|
||||
return takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} contributions
|
||||
* @returns {any}
|
||||
*/
|
||||
export function compose_schema_bundle(contributions) {
|
||||
const ret = wasm.compose_schema_bundle(contributions);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} contract
|
||||
* @returns {any}
|
||||
|
||||
Binary file not shown.
+1
@@ -5,6 +5,7 @@ export const analyze_snapshot: (a: any, b: number, c: number, d: number, e: numb
|
||||
export const apply_changes: (a: any) => [number, number, number];
|
||||
export const changes_between: (a: any, b: any) => [number, number, number];
|
||||
export const complete_current: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
|
||||
export const compose_schema_bundle: (a: any) => [number, number, number];
|
||||
export const evaluate_current: (a: any) => [number, number, number];
|
||||
export const evaluate_snapshot: (a: any, b: any) => [number, number, number];
|
||||
export const format_source: (a: number, b: number) => [number, number, number, number];
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { decodalLanguage } from "decodal-codemirror";
|
||||
|
||||
Deno.test("editor grammar accepts Decodal 0.4 schema syntax", () => {
|
||||
const source = `
|
||||
import "main.dcdl" as WorkspaceConfigSchema
|
||||
{
|
||||
features = {...{ enabled = Bool; }};
|
||||
web = { enabled = Bool; ...Unknown };
|
||||
}
|
||||
`;
|
||||
const tree = decodalLanguage.parser.parse(source);
|
||||
const errors: string[] = [];
|
||||
tree.iterate({
|
||||
enter(node) {
|
||||
if (node.type.isError) {
|
||||
errors.push(`${node.from}..${node.to}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Decodal 0.4 grammar produced parse errors at ${errors.join(", ")}`);
|
||||
}
|
||||
});
|
||||
@@ -1,19 +1,42 @@
|
||||
/// <reference lib="deno.ns" />
|
||||
|
||||
import { assertEquals } from "jsr:@std/assert";
|
||||
// The generated wasm-bindgen loader is JavaScript with an adjacent declaration file.
|
||||
// @ts-expect-error Deno checks the generated JS implementation rather than its .d.ts.
|
||||
import init, {
|
||||
analyze_snapshot,
|
||||
compose_schema_bundle,
|
||||
evaluate_snapshot,
|
||||
} from "../../src/lib/workspace/config-source/generated/config_source_wasm.js";
|
||||
import type { ConfigTreeSnapshot, ToolchainContract } from "../../src/lib/workspace/config-source/types.ts";
|
||||
import type {
|
||||
ConfigTreeSnapshot,
|
||||
ToolchainContract,
|
||||
WorkspaceConfigSchemaBundle,
|
||||
} from "../../src/lib/workspace/config-source/types.ts";
|
||||
|
||||
const bytes = await Deno.readFile(
|
||||
new URL("../../src/lib/workspace/config-source/generated/config_source_wasm_bg.wasm", import.meta.url),
|
||||
);
|
||||
await init({ module_or_path: bytes });
|
||||
|
||||
async function digestText(text: string): Promise<string> {
|
||||
const bytes = new TextEncoder().encode(text);
|
||||
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
||||
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
||||
}
|
||||
|
||||
async function toolchainFingerprint(
|
||||
entrypoints: string[],
|
||||
schemaBundle: WorkspaceConfigSchemaBundle,
|
||||
): Promise<string> {
|
||||
return await digestText(JSON.stringify([
|
||||
2,
|
||||
"0.4.0",
|
||||
1,
|
||||
entrypoints,
|
||||
1,
|
||||
schemaBundle.fingerprint,
|
||||
]));
|
||||
}
|
||||
|
||||
const snapshot: ConfigTreeSnapshot = {
|
||||
revision: 4,
|
||||
digest: "sha256:test-tree",
|
||||
@@ -33,13 +56,15 @@ const snapshot: ConfigTreeSnapshot = {
|
||||
},
|
||||
};
|
||||
|
||||
const emptySchemaBundle = compose_schema_bundle([]) as WorkspaceConfigSchemaBundle;
|
||||
const contract: ToolchainContract = {
|
||||
contract_version: 1,
|
||||
decodal_version: "0.2.0",
|
||||
contract_version: 2,
|
||||
decodal_version: "0.4.0",
|
||||
schema_version: 1,
|
||||
entrypoints: ["workspace.dcdl"],
|
||||
import_policy_version: 1,
|
||||
fingerprint: "sha256:test-contract",
|
||||
schema_bundle: emptySchemaBundle,
|
||||
fingerprint: await toolchainFingerprint(["workspace.dcdl"], emptySchemaBundle),
|
||||
};
|
||||
|
||||
Deno.test("generated WASM evaluates the same virtual import contract", () => {
|
||||
@@ -65,3 +90,73 @@ Deno.test("generated WASM diagnostics carry snapshot provenance", () => {
|
||||
assertEquals(diagnostics[0].tree_digest, "sha256:test-tree");
|
||||
assertEquals(diagnostics[0].kind, "syntax");
|
||||
});
|
||||
|
||||
const featuresSchema = "{ features = {...{ enabled = Bool; }}; }";
|
||||
const webSchema = "{ web = { enabled = Bool; ...Unknown }; }";
|
||||
const schemaBundle = compose_schema_bundle([
|
||||
{
|
||||
provider_id: "builtin:features",
|
||||
namespace: "features",
|
||||
version: "1",
|
||||
source: featuresSchema,
|
||||
source_digest: await digestText(featuresSchema),
|
||||
},
|
||||
{
|
||||
provider_id: "builtin:web",
|
||||
namespace: "web",
|
||||
version: "1",
|
||||
source: webSchema,
|
||||
source_digest: await digestText(webSchema),
|
||||
},
|
||||
]) as WorkspaceConfigSchemaBundle;
|
||||
|
||||
function schemaSnapshot(source: string): ConfigTreeSnapshot {
|
||||
return {
|
||||
revision: 7,
|
||||
digest: "sha256:schema-tree",
|
||||
entries: {
|
||||
"main.dcdl": {
|
||||
path: "main.dcdl",
|
||||
content_type: "decodal",
|
||||
content: source,
|
||||
content_digest: "sha256:main",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const schemaContract: ToolchainContract = {
|
||||
contract_version: 2,
|
||||
decodal_version: "0.4.0",
|
||||
schema_version: 1,
|
||||
entrypoints: ["main.dcdl"],
|
||||
import_policy_version: 1,
|
||||
schema_bundle: schemaBundle,
|
||||
fingerprint: await toolchainFingerprint(["main.dcdl"], schemaBundle),
|
||||
};
|
||||
|
||||
Deno.test("generated WASM applies Decodal 0.4 typed maps and explicit object rest", () => {
|
||||
const result = evaluate_snapshot(
|
||||
schemaSnapshot(
|
||||
"{ features = { console = { enabled = true; }; }; web = { enabled = true; extension_value = 42; }; }",
|
||||
),
|
||||
schemaContract,
|
||||
) as { projections: Array<{ data_json: Record<string, unknown> }> };
|
||||
assertEquals(result.projections[0].data_json, {
|
||||
features: { console: { enabled: true } },
|
||||
web: { enabled: true, extension_value: 42 },
|
||||
});
|
||||
});
|
||||
|
||||
Deno.test("generated WASM rejects unknown root fields with source provenance", () => {
|
||||
let thrown: unknown;
|
||||
try {
|
||||
evaluate_snapshot(schemaSnapshot("{ features = {}; custom = 42; }"), schemaContract);
|
||||
} catch (error) {
|
||||
thrown = error;
|
||||
}
|
||||
const diagnostics = thrown as Array<{ path: string; kind: string }>;
|
||||
assertEquals(Array.isArray(diagnostics), true);
|
||||
assertEquals(diagnostics[0].path, "main.dcdl");
|
||||
assertEquals(diagnostics[0].kind, "constraintviolation");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user