Implement capability generics, checked package specializations and CRUD scaffolding
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.
This commit is contained in:
+40
-2
@@ -2,7 +2,13 @@ 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>) => {
|
||||
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":
|
||||
@@ -19,7 +25,9 @@ export const generateClientContracts = (interfaces: InterfaceRevision[], message
|
||||
uint64: "bigint",
|
||||
}[value.name];
|
||||
case "object-ref":
|
||||
return `{readonly $quixosRef: string}`;
|
||||
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":
|
||||
@@ -42,6 +50,30 @@ export const generateClientContracts = (interfaces: InterfaceRevision[], message
|
||||
.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` +
|
||||
@@ -65,3 +97,9 @@ export const generateClientContracts = (interfaces: InterfaceRevision[], message
|
||||
)} 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);
|
||||
|
||||
Reference in New Issue
Block a user