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
+37 -6
View File
@@ -339,6 +339,30 @@ const endpointFromOption = (params) => {
indexed: readBoolean(object, "indexed") ?? false,
};
};
const symbolKey = (ref) => ref ? [ref.namespace, ref.name, ref.version, ref.hash ?? ""].join(":") : "";
const endpointRoleKey = (endpoint) => [
symbolKey(endpoint.class),
symbolKey(endpoint.interface),
endpoint.projection,
endpoint.cardinality,
].join("|");
const normalizeEdgeEndpoints = (params) => {
if (params.directionality === "directed") {
return {
fromEndpoint: params.fromEndpoint,
toEndpoint: params.toEndpoint,
};
}
return endpointRoleKey(params.fromEndpoint) <= endpointRoleKey(params.toEndpoint)
? {
fromEndpoint: params.fromEndpoint,
toEndpoint: params.toEndpoint,
}
: {
fromEndpoint: params.toEndpoint,
toEndpoint: params.fromEndpoint,
};
};
const symbolArrayFromOption = (value, defaults) => asArray(value)
.map((item) => symbolRefFromOption(item, {
namespace: defaults.schemaNamespace,
@@ -446,21 +470,21 @@ const classSchemaFromType = (type, sourceFile, defaults, operationServices) => {
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`);
}
const sourceType = typeRefForField(field, defaults.schemaVersion);
@@ -468,13 +492,19 @@ const classSchemaFromType = (type, sourceFile, defaults, operationServices) => {
sourceType.symbol.name !== "Ref") {
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 = 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}`,
@@ -488,6 +518,7 @@ const classSchemaFromType = (type, sourceFile, defaults, operationServices) => {
return [
{
id: edgeId,
directionality,
sourceField: fromEndpoint.projection,
fromClass: fromEndpoint.class ?? classId,
toClass: fallbackToClass,