trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
234 lines (230 loc) • 7.29 kB
JavaScript
// src/schema/entity-projection.ts
function entityRecordToPlain(entity) {
const obj = { id: entity.id, type: entity.type };
for (const f of entity.facts) {
if (f.a !== "type") obj[f.a] = f.v;
}
return obj;
}
function bindingEntityId(row) {
const id = row.id ?? row.e ?? row["?e"];
return typeof id === "string" && id.length > 0 ? id : null;
}
function isSparseBinding(row) {
const id = bindingEntityId(row);
if (!id) return false;
return typeof row.type !== "string";
}
function bindingToEntity(row) {
const id = bindingEntityId(row);
if (id && typeof row.type === "string") {
return { ...row, id, type: row.type };
}
if (id) return { id, type: String(row.type ?? ""), ...row };
return row;
}
function hydrateBindings(kernel, bindings) {
return bindings.map((row) => {
if (!isSparseBinding(row)) return bindingToEntity(row);
const id = bindingEntityId(row);
const entity = kernel.getEntity(id);
return entity ? entityRecordToPlain(entity) : bindingToEntity(row);
});
}
// src/schema/eql.ts
function escapeValue(v) {
return v.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
var WHERE_OP_TO_EQL = {
eq: "=",
ne: "!=",
lt: "<",
lte: "<=",
gt: ">",
gte: ">=",
contains: "contains",
startsWith: "startsWith",
endsWith: "endsWith"
};
function isWhereFilter(value) {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return false;
}
return Object.keys(WHERE_OP_TO_EQL).some(
(op) => value[op] !== void 0
);
}
function formatEqlLiteral(value) {
if (typeof value === "string") return `"${escapeValue(value)}"`;
if (typeof value === "boolean") return value ? "true" : "false";
if (typeof value === "number" && Number.isFinite(value)) return String(value);
if (value === null || value === void 0) {
throw new Error("Cannot format null/undefined as an EQL literal");
}
return `"${escapeValue(String(value))}"`;
}
function whereCondition(attr, value) {
if (isWhereFilter(value)) {
const ops = Object.keys(WHERE_OP_TO_EQL).filter(
(op2) => value[op2] !== void 0
);
if (ops.length === 0) {
throw new Error(`Empty where filter for attribute "${attr}"`);
}
if (ops.length > 1) {
throw new Error(
`Where filter for "${attr}" must specify one operator, got: ${ops.join(", ")}`
);
}
const op = ops[0];
return `${attr} ${WHERE_OP_TO_EQL[op]} ${formatEqlLiteral(value[op])}`;
}
return `${attr} = ${formatEqlLiteral(value)}`;
}
function entitiesQuery(type, where) {
const conds = [`type = "${escapeValue(type)}"`];
for (const [k, v] of Object.entries(where ?? {})) {
if (v === void 0 || v === null) continue;
conds.push(whereCondition(k, v));
}
return `find ?e where ${conds.join(" and ")}`;
}
function entityQuery(type, entityId) {
const t = formatEqlLiteral(type);
const id = formatEqlLiteral(entityId);
return `SELECT ?e
WHERE {
[?e "type" ${t}]
}
FILTER ?e = ${id}`;
}
// src/schema/resolve.ts
function isNestedResolveSpec(v) {
return typeof v === "object" && v !== null;
}
function relationTargetName(r) {
return typeof r.target === "string" ? r.target : r.target().type;
}
function relationTargetSchema(r, schemaLookup) {
if (typeof r.target !== "string") return r.target();
return schemaLookup?.(r.target) ?? null;
}
function inverseForeignKey(parent, relationName, child) {
for (const [key, r] of Object.entries(child.relations)) {
if (relationTargetName(r) === parent.type) return key;
}
return null;
}
async function loadByIds(client, ids, cache) {
const missing = ids.filter((id) => !cache.has(id));
if (missing.length === 0) return;
await Promise.all(
missing.map(async (id) => {
cache.set(id, await client.read(id));
})
);
}
async function resolveReverseMany(client, parent, relationName, parents, cache, schemaLookup) {
const rel = parent.relations[relationName];
if (!rel || rel.cardinality !== "many") return;
const childSchema = relationTargetSchema(rel, schemaLookup);
if (!childSchema) return;
const foreignKey = inverseForeignKey(parent, relationName, childSchema);
if (!foreignKey) return;
const parentIds = new Set(parents.map((p) => p.id));
const qr = await client.query(entitiesQuery(childSchema.type));
const childIds = qr.bindings.map((b) => bindingEntityId(b)).filter((id) => Boolean(id));
await loadByIds(client, childIds, cache);
const grouped = /* @__PURE__ */ new Map();
for (const id of childIds) {
const child = cache.get(id);
if (!child) continue;
const fk = child[foreignKey];
if (typeof fk !== "string" || !parentIds.has(fk)) continue;
if (!grouped.has(fk)) grouped.set(fk, []);
grouped.get(fk).push(child);
}
for (const row of parents) {
row[relationName] = grouped.get(row.id) ?? [];
}
}
async function resolveForwardOne(client, parents, relationName, cache) {
const ids = parents.map((p) => p[relationName]).filter((v) => typeof v === "string" && v.length > 0);
await loadByIds(client, ids, cache);
for (const row of parents) {
const ref = row[relationName];
if (typeof ref !== "string") continue;
const loaded = cache.get(ref);
if (loaded) row[relationName] = loaded;
}
}
async function resolveRelations(client, schema, entities, spec, opts) {
if (entities.length === 0 || Object.keys(spec).length === 0) return entities;
const cache = /* @__PURE__ */ new Map();
const rows = opts?.copy === false ? entities : entities.map((e) => ({ ...e }));
for (const [name, enabled] of Object.entries(spec)) {
if (!enabled) continue;
const rel = schema.relations[name];
if (!rel) continue;
if (rel.cardinality === "many") {
await resolveReverseMany(
client,
schema,
name,
rows,
cache,
opts?.schemaLookup
);
if (isNestedResolveSpec(enabled)) {
const childSchema = relationTargetSchema(rel, opts?.schemaLookup);
if (childSchema) {
for (const row of rows) {
const kids = row[name];
if (kids?.length) {
await resolveRelations(client, childSchema, kids, enabled, {
copy: false,
schemaLookup: opts?.schemaLookup
});
}
}
}
}
} else if (isNestedResolveSpec(enabled)) {
await resolveForwardOne(client, rows, name, cache);
const childSchema = relationTargetSchema(rel, opts?.schemaLookup);
if (childSchema) {
const nested = [];
for (const row of rows) {
const loaded = row[name];
if (loaded && typeof loaded === "object" && "id" in loaded) {
nested.push(loaded);
}
}
if (nested.length) {
await resolveRelations(client, childSchema, nested, enabled, {
copy: false,
schemaLookup: opts?.schemaLookup
});
}
}
} else {
await resolveForwardOne(client, rows, name, cache);
}
}
return rows;
}
export {
entityRecordToPlain,
bindingEntityId,
isSparseBinding,
bindingToEntity,
hydrateBindings,
escapeValue,
isWhereFilter,
formatEqlLiteral,
whereCondition,
entitiesQuery,
entityQuery,
inverseForeignKey,
resolveRelations
};