trellis 2.0.8 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -116
- package/dist/cli/index.js +655 -4
- package/dist/core/index.js +471 -2
- package/dist/embeddings/index.js +5 -1
- package/dist/{index-s603ev6w.js → index-5b01h414.js} +1 -1
- package/dist/index-5m0g9r0y.js +1100 -0
- package/dist/{index-zf6htvnm.js → index-7gvjxt27.js} +166 -2
- package/dist/index-hybgxe40.js +1174 -0
- package/dist/index.js +7 -2
- package/dist/transformers.node-bx3q9d7k.js +33130 -0
- package/package.json +9 -4
- package/src/cli/index.ts +939 -0
- package/src/core/agents/harness.ts +380 -0
- package/src/core/agents/index.ts +18 -0
- package/src/core/agents/types.ts +90 -0
- package/src/core/index.ts +85 -2
- package/src/core/kernel/trellis-kernel.ts +593 -0
- package/src/core/ontology/builtins.ts +248 -0
- package/src/core/ontology/index.ts +34 -0
- package/src/core/ontology/registry.ts +209 -0
- package/src/core/ontology/types.ts +124 -0
- package/src/core/ontology/validator.ts +382 -0
- package/src/core/persist/backend.ts +10 -0
- package/src/core/persist/sqlite-backend.ts +298 -0
- package/src/core/plugins/index.ts +17 -0
- package/src/core/plugins/registry.ts +322 -0
- package/src/core/plugins/types.ts +126 -0
- package/src/core/query/datalog.ts +188 -0
- package/src/core/query/engine.ts +370 -0
- package/src/core/query/index.ts +34 -0
- package/src/core/query/parser.ts +481 -0
- package/src/core/query/types.ts +200 -0
- package/src/embeddings/auto-embed.ts +248 -0
- package/src/embeddings/index.ts +7 -0
- package/src/embeddings/model.ts +21 -4
- package/src/embeddings/types.ts +8 -1
- package/src/index.ts +9 -0
- package/src/sync/http-transport.ts +144 -0
- package/src/sync/index.ts +11 -0
- package/src/sync/multi-repo.ts +200 -0
- package/src/sync/ws-transport.ts +145 -0
- package/dist/index-5bhe57y9.js +0 -326
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EQL-S Query Engine — Pattern-matching evaluator over the EAV store.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates queries by matching fact/link patterns against the store,
|
|
5
|
+
* binding variables, applying filters, and computing aggregates.
|
|
6
|
+
*
|
|
7
|
+
* @module trellis/core/query
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { EAVStore, Fact, Link, Atom } from '../store/eav-store.js';
|
|
11
|
+
import type {
|
|
12
|
+
Query, Pattern, FactPattern, LinkPattern, NotPattern, OrPattern,
|
|
13
|
+
RuleApplication, Filter, FilterOp, Aggregate, OrderBy,
|
|
14
|
+
Term, Bindings, DatalogRule,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
import { isVariable, isLiteral } from './types.js';
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Result type
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export interface QueryResult {
|
|
23
|
+
bindings: Record<string, Atom>[];
|
|
24
|
+
executionTime: number;
|
|
25
|
+
count: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Engine
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export class QueryEngine {
|
|
33
|
+
private rules: Map<string, DatalogRule[]> = new Map();
|
|
34
|
+
private maxRuleDepth = 32;
|
|
35
|
+
|
|
36
|
+
constructor(private store: EAVStore) {}
|
|
37
|
+
|
|
38
|
+
/** Register a Datalog rule. Multiple rules with the same name = union. */
|
|
39
|
+
addRule(rule: DatalogRule): void {
|
|
40
|
+
const existing = this.rules.get(rule.name) ?? [];
|
|
41
|
+
existing.push(rule);
|
|
42
|
+
this.rules.set(rule.name, existing);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
removeRule(name: string): void {
|
|
46
|
+
this.rules.delete(name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Execute a query against the store. */
|
|
50
|
+
execute(query: Query): QueryResult {
|
|
51
|
+
const start = performance.now();
|
|
52
|
+
|
|
53
|
+
// Evaluate patterns
|
|
54
|
+
let results = this._evaluatePatterns(query.where, [new Map()]);
|
|
55
|
+
|
|
56
|
+
// Apply filters
|
|
57
|
+
for (const filter of query.filters) {
|
|
58
|
+
results = results.filter((b) => this._evalFilter(filter, b));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Apply aggregates
|
|
62
|
+
if (query.aggregates.length > 0) {
|
|
63
|
+
results = this._aggregate(results, query.aggregates, query.select);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Apply ordering
|
|
67
|
+
if (query.orderBy.length > 0) {
|
|
68
|
+
results = this._order(results, query.orderBy);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Apply offset/limit
|
|
72
|
+
if (query.offset > 0) results = results.slice(query.offset);
|
|
73
|
+
if (query.limit > 0) results = results.slice(0, query.limit);
|
|
74
|
+
|
|
75
|
+
// Project selected variables
|
|
76
|
+
const projected = this._project(results, query.select);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
bindings: projected,
|
|
80
|
+
executionTime: performance.now() - start,
|
|
81
|
+
count: projected.length,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// -------------------------------------------------------------------------
|
|
86
|
+
// Pattern evaluation
|
|
87
|
+
// -------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
private _evaluatePatterns(patterns: Pattern[], bindings: Bindings[]): Bindings[] {
|
|
90
|
+
let current = bindings;
|
|
91
|
+
for (const pattern of patterns) {
|
|
92
|
+
if (current.length === 0) break;
|
|
93
|
+
current = this._evaluatePattern(pattern, current);
|
|
94
|
+
}
|
|
95
|
+
return current;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private _evaluatePattern(pattern: Pattern, bindings: Bindings[]): Bindings[] {
|
|
99
|
+
switch (pattern.kind) {
|
|
100
|
+
case 'fact': return this._evalFactPattern(pattern, bindings);
|
|
101
|
+
case 'link': return this._evalLinkPattern(pattern, bindings);
|
|
102
|
+
case 'not': return this._evalNotPattern(pattern, bindings);
|
|
103
|
+
case 'or': return this._evalOrPattern(pattern, bindings);
|
|
104
|
+
case 'rule': return this._evalRuleApplication(pattern, bindings);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _evalFactPattern(p: FactPattern, bindings: Bindings[]): Bindings[] {
|
|
109
|
+
const results: Bindings[] = [];
|
|
110
|
+
for (const b of bindings) {
|
|
111
|
+
const eResolved = this._resolve(p.entity, b);
|
|
112
|
+
const aResolved = this._resolve(p.attribute, b);
|
|
113
|
+
const vResolved = this._resolve(p.value, b);
|
|
114
|
+
|
|
115
|
+
let facts: Fact[];
|
|
116
|
+
if (eResolved !== undefined && aResolved !== undefined) {
|
|
117
|
+
facts = this.store.getFactsByEntity(String(eResolved))
|
|
118
|
+
.filter((f) => f.a === aResolved);
|
|
119
|
+
} else if (eResolved !== undefined) {
|
|
120
|
+
facts = this.store.getFactsByEntity(String(eResolved));
|
|
121
|
+
} else if (aResolved !== undefined && vResolved !== undefined) {
|
|
122
|
+
facts = this.store.getFactsByValue(String(aResolved), vResolved);
|
|
123
|
+
} else if (aResolved !== undefined) {
|
|
124
|
+
facts = this.store.getFactsByAttribute(String(aResolved));
|
|
125
|
+
} else {
|
|
126
|
+
facts = this.store.getAllFacts();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (vResolved !== undefined) {
|
|
130
|
+
facts = facts.filter((f) => f.v === vResolved);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const fact of facts) {
|
|
134
|
+
const nb = new Map(b);
|
|
135
|
+
if (this._bind(p.entity, fact.e, nb) &&
|
|
136
|
+
this._bind(p.attribute, fact.a, nb) &&
|
|
137
|
+
this._bind(p.value, fact.v, nb)) {
|
|
138
|
+
results.push(nb);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return results;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private _evalLinkPattern(p: LinkPattern, bindings: Bindings[]): Bindings[] {
|
|
146
|
+
const results: Bindings[] = [];
|
|
147
|
+
for (const b of bindings) {
|
|
148
|
+
const srcResolved = this._resolve(p.source, b);
|
|
149
|
+
const attrResolved = this._resolve(p.attribute, b);
|
|
150
|
+
const tgtResolved = this._resolve(p.target, b);
|
|
151
|
+
|
|
152
|
+
let links: Link[];
|
|
153
|
+
if (srcResolved !== undefined && attrResolved !== undefined) {
|
|
154
|
+
links = this.store.getLinksByEntityAndAttribute(String(srcResolved), String(attrResolved));
|
|
155
|
+
} else if (srcResolved !== undefined) {
|
|
156
|
+
links = this.store.getLinksByEntity(String(srcResolved));
|
|
157
|
+
} else if (attrResolved !== undefined) {
|
|
158
|
+
links = this.store.getLinksByAttribute(String(attrResolved));
|
|
159
|
+
} else {
|
|
160
|
+
links = this.store.getAllLinks();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (tgtResolved !== undefined) {
|
|
164
|
+
links = links.filter((l) => l.e2 === tgtResolved);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const link of links) {
|
|
168
|
+
const nb = new Map(b);
|
|
169
|
+
if (this._bind(p.source, link.e1, nb) &&
|
|
170
|
+
this._bind(p.attribute, link.a, nb) &&
|
|
171
|
+
this._bind(p.target, link.e2, nb)) {
|
|
172
|
+
results.push(nb);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private _evalNotPattern(p: NotPattern, bindings: Bindings[]): Bindings[] {
|
|
180
|
+
return bindings.filter((b) => {
|
|
181
|
+
const matches = this._evaluatePattern(p.pattern, [b]);
|
|
182
|
+
return matches.length === 0;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private _evalOrPattern(p: OrPattern, bindings: Bindings[]): Bindings[] {
|
|
187
|
+
const results: Bindings[] = [];
|
|
188
|
+
for (const branch of p.branches) {
|
|
189
|
+
const branchResults = this._evaluatePatterns(branch, bindings);
|
|
190
|
+
results.push(...branchResults);
|
|
191
|
+
}
|
|
192
|
+
return this._dedup(results);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private _evalRuleApplication(p: RuleApplication, bindings: Bindings[], depth = 0): Bindings[] {
|
|
196
|
+
if (depth > this.maxRuleDepth) return [];
|
|
197
|
+
const ruleDefs = this.rules.get(p.name);
|
|
198
|
+
if (!ruleDefs) return [];
|
|
199
|
+
|
|
200
|
+
const results: Bindings[] = [];
|
|
201
|
+
for (const b of bindings) {
|
|
202
|
+
for (const rule of ruleDefs) {
|
|
203
|
+
// Bind rule params from application args
|
|
204
|
+
const ruleBindings = new Map(b);
|
|
205
|
+
let ok = true;
|
|
206
|
+
for (let i = 0; i < rule.params.length && i < p.args.length; i++) {
|
|
207
|
+
const resolved = this._resolve(p.args[i], b);
|
|
208
|
+
if (resolved !== undefined) {
|
|
209
|
+
ruleBindings.set(rule.params[i], resolved);
|
|
210
|
+
} else if (isVariable(p.args[i])) {
|
|
211
|
+
// Leave unbound — will be bound by body
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!ok) continue;
|
|
215
|
+
|
|
216
|
+
// Evaluate body
|
|
217
|
+
let bodyResults = this._evaluatePatterns(rule.body, [ruleBindings]);
|
|
218
|
+
|
|
219
|
+
// Apply rule filters
|
|
220
|
+
for (const f of rule.filters) {
|
|
221
|
+
bodyResults = bodyResults.filter((rb) => this._evalFilter(f, rb));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Map back rule param bindings to the application arg variables
|
|
225
|
+
for (const rb of bodyResults) {
|
|
226
|
+
const nb = new Map(b);
|
|
227
|
+
for (let i = 0; i < rule.params.length && i < p.args.length; i++) {
|
|
228
|
+
if (isVariable(p.args[i])) {
|
|
229
|
+
const val = rb.get(rule.params[i]);
|
|
230
|
+
if (val !== undefined) nb.set(p.args[i].name, val);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
results.push(nb);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return this._dedup(results);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// -------------------------------------------------------------------------
|
|
241
|
+
// Filtering
|
|
242
|
+
// -------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
private _evalFilter(filter: Filter, b: Bindings): boolean {
|
|
245
|
+
const left = this._resolve(filter.left, b);
|
|
246
|
+
const right = this._resolve(filter.right, b);
|
|
247
|
+
if (left === undefined || right === undefined) return false;
|
|
248
|
+
|
|
249
|
+
switch (filter.op) {
|
|
250
|
+
case '=': return left === right;
|
|
251
|
+
case '!=': return left !== right;
|
|
252
|
+
case '<': return (left as any) < (right as any);
|
|
253
|
+
case '<=': return (left as any) <= (right as any);
|
|
254
|
+
case '>': return (left as any) > (right as any);
|
|
255
|
+
case '>=': return (left as any) >= (right as any);
|
|
256
|
+
case 'contains': return String(left).includes(String(right));
|
|
257
|
+
case 'startsWith': return String(left).startsWith(String(right));
|
|
258
|
+
case 'endsWith': return String(left).endsWith(String(right));
|
|
259
|
+
case 'matches': return new RegExp(String(right)).test(String(left));
|
|
260
|
+
default: return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// -------------------------------------------------------------------------
|
|
265
|
+
// Aggregation
|
|
266
|
+
// -------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
private _aggregate(bindings: Bindings[], aggregates: Aggregate[], groupBy: string[]): Bindings[] {
|
|
269
|
+
// Group by non-aggregated select variables
|
|
270
|
+
const aggVarNames = new Set(aggregates.map((a) => a.as));
|
|
271
|
+
const groupVars = groupBy.filter((v) => !aggVarNames.has(v));
|
|
272
|
+
|
|
273
|
+
const groups = new Map<string, Bindings[]>();
|
|
274
|
+
for (const b of bindings) {
|
|
275
|
+
const key = groupVars.map((v) => String(b.get(v) ?? '')).join('\0');
|
|
276
|
+
const group = groups.get(key) ?? [];
|
|
277
|
+
group.push(b);
|
|
278
|
+
groups.set(key, group);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const results: Bindings[] = [];
|
|
282
|
+
for (const [, group] of groups) {
|
|
283
|
+
const nb = new Map(group[0]);
|
|
284
|
+
for (const agg of aggregates) {
|
|
285
|
+
const vals = group.map((b) => b.get(agg.variable)).filter((v) => v !== undefined);
|
|
286
|
+
nb.set(agg.as, this._computeAggregate(agg.op, vals as Atom[]));
|
|
287
|
+
}
|
|
288
|
+
results.push(nb);
|
|
289
|
+
}
|
|
290
|
+
return results;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private _computeAggregate(op: string, vals: Atom[]): Atom {
|
|
294
|
+
switch (op) {
|
|
295
|
+
case 'count': return vals.length;
|
|
296
|
+
case 'sum': return vals.reduce((s, v) => (s as number) + (Number(v) || 0), 0 as any) as number;
|
|
297
|
+
case 'avg': return vals.length ? (vals.reduce((s, v) => (s as number) + (Number(v) || 0), 0 as any) as number) / vals.length : 0;
|
|
298
|
+
case 'min': return vals.reduce((m, v) => (v as any) < (m as any) ? v : m, vals[0]);
|
|
299
|
+
case 'max': return vals.reduce((m, v) => (v as any) > (m as any) ? v : m, vals[0]);
|
|
300
|
+
case 'collect': return JSON.stringify(vals);
|
|
301
|
+
default: return vals.length;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// -------------------------------------------------------------------------
|
|
306
|
+
// Ordering
|
|
307
|
+
// -------------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
private _order(bindings: Bindings[], orderBy: OrderBy[]): Bindings[] {
|
|
310
|
+
return [...bindings].sort((a, b) => {
|
|
311
|
+
for (const o of orderBy) {
|
|
312
|
+
const va = a.get(o.variable);
|
|
313
|
+
const vb = b.get(o.variable);
|
|
314
|
+
if (va === vb) continue;
|
|
315
|
+
if (va === undefined) return 1;
|
|
316
|
+
if (vb === undefined) return -1;
|
|
317
|
+
const cmp = (va as any) < (vb as any) ? -1 : 1;
|
|
318
|
+
return o.direction === 'asc' ? cmp : -cmp;
|
|
319
|
+
}
|
|
320
|
+
return 0;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// -------------------------------------------------------------------------
|
|
325
|
+
// Projection
|
|
326
|
+
// -------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
private _project(bindings: Bindings[], select: string[]): Record<string, Atom>[] {
|
|
329
|
+
return bindings.map((b) => {
|
|
330
|
+
const row: Record<string, Atom> = {};
|
|
331
|
+
if (select.length === 0) {
|
|
332
|
+
for (const [k, v] of b) row[k] = v;
|
|
333
|
+
} else {
|
|
334
|
+
for (const s of select) {
|
|
335
|
+
const v = b.get(s);
|
|
336
|
+
if (v !== undefined) row[s] = v;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return row;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// -------------------------------------------------------------------------
|
|
344
|
+
// Helpers
|
|
345
|
+
// -------------------------------------------------------------------------
|
|
346
|
+
|
|
347
|
+
private _resolve(term: Term, bindings: Bindings): Atom | undefined {
|
|
348
|
+
if (isLiteral(term)) return term.value;
|
|
349
|
+
return bindings.get(term.name);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private _bind(term: Term, value: Atom, bindings: Bindings): boolean {
|
|
353
|
+
if (isLiteral(term)) return term.value === value;
|
|
354
|
+
const existing = bindings.get(term.name);
|
|
355
|
+
if (existing !== undefined) return existing === value;
|
|
356
|
+
bindings.set(term.name, value);
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private _dedup(bindings: Bindings[]): Bindings[] {
|
|
361
|
+
const seen = new Set<string>();
|
|
362
|
+
return bindings.filter((b) => {
|
|
363
|
+
const key = [...b.entries()].sort(([a], [b]) => a.localeCompare(b))
|
|
364
|
+
.map(([k, v]) => `${k}=${v}`).join('\0');
|
|
365
|
+
if (seen.has(key)) return false;
|
|
366
|
+
seen.add(key);
|
|
367
|
+
return true;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EQL-S Query Module — Public API Surface
|
|
3
|
+
*
|
|
4
|
+
* @module trellis/core/query
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
Variable, Literal, Term,
|
|
10
|
+
FactPattern, LinkPattern, NotPattern, OrPattern, RuleApplication, Pattern,
|
|
11
|
+
FilterOp, Filter,
|
|
12
|
+
AggregateOp, Aggregate,
|
|
13
|
+
OrderBy,
|
|
14
|
+
Query,
|
|
15
|
+
Bindings,
|
|
16
|
+
DatalogRule,
|
|
17
|
+
} from './types.js';
|
|
18
|
+
|
|
19
|
+
export { isVariable, isLiteral, variable, literal } from './types.js';
|
|
20
|
+
|
|
21
|
+
// Engine
|
|
22
|
+
export { QueryEngine } from './engine.js';
|
|
23
|
+
export type { QueryResult } from './engine.js';
|
|
24
|
+
|
|
25
|
+
// Parser
|
|
26
|
+
export { parseQuery, parseRule, parseSimple } from './parser.js';
|
|
27
|
+
|
|
28
|
+
// Datalog
|
|
29
|
+
export {
|
|
30
|
+
DatalogRuntime,
|
|
31
|
+
transitiveClosureRules,
|
|
32
|
+
reverseReachabilityRules,
|
|
33
|
+
siblingRules,
|
|
34
|
+
} from './datalog.js';
|