Add undirected edge support to package runtime

This commit is contained in:
Timothy J. Aveni
2026-07-14 22:37:39 -07:00
parent 415eb6b411
commit d2e3172a62
16 changed files with 122 additions and 26 deletions
+48 -6
View File
@@ -18,6 +18,7 @@ import type {
MigrationSpec,
SchemaCompileResult,
SymbolRef,
EdgeDirectionality,
TypeRef,
} from "./schema-ir.js";
@@ -464,6 +465,39 @@ const endpointFromOption = (params: {
};
};
const symbolKey = (ref: SymbolRef | undefined) =>
ref ? [ref.namespace, ref.name, ref.version, ref.hash ?? ""].join(":") : "";
const endpointRoleKey = (endpoint: ReturnType<typeof endpointFromOption>) =>
[
symbolKey(endpoint.class),
symbolKey(endpoint.interface),
endpoint.projection,
endpoint.cardinality,
].join("|");
const normalizeEdgeEndpoints = (params: {
directionality: "directed" | "undirected";
fromEndpoint: ReturnType<typeof endpointFromOption>;
toEndpoint: ReturnType<typeof endpointFromOption>;
}) => {
if (params.directionality === "directed") {
return {
fromEndpoint: params.fromEndpoint,
toEndpoint: params.toEndpoint,
};
}
return endpointRoleKey(params.fromEndpoint) <= endpointRoleKey(params.toEndpoint)
? {
fromEndpoint: params.fromEndpoint,
toEndpoint: params.toEndpoint,
}
: {
fromEndpoint: params.toEndpoint,
toEndpoint: params.fromEndpoint,
};
};
const symbolArrayFromOption = (
value: unknown,
defaults: ReturnType<typeof extractFileDefaults>,
@@ -633,21 +667,21 @@ const classSchemaFromType = (
version: defaults.schemaVersion,
});
const cardinality = cardinalityFromOption(edgeOption.cardinality);
const fromEndpoint = endpointFromOption({
const declaredFromEndpoint = endpointFromOption({
option: edgeOption.this_endpoint,
fallbackClass: classId,
fallbackProjection: field.name,
fallbackCardinality: cardinality,
fallbackVersion: defaults.schemaVersion,
});
const toEndpoint = endpointFromOption({
const declaredToEndpoint = endpointFromOption({
option: edgeOption.other_endpoint,
fallbackClass: legacyTargetClass.name ? legacyTargetClass : undefined,
fallbackProjection: readString(edgeOption, "inverse") ?? "",
fallbackCardinality: "many",
fallbackVersion: defaults.schemaVersion,
});
if (!toEndpoint.projection) {
if (!declaredToEndpoint.projection) {
throw new Error(
`Edge field ${type.fullName}.${field.name} requires other_endpoint.projection or inverse`,
);
@@ -659,13 +693,20 @@ const classSchemaFromType = (
) {
throw new Error(`Edge field ${type.fullName}.${field.name} must use camino.Ref`);
}
const isMany = isManyCardinality(fromEndpoint.cardinality);
const isMany = isManyCardinality(declaredFromEndpoint.cardinality);
if (isMany && !field.repeated) {
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${fromEndpoint.cardinality}`);
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${declaredFromEndpoint.cardinality}`);
}
if (!isMany && field.repeated) {
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${fromEndpoint.cardinality}`);
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${declaredFromEndpoint.cardinality}`);
}
const directionality: EdgeDirectionality =
edgeOption.undirected === true ? "undirected" : "directed";
const { fromEndpoint, toEndpoint } = normalizeEdgeEndpoints({
directionality,
fromEndpoint: declaredFromEndpoint,
toEndpoint: declaredToEndpoint,
});
const edgeId = symbolRefFromOption(edgeOption.id, {
namespace: defaults.schemaNamespace,
name: `${type.name}.${field.name}`,
@@ -679,6 +720,7 @@ const classSchemaFromType = (
return [
{
id: edgeId,
directionality,
sourceField: fromEndpoint.projection,
fromClass: fromEndpoint.class ?? classId,
toClass: fallbackToClass,