Compare commits
18 Commits
0fb0da6b18
...
exported
| Author | SHA1 | Date | |
|---|---|---|---|
| a83c43f9d9 | |||
| 8fc1060b65 | |||
| cad6057316 | |||
| b1d0667045 | |||
| 1df06f4be7 | |||
| 9674ffa108 | |||
| bd684cd07d | |||
| 02e2313aa9 | |||
| 765c321f8a | |||
| df4946e016 | |||
| fa6939d40c | |||
| d2e3172a62 | |||
| 0f2e0f6d89 | |||
| 415eb6b411 | |||
| 5ab1ec5428 | |||
| 85e0dafbeb | |||
| 9c7614cac1 | |||
| b4ab4c49c9 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git",
|
||||
"sourceCommit": "e18cccf3ee47a7b6467b3f922fc5e9a67321d4b7",
|
||||
"sourceCommit": "2acad11c4bcae2bbde2180327d45d5db9cffc9ed",
|
||||
"sourcePath": "quixos-instance/packages/camino-package-runtime",
|
||||
"exportName": "camino-package-runtime",
|
||||
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
|
||||
|
||||
Binary file not shown.
Vendored
+82
-4
@@ -436,9 +436,13 @@ export type AddEdgeRequest = Message<"camino.AddEdgeRequest"> & {
|
||||
[key: string]: Value;
|
||||
};
|
||||
/**
|
||||
* @generated from field: optional int32 ordinal = 5;
|
||||
* @generated from field: optional int32 source_ordinal = 5;
|
||||
*/
|
||||
ordinal?: number | undefined;
|
||||
sourceOrdinal?: number | undefined;
|
||||
/**
|
||||
* @generated from field: optional int32 target_ordinal = 6;
|
||||
*/
|
||||
targetOrdinal?: number | undefined;
|
||||
};
|
||||
/**
|
||||
* Describes the message camino.AddEdgeRequest.
|
||||
@@ -487,6 +491,46 @@ export type ListEdgesResponse = Message<"camino.ListEdgesResponse"> & {
|
||||
* Use `create(ListEdgesResponseSchema)` to create a new message.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -656,6 +700,8 @@ export type CaminoEdge = Message<"camino.CaminoEdge"> & {
|
||||
*/
|
||||
toObjectId: string;
|
||||
/**
|
||||
* Legacy canonical source-side projection fields. Prefer endpoint fields.
|
||||
*
|
||||
* @generated from field: string source_field = 5;
|
||||
*/
|
||||
sourceField: string;
|
||||
@@ -674,9 +720,9 @@ export type CaminoEdge = Message<"camino.CaminoEdge"> & {
|
||||
[key: string]: Value;
|
||||
};
|
||||
/**
|
||||
* @generated from field: optional int32 ordinal = 9;
|
||||
* @generated from field: optional int32 from_ordinal = 9;
|
||||
*/
|
||||
ordinal?: number | undefined;
|
||||
fromOrdinal?: number | undefined;
|
||||
/**
|
||||
* @generated from field: string created_at = 10;
|
||||
*/
|
||||
@@ -685,6 +731,30 @@ export type CaminoEdge = Message<"camino.CaminoEdge"> & {
|
||||
* @generated from field: string updated_at = 11;
|
||||
*/
|
||||
updatedAt: string;
|
||||
/**
|
||||
* @generated from field: string from_projection = 12;
|
||||
*/
|
||||
fromProjection: string;
|
||||
/**
|
||||
* @generated from field: string to_projection = 13;
|
||||
*/
|
||||
toProjection: string;
|
||||
/**
|
||||
* @generated from field: string from_cardinality = 14;
|
||||
*/
|
||||
fromCardinality: string;
|
||||
/**
|
||||
* @generated from field: string to_cardinality = 15;
|
||||
*/
|
||||
toCardinality: string;
|
||||
/**
|
||||
* @generated from field: optional int32 to_ordinal = 16;
|
||||
*/
|
||||
toOrdinal?: number | undefined;
|
||||
/**
|
||||
* @generated from field: string directionality = 17;
|
||||
*/
|
||||
directionality: string;
|
||||
};
|
||||
/**
|
||||
* Describes the message camino.CaminoEdge.
|
||||
@@ -801,6 +871,14 @@ export declare const CaminoService: GenService<{
|
||||
input: typeof ListEdgesRequestSchema;
|
||||
output: typeof ListEdgesResponseSchema;
|
||||
};
|
||||
/**
|
||||
* @generated from rpc camino.CaminoService.ResolveEdge
|
||||
*/
|
||||
resolveEdge: {
|
||||
methodKind: "unary";
|
||||
input: typeof ResolveEdgeRequestSchema;
|
||||
output: typeof ResolveEdgeResponseSchema;
|
||||
};
|
||||
/**
|
||||
* @generated from rpc camino.CaminoService.RemoveEdge
|
||||
*/
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+20
-10
File diff suppressed because one or more lines are too long
Vendored
+95
-1
@@ -323,6 +323,14 @@ export type InterfaceFieldContract = Message<"camino.InterfaceFieldContract"> &
|
||||
* @generated from field: camino.TypeRef type = 6;
|
||||
*/
|
||||
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.
|
||||
@@ -371,6 +379,10 @@ export type InterfaceSchema = Message<"camino.InterfaceSchema"> & {
|
||||
* @generated from field: string proto_message = 5;
|
||||
*/
|
||||
protoMessage: string;
|
||||
/**
|
||||
* @generated from field: repeated string type_parameters = 6;
|
||||
*/
|
||||
typeParameters: string[];
|
||||
};
|
||||
/**
|
||||
* Describes the message camino.InterfaceSchema.
|
||||
@@ -404,6 +416,8 @@ export type EdgeSchema = Message<"camino.EdgeSchema"> & {
|
||||
*/
|
||||
id?: SymbolRef | undefined;
|
||||
/**
|
||||
* Legacy/source-side projection fields. Prefer from_endpoint/to_endpoint.
|
||||
*
|
||||
* @generated from field: string source_field = 2;
|
||||
*/
|
||||
sourceField: string;
|
||||
@@ -427,12 +441,88 @@ export type EdgeSchema = Message<"camino.EdgeSchema"> & {
|
||||
* @generated from field: repeated camino.FieldSchema fields = 7;
|
||||
*/
|
||||
fields: FieldSchema[];
|
||||
/**
|
||||
* @generated from field: camino.EdgeEndpointSchema from_endpoint = 8;
|
||||
*/
|
||||
fromEndpoint?: EdgeEndpointSchema | undefined;
|
||||
/**
|
||||
* @generated from field: camino.EdgeEndpointSchema to_endpoint = 9;
|
||||
*/
|
||||
toEndpoint?: EdgeEndpointSchema | undefined;
|
||||
/**
|
||||
* @generated from field: camino.SymbolRef declaring_class = 10;
|
||||
*/
|
||||
declaringClass?: SymbolRef | undefined;
|
||||
/**
|
||||
* @generated from field: repeated camino.SymbolRef tags = 11;
|
||||
*/
|
||||
tags: SymbolRef[];
|
||||
/**
|
||||
* @generated from field: repeated camino.SymbolRef implements = 12;
|
||||
*/
|
||||
implements: SymbolRef[];
|
||||
/**
|
||||
* @generated from field: bool undirected = 13;
|
||||
*/
|
||||
undirected: boolean;
|
||||
};
|
||||
/**
|
||||
* Describes the message camino.EdgeSchema.
|
||||
* Use `create(EdgeSchemaSchema)` to create a new message.
|
||||
*/
|
||||
export declare const EdgeSchemaSchema: GenMessage<EdgeSchema>;
|
||||
/**
|
||||
* @generated from message camino.EdgeEndpointSchema
|
||||
*/
|
||||
export type EdgeEndpointSchema = Message<"camino.EdgeEndpointSchema"> & {
|
||||
/**
|
||||
* @generated from field: camino.SymbolRef class = 1;
|
||||
*/
|
||||
class?: SymbolRef | undefined;
|
||||
/**
|
||||
* @generated from field: camino.SymbolRef interface = 2;
|
||||
*/
|
||||
interface?: SymbolRef | undefined;
|
||||
/**
|
||||
* @generated from field: string projection = 3;
|
||||
*/
|
||||
projection: string;
|
||||
/**
|
||||
* @generated from field: camino.Cardinality cardinality = 4;
|
||||
*/
|
||||
cardinality: Cardinality;
|
||||
/**
|
||||
* @generated from field: bool indexed = 5;
|
||||
*/
|
||||
indexed: boolean;
|
||||
/**
|
||||
* @generated from field: camino.EdgeMaterialization materialize = 6;
|
||||
*/
|
||||
materialize?: EdgeMaterialization | undefined;
|
||||
};
|
||||
/**
|
||||
* Describes the message camino.EdgeEndpointSchema.
|
||||
* Use `create(EdgeEndpointSchemaSchema)` to create a new message.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -613,7 +703,11 @@ export declare enum Cardinality {
|
||||
/**
|
||||
* @generated from enum value: MANY_ORDERED = 5;
|
||||
*/
|
||||
MANY_ORDERED = 5
|
||||
MANY_ORDERED = 5,
|
||||
/**
|
||||
* @generated from enum value: MANY_UNIQUE_ORDERED = 6;
|
||||
*/
|
||||
MANY_UNIQUE_ORDERED = 6
|
||||
}
|
||||
/**
|
||||
* Describes the enum camino.Cardinality.
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+21
-7
File diff suppressed because one or more lines are too long
+243
-18
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { compileCaminoSchema } from "./schema-compiler.js";
|
||||
import protobuf from "protobufjs";
|
||||
import { compileCaminoSchema, loadCaminoSchemaRoot, } from "./schema-compiler.js";
|
||||
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);
|
||||
};
|
||||
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 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 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) {
|
||||
return type.enumValues.map(stringLiteral).join(" | ");
|
||||
}
|
||||
switch (type.protoType.replace(/^\./, "")) {
|
||||
const protoType = normalizedProtoType(type.protoType);
|
||||
switch (protoType) {
|
||||
case "string":
|
||||
case "google.protobuf.StringValue":
|
||||
return "string";
|
||||
@@ -52,14 +59,26 @@ const tsTypeForTypeRef = (type) => {
|
||||
case "google.protobuf.BytesValue":
|
||||
return "Uint8Array";
|
||||
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";
|
||||
}
|
||||
};
|
||||
const tsTypeForField = (field) => tsTypeForTypeRef(field.type);
|
||||
const tsValueTypeForField = (field) => field.repeated ? `${tsTypeForField(field)}[]` : tsTypeForField(field);
|
||||
const tsFieldApiType = (field) => field.conflict === "crdt"
|
||||
? `CrdtField<${tsValueTypeForField(field)}>`
|
||||
: `Field<${tsValueTypeForField(field)}>`;
|
||||
const tsTypeForField = (field, root) => tsTypeForTypeRef(field.type, root);
|
||||
const tsValueTypeForField = (field, root) => field.repeated ? `${tsTypeForField(field, root)}[]` : tsTypeForField(field, root);
|
||||
const tsFieldApiType = (field, root) => field.conflict === "crdt"
|
||||
? `CrdtField<${tsValueTypeForField(field, root)}>`
|
||||
: `Field<${tsValueTypeForField(field, root)}>`;
|
||||
const functionExportName = (fn, fallback) => {
|
||||
if (isIdentifier(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}`);
|
||||
};
|
||||
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 crdtStorageTypeId = (field) => field.type.protoType ||
|
||||
(field.type.symbol
|
||||
@@ -96,8 +177,8 @@ ${bindings}
|
||||
};`;
|
||||
})
|
||||
.join("\n\n");
|
||||
const interfaceMixins = (schema) => schema.implements.map((implementation) => {
|
||||
const typeArgs = implementation.typeBindings.map((binding) => tsTypeForTypeRef(binding.type));
|
||||
const interfaceMixins = (schema, root) => schema.implements.map((implementation) => {
|
||||
const typeArgs = implementation.typeBindings.map((binding) => tsTypeForTypeRef(binding.type, root));
|
||||
return `${interfaceTypeName(implementation)}${typeArgs.length > 0 ? `<${typeArgs.join(", ")}>` : ""}`;
|
||||
});
|
||||
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 objectTypeName = `${className}Object`;
|
||||
const refTypeName = `${className}Ref`;
|
||||
@@ -134,7 +215,7 @@ const renderRuntime = (schema) => {
|
||||
const serveName = `serve${className}Runtime`;
|
||||
const createObjectName = `create${className}Object`;
|
||||
const interfaceRefTypes = renderInterfaceRefTypes(schema);
|
||||
const refType = [`ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema)]
|
||||
const refType = [`ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema, root)]
|
||||
.join(" & ");
|
||||
const edgeFields = new Set(schema.edges.map((edge) => edge.sourceField));
|
||||
const writableFields = schema.fields.filter((field) => field.storage.kind === "stored" &&
|
||||
@@ -199,12 +280,14 @@ const renderRuntime = (schema) => {
|
||||
serveQuixosPackageRuntime,
|
||||
} 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};
|
||||
|
||||
export type ${objectTypeName} = {
|
||||
id: ${refTypeName};
|
||||
${writableFields
|
||||
.map((field) => ` ${camel(field.name)}: ${tsFieldApiType(field)};`)
|
||||
.map((field) => ` ${camel(field.name)}: ${tsFieldApiType(field, root)};`)
|
||||
.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 className = schema.id.name;
|
||||
return `${generatedWarning}import * as implementation from ${stringLiteral(implementationImport)};
|
||||
@@ -280,10 +492,13 @@ serve${className}Runtime(implementation);
|
||||
`;
|
||||
};
|
||||
const main = async () => {
|
||||
const sourceFile = required(process.argv[2]);
|
||||
const outDir = required(process.argv[3]);
|
||||
const implementationImport = process.argv[4] ?? "../task.impl.js";
|
||||
const reactOnly = process.argv[2] === "--react-only";
|
||||
const offset = reactOnly ? 1 : 0;
|
||||
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 { root } = await loadCaminoSchemaRoot(sourceFile);
|
||||
if (compiled.classes.length !== 1) {
|
||||
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 });
|
||||
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));
|
||||
}
|
||||
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();
|
||||
|
||||
Vendored
+5
@@ -1,3 +1,8 @@
|
||||
import protobuf from "protobufjs";
|
||||
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>;
|
||||
//# sourceMappingURL=schema-compiler.d.ts.map
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"schema-compiler.d.ts","sourceRoot":"","sources":["../src/schema-compiler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAeV,mBAAmB,EAGpB,MAAM,gBAAgB,CAAC;AA0sBxB,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"}
|
||||
Vendored
+147
-18
@@ -114,6 +114,10 @@ const interfaceFieldContractFromOption = (value, schemaVersion) => {
|
||||
return undefined;
|
||||
}
|
||||
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 {
|
||||
required: readBoolean(object, "required") ?? false,
|
||||
readable: readBoolean(object, "readable") ?? false,
|
||||
@@ -123,6 +127,8 @@ const interfaceFieldContractFromOption = (value, schemaVersion) => {
|
||||
? { typeParam: readString(object, "type_param") }
|
||||
: {}),
|
||||
...(type ? { type } : {}),
|
||||
...(refTargetTypeParam ? { refTargetTypeParam } : {}),
|
||||
...(edgeCardinality ? { edgeCardinality } : {}),
|
||||
};
|
||||
};
|
||||
const functionRefFromOption = (value) => {
|
||||
@@ -173,7 +179,8 @@ const cardinalityFromOption = (value) => {
|
||||
normalized === "exactly_one" ||
|
||||
normalized === "many" ||
|
||||
normalized === "many_unique" ||
|
||||
normalized === "many_ordered") {
|
||||
normalized === "many_ordered" ||
|
||||
normalized === "many_unique_ordered") {
|
||||
return normalized;
|
||||
}
|
||||
throw new Error(`Unknown Camino cardinality: ${String(value)}`);
|
||||
@@ -303,6 +310,92 @@ const fieldSchemaFromField = (field, schemaVersion) => {
|
||||
isDisplayLabel: getReflectionOption(field, DISPLAY_LABEL_OPTION) === true,
|
||||
};
|
||||
};
|
||||
const isManyCardinality = (cardinality) => cardinality === "many" ||
|
||||
cardinality === "many_unique" ||
|
||||
cardinality === "many_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 object = asObject(params.option);
|
||||
const classRef = asObject(object?.class)
|
||||
? symbolRefFromOption(object?.class, {
|
||||
namespace: "",
|
||||
name: "",
|
||||
version: params.fallbackVersion,
|
||||
})
|
||||
: params.fallbackClass;
|
||||
const interfaceRef = asObject(object?.interface)
|
||||
? symbolRefFromOption(object?.interface, {
|
||||
namespace: "",
|
||||
name: "",
|
||||
version: params.fallbackVersion,
|
||||
})
|
||||
: undefined;
|
||||
const projection = readString(object, "projection") ?? params.fallbackProjection;
|
||||
const cardinality = object?.cardinality === undefined
|
||||
? params.fallbackCardinality
|
||||
: cardinalityFromOption(object.cardinality);
|
||||
const materialization = materializationFromOption(object?.materialize, params.fallbackVersion);
|
||||
if (!classRef?.name && !interfaceRef?.name) {
|
||||
throw new Error(`Edge endpoint ${projection} requires class or interface`);
|
||||
}
|
||||
return {
|
||||
...(classRef?.name ? { class: classRef } : {}),
|
||||
...(interfaceRef?.name ? { interface: interfaceRef } : {}),
|
||||
projection,
|
||||
cardinality,
|
||||
indexed: readBoolean(object, "indexed") ?? false,
|
||||
...(materialization ? { materialization } : {}),
|
||||
};
|
||||
};
|
||||
const symbolKey = (ref) => ref ? [ref.namespace, ref.name, ref.version, ref.hash ?? ""].join(":") : "";
|
||||
const endpointRoleKey = (endpoint) => [
|
||||
symbolKey(endpoint.class),
|
||||
symbolKey(endpoint.interface),
|
||||
endpoint.projection,
|
||||
endpoint.cardinality,
|
||||
].join("|");
|
||||
const normalizeEdgeEndpoints = (params) => {
|
||||
if (params.directionality === "directed") {
|
||||
return {
|
||||
fromEndpoint: params.fromEndpoint,
|
||||
toEndpoint: params.toEndpoint,
|
||||
};
|
||||
}
|
||||
return endpointRoleKey(params.fromEndpoint) <= endpointRoleKey(params.toEndpoint)
|
||||
? {
|
||||
fromEndpoint: params.fromEndpoint,
|
||||
toEndpoint: params.toEndpoint,
|
||||
}
|
||||
: {
|
||||
fromEndpoint: params.toEndpoint,
|
||||
toEndpoint: params.fromEndpoint,
|
||||
};
|
||||
};
|
||||
const symbolArrayFromOption = (value, defaults) => asArray(value)
|
||||
.map((item) => symbolRefFromOption(item, {
|
||||
namespace: defaults.schemaNamespace,
|
||||
name: "",
|
||||
version: defaults.schemaVersion,
|
||||
}))
|
||||
.filter((symbol) => Boolean(symbol.name));
|
||||
const isStringType = (type) => type.protoType === "string" || type.protoType === "google.protobuf.StringValue";
|
||||
const validateDisplayLabelFields = (type, fields, operationServices) => {
|
||||
const displayLabelFields = fields.filter((field) => field.isDisplayLabel);
|
||||
@@ -397,45 +490,74 @@ const classSchemaFromType = (type, sourceFile, defaults, operationServices) => {
|
||||
if (!edgeOption) {
|
||||
return [];
|
||||
}
|
||||
const targetClass = symbolRefFromOption(edgeOption.target, {
|
||||
const legacyTargetClass = symbolRefFromOption(edgeOption.target, {
|
||||
namespace: defaults.schemaNamespace,
|
||||
name: "",
|
||||
version: defaults.schemaVersion,
|
||||
});
|
||||
if (!targetClass.name) {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} requires target`);
|
||||
}
|
||||
const cardinality = cardinalityFromOption(edgeOption.cardinality);
|
||||
const declaredFromEndpoint = endpointFromOption({
|
||||
option: edgeOption.this_endpoint,
|
||||
fallbackClass: classId,
|
||||
fallbackProjection: field.name,
|
||||
fallbackCardinality: cardinality,
|
||||
fallbackVersion: defaults.schemaVersion,
|
||||
});
|
||||
const declaredToEndpoint = endpointFromOption({
|
||||
option: edgeOption.other_endpoint,
|
||||
fallbackClass: legacyTargetClass.name ? legacyTargetClass : undefined,
|
||||
fallbackProjection: readString(edgeOption, "inverse") ?? "",
|
||||
fallbackCardinality: "many",
|
||||
fallbackVersion: defaults.schemaVersion,
|
||||
});
|
||||
if (!declaredToEndpoint.projection) {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} requires other_endpoint.projection or inverse`);
|
||||
}
|
||||
const sourceType = typeRefForField(field, defaults.schemaVersion);
|
||||
if (sourceType.symbol?.namespace !== "camino" ||
|
||||
sourceType.symbol.name !== "Ref") {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must use camino.Ref`);
|
||||
}
|
||||
const isMany = cardinality === "many" ||
|
||||
cardinality === "many_unique" ||
|
||||
cardinality === "many_ordered";
|
||||
const isMany = isManyCardinality(declaredFromEndpoint.cardinality);
|
||||
if (isMany && !field.repeated) {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${cardinality}`);
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${declaredFromEndpoint.cardinality}`);
|
||||
}
|
||||
if (!isMany && field.repeated) {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${cardinality}`);
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${declaredFromEndpoint.cardinality}`);
|
||||
}
|
||||
const directionality = edgeOption.undirected === true ? "undirected" : "directed";
|
||||
const { fromEndpoint, toEndpoint } = normalizeEdgeEndpoints({
|
||||
directionality,
|
||||
fromEndpoint: declaredFromEndpoint,
|
||||
toEndpoint: declaredToEndpoint,
|
||||
});
|
||||
const edgeId = symbolRefFromOption(edgeOption.id, {
|
||||
namespace: defaults.schemaNamespace,
|
||||
name: `${type.name}.${field.name}`,
|
||||
version: defaults.schemaVersion,
|
||||
});
|
||||
const fallbackToClass = toEndpoint.class ?? {
|
||||
namespace: "",
|
||||
name: "",
|
||||
version: "",
|
||||
};
|
||||
return [
|
||||
{
|
||||
id: edgeId,
|
||||
sourceField: field.name,
|
||||
fromClass: classId,
|
||||
toClass: targetClass,
|
||||
cardinality,
|
||||
...(readString(edgeOption, "inverse")
|
||||
? { inverse: readString(edgeOption, "inverse") }
|
||||
directionality,
|
||||
sourceField: fromEndpoint.projection,
|
||||
fromClass: fromEndpoint.class ?? classId,
|
||||
toClass: fallbackToClass,
|
||||
cardinality: fromEndpoint.cardinality,
|
||||
...(toEndpoint.projection
|
||||
? { inverse: toEndpoint.projection }
|
||||
: {}),
|
||||
fields: [],
|
||||
fromEndpoint,
|
||||
toEndpoint,
|
||||
declaringClass: classId,
|
||||
tags: symbolArrayFromOption(edgeOption.tag, defaults),
|
||||
implements: symbolArrayFromOption(edgeOption.implements, defaults),
|
||||
},
|
||||
];
|
||||
});
|
||||
@@ -510,6 +632,9 @@ const interfaceSchemaFromType = (type, sourceFile, defaults) => {
|
||||
fields: type.fieldsArray.map((field) => fieldSchemaFromField(field, defaults.schemaVersion)),
|
||||
sourceFile,
|
||||
protoMessage: type.fullName,
|
||||
typeParameters: asArray(interfaceOption.type_parameter)
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean),
|
||||
};
|
||||
};
|
||||
const walkTypes = (namespace, visit) => {
|
||||
@@ -533,9 +658,8 @@ const walkServices = (namespace, visit) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
export const compileCaminoSchema = async (sourceFile) => {
|
||||
export const loadCaminoSchemaRoot = async (sourceFile) => {
|
||||
const absoluteSourceFile = path.resolve(sourceFile);
|
||||
const defaults = extractFileDefaults(absoluteSourceFile);
|
||||
const root = new protobuf.Root();
|
||||
const envIncludeDirs = [
|
||||
process.env.QUIXOS_PROTO_PATH,
|
||||
@@ -568,6 +692,11 @@ export const compileCaminoSchema = async (sourceFile) => {
|
||||
};
|
||||
await root.load(absoluteSourceFile, { keepCase: true });
|
||||
root.resolveAll();
|
||||
return { absoluteSourceFile, root };
|
||||
};
|
||||
export const compileCaminoSchema = async (sourceFile) => {
|
||||
const { absoluteSourceFile, root } = await loadCaminoSchemaRoot(sourceFile);
|
||||
const defaults = extractFileDefaults(absoluteSourceFile);
|
||||
const operationServices = [];
|
||||
walkServices(root, (service) => {
|
||||
operationServices.push(operationServiceFromService(service, defaults.schemaVersion));
|
||||
|
||||
Vendored
+23
-1
@@ -12,7 +12,12 @@ export type FunctionRef = {
|
||||
versionRef?: string;
|
||||
};
|
||||
export type ConflictStrategy = "replace" | "preserve_conflicts" | "crdt";
|
||||
export type Cardinality = "optional_one" | "exactly_one" | "many" | "many_unique" | "many_ordered";
|
||||
export type Cardinality = "optional_one" | "exactly_one" | "many" | "many_unique" | "many_ordered" | "many_unique_ordered";
|
||||
export type EdgeDirectionality = "directed" | "undirected";
|
||||
export type EdgeMaterialization = {
|
||||
class: SymbolRef;
|
||||
connectProjection: string;
|
||||
};
|
||||
export type FieldStorageKind = "stored" | "derived" | "lazy" | "external" | "static_final";
|
||||
export type FieldStorage = {
|
||||
kind: FieldStorageKind;
|
||||
@@ -39,6 +44,8 @@ export type InterfaceFieldContract = {
|
||||
watchable: boolean;
|
||||
typeParam?: string;
|
||||
type?: TypeRef;
|
||||
refTargetTypeParam?: string;
|
||||
edgeCardinality?: Cardinality;
|
||||
};
|
||||
export type InterfaceImplementation = {
|
||||
interface: SymbolRef;
|
||||
@@ -56,14 +63,28 @@ export type FieldSchema = {
|
||||
interfaceContract?: InterfaceFieldContract;
|
||||
isDisplayLabel: boolean;
|
||||
};
|
||||
export type EdgeEndpointSchema = {
|
||||
class?: SymbolRef;
|
||||
interface?: SymbolRef;
|
||||
projection: string;
|
||||
cardinality: Cardinality;
|
||||
indexed: boolean;
|
||||
materialization?: EdgeMaterialization;
|
||||
};
|
||||
export type EdgeSchema = {
|
||||
id: SymbolRef;
|
||||
directionality: EdgeDirectionality;
|
||||
sourceField: string;
|
||||
fromClass: SymbolRef;
|
||||
toClass: SymbolRef;
|
||||
cardinality: Cardinality;
|
||||
inverse?: string;
|
||||
fields: FieldSchema[];
|
||||
fromEndpoint: EdgeEndpointSchema;
|
||||
toEndpoint: EdgeEndpointSchema;
|
||||
declaringClass: SymbolRef;
|
||||
tags: SymbolRef[];
|
||||
implements: SymbolRef[];
|
||||
};
|
||||
export type MethodSchema = {
|
||||
name: string;
|
||||
@@ -107,6 +128,7 @@ export type InterfaceSchema = {
|
||||
fields: FieldSchema[];
|
||||
sourceFile: string;
|
||||
protoMessage: string;
|
||||
typeParameters: string[];
|
||||
};
|
||||
export type SchemaCompileResult = {
|
||||
sourceFile: string;
|
||||
|
||||
Vendored
+1
-1
@@ -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
@@ -74,17 +74,17 @@
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1783954303,
|
||||
"narHash": "sha256-xywk5pWpvOD2rWmNaZ3arYgkx3Km/qsos4g1uB2pXVA=",
|
||||
"lastModified": 1784395768,
|
||||
"narHash": "sha256-g03xOnzJHiL/edNV6nqURtaPqiapq2uCm7wQTZh4b5s=",
|
||||
"ref": "refs/heads/exported",
|
||||
"rev": "ff07b0d7933ad01c0b52bae2dcab1445e8e91eeb",
|
||||
"revCount": 14,
|
||||
"rev": "e6b34535afa6e94a118e1f9d082054855e7df632",
|
||||
"revCount": 24,
|
||||
"type": "git",
|
||||
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
||||
},
|
||||
"original": {
|
||||
"ref": "refs/heads/exported",
|
||||
"rev": "ff07b0d7933ad01c0b52bae2dcab1445e8e91eeb",
|
||||
"rev": "e6b34535afa6e94a118e1f9d082054855e7df632",
|
||||
"type": "git",
|
||||
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
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=ff07b0d7933ad01c0b52bae2dcab1445e8e91eeb";
|
||||
quixos-protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/heads/exported&rev=e6b34535afa6e94a118e1f9d082054855e7df632";
|
||||
quixosNixHelpers = {
|
||||
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/heads/exported&rev=1f0c39b01501d646fe97dfc6e5777ccab0bde2f1";
|
||||
flake = false;
|
||||
|
||||
+108
-14
File diff suppressed because one or more lines are too long
+123
-7
File diff suppressed because one or more lines are too long
+284
-19
@@ -1,7 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { compileCaminoSchema } from "./schema-compiler.js";
|
||||
import protobuf from "protobufjs";
|
||||
import {
|
||||
compileCaminoSchema,
|
||||
loadCaminoSchemaRoot,
|
||||
} from "./schema-compiler.js";
|
||||
import type {
|
||||
ClassSchema,
|
||||
FieldSchema,
|
||||
@@ -12,7 +16,7 @@ import type {
|
||||
|
||||
const usage = (): never => {
|
||||
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);
|
||||
};
|
||||
@@ -46,11 +50,22 @@ const classId = (schema: ClassSchema) =>
|
||||
const symbolId = (value: { namespace: string; name: string; version: string; hash?: string }) =>
|
||||
`${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) {
|
||||
return type.enumValues.map(stringLiteral).join(" | ");
|
||||
}
|
||||
switch (type.protoType.replace(/^\./, "")) {
|
||||
const protoType = normalizedProtoType(type.protoType);
|
||||
switch (protoType) {
|
||||
case "string":
|
||||
case "google.protobuf.StringValue":
|
||||
return "string";
|
||||
@@ -80,19 +95,31 @@ const tsTypeForTypeRef = (type: TypeRef): string => {
|
||||
case "google.protobuf.BytesValue":
|
||||
return "Uint8Array";
|
||||
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";
|
||||
}
|
||||
};
|
||||
|
||||
const tsTypeForField = (field: FieldSchema): string => tsTypeForTypeRef(field.type);
|
||||
const tsTypeForField = (field: FieldSchema, root?: protobuf.Root): string =>
|
||||
tsTypeForTypeRef(field.type, root);
|
||||
|
||||
const tsValueTypeForField = (field: FieldSchema) =>
|
||||
field.repeated ? `${tsTypeForField(field)}[]` : tsTypeForField(field);
|
||||
const tsValueTypeForField = (field: FieldSchema, root?: protobuf.Root) =>
|
||||
field.repeated ? `${tsTypeForField(field, root)}[]` : tsTypeForField(field, root);
|
||||
|
||||
const tsFieldApiType = (field: FieldSchema) =>
|
||||
const tsFieldApiType = (field: FieldSchema, root?: protobuf.Root) =>
|
||||
field.conflict === "crdt"
|
||||
? `CrdtField<${tsValueTypeForField(field)}>`
|
||||
: `Field<${tsValueTypeForField(field)}>`;
|
||||
? `CrdtField<${tsValueTypeForField(field, root)}>`
|
||||
: `Field<${tsValueTypeForField(field, root)}>`;
|
||||
|
||||
const functionExportName = (fn: FunctionRef, fallback: string) => {
|
||||
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 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 = {
|
||||
exportName: string;
|
||||
symbol: string;
|
||||
@@ -158,10 +253,10 @@ ${bindings}
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const interfaceMixins = (schema: ClassSchema) =>
|
||||
const interfaceMixins = (schema: ClassSchema, root: protobuf.Root) =>
|
||||
schema.implements.map((implementation) => {
|
||||
const typeArgs = implementation.typeBindings.map((binding) =>
|
||||
tsTypeForTypeRef(binding.type),
|
||||
tsTypeForTypeRef(binding.type, root),
|
||||
);
|
||||
return `${interfaceTypeName(implementation)}${
|
||||
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 objectTypeName = `${className}Object`;
|
||||
const refTypeName = `${className}Ref`;
|
||||
@@ -212,7 +307,7 @@ const renderRuntime = (schema: ClassSchema) => {
|
||||
const createObjectName = `create${className}Object`;
|
||||
const interfaceRefTypes = renderInterfaceRefTypes(schema);
|
||||
const refType =
|
||||
[ `ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema)]
|
||||
[ `ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema, root)]
|
||||
.join(" & ");
|
||||
const edgeFields = new Set(schema.edges.map((edge) => edge.sourceField));
|
||||
const writableFields = schema.fields.filter(
|
||||
@@ -291,6 +386,8 @@ const renderRuntime = (schema: ClassSchema) => {
|
||||
serveQuixosPackageRuntime,
|
||||
} 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};
|
||||
|
||||
export type ${objectTypeName} = {
|
||||
@@ -298,7 +395,7 @@ export type ${objectTypeName} = {
|
||||
${writableFields
|
||||
.map(
|
||||
(field) =>
|
||||
` ${camel(field.name)}: ${tsFieldApiType(field)};`,
|
||||
` ${camel(field.name)}: ${tsFieldApiType(field, root)};`,
|
||||
)
|
||||
.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 className = schema.id.name;
|
||||
return `${generatedWarning}import * as implementation from ${stringLiteral(implementationImport)};
|
||||
@@ -389,10 +636,13 @@ serve${className}Runtime(implementation);
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const sourceFile = required(process.argv[2]);
|
||||
const outDir = required(process.argv[3]);
|
||||
const implementationImport = process.argv[4] ?? "../task.impl.js";
|
||||
const reactOnly = process.argv[2] === "--react-only";
|
||||
const offset = reactOnly ? 1 : 0;
|
||||
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 { root } = await loadCaminoSchemaRoot(sourceFile);
|
||||
if (compiled.classes.length !== 1) {
|
||||
throw new Error(
|
||||
`Expected exactly one Camino class in ${sourceFile}, found ${compiled.classes.length}`,
|
||||
@@ -404,11 +654,26 @@ const main = async () => {
|
||||
}
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
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),
|
||||
);
|
||||
}
|
||||
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();
|
||||
|
||||
+189
-20
@@ -18,6 +18,7 @@ import type {
|
||||
MigrationSpec,
|
||||
SchemaCompileResult,
|
||||
SymbolRef,
|
||||
EdgeDirectionality,
|
||||
TypeRef,
|
||||
} from "./schema-ir.js";
|
||||
|
||||
@@ -186,6 +187,11 @@ const interfaceFieldContractFromOption = (
|
||||
return undefined;
|
||||
}
|
||||
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 {
|
||||
required: readBoolean(object, "required") ?? false,
|
||||
readable: readBoolean(object, "readable") ?? false,
|
||||
@@ -195,6 +201,8 @@ const interfaceFieldContractFromOption = (
|
||||
? { typeParam: readString(object, "type_param") }
|
||||
: {}),
|
||||
...(type ? { type } : {}),
|
||||
...(refTargetTypeParam ? { refTargetTypeParam } : {}),
|
||||
...(edgeCardinality ? { edgeCardinality } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -249,7 +257,8 @@ const cardinalityFromOption = (value: unknown): Cardinality => {
|
||||
normalized === "exactly_one" ||
|
||||
normalized === "many" ||
|
||||
normalized === "many_unique" ||
|
||||
normalized === "many_ordered"
|
||||
normalized === "many_ordered" ||
|
||||
normalized === "many_unique_ordered"
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
@@ -419,6 +428,124 @@ const fieldSchemaFromField = (
|
||||
};
|
||||
};
|
||||
|
||||
const isManyCardinality = (cardinality: Cardinality) =>
|
||||
cardinality === "many" ||
|
||||
cardinality === "many_unique" ||
|
||||
cardinality === "many_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: {
|
||||
option: unknown;
|
||||
fallbackClass?: SymbolRef;
|
||||
fallbackProjection: string;
|
||||
fallbackCardinality: Cardinality;
|
||||
fallbackVersion: string;
|
||||
}) => {
|
||||
const object = asObject(params.option);
|
||||
const classRef = asObject(object?.class)
|
||||
? symbolRefFromOption(object?.class, {
|
||||
namespace: "",
|
||||
name: "",
|
||||
version: params.fallbackVersion,
|
||||
})
|
||||
: params.fallbackClass;
|
||||
const interfaceRef = asObject(object?.interface)
|
||||
? symbolRefFromOption(object?.interface, {
|
||||
namespace: "",
|
||||
name: "",
|
||||
version: params.fallbackVersion,
|
||||
})
|
||||
: undefined;
|
||||
const projection = readString(object, "projection") ?? params.fallbackProjection;
|
||||
const cardinality = object?.cardinality === undefined
|
||||
? params.fallbackCardinality
|
||||
: cardinalityFromOption(object.cardinality);
|
||||
const materialization = materializationFromOption(
|
||||
object?.materialize,
|
||||
params.fallbackVersion,
|
||||
);
|
||||
if (!classRef?.name && !interfaceRef?.name) {
|
||||
throw new Error(`Edge endpoint ${projection} requires class or interface`);
|
||||
}
|
||||
return {
|
||||
...(classRef?.name ? { class: classRef } : {}),
|
||||
...(interfaceRef?.name ? { interface: interfaceRef } : {}),
|
||||
projection,
|
||||
cardinality,
|
||||
indexed: readBoolean(object, "indexed") ?? false,
|
||||
...(materialization ? { materialization } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
const symbolKey = (ref: SymbolRef | undefined) =>
|
||||
ref ? [ref.namespace, ref.name, ref.version, ref.hash ?? ""].join(":") : "";
|
||||
|
||||
const endpointRoleKey = (endpoint: ReturnType<typeof endpointFromOption>) =>
|
||||
[
|
||||
symbolKey(endpoint.class),
|
||||
symbolKey(endpoint.interface),
|
||||
endpoint.projection,
|
||||
endpoint.cardinality,
|
||||
].join("|");
|
||||
|
||||
const normalizeEdgeEndpoints = (params: {
|
||||
directionality: "directed" | "undirected";
|
||||
fromEndpoint: ReturnType<typeof endpointFromOption>;
|
||||
toEndpoint: ReturnType<typeof endpointFromOption>;
|
||||
}) => {
|
||||
if (params.directionality === "directed") {
|
||||
return {
|
||||
fromEndpoint: params.fromEndpoint,
|
||||
toEndpoint: params.toEndpoint,
|
||||
};
|
||||
}
|
||||
return endpointRoleKey(params.fromEndpoint) <= endpointRoleKey(params.toEndpoint)
|
||||
? {
|
||||
fromEndpoint: params.fromEndpoint,
|
||||
toEndpoint: params.toEndpoint,
|
||||
}
|
||||
: {
|
||||
fromEndpoint: params.toEndpoint,
|
||||
toEndpoint: params.fromEndpoint,
|
||||
};
|
||||
};
|
||||
|
||||
const symbolArrayFromOption = (
|
||||
value: unknown,
|
||||
defaults: ReturnType<typeof extractFileDefaults>,
|
||||
) =>
|
||||
asArray(value)
|
||||
.map((item) =>
|
||||
symbolRefFromOption(item, {
|
||||
namespace: defaults.schemaNamespace,
|
||||
name: "",
|
||||
version: defaults.schemaVersion,
|
||||
}),
|
||||
)
|
||||
.filter((symbol) => Boolean(symbol.name));
|
||||
|
||||
const isStringType = (type: TypeRef) =>
|
||||
type.protoType === "string" || type.protoType === "google.protobuf.StringValue";
|
||||
|
||||
@@ -568,15 +695,31 @@ const classSchemaFromType = (
|
||||
if (!edgeOption) {
|
||||
return [];
|
||||
}
|
||||
const targetClass = symbolRefFromOption(edgeOption.target, {
|
||||
const legacyTargetClass = symbolRefFromOption(edgeOption.target, {
|
||||
namespace: defaults.schemaNamespace,
|
||||
name: "",
|
||||
version: defaults.schemaVersion,
|
||||
});
|
||||
if (!targetClass.name) {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} requires target`);
|
||||
}
|
||||
const cardinality = cardinalityFromOption(edgeOption.cardinality);
|
||||
const declaredFromEndpoint = endpointFromOption({
|
||||
option: edgeOption.this_endpoint,
|
||||
fallbackClass: classId,
|
||||
fallbackProjection: field.name,
|
||||
fallbackCardinality: cardinality,
|
||||
fallbackVersion: defaults.schemaVersion,
|
||||
});
|
||||
const declaredToEndpoint = endpointFromOption({
|
||||
option: edgeOption.other_endpoint,
|
||||
fallbackClass: legacyTargetClass.name ? legacyTargetClass : undefined,
|
||||
fallbackProjection: readString(edgeOption, "inverse") ?? "",
|
||||
fallbackCardinality: "many",
|
||||
fallbackVersion: defaults.schemaVersion,
|
||||
});
|
||||
if (!declaredToEndpoint.projection) {
|
||||
throw new Error(
|
||||
`Edge field ${type.fullName}.${field.name} requires other_endpoint.projection or inverse`,
|
||||
);
|
||||
}
|
||||
const sourceType = typeRefForField(field, defaults.schemaVersion);
|
||||
if (
|
||||
sourceType.symbol?.namespace !== "camino" ||
|
||||
@@ -584,32 +727,47 @@ const classSchemaFromType = (
|
||||
) {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must use camino.Ref`);
|
||||
}
|
||||
const isMany =
|
||||
cardinality === "many" ||
|
||||
cardinality === "many_unique" ||
|
||||
cardinality === "many_ordered";
|
||||
const isMany = isManyCardinality(declaredFromEndpoint.cardinality);
|
||||
if (isMany && !field.repeated) {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${cardinality}`);
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${declaredFromEndpoint.cardinality}`);
|
||||
}
|
||||
if (!isMany && field.repeated) {
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${cardinality}`);
|
||||
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${declaredFromEndpoint.cardinality}`);
|
||||
}
|
||||
const directionality: EdgeDirectionality =
|
||||
edgeOption.undirected === true ? "undirected" : "directed";
|
||||
const { fromEndpoint, toEndpoint } = normalizeEdgeEndpoints({
|
||||
directionality,
|
||||
fromEndpoint: declaredFromEndpoint,
|
||||
toEndpoint: declaredToEndpoint,
|
||||
});
|
||||
const edgeId = symbolRefFromOption(edgeOption.id, {
|
||||
namespace: defaults.schemaNamespace,
|
||||
name: `${type.name}.${field.name}`,
|
||||
version: defaults.schemaVersion,
|
||||
});
|
||||
const fallbackToClass = toEndpoint.class ?? {
|
||||
namespace: "",
|
||||
name: "",
|
||||
version: "",
|
||||
};
|
||||
return [
|
||||
{
|
||||
id: edgeId,
|
||||
sourceField: field.name,
|
||||
fromClass: classId,
|
||||
toClass: targetClass,
|
||||
cardinality,
|
||||
...(readString(edgeOption, "inverse")
|
||||
? { inverse: readString(edgeOption, "inverse") }
|
||||
directionality,
|
||||
sourceField: fromEndpoint.projection,
|
||||
fromClass: fromEndpoint.class ?? classId,
|
||||
toClass: fallbackToClass,
|
||||
cardinality: fromEndpoint.cardinality,
|
||||
...(toEndpoint.projection
|
||||
? { inverse: toEndpoint.projection }
|
||||
: {}),
|
||||
fields: [],
|
||||
fromEndpoint,
|
||||
toEndpoint,
|
||||
declaringClass: classId,
|
||||
tags: symbolArrayFromOption(edgeOption.tag, defaults),
|
||||
implements: symbolArrayFromOption(edgeOption.implements, defaults),
|
||||
},
|
||||
];
|
||||
});
|
||||
@@ -703,6 +861,9 @@ const interfaceSchemaFromType = (
|
||||
),
|
||||
sourceFile,
|
||||
protoMessage: type.fullName,
|
||||
typeParameters: asArray(interfaceOption.type_parameter)
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -733,11 +894,10 @@ const walkServices = (
|
||||
}
|
||||
};
|
||||
|
||||
export const compileCaminoSchema = async (
|
||||
export const loadCaminoSchemaRoot = async (
|
||||
sourceFile: string,
|
||||
): Promise<SchemaCompileResult> => {
|
||||
): Promise<{ absoluteSourceFile: string; root: protobuf.Root }> => {
|
||||
const absoluteSourceFile = path.resolve(sourceFile);
|
||||
const defaults = extractFileDefaults(absoluteSourceFile);
|
||||
const root = new protobuf.Root();
|
||||
const envIncludeDirs = [
|
||||
process.env.QUIXOS_PROTO_PATH,
|
||||
@@ -772,6 +932,15 @@ export const compileCaminoSchema = async (
|
||||
await root.load(absoluteSourceFile, { keepCase: true });
|
||||
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[] = [];
|
||||
walkServices(root, (service) => {
|
||||
operationServices.push(
|
||||
|
||||
+27
-1
@@ -23,7 +23,15 @@ export type Cardinality =
|
||||
| "exactly_one"
|
||||
| "many"
|
||||
| "many_unique"
|
||||
| "many_ordered";
|
||||
| "many_ordered"
|
||||
| "many_unique_ordered";
|
||||
|
||||
export type EdgeDirectionality = "directed" | "undirected";
|
||||
|
||||
export type EdgeMaterialization = {
|
||||
class: SymbolRef;
|
||||
connectProjection: string;
|
||||
};
|
||||
|
||||
export type FieldStorageKind =
|
||||
| "stored"
|
||||
@@ -61,6 +69,8 @@ export type InterfaceFieldContract = {
|
||||
watchable: boolean;
|
||||
typeParam?: string;
|
||||
type?: TypeRef;
|
||||
refTargetTypeParam?: string;
|
||||
edgeCardinality?: Cardinality;
|
||||
};
|
||||
|
||||
export type InterfaceImplementation = {
|
||||
@@ -81,14 +91,29 @@ export type FieldSchema = {
|
||||
isDisplayLabel: boolean;
|
||||
};
|
||||
|
||||
export type EdgeEndpointSchema = {
|
||||
class?: SymbolRef;
|
||||
interface?: SymbolRef;
|
||||
projection: string;
|
||||
cardinality: Cardinality;
|
||||
indexed: boolean;
|
||||
materialization?: EdgeMaterialization;
|
||||
};
|
||||
|
||||
export type EdgeSchema = {
|
||||
id: SymbolRef;
|
||||
directionality: EdgeDirectionality;
|
||||
sourceField: string;
|
||||
fromClass: SymbolRef;
|
||||
toClass: SymbolRef;
|
||||
cardinality: Cardinality;
|
||||
inverse?: string;
|
||||
fields: FieldSchema[];
|
||||
fromEndpoint: EdgeEndpointSchema;
|
||||
toEndpoint: EdgeEndpointSchema;
|
||||
declaringClass: SymbolRef;
|
||||
tags: SymbolRef[];
|
||||
implements: SymbolRef[];
|
||||
};
|
||||
|
||||
export type MethodSchema = {
|
||||
@@ -139,6 +164,7 @@ export type InterfaceSchema = {
|
||||
fields: FieldSchema[];
|
||||
sourceFile: string;
|
||||
protoMessage: string;
|
||||
typeParameters: string[];
|
||||
};
|
||||
|
||||
export type SchemaCompileResult = {
|
||||
|
||||
Reference in New Issue
Block a user