trellis 1.0.8 → 2.0.6

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +564 -83
  3. package/bin/trellis.mjs +2 -0
  4. package/dist/cli/index.js +4718 -0
  5. package/dist/core/index.js +12 -0
  6. package/dist/decisions/index.js +19 -0
  7. package/dist/embeddings/index.js +43 -0
  8. package/dist/index-1j1anhmr.js +4038 -0
  9. package/dist/index-3s0eak0p.js +1556 -0
  10. package/dist/index-8pce39mh.js +272 -0
  11. package/dist/index-a76rekgs.js +67 -0
  12. package/dist/index-cy9k1g6v.js +684 -0
  13. package/dist/index-fd4e26s4.js +69 -0
  14. package/dist/{store/eav-store.js → index-gkvhzm9f.js} +4 -6
  15. package/dist/index-gnw8d7d6.js +51 -0
  16. package/dist/index-vkpkfwhq.js +817 -0
  17. package/dist/index.js +118 -2876
  18. package/dist/links/index.js +55 -0
  19. package/dist/transformers-m9je15kg.js +32491 -0
  20. package/dist/vcs/index.js +110 -0
  21. package/logo.png +0 -0
  22. package/logo.svg +9 -0
  23. package/package.json +79 -76
  24. package/src/cli/index.ts +2340 -0
  25. package/src/core/index.ts +35 -0
  26. package/src/core/kernel/middleware.ts +44 -0
  27. package/src/core/persist/backend.ts +64 -0
  28. package/src/core/store/eav-store.ts +467 -0
  29. package/src/decisions/auto-capture.ts +136 -0
  30. package/src/decisions/hooks.ts +163 -0
  31. package/src/decisions/index.ts +261 -0
  32. package/src/decisions/types.ts +103 -0
  33. package/src/embeddings/chunker.ts +327 -0
  34. package/src/embeddings/index.ts +41 -0
  35. package/src/embeddings/model.ts +95 -0
  36. package/src/embeddings/search.ts +305 -0
  37. package/src/embeddings/store.ts +313 -0
  38. package/src/embeddings/types.ts +85 -0
  39. package/src/engine.ts +1083 -0
  40. package/src/garden/cluster.ts +330 -0
  41. package/src/garden/garden.ts +306 -0
  42. package/src/garden/index.ts +29 -0
  43. package/src/git/git-exporter.ts +286 -0
  44. package/src/git/git-importer.ts +329 -0
  45. package/src/git/git-reader.ts +189 -0
  46. package/src/git/index.ts +22 -0
  47. package/src/identity/governance.ts +211 -0
  48. package/src/identity/identity.ts +224 -0
  49. package/src/identity/index.ts +30 -0
  50. package/src/identity/signing-middleware.ts +97 -0
  51. package/src/index.ts +20 -0
  52. package/src/links/index.ts +49 -0
  53. package/src/links/lifecycle.ts +400 -0
  54. package/src/links/parser.ts +484 -0
  55. package/src/links/ref-index.ts +186 -0
  56. package/src/links/resolver.ts +314 -0
  57. package/src/links/types.ts +108 -0
  58. package/src/mcp/index.ts +22 -0
  59. package/src/mcp/server.ts +1278 -0
  60. package/src/semantic/csharp-parser.ts +493 -0
  61. package/src/semantic/go-parser.ts +585 -0
  62. package/src/semantic/index.ts +34 -0
  63. package/src/semantic/java-parser.ts +456 -0
  64. package/src/semantic/python-parser.ts +659 -0
  65. package/src/semantic/ruby-parser.ts +446 -0
  66. package/src/semantic/rust-parser.ts +784 -0
  67. package/src/semantic/semantic-merge.ts +210 -0
  68. package/src/semantic/ts-parser.ts +681 -0
  69. package/src/semantic/types.ts +175 -0
  70. package/src/sync/index.ts +32 -0
  71. package/src/sync/memory-transport.ts +66 -0
  72. package/src/sync/reconciler.ts +237 -0
  73. package/src/sync/sync-engine.ts +258 -0
  74. package/src/sync/types.ts +104 -0
  75. package/src/vcs/blob-store.ts +124 -0
  76. package/src/vcs/branch.ts +150 -0
  77. package/src/vcs/checkpoint.ts +64 -0
  78. package/src/vcs/decompose.ts +469 -0
  79. package/src/vcs/diff.ts +409 -0
  80. package/src/vcs/engine-context.ts +26 -0
  81. package/src/vcs/index.ts +23 -0
  82. package/src/vcs/issue.ts +800 -0
  83. package/src/vcs/merge.ts +425 -0
  84. package/src/vcs/milestone.ts +124 -0
  85. package/src/vcs/ops.ts +59 -0
  86. package/src/vcs/types.ts +213 -0
  87. package/src/vcs/vcs-middleware.ts +81 -0
  88. package/src/watcher/fs-watcher.ts +217 -0
  89. package/src/watcher/index.ts +9 -0
  90. package/src/watcher/ingestion.ts +116 -0
  91. package/dist/ai/index.js +0 -688
  92. package/dist/cli/server.js +0 -3321
  93. package/dist/cli/tql.js +0 -5282
  94. package/dist/client/tql-client.js +0 -108
  95. package/dist/graph/index.js +0 -2248
  96. package/dist/kernel/logic-middleware.js +0 -179
  97. package/dist/kernel/middleware.js +0 -0
  98. package/dist/kernel/operations.js +0 -32
  99. package/dist/kernel/schema-middleware.js +0 -34
  100. package/dist/kernel/security-middleware.js +0 -53
  101. package/dist/kernel/trellis-kernel.js +0 -2239
  102. package/dist/kernel/workspace.js +0 -91
  103. package/dist/persist/backend.js +0 -0
  104. package/dist/persist/sqlite-backend.js +0 -123
  105. package/dist/query/index.js +0 -1643
  106. package/dist/server/index.js +0 -3309
  107. package/dist/workflows/index.js +0 -3160
