52803dda05
Add kinded parameters, capability bounds, Self, aliases and closed application identities. Check generic implementations universally and build candidate-specific codecs and descriptors from immutable schemas. Preserve lexical aliases and exact dispatch identities in package and host bindings. Add an imperative CRUD+index domain scaffold with explicit soft-deletion semantics, source/codegen regression coverage, installed CLI tests and an authoring guide. Existing Web Studio opaque props and class-level create-menu migration are separate from the implemented language core.
106 lines
4.9 KiB
TypeScript
106 lines
4.9 KiB
TypeScript
import type { InterfaceRevision, ValueType } from "../capability-model/types.js";
|
|
|
|
/** Host clients have no package receiver, but must use the same checked
|
|
* interface signatures and argument framing as generated package ports. */
|
|
export const generateClientContracts = (
|
|
interfaces: InterfaceRevision[],
|
|
messages: Record<string, string>,
|
|
qualified = false,
|
|
) => {
|
|
if (interfaces.some((entry) => entry.template))
|
|
throw new Error("Host contracts require closed interface applications, not generic definitions");
|
|
const type = (value: ValueType): string => {
|
|
switch (value.kind) {
|
|
case "builtin":
|
|
return value.name === "unit" ? "undefined" : "string";
|
|
case "scalar":
|
|
return {
|
|
bool: "boolean",
|
|
string: "string",
|
|
bytes: "Uint8Array",
|
|
int32: "number",
|
|
uint32: "number",
|
|
double: "number",
|
|
int64: "bigint",
|
|
uint64: "bigint",
|
|
}[value.name];
|
|
case "object-ref":
|
|
return qualified
|
|
? `CapabilityReference<${JSON.stringify(value.expectation.kind === "atom" ? `atom:${value.expectation.atomId}` : `interface:${value.expectation.interfaceRevisionId}`)}>`
|
|
: `{readonly $quixosRef: string}`;
|
|
case "optional":
|
|
return `(${type(value.value)} | null)`;
|
|
case "list":
|
|
return `Array<${type(value.value)}>`;
|
|
case "record":
|
|
return `{${Object.entries(value.fields)
|
|
.map(([name, field]) => `${JSON.stringify(name)}${field.kind === "optional" ? "?" : ""}: ${type(field)}`)
|
|
.join("; ")}}`;
|
|
case "message": {
|
|
const binding = messages[value.descriptorId];
|
|
if (!binding) throw new Error(`Missing host message type ${value.descriptorId}`);
|
|
return binding;
|
|
}
|
|
}
|
|
};
|
|
const operations = interfaces.flatMap((iface) =>
|
|
iface.members.flatMap((member) =>
|
|
member.operations
|
|
.filter((operation) => operation.mode === "call")
|
|
.map((operation) => ({ ...operation, interfaceRevisionId: iface.revisionId })),
|
|
),
|
|
);
|
|
if (qualified) {
|
|
const contracts = interfaces.map((iface) => {
|
|
const members = iface.members.flatMap((member) =>
|
|
member.operations
|
|
.filter((op) => op.mode === "call")
|
|
.map((op) => ` ${JSON.stringify(op.id)}: {input: ${type(op.inputType)}; output: ${type(op.outputType)}};`),
|
|
);
|
|
return `${JSON.stringify(iface.revisionId)}: {\n${members.join("\n")}\n}`;
|
|
});
|
|
if (new Set(interfaces.map((iface) => iface.revisionId)).size !== interfaces.length)
|
|
throw new Error("Duplicate closed interface identity");
|
|
return (
|
|
`// Generated closed capability contracts. Dispatch by interface AND operation.\n` +
|
|
`declare const referenceType: unique symbol;\nexport type CapabilityReference<T extends string> = {readonly $quixosRef: string; readonly [referenceType]: T};\n` +
|
|
`export type CapabilityContracts = {${contracts.join(";\n")}};\n` +
|
|
`export type CapabilityInput<I extends keyof CapabilityContracts, O extends keyof CapabilityContracts[I]> = CapabilityContracts[I][O] extends {input: infer T} ? T : never;\n` +
|
|
`export type CapabilityOutput<I extends keyof CapabilityContracts, O extends keyof CapabilityContracts[I]> = CapabilityContracts[I][O] extends {output: infer T} ? T : never;\n` +
|
|
`export const capabilityApplications = ${JSON.stringify(Object.fromEntries(interfaces.map((iface) => [iface.revisionId, { definitionId: iface.application?.definitionId ?? iface.revisionId, arguments: iface.application?.arguments ?? [], ...(iface.application?.self ? { self: iface.application.self } : {}) }])))} as const;\n`
|
|
);
|
|
}
|
|
if (new Set(operations.map((entry) => entry.id)).size !== operations.length)
|
|
throw new Error(
|
|
"Host operation IDs are ambiguous across interfaces; select a closed interface application explicitly",
|
|
);
|
|
return (
|
|
`// Generated from checked QX interfaces. Regenerate with scripts/generate-platform-contracts.mjs.\n` +
|
|
`export type PlatformInputs = {\n${operations.map((operation) => ` ${JSON.stringify(operation.id)}: ${type(operation.inputType)};`).join("\n")}\n};\n` +
|
|
`export const platformOperations = ${JSON.stringify(
|
|
Object.fromEntries(
|
|
operations.map((operation) => [
|
|
operation.id,
|
|
{
|
|
interfaceRevisionId: operation.interfaceRevisionId,
|
|
input:
|
|
operation.inputType.kind === "builtin" && operation.inputType.name === "unit"
|
|
? "unit"
|
|
: ["record", "message"].includes(operation.inputType.kind)
|
|
? "fields"
|
|
: "value",
|
|
},
|
|
]),
|
|
),
|
|
null,
|
|
2,
|
|
)} as const;\n`
|
|
);
|
|
};
|
|
|
|
/** New consumers use exact closed interface identities; operation IDs alone are not unique. */
|
|
export const generateAppliedClientContracts = (
|
|
interfaces: InterfaceRevision[],
|
|
messages: Record<string, string> = {},
|
|
) => generateClientContracts(interfaces, messages, true);
|