2026-09-15 15:23:24 -07:00
|
|
|
import type { InterfaceRevision, ValueType } from "../capability-model/types.js";
|
2026-09-14 15:24:37 -07:00
|
|
|
|
|
|
|
|
/** 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>) => {
|
|
|
|
|
const type = (value: ValueType): string => {
|
|
|
|
|
switch (value.kind) {
|
2026-09-15 15:23:24 -07:00
|
|
|
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 `{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("; ")}}`;
|
2026-09-14 15:24:37 -07:00
|
|
|
case "message": {
|
|
|
|
|
const binding = messages[value.descriptorId];
|
|
|
|
|
if (!binding) throw new Error(`Missing host message type ${value.descriptorId}`);
|
|
|
|
|
return binding;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-09-15 15:23:24 -07:00
|
|
|
const operations = interfaces.flatMap((iface) =>
|
|
|
|
|
iface.members.flatMap((member) =>
|
|
|
|
|
member.operations
|
|
|
|
|
.filter((operation) => operation.mode === "call")
|
|
|
|
|
.map((operation) => ({ ...operation, interfaceRevisionId: iface.revisionId })),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
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`
|
|
|
|
|
);
|
2026-09-14 15:24:37 -07:00
|
|
|
};
|