Generate typed asynchronous service capability bindings
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js";
|
||||
import { compileExecutionPlan } from "../src/execution-world/plan.js";
|
||||
import type { InterfaceRevision, PackageRevision } from "../src/capability-model/types.js";
|
||||
export async function asyncObjectPlan() {
|
||||
const read = (name: string) => readFile(`test/fixtures/async-objects/${name}.qx`, "utf8");
|
||||
const source = { repository: "https://example.test/async.git", commit: "a".repeat(40) };
|
||||
const interfaces = new Map<string, InterfaceRevision>();
|
||||
const packages = new Map<string, PackageRevision>();
|
||||
for (const name of ["DocumentData", "SummaryController", "Summarizer"]) {
|
||||
const result = compileCapabilityResourceSource(await read(name), { source });
|
||||
assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics));
|
||||
interfaces.set(name, result.resource.revision);
|
||||
}
|
||||
const environment = { interfaces, interfaceClosure: [...interfaces.values()] };
|
||||
for (const name of ["ControllerRuntime", "ProviderRuntime"]) {
|
||||
const result = compileCapabilityResourceSource(await read(name), { source, environment });
|
||||
assert.ok(result.ok && result.resource.kind === "package", JSON.stringify(result.diagnostics));
|
||||
packages.set(name, result.resource.revision);
|
||||
}
|
||||
const result = compileCapabilitySource(await read("workspace"), "workspace.qx", { ...environment, packages });
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
return compileExecutionPlan(result.workspace);
|
||||
}
|
||||
@@ -87,3 +87,18 @@ const negative: PortableImplementation = {
|
||||
void negative;
|
||||
`,
|
||||
);
|
||||
|
||||
const { asyncObjectPlan } = await import("./async-fixture.js");
|
||||
const { generateRemoteTypeScriptBindings } = await import("../src/bindings/portable.js");
|
||||
const asyncPlan = await asyncObjectPlan();
|
||||
await mkdir("dist/portable-reader/async", { recursive: true });
|
||||
await writeFile("dist/portable-reader/async/plan.json", JSON.stringify(asyncPlan));
|
||||
await writeFile("dist/portable-reader/async/kernel.json", JSON.stringify(compileKernelProgram(asyncPlan)));
|
||||
for (const [name, revision] of [
|
||||
["controller", "async:controller-runtime@1"],
|
||||
["provider", "async:provider-runtime@2"],
|
||||
] as const)
|
||||
await writeFile(
|
||||
`dist/portable-reader/async/${name}.ts`,
|
||||
generateRemoteTypeScriptBindings(asyncPlan.definition.bindingSchema, revision),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
external atom Document id "async:document";
|
||||
import interface DocumentData;
|
||||
import interface Summarizer;
|
||||
package ControllerRuntime id "async:controller-runtime" revision "async:controller-runtime@1" {
|
||||
operation refresh id "async:controller:refresh" : record { operationId: string; } -> string
|
||||
mode call execution remote receiver atom Document requires {
|
||||
interface document id "async:controller:document" : DocumentData using [content.get, summary.set];
|
||||
interface provider id "async:controller:provider" : Summarizer using [summarize.call];
|
||||
};
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
type Summary = record { text: string; basedOn: string; };
|
||||
interface DocumentData id "async:document-data" revision "async:document-data@1" {
|
||||
queryable value content id "async:content" : string {
|
||||
get id "async:content:get" execution snapshot targets [browser, server];
|
||||
}
|
||||
queryable value summary id "async:summary" : Summary {
|
||||
get id "async:summary:get" execution snapshot targets [browser, server];
|
||||
set id "async:summary:set" execution transaction targets [browser, server];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
external atom Provider id "async:provider";
|
||||
package ProviderRuntime id "async:provider-runtime" revision "async:provider-runtime@2" {
|
||||
operation summarize id "async:provider:summarize" : string -> string
|
||||
mode call execution remote receiver atom Provider;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
interface Summarizer id "async:summarizer" revision "async:summarizer@1" {
|
||||
operation summarize id "async:summarize" : string -> string {
|
||||
call id "async:summarize:call" execution remote;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
interface SummaryController id "async:controller" revision "async:controller@1" {
|
||||
operation refresh id "async:refresh" : record { operationId: string; } -> string {
|
||||
call id "async:refresh:call" execution remote;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
workspace AsyncObjects id "workspace:async" revision "workspace:async@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||
import interface DocumentData;
|
||||
import interface SummaryController;
|
||||
import interface Summarizer;
|
||||
import package ControllerRuntime;
|
||||
import package ProviderRuntime;
|
||||
atom Document id "async:document";
|
||||
atom Provider id "async:provider";
|
||||
conform Document as DocumentData id "async:document:data" {
|
||||
private state Content id "async:state:content" on Document : string policy register;
|
||||
private state Summary id "async:state:summary" on Document : record { text: string; basedOn: string; }
|
||||
policy register default { "text": "", "basedOn": "" };
|
||||
private edge ProviderLink id "async:provider-link" {
|
||||
atom Document projection provider id "async:provider-projection" exactly-one public-traversal;
|
||||
atom Provider projection documents id "async:documents-projection" many-unique;
|
||||
}
|
||||
initialize { content to state Content; provider to edge ProviderLink.provider; };
|
||||
bind content.get to state Content.read;
|
||||
bind summary.get to state Summary.read;
|
||||
bind summary.set to state Summary.write;
|
||||
}
|
||||
conform Document as SummaryController id "async:document:controller" {
|
||||
bind refresh.call to package ControllerRuntime.refresh with {
|
||||
document to interface DocumentData;
|
||||
provider to interface Summarizer via edge ProviderLink.provider;
|
||||
};
|
||||
}
|
||||
conform Provider as Summarizer id "async:provider:summarizer" {
|
||||
bind summarize.call to package ProviderRuntime.summarize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { asyncObjectPlan } from "./async-fixture.js";
|
||||
import { generateRemoteTypeScriptBindings, generatePortableTypeScriptBindings } from "../src/bindings/portable.js";
|
||||
test("remote adapters expose selected typed calls, lifetimes and explicit transaction identities", async () => {
|
||||
const plan = await asyncObjectPlan();
|
||||
const schema = plan.definition.bindingSchema;
|
||||
const source = generateRemoteTypeScriptBindings(schema, "async:controller-runtime@1");
|
||||
assert.match(source, /readonly signal: AbortSignal/);
|
||||
assert.match(source, /readonly "summary.set":.*options: ServerMutationOptions/);
|
||||
assert.match(source, /readonly "summarize.call": \(input: string\) => Promise<string>/);
|
||||
assert.doesNotMatch(source, /readonly "summary.get"|context\.conform|readonly snapshot:/);
|
||||
assert.doesNotMatch(
|
||||
generatePortableTypeScriptBindings(schema, "async:controller-runtime@1", { target: "browser" }),
|
||||
/readonly "refresh"/,
|
||||
);
|
||||
const entry = schema.packages.find((p) => p.revisionId === "async:controller-runtime@1")!.exports[0]!;
|
||||
entry.dependencyPorts[0]!.requirement = {
|
||||
kind: "state",
|
||||
valueType: { kind: "scalar", name: "string" },
|
||||
primitives: ["write"],
|
||||
};
|
||||
assert.throws(
|
||||
() => generateRemoteTypeScriptBindings(schema, "async:controller-runtime@1"),
|
||||
/named transaction capability/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user