Build checked component artifacts and shared React session SDK (WI-09)

This commit is contained in:
Timothy J. Aveni
2026-09-21 16:01:48 -07:00
parent 24073d7f2a
commit 2cf53280e4
9 changed files with 104 additions and 5 deletions
+56 -1
View File
@@ -127,7 +127,62 @@ export function generatePackageQueryClientBindings(
): string {
const queries = (pkg.checkedQueries ?? []).map(
(query) =>
`${JSON.stringify(query.declaration.displayName)}: definePackageQuery<${tsType(query.variables)}, ${tsType(query.output)}>(${JSON.stringify(publicQueryContract(pkg, query.declaration.id))})`,
`${JSON.stringify(query.declaration.displayName)}: definePackageQuery<${tsType(query.variables)}, ${tsType(query.output)}, ${JSON.stringify(publicQueryContract(pkg, query.declaration.id).placement)}>(${JSON.stringify(publicQueryContract(pkg, query.declaration.id))})`,
);
return `// Generated from checked package query contracts. Regenerate; do not edit.\nimport { definePackageQuery, type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const queries = {${queries.join(",\n")}} as const;\n`;
}
/** Receipt-covered bindings expose the package's imports, component subjects and
* declared queries, never the workspace's complete atom/interface inventory. */
export function generateComponentClientBindings(plan: ExecutionPlan, packageRevision: string): string {
const pkg = plan.definition.workspace.packageImports.find((pkg) => pkg.revisionId === packageRevision);
if (!pkg || !pkg.components?.length)
throw new ExecutionPlanError("NO_COMPONENT_CONTRACT", packageRevision, "Package declares no component entries");
const byId = new Map(plan.definition.workspace.interfaceImports.map((iface) => [iface.revisionId as string, iface]));
const find = (id: string) => {
const contract = byId.get(id);
if (!contract || contract.template)
throw new ExecutionPlanError("OPEN_CLIENT_CONTRACT", id, "Component contracts must be closed");
return contract;
};
const imports = Object.fromEntries(
Object.entries(pkg.clientInterfaces ?? {}).map(([alias, id]) => [alias, find(id)]),
);
const subjects = Object.fromEntries(
pkg.components.map((component) => [component.displayName, find(component.subject)]),
);
const interfaces = generateInterfaceClientBindings(imports, "@quixos/camino-react");
const subjectBindings = generateInterfaceClientBindings(subjects, "@quixos/camino-react")
.split("\n")
.filter((line) => !line.startsWith("import "))
.join("\n")
.replace("export const interfaces =", "export const subjects =");
const queries = generatePackageQueryClientBindings(pkg, "@quixos/camino-react")
.split("\n")
.filter((line) => !line.startsWith("import "))
.join("\n");
return (
interfaces +
'\nimport {definePackageQuery,type ComponentType,type ComponentProps} from "@quixos/camino-react";\n' +
subjectBindings +
"\n" +
queries +
"\nexport interface Components {\n" +
pkg.components
.map(
(component) =>
`${JSON.stringify(component.displayName)}: ComponentType<ComponentProps<${JSON.stringify(component.subject)}>>;`,
)
.join("\n") +
"\n}\n"
);
}
export function componentAssetDeclarations(): string {
return (
"// Generated component asset modules. Regenerate; do not edit.\n" +
["svg", "png", "jpg", "jpeg", "gif", "webp", "avif", "woff", "woff2", "ttf", "ico"]
.map((extension) => `declare module "*.${extension}" { const value: string; export default value; }`)
.join("\n") +
'\ndeclare module "*.css";\n'
);
}