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.
Files changed (96) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/embeddings/index.js +1 -1
  3. package/dist/{index-7gvjxt27.js → index-2917tjd8.js} +1 -1
  4. package/package.json +2 -10
  5. package/dist/transformers.node-bx3q9d7k.js +0 -33130
  6. package/src/cli/index.ts +0 -3356
  7. package/src/core/agents/harness.ts +0 -380
  8. package/src/core/agents/index.ts +0 -18
  9. package/src/core/agents/types.ts +0 -90
  10. package/src/core/index.ts +0 -118
  11. package/src/core/kernel/middleware.ts +0 -44
  12. package/src/core/kernel/trellis-kernel.ts +0 -593
  13. package/src/core/ontology/builtins.ts +0 -248
  14. package/src/core/ontology/index.ts +0 -34
  15. package/src/core/ontology/registry.ts +0 -209
  16. package/src/core/ontology/types.ts +0 -124
  17. package/src/core/ontology/validator.ts +0 -382
  18. package/src/core/persist/backend.ts +0 -74
  19. package/src/core/persist/sqlite-backend.ts +0 -298
  20. package/src/core/plugins/index.ts +0 -17
  21. package/src/core/plugins/registry.ts +0 -322
  22. package/src/core/plugins/types.ts +0 -126
  23. package/src/core/query/datalog.ts +0 -188
  24. package/src/core/query/engine.ts +0 -370
  25. package/src/core/query/index.ts +0 -34
  26. package/src/core/query/parser.ts +0 -481
  27. package/src/core/query/types.ts +0 -200
  28. package/src/core/store/eav-store.ts +0 -467
  29. package/src/decisions/auto-capture.ts +0 -136
  30. package/src/decisions/hooks.ts +0 -163
  31. package/src/decisions/index.ts +0 -261
  32. package/src/decisions/types.ts +0 -103
  33. package/src/embeddings/auto-embed.ts +0 -248
  34. package/src/embeddings/chunker.ts +0 -327
  35. package/src/embeddings/index.ts +0 -48
  36. package/src/embeddings/model.ts +0 -112
  37. package/src/embeddings/search.ts +0 -305
  38. package/src/embeddings/store.ts +0 -313
  39. package/src/embeddings/types.ts +0 -92
  40. package/src/engine.ts +0 -1125
  41. package/src/garden/cluster.ts +0 -330
  42. package/src/garden/garden.ts +0 -306
  43. package/src/garden/index.ts +0 -29
  44. package/src/git/git-exporter.ts +0 -286
  45. package/src/git/git-importer.ts +0 -329
  46. package/src/git/git-reader.ts +0 -189
  47. package/src/git/index.ts +0 -22
  48. package/src/identity/governance.ts +0 -211
  49. package/src/identity/identity.ts +0 -224
  50. package/src/identity/index.ts +0 -30
  51. package/src/identity/signing-middleware.ts +0 -97
  52. package/src/index.ts +0 -29
  53. package/src/links/index.ts +0 -49
  54. package/src/links/lifecycle.ts +0 -400
  55. package/src/links/parser.ts +0 -484
  56. package/src/links/ref-index.ts +0 -186
  57. package/src/links/resolver.ts +0 -314
  58. package/src/links/types.ts +0 -108
  59. package/src/mcp/index.ts +0 -22
  60. package/src/mcp/server.ts +0 -1278
  61. package/src/semantic/csharp-parser.ts +0 -493
  62. package/src/semantic/go-parser.ts +0 -585
  63. package/src/semantic/index.ts +0 -34
  64. package/src/semantic/java-parser.ts +0 -456
  65. package/src/semantic/python-parser.ts +0 -659
  66. package/src/semantic/ruby-parser.ts +0 -446
  67. package/src/semantic/rust-parser.ts +0 -784
  68. package/src/semantic/semantic-merge.ts +0 -210
  69. package/src/semantic/ts-parser.ts +0 -681
  70. package/src/semantic/types.ts +0 -175
  71. package/src/sync/http-transport.ts +0 -144
  72. package/src/sync/index.ts +0 -43
  73. package/src/sync/memory-transport.ts +0 -66
  74. package/src/sync/multi-repo.ts +0 -200
  75. package/src/sync/reconciler.ts +0 -237
  76. package/src/sync/sync-engine.ts +0 -258
  77. package/src/sync/types.ts +0 -104
  78. package/src/sync/ws-transport.ts +0 -145
  79. package/src/ui/client.html +0 -695
  80. package/src/ui/server.ts +0 -419
  81. package/src/vcs/blob-store.ts +0 -124
  82. package/src/vcs/branch.ts +0 -150
  83. package/src/vcs/checkpoint.ts +0 -64
  84. package/src/vcs/decompose.ts +0 -469
  85. package/src/vcs/diff.ts +0 -409
  86. package/src/vcs/engine-context.ts +0 -26
  87. package/src/vcs/index.ts +0 -23
  88. package/src/vcs/issue.ts +0 -800
  89. package/src/vcs/merge.ts +0 -425
  90. package/src/vcs/milestone.ts +0 -124
  91. package/src/vcs/ops.ts +0 -59
  92. package/src/vcs/types.ts +0 -213
  93. package/src/vcs/vcs-middleware.ts +0 -81
  94. package/src/watcher/fs-watcher.ts +0 -255
  95. package/src/watcher/index.ts +0 -9
  96. package/src/watcher/ingestion.ts +0 -116