@@ -0,0 +1,35 @@
1
+ /**
2
+ * TrellisVCS Core — EAV Store, Kernel Types, Middleware
3
+ *
4
+ * Inlined from trellis-core for single-package publish.
5
+ * Consumers: `import { EAVStore, Fact } from 'trellis/core'`
6
+ *
7
+ * @module trellis/core
8
+ */
9
+
10
+ // EAV Store
11
+ export { EAVStore, flatten, jsonEntityFacts } from './store/eav-store.js';
12
+
13
+ export type {
14
+ Atom,
15
+ EntityRef,
16
+ Fact,
17
+ Link,
18
+ CatalogEntry,
19
+ QueryTraceEntry,
20
+ QueryResult,
21
+ } from './store/eav-store.js';
22
+
23
+ // Kernel persistence types
24
+ export type {
25
+ KernelOp,
26
+ KernelOpKind,
27
+ KernelBackend,
28
+ } from './persist/backend.js';
29
+
30
+ // Kernel middleware types
31
+ export type {
32
+ KernelMiddleware,
33
+ MiddlewareContext,
34
+ OpMiddlewareNext,
35
+ } from './kernel/middleware.js';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Kernel middleware types.
3
+ * Slim version inlined from trellis-core — only includes the types
4
+ * used by the VCS layer (op middleware). Query middleware types are
5
+ * omitted to avoid pulling in the full query engine dependency chain.
6
+ *
7
+ * @module trellis/core
8
+ */
9
+
10
+ import type { KernelOp } from '../persist/backend.js';
11
+
12
+ export type MiddlewareContext = {
13
+ agentId?: string;
14
+ [key: string]: unknown;
15
+ };
16
+
17
+ export type OpMiddlewareNext = (
18
+ op: KernelOp,
19
+ ctx: MiddlewareContext,
20
+ ) => void | Promise<void>;
21
+
22
+ export interface KernelMiddleware {
23
+ name: string;
24
+
25
+ /**
26
+ * Hook into kernel operations (mutations).
27
+ * Can throw to block the operation (e.g. for security).
28
+ */
29
+ handleOp?: (
30
+ op: KernelOp,
31
+ ctx: MiddlewareContext,
32
+ next: OpMiddlewareNext,
33
+ ) => void | Promise<void>;
34
+
35
+ /**
36
+ * Hook into kernel queries.
37
+ * Typed loosely here to avoid importing the full query engine.
38
+ */
39
+ handleQuery?: (
40
+ query: unknown,
41
+ ctx: MiddlewareContext,
42
+ next: (...args: any[]) => any,
43
+ ) => any;
44
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Kernel persistence backend types.
3
+ * Inlined from trellis-core for single-package publish.
4
+ *
5
+ * @module trellis/core
6
+ */
7
+
8
+ import type { Fact, Link } from '../store/eav-store.js';
9
+
10
+ export type KernelOpKind =
11
+ | 'addFacts'
12
+ | 'addLinks'
13
+ | 'deleteFacts'
14
+ | 'deleteLinks';
15
+
16
+ export interface KernelOp {
17
+ /**
18
+ * Content hash of this operation (including previousHash).
19
+ * Format: trellis:op:{hash}
20
+ */
21
+ hash: string;
22
+
23
+ /**
24
+ * Kind of operation.
25
+ */
26
+ kind: KernelOpKind;
27
+
28
+ /**
29
+ * ISO timestamp of when the op was created.
30
+ */
31
+ timestamp: string;
32
+
33
+ /**
34
+ * The ID of the agent that performed the operation.
35
+ */
36
+ agentId: string;
37
+
38
+ /**
39
+ * Hash of the previous operation in the local chain.
40
+ */
41
+ previousHash?: string;
42
+
43
+ /**
44
+ * The actual data payload.
45
+ */
46
+ facts?: Fact[];
47
+ links?: Link[];
48
+ }
49
+
50
+ export interface KernelBackend {
51
+ init(): void;
52
+ append(op: KernelOp): void;
53
+ readAll(): KernelOp[];
54
+ readUntil(hash: string): KernelOp[];
55
+ readAfter(hash: string): KernelOp[];
56
+ readUntilTimestamp(isoTimestamp: string): KernelOp[];
57
+ getLastOp(): KernelOp | undefined;
58
+
59
+ // Snapshot support
60
+ saveSnapshot(lastOpHash: string, data: any): void;
61
+ loadLatestSnapshot(): { lastOpHash: string; data: any } | undefined;
62
+
63
+ close?(): void;
64
+ }
@@ -0,0 +1,467 @@
1
+ /**
2
+ * EAV-based Datalog Engine with Path-Aware Ingestor
3
+ *
4
+ * Core types and fact storage for schema-agnostic data processing.
5
+ * Inlined from trellis-core for single-package publish.
6
+ *
7
+ * @module trellis/core
8
+ */
9
+
10
+ export type Atom = string | number | boolean | Date | EntityRef;
11
+ export type EntityRef = string;
12
+
13
+ export interface Fact {
14
+ e: string; // entity
15
+ a: string; // attribute (JSONPath)
16
+ v: Atom; // value
17
+ }
18
+
19
+ export interface Link {
20
+ e1: string; // source entity
21
+ a: string; // relationship attribute
22
+ e2: string; // target entity
23
+ }
24
+
25
+ export interface CatalogEntry {
26
+ attribute: string;
27
+ type: 'string' | 'number' | 'boolean' | 'date' | 'mixed';
28
+ cardinality: 'one' | 'many';
29
+ distinctCount: number;
30
+ examples: Atom[];
31
+ min?: number;
32
+ max?: number;
33
+ }
34
+
35
+ export interface QueryTraceEntry {
36
+ goal: string;
37
+ bindingsCount: number;
38
+ durationMs: number;
39
+ }
40
+
41
+ export interface QueryResult {
42
+ bindings: Record<string, Atom>[];
43
+ executionTime: number;
44
+ plan?: string;
45
+ trace?: QueryTraceEntry[];
46
+ }
47
+
48
+ /**
49
+ * Path-aware JSON flattener
50
+ * Converts nested JSON into attribute-value pairs with dot notation
51
+ */
52
+ export function* flatten(obj: any, base = ''): Generator<[string, any]> {
53
+ if (Array.isArray(obj)) {
54
+ for (const v of obj) {
55
+ yield* flatten(v, base);
56
+ }
57
+ } else if (obj && typeof obj === 'object') {
58
+ for (const [k, v] of Object.entries(obj)) {
59
+ yield* flatten(v, base ? `${base}.${k}` : k);
60
+ }
61
+ } else {
62
+ yield [base, obj];
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Convert JSON entity to EAV facts
68
+ */
69
+ export function jsonEntityFacts(
70
+ entityId: string,
71
+ root: any,
72
+ type: string,
73
+ ): Fact[] {
74
+ const facts: Fact[] = [{ e: entityId, a: 'type', v: type }];
75
+
76
+ for (const [a, v] of flatten(root)) {
77
+ if (v === undefined || v === null) continue;
78
+
79
+ if (Array.isArray(v)) {
80
+ for (const el of v) {
81
+ facts.push({ e: entityId, a, v: el as any });
82
+ }
83
+ } else if (typeof v === 'object') {
84
+ // Handled by flatten recursion
85
+ } else {
86
+ facts.push({ e: entityId, a, v: v as any });
87
+ }
88
+ }
89
+
90
+ return facts;
91
+ }
92
+
93
+ /**
94
+ * In-memory EAV triple store
95
+ */
96
+ export class EAVStore {
97
+ private facts: Fact[] = [];
98
+ private links: Link[] = [];
99
+ private catalog: Map<string, CatalogEntry> = new Map();
100
+
101
+ // Indexes for fast lookups
102
+ private eavIndex: Map<string, Map<string, Set<number>>> = new Map();
103
+ private aevIndex: Map<string, Map<string, Set<number>>> = new Map();
104
+ private aveIndex: Map<string, Map<Atom, Set<number>>> = new Map();
105
+
106
+ // Link indexes for graph queries
107
+ private linkIndex: Map<string, Map<string, Set<string>>> = new Map(); // e1 -> a -> e2s
108
+ private linkReverseIndex: Map<string, Map<string, Set<string>>> = new Map(); // e2 -> a -> e1s
109
+ private linkAttrIndex: Map<string, Set<[string, string]>> = new Map(); // a -> [(e1, e2)]
110
+
111
+ // Distinct value tracking
112
+ private distinct = new Map<string, Set<string>>(); // attr -> set of valueKey
113
+
114
+ addFacts(facts: Fact[]): void {
115
+ for (let i = 0; i < facts.length; i++) {
116
+ const fact = facts[i];
117
+ if (fact) {
118
+ this.facts.push(fact);
119
+ this.updateIndexes(fact, this.facts.length - 1);
120
+ this.updateCatalog(fact);
121
+ }
122
+ }
123
+ }
124
+
125
+ addLinks(links: Link[]): void {
126
+ for (const link of links) {
127
+ this.links.push(link);
128
+ this.updateLinkIndexes(link);
129
+ }
130
+ }
131
+
132
+ deleteFacts(factsToDelete: Fact[]): void {
133
+ for (const fact of factsToDelete) {
134
+ // Find the fact index
135
+ const valueKey = this.valueKey(fact.v);
136
+ const indices = this.aveIndex.get(fact.a)?.get(valueKey);
137
+ if (!indices) continue;
138
+
139
+ let foundIdx = -1;
140
+ for (const idx of indices) {
141
+ const storedFact = this.facts[idx];
142
+ if (storedFact && storedFact.e === fact.e && storedFact.a === fact.a) {
143
+ foundIdx = idx;
144
+ break;
145
+ }
146
+ }
147
+
148
+ if (foundIdx !== -1) {
149
+ // Remove from main facts (set to null to maintain indices if needed, or just remove and rebuild)
150
+ // For simplicity and to keep indices valid without shifting, we'll set to undefined
151
+ this.facts[foundIdx] = undefined as any;
152
+
153
+ // Remove from indexes
154
+ this.eavIndex.get(fact.e)?.get(fact.a)?.delete(foundIdx);
155
+ this.aevIndex.get(fact.a)?.get(fact.e)?.delete(foundIdx);
156
+ this.aveIndex.get(fact.a)?.get(valueKey)?.delete(foundIdx);
157
+
158
+ // Update catalog (approximate, since we don't fully rebuild it)
159
+ const entry = this.catalog.get(fact.a);
160
+ if (entry) {
161
+ // If we had a specific count, we'd decrement, but distinctCount needs re-check
162
+ // For now we'll just leave it or let it be refreshed later
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ deleteLinks(linksToDelete: Link[]): void {
169
+ for (const link of linksToDelete) {
170
+ // Find and remove from main links list
171
+ const initialLen = this.links.length;
172
+ this.links = this.links.filter(
173
+ (l) => !(l.e1 === link.e1 && l.a === link.a && l.e2 === link.e2),
174
+ );
175
+
176
+ if (this.links.length < initialLen) {
177
+ // Remove from indexes
178
+ this.linkIndex.get(link.e1)?.get(link.a)?.delete(link.e2);
179
+ this.linkReverseIndex.get(link.e2)?.get(link.a)?.delete(link.e1);
180
+
181
+ const attrPairs = this.linkAttrIndex.get(link.a);
182
+ if (attrPairs) {
183
+ for (const pair of attrPairs) {
184
+ if (pair[0] === link.e1 && pair[1] === link.e2) {
185
+ attrPairs.delete(pair);
186
+ break;
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ private updateIndexes(fact: Fact, index: number): void {
195
+ // EAV index: entity -> attribute -> fact indices
196
+ if (!this.eavIndex.has(fact.e)) {
197
+ this.eavIndex.set(fact.e, new Map());
198
+ }
199
+ if (!this.eavIndex.get(fact.e)!.has(fact.a)) {
200
+ this.eavIndex.get(fact.e)!.set(fact.a, new Set());
201
+ }
202
+ this.eavIndex.get(fact.e)!.get(fact.a)!.add(index);
203
+
204
+ // AEV index: attribute -> entity -> fact indices
205
+ if (!this.aevIndex.has(fact.a)) {
206
+ this.aevIndex.set(fact.a, new Map());
207
+ }
208
+ if (!this.aevIndex.get(fact.a)!.has(fact.e)) {
209
+ this.aevIndex.get(fact.a)!.set(fact.e, new Set());
210
+ }
211
+ this.aevIndex.get(fact.a)!.get(fact.e)!.add(index);
212
+
213
+ // AVE index: attribute -> value -> fact indices
214
+ if (!this.aveIndex.has(fact.a)) {
215
+ this.aveIndex.set(fact.a, new Map());
216
+ }
217
+ const valueKey = this.valueKey(fact.v);
218
+ if (!this.aveIndex.get(fact.a)!.has(valueKey)) {
219
+ this.aveIndex.get(fact.a)!.set(valueKey, new Set());
220
+ }
221
+ this.aveIndex.get(fact.a)!.get(valueKey)!.add(index);
222
+ }
223
+
224
+ private updateLinkIndexes(link: Link): void {
225
+ // Forward index: e1 -> a -> e2s
226
+ if (!this.linkIndex.has(link.e1)) {
227
+ this.linkIndex.set(link.e1, new Map());
228
+ }
229
+ const e1Attrs = this.linkIndex.get(link.e1)!;
230
+ if (!e1Attrs.has(link.a)) {
231
+ e1Attrs.set(link.a, new Set());
232
+ }
233
+ e1Attrs.get(link.a)!.add(link.e2);
234
+
235
+ // Reverse index: e2 -> a -> e1s
236
+ if (!this.linkReverseIndex.has(link.e2)) {
237
+ this.linkReverseIndex.set(link.e2, new Map());
238
+ }
239
+ const e2Attrs = this.linkReverseIndex.get(link.e2)!;
240
+ if (!e2Attrs.has(link.a)) {
241
+ e2Attrs.set(link.a, new Set());
242
+ }
243
+ e2Attrs.get(link.a)!.add(link.e1);
244
+
245
+ // Attribute index: a -> [(e1, e2)]
246
+ if (!this.linkAttrIndex.has(link.a)) {
247
+ this.linkAttrIndex.set(link.a, new Set());
248
+ }
249
+ this.linkAttrIndex.get(link.a)!.add([link.e1, link.e2]);
250
+ }
251
+
252
+ private valueKey(v: Atom): string {
253
+ if (v instanceof Date) return `date:${v.toISOString()}`;
254
+ return `${typeof v}:${v}`;
255
+ }
256
+
257
+ private updateCatalog(fact: Fact): void {
258
+ const entry = this.catalog.get(fact.a) || {
259
+ attribute: fact.a,
260
+ type: this.inferType(fact.v),
261
+ cardinality: 'one',
262
+ distinctCount: 0,
263
+ examples: [],
264
+ };
265
+
266
+ // Update type (may become 'mixed')
267
+ const factType = this.inferType(fact.v);
268
+ if (entry.type !== factType && entry.type !== 'mixed') {
269
+ entry.type = 'mixed';
270
+ }
271
+
272
+ // Update cardinality (if we see multiple values for same entity+attribute)
273
+ const entityAttrs = this.eavIndex.get(fact.e)?.get(fact.a);
274
+ if (entityAttrs && entityAttrs.size > 1) {
275
+ entry.cardinality = 'many';
276
+ }
277
+
278
+ // Update distinct count
279
+ const k = this.valueKey(fact.v);
280
+ const s =
281
+ this.distinct.get(fact.a) ||
282
+ (this.distinct.set(fact.a, new Set()), this.distinct.get(fact.a)!);
283
+ s.add(k);
284
+ entry.distinctCount = s.size;
285
+
286
+ // Update examples (keep first 5)
287
+ if (entry.examples.length < 5 && !entry.examples.includes(fact.v)) {
288
+ entry.examples.push(fact.v);
289
+ }
290
+
291
+ // Update numeric ranges
292
+ if (typeof fact.v === 'number') {
293
+ entry.min = Math.min(entry.min ?? fact.v, fact.v);
294
+ entry.max = Math.max(entry.max ?? fact.v, fact.v);
295
+ }
296
+
297
+ this.catalog.set(fact.a, entry);
298
+ }
299
+
300
+ private inferType(
301
+ v: Atom,
302
+ ): 'string' | 'number' | 'boolean' | 'date' | 'mixed' {
303
+ if (typeof v === 'string') return 'string';
304
+ if (typeof v === 'number') return 'number';
305
+ if (typeof v === 'boolean') return 'boolean';
306
+ if (v instanceof Date) return 'date';
307
+ return 'mixed';
308
+ }
309
+
310
+ // Query methods
311
+ getFactsByEntity(entity: string): Fact[] {
312
+ const indices = this.eavIndex.get(entity);
313
+ if (!indices) return [];
314
+
315
+ const result: Fact[] = [];
316
+ for (const attrIndices of indices.values()) {
317
+ for (const idx of attrIndices) {
318
+ const fact = this.facts[idx];
319
+ if (fact) {
320
+ result.push(fact);
321
+ }
322
+ }
323
+ }
324
+ return result;
325
+ }
326
+
327
+ getFactsByAttribute(attribute: string): Fact[] {
328
+ const indices = this.aevIndex.get(attribute);
329
+ if (!indices) return [];
330
+
331
+ const result: Fact[] = [];
332
+ for (const entityIndices of indices.values()) {
333
+ for (const idx of entityIndices) {
334
+ const fact = this.facts[idx];
335
+ if (fact) {
336
+ result.push(fact);
337
+ }
338
+ }
339
+ }
340
+ return result;
341
+ }
342
+
343
+ getFactsByValue(attribute: string, value: Atom): Fact[] {
344
+ const indices = this.aveIndex.get(attribute)?.get(this.valueKey(value));
345
+ if (!indices) return [];
346
+
347
+ return Array.from(indices)
348
+ .map((idx) => this.facts[idx])
349
+ .filter((fact): fact is Fact => fact !== undefined);
350
+ }
351
+
352
+ getCatalog(): CatalogEntry[] {
353
+ return Array.from(this.catalog.values());
354
+ }
355
+
356
+ getCatalogEntry(attribute: string): CatalogEntry | undefined {
357
+ return this.catalog.get(attribute);
358
+ }
359
+
360
+ // Statistics
361
+ getAllFacts(): Fact[] {
362
+ return this.facts.filter((f): f is Fact => f !== undefined);
363
+ }
364
+
365
+ getAllLinks(): Link[] {
366
+ return [...this.links];
367
+ }
368
+
369
+ getLinksByEntity(entity: string): Link[] {
370
+ const results: Link[] = [];
371
+ const forwardLinks = this.linkIndex.get(entity);
372
+ if (forwardLinks) {
373
+ for (const [attr, targets] of forwardLinks) {
374
+ for (const target of targets) {
375
+ results.push({ e1: entity, a: attr, e2: target });
376
+ }
377
+ }
378
+ }
379
+ const reverseLinks = this.linkReverseIndex.get(entity);
380
+ if (reverseLinks) {
381
+ for (const [attr, sources] of reverseLinks) {
382
+ for (const source of sources) {
383
+ results.push({ e1: source, a: attr, e2: entity });
384
+ }
385
+ }
386
+ }
387
+ return results;
388
+ }
389
+
390
+ getLinksByAttribute(attribute: string): Link[] {
391
+ const results: Link[] = [];
392
+ const links = this.linkAttrIndex.get(attribute);
393
+ if (links) {
394
+ for (const [e1, e2] of links) {
395
+ results.push({ e1, a: attribute, e2 });
396
+ }
397
+ }
398
+ return results;
399
+ }
400
+
401
+ getLinksByEntityAndAttribute(entity: string, attribute: string): Link[] {
402
+ const results: Link[] = [];
403
+ const attrs = this.linkIndex.get(entity);
404
+ if (attrs) {
405
+ const targets = attrs.get(attribute);
406
+ if (targets) {
407
+ for (const target of targets) {
408
+ results.push({ e1: entity, a: attribute, e2: target });
409
+ }
410
+ }
411
+ }
412
+ return results;
413
+ }
414
+
415
+ getStats() {
416
+ return {
417
+ totalFacts: this.facts.length,
418
+ totalLinks: this.links.length,
419
+ uniqueEntities: this.eavIndex.size,
420
+ uniqueAttributes: this.aevIndex.size,
421
+ catalogEntries: this.catalog.size,
422
+ };
423
+ }
424
+
425
+ /**
426
+ * Creates a serializable snapshot of the current store state.
427
+ */
428
+ snapshot(): { facts: Fact[]; links: Link[]; catalog: CatalogEntry[] } {
429
+ return {
430
+ facts: this.facts.filter((f) => f !== undefined),
431
+ links: [...this.links],
432
+ catalog: this.getCatalog(),
433
+ };
434
+ }
435
+
436
+ /**
437
+ * Restores the store state from a snapshot and rebuilds all indexes.
438
+ */
439
+ restore(snapshot: {
440
+ facts: Fact[];
441
+ links: Link[];
442
+ catalog: CatalogEntry[];
443
+ }): void {
444
+ // Clear current state
445
+ this.facts = [];
446
+ this.links = [];
447
+ this.catalog.clear();
448
+ this.eavIndex.clear();
449
+ this.aevIndex.clear();
450
+ this.aveIndex.clear();
451
+ this.linkIndex.clear();
452
+ this.linkReverseIndex.clear();
453
+ this.linkAttrIndex.clear();
454
+ this.distinct.clear();
455
+
456
+ // Re-add data
457
+ this.addFacts(snapshot.facts);
458
+ this.addLinks(snapshot.links);
459
+
460
+ // Explicitly restore catalog if provided (though addFacts rebuilds it partially)
461
+ if (snapshot.catalog) {
462
+ for (const entry of snapshot.catalog) {
463
+ this.catalog.set(entry.attribute, entry);
464
+ }
465
+ }
466
+ }
467
+ }