Teach package runtime edge endpoints

This commit is contained in:
Timothy J. Aveni
2026-07-14 07:57:20 -07:00
parent b4ab4c49c9
commit 85e0dafbeb
16 changed files with 425 additions and 62 deletions
+110 -17
View File
@@ -249,7 +249,8 @@ const cardinalityFromOption = (value: unknown): Cardinality => {
normalized === "exactly_one" ||
normalized === "many" ||
normalized === "many_unique" ||
normalized === "many_ordered"
normalized === "many_ordered" ||
normalized === "many_unique_ordered"
) {
return normalized;
}
@@ -419,6 +420,67 @@ const fieldSchemaFromField = (
};
};
const isManyCardinality = (cardinality: Cardinality) =>
cardinality === "many" ||
cardinality === "many_unique" ||
cardinality === "many_ordered" ||
cardinality === "many_unique_ordered";
const isOrderedCardinality = (cardinality: Cardinality) =>
cardinality === "many_ordered" || cardinality === "many_unique_ordered";
const endpointFromOption = (params: {
option: unknown;
fallbackClass?: SymbolRef;
fallbackProjection: string;
fallbackCardinality: Cardinality;
fallbackVersion: string;
}) => {
const object = asObject(params.option);
const classRef = asObject(object?.class)
? symbolRefFromOption(object?.class, {
namespace: "",
name: "",
version: params.fallbackVersion,
})
: params.fallbackClass;
const interfaceRef = asObject(object?.interface)
? symbolRefFromOption(object?.interface, {
namespace: "",
name: "",
version: params.fallbackVersion,
})
: undefined;
const projection = readString(object, "projection") ?? params.fallbackProjection;
const cardinality = object?.cardinality === undefined
? params.fallbackCardinality
: cardinalityFromOption(object.cardinality);
if (!classRef?.name && !interfaceRef?.name) {
throw new Error(`Edge endpoint ${projection} requires class or interface`);
}
return {
...(classRef?.name ? { class: classRef } : {}),
...(interfaceRef?.name ? { interface: interfaceRef } : {}),
projection,
cardinality,
indexed: readBoolean(object, "indexed") ?? false,
};
};
const symbolArrayFromOption = (
value: unknown,
defaults: ReturnType<typeof extractFileDefaults>,
) =>
asArray(value)
.map((item) =>
symbolRefFromOption(item, {
namespace: defaults.schemaNamespace,
name: "",
version: defaults.schemaVersion,
}),
)
.filter((symbol) => Boolean(symbol.name));
const isStringType = (type: TypeRef) =>
type.protoType === "string" || type.protoType === "google.protobuf.StringValue";
@@ -568,15 +630,31 @@ const classSchemaFromType = (
if (!edgeOption) {
return [];
}
const targetClass = symbolRefFromOption(edgeOption.target, {
const legacyTargetClass = 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 fromEndpoint = endpointFromOption({
option: edgeOption.this_endpoint,
fallbackClass: classId,
fallbackProjection: field.name,
fallbackCardinality: cardinality,
fallbackVersion: defaults.schemaVersion,
});
const toEndpoint = endpointFromOption({
option: edgeOption.other_endpoint,
fallbackClass: legacyTargetClass.name ? legacyTargetClass : undefined,
fallbackProjection: readString(edgeOption, "inverse") ?? "",
fallbackCardinality: "many",
fallbackVersion: defaults.schemaVersion,
});
if (!toEndpoint.projection) {
throw new Error(
`Edge field ${type.fullName}.${field.name} requires other_endpoint.projection or inverse`,
);
}
const sourceType = typeRefForField(field, defaults.schemaVersion);
if (
sourceType.symbol?.namespace !== "camino" ||
@@ -584,32 +662,47 @@ const classSchemaFromType = (
) {
throw new Error(`Edge field ${type.fullName}.${field.name} must use camino.Ref`);
}
const isMany =
cardinality === "many" ||
cardinality === "many_unique" ||
cardinality === "many_ordered";
const isMany = isManyCardinality(fromEndpoint.cardinality);
if (isMany && !field.repeated) {
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${cardinality}`);
throw new Error(`Edge field ${type.fullName}.${field.name} must be repeated for ${fromEndpoint.cardinality}`);
}
if (!isMany && field.repeated) {
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${cardinality}`);
throw new Error(`Edge field ${type.fullName}.${field.name} must not be repeated for ${fromEndpoint.cardinality}`);
}
if (
isOrderedCardinality(fromEndpoint.cardinality) &&
isOrderedCardinality(toEndpoint.cardinality)
) {
throw new Error(
`Edge field ${type.fullName}.${field.name} cannot be ordered on both endpoints yet`,
);
}
const edgeId = symbolRefFromOption(edgeOption.id, {
namespace: defaults.schemaNamespace,
name: `${type.name}.${field.name}`,
version: defaults.schemaVersion,
});
const fallbackToClass = toEndpoint.class ?? {
namespace: "",
name: "",
version: "",
};
return [
{
id: edgeId,
sourceField: field.name,
fromClass: classId,
toClass: targetClass,
cardinality,
...(readString(edgeOption, "inverse")
? { inverse: readString(edgeOption, "inverse") }
sourceField: fromEndpoint.projection,
fromClass: fromEndpoint.class ?? classId,
toClass: fallbackToClass,
cardinality: fromEndpoint.cardinality,
...(toEndpoint.projection
? { inverse: toEndpoint.projection }
: {}),
fields: [],
fromEndpoint,
toEndpoint,
declaringClass: classId,
tags: symbolArrayFromOption(edgeOption.tag, defaults),
implements: symbolArrayFromOption(edgeOption.implements, defaults),
},
];
});