trellis 2.0.13 → 2.1.2
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/dist/cli/index.js +1 -1
- package/dist/embeddings/index.js +1 -1
- package/dist/{index-7gvjxt27.js → index-2917tjd8.js} +1 -1
- package/package.json +2 -10
- package/dist/transformers.node-bx3q9d7k.js +0 -33130
- package/src/cli/index.ts +0 -3356
- package/src/core/agents/harness.ts +0 -380
- package/src/core/agents/index.ts +0 -18
- package/src/core/agents/types.ts +0 -90
- package/src/core/index.ts +0 -118
- package/src/core/kernel/middleware.ts +0 -44
- package/src/core/kernel/trellis-kernel.ts +0 -593
- package/src/core/ontology/builtins.ts +0 -248
- package/src/core/ontology/index.ts +0 -34
- package/src/core/ontology/registry.ts +0 -209
- package/src/core/ontology/types.ts +0 -124
- package/src/core/ontology/validator.ts +0 -382
- package/src/core/persist/backend.ts +0 -74
- package/src/core/persist/sqlite-backend.ts +0 -298
- package/src/core/plugins/index.ts +0 -17
- package/src/core/plugins/registry.ts +0 -322
- package/src/core/plugins/types.ts +0 -126
- package/src/core/query/datalog.ts +0 -188
- package/src/core/query/engine.ts +0 -370
- package/src/core/query/index.ts +0 -34
- package/src/core/query/parser.ts +0 -481
- package/src/core/query/types.ts +0 -200
- package/src/core/store/eav-store.ts +0 -467
- package/src/decisions/auto-capture.ts +0 -136
- package/src/decisions/hooks.ts +0 -163
- package/src/decisions/index.ts +0 -261
- package/src/decisions/types.ts +0 -103
- package/src/embeddings/auto-embed.ts +0 -248
- package/src/embeddings/chunker.ts +0 -327
- package/src/embeddings/index.ts +0 -48
- package/src/embeddings/model.ts +0 -112
- package/src/embeddings/search.ts +0 -305
- package/src/embeddings/store.ts +0 -313
- package/src/embeddings/types.ts +0 -92
- package/src/engine.ts +0 -1125
- package/src/garden/cluster.ts +0 -330
- package/src/garden/garden.ts +0 -306
- package/src/garden/index.ts +0 -29
- package/src/git/git-exporter.ts +0 -286
- package/src/git/git-importer.ts +0 -329
- package/src/git/git-reader.ts +0 -189
- package/src/git/index.ts +0 -22
- package/src/identity/governance.ts +0 -211
- package/src/identity/identity.ts +0 -224
- package/src/identity/index.ts +0 -30
- package/src/identity/signing-middleware.ts +0 -97
- package/src/index.ts +0 -29
- package/src/links/index.ts +0 -49
- package/src/links/lifecycle.ts +0 -400
- package/src/links/parser.ts +0 -484
- package/src/links/ref-index.ts +0 -186
- package/src/links/resolver.ts +0 -314
- package/src/links/types.ts +0 -108
- package/src/mcp/index.ts +0 -22
- package/src/mcp/server.ts +0 -1278
- package/src/semantic/csharp-parser.ts +0 -493
- package/src/semantic/go-parser.ts +0 -585
- package/src/semantic/index.ts +0 -34
- package/src/semantic/java-parser.ts +0 -456
- package/src/semantic/python-parser.ts +0 -659
- package/src/semantic/ruby-parser.ts +0 -446
- package/src/semantic/rust-parser.ts +0 -784
- package/src/semantic/semantic-merge.ts +0 -210
- package/src/semantic/ts-parser.ts +0 -681
- package/src/semantic/types.ts +0 -175
- package/src/sync/http-transport.ts +0 -144
- package/src/sync/index.ts +0 -43
- package/src/sync/memory-transport.ts +0 -66
- package/src/sync/multi-repo.ts +0 -200
- package/src/sync/reconciler.ts +0 -237
- package/src/sync/sync-engine.ts +0 -258
- package/src/sync/types.ts +0 -104
- package/src/sync/ws-transport.ts +0 -145
- package/src/ui/client.html +0 -695
- package/src/ui/server.ts +0 -419
- package/src/vcs/blob-store.ts +0 -124
- package/src/vcs/branch.ts +0 -150
- package/src/vcs/checkpoint.ts +0 -64
- package/src/vcs/decompose.ts +0 -469
- package/src/vcs/diff.ts +0 -409
- package/src/vcs/engine-context.ts +0 -26
- package/src/vcs/index.ts +0 -23
- package/src/vcs/issue.ts +0 -800
- package/src/vcs/merge.ts +0 -425
- package/src/vcs/milestone.ts +0 -124
- package/src/vcs/ops.ts +0 -59
- package/src/vcs/types.ts +0 -213
- package/src/vcs/vcs-middleware.ts +0 -81
- package/src/watcher/fs-watcher.ts +0 -255
- package/src/watcher/index.ts +0 -9
- package/src/watcher/ingestion.ts +0 -116
package/src/core/query/index.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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';
|
package/src/core/query/parser.ts
DELETED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EQL-S Query Parser — Parses a simple DSL into Query AST.
|
|
3
|
-
*
|
|
4
|
-
* Syntax overview:
|
|
5
|
-
* SELECT ?e ?name
|
|
6
|
-
* WHERE {
|
|
7
|
-
* [?e "type" "Project"]
|
|
8
|
-
* [?e "name" ?name]
|
|
9
|
-
* (?e "memberOf" ?org)
|
|
10
|
-
* }
|
|
11
|
-
* FILTER ?name != "archived"
|
|
12
|
-
* ORDER BY ?name ASC
|
|
13
|
-
* LIMIT 10
|
|
14
|
-
* OFFSET 5
|
|
15
|
-
*
|
|
16
|
-
* Fact patterns: [entity attr value]
|
|
17
|
-
* Link patterns: (source attr target)
|
|
18
|
-
* Not patterns: NOT [entity attr value]
|
|
19
|
-
* Or patterns: OR { branch1 } { branch2 }
|
|
20
|
-
* Rule calls: ruleName(?x, ?y)
|
|
21
|
-
*
|
|
22
|
-
* Variables start with `?`. Strings are double-quoted.
|
|
23
|
-
* Numbers are bare. Booleans: true/false.
|
|
24
|
-
*
|
|
25
|
-
* @module trellis/core/query
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import type {
|
|
29
|
-
Query, Pattern, FactPattern, LinkPattern, NotPattern, OrPattern,
|
|
30
|
-
RuleApplication, Filter, FilterOp, Aggregate, AggregateOp, OrderBy,
|
|
31
|
-
Term, DatalogRule,
|
|
32
|
-
} from './types.js';
|
|
33
|
-
import { variable, literal } from './types.js';
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Tokenizer
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
type TokenKind = 'word' | 'string' | 'number' | 'symbol' | 'eof';
|
|
40
|
-
|
|
41
|
-
interface Token {
|
|
42
|
-
kind: TokenKind;
|
|
43
|
-
value: string;
|
|
44
|
-
pos: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function tokenize(input: string): Token[] {
|
|
48
|
-
const tokens: Token[] = [];
|
|
49
|
-
let i = 0;
|
|
50
|
-
while (i < input.length) {
|
|
51
|
-
// Skip whitespace
|
|
52
|
-
if (/\s/.test(input[i])) { i++; continue; }
|
|
53
|
-
|
|
54
|
-
// Skip comments (// to end of line)
|
|
55
|
-
if (input[i] === '/' && input[i + 1] === '/') {
|
|
56
|
-
while (i < input.length && input[i] !== '\n') i++;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const pos = i;
|
|
61
|
-
|
|
62
|
-
// Symbols
|
|
63
|
-
if ('[](){},:'.includes(input[i])) {
|
|
64
|
-
tokens.push({ kind: 'symbol', value: input[i], pos });
|
|
65
|
-
i++;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Multi-char operators
|
|
70
|
-
if (input[i] === '!' && input[i + 1] === '=') {
|
|
71
|
-
tokens.push({ kind: 'symbol', value: '!=', pos });
|
|
72
|
-
i += 2;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (input[i] === '<' && input[i + 1] === '=') {
|
|
76
|
-
tokens.push({ kind: 'symbol', value: '<=', pos });
|
|
77
|
-
i += 2;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (input[i] === '>' && input[i + 1] === '=') {
|
|
81
|
-
tokens.push({ kind: 'symbol', value: '>=', pos });
|
|
82
|
-
i += 2;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if ('<>='.includes(input[i])) {
|
|
86
|
-
tokens.push({ kind: 'symbol', value: input[i], pos });
|
|
87
|
-
i++;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Strings
|
|
92
|
-
if (input[i] === '"') {
|
|
93
|
-
i++;
|
|
94
|
-
let s = '';
|
|
95
|
-
while (i < input.length && input[i] !== '"') {
|
|
96
|
-
if (input[i] === '\\' && i + 1 < input.length) {
|
|
97
|
-
s += input[i + 1]; i += 2;
|
|
98
|
-
} else {
|
|
99
|
-
s += input[i]; i++;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (i < input.length) i++; // skip closing "
|
|
103
|
-
tokens.push({ kind: 'string', value: s, pos });
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Numbers (including negative)
|
|
108
|
-
if (/[0-9]/.test(input[i]) || (input[i] === '-' && i + 1 < input.length && /[0-9]/.test(input[i + 1]))) {
|
|
109
|
-
let n = input[i]; i++;
|
|
110
|
-
while (i < input.length && /[0-9.]/.test(input[i])) { n += input[i]; i++; }
|
|
111
|
-
tokens.push({ kind: 'number', value: n, pos });
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Words (identifiers, variables, keywords)
|
|
116
|
-
if (/[?a-zA-Z_]/.test(input[i])) {
|
|
117
|
-
let w = '';
|
|
118
|
-
while (i < input.length && /[?a-zA-Z0-9_.:/-]/.test(input[i])) { w += input[i]; i++; }
|
|
119
|
-
tokens.push({ kind: 'word', value: w, pos });
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Unknown char — skip
|
|
124
|
-
i++;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
tokens.push({ kind: 'eof', value: '', pos: input.length });
|
|
128
|
-
return tokens;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ---------------------------------------------------------------------------
|
|
132
|
-
// Parser
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
|
|
135
|
-
class Parser {
|
|
136
|
-
private tokens: Token[];
|
|
137
|
-
private pos = 0;
|
|
138
|
-
|
|
139
|
-
constructor(tokens: Token[]) {
|
|
140
|
-
this.tokens = tokens;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private peek(): Token { return this.tokens[this.pos]; }
|
|
144
|
-
private advance(): Token { return this.tokens[this.pos++]; }
|
|
145
|
-
|
|
146
|
-
private expect(kind: TokenKind, value?: string): Token {
|
|
147
|
-
const t = this.advance();
|
|
148
|
-
if (t.kind !== kind || (value !== undefined && t.value !== value)) {
|
|
149
|
-
throw new Error(`Expected ${kind}${value ? ` "${value}"` : ''} at pos ${t.pos}, got ${t.kind} "${t.value}"`);
|
|
150
|
-
}
|
|
151
|
-
return t;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private match(kind: TokenKind, value?: string): boolean {
|
|
155
|
-
const t = this.peek();
|
|
156
|
-
if (t.kind === kind && (value === undefined || t.value === value)) {
|
|
157
|
-
this.pos++;
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private isAt(kind: TokenKind, value?: string): boolean {
|
|
164
|
-
const t = this.peek();
|
|
165
|
-
return t.kind === kind && (value === undefined || t.value === value);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// -----------------------------------------------------------------------
|
|
169
|
-
// Terms
|
|
170
|
-
// -----------------------------------------------------------------------
|
|
171
|
-
|
|
172
|
-
parseTerm(): Term {
|
|
173
|
-
const t = this.peek();
|
|
174
|
-
if (t.kind === 'word' && t.value.startsWith('?')) {
|
|
175
|
-
this.advance();
|
|
176
|
-
return variable(t.value.slice(1));
|
|
177
|
-
}
|
|
178
|
-
if (t.kind === 'string') {
|
|
179
|
-
this.advance();
|
|
180
|
-
return literal(t.value);
|
|
181
|
-
}
|
|
182
|
-
if (t.kind === 'number') {
|
|
183
|
-
this.advance();
|
|
184
|
-
const n = Number(t.value);
|
|
185
|
-
return literal(n);
|
|
186
|
-
}
|
|
187
|
-
if (t.kind === 'word') {
|
|
188
|
-
const v = t.value;
|
|
189
|
-
this.advance();
|
|
190
|
-
if (v === 'true') return literal(true);
|
|
191
|
-
if (v === 'false') return literal(false);
|
|
192
|
-
return literal(v);
|
|
193
|
-
}
|
|
194
|
-
throw new Error(`Unexpected token at pos ${t.pos}: ${t.kind} "${t.value}"`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// -----------------------------------------------------------------------
|
|
198
|
-
// Patterns
|
|
199
|
-
// -----------------------------------------------------------------------
|
|
200
|
-
|
|
201
|
-
parsePattern(): Pattern {
|
|
202
|
-
const t = this.peek();
|
|
203
|
-
|
|
204
|
-
// NOT pattern
|
|
205
|
-
if (t.kind === 'word' && t.value.toUpperCase() === 'NOT') {
|
|
206
|
-
this.advance();
|
|
207
|
-
const inner = this.parsePattern();
|
|
208
|
-
return { kind: 'not', pattern: inner } as NotPattern;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// OR pattern
|
|
212
|
-
if (t.kind === 'word' && t.value.toUpperCase() === 'OR') {
|
|
213
|
-
this.advance();
|
|
214
|
-
const branches: Pattern[][] = [];
|
|
215
|
-
while (this.isAt('symbol', '{')) {
|
|
216
|
-
this.advance();
|
|
217
|
-
const branch: Pattern[] = [];
|
|
218
|
-
while (!this.isAt('symbol', '}') && !this.isAt('eof', undefined)) {
|
|
219
|
-
branch.push(this.parsePattern());
|
|
220
|
-
}
|
|
221
|
-
this.expect('symbol', '}');
|
|
222
|
-
branches.push(branch);
|
|
223
|
-
}
|
|
224
|
-
return { kind: 'or', branches } as OrPattern;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Fact pattern: [e a v]
|
|
228
|
-
if (t.kind === 'symbol' && t.value === '[') {
|
|
229
|
-
this.advance();
|
|
230
|
-
const entity = this.parseTerm();
|
|
231
|
-
const attribute = this.parseTerm();
|
|
232
|
-
const value = this.parseTerm();
|
|
233
|
-
this.expect('symbol', ']');
|
|
234
|
-
return { kind: 'fact', entity, attribute, value } as FactPattern;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Link pattern: (src a tgt)
|
|
238
|
-
if (t.kind === 'symbol' && t.value === '(') {
|
|
239
|
-
this.advance();
|
|
240
|
-
const source = this.parseTerm();
|
|
241
|
-
const attribute = this.parseTerm();
|
|
242
|
-
const target = this.parseTerm();
|
|
243
|
-
this.expect('symbol', ')');
|
|
244
|
-
return { kind: 'link', source, attribute, target } as LinkPattern;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Rule application: ruleName(?x, ?y)
|
|
248
|
-
if (t.kind === 'word' && !t.value.startsWith('?')) {
|
|
249
|
-
const name = this.advance().value;
|
|
250
|
-
if (this.isAt('symbol', '(')) {
|
|
251
|
-
this.advance();
|
|
252
|
-
const args: Term[] = [];
|
|
253
|
-
while (!this.isAt('symbol', ')') && !this.isAt('eof', undefined)) {
|
|
254
|
-
args.push(this.parseTerm());
|
|
255
|
-
this.match('symbol', ',');
|
|
256
|
-
}
|
|
257
|
-
this.expect('symbol', ')');
|
|
258
|
-
return { kind: 'rule', name, args } as RuleApplication;
|
|
259
|
-
}
|
|
260
|
-
throw new Error(`Expected '(' after rule name "${name}" at pos ${t.pos}`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
throw new Error(`Cannot parse pattern at pos ${t.pos}: ${t.kind} "${t.value}"`);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// -----------------------------------------------------------------------
|
|
267
|
-
// Filter
|
|
268
|
-
// -----------------------------------------------------------------------
|
|
269
|
-
|
|
270
|
-
parseFilter(): Filter {
|
|
271
|
-
const left = this.parseTerm();
|
|
272
|
-
const op = this.advance().value as FilterOp;
|
|
273
|
-
const right = this.parseTerm();
|
|
274
|
-
return { kind: 'filter', left, op, right };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// -----------------------------------------------------------------------
|
|
278
|
-
// Full Query
|
|
279
|
-
// -----------------------------------------------------------------------
|
|
280
|
-
|
|
281
|
-
parseQuery(): Query {
|
|
282
|
-
const query: Query = {
|
|
283
|
-
select: [],
|
|
284
|
-
where: [],
|
|
285
|
-
filters: [],
|
|
286
|
-
aggregates: [],
|
|
287
|
-
orderBy: [],
|
|
288
|
-
limit: 0,
|
|
289
|
-
offset: 0,
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
// Parse clauses in any order
|
|
293
|
-
while (!this.isAt('eof', undefined)) {
|
|
294
|
-
const kw = this.peek();
|
|
295
|
-
if (kw.kind !== 'word') {
|
|
296
|
-
throw new Error(`Expected keyword at pos ${kw.pos}, got ${kw.kind} "${kw.value}"`);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
switch (kw.value.toUpperCase()) {
|
|
300
|
-
case 'SELECT': {
|
|
301
|
-
this.advance();
|
|
302
|
-
while (this.peek().kind === 'word' && this.peek().value.startsWith('?')) {
|
|
303
|
-
query.select.push(this.advance().value.slice(1));
|
|
304
|
-
}
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
case 'WHERE': {
|
|
309
|
-
this.advance();
|
|
310
|
-
this.expect('symbol', '{');
|
|
311
|
-
while (!this.isAt('symbol', '}') && !this.isAt('eof', undefined)) {
|
|
312
|
-
query.where.push(this.parsePattern());
|
|
313
|
-
}
|
|
314
|
-
this.expect('symbol', '}');
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
case 'FILTER': {
|
|
319
|
-
this.advance();
|
|
320
|
-
query.filters.push(this.parseFilter());
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
case 'AGGREGATE': {
|
|
325
|
-
this.advance();
|
|
326
|
-
const op = this.advance().value as AggregateOp;
|
|
327
|
-
this.expect('symbol', '(');
|
|
328
|
-
const varName = this.advance().value;
|
|
329
|
-
const varClean = varName.startsWith('?') ? varName.slice(1) : varName;
|
|
330
|
-
this.expect('symbol', ')');
|
|
331
|
-
this.expect('word', 'AS');
|
|
332
|
-
const asName = this.advance().value;
|
|
333
|
-
const asClean = asName.startsWith('?') ? asName.slice(1) : asName;
|
|
334
|
-
query.aggregates.push({ op, variable: varClean, as: asClean });
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
case 'ORDER': {
|
|
339
|
-
this.advance();
|
|
340
|
-
this.expect('word', 'BY');
|
|
341
|
-
while (this.peek().kind === 'word' && this.peek().value.startsWith('?')) {
|
|
342
|
-
const v = this.advance().value.slice(1);
|
|
343
|
-
let dir: 'asc' | 'desc' = 'asc';
|
|
344
|
-
if (this.peek().kind === 'word' && ['ASC', 'DESC'].includes(this.peek().value.toUpperCase())) {
|
|
345
|
-
dir = this.advance().value.toLowerCase() as 'asc' | 'desc';
|
|
346
|
-
}
|
|
347
|
-
query.orderBy.push({ variable: v, direction: dir });
|
|
348
|
-
}
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
case 'LIMIT': {
|
|
353
|
-
this.advance();
|
|
354
|
-
query.limit = Number(this.expect('number').value);
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
case 'OFFSET': {
|
|
359
|
-
this.advance();
|
|
360
|
-
query.offset = Number(this.expect('number').value);
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
default:
|
|
365
|
-
throw new Error(`Unknown keyword "${kw.value}" at pos ${kw.pos}`);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return query;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// -----------------------------------------------------------------------
|
|
373
|
-
// Datalog Rule
|
|
374
|
-
// -----------------------------------------------------------------------
|
|
375
|
-
|
|
376
|
-
parseRule(): DatalogRule {
|
|
377
|
-
// name(?x, ?y) :- body
|
|
378
|
-
const name = this.expect('word').value;
|
|
379
|
-
this.expect('symbol', '(');
|
|
380
|
-
const params: string[] = [];
|
|
381
|
-
while (!this.isAt('symbol', ')') && !this.isAt('eof', undefined)) {
|
|
382
|
-
const v = this.expect('word').value;
|
|
383
|
-
params.push(v.startsWith('?') ? v.slice(1) : v);
|
|
384
|
-
this.match('symbol', ',');
|
|
385
|
-
}
|
|
386
|
-
this.expect('symbol', ')');
|
|
387
|
-
|
|
388
|
-
// :- separator (colon + minus as two tokens, or we accept ":-" as word)
|
|
389
|
-
if (this.isAt('symbol', ':')) {
|
|
390
|
-
this.advance();
|
|
391
|
-
// Accept - as a word or skip
|
|
392
|
-
if (this.peek().kind === 'number' && this.peek().value.startsWith('-')) {
|
|
393
|
-
this.advance();
|
|
394
|
-
}
|
|
395
|
-
} else if (this.isAt('word', ':-')) {
|
|
396
|
-
this.advance();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const body: Pattern[] = [];
|
|
400
|
-
const filters: Filter[] = [];
|
|
401
|
-
|
|
402
|
-
while (!this.isAt('eof', undefined)) {
|
|
403
|
-
if (this.peek().kind === 'word' && this.peek().value.toUpperCase() === 'FILTER') {
|
|
404
|
-
this.advance();
|
|
405
|
-
filters.push(this.parseFilter());
|
|
406
|
-
} else {
|
|
407
|
-
body.push(this.parsePattern());
|
|
408
|
-
}
|
|
409
|
-
this.match('symbol', ',');
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return { name, params, body, filters };
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// ---------------------------------------------------------------------------
|
|
417
|
-
// Public API
|
|
418
|
-
// ---------------------------------------------------------------------------
|
|
419
|
-
|
|
420
|
-
export function parseQuery(input: string): Query {
|
|
421
|
-
const tokens = tokenize(input);
|
|
422
|
-
return new Parser(tokens).parseQuery();
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export function parseRule(input: string): DatalogRule {
|
|
426
|
-
const tokens = tokenize(input);
|
|
427
|
-
return new Parser(tokens).parseRule();
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Shorthand: parse a simple "find entities where" query.
|
|
432
|
-
*
|
|
433
|
-
* Example: `find ?e where type = "Project"`
|
|
434
|
-
* Becomes: SELECT ?e WHERE { [?e "type" "Project"] }
|
|
435
|
-
*/
|
|
436
|
-
export function parseSimple(input: string): Query {
|
|
437
|
-
const trimmed = input.trim();
|
|
438
|
-
|
|
439
|
-
// Try to detect if it's already a full query (starts with SELECT/WHERE)
|
|
440
|
-
const upper = trimmed.toUpperCase();
|
|
441
|
-
if (upper.startsWith('SELECT') || upper.startsWith('WHERE')) {
|
|
442
|
-
return parseQuery(trimmed);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Simple format: find ?vars where attr op value [and attr op value]*
|
|
446
|
-
const findMatch = trimmed.match(/^find\s+(.+?)\s+where\s+(.+)$/i);
|
|
447
|
-
if (findMatch) {
|
|
448
|
-
const vars = findMatch[1].trim().split(/\s+/);
|
|
449
|
-
const conditions = findMatch[2].trim();
|
|
450
|
-
|
|
451
|
-
const selectVars = vars.map((v) => v.startsWith('?') ? v : `?${v}`);
|
|
452
|
-
const entity = selectVars[0];
|
|
453
|
-
|
|
454
|
-
// Parse conditions: "attr op value [and attr op value]*"
|
|
455
|
-
const parts = conditions.split(/\s+and\s+/i);
|
|
456
|
-
const patterns: string[] = [];
|
|
457
|
-
const filters: string[] = [];
|
|
458
|
-
|
|
459
|
-
for (const part of parts) {
|
|
460
|
-
const eqMatch = part.match(/^(\S+)\s*(=|!=|<|<=|>|>=|contains|startsWith|endsWith|matches)\s*(.+)$/);
|
|
461
|
-
if (eqMatch) {
|
|
462
|
-
const [, attr, op, val] = eqMatch;
|
|
463
|
-
const valTrimmed = val.trim();
|
|
464
|
-
if (op === '=') {
|
|
465
|
-
// Direct fact pattern
|
|
466
|
-
patterns.push(`[${entity} "${attr}" ${valTrimmed}]`);
|
|
467
|
-
} else {
|
|
468
|
-
// Need a variable + filter
|
|
469
|
-
const tmpVar = `?_${attr.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
470
|
-
patterns.push(`[${entity} "${attr}" ${tmpVar}]`);
|
|
471
|
-
filters.push(`FILTER ${tmpVar} ${op} ${valTrimmed}`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const fullQuery = `SELECT ${selectVars.join(' ')}\nWHERE {\n ${patterns.join('\n ')}\n}\n${filters.join('\n')}`;
|
|
477
|
-
return parseQuery(fullQuery);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
throw new Error(`Cannot parse query: "${trimmed}". Use full EQL-S syntax or "find ?e where attr = value".`);
|
|
481
|
-
}
|
package/src/core/query/types.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EQL-S Query Types — Entity Query Language (Structured)
|
|
3
|
-
*
|
|
4
|
-
* Defines the AST for structured queries over the EAV store.
|
|
5
|
-
* Queries are composed of patterns (fact/link clauses) that bind
|
|
6
|
-
* variables, plus optional filters, aggregations, and projections.
|
|
7
|
-
*
|
|
8
|
-
* @module trellis/core/query
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { Atom } from '../store/eav-store.js';
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Variables & Terms
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A query variable, prefixed with `?` in the DSL.
|
|
19
|
-
* e.g. `?e`, `?name`, `?type`
|
|
20
|
-
*/
|
|
21
|
-
export interface Variable {
|
|
22
|
-
kind: 'variable';
|
|
23
|
-
name: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* A literal constant value.
|
|
28
|
-
*/
|
|
29
|
-
export interface Literal {
|
|
30
|
-
kind: 'literal';
|
|
31
|
-
value: Atom;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* A term is either a variable or a literal.
|
|
36
|
-
*/
|
|
37
|
-
export type Term = Variable | Literal;
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Patterns (clauses)
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* A fact pattern matches triples (e, a, v) in the EAV store.
|
|
45
|
-
*
|
|
46
|
-
* Example DSL: `[?e "type" "Project"]` or `[?e ?attr ?val]`
|
|
47
|
-
*/
|
|
48
|
-
export interface FactPattern {
|
|
49
|
-
kind: 'fact';
|
|
50
|
-
entity: Term;
|
|
51
|
-
attribute: Term;
|
|
52
|
-
value: Term;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* A link pattern matches links (e1, a, e2) in the graph.
|
|
57
|
-
*
|
|
58
|
-
* Example DSL: `(?src "memberOf" ?tgt)`
|
|
59
|
-
*/
|
|
60
|
-
export interface LinkPattern {
|
|
61
|
-
kind: 'link';
|
|
62
|
-
source: Term;
|
|
63
|
-
attribute: Term;
|
|
64
|
-
target: Term;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* A negation pattern — succeeds when the inner pattern has NO matches.
|
|
69
|
-
*/
|
|
70
|
-
export interface NotPattern {
|
|
71
|
-
kind: 'not';
|
|
72
|
-
pattern: Pattern;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* An or-pattern — succeeds when ANY branch matches.
|
|
77
|
-
*/
|
|
78
|
-
export interface OrPattern {
|
|
79
|
-
kind: 'or';
|
|
80
|
-
branches: Pattern[][];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* A rule application — invoke a named Datalog rule.
|
|
85
|
-
*/
|
|
86
|
-
export interface RuleApplication {
|
|
87
|
-
kind: 'rule';
|
|
88
|
-
name: string;
|
|
89
|
-
args: Term[];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export type Pattern = FactPattern | LinkPattern | NotPattern | OrPattern | RuleApplication;
|
|
93
|
-
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
// Filters
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
|
|
98
|
-
export type FilterOp = '=' | '!=' | '<' | '<=' | '>' | '>=' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
|
|
99
|
-
|
|
100
|
-
export interface Filter {
|
|
101
|
-
kind: 'filter';
|
|
102
|
-
left: Term;
|
|
103
|
-
op: FilterOp;
|
|
104
|
-
right: Term;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
// Aggregation
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
export type AggregateOp = 'count' | 'sum' | 'avg' | 'min' | 'max' | 'collect';
|
|
112
|
-
|
|
113
|
-
export interface Aggregate {
|
|
114
|
-
op: AggregateOp;
|
|
115
|
-
/** Variable to aggregate over. */
|
|
116
|
-
variable: string;
|
|
117
|
-
/** Output variable name. */
|
|
118
|
-
as: string;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
// Ordering
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
|
|
125
|
-
export interface OrderBy {
|
|
126
|
-
variable: string;
|
|
127
|
-
direction: 'asc' | 'desc';
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
// Query
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* A complete EQL-S query.
|
|
136
|
-
*/
|
|
137
|
-
export interface Query {
|
|
138
|
-
/** Variables to project in the result. Empty = return all bound variables. */
|
|
139
|
-
select: string[];
|
|
140
|
-
/** Pattern clauses that must all match (conjunction). */
|
|
141
|
-
where: Pattern[];
|
|
142
|
-
/** Post-match filters. */
|
|
143
|
-
filters: Filter[];
|
|
144
|
-
/** Aggregation functions. */
|
|
145
|
-
aggregates: Aggregate[];
|
|
146
|
-
/** Ordering. */
|
|
147
|
-
orderBy: OrderBy[];
|
|
148
|
-
/** Maximum number of results (0 = unlimited). */
|
|
149
|
-
limit: number;
|
|
150
|
-
/** Number of results to skip. */
|
|
151
|
-
offset: number;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
// Bindings
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* A single set of variable bindings.
|
|
160
|
-
*/
|
|
161
|
-
export type Bindings = Map<string, Atom>;
|
|
162
|
-
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
// Datalog Rules
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* A Datalog rule: `head :- body`.
|
|
169
|
-
*
|
|
170
|
-
* Example: `ancestor(?x, ?z) :- [?x "parent" ?y], ancestor(?y, ?z)`
|
|
171
|
-
*/
|
|
172
|
-
export interface DatalogRule {
|
|
173
|
-
name: string;
|
|
174
|
-
/** Parameter variable names for the head. */
|
|
175
|
-
params: string[];
|
|
176
|
-
/** Body patterns (conjunction). */
|
|
177
|
-
body: Pattern[];
|
|
178
|
-
/** Body filters. */
|
|
179
|
-
filters: Filter[];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// ---------------------------------------------------------------------------
|
|
183
|
-
// Helpers
|
|
184
|
-
// ---------------------------------------------------------------------------
|
|
185
|
-
|
|
186
|
-
export function isVariable(t: Term): t is Variable {
|
|
187
|
-
return t.kind === 'variable';
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function isLiteral(t: Term): t is Literal {
|
|
191
|
-
return t.kind === 'literal';
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export function variable(name: string): Variable {
|
|
195
|
-
return { kind: 'variable', name };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function literal(value: Atom): Literal {
|
|
199
|
-
return { kind: 'literal', value };
|
|
200
|
-
}
|