Compare commits

...

10 Commits

Author SHA1 Message Date
Quixos Subtree Publisher a83c43f9d9 Publish camino-package-runtime from 2acad11c4bcae2bbde2180327d45d5db9cffc9ed 2026-07-18 19:11:20 +00:00
Timothy J. Aveni 8fc1060b65 Publish browser method runtime declaration 2026-07-18 12:11:20 -07:00
Quixos Subtree Publisher cad6057316 Publish camino-package-runtime from ee693da2ca152011d8bddf205cd5d44488335ea1 2026-07-18 19:10:00 +00:00
Timothy J. Aveni b1d0667045 Add schema-driven project task creation 2026-07-18 12:10:00 -07:00
Quixos Subtree Publisher 1df06f4be7 Publish camino-package-runtime from 18c19a5b893e6196a357de9a3c52636b3ce9e33e 2026-07-18 17:35:48 +00:00
Timothy J. Aveni 9674ffa108 Publish updated Camino codegen artifacts 2026-07-18 10:35:48 -07:00
Quixos Subtree Publisher bd684cd07d Publish camino-package-runtime from 9f6f36f9932aa1f938eb507c5ed372200dee0096 2026-07-18 17:33:55 +00:00
Timothy J. Aveni 02e2313aa9 Generate typed React component bindings 2026-07-18 10:33:55 -07:00
Quixos Subtree Publisher 765c321f8a Publish camino-package-runtime from 51607ba09c066e59a5c80a4b6cce46eb61fda501 2026-07-15 14:54:17 +00:00
Timothy J. Aveni df4946e016 Teach package runtime edge materialization 2026-07-15 07:54:17 -07:00
20 changed files with 878 additions and 92 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git", "sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git",
"sourceCommit": "24cede02e95c62c065d5e573d5793e3c426e8080", "sourceCommit": "2acad11c4bcae2bbde2180327d45d5db9cffc9ed",
"sourcePath": "quixos-instance/packages/camino-package-runtime", "sourcePath": "quixos-instance/packages/camino-package-runtime",
"exportName": "camino-package-runtime", "exportName": "camino-package-runtime",
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git" "mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
+48
View File
@@ -491,6 +491,46 @@ export type ListEdgesResponse = Message<"camino.ListEdgesResponse"> & {
* Use `create(ListEdgesResponseSchema)` to create a new message. * Use `create(ListEdgesResponseSchema)` to create a new message.
*/ */
export declare const ListEdgesResponseSchema: GenMessage<ListEdgesResponse>; export declare const ListEdgesResponseSchema: GenMessage<ListEdgesResponse>;
/**
* @generated from message camino.ResolveEdgeRequest
*/
export type ResolveEdgeRequest = Message<"camino.ResolveEdgeRequest"> & {
/**
* @generated from field: string object_id = 1;
*/
objectId: string;
/**
* @generated from field: string projection = 2;
*/
projection: string;
};
/**
* Describes the message camino.ResolveEdgeRequest.
* Use `create(ResolveEdgeRequestSchema)` to create a new message.
*/
export declare const ResolveEdgeRequestSchema: GenMessage<ResolveEdgeRequest>;
/**
* @generated from message camino.ResolveEdgeResponse
*/
export type ResolveEdgeResponse = Message<"camino.ResolveEdgeResponse"> & {
/**
* @generated from field: repeated camino.CaminoEdge edges = 1;
*/
edges: CaminoEdge[];
/**
* @generated from field: camino.CaminoObject materialized_object = 2;
*/
materializedObject?: CaminoObject | undefined;
/**
* @generated from field: bool created = 3;
*/
created: boolean;
};
/**
* Describes the message camino.ResolveEdgeResponse.
* Use `create(ResolveEdgeResponseSchema)` to create a new message.
*/
export declare const ResolveEdgeResponseSchema: GenMessage<ResolveEdgeResponse>;
/** /**
* @generated from message camino.RemoveEdgeRequest * @generated from message camino.RemoveEdgeRequest
*/ */
@@ -831,6 +871,14 @@ export declare const CaminoService: GenService<{
input: typeof ListEdgesRequestSchema; input: typeof ListEdgesRequestSchema;
output: typeof ListEdgesResponseSchema; output: typeof ListEdgesResponseSchema;
}; };
/**
* @generated from rpc camino.CaminoService.ResolveEdge
*/
resolveEdge: {
methodKind: "unary";
input: typeof ResolveEdgeRequestSchema;
output: typeof ResolveEdgeResponseSchema;
};
/** /**
* @generated from rpc camino.CaminoService.RemoveEdge * @generated from rpc camino.CaminoService.RemoveEdge
*/ */
+1 -1
View File
File diff suppressed because one or more lines are too long
+20 -10
View File
File diff suppressed because one or more lines are too long
+34
View File
@@ -323,6 +323,14 @@ export type InterfaceFieldContract = Message<"camino.InterfaceFieldContract"> &
* @generated from field: camino.TypeRef type = 6; * @generated from field: camino.TypeRef type = 6;
*/ */
type?: TypeRef | undefined; type?: TypeRef | undefined;
/**
* @generated from field: string ref_target_type_param = 7;
*/
refTargetTypeParam: string;
/**
* @generated from field: camino.Cardinality edge_cardinality = 8;
*/
edgeCardinality: Cardinality;
}; };
/** /**
* Describes the message camino.InterfaceFieldContract. * Describes the message camino.InterfaceFieldContract.
@@ -371,6 +379,10 @@ export type InterfaceSchema = Message<"camino.InterfaceSchema"> & {
* @generated from field: string proto_message = 5; * @generated from field: string proto_message = 5;
*/ */
protoMessage: string; protoMessage: string;
/**
* @generated from field: repeated string type_parameters = 6;
*/
typeParameters: string[];
}; };
/** /**
* Describes the message camino.InterfaceSchema. * Describes the message camino.InterfaceSchema.
@@ -483,12 +495,34 @@ export type EdgeEndpointSchema = Message<"camino.EdgeEndpointSchema"> & {
* @generated from field: bool indexed = 5; * @generated from field: bool indexed = 5;
*/ */
indexed: boolean; indexed: boolean;
/**
* @generated from field: camino.EdgeMaterialization materialize = 6;
*/
materialize?: EdgeMaterialization | undefined;
}; };
/** /**
* Describes the message camino.EdgeEndpointSchema. * Describes the message camino.EdgeEndpointSchema.
* Use `create(EdgeEndpointSchemaSchema)` to create a new message. * Use `create(EdgeEndpointSchemaSchema)` to create a new message.
*/ */
export declare const EdgeEndpointSchemaSchema: GenMessage<EdgeEndpointSchema>; export declare const EdgeEndpointSchemaSchema: GenMessage<EdgeEndpointSchema>;
/**
* @generated from message camino.EdgeMaterialization
*/
export type EdgeMaterialization = Message<"camino.EdgeMaterialization"> & {
/**
* @generated from field: camino.SymbolRef class = 1;
*/
class?: SymbolRef | undefined;
/**
* @generated from field: string connect_projection = 2;
*/
connectProjection: string;
};
/**
* Describes the message camino.EdgeMaterialization.
* Use `create(EdgeMaterializationSchema)` to create a new message.
*/
export declare const EdgeMaterializationSchema: GenMessage<EdgeMaterialization>;
/** /**
* @generated from message camino.MethodSchema * @generated from message camino.MethodSchema
*/ */
+1 -1
View File
File diff suppressed because one or more lines are too long
+12 -7
View File
File diff suppressed because one or more lines are too long
+243 -18
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env node #!/usr/bin/env node
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import { compileCaminoSchema } from "./schema-compiler.js"; import protobuf from "protobufjs";
import { compileCaminoSchema, loadCaminoSchemaRoot, } from "./schema-compiler.js";
const usage = () => { const usage = () => {
console.error("Usage: camino-codegen-ts-runtime <class-schema.camino.proto> <out-dir> [implementation-import]"); console.error("Usage: camino-codegen-ts-runtime [--react-only] <class-schema.camino.proto> <out-dir> [implementation-import]");
process.exit(1); process.exit(1);
}; };
const required = (value) => value ?? usage(); const required = (value) => value ?? usage();
@@ -18,11 +19,17 @@ const camel = (value) => value
const isIdentifier = (value) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value); const isIdentifier = (value) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
const classId = (schema) => `${schema.id.namespace}:${schema.id.name}:${schema.id.version}:${schema.id.hash ?? ""}`; const classId = (schema) => `${schema.id.namespace}:${schema.id.name}:${schema.id.version}:${schema.id.hash ?? ""}`;
const symbolId = (value) => `${value.namespace}:${value.name}:${value.version}:${value.hash ?? ""}`; const symbolId = (value) => `${value.namespace}:${value.name}:${value.version}:${value.hash ?? ""}`;
const tsTypeForTypeRef = (type) => { const normalizedProtoType = (value) => value.replace(/^\./, "");
const declarationName = (value) => value.name;
const tsTypeForTypeRef = (type, root) => {
if (type.symbol) {
return `ObjectRef<${stringLiteral(symbolId(type.symbol))}>`;
}
if (type.enumValues && type.enumValues.length > 0) { if (type.enumValues && type.enumValues.length > 0) {
return type.enumValues.map(stringLiteral).join(" | "); return type.enumValues.map(stringLiteral).join(" | ");
} }
switch (type.protoType.replace(/^\./, "")) { const protoType = normalizedProtoType(type.protoType);
switch (protoType) {
case "string": case "string":
case "google.protobuf.StringValue": case "google.protobuf.StringValue":
return "string"; return "string";
@@ -52,14 +59,26 @@ const tsTypeForTypeRef = (type) => {
case "google.protobuf.BytesValue": case "google.protobuf.BytesValue":
return "Uint8Array"; return "Uint8Array";
default: default:
if (protoType === "google.protobuf.Empty") {
return "Record<string, never>";
}
try {
const reflected = root?.lookup(protoType);
if (reflected instanceof protobuf.Type || reflected instanceof protobuf.Enum) {
return declarationName(reflected);
}
}
catch {
// Unknown imported message types remain opaque at this codegen boundary.
}
return "unknown"; return "unknown";
} }
}; };
const tsTypeForField = (field) => tsTypeForTypeRef(field.type); const tsTypeForField = (field, root) => tsTypeForTypeRef(field.type, root);
const tsValueTypeForField = (field) => field.repeated ? `${tsTypeForField(field)}[]` : tsTypeForField(field); const tsValueTypeForField = (field, root) => field.repeated ? `${tsTypeForField(field, root)}[]` : tsTypeForField(field, root);
const tsFieldApiType = (field) => field.conflict === "crdt" const tsFieldApiType = (field, root) => field.conflict === "crdt"
? `CrdtField<${tsValueTypeForField(field)}>` ? `CrdtField<${tsValueTypeForField(field, root)}>`
: `Field<${tsValueTypeForField(field)}>`; : `Field<${tsValueTypeForField(field, root)}>`;
const functionExportName = (fn, fallback) => { const functionExportName = (fn, fallback) => {
if (isIdentifier(fn.symbol)) { if (isIdentifier(fn.symbol)) {
return fn.symbol; return fn.symbol;
@@ -71,6 +90,68 @@ const functionExportName = (fn, fallback) => {
throw new Error(`Cannot generate TS export name for function ${fn.symbol}`); throw new Error(`Cannot generate TS export name for function ${fn.symbol}`);
}; };
const generatedWarning = `// @generated by camino-codegen-ts-runtime. Do not edit.\n`; const generatedWarning = `// @generated by camino-codegen-ts-runtime. Do not edit.\n`;
const scalarTsType = (field, root) => {
const primitive = tsTypeForTypeRef({ protoType: field.type }, root);
if (primitive !== "unknown") {
return primitive;
}
const resolved = field.resolvedType;
if (resolved instanceof protobuf.Type || resolved instanceof protobuf.Enum) {
return declarationName(resolved);
}
return "unknown";
};
const collectProtoDeclarations = (root, types) => {
const declarations = new Map();
const visit = (value) => {
const key = value.fullName;
if (declarations.has(key)) {
return;
}
declarations.set(key, value);
if (value instanceof protobuf.Type) {
for (const field of value.fieldsArray) {
if (field.resolvedType instanceof protobuf.Type ||
field.resolvedType instanceof protobuf.Enum) {
visit(field.resolvedType);
}
}
}
};
for (const type of types) {
if (type.symbol || !type.protoType) {
continue;
}
try {
const reflected = root.lookup(normalizedProtoType(type.protoType));
if (reflected instanceof protobuf.Type || reflected instanceof protobuf.Enum) {
visit(reflected);
}
}
catch {
// Opaque imported types are represented as unknown by the generated API.
}
}
return [...declarations.values()]
.map((value) => {
if (value instanceof protobuf.Enum) {
return `export type ${declarationName(value)} = ${Object.keys(value.values)
.map(stringLiteral)
.join(" | ")};`;
}
const fields = value.fieldsArray.map((field) => {
const fieldType = scalarTsType(field, root);
const valueType = field.map
? `Record<string, ${fieldType}>`
: field.repeated
? `${fieldType}[]`
: fieldType;
return ` ${JSON.stringify(field.name)}${field.optional ? "?" : ""}: ${valueType};`;
});
return `export type ${declarationName(value)} = {\n${fields.join("\n")}\n};`;
})
.join("\n\n");
};
const fieldTypeId = (field) => field.type.symbol ? symbolId(field.type.symbol) : field.type.protoType; const fieldTypeId = (field) => field.type.symbol ? symbolId(field.type.symbol) : field.type.protoType;
const crdtStorageTypeId = (field) => field.type.protoType || const crdtStorageTypeId = (field) => field.type.protoType ||
(field.type.symbol (field.type.symbol
@@ -96,8 +177,8 @@ ${bindings}
};`; };`;
}) })
.join("\n\n"); .join("\n\n");
const interfaceMixins = (schema) => schema.implements.map((implementation) => { const interfaceMixins = (schema, root) => schema.implements.map((implementation) => {
const typeArgs = implementation.typeBindings.map((binding) => tsTypeForTypeRef(binding.type)); const typeArgs = implementation.typeBindings.map((binding) => tsTypeForTypeRef(binding.type, root));
return `${interfaceTypeName(implementation)}${typeArgs.length > 0 ? `<${typeArgs.join(", ")}>` : ""}`; return `${interfaceTypeName(implementation)}${typeArgs.length > 0 ? `<${typeArgs.join(", ")}>` : ""}`;
}); });
const operationFunctionSpecs = (schema) => schema.fields.flatMap((field) => { const operationFunctionSpecs = (schema) => schema.fields.flatMap((field) => {
@@ -125,7 +206,7 @@ const operationFunctionSpecs = (schema) => schema.fields.flatMap((field) => {
}, },
})); }));
}); });
const renderRuntime = (schema) => { const renderRuntime = (schema, root) => {
const className = schema.id.name; const className = schema.id.name;
const objectTypeName = `${className}Object`; const objectTypeName = `${className}Object`;
const refTypeName = `${className}Ref`; const refTypeName = `${className}Ref`;
@@ -134,7 +215,7 @@ const renderRuntime = (schema) => {
const serveName = `serve${className}Runtime`; const serveName = `serve${className}Runtime`;
const createObjectName = `create${className}Object`; const createObjectName = `create${className}Object`;
const interfaceRefTypes = renderInterfaceRefTypes(schema); const interfaceRefTypes = renderInterfaceRefTypes(schema);
const refType = [`ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema)] const refType = [`ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema, root)]
.join(" & "); .join(" & ");
const edgeFields = new Set(schema.edges.map((edge) => edge.sourceField)); const edgeFields = new Set(schema.edges.map((edge) => edge.sourceField));
const writableFields = schema.fields.filter((field) => field.storage.kind === "stored" && const writableFields = schema.fields.filter((field) => field.storage.kind === "stored" &&
@@ -199,12 +280,14 @@ const renderRuntime = (schema) => {
serveQuixosPackageRuntime, serveQuixosPackageRuntime,
} from "@quixos/camino-package-runtime"; } from "@quixos/camino-package-runtime";
${collectProtoDeclarations(root, schema.implements.flatMap((entry) => entry.typeBindings.map((binding) => binding.type)))}
${interfaceRefTypes ? `${interfaceRefTypes}\n\n` : ""}export type ${refTypeName} = ${refType}; ${interfaceRefTypes ? `${interfaceRefTypes}\n\n` : ""}export type ${refTypeName} = ${refType};
export type ${objectTypeName} = { export type ${objectTypeName} = {
id: ${refTypeName}; id: ${refTypeName};
${writableFields ${writableFields
.map((field) => ` ${camel(field.name)}: ${tsFieldApiType(field)};`) .map((field) => ` ${camel(field.name)}: ${tsFieldApiType(field, root)};`)
.join("\n")} .join("\n")}
}; };
@@ -271,6 +354,135 @@ export const ${serveName} = (implementation: ${implementationTypeName}) => {
}; };
`; `;
}; };
const implementationNamed = (schema, name) => schema.implements.find((entry) => entry.interface.namespace === "quixos.react" &&
entry.interface.name === name);
const requiredBinding = (implementation, name) => {
const binding = implementation.typeBindings.find((entry) => entry.name === name);
if (!binding) {
throw new Error(`${implementation.interface.name} implementation is missing ${name}`);
}
return binding.type;
};
const renderReactClient = (schema, root) => {
const react = implementationNamed(schema, "ReactComponent");
const componentFor = implementationNamed(schema, "ReactComponentFor");
if (!react || !componentFor) {
return undefined;
}
const renderProps = requiredBinding(react, "RenderProps");
const action = requiredBinding(react, "Action");
const object = requiredBinding(componentFor, "Object");
if (!object.symbol) {
throw new Error(`${schema.id.name}.ReactComponentFor.Object must bind a class symbol`);
}
const edge = schema.edges.find((candidate) => candidate.sourceField === "for_object");
if (!edge) {
throw new Error(`${schema.id.name} must declare its ReactComponentFor edge`);
}
const componentEndpoint = edge.fromEndpoint.projection === "for_object"
? edge.fromEndpoint
: edge.toEndpoint.projection === "for_object"
? edge.toEndpoint
: undefined;
const objectEndpoint = componentEndpoint === edge.fromEndpoint ? edge.toEndpoint : edge.fromEndpoint;
if (!componentEndpoint || !objectEndpoint.projection) {
throw new Error(`${schema.id.name}.for_object edge has no inverse projection`);
}
if (componentEndpoint.cardinality !== "exactly_one") {
throw new Error(`${schema.id.name}.for_object must have exactly_one cardinality`);
}
if (!objectEndpoint.materialization ||
symbolId(objectEndpoint.materialization.class) !== symbolId(schema.id)) {
throw new Error(`${schema.id.name}.${objectEndpoint.projection} must materialize ${classId(schema)}`);
}
const className = schema.id.name;
const componentName = className.endsWith("ReactComponent")
? `${className.slice(0, -"ReactComponent".length)}Component`
: className;
const propsTypeName = `${componentName}Props`;
const renderPropsType = tsTypeForTypeRef(renderProps, root);
const actionType = tsTypeForTypeRef(action, root);
const objectType = tsTypeForTypeRef(object, root);
const declarations = collectProtoDeclarations(root, [renderProps, action]);
return `${generatedWarning}import {
createReactComponentFor,
type ReactComponentHostProps,
type ObjectRef,
} from "@quixos/camino-react-runtime";
${declarations}
export type ${propsTypeName} = {
forObject: ${objectType};
} & ${renderPropsType} & ReactComponentHostProps<${actionType}>;
export const ${componentName} = createReactComponentFor<
${objectType},
${renderPropsType},
${actionType}
>({
componentClassId: ${stringLiteral(classId(schema))},
projection: ${stringLiteral(objectEndpoint.projection)},
});
`;
};
const reactRuntimeDeclaration = `${generatedWarning}declare module "@quixos/camino-react-runtime" {
import type * as AutomergeNamespace from "@automerge/automerge";
export type ObjectRef<ClassId extends string> = string & {
readonly $caminoClass: ClassId;
};
export type LiveFieldProp<T> = {
value: T;
source: {
objectId: string;
fieldName: string;
fieldType?: string;
fieldStorage?: string;
conflictStrategy?: string;
revision?: string | number | bigint;
};
};
export type ReactComponentHostProps<Action> = {
onAction?: (action: Action) => void;
fallback?: unknown;
className?: string;
style?: Record<string, string | number | undefined>;
onError?: (error: Error) => void;
};
export type ReactComponentImplementationProps<CaminoProps, RenderProps, Action> = {
camino: CaminoProps;
render: RenderProps;
dispatch: (action: Action) => void;
};
export const createReactComponentFor: <
ForObject extends ObjectRef<string>,
RenderProps extends object,
Action,
>(config: {
componentClassId: string;
projection: string;
}) => (props: {
forObject: ForObject;
} & RenderProps & ReactComponentHostProps<Action>) => any;
export const callObjectMethod: <Result = unknown>(
object: ObjectRef<string>,
methodName: string,
input?: Record<string, unknown>,
) => Promise<Result>;
export const h: (...args: any[]) => any;
export const useLiveField: <T>(
field: LiveFieldProp<T>,
) => [T, (value: T) => void | Promise<void>];
export const Automerge: typeof AutomergeNamespace;
}
`;
const renderServer = (schema, implementationImport) => { const renderServer = (schema, implementationImport) => {
const className = schema.id.name; const className = schema.id.name;
return `${generatedWarning}import * as implementation from ${stringLiteral(implementationImport)}; return `${generatedWarning}import * as implementation from ${stringLiteral(implementationImport)};
@@ -280,10 +492,13 @@ serve${className}Runtime(implementation);
`; `;
}; };
const main = async () => { const main = async () => {
const sourceFile = required(process.argv[2]); const reactOnly = process.argv[2] === "--react-only";
const outDir = required(process.argv[3]); const offset = reactOnly ? 1 : 0;
const implementationImport = process.argv[4] ?? "../task.impl.js"; const sourceFile = required(process.argv[2 + offset]);
const outDir = required(process.argv[3 + offset]);
const implementationImport = process.argv[4 + offset] ?? "../task.impl.js";
const compiled = await compileCaminoSchema(sourceFile); const compiled = await compileCaminoSchema(sourceFile);
const { root } = await loadCaminoSchemaRoot(sourceFile);
if (compiled.classes.length !== 1) { if (compiled.classes.length !== 1) {
throw new Error(`Expected exactly one Camino class in ${sourceFile}, found ${compiled.classes.length}`); throw new Error(`Expected exactly one Camino class in ${sourceFile}, found ${compiled.classes.length}`);
} }
@@ -293,7 +508,17 @@ const main = async () => {
} }
fs.mkdirSync(outDir, { recursive: true }); fs.mkdirSync(outDir, { recursive: true });
const baseName = `${schema.id.namespace}.${schema.id.name}`; const baseName = `${schema.id.namespace}.${schema.id.name}`;
fs.writeFileSync(path.join(outDir, `${baseName}.runtime.ts`), renderRuntime(schema)); const reactClient = renderReactClient(schema, root);
if (!reactOnly) {
fs.writeFileSync(path.join(outDir, `${baseName}.runtime.ts`), renderRuntime(schema, root));
fs.writeFileSync(path.join(outDir, `${baseName}.server.ts`), renderServer(schema, implementationImport)); fs.writeFileSync(path.join(outDir, `${baseName}.server.ts`), renderServer(schema, implementationImport));
}
if (reactClient) {
fs.writeFileSync(path.join(outDir, `${baseName}.react.ts`), reactClient);
fs.writeFileSync(path.join(outDir, "camino-react-runtime.d.ts"), reactRuntimeDeclaration);
}
else if (reactOnly) {
throw new Error(`${schema.id.name} does not implement ReactComponentFor`);
}
}; };
await main(); await main();
+5
View File
@@ -1,3 +1,8 @@
import protobuf from "protobufjs";
import type { SchemaCompileResult } from "./schema-ir.js"; import type { SchemaCompileResult } from "./schema-ir.js";
export declare const loadCaminoSchemaRoot: (sourceFile: string) => Promise<{
absoluteSourceFile: string;
root: protobuf.Root;
}>;
export declare const compileCaminoSchema: (sourceFile: string) => Promise<SchemaCompileResult>; export declare const compileCaminoSchema: (sourceFile: string) => Promise<SchemaCompileResult>;
//# sourceMappingURL=schema-compiler.d.ts.map //# sourceMappingURL=schema-compiler.d.ts.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"schema-compiler.d.ts","sourceRoot":"","sources":["../src/schema-compiler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAeV,mBAAmB,EAIpB,MAAM,gBAAgB,CAAC;AAq0BxB,eAAO,MAAM,mBAAmB,GAC9B,YAAY,MAAM,KACjB,OAAO,CAAC,mBAAmB,CAuE7B,CAAC"} {"version":3,"file":"schema-compiler.d.ts","sourceRoot":"","sources":["../src/schema-compiler.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,YAAY,CAAC;AAClC,OAAO,KAAK,EAeV,mBAAmB,EAIpB,MAAM,gBAAgB,CAAC;AA02BxB,eAAO,MAAM,oBAAoB,GAC/B,YAAY,MAAM,KACjB,OAAO,CAAC;IAAE,kBAAkB,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAA;CAAE,CAqC7D,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,YAAY,MAAM,KACjB,OAAO,CAAC,mBAAmB,CAsC7B,CAAC"}
+35 -2
View File
@@ -114,6 +114,10 @@ const interfaceFieldContractFromOption = (value, schemaVersion) => {
return undefined; return undefined;
} }
const type = typeRefFromOption(object.type, schemaVersion); const type = typeRefFromOption(object.type, schemaVersion);
const refTargetTypeParam = readString(object, "ref_target_type_param");
const edgeCardinality = object.edge_cardinality === undefined
? undefined
: cardinalityFromOption(object.edge_cardinality);
return { return {
required: readBoolean(object, "required") ?? false, required: readBoolean(object, "required") ?? false,
readable: readBoolean(object, "readable") ?? false, readable: readBoolean(object, "readable") ?? false,
@@ -123,6 +127,8 @@ const interfaceFieldContractFromOption = (value, schemaVersion) => {
? { typeParam: readString(object, "type_param") } ? { typeParam: readString(object, "type_param") }
: {}), : {}),
...(type ? { type } : {}), ...(type ? { type } : {}),
...(refTargetTypeParam ? { refTargetTypeParam } : {}),
...(edgeCardinality ? { edgeCardinality } : {}),
}; };
}; };
const functionRefFromOption = (value) => { const functionRefFromOption = (value) => {
@@ -308,6 +314,24 @@ const isManyCardinality = (cardinality) => cardinality === "many" ||
cardinality === "many_unique" || cardinality === "many_unique" ||
cardinality === "many_ordered" || cardinality === "many_ordered" ||
cardinality === "many_unique_ordered"; cardinality === "many_unique_ordered";
const materializationFromOption = (value, fallbackVersion) => {
const object = asObject(value);
if (!object) {
return undefined;
}
const classRef = symbolRefFromOption(object.class, {
namespace: "",
name: "",
version: fallbackVersion,
});
if (!classRef.name) {
throw new Error("Edge materialization requires class");
}
return {
class: classRef,
connectProjection: readString(object, "connect_projection") ?? "",
};
};
const endpointFromOption = (params) => { const endpointFromOption = (params) => {
const object = asObject(params.option); const object = asObject(params.option);
const classRef = asObject(object?.class) const classRef = asObject(object?.class)
@@ -328,6 +352,7 @@ const endpointFromOption = (params) => {
const cardinality = object?.cardinality === undefined const cardinality = object?.cardinality === undefined
? params.fallbackCardinality ? params.fallbackCardinality
: cardinalityFromOption(object.cardinality); : cardinalityFromOption(object.cardinality);
const materialization = materializationFromOption(object?.materialize, params.fallbackVersion);
if (!classRef?.name && !interfaceRef?.name) { if (!classRef?.name && !interfaceRef?.name) {
throw new Error(`Edge endpoint ${projection} requires class or interface`); throw new Error(`Edge endpoint ${projection} requires class or interface`);
} }
@@ -337,6 +362,7 @@ const endpointFromOption = (params) => {
projection, projection,
cardinality, cardinality,
indexed: readBoolean(object, "indexed") ?? false, indexed: readBoolean(object, "indexed") ?? false,
...(materialization ? { materialization } : {}),
}; };
}; };
const symbolKey = (ref) => ref ? [ref.namespace, ref.name, ref.version, ref.hash ?? ""].join(":") : ""; const symbolKey = (ref) => ref ? [ref.namespace, ref.name, ref.version, ref.hash ?? ""].join(":") : "";
@@ -606,6 +632,9 @@ const interfaceSchemaFromType = (type, sourceFile, defaults) => {
fields: type.fieldsArray.map((field) => fieldSchemaFromField(field, defaults.schemaVersion)), fields: type.fieldsArray.map((field) => fieldSchemaFromField(field, defaults.schemaVersion)),
sourceFile, sourceFile,
protoMessage: type.fullName, protoMessage: type.fullName,
typeParameters: asArray(interfaceOption.type_parameter)
.map((entry) => String(entry).trim())
.filter(Boolean),
}; };
}; };
const walkTypes = (namespace, visit) => { const walkTypes = (namespace, visit) => {
@@ -629,9 +658,8 @@ const walkServices = (namespace, visit) => {
} }
} }
}; };
export const compileCaminoSchema = async (sourceFile) => { export const loadCaminoSchemaRoot = async (sourceFile) => {
const absoluteSourceFile = path.resolve(sourceFile); const absoluteSourceFile = path.resolve(sourceFile);
const defaults = extractFileDefaults(absoluteSourceFile);
const root = new protobuf.Root(); const root = new protobuf.Root();
const envIncludeDirs = [ const envIncludeDirs = [
process.env.QUIXOS_PROTO_PATH, process.env.QUIXOS_PROTO_PATH,
@@ -664,6 +692,11 @@ export const compileCaminoSchema = async (sourceFile) => {
}; };
await root.load(absoluteSourceFile, { keepCase: true }); await root.load(absoluteSourceFile, { keepCase: true });
root.resolveAll(); root.resolveAll();
return { absoluteSourceFile, root };
};
export const compileCaminoSchema = async (sourceFile) => {
const { absoluteSourceFile, root } = await loadCaminoSchemaRoot(sourceFile);
const defaults = extractFileDefaults(absoluteSourceFile);
const operationServices = []; const operationServices = [];
walkServices(root, (service) => { walkServices(root, (service) => {
operationServices.push(operationServiceFromService(service, defaults.schemaVersion)); operationServices.push(operationServiceFromService(service, defaults.schemaVersion));
+8
View File
@@ -14,6 +14,10 @@ export type FunctionRef = {
export type ConflictStrategy = "replace" | "preserve_conflicts" | "crdt"; export type ConflictStrategy = "replace" | "preserve_conflicts" | "crdt";
export type Cardinality = "optional_one" | "exactly_one" | "many" | "many_unique" | "many_ordered" | "many_unique_ordered"; export type Cardinality = "optional_one" | "exactly_one" | "many" | "many_unique" | "many_ordered" | "many_unique_ordered";
export type EdgeDirectionality = "directed" | "undirected"; export type EdgeDirectionality = "directed" | "undirected";
export type EdgeMaterialization = {
class: SymbolRef;
connectProjection: string;
};
export type FieldStorageKind = "stored" | "derived" | "lazy" | "external" | "static_final"; export type FieldStorageKind = "stored" | "derived" | "lazy" | "external" | "static_final";
export type FieldStorage = { export type FieldStorage = {
kind: FieldStorageKind; kind: FieldStorageKind;
@@ -40,6 +44,8 @@ export type InterfaceFieldContract = {
watchable: boolean; watchable: boolean;
typeParam?: string; typeParam?: string;
type?: TypeRef; type?: TypeRef;
refTargetTypeParam?: string;
edgeCardinality?: Cardinality;
}; };
export type InterfaceImplementation = { export type InterfaceImplementation = {
interface: SymbolRef; interface: SymbolRef;
@@ -63,6 +69,7 @@ export type EdgeEndpointSchema = {
projection: string; projection: string;
cardinality: Cardinality; cardinality: Cardinality;
indexed: boolean; indexed: boolean;
materialization?: EdgeMaterialization;
}; };
export type EdgeSchema = { export type EdgeSchema = {
id: SymbolRef; id: SymbolRef;
@@ -121,6 +128,7 @@ export type InterfaceSchema = {
fields: FieldSchema[]; fields: FieldSchema[];
sourceFile: string; sourceFile: string;
protoMessage: string; protoMessage: string;
typeParameters: string[];
}; };
export type SchemaCompileResult = { export type SchemaCompileResult = {
sourceFile: string; sourceFile: string;
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"schema-ir.d.ts","sourceRoot":"","sources":["../src/schema-ir.ts"],"names":[],"mappings":"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"} {"version":3,"file":"schema-ir.d.ts","sourceRoot":"","sources":["../src/schema-ir.ts"],"names":[],"mappings":"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"}
Generated
+5 -5
View File
@@ -74,17 +74,17 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1784093774, "lastModified": 1784395768,
"narHash": "sha256-tG29sWqPIiDNacXu/zaaQrQI2PS18kGu56jyqcUdD0Q=", "narHash": "sha256-g03xOnzJHiL/edNV6nqURtaPqiapq2uCm7wQTZh4b5s=",
"ref": "refs/heads/exported", "ref": "refs/heads/exported",
"rev": "ca0381d2801aa0cb78275d03568049df41a23281", "rev": "e6b34535afa6e94a118e1f9d082054855e7df632",
"revCount": 20, "revCount": 24,
"type": "git", "type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git" "url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
}, },
"original": { "original": {
"ref": "refs/heads/exported", "ref": "refs/heads/exported",
"rev": "ca0381d2801aa0cb78275d03568049df41a23281", "rev": "e6b34535afa6e94a118e1f9d082054855e7df632",
"type": "git", "type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git" "url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
} }
+1 -1
View File
@@ -4,7 +4,7 @@
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
quixos-protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/heads/exported&rev=ca0381d2801aa0cb78275d03568049df41a23281"; quixos-protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/heads/exported&rev=e6b34535afa6e94a118e1f9d082054855e7df632";
quixosNixHelpers = { quixosNixHelpers = {
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/heads/exported&rev=1f0c39b01501d646fe97dfc6e5777ccab0bde2f1"; url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/heads/exported&rev=1f0c39b01501d646fe97dfc6e5777ccab0bde2f1";
flake = false; flake = false;
+67 -10
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+284 -19
View File
@@ -1,7 +1,11 @@
#!/usr/bin/env node #!/usr/bin/env node
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import { compileCaminoSchema } from "./schema-compiler.js"; import protobuf from "protobufjs";
import {
compileCaminoSchema,
loadCaminoSchemaRoot,
} from "./schema-compiler.js";
import type { import type {
ClassSchema, ClassSchema,
FieldSchema, FieldSchema,
@@ -12,7 +16,7 @@ import type {
const usage = (): never => { const usage = (): never => {
console.error( console.error(
"Usage: camino-codegen-ts-runtime <class-schema.camino.proto> <out-dir> [implementation-import]", "Usage: camino-codegen-ts-runtime [--react-only] <class-schema.camino.proto> <out-dir> [implementation-import]",
); );
process.exit(1); process.exit(1);
}; };
@@ -46,11 +50,22 @@ const classId = (schema: ClassSchema) =>
const symbolId = (value: { namespace: string; name: string; version: string; hash?: string }) => const symbolId = (value: { namespace: string; name: string; version: string; hash?: string }) =>
`${value.namespace}:${value.name}:${value.version}:${value.hash ?? ""}`; `${value.namespace}:${value.name}:${value.version}:${value.hash ?? ""}`;
const tsTypeForTypeRef = (type: TypeRef): string => { const normalizedProtoType = (value: string) => value.replace(/^\./, "");
const declarationName = (value: protobuf.Type | protobuf.Enum) => value.name;
const tsTypeForTypeRef = (
type: TypeRef,
root?: protobuf.Root,
): string => {
if (type.symbol) {
return `ObjectRef<${stringLiteral(symbolId(type.symbol))}>`;
}
if (type.enumValues && type.enumValues.length > 0) { if (type.enumValues && type.enumValues.length > 0) {
return type.enumValues.map(stringLiteral).join(" | "); return type.enumValues.map(stringLiteral).join(" | ");
} }
switch (type.protoType.replace(/^\./, "")) { const protoType = normalizedProtoType(type.protoType);
switch (protoType) {
case "string": case "string":
case "google.protobuf.StringValue": case "google.protobuf.StringValue":
return "string"; return "string";
@@ -80,19 +95,31 @@ const tsTypeForTypeRef = (type: TypeRef): string => {
case "google.protobuf.BytesValue": case "google.protobuf.BytesValue":
return "Uint8Array"; return "Uint8Array";
default: default:
if (protoType === "google.protobuf.Empty") {
return "Record<string, never>";
}
try {
const reflected = root?.lookup(protoType);
if (reflected instanceof protobuf.Type || reflected instanceof protobuf.Enum) {
return declarationName(reflected);
}
} catch {
// Unknown imported message types remain opaque at this codegen boundary.
}
return "unknown"; return "unknown";
} }
}; };
const tsTypeForField = (field: FieldSchema): string => tsTypeForTypeRef(field.type); const tsTypeForField = (field: FieldSchema, root?: protobuf.Root): string =>
tsTypeForTypeRef(field.type, root);
const tsValueTypeForField = (field: FieldSchema) => const tsValueTypeForField = (field: FieldSchema, root?: protobuf.Root) =>
field.repeated ? `${tsTypeForField(field)}[]` : tsTypeForField(field); field.repeated ? `${tsTypeForField(field, root)}[]` : tsTypeForField(field, root);
const tsFieldApiType = (field: FieldSchema) => const tsFieldApiType = (field: FieldSchema, root?: protobuf.Root) =>
field.conflict === "crdt" field.conflict === "crdt"
? `CrdtField<${tsValueTypeForField(field)}>` ? `CrdtField<${tsValueTypeForField(field, root)}>`
: `Field<${tsValueTypeForField(field)}>`; : `Field<${tsValueTypeForField(field, root)}>`;
const functionExportName = (fn: FunctionRef, fallback: string) => { const functionExportName = (fn: FunctionRef, fallback: string) => {
if (isIdentifier(fn.symbol)) { if (isIdentifier(fn.symbol)) {
@@ -107,6 +134,74 @@ const functionExportName = (fn: FunctionRef, fallback: string) => {
const generatedWarning = `// @generated by camino-codegen-ts-runtime. Do not edit.\n`; const generatedWarning = `// @generated by camino-codegen-ts-runtime. Do not edit.\n`;
const scalarTsType = (field: protobuf.Field, root: protobuf.Root): string => {
const primitive = tsTypeForTypeRef({ protoType: field.type }, root);
if (primitive !== "unknown") {
return primitive;
}
const resolved = field.resolvedType;
if (resolved instanceof protobuf.Type || resolved instanceof protobuf.Enum) {
return declarationName(resolved);
}
return "unknown";
};
const collectProtoDeclarations = (
root: protobuf.Root,
types: TypeRef[],
) => {
const declarations = new Map<string, protobuf.Type | protobuf.Enum>();
const visit = (value: protobuf.Type | protobuf.Enum) => {
const key = value.fullName;
if (declarations.has(key)) {
return;
}
declarations.set(key, value);
if (value instanceof protobuf.Type) {
for (const field of value.fieldsArray) {
if (
field.resolvedType instanceof protobuf.Type ||
field.resolvedType instanceof protobuf.Enum
) {
visit(field.resolvedType);
}
}
}
};
for (const type of types) {
if (type.symbol || !type.protoType) {
continue;
}
try {
const reflected = root.lookup(normalizedProtoType(type.protoType));
if (reflected instanceof protobuf.Type || reflected instanceof protobuf.Enum) {
visit(reflected);
}
} catch {
// Opaque imported types are represented as unknown by the generated API.
}
}
return [...declarations.values()]
.map((value) => {
if (value instanceof protobuf.Enum) {
return `export type ${declarationName(value)} = ${Object.keys(value.values)
.map(stringLiteral)
.join(" | ")};`;
}
const fields = value.fieldsArray.map((field) => {
const fieldType = scalarTsType(field, root);
const valueType = field.map
? `Record<string, ${fieldType}>`
: field.repeated
? `${fieldType}[]`
: fieldType;
return ` ${JSON.stringify(field.name)}${field.optional ? "?" : ""}: ${valueType};`;
});
return `export type ${declarationName(value)} = {\n${fields.join("\n")}\n};`;
})
.join("\n\n");
};
type FunctionSpec = { type FunctionSpec = {
exportName: string; exportName: string;
symbol: string; symbol: string;
@@ -158,10 +253,10 @@ ${bindings}
}) })
.join("\n\n"); .join("\n\n");
const interfaceMixins = (schema: ClassSchema) => const interfaceMixins = (schema: ClassSchema, root: protobuf.Root) =>
schema.implements.map((implementation) => { schema.implements.map((implementation) => {
const typeArgs = implementation.typeBindings.map((binding) => const typeArgs = implementation.typeBindings.map((binding) =>
tsTypeForTypeRef(binding.type), tsTypeForTypeRef(binding.type, root),
); );
return `${interfaceTypeName(implementation)}${ return `${interfaceTypeName(implementation)}${
typeArgs.length > 0 ? `<${typeArgs.join(", ")}>` : "" typeArgs.length > 0 ? `<${typeArgs.join(", ")}>` : ""
@@ -202,7 +297,7 @@ const operationFunctionSpecs = (schema: ClassSchema): FunctionSpec[] =>
})); }));
}); });
const renderRuntime = (schema: ClassSchema) => { const renderRuntime = (schema: ClassSchema, root: protobuf.Root) => {
const className = schema.id.name; const className = schema.id.name;
const objectTypeName = `${className}Object`; const objectTypeName = `${className}Object`;
const refTypeName = `${className}Ref`; const refTypeName = `${className}Ref`;
@@ -212,7 +307,7 @@ const renderRuntime = (schema: ClassSchema) => {
const createObjectName = `create${className}Object`; const createObjectName = `create${className}Object`;
const interfaceRefTypes = renderInterfaceRefTypes(schema); const interfaceRefTypes = renderInterfaceRefTypes(schema);
const refType = const refType =
[ `ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema)] [ `ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema, root)]
.join(" & "); .join(" & ");
const edgeFields = new Set(schema.edges.map((edge) => edge.sourceField)); const edgeFields = new Set(schema.edges.map((edge) => edge.sourceField));
const writableFields = schema.fields.filter( const writableFields = schema.fields.filter(
@@ -291,6 +386,8 @@ const renderRuntime = (schema: ClassSchema) => {
serveQuixosPackageRuntime, serveQuixosPackageRuntime,
} from "@quixos/camino-package-runtime"; } from "@quixos/camino-package-runtime";
${collectProtoDeclarations(root, schema.implements.flatMap((entry) => entry.typeBindings.map((binding) => binding.type)))}
${interfaceRefTypes ? `${interfaceRefTypes}\n\n` : ""}export type ${refTypeName} = ${refType}; ${interfaceRefTypes ? `${interfaceRefTypes}\n\n` : ""}export type ${refTypeName} = ${refType};
export type ${objectTypeName} = { export type ${objectTypeName} = {
@@ -298,7 +395,7 @@ export type ${objectTypeName} = {
${writableFields ${writableFields
.map( .map(
(field) => (field) =>
` ${camel(field.name)}: ${tsFieldApiType(field)};`, ` ${camel(field.name)}: ${tsFieldApiType(field, root)};`,
) )
.join("\n")} .join("\n")}
}; };
@@ -379,6 +476,156 @@ export const ${serveName} = (implementation: ${implementationTypeName}) => {
`; `;
}; };
const implementationNamed = (schema: ClassSchema, name: string) =>
schema.implements.find(
(entry) =>
entry.interface.namespace === "quixos.react" &&
entry.interface.name === name,
);
const requiredBinding = (
implementation: InterfaceImplementation,
name: string,
) => {
const binding = implementation.typeBindings.find((entry) => entry.name === name);
if (!binding) {
throw new Error(
`${implementation.interface.name} implementation is missing ${name}`,
);
}
return binding.type;
};
const renderReactClient = (schema: ClassSchema, root: protobuf.Root) => {
const react = implementationNamed(schema, "ReactComponent");
const componentFor = implementationNamed(schema, "ReactComponentFor");
if (!react || !componentFor) {
return undefined;
}
const renderProps = requiredBinding(react, "RenderProps");
const action = requiredBinding(react, "Action");
const object = requiredBinding(componentFor, "Object");
if (!object.symbol) {
throw new Error(`${schema.id.name}.ReactComponentFor.Object must bind a class symbol`);
}
const edge = schema.edges.find((candidate) => candidate.sourceField === "for_object");
if (!edge) {
throw new Error(`${schema.id.name} must declare its ReactComponentFor edge`);
}
const componentEndpoint =
edge.fromEndpoint.projection === "for_object"
? edge.fromEndpoint
: edge.toEndpoint.projection === "for_object"
? edge.toEndpoint
: undefined;
const objectEndpoint =
componentEndpoint === edge.fromEndpoint ? edge.toEndpoint : edge.fromEndpoint;
if (!componentEndpoint || !objectEndpoint.projection) {
throw new Error(`${schema.id.name}.for_object edge has no inverse projection`);
}
if (componentEndpoint.cardinality !== "exactly_one") {
throw new Error(`${schema.id.name}.for_object must have exactly_one cardinality`);
}
if (
!objectEndpoint.materialization ||
symbolId(objectEndpoint.materialization.class) !== symbolId(schema.id)
) {
throw new Error(
`${schema.id.name}.${objectEndpoint.projection} must materialize ${classId(schema)}`,
);
}
const className = schema.id.name;
const componentName = className.endsWith("ReactComponent")
? `${className.slice(0, -"ReactComponent".length)}Component`
: className;
const propsTypeName = `${componentName}Props`;
const renderPropsType = tsTypeForTypeRef(renderProps, root);
const actionType = tsTypeForTypeRef(action, root);
const objectType = tsTypeForTypeRef(object, root);
const declarations = collectProtoDeclarations(root, [renderProps, action]);
return `${generatedWarning}import {
createReactComponentFor,
type ReactComponentHostProps,
type ObjectRef,
} from "@quixos/camino-react-runtime";
${declarations}
export type ${propsTypeName} = {
forObject: ${objectType};
} & ${renderPropsType} & ReactComponentHostProps<${actionType}>;
export const ${componentName} = createReactComponentFor<
${objectType},
${renderPropsType},
${actionType}
>({
componentClassId: ${stringLiteral(classId(schema))},
projection: ${stringLiteral(objectEndpoint.projection)},
});
`;
};
const reactRuntimeDeclaration = `${generatedWarning}declare module "@quixos/camino-react-runtime" {
import type * as AutomergeNamespace from "@automerge/automerge";
export type ObjectRef<ClassId extends string> = string & {
readonly $caminoClass: ClassId;
};
export type LiveFieldProp<T> = {
value: T;
source: {
objectId: string;
fieldName: string;
fieldType?: string;
fieldStorage?: string;
conflictStrategy?: string;
revision?: string | number | bigint;
};
};
export type ReactComponentHostProps<Action> = {
onAction?: (action: Action) => void;
fallback?: unknown;
className?: string;
style?: Record<string, string | number | undefined>;
onError?: (error: Error) => void;
};
export type ReactComponentImplementationProps<CaminoProps, RenderProps, Action> = {
camino: CaminoProps;
render: RenderProps;
dispatch: (action: Action) => void;
};
export const createReactComponentFor: <
ForObject extends ObjectRef<string>,
RenderProps extends object,
Action,
>(config: {
componentClassId: string;
projection: string;
}) => (props: {
forObject: ForObject;
} & RenderProps & ReactComponentHostProps<Action>) => any;
export const callObjectMethod: <Result = unknown>(
object: ObjectRef<string>,
methodName: string,
input?: Record<string, unknown>,
) => Promise<Result>;
export const h: (...args: any[]) => any;
export const useLiveField: <T>(
field: LiveFieldProp<T>,
) => [T, (value: T) => void | Promise<void>];
export const Automerge: typeof AutomergeNamespace;
}
`;
const renderServer = (schema: ClassSchema, implementationImport: string) => { const renderServer = (schema: ClassSchema, implementationImport: string) => {
const className = schema.id.name; const className = schema.id.name;
return `${generatedWarning}import * as implementation from ${stringLiteral(implementationImport)}; return `${generatedWarning}import * as implementation from ${stringLiteral(implementationImport)};
@@ -389,10 +636,13 @@ serve${className}Runtime(implementation);
}; };
const main = async () => { const main = async () => {
const sourceFile = required(process.argv[2]); const reactOnly = process.argv[2] === "--react-only";
const outDir = required(process.argv[3]); const offset = reactOnly ? 1 : 0;
const implementationImport = process.argv[4] ?? "../task.impl.js"; const sourceFile = required(process.argv[2 + offset]);
const outDir = required(process.argv[3 + offset]);
const implementationImport = process.argv[4 + offset] ?? "../task.impl.js";
const compiled = await compileCaminoSchema(sourceFile); const compiled = await compileCaminoSchema(sourceFile);
const { root } = await loadCaminoSchemaRoot(sourceFile);
if (compiled.classes.length !== 1) { if (compiled.classes.length !== 1) {
throw new Error( throw new Error(
`Expected exactly one Camino class in ${sourceFile}, found ${compiled.classes.length}`, `Expected exactly one Camino class in ${sourceFile}, found ${compiled.classes.length}`,
@@ -404,11 +654,26 @@ const main = async () => {
} }
fs.mkdirSync(outDir, { recursive: true }); fs.mkdirSync(outDir, { recursive: true });
const baseName = `${schema.id.namespace}.${schema.id.name}`; const baseName = `${schema.id.namespace}.${schema.id.name}`;
fs.writeFileSync(path.join(outDir, `${baseName}.runtime.ts`), renderRuntime(schema)); const reactClient = renderReactClient(schema, root);
if (!reactOnly) {
fs.writeFileSync(
path.join(outDir, `${baseName}.runtime.ts`),
renderRuntime(schema, root),
);
fs.writeFileSync( fs.writeFileSync(
path.join(outDir, `${baseName}.server.ts`), path.join(outDir, `${baseName}.server.ts`),
renderServer(schema, implementationImport), renderServer(schema, implementationImport),
); );
}
if (reactClient) {
fs.writeFileSync(path.join(outDir, `${baseName}.react.ts`), reactClient);
fs.writeFileSync(
path.join(outDir, "camino-react-runtime.d.ts"),
reactRuntimeDeclaration,
);
} else if (reactOnly) {
throw new Error(`${schema.id.name} does not implement ReactComponentFor`);
}
}; };
await main(); await main();
+48 -3
View File
@@ -187,6 +187,11 @@ const interfaceFieldContractFromOption = (
return undefined; return undefined;
} }
const type = typeRefFromOption(object.type, schemaVersion); const type = typeRefFromOption(object.type, schemaVersion);
const refTargetTypeParam = readString(object, "ref_target_type_param");
const edgeCardinality =
object.edge_cardinality === undefined
? undefined
: cardinalityFromOption(object.edge_cardinality);
return { return {
required: readBoolean(object, "required") ?? false, required: readBoolean(object, "required") ?? false,
readable: readBoolean(object, "readable") ?? false, readable: readBoolean(object, "readable") ?? false,
@@ -196,6 +201,8 @@ const interfaceFieldContractFromOption = (
? { typeParam: readString(object, "type_param") } ? { typeParam: readString(object, "type_param") }
: {}), : {}),
...(type ? { type } : {}), ...(type ? { type } : {}),
...(refTargetTypeParam ? { refTargetTypeParam } : {}),
...(edgeCardinality ? { edgeCardinality } : {}),
}; };
}; };
@@ -427,6 +434,28 @@ const isManyCardinality = (cardinality: Cardinality) =>
cardinality === "many_ordered" || cardinality === "many_ordered" ||
cardinality === "many_unique_ordered"; cardinality === "many_unique_ordered";
const materializationFromOption = (
value: unknown,
fallbackVersion: string,
) => {
const object = asObject(value);
if (!object) {
return undefined;
}
const classRef = symbolRefFromOption(object.class, {
namespace: "",
name: "",
version: fallbackVersion,
});
if (!classRef.name) {
throw new Error("Edge materialization requires class");
}
return {
class: classRef,
connectProjection: readString(object, "connect_projection") ?? "",
};
};
const endpointFromOption = (params: { const endpointFromOption = (params: {
option: unknown; option: unknown;
fallbackClass?: SymbolRef; fallbackClass?: SymbolRef;
@@ -453,6 +482,10 @@ const endpointFromOption = (params: {
const cardinality = object?.cardinality === undefined const cardinality = object?.cardinality === undefined
? params.fallbackCardinality ? params.fallbackCardinality
: cardinalityFromOption(object.cardinality); : cardinalityFromOption(object.cardinality);
const materialization = materializationFromOption(
object?.materialize,
params.fallbackVersion,
);
if (!classRef?.name && !interfaceRef?.name) { if (!classRef?.name && !interfaceRef?.name) {
throw new Error(`Edge endpoint ${projection} requires class or interface`); throw new Error(`Edge endpoint ${projection} requires class or interface`);
} }
@@ -462,6 +495,7 @@ const endpointFromOption = (params: {
projection, projection,
cardinality, cardinality,
indexed: readBoolean(object, "indexed") ?? false, indexed: readBoolean(object, "indexed") ?? false,
...(materialization ? { materialization } : {}),
}; };
}; };
@@ -827,6 +861,9 @@ const interfaceSchemaFromType = (
), ),
sourceFile, sourceFile,
protoMessage: type.fullName, protoMessage: type.fullName,
typeParameters: asArray(interfaceOption.type_parameter)
.map((entry) => String(entry).trim())
.filter(Boolean),
}; };
}; };
@@ -857,11 +894,10 @@ const walkServices = (
} }
}; };
export const compileCaminoSchema = async ( export const loadCaminoSchemaRoot = async (
sourceFile: string, sourceFile: string,
): Promise<SchemaCompileResult> => { ): Promise<{ absoluteSourceFile: string; root: protobuf.Root }> => {
const absoluteSourceFile = path.resolve(sourceFile); const absoluteSourceFile = path.resolve(sourceFile);
const defaults = extractFileDefaults(absoluteSourceFile);
const root = new protobuf.Root(); const root = new protobuf.Root();
const envIncludeDirs = [ const envIncludeDirs = [
process.env.QUIXOS_PROTO_PATH, process.env.QUIXOS_PROTO_PATH,
@@ -896,6 +932,15 @@ export const compileCaminoSchema = async (
await root.load(absoluteSourceFile, { keepCase: true }); await root.load(absoluteSourceFile, { keepCase: true });
root.resolveAll(); root.resolveAll();
return { absoluteSourceFile, root };
};
export const compileCaminoSchema = async (
sourceFile: string,
): Promise<SchemaCompileResult> => {
const { absoluteSourceFile, root } = await loadCaminoSchemaRoot(sourceFile);
const defaults = extractFileDefaults(absoluteSourceFile);
const operationServices: OperationServiceSchema[] = []; const operationServices: OperationServiceSchema[] = [];
walkServices(root, (service) => { walkServices(root, (service) => {
operationServices.push( operationServices.push(
+9
View File
@@ -28,6 +28,11 @@ export type Cardinality =
export type EdgeDirectionality = "directed" | "undirected"; export type EdgeDirectionality = "directed" | "undirected";
export type EdgeMaterialization = {
class: SymbolRef;
connectProjection: string;
};
export type FieldStorageKind = export type FieldStorageKind =
| "stored" | "stored"
| "derived" | "derived"
@@ -64,6 +69,8 @@ export type InterfaceFieldContract = {
watchable: boolean; watchable: boolean;
typeParam?: string; typeParam?: string;
type?: TypeRef; type?: TypeRef;
refTargetTypeParam?: string;
edgeCardinality?: Cardinality;
}; };
export type InterfaceImplementation = { export type InterfaceImplementation = {
@@ -90,6 +97,7 @@ export type EdgeEndpointSchema = {
projection: string; projection: string;
cardinality: Cardinality; cardinality: Cardinality;
indexed: boolean; indexed: boolean;
materialization?: EdgeMaterialization;
}; };
export type EdgeSchema = { export type EdgeSchema = {
@@ -156,6 +164,7 @@ export type InterfaceSchema = {
fields: FieldSchema[]; fields: FieldSchema[];
sourceFile: string; sourceFile: string;
protoMessage: string; protoMessage: string;
typeParameters: string[];
}; };
export type SchemaCompileResult = { export type SchemaCompileResult = {