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
|
@@ -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
|
-
}
|
package/src/core/query/engine.ts
DELETED
|
@@ -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
|
-
}
|