111 lines
5.1 KiB
TypeScript
111 lines
5.1 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import {
|
|
compileCapabilityResourceSource,
|
|
compileCapabilitySource,
|
|
formatQx,
|
|
} from "../src/capability-language/index.js";
|
|
import { compileExecutionPlan } from "../src/execution-world/plan.js";
|
|
import { compileKernelProgram } from "../src/execution-world/kernel.js";
|
|
import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js";
|
|
const source = { repository: "https://example.test/keyed.git", commit: "a".repeat(40) };
|
|
const iface = compileCapabilityResourceSource(
|
|
`interface Index<object Item> id "index" revision "index@1" {
|
|
queryable relation entries id "entries" : many object Item keyed "string" {
|
|
resolve id "resolve" execution snapshot targets [browser,server];
|
|
connect id "connect" execution transaction targets [browser,server];
|
|
}
|
|
operation add id "add" : record {mapKey:string;target:ref<Item>;} -> unit {
|
|
call id "add-call" execution transaction targets [browser,server];
|
|
}
|
|
}`,
|
|
{ source },
|
|
);
|
|
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
|
|
const pkg = compileCapabilityResourceSource(
|
|
`package Runtime id "runtime" revision "runtime@1" {
|
|
operation add<object Item> id "add" : record {mapKey:string;target:ref<Item>;} -> unit mode call execution transaction targets [browser,server] receiver any requires {
|
|
edge entries id "entries" : many object Item keyed "string" [resolve,connect];
|
|
};
|
|
}`,
|
|
{ source },
|
|
);
|
|
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
|
const indexContract = iface.resource.revision,
|
|
runtimeContract = pkg.resource.revision;
|
|
const declarations = `workspace W id "w" revision "w@1" commit "${source.commit}" {
|
|
import interface Index; import package Runtime; atom Node id "node";
|
|
conform Node as Index<atom Node> id "node:index" {
|
|
private edge Entries id "entries" {
|
|
atom Node projection entries id "entries" many keyed "string";
|
|
atom Node projection parent id "parent" optional-one;
|
|
}
|
|
bind entries.resolve to edge Entries.entries.resolve;
|
|
bind entries.connect to edge Entries.entries.connect;
|
|
bind add.call to package Runtime.add<atom Node> with {entries to edge Entries.entries;};
|
|
}
|
|
}`;
|
|
const compile = (text = declarations) =>
|
|
compileCapabilitySource(formatQx(text), "workspace.qx", {
|
|
interfaces: new Map([["Index", indexContract]]),
|
|
packages: new Map([["Runtime", runtimeContract]]),
|
|
});
|
|
test("generic keyed relationships and portable ports have matching checked shapes", () => {
|
|
const result = compile();
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
const closed = result.workspace.interfaceImports.find((i) => i.application)!;
|
|
assert.equal(closed.members[0]!.kind, "relationship");
|
|
assert.equal("keyType" in closed.members[0]! && closed.members[0]!.keyType, "string");
|
|
const operation = closed.members[0]!.operations.find((o) => o.displayName === "connect")!;
|
|
assert.equal(operation.inputType.kind, "record");
|
|
const plan = compileExecutionPlan(result.workspace);
|
|
assert.doesNotThrow(() => compileKernelProgram(plan));
|
|
const bindings = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", {
|
|
target: "browser",
|
|
});
|
|
assert.match(bindings, /readonly "mapKey": string/);
|
|
assert.match(bindings, /readonly "connect": \(input: \{ readonly "mapKey": string;/);
|
|
});
|
|
test("key contracts cannot silently bind to unkeyed storage or the inverse projection", () => {
|
|
for (const text of [
|
|
declarations.replace('many keyed "string";', "many;"),
|
|
declarations.replace("entries to edge Entries.entries", "entries to edge Entries.parent"),
|
|
]) {
|
|
const result = compile(text);
|
|
assert.equal(result.ok, false);
|
|
assert.match(JSON.stringify(result.diagnostics), /key type|keyed projection|signature/i);
|
|
}
|
|
});
|
|
|
|
test("checked shared edges lower without granting conformance initialization ownership", () => {
|
|
const edge = declarations.match(/ private edge Entries[\s\S]*?\n }/)![0];
|
|
const shared = declarations
|
|
.replace(edge, "")
|
|
.replace(" conform Node as", edge.replace("private edge", "shared edge") + "\n conform Node as");
|
|
const result = compile(shared);
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
const program = compileKernelProgram(compileExecutionPlan(result.workspace)) as {
|
|
model: { edges: { id: string; owner: string | null }[] };
|
|
};
|
|
assert.deepEqual(
|
|
program.model.edges.map((edge) => ({ id: edge.id, owner: edge.owner })),
|
|
[{ id: "entries", owner: null }],
|
|
);
|
|
const seeded = compile(
|
|
shared.replace(
|
|
" bind entries.resolve",
|
|
" initialize { initial to edge Entries.entries; };\n bind entries.resolve",
|
|
),
|
|
);
|
|
assert.ok(!seeded.ok);
|
|
assert.ok(seeded.diagnostics.some((issue) => issue.code === "invalid-initialization"));
|
|
const scalar = compile(
|
|
shared.replace(
|
|
' atom Node id "node";',
|
|
' atom Node id "node"; shared state Shared id "shared" on Node : string policy register default "";',
|
|
),
|
|
);
|
|
assert.ok(scalar.ok, JSON.stringify(scalar.diagnostics));
|
|
assert.throws(() => compileKernelProgram(compileExecutionPlan(scalar.workspace)), /UNSUPPORTED_SHARED_STORAGE/);
|
|
});
|