trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
1,280 lines (1,275 loc) • 34.9 kB
JavaScript
import {
QueryEngine
} from "./chunk-BYTAOXGW.js";
import {
EAVStore,
init_eav_store
} from "./chunk-G3XIHPSQ.js";
import {
filterDurableAttributes,
findSchemaForType
} from "./chunk-LEGH72HW.js";
import {
OP_PREIMAGE_VERSION,
hashKernelOp,
init_canonical_op
} from "./chunk-RUMOVKR4.js";
// src/core/kernel/trellis-kernel.ts
init_eav_store();
init_canonical_op();
// src/core/ontology/core-ontology.ts
var VERSION = "1.0.0";
function f(name, valueType, opts) {
return { name, valueType, ...opts };
}
var thing = {
"@id": "core:Thing",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
label: "Thing",
icon: "lucide:box",
fields: [
f("id", "title", { required: true }),
f("createdAt", "date"),
f("updatedAt", "date"),
f("createdBy", "relation", {
relation: { targetSchema: "core:Member", cardinality: "one" }
}),
f("tags", "multi_select")
]
};
var record = {
"@id": "core:Record",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Record",
icon: "lucide:file",
fields: [
f("title", "title", { required: true }),
f("description", "rich_text"),
f("status", "select"),
f("tags", "multi_select")
]
};
var document = {
"@id": "core:Document",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Record",
label: "Document",
icon: "lucide:file-text",
fields: [
f("content", "rich_text"),
f("mimeType", "rich_text"),
f("fileUrl", "url")
]
};
var event = {
"@id": "core:Event",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Record",
label: "Event",
icon: "lucide:calendar",
fields: [
f("startDate", "date"),
f("endDate", "date"),
f("location", "rich_text"),
f("allDay", "checkbox")
]
};
var collection = {
"@id": "core:Collection",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Collection",
icon: "lucide:database",
fields: [
f("title", "title", { required: true }),
f("description", "rich_text"),
f("icon", "rich_text"),
f("schema", "rich_text"),
f("recordType", "relation", {
relation: { targetSchema: "core:Record", cardinality: "one" }
})
]
};
var tag = {
"@id": "core:Tag",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Tag",
icon: "lucide:tag",
fields: [
f("name", "title", { required: true }),
f("slug", "rich_text"),
f("color", "rich_text"),
f("icon", "rich_text"),
f("parentTag", "relation", {
relation: { targetSchema: "core:Tag", cardinality: "one" }
})
]
};
var workspace = {
"@id": "core:Workspace",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Workspace",
icon: "lucide:building-2",
fields: [
f("name", "title", { required: true }),
f("slug", "rich_text"),
f("avatar", "files"),
f("plan", "select")
]
};
var app = {
"@id": "core:App",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "App",
icon: "lucide:layout-grid",
fields: [
f("name", "title", { required: true }),
f("slug", "rich_text"),
f("icon", "rich_text"),
f("color", "rich_text"),
f("description", "rich_text"),
f("ontologies", "multi_select")
]
};
var member = {
"@id": "core:Member",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Member",
icon: "lucide:user",
fields: [
f("name", "title", { required: true }),
f("email", "email"),
f("avatar", "files"),
f("role", "select", {
required: true,
selectOptions: ["owner", "admin", "member", "guest"],
defaultValue: "member"
}),
f("status", "select", {
required: true,
selectOptions: ["pending", "active", "suspended"],
defaultValue: "pending"
}),
f("orgId", "relation", {
required: true,
relation: { targetSchema: "core:Workspace", cardinality: "one" }
}),
f("userId", "relation", {
relation: { targetSchema: "core:Person", cardinality: "one" }
}),
f("invitedAt", "date"),
f("joinedAt", "date")
]
};
var notification = {
"@id": "core:Notification",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Notification",
icon: "lucide:bell",
fields: [
f("recipientId", "relation", {
required: true,
relation: { targetSchema: "core:Person", cardinality: "one" }
}),
f("orgId", "relation", {
relation: { targetSchema: "core:Workspace", cardinality: "one" }
}),
f("orgName", "rich_text"),
f("type", "select", {
required: true,
selectOptions: [
"invite_accepted",
"invite_sent",
"member_joined",
"member_removed",
"role_changed",
"mention",
"comment",
"entity_updated",
"system"
]
}),
f("title", "title", { required: true }),
f("message", "rich_text", { required: true }),
f("actionUrl", "url"),
f("icon", "rich_text"),
f("variant", "select", {
selectOptions: ["default", "success", "warning", "destructive", "info"]
}),
f("isRead", "checkbox", { defaultValue: false }),
f("actorId", "relation", {
relation: { targetSchema: "core:Person", cardinality: "one" }
}),
f("actorName", "rich_text"),
f("metadata", "rich_text"),
f("createdAt", "date", { required: true })
]
};
var share = {
"@id": "core:Share",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Share",
icon: "lucide:share-2",
fields: [
f("entityId", "relation", { required: true }),
f("entityType", "select", { selectOptions: ["entity", "collection"] }),
f("userId", "relation", {
required: true,
relation: { targetSchema: "core:Person", cardinality: "one" }
}),
f("orgId", "relation", {
relation: { targetSchema: "core:Workspace", cardinality: "one" }
}),
f("permission", "select", {
required: true,
selectOptions: ["view", "comment", "edit"],
defaultValue: "view"
}),
f("sharedBy", "relation", {
relation: { targetSchema: "core:Person", cardinality: "one" }
}),
f("createdAt", "date", { required: true })
]
};
var person = {
"@id": "core:Person",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Person",
icon: "lucide:user",
fields: [f("name", "title", { required: true })]
};
var workflow = {
"@id": "core:Workflow",
"@type": "trellis:Schema",
version: VERSION,
tier: "core",
subClassOf: "core:Thing",
label: "Workflow",
icon: "lucide:git-branch",
fields: [
f("name", "title", { required: true }),
f("trigger", "rich_text"),
f("steps", "multi_select"),
f("active", "checkbox")
]
};
var workflowStep = {
"@id": "core:WorkflowStep",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "Workflow Step",
icon: "lucide:list-checks",
fields: [
f("name", "title", { required: true }),
f("description", "rich_text"),
f("commands", "json"),
f("turbo", "checkbox"),
f("layer", "select", {
selectOptions: ["pre_flight", "setup", "implement", "review", "closure"]
})
]
};
var workflowEdge = {
"@id": "core:WorkflowEdge",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "Workflow Edge",
icon: "lucide:arrow-right",
fields: [
f("name", "title", { required: true }),
f("condition", "rich_text"),
f("status", "select", {
selectOptions: ["HANDOFF", "CLARIFY", "REJECT", "BLOCKED", "DECISION"],
defaultValue: "HANDOFF"
}),
f("from", "relation", {
relation: { targetSchema: "core:WorkflowStep", cardinality: "one" }
}),
f("to", "relation", {
relation: { targetSchema: "core:WorkflowStep", cardinality: "one" }
})
]
};
var workflowGate = {
"@id": "core:WorkflowGate",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "Workflow Gate",
icon: "lucide:shield-check",
fields: [
f("name", "title", { required: true }),
f("type", "select", {
required: true,
selectOptions: ["test", "manual", "ac_check", "semantic_diff"]
}),
f("criteria", "rich_text"),
f("onFail", "select", {
selectOptions: ["stop", "retry", "route_to"],
defaultValue: "stop"
}),
f("step", "relation", {
relation: { targetSchema: "core:WorkflowStep", cardinality: "one" }
}),
f("retryStep", "relation", {
relation: { targetSchema: "core:WorkflowStep", cardinality: "one" }
}),
f("failRoute", "relation", {
relation: { targetSchema: "core:WorkflowEdge", cardinality: "one" }
})
]
};
var agent = {
"@id": "core:Agent",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "Agent",
icon: "lucide:bot",
fields: [
f("name", "title", { required: true }),
f("description", "rich_text"),
f("role", "select", {
required: true,
selectOptions: [
"strategist",
"designer",
"architect",
"executor",
"reviewer",
"optimizer",
"synthesist",
"writer",
"human"
]
}),
f("inbox", "rich_text"),
f("model", "rich_text"),
f("status", "select", {
defaultValue: "active",
selectOptions: ["active", "inactive", "deprecated"]
}),
f("capabilities", "multi_select"),
f("provider", "rich_text"),
f("systemPrompt", "rich_text"),
f("temperature", "number"),
f("maxTokens", "number"),
f("workflow", "relation", {
relation: { targetSchema: "core:Workflow", cardinality: "one" }
}),
f("tools", "relation", {
relation: { targetSchema: "core:Tool", cardinality: "many" }
})
]
};
var tool = {
"@id": "core:Tool",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "Tool",
icon: "lucide:wrench",
fields: [
f("name", "title", { required: true }),
f("description", "rich_text"),
f("schema", "json"),
f("endpoint", "url")
]
};
var agentRun = {
"@id": "trellis:AgentRun",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "AgentRun",
icon: "lucide:play",
fields: [
f("startedAt", "date", { required: true }),
f("completedAt", "date"),
f("status", "select", {
required: true,
selectOptions: ["running", "plan_pending", "paused", "completed", "failed", "cancelled"],
defaultValue: "running"
}),
f("input", "rich_text"),
f("output", "rich_text"),
f("totalTokens", "number"),
f("promptTokens", "number"),
f("completionTokens", "number"),
f("maxRetries", "number"),
f("timeoutMs", "number"),
f("executedBy", "relation", {
relation: { targetSchema: "core:Agent", cardinality: "one" }
}),
f("hasPlan", "relation", {
relation: { cardinality: "many" }
}),
f("usedTool", "relation", {
relation: { targetSchema: "core:Tool", cardinality: "many" }
}),
f("handoffTo", "relation", {
relation: { targetSchema: "trellis:AgentRun", cardinality: "many" }
}),
f("handoffFrom", "relation", {
relation: { targetSchema: "trellis:AgentRun", cardinality: "one" }
})
]
};
var decisionTrace = {
"@id": "trellis:DecisionTrace",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "DecisionTrace",
icon: "lucide:git-branch",
fields: [
f("toolName", "title", { required: true }),
f("timestamp", "date", { required: true }),
f("input", "json"),
f("output", "rich_text"),
f("rationale", "rich_text"),
f("alternatives", "json"),
f("belongsToRun", "relation", {
relation: { targetSchema: "trellis:AgentRun", cardinality: "one" }
}),
f("madeBy", "relation", {
relation: { targetSchema: "core:Agent", cardinality: "one" }
}),
f("relatedTo", "relation", {
relation: { cardinality: "many" }
})
]
};
var workerPoolTask = {
"@id": "trellis:WorkerPoolTask",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "WorkerPool Task",
icon: "lucide:list-queue",
fields: [
f("agentId", "title", { required: true }),
f("runId", "title", { required: true }),
f("input", "rich_text"),
f("status", "select", {
required: true,
selectOptions: ["queued", "running", "paused", "completed", "failed", "cancelled"],
defaultValue: "queued"
}),
f("queuedAt", "date", { required: true }),
f("startedAt", "date"),
f("completedAt", "date"),
f("error", "rich_text")
]
};
var dagRun = {
"@id": "trellis:DAGRun",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "DAG Run",
icon: "lucide:workflow",
fields: [
f("workflowId", "title", { required: true }),
f("workflowName", "title"),
f("status", "select", {
required: true,
selectOptions: ["running", "completed", "failed", "cancelled"],
defaultValue: "running"
}),
f("steps", "json"),
f("startedAt", "date", { required: true }),
f("completedAt", "date")
]
};
var handoff = {
"@id": "core:Handoff",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "Handoff",
icon: "lucide:arrow-left-right",
fields: [
f("name", "title", { required: true }),
f("status", "select", {
required: true,
selectOptions: ["HANDOFF", "CLARIFY", "REJECT", "BLOCKED", "DECISION"]
}),
f("body", "rich_text"),
f("refs", "multi_select"),
f("timestamp", "date"),
f("from", "relation", {
relation: { targetSchema: "core:Agent", cardinality: "one" }
}),
f("to", "relation", {
relation: { targetSchema: "core:Agent", cardinality: "one" }
}),
f("re", "relation", {
relation: { cardinality: "one" }
})
]
};
var pipeline = {
"@id": "trellis:Pipeline",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "Pipeline",
icon: "lucide:git-merge",
fields: [
f("name", "title", { required: true }),
f("description", "rich_text"),
f("trigger", "rich_text"),
f("active", "checkbox"),
f("phases", "relation", {
relation: { targetSchema: "trellis:PipelinePhase", cardinality: "many" }
}),
f("workflow", "relation", {
relation: { targetSchema: "core:Workflow", cardinality: "many" }
})
]
};
var pipelinePhase = {
"@id": "trellis:PipelinePhase",
"@type": "trellis:Schema",
version: VERSION,
tier: "system",
subClassOf: "core:Thing",
label: "Pipeline Phase",
icon: "lucide:step-forward",
fields: [
f("name", "title", { required: true }),
f("description", "rich_text"),
f("order", "number"),
f("agentRole", "select", {
required: true,
selectOptions: [
"strategist",
"designer",
"architect",
"executor",
"reviewer",
"optimizer",
"synthesist",
"writer",
"human"
]
}),
f("workflow", "relation", {
relation: { targetSchema: "core:Workflow", cardinality: "one" }
})
]
};
var CORE_ONTOLOGY = [
thing,
record,
document,
event,
collection,
tag,
workspace,
app,
member,
notification,
share,
person,
workflow,
workflowStep,
workflowEdge,
workflowGate,
agent,
tool,
agentRun,
decisionTrace,
workerPoolTask,
dagRun,
handoff,
pipeline,
pipelinePhase
];
// src/core/kernel/trellis-kernel.ts
var TrellisKernel = class {
store;
backend;
middleware;
agentId;
snapshotThreshold;
opsSinceSnapshot = 0;
_booted = false;
ontologies = /* @__PURE__ */ new Map();
workspaceConfig = null;
autoReplay = true;
defaultProvenance;
constructor(config) {
this.store = new EAVStore();
this.backend = config.backend;
this.agentId = config.agentId;
this.middleware = config.middleware ?? [];
this.snapshotThreshold = config.snapshotThreshold ?? 0;
this.autoReplay = config.autoReplay ?? true;
this.defaultProvenance = config.provenance ?? {
actorType: "machine",
origin: "sdk"
};
for (const schema of CORE_ONTOLOGY) {
this.ontologies.set(schema["@id"], schema);
}
}
// -------------------------------------------------------------------------
// Lifecycle
// -------------------------------------------------------------------------
/**
* Initialize the backend and replay persisted state.
* Loads latest snapshot if available, then replays ops after it.
*/
boot() {
this.backend.init();
if (!this.autoReplay) {
this._booted = true;
return { opsReplayed: 0, fromSnapshot: false };
}
let opsReplayed = 0;
let fromSnapshot = false;
const snapshot = this.backend.loadLatestSnapshot();
if (snapshot) {
this.store.restore(snapshot.data);
fromSnapshot = true;
const recentOps = this.backend.readAfter(snapshot.lastOpHash);
for (const op of recentOps) {
this._replayOp(op);
opsReplayed++;
}
} else {
const allOps = this.backend.readAll();
for (const op of allOps) {
this._replayOp(op);
opsReplayed++;
}
}
this._booted = true;
return { opsReplayed, fromSnapshot };
}
/**
* Close the backend connection.
*/
close() {
this.backend.close?.();
this._booted = false;
}
isBooted() {
return this._booted;
}
// -------------------------------------------------------------------------
// Mutations
// -------------------------------------------------------------------------
/**
* Apply a mutation to the graph. Creates an op, runs it through middleware,
* decomposes into EAV primitives, persists, and returns the result.
*/
async mutate(kind, payload, ctx) {
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
const lastOp = this.backend.getLastOp();
const agentId = ctx?.agentId ?? this.agentId;
const provenance = payload.provenance ?? ctx?.provenance ?? this.defaultProvenance;
const resolved = { ...payload, provenance };
const hash = await hashKernelOp(
{ kind, timestamp, agentId, previousHash: lastOp?.hash },
resolved
);
const op = {
hash,
kind,
timestamp,
agentId,
previousHash: lastOp?.hash,
v: OP_PREIMAGE_VERSION,
facts: [...payload.facts ?? []],
links: [...payload.links ?? []],
deleteFacts: payload.deleteFacts?.length ? [...payload.deleteFacts] : void 0,
deleteLinks: payload.deleteLinks?.length ? [...payload.deleteLinks] : void 0,
provenance
};
const mwCtx = { agentId, ...ctx };
await this._runMiddleware(op, mwCtx);
let factsAdded = 0;
let factsDeleted = 0;
let linksAdded = 0;
let linksDeleted = 0;
if (payload.deleteFacts && payload.deleteFacts.length > 0) {
this.store.deleteFacts(payload.deleteFacts);
factsDeleted = payload.deleteFacts.length;
}
if (payload.deleteLinks && payload.deleteLinks.length > 0) {
this.store.deleteLinks(payload.deleteLinks);
linksDeleted = payload.deleteLinks.length;
}
if (payload.facts && payload.facts.length > 0) {
this.store.addFacts(payload.facts);
factsAdded = payload.facts.length;
}
if (payload.links && payload.links.length > 0) {
this.store.addLinks(payload.links);
linksAdded = payload.links.length;
}
this.backend.append(op);
this.opsSinceSnapshot++;
if (this.snapshotThreshold > 0 && this.opsSinceSnapshot >= this.snapshotThreshold) {
this.checkpoint();
}
return {
op,
factsDelta: { added: factsAdded, deleted: factsDeleted },
linksDelta: { added: linksAdded, deleted: linksDeleted }
};
}
/**
* Create a snapshot of the current store state.
*/
checkpoint() {
const lastOp = this.backend.getLastOp();
if (!lastOp) return;
this.backend.saveSnapshot(lastOp.hash, this.store.snapshot());
this.opsSinceSnapshot = 0;
}
// -------------------------------------------------------------------------
// Queries
// -------------------------------------------------------------------------
/**
* Get the underlying EAV store for direct queries.
*/
getStore() {
return this.store;
}
/**
* Get the persistence backend.
*/
getBackend() {
return this.backend;
}
/**
* Get the agent ID.
*/
getAgentId() {
return this.agentId;
}
/**
* Read all persisted ops.
*/
readAllOps() {
return this.backend.readAll();
}
/**
* Get the last persisted op.
*/
getLastOp() {
return this.backend.getLastOp();
}
/**
* Create a QueryEngine bound to this kernel's store.
*/
createQueryEngine() {
return new QueryEngine(this.store);
}
/**
* Execute an EQL-S query, routing through middleware handleQuery hooks.
* If no middleware intercepts, the query runs directly against the store.
*/
async query(q) {
const engine = new QueryEngine(this.store);
const ctx = {
agentId: this.agentId,
store: this.store
};
const chain = this.middleware.filter((m) => m.handleQuery);
if (chain.length === 0) {
return engine.execute(q);
}
let result;
let idx = 0;
const next = (query, context) => {
if (idx < chain.length) {
const mw = chain[idx++];
return mw.handleQuery(query, context, next);
}
result = engine.execute(query);
return result;
};
result = next(q, ctx);
return result;
}
/**
* Time-travel: reconstruct the store state at a specific op hash.
* Returns a new EAVStore with state replayed up to (and including) that op.
*/
timeTravel(opHash) {
const ops = this.backend.readUntil(opHash);
const snapshot = new EAVStore();
for (const op of ops) {
if (op.deleteFacts && op.deleteFacts.length > 0) {
snapshot.deleteFacts(op.deleteFacts);
}
if (op.deleteLinks && op.deleteLinks.length > 0) {
snapshot.deleteLinks(op.deleteLinks);
}
if (op.facts && op.facts.length > 0) {
snapshot.addFacts(op.facts);
}
if (op.links && op.links.length > 0) {
snapshot.addLinks(op.links);
}
}
return snapshot;
}
// -------------------------------------------------------------------------
// Entity CRUD (high-level graph operations)
// -------------------------------------------------------------------------
/**
* Create a new entity with the given type and attributes.
* Returns the entity ID.
*/
async createEntity(entityId, type, attributes = {}, links, ctx) {
const schema = findSchemaForType(this.ontologies.values(), type);
const filtered = filterDurableAttributes(
attributes,
schema
);
const facts = [{ e: entityId, a: "type", v: type }];
if (filtered.createdAt === void 0) {
facts.push({ e: entityId, a: "createdAt", v: (/* @__PURE__ */ new Date()).toISOString() });
}
for (const [attr, value] of Object.entries(filtered)) {
facts.push({ e: entityId, a: attr, v: value });
}
const linkRecords = (links ?? []).map((l) => ({
e1: entityId,
a: l.attribute,
e2: l.targetEntityId
}));
return this.mutate(
"addFacts",
{
facts,
links: linkRecords.length > 0 ? linkRecords : void 0
},
ctx
);
}
/**
* Get an entity by ID, returning all its facts and links.
*/
getEntity(entityId) {
const facts = this.store.getFactsByEntity(entityId);
if (facts.length === 0) return null;
const typeFact = facts.find((f2) => f2.a === "type");
return {
id: entityId,
type: typeFact?.v ?? "unknown",
facts,
links: this.store.getLinksByEntity(entityId)
};
}
/**
* Update an entity's attributes. Deletes old values and adds new ones.
*/
async updateEntity(entityId, updates, ctx) {
const existing = this.getEntity(entityId);
const schema = existing ? findSchemaForType(this.ontologies.values(), existing.type) : void 0;
const filtered = filterDurableAttributes(
updates,
schema
);
const existingFacts = this.store.getFactsByEntity(entityId);
const deleteFacts = [];
const addFacts = [];
for (const [attr, newValue] of Object.entries(filtered)) {
const existingAttr = existingFacts.filter((f2) => f2.a === attr);
deleteFacts.push(...existingAttr);
addFacts.push({ e: entityId, a: attr, v: newValue });
}
const updatedAtFacts = existingFacts.filter((f2) => f2.a === "updatedAt");
deleteFacts.push(...updatedAtFacts);
addFacts.push({ e: entityId, a: "updatedAt", v: (/* @__PURE__ */ new Date()).toISOString() });
return this.mutate(
"addFacts",
{
facts: addFacts,
deleteFacts
},
ctx
);
}
/**
* Delete an entity and all its facts and links.
*/
async deleteEntity(entityId, ctx) {
const facts = this.store.getFactsByEntity(entityId);
const links = this.store.getLinksByEntity(entityId);
return this.mutate(
"deleteFacts",
{
deleteFacts: facts,
deleteLinks: links
},
ctx
);
}
/**
* List entities by type, with optional attribute filters.
*/
listEntities(type, filters) {
let entityIds;
if (type) {
const typeFacts = this.store.getFactsByValue("type", type);
entityIds = new Set(typeFacts.map((f2) => f2.e));
} else {
const allTypeFacts = this.store.getFactsByAttribute("type");
entityIds = new Set(allTypeFacts.map((f2) => f2.e));
}
if (filters) {
for (const [attr, value] of Object.entries(filters)) {
const matchingFacts = this.store.getFactsByValue(attr, value);
const matchingEntities = new Set(matchingFacts.map((f2) => f2.e));
for (const id of entityIds) {
if (!matchingEntities.has(id)) {
entityIds.delete(id);
}
}
}
}
return Array.from(entityIds).map((id) => this.getEntity(id)).filter(Boolean);
}
/**
* Add a link between two entities.
*/
async addLink(sourceId, attribute, targetId, ctx) {
return this.mutate(
"addLinks",
{
links: [{ e1: sourceId, a: attribute, e2: targetId }]
},
ctx
);
}
/**
* Remove a link between two entities.
*/
async removeLink(sourceId, attribute, targetId, ctx) {
return this.mutate(
"deleteLinks",
{
deleteLinks: [{ e1: sourceId, a: attribute, e2: targetId }]
},
ctx
);
}
/**
* Add a fact to an entity.
*/
async addFact(entityId, attribute, value, ctx, meta) {
return this.mutate(
"addFacts",
{ facts: [{ e: entityId, a: attribute, v: value, ...meta ? { meta } : {} }] },
ctx
);
}
/**
* Remove a fact from an entity.
*/
async removeFact(entityId, attribute, value, ctx) {
return this.mutate(
"deleteFacts",
{ deleteFacts: [{ e: entityId, a: attribute, v: value }] },
ctx
);
}
// -------------------------------------------------------------------------
// Ontology CRUD
// -------------------------------------------------------------------------
/**
* Get an ontology schema by ID.
*/
getOntology(id) {
return this.ontologies.get(id);
}
/**
* List all ontologies.
*/
listOntologies() {
return Array.from(this.ontologies.values());
}
/**
* Create a new ontology schema.
*/
createOntology(schema) {
const tier = schema.tier ?? "user";
if (tier === "core") {
throw new Error("Cannot modify core ontologies");
}
if (this.ontologies.has(schema["@id"])) {
throw new Error(`Ontology ${schema["@id"]} already exists`);
}
this.ontologies.set(schema["@id"], schema);
const fact = {
e: schema["@id"],
a: "schema",
v: JSON.stringify(schema)
};
this.store.addFacts([fact]);
}
/**
* Update an existing ontology schema.
*/
updateOntology(id, updates) {
const existing = this.ontologies.get(id);
if (!existing) {
throw new Error(`Ontology ${id} not found`);
}
const tier = existing.tier ?? "user";
if (tier === "core") {
throw new Error("Cannot modify core ontologies");
}
const updated = { ...existing, ...updates };
this.ontologies.set(id, updated);
const existingFacts = this.store.getFactsByEntity(id);
const schemaFacts = existingFacts.filter((f2) => f2.a === "schema");
const deleteFacts = [...schemaFacts];
const addFacts = [
{
e: id,
a: "schema",
v: JSON.stringify(updated)
}
];
this.store.deleteFacts(deleteFacts);
this.store.addFacts(addFacts);
}
/**
* Delete an ontology schema.
*/
deleteOntology(id) {
const existing = this.ontologies.get(id);
if (!existing) {
throw new Error(`Ontology ${id} not found`);
}
const tier = existing.tier ?? "user";
if (tier === "core") {
throw new Error("Cannot delete core ontologies");
}
this.ontologies.delete(id);
const facts = this.store.getFactsByEntity(id);
this.store.deleteFacts(facts);
}
// -------------------------------------------------------------------------
// Workspace
// -------------------------------------------------------------------------
/**
* Boot the kernel with a workspace configuration.
* Loads ontologies, projections, and seed data.
*/
bootWorkspace(config) {
this.workspaceConfig = config;
if (config.workspace.ontologies) {
for (const [id, schema] of Object.entries(config.workspace.ontologies)) {
if (!this.ontologies.has(id)) {
this.ontologies.set(id, schema);
}
}
}
if (config.workspace.graph?.nodes) {
for (const node of config.workspace.graph.nodes) {
const n = node;
if (n.id && n.type) {
const facts = [
{ e: String(n.id), a: "type", v: String(n.type) }
];
for (const [key, value] of Object.entries(n)) {
if (key !== "id" && key !== "type") {
facts.push({ e: String(n.id), a: key, v: value });
}
}
this.store.addFacts(facts);
}
}
}
if (config.workspace.graph?.edges) {
for (const edge of config.workspace.graph.edges) {
const e = edge;
if (e.source && e.target && e.relation) {
this.store.addLinks([
{
e1: String(e.source),
a: String(e.relation),
e2: String(e.target)
}
]);
}
}
}
this.boot();
}
/**
* Export the current workspace configuration.
*/
exportWorkspace() {
const ontologies = {};
for (const [id, schema] of this.ontologies) {
if (schema.tier !== "core") {
ontologies[id] = schema;
}
}
const allFacts = this.store.getAllFacts();
const nodes = [];
const entityIds = new Set(allFacts.map((f2) => f2.e));
for (const id of entityIds) {
const facts = this.store.getFactsByEntity(id);
const typeFact = facts.find((f2) => f2.a === "type");
const node = { id };
if (typeFact) {
node.type = typeFact.v;
}
for (const fact of facts) {
if (fact.a !== "type") {
node[fact.a] = fact.v;
}
}
nodes.push(node);
}
const allLinks = this.store.getAllLinks();
const edges = allLinks.map((l) => ({
source: l.e1,
relation: l.a,
target: l.e2
}));
return {
workspace: {
name: this.workspaceConfig?.workspace.name,
description: this.workspaceConfig?.workspace.description,
ontologies: Object.keys(ontologies).length > 0 ? ontologies : void 0,
graph: { nodes, edges },
projections: this.workspaceConfig?.workspace.projections,
routes: this.workspaceConfig?.workspace.routes,
app: this.workspaceConfig?.workspace.app
}
};
}
// -------------------------------------------------------------------------
// TQL Compatibility Aliases
// -------------------------------------------------------------------------
/**
* Create a node (alias for createEntity with schema validation).
*/
async createNode(id, data, type, ctx) {
return this.createEntity(id, type, data, void 0, ctx);
}
/**
* Update a node (alias for updateEntity with schema validation).
*/
async updateNode(id, data, type, ctx) {
return this.updateEntity(id, data, ctx);
}
/**
* Delete a node (alias for deleteEntity).
*/
async deleteNode(id, ctx) {
return this.deleteEntity(id, ctx);
}
/**
* Link two nodes (alias for addLink).
*/
async link(e1, a, e2, ctx) {
return this.addLink(e1, a, e2, ctx);
}
/**
* Unlink two nodes (alias for removeLink).
*/
async unlink(e1, a, e2, ctx) {
return this.removeLink(e1, a, e2, ctx);
}
// -------------------------------------------------------------------------
// Middleware
// -------------------------------------------------------------------------
addMiddleware(mw) {
this.middleware.push(mw);
}
removeMiddleware(name) {
this.middleware = this.middleware.filter((m) => m.name !== name);
}
// -------------------------------------------------------------------------
// Internal
// -------------------------------------------------------------------------
async _runMiddleware(op, ctx) {
const chain = [...this.middleware];
let idx = 0;
const next = async (op2, ctx2) => {
const mw = chain[idx++];
if (mw?.handleOp) {
await mw.handleOp(op2, ctx2, next);
}
};
if (chain.length > 0) {
await next(op, ctx);
}
}
_replayOp(op) {
if (op.deleteFacts && op.deleteFacts.length > 0) {
this.store.deleteFacts(op.deleteFacts);
}
if (op.deleteLinks && op.deleteLinks.length > 0) {
this.store.deleteLinks(op.deleteLinks);
}
if (op.facts && op.facts.length > 0) {
this.store.addFacts(op.facts);
}
if (op.links && op.links.length > 0) {
this.store.addLinks(op.links);
}
}
};
export {
TrellisKernel
};