From 740bbc7affb5ef1fd6586c0affb87bba451e52b7 Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Mon, 13 Jul 2026 21:49:18 -0700 Subject: [PATCH] Move Camino package codegen into package runtime --- .yarn/install-state.gz | Bin 8348 -> 13737 bytes flake.lock | 10 +- flake.nix | 2 +- package.json | 6 +- src/codegen-ts-runtime.ts | 414 +++++++++++++++++++ src/schema-compiler.ts | 809 ++++++++++++++++++++++++++++++++++++++ src/schema-ir.ts | 148 +++++++ yarn-project.nix | 13 + yarn.lock | 110 ++++++ 9 files changed, 1505 insertions(+), 7 deletions(-) create mode 100644 src/codegen-ts-runtime.ts create mode 100644 src/schema-compiler.ts create mode 100644 src/schema-ir.ts diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 93cecc4f2cd532b3538459aec7869b59b2b5b10f..5b4694f3804f22899d66d098522e168166960522 100644 GIT binary patch literal 13737 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 TT6J*${m=hDpeZH7hbRF6fGcDo literal 8348 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 { + console.error( + "Usage: camino-codegen-ts-runtime [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 & { + 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(); diff --git a/src/schema-compiler.ts b/src/schema-compiler.ts new file mode 100644 index 0000000..3b9354a --- /dev/null +++ b/src/schema-compiler.ts @@ -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; + +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, +): 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 => + 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, + 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, +): 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 => { + 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, + }; +}; diff --git a/src/schema-ir.ts b/src/schema-ir.ts new file mode 100644 index 0000000..92fc5b0 --- /dev/null +++ b/src/schema-ir.ts @@ -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[]; +}; diff --git a/yarn-project.nix b/yarn-project.nix index 8e90bd7..5e99fde 100644 --- a/yarn-project.nix +++ b/yarn-project.nix @@ -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=="; }; "@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=="; }; +"@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=="; }; "@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=="; }; "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=="; }; +"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.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::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::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:8.3.0" = { filename = "undici-types-npm-8.3.0-d34470de3e-c8aa7e2fbe.zip"; hash = "sha512-yKp+L76/zlGWVNr63A7OWb6IjSzK8YD7RJXah157U20kVjRcOEBpx+bz6cmrdDXwdJV9owbxQjQ+7ob/gEiFWg=="; }; }; in overriddenProject diff --git a/yarn.lock b/yarn.lock index be65bd7..b47d038 100644 --- a/yarn.lock +++ b/yarn.lock @@ -66,6 +66,71 @@ __metadata: languageName: node 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": version: 0.1.0 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" "@quixos/camino-datatypes": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=e061e290709c672d2dc675e5c871733de195211a" "@types/node": "npm:^24" + protobufjs: "npm:^7.5.4" typescript: "npm:^5.9.3" + bin: + camino-codegen-ts-runtime: dist/src/codegen-ts-runtime.js languageName: unknown 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": version: 24.13.3 resolution: "@types/node@npm:24.13.3" @@ -122,6 +199,13 @@ __metadata: languageName: node 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": version: 2.1.3 resolution: "ms@npm:2.1.3" @@ -129,6 +213,25 @@ __metadata: languageName: node 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": version: 5.4.5 resolution: "typescript@npm:5.4.5" @@ -175,3 +278,10 @@ __metadata: checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d languageName: node 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