trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
362 lines (359 loc) • 11.9 kB
JavaScript
import {
__esm
} from "./chunk-2ESYSVXG.js";
// src/core/store/eav-store.ts
function* flatten(obj, base = "") {
if (Array.isArray(obj)) {
for (const v of obj) {
yield* flatten(v, base);
}
} else if (obj && typeof obj === "object") {
for (const [k, v] of Object.entries(obj)) {
yield* flatten(v, base ? `${base}.${k}` : k);
}
} else {
yield [base, obj];
}
}
function jsonEntityFacts(entityId, root, type) {
const facts = [{ e: entityId, a: "type", v: type }];
for (const [a, v] of flatten(root)) {
if (v === void 0 || v === null) continue;
if (Array.isArray(v)) {
for (const el of v) {
facts.push({ e: entityId, a, v: el });
}
} else if (typeof v === "object") {
} else {
facts.push({ e: entityId, a, v });
}
}
return facts;
}
var EAVStore;
var init_eav_store = __esm({
"src/core/store/eav-store.ts"() {
"use strict";
EAVStore = class {
facts = [];
links = [];
catalog = /* @__PURE__ */ new Map();
// Indexes for fast lookups
eavIndex = /* @__PURE__ */ new Map();
aevIndex = /* @__PURE__ */ new Map();
aveIndex = /* @__PURE__ */ new Map();
// Link indexes for graph queries
linkIndex = /* @__PURE__ */ new Map();
// e1 -> a -> e2s
linkReverseIndex = /* @__PURE__ */ new Map();
// e2 -> a -> e1s
linkAttrIndex = /* @__PURE__ */ new Map();
// a -> [(e1, e2)]
// Distinct value tracking
distinct = /* @__PURE__ */ new Map();
// attr -> set of valueKey
addFacts(facts) {
for (let i = 0; i < facts.length; i++) {
const fact = facts[i];
if (fact) {
if (this.hasFact(fact.e, fact.a, fact.v)) {
continue;
}
this.facts.push(fact);
this.updateIndexes(fact, this.facts.length - 1);
this.updateCatalog(fact);
}
}
}
/**
* Check if a fact already exists in the store.
*/
hasFact(entity, attribute, value) {
const valueKey = this.valueKey(value);
const indices = this.aveIndex.get(attribute)?.get(valueKey);
if (!indices) return false;
for (const idx of indices) {
const fact = this.facts[idx];
if (fact && fact.e === entity && fact.a === attribute) {
return true;
}
}
return false;
}
addLinks(links) {
for (const link of links) {
this.links.push(link);
this.updateLinkIndexes(link);
}
}
deleteFacts(factsToDelete) {
for (const fact of factsToDelete) {
const valueKey = this.valueKey(fact.v);
const indices = this.aveIndex.get(fact.a)?.get(valueKey);
if (!indices) continue;
let foundIdx = -1;
for (const idx of indices) {
const storedFact = this.facts[idx];
if (storedFact && storedFact.e === fact.e && storedFact.a === fact.a) {
foundIdx = idx;
break;
}
}
if (foundIdx !== -1) {
this.facts[foundIdx] = void 0;
this.eavIndex.get(fact.e)?.get(fact.a)?.delete(foundIdx);
this.aevIndex.get(fact.a)?.get(fact.e)?.delete(foundIdx);
this.aveIndex.get(fact.a)?.get(valueKey)?.delete(foundIdx);
const entry = this.catalog.get(fact.a);
if (entry) {
}
}
}
}
deleteLinks(linksToDelete) {
for (const link of linksToDelete) {
const initialLen = this.links.length;
this.links = this.links.filter(
(l) => !(l.e1 === link.e1 && l.a === link.a && l.e2 === link.e2)
);
if (this.links.length < initialLen) {
this.linkIndex.get(link.e1)?.get(link.a)?.delete(link.e2);
this.linkReverseIndex.get(link.e2)?.get(link.a)?.delete(link.e1);
const attrPairs = this.linkAttrIndex.get(link.a);
if (attrPairs) {
for (const pair of attrPairs) {
if (pair[0] === link.e1 && pair[1] === link.e2) {
attrPairs.delete(pair);
break;
}
}
}
}
}
}
updateIndexes(fact, index) {
if (!this.eavIndex.has(fact.e)) {
this.eavIndex.set(fact.e, /* @__PURE__ */ new Map());
}
if (!this.eavIndex.get(fact.e).has(fact.a)) {
this.eavIndex.get(fact.e).set(fact.a, /* @__PURE__ */ new Set());
}
this.eavIndex.get(fact.e).get(fact.a).add(index);
if (!this.aevIndex.has(fact.a)) {
this.aevIndex.set(fact.a, /* @__PURE__ */ new Map());
}
if (!this.aevIndex.get(fact.a).has(fact.e)) {
this.aevIndex.get(fact.a).set(fact.e, /* @__PURE__ */ new Set());
}
this.aevIndex.get(fact.a).get(fact.e).add(index);
if (!this.aveIndex.has(fact.a)) {
this.aveIndex.set(fact.a, /* @__PURE__ */ new Map());
}
const valueKey = this.valueKey(fact.v);
if (!this.aveIndex.get(fact.a).has(valueKey)) {
this.aveIndex.get(fact.a).set(valueKey, /* @__PURE__ */ new Set());
}
this.aveIndex.get(fact.a).get(valueKey).add(index);
}
updateLinkIndexes(link) {
if (!this.linkIndex.has(link.e1)) {
this.linkIndex.set(link.e1, /* @__PURE__ */ new Map());
}
const e1Attrs = this.linkIndex.get(link.e1);
if (!e1Attrs.has(link.a)) {
e1Attrs.set(link.a, /* @__PURE__ */ new Set());
}
e1Attrs.get(link.a).add(link.e2);
if (!this.linkReverseIndex.has(link.e2)) {
this.linkReverseIndex.set(link.e2, /* @__PURE__ */ new Map());
}
const e2Attrs = this.linkReverseIndex.get(link.e2);
if (!e2Attrs.has(link.a)) {
e2Attrs.set(link.a, /* @__PURE__ */ new Set());
}
e2Attrs.get(link.a).add(link.e1);
if (!this.linkAttrIndex.has(link.a)) {
this.linkAttrIndex.set(link.a, /* @__PURE__ */ new Set());
}
this.linkAttrIndex.get(link.a).add([link.e1, link.e2]);
}
valueKey(v) {
if (v instanceof Date) return `date:${v.toISOString()}`;
return `${typeof v}:${v}`;
}
updateCatalog(fact) {
const entry = this.catalog.get(fact.a) || {
attribute: fact.a,
type: this.inferType(fact.v),
cardinality: "one",
distinctCount: 0,
examples: []
};
const factType = this.inferType(fact.v);
if (entry.type !== factType && entry.type !== "mixed") {
entry.type = "mixed";
}
const entityAttrs = this.eavIndex.get(fact.e)?.get(fact.a);
if (entityAttrs && entityAttrs.size > 1) {
entry.cardinality = "many";
}
const k = this.valueKey(fact.v);
const s = this.distinct.get(fact.a) || (this.distinct.set(fact.a, /* @__PURE__ */ new Set()), this.distinct.get(fact.a));
s.add(k);
entry.distinctCount = s.size;
if (entry.examples.length < 5 && !entry.examples.includes(fact.v)) {
entry.examples.push(fact.v);
}
if (typeof fact.v === "number") {
entry.min = Math.min(entry.min ?? fact.v, fact.v);
entry.max = Math.max(entry.max ?? fact.v, fact.v);
}
this.catalog.set(fact.a, entry);
}
inferType(v) {
if (typeof v === "string") return "string";
if (typeof v === "number") return "number";
if (typeof v === "boolean") return "boolean";
if (v instanceof Date) return "date";
return "mixed";
}
// Query methods
getFactsByEntity(entity) {
const indices = this.eavIndex.get(entity);
if (!indices) return [];
const result = [];
for (const attrIndices of indices.values()) {
for (const idx of attrIndices) {
const fact = this.facts[idx];
if (fact) {
result.push(fact);
}
}
}
return result;
}
getFactsByAttribute(attribute) {
const indices = this.aevIndex.get(attribute);
if (!indices) return [];
const result = [];
for (const entityIndices of indices.values()) {
for (const idx of entityIndices) {
const fact = this.facts[idx];
if (fact) {
result.push(fact);
}
}
}
return result;
}
getFactsByValue(attribute, value) {
const indices = this.aveIndex.get(attribute)?.get(this.valueKey(value));
if (!indices) return [];
return Array.from(indices).map((idx) => this.facts[idx]).filter((fact) => fact !== void 0);
}
getCatalog() {
return Array.from(this.catalog.values());
}
getCatalogEntry(attribute) {
return this.catalog.get(attribute);
}
// Statistics
getAllFacts() {
return this.facts.filter((f) => f !== void 0);
}
getAllLinks() {
return [...this.links];
}
getLinksByEntity(entity) {
const results = [];
const forwardLinks = this.linkIndex.get(entity);
if (forwardLinks) {
for (const [attr, targets] of forwardLinks) {
for (const target of targets) {
results.push({ e1: entity, a: attr, e2: target });
}
}
}
const reverseLinks = this.linkReverseIndex.get(entity);
if (reverseLinks) {
for (const [attr, sources] of reverseLinks) {
for (const source of sources) {
results.push({ e1: source, a: attr, e2: entity });
}
}
}
return results;
}
getLinksByAttribute(attribute) {
const results = [];
const links = this.linkAttrIndex.get(attribute);
if (links) {
for (const [e1, e2] of links) {
results.push({ e1, a: attribute, e2 });
}
}
return results;
}
getLinksByEntityAndAttribute(entity, attribute) {
const results = [];
const attrs = this.linkIndex.get(entity);
if (attrs) {
const targets = attrs.get(attribute);
if (targets) {
for (const target of targets) {
results.push({ e1: entity, a: attribute, e2: target });
}
}
}
return results;
}
getStats() {
return {
totalFacts: this.facts.length,
totalLinks: this.links.length,
uniqueEntities: this.eavIndex.size,
uniqueAttributes: this.aevIndex.size,
catalogEntries: this.catalog.size
};
}
/**
* Creates a serializable snapshot of the current store state.
*/
snapshot() {
return {
facts: this.facts.filter((f) => f !== void 0),
links: [...this.links],
catalog: this.getCatalog()
};
}
/**
* Restores the store state from a snapshot and rebuilds all indexes.
*/
restore(snapshot) {
this.facts = [];
this.links = [];
this.catalog.clear();
this.eavIndex.clear();
this.aevIndex.clear();
this.aveIndex.clear();
this.linkIndex.clear();
this.linkReverseIndex.clear();
this.linkAttrIndex.clear();
this.distinct.clear();
this.addFacts(snapshot.facts);
this.addLinks(snapshot.links);
if (snapshot.catalog) {
for (const entry of snapshot.catalog) {
this.catalog.set(entry.attribute, entry);
}
}
}
};
}
});
export {
flatten,
jsonEntityFacts,
EAVStore,
init_eav_store
};