@@ -1,126 +0,0 @@
1
- /**
2
- * Plugin System Types
3
- *
4
- * Defines the plugin interface, manifest format, lifecycle hooks,
5
- * and event system for extensibility.
6
- *
7
- * @module trellis/core/plugins
8
- */
9
-
10
- import type { KernelMiddleware } from '../kernel/middleware.js';
11
- import type { OntologySchema } from '../ontology/types.js';
12
- import type { DatalogRule } from '../query/types.js';
13
- import type { KernelOp } from '../persist/backend.js';
14
-
15
- // ---------------------------------------------------------------------------
16
- // Plugin definition
17
- // ---------------------------------------------------------------------------
18
-
19
- export interface PluginDef {
20
- /** Unique plugin identifier (e.g. "trellis:security", "my-org:custom"). */
21
- id: string;
22
- /** Human-readable name. */
23
- name: string;
24
- /** Semantic version. */
25
- version: string;
26
- /** Description. */
27
- description?: string;
28
- /** Plugin dependencies (other plugin IDs). */
29
- dependencies?: string[];
30
-
31
- /** Kernel middleware provided by this plugin. */
32
- middleware?: KernelMiddleware[];
33
- /** Ontology schemas provided by this plugin. */
34
- ontologies?: OntologySchema[];
35
- /** Datalog rules provided by this plugin. */
36
- rules?: DatalogRule[];
37
- /** Event listeners provided by this plugin. */
38
- eventHandlers?: EventHandler[];
39
-
40
- /** Called when the plugin is loaded. */
41
- onLoad?: (ctx: PluginContext) => void | Promise<void>;
42
- /** Called when the plugin is unloaded. */
43
- onUnload?: (ctx: PluginContext) => void | Promise<void>;
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // Plugin context — what the plugin receives during lifecycle
48
- // ---------------------------------------------------------------------------
49
-
50
- export interface PluginContext {
51
- /** The plugin's own ID. */
52
- pluginId: string;
53
- /** Subscribe to events. */
54
- on: (event: string, handler: EventCallback) => void;
55
- /** Emit an event. */
56
- emit: (event: string, data?: unknown) => void;
57
- /** Get workspace config value. */
58
- getConfig: (key: string) => unknown;
59
- /** Log a message. */
60
- log: (message: string) => void;
61
- }
62
-
63
- // ---------------------------------------------------------------------------
64
- // Event system
65
- // ---------------------------------------------------------------------------
66
-
67
- export type EventCallback = (data: unknown) => void | Promise<void>;
68
-
69
- export interface EventHandler {
70
- /** Event name pattern (e.g. "op:*", "entity:created", "milestone:created"). */
71
- event: string;
72
- /** Handler function. */
73
- handler: EventCallback;
74
- }
75
-
76
- /** Well-known event names. */
77
- export type WellKnownEvent =
78
- | 'op:applied'
79
- | 'entity:created'
80
- | 'entity:updated'
81
- | 'entity:deleted'
82
- | 'link:added'
83
- | 'link:removed'
84
- | 'milestone:created'
85
- | 'issue:created'
86
- | 'issue:closed'
87
- | 'plugin:loaded'
88
- | 'plugin:unloaded';
89
-
90
- // ---------------------------------------------------------------------------
91
- // Workspace configuration
92
- // ---------------------------------------------------------------------------
93
-
94
- export interface WorkspaceConfig {
95
- /** Active ontology IDs. */
96
- ontologies?: string[];
97
- /** Active plugin IDs. */
98
- plugins?: string[];
99
- /** Tracked file patterns (globs). */
100
- trackedPaths?: string[];
101
- /** Ignore patterns. */
102
- ignorePaths?: string[];
103
- /** Branch policies. */
104
- branchPolicies?: Record<string, { linear?: boolean }>;
105
- /** Embedding model override. */
106
- embeddingModel?: string;
107
- /** Snapshot threshold (ops between auto-snapshots). */
108
- snapshotThreshold?: number;
109
- /** Custom key-value settings. */
110
- settings?: Record<string, unknown>;
111
- }
112
-
113
- // ---------------------------------------------------------------------------
114
- // Plugin manifest (for on-disk .trellis/plugins.json)
115
- // ---------------------------------------------------------------------------
116
-
117
- export interface PluginManifest {
118
- /** Plugin ID. */
119
- id: string;
120
- /** Installed version. */
121
- version: string;
122
- /** Whether the plugin is enabled. */
123
- enabled: boolean;
124
- /** Plugin-specific configuration. */
125
- config?: Record<string, unknown>;
126
- }
@@ -1,188 +0,0 @@
1
- /**
2
- * Datalog Evaluator — Rule-based recursive queries.
3
- *
4
- * Provides transitive closure, reachability, and other recursive
5
- * graph queries via Datalog-style rules evaluated over the EAV store.
6
- *
7
- * Built-in rules:
8
- * - `reachable(?src, ?tgt)` via a named link attribute
9
- * - `ancestor(?x, ?y)` (generic transitive closure over any link)
10
- *
11
- * Custom rules can be registered via `addRule()`.
12
- *
13
- * @module trellis/core/query
14
- */
15
-
16
- import type { EAVStore } from '../store/eav-store.js';
17
- import type { DatalogRule } from './types.js';
18
- import { variable, literal } from './types.js';
19
- import { QueryEngine } from './engine.js';
20
-
21
- // ---------------------------------------------------------------------------
22
- // Built-in rule constructors
23
- // ---------------------------------------------------------------------------
24
-
25
- /**
26
- * Creates a transitive closure rule over a specific link attribute.
27
- *
28
- * `reachable(?x, ?y) :- (x attr y)`
29
- * `reachable(?x, ?y) :- (x attr ?z), reachable(?z, ?y)`
30
- */
31
- export function transitiveClosureRules(ruleName: string, linkAttribute: string): DatalogRule[] {
32
- return [
33
- // Base case: direct link
34
- {
35
- name: ruleName,
36
- params: ['x', 'y'],
37
- body: [
38
- {
39
- kind: 'link',
40
- source: variable('x'),
41
- attribute: literal(linkAttribute),
42
- target: variable('y'),
43
- },
44
- ],
45
- filters: [],
46
- },
47
- // Recursive case: indirect via intermediate
48
- {
49
- name: ruleName,
50
- params: ['x', 'y'],
51
- body: [
52
- {
53
- kind: 'link',
54
- source: variable('x'),
55
- attribute: literal(linkAttribute),
56
- target: variable('z'),
57
- },
58
- {
59
- kind: 'rule',
60
- name: ruleName,
61
- args: [variable('z'), variable('y')],
62
- },
63
- ],
64
- filters: [],
65
- },
66
- ];
67
- }
68
-
69
- /**
70
- * Creates a reverse reachability rule (follows links backwards).
71
- */
72
- export function reverseReachabilityRules(ruleName: string, linkAttribute: string): DatalogRule[] {
73
- return [
74
- {
75
- name: ruleName,
76
- params: ['x', 'y'],
77
- body: [
78
- {
79
- kind: 'link',
80
- source: variable('y'),
81
- attribute: literal(linkAttribute),
82
- target: variable('x'),
83
- },
84
- ],
85
- filters: [],
86
- },
87
- {
88
- name: ruleName,
89
- params: ['x', 'y'],
90
- body: [
91
- {
92
- kind: 'link',
93
- source: variable('z'),
94
- attribute: literal(linkAttribute),
95
- target: variable('x'),
96
- },
97
- {
98
- kind: 'rule',
99
- name: ruleName,
100
- args: [variable('z'), variable('y')],
101
- },
102
- ],
103
- filters: [],
104
- },
105
- ];
106
- }
107
-
108
- /**
109
- * Creates a "sibling" rule — entities that share a common parent via a link attribute.
110
- *
111
- * `sibling(?a, ?b) :- (?a attr ?parent), (?b attr ?parent)`
112
- * FILTER ?a != ?b
113
- */
114
- export function siblingRules(ruleName: string, linkAttribute: string): DatalogRule[] {
115
- return [
116
- {
117
- name: ruleName,
118
- params: ['a', 'b'],
119
- body: [
120
- {
121
- kind: 'link',
122
- source: variable('a'),
123
- attribute: literal(linkAttribute),
124
- target: variable('parent'),
125
- },
126
- {
127
- kind: 'link',
128
- source: variable('b'),
129
- attribute: literal(linkAttribute),
130
- target: variable('parent'),
131
- },
132
- ],
133
- filters: [
134
- {
135
- kind: 'filter',
136
- left: variable('a'),
137
- op: '!=',
138
- right: variable('b'),
139
- },
140
- ],
141
- },
142
- ];
143
- }
144
-
145
- // ---------------------------------------------------------------------------
146
- // Datalog Runtime — convenience wrapper around QueryEngine + rules
147
- // ---------------------------------------------------------------------------
148
-
149
- export class DatalogRuntime {
150
- private engine: QueryEngine;
151
-
152
- constructor(store: EAVStore) {
153
- this.engine = new QueryEngine(store);
154
- }
155
-
156
- /** Register a Datalog rule (or multiple). */
157
- addRule(rule: DatalogRule): void {
158
- this.engine.addRule(rule);
159
- }
160
-
161
- addRules(rules: DatalogRule[]): void {
162
- for (const r of rules) this.engine.addRule(r);
163
- }
164
-
165
- removeRule(name: string): void {
166
- this.engine.removeRule(name);
167
- }
168
-
169
- /** Register built-in transitive closure for a link attribute. */
170
- registerTransitiveClosure(ruleName: string, linkAttribute: string): void {
171
- this.addRules(transitiveClosureRules(ruleName, linkAttribute));
172
- }
173
-
174
- /** Register built-in reverse reachability for a link attribute. */
175
- registerReverseReachability(ruleName: string, linkAttribute: string): void {
176
- this.addRules(reverseReachabilityRules(ruleName, linkAttribute));
177
- }
178
-
179
- /** Register built-in sibling rule for a link attribute. */
180
- registerSiblings(ruleName: string, linkAttribute: string): void {
181
- this.addRules(siblingRules(ruleName, linkAttribute));
182
- }
183
-
184
- /** Get the underlying QueryEngine for direct query execution. */
185
- getEngine(): QueryEngine {
186
- return this.engine;
187
- }
188
- }
@@ -1,370 +0,0 @@
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
- }