Generate typed asynchronous service capability bindings
This commit is contained in:
+20
-9
@@ -1,6 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { readFile, writeFile } from "node:fs/promises";
|
import { readFile, writeFile } from "node:fs/promises";
|
||||||
import { generateTypeScriptBindings, generatePortableTypeScriptBindings } from "./index.js";
|
import {
|
||||||
|
generateTypeScriptBindings,
|
||||||
|
generatePortableTypeScriptBindings,
|
||||||
|
generateRemoteTypeScriptBindings,
|
||||||
|
} from "./index.js";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { reactPlatformTypes } from "./react-platform.js";
|
import { reactPlatformTypes } from "./react-platform.js";
|
||||||
import { generateReactBindings } from "./react.js";
|
import { generateReactBindings } from "./react.js";
|
||||||
@@ -11,16 +15,23 @@ const main = async () => {
|
|||||||
throw new Error("usage: quixos-codegen-ts SCHEMA.json PACKAGE_REVISION OUTPUT.ts [OPTIONS.json]");
|
throw new Error("usage: quixos-codegen-ts SCHEMA.json PACKAGE_REVISION OUTPUT.ts [OPTIONS.json]");
|
||||||
const config = options ? JSON.parse(await readFile(options, "utf8")) : {};
|
const config = options ? JSON.parse(await readFile(options, "utf8")) : {};
|
||||||
const contracts = JSON.parse(await readFile(schema, "utf8"));
|
const contracts = JSON.parse(await readFile(schema, "utf8"));
|
||||||
if (config.portable && config.react)
|
if ([config.portable, config.remote, config.react].filter(Boolean).length > 1)
|
||||||
throw new Error("Portable handlers and React component bindings are separate artifacts");
|
throw new Error("Portable, remote and React bindings are separate artifacts");
|
||||||
|
if (config.remote !== undefined && config.remote !== true) throw new Error("Remote binding mode must be true");
|
||||||
if (config.portable && typeof config.portable.target !== "string")
|
if (config.portable && typeof config.portable.target !== "string")
|
||||||
throw new Error("Portable bindings require an explicit target");
|
throw new Error("Portable bindings require an explicit target");
|
||||||
const generated = config.portable
|
const generated = config.remote
|
||||||
? generatePortableTypeScriptBindings(contracts, revision, {
|
? generateRemoteTypeScriptBindings(
|
||||||
...config.portable,
|
contracts,
|
||||||
...(config.runtimeModule ? { runtimeModule: config.runtimeModule } : {}),
|
revision,
|
||||||
})
|
config.runtimeModule ? { runtimeModule: config.runtimeModule } : {},
|
||||||
: generateTypeScriptBindings(contracts, revision, config);
|
)
|
||||||
|
: config.portable
|
||||||
|
? generatePortableTypeScriptBindings(contracts, revision, {
|
||||||
|
...config.portable,
|
||||||
|
...(config.runtimeModule ? { runtimeModule: config.runtimeModule } : {}),
|
||||||
|
})
|
||||||
|
: generateTypeScriptBindings(contracts, revision, config);
|
||||||
await writeFile(output, generated);
|
await writeFile(output, generated);
|
||||||
if (config.react) {
|
if (config.react) {
|
||||||
await writeFile(path.join(path.dirname(output), "web-studio-react-runtime.d.ts"), reactPlatformTypes);
|
await writeFile(path.join(path.dirname(output), "web-studio-react-runtime.d.ts"), reactPlatformTypes);
|
||||||
|
|||||||
@@ -424,6 +424,7 @@ export const generateTypeScriptBindings = (
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
generatePortableTypeScriptBindings,
|
generatePortableTypeScriptBindings,
|
||||||
|
generateRemoteTypeScriptBindings,
|
||||||
portableValueSchema,
|
portableValueSchema,
|
||||||
PortableBindingError,
|
PortableBindingError,
|
||||||
type PortableBindingOptions,
|
type PortableBindingOptions,
|
||||||
|
|||||||
+37
-11
@@ -104,7 +104,13 @@ interface Method {
|
|||||||
output: ValueType;
|
output: ValueType;
|
||||||
keyed: boolean;
|
keyed: boolean;
|
||||||
}
|
}
|
||||||
function methods(schema: BindingSchema, entry: PackageExport, port: DependencyPort, target: string): Method[] {
|
function methods(
|
||||||
|
schema: BindingSchema,
|
||||||
|
entry: PackageExport,
|
||||||
|
port: DependencyPort,
|
||||||
|
target: string,
|
||||||
|
remote = false,
|
||||||
|
): Method[] {
|
||||||
const req = port.requirement;
|
const req = port.requirement;
|
||||||
const path = `${entry.displayName}.ports.${port.displayName}`;
|
const path = `${entry.displayName}.ports.${port.displayName}`;
|
||||||
const method = (
|
const method = (
|
||||||
@@ -115,6 +121,8 @@ function methods(schema: BindingSchema, entry: PackageExport, port: DependencyPo
|
|||||||
keyed = false,
|
keyed = false,
|
||||||
): Method => ({ name, portId: port.id, portName: port.displayName, action, input, output, keyed });
|
): Method => ({ name, portId: port.id, portName: port.displayName, action, input, output, keyed });
|
||||||
const writable = () => {
|
const writable = () => {
|
||||||
|
if (remote)
|
||||||
|
throw new PortableBindingError(path, "Remote writes and creation require a named transaction capability");
|
||||||
if (entry.execution?.kind !== "transaction")
|
if (entry.execution?.kind !== "transaction")
|
||||||
throw new PortableBindingError(path, "Snapshot context cannot expose mutation ports");
|
throw new PortableBindingError(path, "Snapshot context cannot expose mutation ports");
|
||||||
};
|
};
|
||||||
@@ -166,8 +174,7 @@ function methods(schema: BindingSchema, entry: PackageExport, port: DependencyPo
|
|||||||
.map(({ member, op }) => {
|
.map(({ member, op }) => {
|
||||||
const execution: ExecutionContract = op.execution ?? { kind: "remote" };
|
const execution: ExecutionContract = op.execution ?? { kind: "remote" };
|
||||||
if (
|
if (
|
||||||
execution.kind === "remote" ||
|
(execution.kind === "remote" ? !remote : !execution.targets.includes(target)) ||
|
||||||
!execution.targets.includes(target) ||
|
|
||||||
op.mode !== "call" ||
|
op.mode !== "call" ||
|
||||||
op.scope === "class"
|
op.scope === "class"
|
||||||
)
|
)
|
||||||
@@ -175,7 +182,7 @@ function methods(schema: BindingSchema, entry: PackageExport, port: DependencyPo
|
|||||||
`${path}.${member.displayName}.${op.displayName}`,
|
`${path}.${member.displayName}.${op.displayName}`,
|
||||||
`Selected operation is not an object call available in portable target ${target}; select a portable subset or move this handler to remote execution`,
|
`Selected operation is not an object call available in portable target ${target}; select a portable subset or move this handler to remote execution`,
|
||||||
);
|
);
|
||||||
if (execution.kind === "transaction") writable();
|
if (execution.kind === "transaction" && !remote) writable();
|
||||||
return method(
|
return method(
|
||||||
`${member.displayName}.${op.displayName}`,
|
`${member.displayName}.${op.displayName}`,
|
||||||
{ kind: "interface", operationId: op.id },
|
{ kind: "interface", operationId: op.id },
|
||||||
@@ -199,10 +206,28 @@ export function generatePortableTypeScriptBindings(
|
|||||||
revision: string,
|
revision: string,
|
||||||
options: PortableBindingOptions,
|
options: PortableBindingOptions,
|
||||||
): string {
|
): string {
|
||||||
|
return generateBindings(schema, revision, options, false);
|
||||||
|
}
|
||||||
|
export function generateRemoteTypeScriptBindings(
|
||||||
|
schema: BindingSchema,
|
||||||
|
revision: string,
|
||||||
|
options: { runtimeModule?: string } = {},
|
||||||
|
): string {
|
||||||
|
return generateBindings(schema, revision, { ...options, target: "server" }, true);
|
||||||
|
}
|
||||||
|
function generateBindings(
|
||||||
|
schema: BindingSchema,
|
||||||
|
revision: string,
|
||||||
|
options: PortableBindingOptions,
|
||||||
|
remote: boolean,
|
||||||
|
): string {
|
||||||
|
const mode = remote ? "Remote" : "Portable";
|
||||||
const pkg = schema.packages.find((p) => p.revisionId === revision);
|
const pkg = schema.packages.find((p) => p.revisionId === revision);
|
||||||
if (!pkg) throw new PortableBindingError(revision, "Missing package");
|
if (!pkg) throw new PortableBindingError(revision, "Missing package");
|
||||||
const entries = pkg.exports.filter(
|
const entries = pkg.exports.filter((e) =>
|
||||||
(e) => e.execution && e.execution.kind !== "remote" && e.execution.targets.includes(options.target),
|
remote
|
||||||
|
? !e.execution || e.execution.kind === "remote"
|
||||||
|
: e.execution && e.execution.kind !== "remote" && e.execution.targets.includes(options.target),
|
||||||
);
|
);
|
||||||
const names = new Set<string>();
|
const names = new Set<string>();
|
||||||
const contexts: string[] = [],
|
const contexts: string[] = [],
|
||||||
@@ -210,6 +235,7 @@ export function generatePortableTypeScriptBindings(
|
|||||||
adapters: string[] = [],
|
adapters: string[] = [],
|
||||||
descriptors: unknown[] = [];
|
descriptors: unknown[] = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
|
const effect = entry.execution?.kind ?? "remote";
|
||||||
// The checked binding schema already contains concrete generic applications.
|
// The checked binding schema already contains concrete generic applications.
|
||||||
// Each has its own identity, value codecs and specialized dependency ports.
|
// Each has its own identity, value codecs and specialized dependency ports.
|
||||||
if (entry.kind === "constructor")
|
if (entry.kind === "constructor")
|
||||||
@@ -223,12 +249,12 @@ export function generatePortableTypeScriptBindings(
|
|||||||
if (portNames.has(port.displayName))
|
if (portNames.has(port.displayName))
|
||||||
throw new PortableBindingError(entry.id, `Duplicate port name ${port.displayName}`);
|
throw new PortableBindingError(entry.id, `Duplicate port name ${port.displayName}`);
|
||||||
portNames.add(port.displayName);
|
portNames.add(port.displayName);
|
||||||
const selected = methods(schema, entry, port, options.target);
|
const selected = methods(schema, entry, port, options.target, remote);
|
||||||
if (new Set(selected.map((m) => m.name)).size !== selected.length)
|
if (new Set(selected.map((m) => m.name)).size !== selected.length)
|
||||||
throw new PortableBindingError(port.id, "Duplicate method name");
|
throw new PortableBindingError(port.id, "Duplicate method name");
|
||||||
all.push(...selected);
|
all.push(...selected);
|
||||||
facets.push(
|
facets.push(
|
||||||
`readonly ${q(port.displayName)}: { ${selected.map((m) => `readonly ${q(m.name)}: (${[...(isUnit(m.input) ? [] : [`input: ${tsType(m.input)}`]), ...(m.keyed ? ["options: MutationKey"] : [])].join(", ")}) => Promise<${tsType(m.output)}>`).join("; ")} }`,
|
`readonly ${q(port.displayName)}: { ${selected.map((m) => `readonly ${q(m.name)}: (${[...(isUnit(m.input) ? [] : [`input: ${tsType(m.input)}`]), ...(m.keyed ? [remote ? "options: ServerMutationOptions" : "options: MutationKey"] : [])].join(", ")}) => Promise<${tsType(m.output)}>`).join("; ")} }`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const receiver =
|
const receiver =
|
||||||
@@ -244,7 +270,7 @@ export function generatePortableTypeScriptBindings(
|
|||||||
? "ExactReference"
|
? "ExactReference"
|
||||||
: `ManagedReference<"atom", ${q(receiver.id)}>`;
|
: `ManagedReference<"atom", ${q(receiver.id)}>`;
|
||||||
contexts.push(
|
contexts.push(
|
||||||
`readonly ${q(entry.displayName)}: { readonly effect: ${q(entry.execution!.kind)}; readonly input: ${tsType(entry.inputType)}; readonly receiver: ${receiverType}; readonly ports: { ${facets.join("; ")} } }`,
|
`readonly ${q(entry.displayName)}: { readonly effect: ${q(effect)}; ${remote ? "readonly signal: AbortSignal;" : ""} readonly input: ${tsType(entry.inputType)}; readonly receiver: ${receiverType}; readonly ports: { ${facets.join("; ")} } }`,
|
||||||
);
|
);
|
||||||
const output = tsType(entry.outputType);
|
const output = tsType(entry.outputType);
|
||||||
implementations.push(
|
implementations.push(
|
||||||
@@ -256,12 +282,12 @@ export function generatePortableTypeScriptBindings(
|
|||||||
descriptors.push({
|
descriptors.push({
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
name: entry.displayName,
|
name: entry.displayName,
|
||||||
effect: entry.execution!.kind,
|
effect,
|
||||||
receiver,
|
receiver,
|
||||||
input: portableValueSchema(entry.inputType),
|
input: portableValueSchema(entry.inputType),
|
||||||
output: portableValueSchema(entry.outputType),
|
output: portableValueSchema(entry.outputType),
|
||||||
methods: all.map((m) => ({ ...m, input: portableValueSchema(m.input), output: portableValueSchema(m.output) })),
|
methods: all.map((m) => ({ ...m, input: portableValueSchema(m.input), output: portableValueSchema(m.output) })),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return `// Generated portable bindings. Regenerate from the checked capability schema; do not edit.\nimport { definePortableRegistry, type PortableExport, type PreparedChange, type ManagedReference, type ExactReference, type MutationKey } from ${q(options.runtimeModule ?? "@quixos/camino-replica-core")};\nexport const bindingDigest = ${q(hexDigest(schema))};\nexport const packageRevision = ${q(revision)};\nexport const target = ${q(options.target)};\nexport type Contexts = { ${contexts.join(";\n")} };\nexport type PortableImplementation = { ${implementations.join(";\n")} };\nexport const declarations = ${JSON.stringify(descriptors, null, 2)} as const satisfies readonly PortableExport[];\nexport function createRegistry(handlers: PortableImplementation) { const installed = { ...handlers }; return definePortableRegistry(declarations, { ${adapters.join(",\n")} }); }\n`;
|
return `// Generated ${remote ? "remote" : "portable"} bindings. Regenerate from the checked capability schema; do not edit.\nimport { define${mode}Registry, type ${mode}Export, type PreparedChange, type ManagedReference, type ExactReference, type ${remote ? "ServerMutationOptions" : "MutationKey"} } from ${q(options.runtimeModule ?? "@quixos/camino-replica-core")};\nexport const bindingDigest = ${q(hexDigest(schema))};\nexport const packageRevision = ${q(revision)};\nexport const target = ${q(options.target)};\nexport type Contexts = { ${contexts.join(";\n")} };\nexport type ${mode}Implementation = { ${implementations.join(";\n")} };\nexport const declarations = ${JSON.stringify(descriptors, null, 2)} as const satisfies readonly ${mode}Export[];\nexport function createRegistry(handlers: ${mode}Implementation) { const installed = { ...handlers }; return define${mode}Registry(declarations, { ${adapters.join(",\n")} }); }\n`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export * from "./files.js";
|
|||||||
|
|
||||||
export * from "./receipts.js";
|
export * from "./receipts.js";
|
||||||
|
|
||||||
export { generatePortableTypeScriptBindings } from "../bindings/portable.js";
|
export { generatePortableTypeScriptBindings, generateRemoteTypeScriptBindings } from "../bindings/portable.js";
|
||||||
|
|
||||||
export * from "./kernel.js";
|
export * from "./kernel.js";
|
||||||
export * from "./projections.js";
|
export * from "./projections.js";
|
||||||
|
|||||||
@@ -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;
|
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