Publish camino-package-runtime from 9f6f36f9932aa1f938eb507c5ed372200dee0096
This commit is contained in:
@@ -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": "51607ba09c066e59a5c80a4b6cce46eb61fda501",
|
"sourceCommit": "9f6f36f9932aa1f938eb507c5ed372200dee0096",
|
||||||
"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"
|
||||||
|
|||||||
Generated
+5
-5
@@ -74,17 +74,17 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784127127,
|
"lastModified": 1784395768,
|
||||||
"narHash": "sha256-M36R+j10krhGRrhCozKkV7PsRM9BklQNqWox2bW7t/0=",
|
"narHash": "sha256-g03xOnzJHiL/edNV6nqURtaPqiapq2uCm7wQTZh4b5s=",
|
||||||
"ref": "refs/heads/exported",
|
"ref": "refs/heads/exported",
|
||||||
"rev": "a9134843bc947546c78b28902d081f451733ddba",
|
"rev": "e6b34535afa6e94a118e1f9d082054855e7df632",
|
||||||
"revCount": 22,
|
"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": "a9134843bc947546c78b28902d081f451733ddba",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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=a9134843bc947546c78b28902d081f451733ddba";
|
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;
|
||||||
|
|||||||
+16
-1
File diff suppressed because one or more lines are too long
+282
-23
@@ -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,150 @@ 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 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 +630,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 +648,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);
|
||||||
fs.writeFileSync(
|
if (!reactOnly) {
|
||||||
path.join(outDir, `${baseName}.server.ts`),
|
fs.writeFileSync(
|
||||||
renderServer(schema, implementationImport),
|
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();
|
await main();
|
||||||
|
|||||||
+21
-3
@@ -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 } : {}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -854,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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -884,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,
|
||||||
@@ -923,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(
|
||||||
|
|||||||
@@ -69,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 = {
|
||||||
@@ -162,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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user