Compare commits
2 Commits
4c042e571c
...
337036e996
| Author | SHA1 | Date | |
|---|---|---|---|
| 337036e996 | |||
| 740bbc7aff |
@@ -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": "e22940cf94a096c58a966d4487447f31ddc77f69",
|
"sourceCommit": "e385e8ca91696f200d91df4f31a033d922be7cff",
|
||||||
"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"
|
||||||
|
|||||||
Binary file not shown.
Generated
+5
-5
@@ -74,17 +74,17 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783905858,
|
"lastModified": 1783954303,
|
||||||
"narHash": "sha256-R8q7hiklpUmKUDs6DCglEsI21CYnMxydZHv9+xq7hj4=",
|
"narHash": "sha256-xywk5pWpvOD2rWmNaZ3arYgkx3Km/qsos4g1uB2pXVA=",
|
||||||
"ref": "refs/heads/exported",
|
"ref": "refs/heads/exported",
|
||||||
"rev": "c5141a24c4e82672d66d4b45ac11c45d7d88e1f7",
|
"rev": "ff07b0d7933ad01c0b52bae2dcab1445e8e91eeb",
|
||||||
"revCount": 10,
|
"revCount": 14,
|
||||||
"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": "c5141a24c4e82672d66d4b45ac11c45d7d88e1f7",
|
"rev": "ff07b0d7933ad01c0b52bae2dcab1445e8e91eeb",
|
||||||
"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=c5141a24c4e82672d66d4b45ac11c45d7d88e1f7";
|
quixos-protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/heads/exported&rev=ff07b0d7933ad01c0b52bae2dcab1445e8e91eeb";
|
||||||
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;
|
||||||
|
|||||||
+5
-1
@@ -4,6 +4,9 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "yarn@4.14.1",
|
"packageManager": "yarn@4.14.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"camino-codegen-ts-runtime": "dist/src/codegen-ts-runtime.js"
|
||||||
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
@@ -20,7 +23,8 @@
|
|||||||
"@bufbuild/protobuf": "^2.12.1",
|
"@bufbuild/protobuf": "^2.12.1",
|
||||||
"@connectrpc/connect": "^2.1.2",
|
"@connectrpc/connect": "^2.1.2",
|
||||||
"@connectrpc/connect-node": "^2.1.2",
|
"@connectrpc/connect-node": "^2.1.2",
|
||||||
"@quixos/camino-datatypes": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a"
|
"@quixos/camino-datatypes": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a",
|
||||||
|
"protobufjs": "^7.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bufbuild/protoc-gen-es": "^2.12.1",
|
"@bufbuild/protoc-gen-es": "^2.12.1",
|
||||||
|
|||||||
@@ -0,0 +1,414 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { compileCaminoSchema } from "./schema-compiler.js";
|
||||||
|
import type {
|
||||||
|
ClassSchema,
|
||||||
|
FieldSchema,
|
||||||
|
FunctionRef,
|
||||||
|
InterfaceImplementation,
|
||||||
|
TypeRef,
|
||||||
|
} from "./schema-ir.js";
|
||||||
|
|
||||||
|
const usage = (): never => {
|
||||||
|
console.error(
|
||||||
|
"Usage: camino-codegen-ts-runtime <class-schema.camino.proto> <out-dir> [implementation-import]",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const required = (value: string | undefined) => value ?? usage();
|
||||||
|
|
||||||
|
const stringLiteral = (value: string) => JSON.stringify(value);
|
||||||
|
|
||||||
|
const upperFirst = (value: string) =>
|
||||||
|
value.length === 0 ? value : `${value[0]?.toUpperCase()}${value.slice(1)}`;
|
||||||
|
|
||||||
|
const lowerFirst = (value: string) =>
|
||||||
|
value.length === 0 ? value : `${value[0]?.toLowerCase()}${value.slice(1)}`;
|
||||||
|
|
||||||
|
const camel = (value: string) =>
|
||||||
|
value
|
||||||
|
.split(/[^a-zA-Z0-9]+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((part, index) =>
|
||||||
|
index === 0 ? lowerFirst(part) : upperFirst(lowerFirst(part)),
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const isIdentifier = (value: string) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
|
||||||
|
|
||||||
|
const classId = (schema: ClassSchema) =>
|
||||||
|
`${schema.id.namespace}:${schema.id.name}:${schema.id.version}:${
|
||||||
|
schema.id.hash ?? ""
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const symbolId = (value: { namespace: string; name: string; version: string; hash?: string }) =>
|
||||||
|
`${value.namespace}:${value.name}:${value.version}:${value.hash ?? ""}`;
|
||||||
|
|
||||||
|
const tsTypeForTypeRef = (type: TypeRef): string => {
|
||||||
|
if (type.enumValues && type.enumValues.length > 0) {
|
||||||
|
return type.enumValues.map(stringLiteral).join(" | ");
|
||||||
|
}
|
||||||
|
switch (type.protoType.replace(/^\./, "")) {
|
||||||
|
case "string":
|
||||||
|
case "google.protobuf.StringValue":
|
||||||
|
return "string";
|
||||||
|
case "bool":
|
||||||
|
case "google.protobuf.BoolValue":
|
||||||
|
return "boolean";
|
||||||
|
case "double":
|
||||||
|
case "float":
|
||||||
|
case "google.protobuf.DoubleValue":
|
||||||
|
case "google.protobuf.FloatValue":
|
||||||
|
case "int32":
|
||||||
|
case "sint32":
|
||||||
|
case "sfixed32":
|
||||||
|
case "uint32":
|
||||||
|
case "fixed32":
|
||||||
|
case "int64":
|
||||||
|
case "sint64":
|
||||||
|
case "sfixed64":
|
||||||
|
case "uint64":
|
||||||
|
case "fixed64":
|
||||||
|
case "google.protobuf.Int32Value":
|
||||||
|
case "google.protobuf.UInt32Value":
|
||||||
|
case "google.protobuf.Int64Value":
|
||||||
|
case "google.protobuf.UInt64Value":
|
||||||
|
return "number";
|
||||||
|
case "bytes":
|
||||||
|
case "google.protobuf.BytesValue":
|
||||||
|
return "Uint8Array";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tsTypeForField = (field: FieldSchema): string => tsTypeForTypeRef(field.type);
|
||||||
|
|
||||||
|
const tsValueTypeForField = (field: FieldSchema) =>
|
||||||
|
field.repeated ? `${tsTypeForField(field)}[]` : tsTypeForField(field);
|
||||||
|
|
||||||
|
const tsFieldApiType = (field: FieldSchema) =>
|
||||||
|
field.conflict === "crdt"
|
||||||
|
? `CrdtField<${tsValueTypeForField(field)}>`
|
||||||
|
: `Field<${tsValueTypeForField(field)}>`;
|
||||||
|
|
||||||
|
const functionExportName = (fn: FunctionRef, fallback: string) => {
|
||||||
|
if (isIdentifier(fn.symbol)) {
|
||||||
|
return fn.symbol;
|
||||||
|
}
|
||||||
|
const fallbackName = camel(fallback);
|
||||||
|
if (isIdentifier(fallbackName)) {
|
||||||
|
return fallbackName;
|
||||||
|
}
|
||||||
|
throw new Error(`Cannot generate TS export name for function ${fn.symbol}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatedWarning = `// @generated by camino-codegen-ts-runtime. Do not edit.\n`;
|
||||||
|
|
||||||
|
type FunctionSpec = {
|
||||||
|
exportName: string;
|
||||||
|
symbol: string;
|
||||||
|
operation?: string;
|
||||||
|
packageNamespace: string;
|
||||||
|
packageName: string;
|
||||||
|
field?: {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
storage: string;
|
||||||
|
conflict: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldTypeId = (field: FieldSchema) =>
|
||||||
|
field.type.symbol ? symbolId(field.type.symbol) : field.type.protoType;
|
||||||
|
|
||||||
|
const crdtStorageTypeId = (field: FieldSchema) =>
|
||||||
|
field.type.protoType ||
|
||||||
|
(field.type.symbol
|
||||||
|
? `${field.type.symbol.namespace}.${field.type.symbol.name}`
|
||||||
|
: fieldTypeId(field));
|
||||||
|
|
||||||
|
const fieldStorageId = (field: FieldSchema) =>
|
||||||
|
field.ops && field.storage.kind === "stored" ? "derived" : field.storage.kind;
|
||||||
|
|
||||||
|
const interfaceTypeName = (implementation: InterfaceImplementation) =>
|
||||||
|
`${implementation.interface.name}Ref`;
|
||||||
|
|
||||||
|
const renderInterfaceRefTypes = (schema: ClassSchema) =>
|
||||||
|
schema.implements
|
||||||
|
.map((implementation) => {
|
||||||
|
const typeParams = implementation.typeBindings.map((binding) => binding.name);
|
||||||
|
const generic =
|
||||||
|
typeParams.length > 0 ? `<${typeParams.join(", ")}>` : "";
|
||||||
|
const bindings =
|
||||||
|
typeParams.length > 0
|
||||||
|
? typeParams
|
||||||
|
.map((param) => ` readonly ${param}: ${param};`)
|
||||||
|
.join("\n")
|
||||||
|
: " readonly value: unknown;";
|
||||||
|
return `export type ${interfaceTypeName(implementation)}${generic} = ObjectRef<string> & {
|
||||||
|
readonly $caminoInterfaces: {
|
||||||
|
readonly ${stringLiteral(symbolId(implementation.interface))}: {
|
||||||
|
${bindings}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};`;
|
||||||
|
})
|
||||||
|
.join("\n\n");
|
||||||
|
|
||||||
|
const interfaceMixins = (schema: ClassSchema) =>
|
||||||
|
schema.implements.map((implementation) => {
|
||||||
|
const typeArgs = implementation.typeBindings.map((binding) =>
|
||||||
|
tsTypeForTypeRef(binding.type),
|
||||||
|
);
|
||||||
|
return `${interfaceTypeName(implementation)}${
|
||||||
|
typeArgs.length > 0 ? `<${typeArgs.join(", ")}>` : ""
|
||||||
|
}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const operationFunctionSpecs = (schema: ClassSchema): FunctionSpec[] =>
|
||||||
|
schema.fields.flatMap((field) => {
|
||||||
|
const serviceName = field.ops?.service.replace(/^\./, "");
|
||||||
|
if (!serviceName) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const service = schema.operationServices.find(
|
||||||
|
(candidate) => candidate.fullName === serviceName,
|
||||||
|
);
|
||||||
|
if (!service) {
|
||||||
|
throw new Error(
|
||||||
|
`${schema.id.name}.${field.name} points to missing operation service ${field.ops?.service}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return service.operations.map((operation) => ({
|
||||||
|
exportName: functionExportName(
|
||||||
|
operation.function,
|
||||||
|
`${field.name}_${operation.name}`,
|
||||||
|
),
|
||||||
|
symbol: operation.function.symbol,
|
||||||
|
...(operation.function.operation
|
||||||
|
? { operation: operation.function.operation }
|
||||||
|
: {}),
|
||||||
|
packageNamespace: operation.function.packageNamespace,
|
||||||
|
packageName: operation.function.packageName,
|
||||||
|
field: {
|
||||||
|
name: field.name,
|
||||||
|
type: fieldTypeId(field),
|
||||||
|
storage: fieldStorageId(field),
|
||||||
|
conflict: field.conflict,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderRuntime = (schema: ClassSchema) => {
|
||||||
|
const className = schema.id.name;
|
||||||
|
const objectTypeName = `${className}Object`;
|
||||||
|
const refTypeName = `${className}Ref`;
|
||||||
|
const contextTypeName = `${className}Context`;
|
||||||
|
const implementationTypeName = `${className}Implementation`;
|
||||||
|
const serveName = `serve${className}Runtime`;
|
||||||
|
const createObjectName = `create${className}Object`;
|
||||||
|
const interfaceRefTypes = renderInterfaceRefTypes(schema);
|
||||||
|
const refType =
|
||||||
|
[ `ObjectRef<${stringLiteral(classId(schema))}>`, ...interfaceMixins(schema)]
|
||||||
|
.join(" & ");
|
||||||
|
const edgeFields = new Set(schema.edges.map((edge) => edge.sourceField));
|
||||||
|
const writableFields = schema.fields.filter(
|
||||||
|
(field) =>
|
||||||
|
field.storage.kind === "stored" &&
|
||||||
|
!field.ops &&
|
||||||
|
!edgeFields.has(field.name),
|
||||||
|
);
|
||||||
|
const functionSpecs = [
|
||||||
|
...(schema.constructorSpec
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
exportName: functionExportName(
|
||||||
|
schema.constructorSpec.function,
|
||||||
|
"constructor",
|
||||||
|
),
|
||||||
|
symbol: schema.constructorSpec.function.symbol,
|
||||||
|
...(schema.constructorSpec.function.operation
|
||||||
|
? { operation: schema.constructorSpec.function.operation }
|
||||||
|
: {}),
|
||||||
|
packageNamespace: schema.constructorSpec.function.packageNamespace,
|
||||||
|
packageName: schema.constructorSpec.function.packageName,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...schema.methods.map((method) => ({
|
||||||
|
exportName: functionExportName(method.function, method.name),
|
||||||
|
symbol: method.function.symbol,
|
||||||
|
...(method.function.operation ? { operation: method.function.operation } : {}),
|
||||||
|
packageNamespace: method.function.packageNamespace,
|
||||||
|
packageName: method.function.packageName,
|
||||||
|
})),
|
||||||
|
...schema.fields.flatMap((field) =>
|
||||||
|
field.storage.resolver
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
exportName: functionExportName(field.storage.resolver, field.name),
|
||||||
|
symbol: field.storage.resolver.symbol,
|
||||||
|
...(field.storage.resolver.operation
|
||||||
|
? { operation: field.storage.resolver.operation }
|
||||||
|
: {}),
|
||||||
|
packageNamespace: field.storage.resolver.packageNamespace,
|
||||||
|
packageName: field.storage.resolver.packageName,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
),
|
||||||
|
...operationFunctionSpecs(schema),
|
||||||
|
];
|
||||||
|
const firstFunction = functionSpecs[0];
|
||||||
|
if (!firstFunction) {
|
||||||
|
throw new Error(`${schema.id.name} has no runtime functions to generate`);
|
||||||
|
}
|
||||||
|
const mixedPackage = functionSpecs.find(
|
||||||
|
(spec) =>
|
||||||
|
spec.packageNamespace !== firstFunction.packageNamespace ||
|
||||||
|
spec.packageName !== firstFunction.packageName,
|
||||||
|
);
|
||||||
|
if (mixedPackage) {
|
||||||
|
throw new Error(
|
||||||
|
`${schema.id.name} points to multiple packages; v0 TS runtime generation expects one package`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${generatedWarning}import {
|
||||||
|
type CaminoClient,
|
||||||
|
createCrdtField,
|
||||||
|
createField,
|
||||||
|
type CrdtField,
|
||||||
|
type Field,
|
||||||
|
type FieldOperation,
|
||||||
|
type InvokeRequest,
|
||||||
|
objectRef,
|
||||||
|
type ObjectRef,
|
||||||
|
type RuntimeHandler,
|
||||||
|
serveQuixosPackageRuntime,
|
||||||
|
} from "@quixos/camino-package-runtime";
|
||||||
|
|
||||||
|
${interfaceRefTypes ? `${interfaceRefTypes}\n\n` : ""}export type ${refTypeName} = ${refType};
|
||||||
|
|
||||||
|
export type ${objectTypeName} = {
|
||||||
|
id: ${refTypeName};
|
||||||
|
${writableFields
|
||||||
|
.map(
|
||||||
|
(field) =>
|
||||||
|
` ${camel(field.name)}: ${tsFieldApiType(field)};`,
|
||||||
|
)
|
||||||
|
.join("\n")}
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ${contextTypeName} = {
|
||||||
|
object: ${objectTypeName};
|
||||||
|
camino: CaminoClient;
|
||||||
|
request: InvokeRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ${implementationTypeName} = {
|
||||||
|
${functionSpecs
|
||||||
|
.map((spec) =>
|
||||||
|
spec.operation
|
||||||
|
? ` ${spec.exportName}: FieldOperation<${contextTypeName}>;`
|
||||||
|
: ` ${spec.exportName}: RuntimeHandler<${contextTypeName}>;`,
|
||||||
|
)
|
||||||
|
.filter((line, index, lines) => lines.indexOf(line) === index)
|
||||||
|
.join("\n")}
|
||||||
|
};
|
||||||
|
|
||||||
|
const packageNamespace = ${stringLiteral(firstFunction.packageNamespace)};
|
||||||
|
const packageName = ${stringLiteral(firstFunction.packageName)};
|
||||||
|
const runtimeProtocolVersion = ${stringLiteral("camino-orch-v0")};
|
||||||
|
|
||||||
|
const functions = [
|
||||||
|
${functionSpecs
|
||||||
|
.map(
|
||||||
|
(spec) =>
|
||||||
|
` { exportName: ${stringLiteral(spec.exportName)}, symbol: ${stringLiteral(spec.symbol)}${
|
||||||
|
spec.operation ? `, operation: ${stringLiteral(spec.operation)}` : ""
|
||||||
|
}${
|
||||||
|
spec.field
|
||||||
|
? `, field: { name: ${stringLiteral(spec.field.name)}, type: ${stringLiteral(spec.field.type)}, storage: ${stringLiteral(spec.field.storage)}, conflict: ${stringLiteral(spec.field.conflict)} }`
|
||||||
|
: ""
|
||||||
|
} },`,
|
||||||
|
)
|
||||||
|
.join("\n")}
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const ${createObjectName} = (
|
||||||
|
camino: CaminoClient,
|
||||||
|
objectId: string,
|
||||||
|
): ${objectTypeName} => {
|
||||||
|
const id = objectRef<${stringLiteral(classId(schema))}>(objectId) as ${refTypeName};
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
${writableFields
|
||||||
|
.map(
|
||||||
|
(fieldSchema) =>
|
||||||
|
fieldSchema.conflict === "crdt"
|
||||||
|
? ` ${camel(fieldSchema.name)}: createCrdtField(camino, id, ${stringLiteral(fieldSchema.name)}, ${stringLiteral(crdtStorageTypeId(fieldSchema))}),`
|
||||||
|
: ` ${camel(fieldSchema.name)}: createField(camino, id, ${stringLiteral(fieldSchema.name)}),`,
|
||||||
|
)
|
||||||
|
.join("\n")}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const create${className}Context = (
|
||||||
|
camino: CaminoClient,
|
||||||
|
request: InvokeRequest,
|
||||||
|
): ${contextTypeName} => ({
|
||||||
|
object: ${createObjectName}(camino, request.objectId),
|
||||||
|
camino,
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ${serveName} = (implementation: ${implementationTypeName}) => {
|
||||||
|
serveQuixosPackageRuntime({
|
||||||
|
packageNamespace,
|
||||||
|
packageName,
|
||||||
|
runtimeProtocolVersion,
|
||||||
|
functions,
|
||||||
|
implementation,
|
||||||
|
createContext: create${className}Context,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderServer = (schema: ClassSchema, implementationImport: string) => {
|
||||||
|
const className = schema.id.name;
|
||||||
|
return `${generatedWarning}import * as implementation from ${stringLiteral(implementationImport)};
|
||||||
|
import { serve${className}Runtime } from "./${schema.id.namespace}.${className}.runtime.js";
|
||||||
|
|
||||||
|
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 compiled = await compileCaminoSchema(sourceFile);
|
||||||
|
if (compiled.classes.length !== 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected exactly one Camino class in ${sourceFile}, found ${compiled.classes.length}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const schema = compiled.classes[0];
|
||||||
|
if (!schema) {
|
||||||
|
throw new Error(`No Camino class found in ${sourceFile}`);
|
||||||
|
}
|
||||||
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
const baseName = `${schema.id.namespace}.${schema.id.name}`;
|
||||||
|
fs.writeFileSync(path.join(outDir, `${baseName}.runtime.ts`), renderRuntime(schema));
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(outDir, `${baseName}.server.ts`),
|
||||||
|
renderServer(schema, implementationImport),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
await main();
|
||||||
@@ -0,0 +1,809 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import protobuf from "protobufjs";
|
||||||
|
import type {
|
||||||
|
Cardinality,
|
||||||
|
ClassSchema,
|
||||||
|
ConflictStrategy,
|
||||||
|
FieldSchema,
|
||||||
|
FieldStorage,
|
||||||
|
FieldStorageKind,
|
||||||
|
FunctionRef,
|
||||||
|
InterfaceFieldContract,
|
||||||
|
InterfaceImplementation,
|
||||||
|
InterfaceSchema,
|
||||||
|
MethodSchema,
|
||||||
|
OperationSchema,
|
||||||
|
OperationServiceSchema,
|
||||||
|
MigrationSpec,
|
||||||
|
SchemaCompileResult,
|
||||||
|
SymbolRef,
|
||||||
|
TypeRef,
|
||||||
|
} from "./schema-ir.js";
|
||||||
|
|
||||||
|
type OptionBag = Record<string, unknown>;
|
||||||
|
|
||||||
|
const CLASS_OPTION = "(camino.class)";
|
||||||
|
const INTERFACE_OPTION = "(camino.interface)";
|
||||||
|
const IMPLEMENTS_OPTION = "(camino.implements)";
|
||||||
|
const CONSTRUCTOR_OPTION = "(camino.constructor)";
|
||||||
|
const METHOD_OPTION = "(camino.method)";
|
||||||
|
const MIGRATION_OPTION = "(camino.migration)";
|
||||||
|
const CONFLICT_OPTION = "(camino.conflict)";
|
||||||
|
const EDGE_OPTION = "(camino.edge)";
|
||||||
|
const FIELD_STORAGE_OPTION = "(camino.field_storage)";
|
||||||
|
const FIELD_OPS_OPTION = "(camino.field_ops)";
|
||||||
|
const INTERFACE_FIELD_OPTION = "(camino.interface_field)";
|
||||||
|
const DISPLAY_LABEL_OPTION = "(camino.display_label)";
|
||||||
|
const IMPL_OPTION = "(camino.impl)";
|
||||||
|
const SCHEMA_NAMESPACE_OPTION = "(camino.schema_namespace)";
|
||||||
|
const SCHEMA_VERSION_OPTION = "(camino.schema_version)";
|
||||||
|
|
||||||
|
const enumName = (value: unknown) =>
|
||||||
|
String(value)
|
||||||
|
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
||||||
|
.replace(/-/g, "_")
|
||||||
|
.toUpperCase();
|
||||||
|
|
||||||
|
const lowerEnumName = (value: unknown) => enumName(value).toLowerCase();
|
||||||
|
|
||||||
|
const getOption = (
|
||||||
|
options: OptionBag | undefined,
|
||||||
|
name: string,
|
||||||
|
): unknown => {
|
||||||
|
if (!options) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (Object.hasOwn(options, name)) {
|
||||||
|
return options[name];
|
||||||
|
}
|
||||||
|
const bareName = name.replace(/^\((.*)\)$/, "$1");
|
||||||
|
if (Object.hasOwn(options, bareName)) {
|
||||||
|
return options[bareName];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getParsedOptions = (
|
||||||
|
reflection: protobuf.ReflectionObject,
|
||||||
|
): OptionBag[] => {
|
||||||
|
const parsedOptions = (reflection as unknown as { parsedOptions?: unknown })
|
||||||
|
.parsedOptions;
|
||||||
|
return Array.isArray(parsedOptions)
|
||||||
|
? parsedOptions.filter(
|
||||||
|
(option): option is OptionBag =>
|
||||||
|
Boolean(option) && typeof option === "object" && !Array.isArray(option),
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReflectionOptions = (
|
||||||
|
reflection: protobuf.ReflectionObject,
|
||||||
|
name: string,
|
||||||
|
): unknown[] => {
|
||||||
|
const parsedValues = getParsedOptions(reflection).flatMap((option) => {
|
||||||
|
const value = getOption(option, name);
|
||||||
|
return value === undefined ? [] : [value];
|
||||||
|
});
|
||||||
|
if (parsedValues.length > 0) {
|
||||||
|
return parsedValues;
|
||||||
|
}
|
||||||
|
const value = getOption(reflection.options, name);
|
||||||
|
return value === undefined ? [] : [value];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReflectionOption = (
|
||||||
|
reflection: protobuf.ReflectionObject,
|
||||||
|
name: string,
|
||||||
|
): unknown => getReflectionOptions(reflection, name)[0];
|
||||||
|
|
||||||
|
const asObject = (value: unknown): OptionBag | undefined =>
|
||||||
|
value && typeof value === "object" && !Array.isArray(value)
|
||||||
|
? (value as OptionBag)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const asArray = (value: unknown): unknown[] =>
|
||||||
|
Array.isArray(value) ? value : value === undefined ? [] : [value];
|
||||||
|
|
||||||
|
const readString = (
|
||||||
|
object: OptionBag | undefined,
|
||||||
|
key: string,
|
||||||
|
): string | undefined => {
|
||||||
|
const value = object?.[key];
|
||||||
|
return typeof value === "string" ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readNumber = (
|
||||||
|
object: OptionBag | undefined,
|
||||||
|
key: string,
|
||||||
|
): number | undefined => {
|
||||||
|
const value = object?.[key];
|
||||||
|
return typeof value === "number" && Number.isFinite(value)
|
||||||
|
? value
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readBoolean = (
|
||||||
|
object: OptionBag | undefined,
|
||||||
|
key: string,
|
||||||
|
): boolean | undefined => {
|
||||||
|
const value = object?.[key];
|
||||||
|
return typeof value === "boolean" ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const symbolRefFromOption = (
|
||||||
|
value: unknown,
|
||||||
|
fallback: SymbolRef,
|
||||||
|
): SymbolRef => {
|
||||||
|
const object = asObject(value);
|
||||||
|
if (!object) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
namespace: readString(object, "namespace") ?? fallback.namespace,
|
||||||
|
name: readString(object, "name") ?? fallback.name,
|
||||||
|
version: readString(object, "version") ?? fallback.version,
|
||||||
|
...(readString(object, "hash")
|
||||||
|
? { hash: readString(object, "hash") }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeRefFromOption = (
|
||||||
|
value: unknown,
|
||||||
|
fallbackVersion: string,
|
||||||
|
): TypeRef | undefined => {
|
||||||
|
const object = asObject(value);
|
||||||
|
if (!object) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const symbol = asObject(object.symbol)
|
||||||
|
? symbolRefFromOption(object.symbol, {
|
||||||
|
namespace: "",
|
||||||
|
name: "",
|
||||||
|
version: fallbackVersion,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
const protoType = readString(object, "proto_type")?.replace(/^\./, "");
|
||||||
|
const symbolProtoType =
|
||||||
|
symbol?.namespace && symbol.name ? `${symbol.namespace}.${symbol.name}` : "";
|
||||||
|
const resolvedProtoType = protoType ?? symbolProtoType;
|
||||||
|
if (!resolvedProtoType) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
protoType: resolvedProtoType,
|
||||||
|
...(symbol?.namespace && symbol.name ? { symbol } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const interfaceFieldContractFromOption = (
|
||||||
|
value: unknown,
|
||||||
|
schemaVersion: string,
|
||||||
|
): InterfaceFieldContract | undefined => {
|
||||||
|
const object = asObject(value);
|
||||||
|
if (!object) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const type = typeRefFromOption(object.type, schemaVersion);
|
||||||
|
return {
|
||||||
|
required: readBoolean(object, "required") ?? false,
|
||||||
|
readable: readBoolean(object, "readable") ?? false,
|
||||||
|
writable: readBoolean(object, "writable") ?? false,
|
||||||
|
watchable: readBoolean(object, "watchable") ?? false,
|
||||||
|
...(readString(object, "type_param")
|
||||||
|
? { typeParam: readString(object, "type_param") }
|
||||||
|
: {}),
|
||||||
|
...(type ? { type } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const functionRefFromOption = (value: unknown): FunctionRef => {
|
||||||
|
const object = asObject(value);
|
||||||
|
if (!object) {
|
||||||
|
throw new Error("Expected function ref option object");
|
||||||
|
}
|
||||||
|
const packageNamespace = readString(object, "package_namespace");
|
||||||
|
const packageName = readString(object, "package_name");
|
||||||
|
const symbol = readString(object, "symbol");
|
||||||
|
if (!packageNamespace || !packageName || !symbol) {
|
||||||
|
throw new Error("Function ref requires package_namespace, package_name, and symbol");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
packageNamespace,
|
||||||
|
packageName,
|
||||||
|
symbol,
|
||||||
|
...(readString(object, "operation")
|
||||||
|
? { operation: readString(object, "operation") }
|
||||||
|
: {}),
|
||||||
|
...(readString(object, "version_ref")
|
||||||
|
? { versionRef: readString(object, "version_ref") }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const conflictFromOption = (value: unknown): ConflictStrategy => {
|
||||||
|
const normalized = enumName(value);
|
||||||
|
if (!value || normalized === "CONFLICT_STRATEGY_UNSPECIFIED") {
|
||||||
|
return "preserve_conflicts";
|
||||||
|
}
|
||||||
|
if (normalized === "REPLACE") {
|
||||||
|
return "replace";
|
||||||
|
}
|
||||||
|
if (normalized === "PRESERVE_CONFLICTS") {
|
||||||
|
return "preserve_conflicts";
|
||||||
|
}
|
||||||
|
if (normalized === "CRDT") {
|
||||||
|
return "crdt";
|
||||||
|
}
|
||||||
|
throw new Error(`Unknown Camino conflict strategy: ${String(value)}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cardinalityFromOption = (value: unknown): Cardinality => {
|
||||||
|
const normalized = lowerEnumName(value);
|
||||||
|
if (!value || normalized === "cardinality_unspecified") {
|
||||||
|
return "many";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
normalized === "optional_one" ||
|
||||||
|
normalized === "exactly_one" ||
|
||||||
|
normalized === "many" ||
|
||||||
|
normalized === "many_unique" ||
|
||||||
|
normalized === "many_ordered"
|
||||||
|
) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
throw new Error(`Unknown Camino cardinality: ${String(value)}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldStorageKindFromOption = (value: unknown): FieldStorageKind => {
|
||||||
|
if (value === 5) {
|
||||||
|
return "static_final";
|
||||||
|
}
|
||||||
|
const normalized = lowerEnumName(value);
|
||||||
|
if (!value || normalized === "field_storage_kind_unspecified") {
|
||||||
|
return "stored";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
normalized === "stored" ||
|
||||||
|
normalized === "derived" ||
|
||||||
|
normalized === "lazy" ||
|
||||||
|
normalized === "external" ||
|
||||||
|
normalized === "static_final"
|
||||||
|
) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
throw new Error(`Unknown Camino field storage kind: ${String(value)}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldStorageFromOption = (value: unknown): FieldStorage => {
|
||||||
|
const object = asObject(value);
|
||||||
|
if (!object) {
|
||||||
|
return {
|
||||||
|
kind: "stored",
|
||||||
|
cache: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const kind = fieldStorageKindFromOption(object.kind);
|
||||||
|
const resolver = object.resolver === undefined
|
||||||
|
? undefined
|
||||||
|
: functionRefFromOption(object.resolver);
|
||||||
|
if ((kind === "derived" || kind === "lazy" || kind === "external") && !resolver) {
|
||||||
|
throw new Error(`${kind} field storage requires resolver`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
kind,
|
||||||
|
...(resolver ? { resolver } : {}),
|
||||||
|
cache: object.cache === true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldOpsFromOption = (value: unknown) => {
|
||||||
|
const object = asObject(value);
|
||||||
|
if (!object) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const implementation = enumName(object.implementation);
|
||||||
|
if (implementation !== "SERVICE") {
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported Camino field implementation: ${String(object.implementation)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const service = readString(object, "service");
|
||||||
|
if (!service) {
|
||||||
|
throw new Error("SERVICE field_ops requires service");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
implementation: "service" as const,
|
||||||
|
service,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractFileDefaults = (sourceFile: string) => {
|
||||||
|
const text = fs.readFileSync(sourceFile, "utf8");
|
||||||
|
const packageMatch = /^\s*package\s+([a-zA-Z0-9_.]+)\s*;/m.exec(text);
|
||||||
|
const namespaceMatch =
|
||||||
|
/^\s*option\s+\(camino\.schema_namespace\)\s*=\s*"([^"]+)"\s*;/m.exec(
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
const versionMatch =
|
||||||
|
/^\s*option\s+\(camino\.schema_version\)\s*=\s*"([^"]+)"\s*;/m.exec(text);
|
||||||
|
|
||||||
|
return {
|
||||||
|
packageName: packageMatch?.[1] ?? "",
|
||||||
|
schemaNamespace: namespaceMatch?.[1] ?? packageMatch?.[1] ?? "",
|
||||||
|
schemaVersion: versionMatch?.[1] ?? "1",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const protoSymbolFromReflection = (
|
||||||
|
reflection: protobuf.ReflectionObject | null,
|
||||||
|
fallbackVersion: string,
|
||||||
|
): SymbolRef | undefined => {
|
||||||
|
if (!reflection?.fullName) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const parts = reflection.fullName.replace(/^\./, "").split(".");
|
||||||
|
const name = parts.pop();
|
||||||
|
if (!name) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
namespace: parts.join("."),
|
||||||
|
name,
|
||||||
|
version: fallbackVersion,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeRefForField = (
|
||||||
|
field: protobuf.Field,
|
||||||
|
schemaVersion: string,
|
||||||
|
): TypeRef => {
|
||||||
|
const symbol = protoSymbolFromReflection(field.resolvedType, schemaVersion);
|
||||||
|
const enumValues =
|
||||||
|
field.resolvedType instanceof protobuf.Enum
|
||||||
|
? Object.entries(field.resolvedType.values)
|
||||||
|
.filter(([_name, value]) => value !== 0)
|
||||||
|
.map(([name]) => name)
|
||||||
|
: undefined;
|
||||||
|
return {
|
||||||
|
protoType: field.type,
|
||||||
|
...(symbol ? { symbol } : {}),
|
||||||
|
...(enumValues ? { enumValues } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeRefForType = (
|
||||||
|
type: protobuf.Type | undefined,
|
||||||
|
fallbackType: string,
|
||||||
|
schemaVersion: string,
|
||||||
|
): TypeRef => {
|
||||||
|
const symbol = protoSymbolFromReflection(type ?? null, schemaVersion);
|
||||||
|
return {
|
||||||
|
protoType: type?.fullName?.replace(/^\./, "") ?? fallbackType,
|
||||||
|
...(symbol ? { symbol } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldSchemaFromField = (
|
||||||
|
field: protobuf.Field,
|
||||||
|
schemaVersion: string,
|
||||||
|
): FieldSchema => {
|
||||||
|
const storage = fieldStorageFromOption(
|
||||||
|
getReflectionOption(field, FIELD_STORAGE_OPTION),
|
||||||
|
);
|
||||||
|
const ops = fieldOpsFromOption(getReflectionOption(field, FIELD_OPS_OPTION));
|
||||||
|
if (ops && storage.kind !== "stored" && storage.kind !== "static_final") {
|
||||||
|
throw new Error(
|
||||||
|
`Field ${field.fullName} cannot declare both field_ops and package-resolved storage`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (storage.kind === "static_final" && !ops) {
|
||||||
|
throw new Error(`Field ${field.fullName} with static_final storage requires field_ops`);
|
||||||
|
}
|
||||||
|
const interfaceContract = interfaceFieldContractFromOption(
|
||||||
|
getReflectionOption(field, INTERFACE_FIELD_OPTION),
|
||||||
|
schemaVersion,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
name: field.name,
|
||||||
|
tag: field.id,
|
||||||
|
type: typeRefForField(field, schemaVersion),
|
||||||
|
conflict: conflictFromOption(getReflectionOption(field, CONFLICT_OPTION)),
|
||||||
|
repeated: field.repeated,
|
||||||
|
optional: field.optional,
|
||||||
|
storage,
|
||||||
|
...(ops ? { ops } : {}),
|
||||||
|
...(interfaceContract ? { interfaceContract } : {}),
|
||||||
|
isDisplayLabel: getReflectionOption(field, DISPLAY_LABEL_OPTION) === true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const isStringType = (type: TypeRef) =>
|
||||||
|
type.protoType === "string" || type.protoType === "google.protobuf.StringValue";
|
||||||
|
|
||||||
|
const validateDisplayLabelFields = (
|
||||||
|
type: protobuf.Type,
|
||||||
|
fields: FieldSchema[],
|
||||||
|
operationServices: OperationServiceSchema[],
|
||||||
|
) => {
|
||||||
|
const displayLabelFields = fields.filter((field) => field.isDisplayLabel);
|
||||||
|
if (displayLabelFields.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Class ${type.fullName} declares multiple Camino display label fields`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const displayLabelField = displayLabelFields[0];
|
||||||
|
if (!displayLabelField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!displayLabelField.ops) {
|
||||||
|
if (!isStringType(displayLabelField.type)) {
|
||||||
|
throw new Error(
|
||||||
|
`Display label field ${type.fullName}.${displayLabelField.name} must have string type`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const serviceName = displayLabelField.ops.service.replace(/^\./, "");
|
||||||
|
const getOperation = operationServices
|
||||||
|
.find((service) => service.fullName === serviceName)
|
||||||
|
?.operations.find((operation) => operation.name === "Get");
|
||||||
|
if (!getOperation || !isStringType(getOperation.outputType)) {
|
||||||
|
throw new Error(
|
||||||
|
`Display label field ${type.fullName}.${displayLabelField.name} must resolve to string`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeBindingFromOption = (
|
||||||
|
value: unknown,
|
||||||
|
schemaVersion: string,
|
||||||
|
) => {
|
||||||
|
const object = asObject(value);
|
||||||
|
if (!object) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const name = readString(object, "name");
|
||||||
|
const type = typeRefFromOption(object.type, schemaVersion);
|
||||||
|
if (!name || !type) {
|
||||||
|
throw new Error("Interface type_binding requires name and type");
|
||||||
|
}
|
||||||
|
return { name, type };
|
||||||
|
};
|
||||||
|
|
||||||
|
const interfaceImplementationFromOption = (
|
||||||
|
value: unknown,
|
||||||
|
defaults: ReturnType<typeof extractFileDefaults>,
|
||||||
|
): InterfaceImplementation => {
|
||||||
|
const object = asObject(value);
|
||||||
|
if (!object) {
|
||||||
|
throw new Error("Expected implements option object");
|
||||||
|
}
|
||||||
|
const interfaceId = symbolRefFromOption(object.interface, {
|
||||||
|
namespace: defaults.schemaNamespace,
|
||||||
|
name: "",
|
||||||
|
version: defaults.schemaVersion,
|
||||||
|
});
|
||||||
|
if (!interfaceId.name) {
|
||||||
|
throw new Error("implements option requires interface name");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
interface: interfaceId,
|
||||||
|
typeBindings: asArray(object.type_binding)
|
||||||
|
.map((binding) => typeBindingFromOption(binding, defaults.schemaVersion))
|
||||||
|
.filter((binding): binding is NonNullable<typeof binding> =>
|
||||||
|
Boolean(binding),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const serviceName = (service: protobuf.Service) =>
|
||||||
|
service.fullName.replace(/^\./, "");
|
||||||
|
|
||||||
|
const operationFromMethod = (
|
||||||
|
method: protobuf.Method,
|
||||||
|
schemaVersion: string,
|
||||||
|
): OperationSchema => {
|
||||||
|
const fn = functionRefFromOption(getReflectionOption(method, IMPL_OPTION));
|
||||||
|
const methodWithTypes = method as protobuf.Method & {
|
||||||
|
resolvedRequestType?: protobuf.Type;
|
||||||
|
resolvedResponseType?: protobuf.Type;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
name: method.name,
|
||||||
|
inputType: typeRefForType(
|
||||||
|
methodWithTypes.resolvedRequestType,
|
||||||
|
method.requestType,
|
||||||
|
schemaVersion,
|
||||||
|
),
|
||||||
|
outputType: typeRefForType(
|
||||||
|
methodWithTypes.resolvedResponseType,
|
||||||
|
method.responseType,
|
||||||
|
schemaVersion,
|
||||||
|
),
|
||||||
|
function: fn,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const operationServiceFromService = (
|
||||||
|
service: protobuf.Service,
|
||||||
|
schemaVersion: string,
|
||||||
|
): OperationServiceSchema => ({
|
||||||
|
name: service.name,
|
||||||
|
fullName: serviceName(service),
|
||||||
|
operations: service.methodsArray.map((method) =>
|
||||||
|
operationFromMethod(method, schemaVersion),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const classSchemaFromType = (
|
||||||
|
type: protobuf.Type,
|
||||||
|
sourceFile: string,
|
||||||
|
defaults: ReturnType<typeof extractFileDefaults>,
|
||||||
|
operationServices: OperationServiceSchema[],
|
||||||
|
): ClassSchema | undefined => {
|
||||||
|
const classOption = asObject(getReflectionOption(type, CLASS_OPTION));
|
||||||
|
if (!classOption) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackClassRef = {
|
||||||
|
namespace: defaults.schemaNamespace,
|
||||||
|
name: type.name,
|
||||||
|
version: defaults.schemaVersion,
|
||||||
|
};
|
||||||
|
const classId = symbolRefFromOption(classOption.id, fallbackClassRef);
|
||||||
|
const classVersion = readNumber(classOption, "version") ?? Number(classId.version);
|
||||||
|
if (!Number.isInteger(classVersion) || classVersion <= 0) {
|
||||||
|
throw new Error(`Class ${type.fullName} has invalid Camino class version`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = type.fieldsArray.map((field) =>
|
||||||
|
fieldSchemaFromField(field, defaults.schemaVersion),
|
||||||
|
);
|
||||||
|
validateDisplayLabelFields(type, fields, operationServices);
|
||||||
|
const edges = type.fieldsArray.flatMap((field) => {
|
||||||
|
const edgeOption = asObject(getReflectionOption(field, EDGE_OPTION));
|
||||||
|
if (!edgeOption) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const targetClass = 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 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";
|
||||||
|
if (isMany && !field.repeated) {
|
||||||
|
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${cardinality}`);
|
||||||
|
}
|
||||||
|
if (!isMany && field.repeated) {
|
||||||
|
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${cardinality}`);
|
||||||
|
}
|
||||||
|
const edgeId = symbolRefFromOption(edgeOption.id, {
|
||||||
|
namespace: defaults.schemaNamespace,
|
||||||
|
name: `${type.name}.${field.name}`,
|
||||||
|
version: defaults.schemaVersion,
|
||||||
|
});
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: edgeId,
|
||||||
|
sourceField: field.name,
|
||||||
|
fromClass: classId,
|
||||||
|
toClass: targetClass,
|
||||||
|
cardinality,
|
||||||
|
...(readString(edgeOption, "inverse")
|
||||||
|
? { inverse: readString(edgeOption, "inverse") }
|
||||||
|
: {}),
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const methods: MethodSchema[] = getReflectionOptions(type, METHOD_OPTION)
|
||||||
|
.map(asObject)
|
||||||
|
.filter((option): option is OptionBag => Boolean(option))
|
||||||
|
.map((option) => {
|
||||||
|
const name = readString(option, "name");
|
||||||
|
if (!name) {
|
||||||
|
throw new Error(`Method option on ${type.fullName} requires name`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
function: functionRefFromOption(option.function),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const migrations: MigrationSpec[] = getReflectionOptions(type, MIGRATION_OPTION)
|
||||||
|
.map(asObject)
|
||||||
|
.filter((option): option is OptionBag => Boolean(option))
|
||||||
|
.map((option) => {
|
||||||
|
const fromVersion = readNumber(option, "from_version");
|
||||||
|
const toVersion = readNumber(option, "to_version");
|
||||||
|
if (!fromVersion || !toVersion) {
|
||||||
|
throw new Error(`Migration option on ${type.fullName} requires versions`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fromVersion,
|
||||||
|
toVersion,
|
||||||
|
function: functionRefFromOption(option.function),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const constructorOption = asObject(
|
||||||
|
getReflectionOption(type, CONSTRUCTOR_OPTION),
|
||||||
|
);
|
||||||
|
const constructorSpec = constructorOption
|
||||||
|
? { function: functionRefFromOption(constructorOption.function) }
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: classId,
|
||||||
|
version: classVersion,
|
||||||
|
fields,
|
||||||
|
edges,
|
||||||
|
methods,
|
||||||
|
migrations,
|
||||||
|
operationServices,
|
||||||
|
implements: getReflectionOptions(type, IMPLEMENTS_OPTION).map((option) =>
|
||||||
|
interfaceImplementationFromOption(option, defaults),
|
||||||
|
),
|
||||||
|
...(constructorSpec ? { constructorSpec } : {}),
|
||||||
|
sourceFile,
|
||||||
|
protoMessage: type.fullName,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const interfaceSchemaFromType = (
|
||||||
|
type: protobuf.Type,
|
||||||
|
sourceFile: string,
|
||||||
|
defaults: ReturnType<typeof extractFileDefaults>,
|
||||||
|
): InterfaceSchema | undefined => {
|
||||||
|
const interfaceOption = asObject(getReflectionOption(type, INTERFACE_OPTION));
|
||||||
|
if (!interfaceOption) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const classOption = asObject(getReflectionOption(type, CLASS_OPTION));
|
||||||
|
if (classOption) {
|
||||||
|
throw new Error(`${type.fullName} cannot be both a Camino class and interface`);
|
||||||
|
}
|
||||||
|
const fallbackInterfaceRef = {
|
||||||
|
namespace: defaults.schemaNamespace,
|
||||||
|
name: type.name,
|
||||||
|
version: defaults.schemaVersion,
|
||||||
|
};
|
||||||
|
const interfaceId = symbolRefFromOption(
|
||||||
|
interfaceOption.id,
|
||||||
|
fallbackInterfaceRef,
|
||||||
|
);
|
||||||
|
const interfaceVersion =
|
||||||
|
readNumber(interfaceOption, "version") ?? Number(interfaceId.version);
|
||||||
|
if (!Number.isInteger(interfaceVersion) || interfaceVersion <= 0) {
|
||||||
|
throw new Error(`Interface ${type.fullName} has invalid Camino interface version`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: interfaceId,
|
||||||
|
version: interfaceVersion,
|
||||||
|
fields: type.fieldsArray.map((field) =>
|
||||||
|
fieldSchemaFromField(field, defaults.schemaVersion),
|
||||||
|
),
|
||||||
|
sourceFile,
|
||||||
|
protoMessage: type.fullName,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const walkTypes = (
|
||||||
|
namespace: protobuf.NamespaceBase,
|
||||||
|
visit: (type: protobuf.Type) => void,
|
||||||
|
) => {
|
||||||
|
for (const nested of namespace.nestedArray) {
|
||||||
|
if (nested instanceof protobuf.Type) {
|
||||||
|
visit(nested);
|
||||||
|
walkTypes(nested, visit);
|
||||||
|
} else if (nested instanceof protobuf.Namespace) {
|
||||||
|
walkTypes(nested, visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const walkServices = (
|
||||||
|
namespace: protobuf.NamespaceBase,
|
||||||
|
visit: (service: protobuf.Service) => void,
|
||||||
|
) => {
|
||||||
|
for (const nested of namespace.nestedArray) {
|
||||||
|
if (nested instanceof protobuf.Service) {
|
||||||
|
visit(nested);
|
||||||
|
} else if (nested instanceof protobuf.Namespace) {
|
||||||
|
walkServices(nested, visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const compileCaminoSchema = async (
|
||||||
|
sourceFile: string,
|
||||||
|
): Promise<SchemaCompileResult> => {
|
||||||
|
const absoluteSourceFile = path.resolve(sourceFile);
|
||||||
|
const defaults = extractFileDefaults(absoluteSourceFile);
|
||||||
|
const root = new protobuf.Root();
|
||||||
|
const envIncludeDirs = [
|
||||||
|
process.env.QUIXOS_PROTO_PATH,
|
||||||
|
process.env.CAMINO_PROTO_PATH,
|
||||||
|
]
|
||||||
|
.filter((value): value is string => Boolean(value))
|
||||||
|
.join(path.delimiter)
|
||||||
|
.split(path.delimiter)
|
||||||
|
.map((entry) => entry.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((entry) => path.resolve(entry));
|
||||||
|
const includeDirs = [
|
||||||
|
path.dirname(absoluteSourceFile),
|
||||||
|
path.resolve("../../quixos-protocol/proto"),
|
||||||
|
path.resolve("quixos-protocol/proto"),
|
||||||
|
path.resolve("proto"),
|
||||||
|
...envIncludeDirs,
|
||||||
|
];
|
||||||
|
root.resolvePath = (origin, target) => {
|
||||||
|
if (path.isAbsolute(target) && fs.existsSync(target)) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
const originDir = origin ? path.dirname(origin) : undefined;
|
||||||
|
const candidates = [
|
||||||
|
...(originDir ? [path.resolve(originDir, target)] : []),
|
||||||
|
...includeDirs.map((dir) => path.resolve(dir, target)),
|
||||||
|
];
|
||||||
|
const found = candidates.find((candidate) => fs.existsSync(candidate));
|
||||||
|
return found ?? target;
|
||||||
|
};
|
||||||
|
|
||||||
|
await root.load(absoluteSourceFile, { keepCase: true });
|
||||||
|
root.resolveAll();
|
||||||
|
|
||||||
|
const operationServices: OperationServiceSchema[] = [];
|
||||||
|
walkServices(root, (service) => {
|
||||||
|
operationServices.push(
|
||||||
|
operationServiceFromService(service, defaults.schemaVersion),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const classes: ClassSchema[] = [];
|
||||||
|
const interfaces: InterfaceSchema[] = [];
|
||||||
|
walkTypes(root, (type) => {
|
||||||
|
const interfaceSchema = interfaceSchemaFromType(
|
||||||
|
type,
|
||||||
|
absoluteSourceFile,
|
||||||
|
defaults,
|
||||||
|
);
|
||||||
|
if (interfaceSchema) {
|
||||||
|
interfaces.push(interfaceSchema);
|
||||||
|
}
|
||||||
|
const classSchema = classSchemaFromType(
|
||||||
|
type,
|
||||||
|
absoluteSourceFile,
|
||||||
|
defaults,
|
||||||
|
operationServices,
|
||||||
|
);
|
||||||
|
if (classSchema) {
|
||||||
|
classes.push(classSchema);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceFile: absoluteSourceFile,
|
||||||
|
classes,
|
||||||
|
interfaces,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
export type SymbolRef = {
|
||||||
|
namespace: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
hash?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FunctionRef = {
|
||||||
|
packageNamespace: string;
|
||||||
|
packageName: string;
|
||||||
|
symbol: string;
|
||||||
|
operation?: string;
|
||||||
|
versionRef?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConflictStrategy =
|
||||||
|
| "replace"
|
||||||
|
| "preserve_conflicts"
|
||||||
|
| "crdt";
|
||||||
|
|
||||||
|
export type Cardinality =
|
||||||
|
| "optional_one"
|
||||||
|
| "exactly_one"
|
||||||
|
| "many"
|
||||||
|
| "many_unique"
|
||||||
|
| "many_ordered";
|
||||||
|
|
||||||
|
export type FieldStorageKind =
|
||||||
|
| "stored"
|
||||||
|
| "derived"
|
||||||
|
| "lazy"
|
||||||
|
| "external"
|
||||||
|
| "static_final";
|
||||||
|
|
||||||
|
export type FieldStorage = {
|
||||||
|
kind: FieldStorageKind;
|
||||||
|
resolver?: FunctionRef;
|
||||||
|
cache: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FieldOps = {
|
||||||
|
implementation: "service";
|
||||||
|
service: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TypeRef = {
|
||||||
|
protoType: string;
|
||||||
|
symbol?: SymbolRef;
|
||||||
|
enumValues?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TypeBinding = {
|
||||||
|
name: string;
|
||||||
|
type: TypeRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InterfaceFieldContract = {
|
||||||
|
required: boolean;
|
||||||
|
readable: boolean;
|
||||||
|
writable: boolean;
|
||||||
|
watchable: boolean;
|
||||||
|
typeParam?: string;
|
||||||
|
type?: TypeRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InterfaceImplementation = {
|
||||||
|
interface: SymbolRef;
|
||||||
|
typeBindings: TypeBinding[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FieldSchema = {
|
||||||
|
name: string;
|
||||||
|
tag: number;
|
||||||
|
type: TypeRef;
|
||||||
|
conflict: ConflictStrategy;
|
||||||
|
repeated: boolean;
|
||||||
|
optional: boolean;
|
||||||
|
storage: FieldStorage;
|
||||||
|
ops?: FieldOps;
|
||||||
|
interfaceContract?: InterfaceFieldContract;
|
||||||
|
isDisplayLabel: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EdgeSchema = {
|
||||||
|
id: SymbolRef;
|
||||||
|
sourceField: string;
|
||||||
|
fromClass: SymbolRef;
|
||||||
|
toClass: SymbolRef;
|
||||||
|
cardinality: Cardinality;
|
||||||
|
inverse?: string;
|
||||||
|
fields: FieldSchema[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MethodSchema = {
|
||||||
|
name: string;
|
||||||
|
function: FunctionRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MigrationSpec = {
|
||||||
|
fromVersion: number;
|
||||||
|
toVersion: number;
|
||||||
|
function: FunctionRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConstructorSpec = {
|
||||||
|
function: FunctionRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OperationSchema = {
|
||||||
|
name: string;
|
||||||
|
inputType: TypeRef;
|
||||||
|
outputType: TypeRef;
|
||||||
|
function: FunctionRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OperationServiceSchema = {
|
||||||
|
name: string;
|
||||||
|
fullName: string;
|
||||||
|
operations: OperationSchema[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClassSchema = {
|
||||||
|
id: SymbolRef;
|
||||||
|
version: number;
|
||||||
|
fields: FieldSchema[];
|
||||||
|
edges: EdgeSchema[];
|
||||||
|
methods: MethodSchema[];
|
||||||
|
migrations: MigrationSpec[];
|
||||||
|
operationServices: OperationServiceSchema[];
|
||||||
|
implements: InterfaceImplementation[];
|
||||||
|
constructorSpec?: ConstructorSpec;
|
||||||
|
sourceFile: string;
|
||||||
|
protoMessage: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InterfaceSchema = {
|
||||||
|
id: SymbolRef;
|
||||||
|
version: number;
|
||||||
|
fields: FieldSchema[];
|
||||||
|
sourceFile: string;
|
||||||
|
protoMessage: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SchemaCompileResult = {
|
||||||
|
sourceFile: string;
|
||||||
|
classes: ClassSchema[];
|
||||||
|
interfaces: InterfaceSchema[];
|
||||||
|
};
|
||||||
@@ -162,16 +162,29 @@ cacheEntries = {
|
|||||||
"@bufbuild/protoplugin@npm:2.12.1" = { filename = "@bufbuild-protoplugin-npm-2.12.1-e1aebb80bf-c7b401ab31.zip"; hash = "sha512-x7QBqzHZSsaCXLm75c/3XQXSjuINRnskt6XUKZuaygZmhuo5t6OoRTuLWgGD+qd4HoP8LliAZhgHudMVX3Hr6Q=="; };
|
"@bufbuild/protoplugin@npm:2.12.1" = { filename = "@bufbuild-protoplugin-npm-2.12.1-e1aebb80bf-c7b401ab31.zip"; hash = "sha512-x7QBqzHZSsaCXLm75c/3XQXSjuINRnskt6XUKZuaygZmhuo5t6OoRTuLWgGD+qd4HoP8LliAZhgHudMVX3Hr6Q=="; };
|
||||||
"@connectrpc/connect-node@npm:2.1.2" = { filename = "@connectrpc-connect-node-npm-2.1.2-021434d5d5-ba8b0c9384.zip"; hash = "sha512-uosMk4TDgps3eqeoJtwM1Qpgk1J9LscBRgSRKDYzbqlgSitazqQrEEd/i+RWnknd7WbKD8d/X1sCcDXgKeE+qQ=="; };
|
"@connectrpc/connect-node@npm:2.1.2" = { filename = "@connectrpc-connect-node-npm-2.1.2-021434d5d5-ba8b0c9384.zip"; hash = "sha512-uosMk4TDgps3eqeoJtwM1Qpgk1J9LscBRgSRKDYzbqlgSitazqQrEEd/i+RWnknd7WbKD8d/X1sCcDXgKeE+qQ=="; };
|
||||||
"@connectrpc/connect@npm:2.1.2" = { filename = "@connectrpc-connect-npm-2.1.2-ab34825de3-4e15000890.zip"; hash = "sha512-ThUACJAjt1Lv1E+pQKWuHh+gDhvrxLoM7ScruTWSDYw7hbd0UOZErUQruMqaJ1sMFxNviWuo7/A2F4Ex2wHWFQ=="; };
|
"@connectrpc/connect@npm:2.1.2" = { filename = "@connectrpc-connect-npm-2.1.2-ab34825de3-4e15000890.zip"; hash = "sha512-ThUACJAjt1Lv1E+pQKWuHh+gDhvrxLoM7ScruTWSDYw7hbd0UOZErUQruMqaJ1sMFxNviWuo7/A2F4Ex2wHWFQ=="; };
|
||||||
|
"@protobufjs/aspromise@npm:1.1.2" = { filename = "@protobufjs-aspromise-npm-1.1.2-71d00b938f-a83343a468.zip"; hash = "sha512-qDNDpGj/W17Gv/Nv14imTIOeSKB/+fT4E1ZPWMr0TQEc1lBO0hR780g1vXp90hBwUq91WWHGsJj9iQK09lANDw=="; };
|
||||||
|
"@protobufjs/base64@npm:1.1.2" = { filename = "@protobufjs-base64-npm-1.1.2-cd8ca6814a-eec925e681.zip"; hash = "sha512-7skl5oEIGvGQuO4jH5utMQHhiau8GC/yedprUx59vSpW8fMG83qAsb6eAKotJxaQ0I3MXzJvccnu2FRmdcjK9g=="; };
|
||||||
|
"@protobufjs/codegen@npm:2.0.5" = { filename = "@protobufjs-codegen-npm-2.0.5-bb74ff329d-1b8a2ae56e.zip"; hash = "sha512-G4oq5W7mClbp0gXNS2ByoVA8UGm467kFcQ+XT/AJig0HAGQcE34KjZje3xRCMVahBqlDNpXL9SV0gQ9VAA/cqw=="; };
|
||||||
|
"@protobufjs/eventemitter@npm:1.1.1" = { filename = "@protobufjs-eventemitter-npm-1.1.1-dbe0dfc812-8e06193d46.zip"; hash = "sha512-jgYZPUYpxefAnU+MLduo/E36c58BSfM6HZAVaNNbt7i1J3pOhFK6873QswL9WZzyVdGTJnqpOgpHR+I80HPErA=="; };
|
||||||
|
"@protobufjs/fetch@npm:1.1.1" = { filename = "@protobufjs-fetch-npm-1.1.1-b98d5396a8-a497ff5433.zip"; hash = "sha512-pJf/VDOFToV38EJ5g+o5uRE7SagSD5RRUpHXYzJwYdLDAT5g4k6kNtCR2vrgGg9usYZ+OxYWBF2Wox2LPGRu1A=="; };
|
||||||
|
"@protobufjs/float@npm:1.0.2" = { filename = "@protobufjs-float-npm-1.0.2-5678f64d08-18f2bdede7.zip"; hash = "sha512-GPK97edv/PAXBwivFcnJ22JZt3HmuExRsG3zSpwznbvuwmfRTOC93SCswUKx2YDZg9MUNDmN9/mOsMlKDreQaQ=="; };
|
||||||
|
"@protobufjs/path@npm:1.1.2" = { filename = "@protobufjs-path-npm-1.1.2-641d08de76-cece0a938e.zip"; hash = "sha512-zs4Kk45/Xf0voD+MFPLxz4sNbhOscyb/TJbqMR7/1ft64LunVPv1BTEq8uOFACUMkOaFBrl8AjYKQ3k9iKDYtA=="; };
|
||||||
|
"@protobufjs/pool@npm:1.1.0" = { filename = "@protobufjs-pool-npm-1.1.0-47a76f96a1-eda2718b7f.zip"; hash = "sha512-7aJxi38iKsbmrTb3WKku+Q0mUmAmoZ9PF/Zo9F4DBqW9c03vP0j1H4E0rgl4tiYqXFF8CLEVpVF1bRo6rfzwOA=="; };
|
||||||
|
"@protobufjs/utf8@npm:1.1.2" = { filename = "@protobufjs-utf8-npm-1.1.2-9c7ca5968f-975c6e2c03.zip"; hash = "sha512-l1xuLAMZvVDBdTHRhrU3qIy00yBePKkpfNhCtjHTQdoO4/8WvoZWzSZgdW3HU7KcimoUwK2O1gLB+R3fIuez8w=="; };
|
||||||
"@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a" = { filename = "@quixos-camino-datatypes-https-faf980267e-4ed01c75c1.zip"; hash = "sha512-TtAcdcG7mReSI/kmbu+U91w39uYetoOiQbq6ex48zaTphDXn1vO1aaw0k8kEDhQgE1LMZrFgHyPzjlUIkyKdrA=="; };
|
"@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a" = { filename = "@quixos-camino-datatypes-https-faf980267e-4ed01c75c1.zip"; hash = "sha512-TtAcdcG7mReSI/kmbu+U91w39uYetoOiQbq6ex48zaTphDXn1vO1aaw0k8kEDhQgE1LMZrFgHyPzjlUIkyKdrA=="; };
|
||||||
"@types/node@npm:24.13.3" = { filename = "@types-node-npm-24.13.3-b512a0bbeb-a5bc08f49b.zip"; hash = "sha512-pbwI9JuVgdzcqQ4CzXcZejx5mECAdmSULlzVFhpVPU0q6AZPfjKUQQ10jX0Ji1wYSL5/pQwG8pxBk6sWvk5Z6w=="; };
|
"@types/node@npm:24.13.3" = { filename = "@types-node-npm-24.13.3-b512a0bbeb-a5bc08f49b.zip"; hash = "sha512-pbwI9JuVgdzcqQ4CzXcZejx5mECAdmSULlzVFhpVPU0q6AZPfjKUQQ10jX0Ji1wYSL5/pQwG8pxBk6sWvk5Z6w=="; };
|
||||||
|
"@types/node@npm:26.1.1" = { filename = "@types-node-npm-26.1.1-7a7a9f32f4-25ac509319.zip"; hash = "sha512-JaxQkxlXNt0HTVAnrbuoVhe8lR1aQVBuvTuQcwSKy3IzYT84ItX5uUR/nAhByR9jh8Rig5FnZ+PvefI5F0UsRg=="; };
|
||||||
"@typescript/vfs@npm:1.6.4" = { filename = "@typescript-vfs-npm-1.6.4-78935c9d3e-acb9de42f2.zip"; hash = "sha512-rLneQvI/2j9149eQC6EG7zI6L0588OSpTbtFfibTU8pjuzUZPtGjL8hzPzrlkJlhKykLBr1iIWlIYb65v7YvYg=="; };
|
"@typescript/vfs@npm:1.6.4" = { filename = "@typescript-vfs-npm-1.6.4-78935c9d3e-acb9de42f2.zip"; hash = "sha512-rLneQvI/2j9149eQC6EG7zI6L0588OSpTbtFfibTU8pjuzUZPtGjL8hzPzrlkJlhKykLBr1iIWlIYb65v7YvYg=="; };
|
||||||
"debug@npm:4.4.3" = { filename = "debug-npm-4.4.3-0105c6123a-d79136ec6c.zip"; hash = "sha512-15E27GyD7L79D2pVk9pqnJHsTX3cS1TIg9bnHsmsy19noaXpbQCjKBlrW1yG02XpjYo6cIVqrxa057GYXmf1pg=="; };
|
"debug@npm:4.4.3" = { filename = "debug-npm-4.4.3-0105c6123a-d79136ec6c.zip"; hash = "sha512-15E27GyD7L79D2pVk9pqnJHsTX3cS1TIg9bnHsmsy19noaXpbQCjKBlrW1yG02XpjYo6cIVqrxa057GYXmf1pg=="; };
|
||||||
|
"long@npm:5.3.2" = { filename = "long-npm-5.3.2-f80d0f7d39-7130fe1cbc.zip"; hash = "sha512-cTD+HLzi3KBnNLNbcNOAyj9wJxx/iFLJIqfGLIbE418MOSkFZeynEzxiWQjUDhJqxXwCsbGkY2uUV9d+HmC5gQ=="; };
|
||||||
"ms@npm:2.1.3" = { filename = "ms-npm-2.1.3-81ff3cfac1-d924b57e73.zip"; hash = "sha512-2SS1fnMSs7Y60h/Fs9wK9eeNYaH8fPtUV+2vJjJr9ivlMHzIf/toYu8cKzOwIzzbXU8BxMlYzA1mCUi2Wih6SA=="; };
|
"ms@npm:2.1.3" = { filename = "ms-npm-2.1.3-81ff3cfac1-d924b57e73.zip"; hash = "sha512-2SS1fnMSs7Y60h/Fs9wK9eeNYaH8fPtUV+2vJjJr9ivlMHzIf/toYu8cKzOwIzzbXU8BxMlYzA1mCUi2Wih6SA=="; };
|
||||||
|
"protobufjs@npm:7.6.5" = { filename = "protobufjs-npm-7.6.5-ff4a872120-863eaca9c6.zip"; hash = "sha512-hj6sqcb0W/z7h4fFRfU+nGaWUH4yjjmBtGQ8EtNd9/KCYCHBDj/TC+80LBOo6dMGVHusnAhJ7jv1D3cKEvAdxQ=="; };
|
||||||
"typescript@npm:5.4.5" = { filename = "typescript-npm-5.4.5-8568a42232-2954022ada.zip"; hash = "sha512-KVQCKto0D9PWqeK45TT2XVfJLV85iaJjdUp4q6VJ9+ZSmswZIZE1YKS4FsRtzn30pNKfnxGj3A1CE7t20EMlHg=="; };
|
"typescript@npm:5.4.5" = { filename = "typescript-npm-5.4.5-8568a42232-2954022ada.zip"; hash = "sha512-KVQCKto0D9PWqeK45TT2XVfJLV85iaJjdUp4q6VJ9+ZSmswZIZE1YKS4FsRtzn30pNKfnxGj3A1CE7t20EMlHg=="; };
|
||||||
"typescript@npm:5.9.3" = { filename = "typescript-npm-5.9.3-48715be868-6bd7552ce3.zip"; hash = "sha512-a9dVLOOfl+cR21qgSPb5mVtT8cUvfYZnwavcFwDGinajCPV5zTCc5rU2Rt606aG+fIE6k7qvCijM1TajAnDhxQ=="; };
|
"typescript@npm:5.9.3" = { filename = "typescript-npm-5.9.3-48715be868-6bd7552ce3.zip"; hash = "sha512-a9dVLOOfl+cR21qgSPb5mVtT8cUvfYZnwavcFwDGinajCPV5zTCc5rU2Rt606aG+fIE6k7qvCijM1TajAnDhxQ=="; };
|
||||||
"typescript@patch:typescript@npm%3A5.4.5#optional!builtin<compat/typescript>::version=5.4.5&hash=5adc0c" = { filename = "typescript-patch-6e159bfddb-db2ad2a16c.zip"; hash = "sha512-2yrSoWyoKfUEJ+6x2hVeekXlmO7HsIbYtOi6ROWiNfdY5gbWgcZpkiMNP8O4mVhl5f0LIqLJVIbQsyAPgwcuyQ=="; };
|
"typescript@patch:typescript@npm%3A5.4.5#optional!builtin<compat/typescript>::version=5.4.5&hash=5adc0c" = { filename = "typescript-patch-6e159bfddb-db2ad2a16c.zip"; hash = "sha512-2yrSoWyoKfUEJ+6x2hVeekXlmO7HsIbYtOi6ROWiNfdY5gbWgcZpkiMNP8O4mVhl5f0LIqLJVIbQsyAPgwcuyQ=="; };
|
||||||
"typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5" = { filename = "typescript-patch-6fda4d02cf-ad09fdf7a7.zip"; hash = "sha512-rQn996dWgU3OZbxgwWV7QNREUTRoWO6iMOEPLpWiidkYO24y5cEelazAzMIUtPNiidytS/GIawrbhNcR0zakMA=="; };
|
"typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5" = { filename = "typescript-patch-6fda4d02cf-ad09fdf7a7.zip"; hash = "sha512-rQn996dWgU3OZbxgwWV7QNREUTRoWO6iMOEPLpWiidkYO24y5cEelazAzMIUtPNiidytS/GIawrbhNcR0zakMA=="; };
|
||||||
"undici-types@npm:7.18.2" = { filename = "undici-types-npm-7.18.2-3e6d69d829-85a7918911.zip"; hash = "sha512-haeRiRE6I4lZ16ZHNo5PfFVZw6QE69uPxEiBRc6UJvzYIlKoRKMCeY38Djfm+xeP9IHtA7xMr2NMV1fZ70NSHQ=="; };
|
"undici-types@npm:7.18.2" = { filename = "undici-types-npm-7.18.2-3e6d69d829-85a7918911.zip"; hash = "sha512-haeRiRE6I4lZ16ZHNo5PfFVZw6QE69uPxEiBRc6UJvzYIlKoRKMCeY38Djfm+xeP9IHtA7xMr2NMV1fZ70NSHQ=="; };
|
||||||
|
"undici-types@npm:8.3.0" = { filename = "undici-types-npm-8.3.0-d34470de3e-c8aa7e2fbe.zip"; hash = "sha512-yKp+L76/zlGWVNr63A7OWb6IjSzK8YD7RJXah157U20kVjRcOEBpx+bz6cmrdDXwdJV9owbxQjQ+7ob/gEiFWg=="; };
|
||||||
};
|
};
|
||||||
|
|
||||||
in overriddenProject
|
in overriddenProject
|
||||||
|
|||||||
@@ -66,6 +66,71 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2":
|
||||||
|
version: 1.1.2
|
||||||
|
resolution: "@protobufjs/aspromise@npm:1.1.2"
|
||||||
|
checksum: 10c0/a83343a468ff5b5ec6bff36fd788a64c839e48a07ff9f4f813564f58caf44d011cd6504ed2147bf34835bd7a7dd2107052af755961c6b098fd8902b4f6500d0f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/base64@npm:^1.1.2":
|
||||||
|
version: 1.1.2
|
||||||
|
resolution: "@protobufjs/base64@npm:1.1.2"
|
||||||
|
checksum: 10c0/eec925e681081af190b8ee231f9bad3101e189abbc182ff279da6b531e7dbd2a56f1f306f37a80b1be9e00aa2d271690d08dcc5f326f71c9eed8546675c8caf6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/codegen@npm:^2.0.5":
|
||||||
|
version: 2.0.5
|
||||||
|
resolution: "@protobufjs/codegen@npm:2.0.5"
|
||||||
|
checksum: 10c0/1b8a2ae56ee60a56e9d205cd4b6072a1503c5069b8ebb905710f974ff0098a0d0700641c137e0a8d98dedf14423156a106a9433695cbf52574810f55000fdcab
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/eventemitter@npm:^1.1.1":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "@protobufjs/eventemitter@npm:1.1.1"
|
||||||
|
checksum: 10c0/8e06193d4629c5e7c09d4f8c2ddba8fc4dfa739f0149f33a1d901568d35bb7b8b5277a4e8452baf3bdd0b302fd599cf255d193267aa93a0a4747e23cd073c4ac
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/fetch@npm:^1.1.1":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "@protobufjs/fetch@npm:1.1.1"
|
||||||
|
dependencies:
|
||||||
|
"@protobufjs/aspromise": "npm:^1.1.1"
|
||||||
|
checksum: 10c0/a497ff5433854e8577f0427983ea39b9113b49a8120f94515291d763327061d2c3013e60e24ea436d091dafae01a0f6eb1867e3b1616045d96a31d8b3c646ed4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/float@npm:^1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@protobufjs/float@npm:1.0.2"
|
||||||
|
checksum: 10c0/18f2bdede76ffcf0170708af15c9c9db6259b771e6b84c51b06df34a9c339dbbeec267d14ce0bddd20acc142b1d980d983d31434398df7f98eb0c94a0eb79069
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/path@npm:^1.1.2":
|
||||||
|
version: 1.1.2
|
||||||
|
resolution: "@protobufjs/path@npm:1.1.2"
|
||||||
|
checksum: 10c0/cece0a938e7f5dfd2fa03f8c14f2f1cf8b0d6e13ac7326ff4c96ea311effd5fb7ae0bba754fbf505312af2e38500250c90e68506b97c02360a43793d88a0d8b4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/pool@npm:^1.1.0":
|
||||||
|
version: 1.1.0
|
||||||
|
resolution: "@protobufjs/pool@npm:1.1.0"
|
||||||
|
checksum: 10c0/eda2718b7f222ac6e6ad36f758a92ef90d26526026a19f4f17f668f45e0306a5bd734def3f48f51f8134ae0978b6262a5c517c08b115a551756d1a3aadfcf038
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@protobufjs/utf8@npm:^1.1.1":
|
||||||
|
version: 1.1.2
|
||||||
|
resolution: "@protobufjs/utf8@npm:1.1.2"
|
||||||
|
checksum: 10c0/975c6e2c0319bd50c17531d186b537a88cb4d3205e3ca9297cd842b631d341da0ee3ff16be8656cd2660756dc753b29c8a6a14c0ad8ed602c1f91ddf22e7b3f3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a":
|
"@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a":
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
resolution: "@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a"
|
resolution: "@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a"
|
||||||
@@ -86,10 +151,22 @@ __metadata:
|
|||||||
"@connectrpc/connect-node": "npm:^2.1.2"
|
"@connectrpc/connect-node": "npm:^2.1.2"
|
||||||
"@quixos/camino-datatypes": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a"
|
"@quixos/camino-datatypes": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a"
|
||||||
"@types/node": "npm:^24"
|
"@types/node": "npm:^24"
|
||||||
|
protobufjs: "npm:^7.5.4"
|
||||||
typescript: "npm:^5.9.3"
|
typescript: "npm:^5.9.3"
|
||||||
|
bin:
|
||||||
|
camino-codegen-ts-runtime: dist/src/codegen-ts-runtime.js
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@types/node@npm:>=13.7.0":
|
||||||
|
version: 26.1.1
|
||||||
|
resolution: "@types/node@npm:26.1.1"
|
||||||
|
dependencies:
|
||||||
|
undici-types: "npm:~8.3.0"
|
||||||
|
checksum: 10c0/25ac5093195736dd074d5027adbba85617bc951d5a41506ebd3b9073048acb7233613f3822d5f9b9447f9c0841c91f6387c46283916767e3ef79f23917452c46
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/node@npm:^24":
|
"@types/node@npm:^24":
|
||||||
version: 24.13.3
|
version: 24.13.3
|
||||||
resolution: "@types/node@npm:24.13.3"
|
resolution: "@types/node@npm:24.13.3"
|
||||||
@@ -122,6 +199,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"long@npm:^5.3.2":
|
||||||
|
version: 5.3.2
|
||||||
|
resolution: "long@npm:5.3.2"
|
||||||
|
checksum: 10c0/7130fe1cbce2dca06734b35b70d380ca3f70271c7f8852c922a7c62c86c4e35f0c39290565eca7133c625908d40e126ac57c02b1b1a4636b9457d77e1e60b981
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ms@npm:^2.1.3":
|
"ms@npm:^2.1.3":
|
||||||
version: 2.1.3
|
version: 2.1.3
|
||||||
resolution: "ms@npm:2.1.3"
|
resolution: "ms@npm:2.1.3"
|
||||||
@@ -129,6 +213,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"protobufjs@npm:^7.5.4":
|
||||||
|
version: 7.6.5
|
||||||
|
resolution: "protobufjs@npm:7.6.5"
|
||||||
|
dependencies:
|
||||||
|
"@protobufjs/aspromise": "npm:^1.1.2"
|
||||||
|
"@protobufjs/base64": "npm:^1.1.2"
|
||||||
|
"@protobufjs/codegen": "npm:^2.0.5"
|
||||||
|
"@protobufjs/eventemitter": "npm:^1.1.1"
|
||||||
|
"@protobufjs/fetch": "npm:^1.1.1"
|
||||||
|
"@protobufjs/float": "npm:^1.0.2"
|
||||||
|
"@protobufjs/path": "npm:^1.1.2"
|
||||||
|
"@protobufjs/pool": "npm:^1.1.0"
|
||||||
|
"@protobufjs/utf8": "npm:^1.1.1"
|
||||||
|
"@types/node": "npm:>=13.7.0"
|
||||||
|
long: "npm:^5.3.2"
|
||||||
|
checksum: 10c0/863eaca9c6f45bfcfb8787c545f53e9c6696507e328e3981b4643c12d35df7f2826021c10e3fd30bef342c13a8e9d306547bac9c0849ee3bf50f770a12f01dc5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"typescript@npm:5.4.5":
|
"typescript@npm:5.4.5":
|
||||||
version: 5.4.5
|
version: 5.4.5
|
||||||
resolution: "typescript@npm:5.4.5"
|
resolution: "typescript@npm:5.4.5"
|
||||||
@@ -175,3 +278,10 @@ __metadata:
|
|||||||
checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d
|
checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"undici-types@npm:~8.3.0":
|
||||||
|
version: 8.3.0
|
||||||
|
resolution: "undici-types@npm:8.3.0"
|
||||||
|
checksum: 10c0/c8aa7e2fbebfce519654dafadc0ece59be888d2ccaf180fb4495da875e7b536d2456345c384069c7e6f3e9c9ab7435f074957da306f142343eee86ff8048855a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|||||||
Reference in New Issue
Block a user