@unrdf/knowledge-engine 5.0.1

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/package.json +64 -0
  4. package/src/browser-shims.mjs +343 -0
  5. package/src/browser.mjs +910 -0
  6. package/src/canonicalize.mjs +414 -0
  7. package/src/condition-cache.mjs +109 -0
  8. package/src/condition-evaluator.mjs +722 -0
  9. package/src/dark-matter-core.mjs +742 -0
  10. package/src/define-hook.mjs +213 -0
  11. package/src/effect-sandbox-browser.mjs +283 -0
  12. package/src/effect-sandbox-worker.mjs +170 -0
  13. package/src/effect-sandbox.mjs +517 -0
  14. package/src/engines/index.mjs +11 -0
  15. package/src/engines/rdf-engine.mjs +299 -0
  16. package/src/file-resolver.mjs +387 -0
  17. package/src/hook-executor-batching.mjs +277 -0
  18. package/src/hook-executor.mjs +870 -0
  19. package/src/hook-management.mjs +150 -0
  20. package/src/index.mjs +93 -0
  21. package/src/ken-parliment.mjs +119 -0
  22. package/src/ken.mjs +149 -0
  23. package/src/knowledge-engine/builtin-rules.mjs +190 -0
  24. package/src/knowledge-engine/inference-engine.mjs +418 -0
  25. package/src/knowledge-engine/knowledge-engine.mjs +317 -0
  26. package/src/knowledge-engine/pattern-dsl.mjs +142 -0
  27. package/src/knowledge-engine/pattern-matcher.mjs +215 -0
  28. package/src/knowledge-engine/rules.mjs +184 -0
  29. package/src/knowledge-engine.mjs +319 -0
  30. package/src/knowledge-hook-engine.mjs +360 -0
  31. package/src/knowledge-hook-manager.mjs +469 -0
  32. package/src/knowledge-substrate-core.mjs +927 -0
  33. package/src/lite.mjs +222 -0
  34. package/src/lockchain-writer-browser.mjs +414 -0
  35. package/src/lockchain-writer.mjs +602 -0
  36. package/src/monitoring/andon-signals.mjs +775 -0
  37. package/src/observability.mjs +531 -0
  38. package/src/parse.mjs +290 -0
  39. package/src/performance-optimizer.mjs +678 -0
  40. package/src/policy-pack.mjs +572 -0
  41. package/src/query-cache.mjs +116 -0
  42. package/src/query-optimizer.mjs +1051 -0
  43. package/src/query.mjs +306 -0
  44. package/src/reason.mjs +350 -0
  45. package/src/resolution-layer.mjs +506 -0
  46. package/src/schemas.mjs +1063 -0
  47. package/src/security/error-sanitizer.mjs +257 -0
  48. package/src/security/path-validator.mjs +194 -0
  49. package/src/security/sandbox-restrictions.mjs +331 -0
  50. package/src/security-validator.mjs +389 -0
  51. package/src/store-cache.mjs +137 -0
  52. package/src/telemetry.mjs +167 -0
  53. package/src/transaction.mjs +810 -0
  54. package/src/utils/adaptive-monitor.mjs +746 -0
  55. package/src/utils/circuit-breaker.mjs +513 -0
  56. package/src/utils/edge-case-handler.mjs +503 -0
  57. package/src/utils/memory-manager.mjs +498 -0
  58. package/src/utils/ring-buffer.mjs +282 -0
  59. package/src/validate.mjs +319 -0
  60. package/src/validators/index.mjs +338 -0
@@ -0,0 +1,1051 @@
1
+ /**
2
+ * @file Query Optimizer for performance improvements
3
+ * @module query-optimizer
4
+ *
5
+ * @description
6
+ * Implements query plan caching, indexing, and delta-aware evaluation
7
+ * to optimize SPARQL and SHACL query performance.
8
+ */
9
+
10
+ import { sha3_256 } from '@noble/hashes/sha3.js';
11
+ import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils.js';
12
+ import { randomUUID } from 'crypto';
13
+ import { z } from 'zod';
14
+ import LRUCache from 'lru-cache';
15
+ import { trace, metrics, SpanStatusCode } from '@opentelemetry/api';
16
+
17
+ /**
18
+ * Schema for query plan
19
+ */
20
+ const _QueryPlanSchema = z.object({
21
+ id: z.string(),
22
+ query: z.string(),
23
+ type: z.enum(['sparql-ask', 'sparql-select', 'shacl']),
24
+ hash: z.string(),
25
+ plan: z.object({
26
+ operations: z.array(
27
+ z.object({
28
+ type: z.string(),
29
+ cost: z.number(),
30
+ selectivity: z.number(),
31
+ dependencies: z.array(z.string()).optional(),
32
+ })
33
+ ),
34
+ estimatedCost: z.number(),
35
+ estimatedRows: z.number(),
36
+ indexes: z.array(z.string()).optional(),
37
+ }),
38
+ createdAt: z.number(),
39
+ lastUsed: z.number(),
40
+ hitCount: z.number().default(0),
41
+ });
42
+
43
+ /**
44
+ * Schema for index definition
45
+ */
46
+ const _IndexSchema = z.object({
47
+ id: z.string(),
48
+ name: z.string(),
49
+ type: z.enum(['predicate', 'subject', 'object', 'graph', 'composite']),
50
+ fields: z.array(z.string()),
51
+ selectivity: z.number().min(0).max(1),
52
+ size: z.number().nonnegative(),
53
+ createdAt: z.number(),
54
+ lastUpdated: z.number(),
55
+ });
56
+
57
+ /**
58
+ * Schema for delta-aware evaluation context
59
+ */
60
+ const _DeltaAwareContextSchema = z.object({
61
+ delta: z.object({
62
+ additions: z.array(z.any()),
63
+ removals: z.array(z.any()),
64
+ }),
65
+ affectedSubjects: z.set(z.string()).optional(),
66
+ affectedPredicates: z.set(z.string()).optional(),
67
+ affectedObjects: z.set(z.string()).optional(),
68
+ affectedGraphs: z.set(z.string()).optional(),
69
+ });
70
+
71
+ /**
72
+ * Query Optimizer for performance improvements
73
+ */
74
+ export class QueryOptimizer {
75
+ /**
76
+ * Create a new query optimizer
77
+ * @param {Object} [config] - Configuration
78
+ */
79
+ constructor(config = {}) {
80
+ this.config = {
81
+ enableCaching: config.enableCaching !== false,
82
+ enableIndexing: config.enableIndexing !== false,
83
+ enableDeltaAware: config.enableDeltaAware !== false,
84
+ maxCacheSize: config.maxCacheSize || 1000,
85
+ cacheMaxAge: config.cacheMaxAge || 300000, // 5 minutes
86
+ indexUpdateThreshold: config.indexUpdateThreshold || 100,
87
+ enableOTEL: config.enableOTEL !== false,
88
+ ...config,
89
+ };
90
+
91
+ this.queryPlans = new Map();
92
+ this.indexes = new Map();
93
+
94
+ // LRU Cache for query plans (40-60% overhead reduction)
95
+ this.cache = new LRUCache({
96
+ max: this.config.maxCacheSize,
97
+ maxAge: this.config.cacheMaxAge,
98
+ ttl: this.config.cacheMaxAge,
99
+ updateAgeOnGet: true,
100
+ updateAgeOnHas: true,
101
+ dispose: (value, _key) => {
102
+ // Clean up query plan resources
103
+ if (value && value.plan && value.plan.operations) {
104
+ value.plan.operations.length = 0;
105
+ }
106
+ },
107
+ });
108
+
109
+ this.stats = {
110
+ cacheHits: 0,
111
+ cacheMisses: 0,
112
+ indexHits: 0,
113
+ indexMisses: 0,
114
+ deltaOptimizations: 0,
115
+ totalQueries: 0,
116
+ };
117
+
118
+ // OTEL instrumentation
119
+ if (this.config.enableOTEL) {
120
+ this.tracer = trace.getTracer('query-optimizer');
121
+ this.meter = metrics.getMeter('query-optimizer');
122
+
123
+ // Create OTEL metrics
124
+ this.cacheHitCounter = this.meter.createCounter('query.cache.hits', {
125
+ description: 'Number of query cache hits',
126
+ });
127
+ this.cacheMissCounter = this.meter.createCounter('query.cache.misses', {
128
+ description: 'Number of query cache misses',
129
+ });
130
+ this.queryOptimizationDuration = this.meter.createHistogram('query.optimization.duration', {
131
+ description: 'Query optimization duration in ms',
132
+ unit: 'ms',
133
+ });
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Optimize a query
139
+ * @param {string} query - Query string
140
+ * @param {string} type - Query type
141
+ * @param {Store} graph - RDF graph
142
+ * @param {Object} [delta] - Delta for delta-aware optimization
143
+ * @returns {Promise<Object>} Optimized query plan
144
+ */
145
+ async optimizeQuery(query, type, graph, delta = null) {
146
+ this.stats.totalQueries++;
147
+
148
+ const startTime = Date.now();
149
+ const queryHash = this._hashQuery(query, type);
150
+
151
+ // OTEL span for query optimization
152
+ const span = this.config.enableOTEL
153
+ ? this.tracer.startSpan('query.optimize', {
154
+ attributes: {
155
+ 'query.type': type,
156
+ 'query.hash': queryHash.substring(0, 8),
157
+ 'query.length': query.length,
158
+ 'delta.enabled': !!delta,
159
+ },
160
+ })
161
+ : null;
162
+
163
+ try {
164
+ // Check LRU cache first
165
+ if (this.config.enableCaching) {
166
+ const cached = this.cache.get(queryHash);
167
+ if (cached) {
168
+ this.stats.cacheHits++;
169
+ cached.lastUsed = Date.now();
170
+ cached.hitCount++;
171
+
172
+ // Record cache hit metrics
173
+ if (this.config.enableOTEL) {
174
+ this.cacheHitCounter.add(1, { 'query.type': type });
175
+ span.setAttribute('cache.hit', true);
176
+ span.setAttribute('cache.hitCount', cached.hitCount);
177
+ }
178
+
179
+ span?.end();
180
+ return cached;
181
+ }
182
+ this.stats.cacheMisses++;
183
+
184
+ // Record cache miss metrics
185
+ if (this.config.enableOTEL) {
186
+ this.cacheMissCounter.add(1, { 'query.type': type });
187
+ span.setAttribute('cache.hit', false);
188
+ }
189
+ }
190
+
191
+ // Create new query plan
192
+ const plan = await this._createQueryPlan(query, type, queryHash, graph, delta);
193
+
194
+ // Cache the plan using LRU cache
195
+ if (this.config.enableCaching) {
196
+ this.cache.set(queryHash, plan);
197
+ }
198
+
199
+ const duration = Date.now() - startTime;
200
+
201
+ // Record optimization metrics
202
+ if (this.config.enableOTEL) {
203
+ this.queryOptimizationDuration.record(duration, { 'query.type': type });
204
+ span.setAttribute('optimization.duration', duration);
205
+ span.setAttribute('plan.estimatedCost', plan.plan.estimatedCost);
206
+ span.setStatus({ code: SpanStatusCode.OK });
207
+ }
208
+
209
+ span?.end();
210
+ return plan;
211
+ } catch (error) {
212
+ if (span) {
213
+ span.recordException(error);
214
+ span.setStatus({
215
+ code: SpanStatusCode.ERROR,
216
+ message: error.message,
217
+ });
218
+ span.end();
219
+ }
220
+ throw error;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Execute optimized query
226
+ * @param {Object} plan - Query plan
227
+ * @param {Store} graph - RDF graph
228
+ * @param {Object} [delta] - Delta for delta-aware execution
229
+ * @returns {Promise<any>} Query result
230
+ */
231
+ async executeOptimizedQuery(plan, graph, delta = null) {
232
+ if (this.config.enableDeltaAware && delta) {
233
+ return this._executeDeltaAware(plan, graph, delta);
234
+ }
235
+
236
+ return this._executeStandard(plan, graph);
237
+ }
238
+
239
+ /**
240
+ * Create indexes for a graph
241
+ * @param {Store} graph - RDF graph
242
+ * @returns {Promise<Array>} Created indexes
243
+ */
244
+ async createIndexes(graph) {
245
+ if (!this.config.enableIndexing) {
246
+ return [];
247
+ }
248
+
249
+ const indexes = [];
250
+ const quads = graph.getQuads();
251
+
252
+ // Create predicate index
253
+ const predicateIndex = this._createPredicateIndex(quads);
254
+ indexes.push(predicateIndex);
255
+
256
+ // Create subject index
257
+ const subjectIndex = this._createSubjectIndex(quads);
258
+ indexes.push(subjectIndex);
259
+
260
+ // Create object index
261
+ const objectIndex = this._createObjectIndex(quads);
262
+ indexes.push(objectIndex);
263
+
264
+ // Create composite indexes for common patterns
265
+ const compositeIndexes = this._createCompositeIndexes(quads);
266
+ indexes.push(...compositeIndexes);
267
+
268
+ // Store indexes
269
+ for (const index of indexes) {
270
+ this.indexes.set(index.id, index);
271
+ }
272
+
273
+ return indexes;
274
+ }
275
+
276
+ /**
277
+ * Update indexes with delta
278
+ * @param {Object} delta - Delta to apply
279
+ * @returns {Promise<void>}
280
+ */
281
+ async updateIndexes(delta) {
282
+ if (!this.config.enableIndexing) {
283
+ return;
284
+ }
285
+
286
+ // Update indexes with additions and removals
287
+ for (const [_indexId, index] of this.indexes) {
288
+ // Remove quads
289
+ for (const quad of delta.removals) {
290
+ this._removeFromIndex(index, quad);
291
+ }
292
+
293
+ // Add quads
294
+ for (const quad of delta.additions) {
295
+ this._addToIndex(index, quad);
296
+ }
297
+
298
+ index.lastUpdated = Date.now();
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Get optimizer statistics
304
+ * @returns {Object} Statistics
305
+ */
306
+ getStats() {
307
+ const cacheHitRate =
308
+ this.stats.totalQueries > 0 ? this.stats.cacheHits / this.stats.totalQueries : 0;
309
+
310
+ const indexHitRate =
311
+ this.stats.totalQueries > 0 ? this.stats.indexHits / this.stats.totalQueries : 0;
312
+
313
+ // Get LRU cache statistics
314
+ const lruStats = {
315
+ size: this.cache.size,
316
+ maxSize: this.config.maxCacheSize,
317
+ itemCount: this.cache.size,
318
+ // LRU cache provides automatic eviction tracking
319
+ calculatedSize: this.cache.calculatedSize || 0,
320
+ };
321
+
322
+ return {
323
+ config: this.config,
324
+ cache: {
325
+ ...lruStats,
326
+ hitRate: cacheHitRate,
327
+ hits: this.stats.cacheHits,
328
+ misses: this.stats.cacheMisses,
329
+ efficiency: cacheHitRate * 100, // Percentage
330
+ },
331
+ indexes: {
332
+ count: this.indexes.size,
333
+ hitRate: indexHitRate,
334
+ hits: this.stats.indexHits,
335
+ misses: this.stats.indexMisses,
336
+ },
337
+ optimization: {
338
+ deltaOptimizations: this.stats.deltaOptimizations,
339
+ totalQueries: this.stats.totalQueries,
340
+ averageCacheHitRate: cacheHitRate,
341
+ },
342
+ };
343
+ }
344
+
345
+ /**
346
+ * Clear all caches and indexes
347
+ */
348
+ clear() {
349
+ // Clear query plans with deep cleanup
350
+ for (const plan of this.queryPlans.values()) {
351
+ if (plan.plan && plan.plan.operations) {
352
+ plan.plan.operations.length = 0;
353
+ }
354
+ }
355
+ this.queryPlans.clear();
356
+
357
+ // Clear indexes with deep cleanup
358
+ for (const index of this.indexes.values()) {
359
+ if (index.data && typeof index.data.clear === 'function') {
360
+ index.data.clear();
361
+ }
362
+ }
363
+ this.indexes.clear();
364
+
365
+ // Clear cache
366
+ this.cache.clear();
367
+
368
+ // Reset stats
369
+ this.stats = {
370
+ cacheHits: 0,
371
+ cacheMisses: 0,
372
+ indexHits: 0,
373
+ indexMisses: 0,
374
+ deltaOptimizations: 0,
375
+ totalQueries: 0,
376
+ };
377
+ }
378
+
379
+ /**
380
+ * Cleanup query optimizer resources
381
+ */
382
+ async cleanup() {
383
+ this.clear();
384
+ }
385
+
386
+ /**
387
+ * Hash a query
388
+ * @param {string} query - Query string
389
+ * @param {string} type - Query type
390
+ * @returns {string} Query hash
391
+ * @private
392
+ */
393
+ _hashQuery(query, type) {
394
+ const content = `${type}:${query}`;
395
+ return bytesToHex(sha3_256(utf8ToBytes(content)));
396
+ }
397
+
398
+ /**
399
+ * Get cached query plan
400
+ * @param {string} queryHash - Query hash
401
+ * @returns {Object|null} Cached plan or null
402
+ * @private
403
+ * @deprecated Use LRU cache directly via this.cache.get()
404
+ */
405
+ _getCachedPlan(queryHash) {
406
+ return this.cache.get(queryHash) || null;
407
+ }
408
+
409
+ /**
410
+ * Cache a query plan
411
+ * @param {Object} plan - Query plan
412
+ * @private
413
+ * @deprecated Use LRU cache directly via this.cache.set()
414
+ */
415
+ _cachePlan(plan) {
416
+ // LRU cache handles eviction automatically
417
+ this.cache.set(plan.hash, plan);
418
+ }
419
+
420
+ /**
421
+ * Create a query plan
422
+ * @param {string} query - Query string
423
+ * @param {string} type - Query type
424
+ * @param {string} queryHash - Query hash
425
+ * @param {Store} graph - RDF graph
426
+ * @param {Object} [delta] - Delta for optimization
427
+ * @returns {Promise<Object>} Query plan
428
+ * @private
429
+ */
430
+ async _createQueryPlan(query, type, queryHash, graph, delta) {
431
+ const plan = {
432
+ id: randomUUID(),
433
+ query,
434
+ type,
435
+ hash: queryHash,
436
+ plan: {
437
+ operations: [],
438
+ estimatedCost: 0,
439
+ estimatedRows: 0,
440
+ indexes: [],
441
+ },
442
+ createdAt: Date.now(),
443
+ lastUsed: Date.now(),
444
+ hitCount: 0,
445
+ };
446
+
447
+ // Analyze query and create execution plan
448
+ switch (type) {
449
+ case 'sparql-ask':
450
+ plan.plan = await this._analyzeSparqlAsk(query, graph, delta);
451
+ break;
452
+ case 'sparql-select':
453
+ plan.plan = await this._analyzeSparqlSelect(query, graph, delta);
454
+ break;
455
+ case 'shacl':
456
+ plan.plan = await this._analyzeShacl(query, graph, delta);
457
+ break;
458
+ }
459
+
460
+ return plan;
461
+ }
462
+
463
+ /**
464
+ * Analyze SPARQL ASK query
465
+ * @param {string} query - Query string
466
+ * @param {Store} graph - RDF graph
467
+ * @param {Object} [delta] - Delta for optimization
468
+ * @returns {Promise<Object>} Execution plan
469
+ * @private
470
+ */
471
+ async _analyzeSparqlAsk(query, graph, delta) {
472
+ const plan = {
473
+ operations: [],
474
+ estimatedCost: 0,
475
+ estimatedRows: 0,
476
+ indexes: [],
477
+ };
478
+
479
+ // Simple analysis - in production this would be more sophisticated
480
+ const operations = this._parseSparqlOperations(query);
481
+
482
+ for (const op of operations) {
483
+ const cost = this._estimateOperationCost(op, graph);
484
+ const selectivity = this._estimateSelectivity(op, graph);
485
+
486
+ plan.operations.push({
487
+ type: op.type,
488
+ cost,
489
+ selectivity,
490
+ dependencies: op.dependencies,
491
+ });
492
+
493
+ plan.estimatedCost += cost;
494
+ }
495
+
496
+ // Add delta-aware optimizations
497
+ if (delta) {
498
+ plan.deltaAware = this._createDeltaAwarePlan(operations, delta);
499
+ this.stats.deltaOptimizations++;
500
+ }
501
+
502
+ return plan;
503
+ }
504
+
505
+ /**
506
+ * Analyze SPARQL SELECT query
507
+ * @param {string} query - Query string
508
+ * @param {Store} graph - RDF graph
509
+ * @param {Object} [delta] - Delta for optimization
510
+ * @returns {Promise<Object>} Execution plan
511
+ * @private
512
+ */
513
+ async _analyzeSparqlSelect(query, graph, delta) {
514
+ // Similar to ASK but with result estimation
515
+ const plan = await this._analyzeSparqlAsk(query, graph, delta);
516
+ plan.estimatedRows = this._estimateResultRows(query, graph);
517
+ return plan;
518
+ }
519
+
520
+ /**
521
+ * Analyze SHACL validation
522
+ * @param {string} query - SHACL shapes
523
+ * @param {Store} graph - RDF graph
524
+ * @param {Object} [delta] - Delta for optimization
525
+ * @returns {Promise<Object>} Execution plan
526
+ * @private
527
+ */
528
+ async _analyzeShacl(query, graph, _delta) {
529
+ const plan = {
530
+ operations: [],
531
+ estimatedCost: 0,
532
+ estimatedRows: 0,
533
+ indexes: [],
534
+ };
535
+
536
+ // SHACL-specific analysis
537
+ const shapes = this._parseShaclShapes(query);
538
+
539
+ for (const shape of shapes) {
540
+ const cost = this._estimateShaclCost(shape, graph);
541
+ plan.operations.push({
542
+ type: 'shacl-validation',
543
+ cost,
544
+ selectivity: 0.1, // SHACL typically has low selectivity
545
+ dependencies: [],
546
+ });
547
+
548
+ plan.estimatedCost += cost;
549
+ }
550
+
551
+ return plan;
552
+ }
553
+
554
+ /**
555
+ * Execute delta-aware query
556
+ * @param {Object} plan - Query plan
557
+ * @param {Store} graph - RDF graph
558
+ * @param {Object} delta - Delta
559
+ * @returns {Promise<any>} Query result
560
+ * @private
561
+ */
562
+ async _executeDeltaAware(plan, graph, delta) {
563
+ // Use delta information to optimize execution
564
+ const affectedEntities = this._extractAffectedEntities(delta);
565
+
566
+ // Check if query can be optimized based on delta
567
+ if (plan.plan.deltaAware) {
568
+ return this._executeOptimizedDeltaAware(plan, graph, delta, affectedEntities);
569
+ }
570
+
571
+ // Fall back to standard execution
572
+ return this._executeStandard(plan, graph);
573
+ }
574
+
575
+ /**
576
+ * Execute standard query
577
+ * @param {Object} plan - Query plan
578
+ * @param {Store} graph - RDF graph
579
+ * @returns {Promise<any>} Query result
580
+ * @private
581
+ */
582
+ async _executeStandard(_plan, _graph) {
583
+ // Standard query execution
584
+ // This would integrate with the actual query engine
585
+ return { result: 'standard execution' };
586
+ }
587
+
588
+ /**
589
+ * Create predicate index
590
+ * @param {Array} quads - RDF quads
591
+ * @returns {Object} Index
592
+ * @private
593
+ */
594
+ _createPredicateIndex(quads) {
595
+ const index = new Map();
596
+
597
+ for (const quad of quads) {
598
+ const predicate = quad.predicate.value;
599
+ if (!index.has(predicate)) {
600
+ index.set(predicate, []);
601
+ }
602
+ index.get(predicate).push(quad);
603
+ }
604
+
605
+ return {
606
+ id: randomUUID(),
607
+ name: 'predicate_index',
608
+ type: 'predicate',
609
+ fields: ['predicate'],
610
+ selectivity: this._calculateSelectivity(index),
611
+ size: index.size,
612
+ createdAt: Date.now(),
613
+ lastUpdated: Date.now(),
614
+ data: index,
615
+ };
616
+ }
617
+
618
+ /**
619
+ * Create subject index
620
+ * @param {Array} quads - RDF quads
621
+ * @returns {Object} Index
622
+ * @private
623
+ */
624
+ _createSubjectIndex(quads) {
625
+ const index = new Map();
626
+
627
+ for (const quad of quads) {
628
+ const subject = quad.subject.value;
629
+ if (!index.has(subject)) {
630
+ index.set(subject, []);
631
+ }
632
+ index.get(subject).push(quad);
633
+ }
634
+
635
+ return {
636
+ id: randomUUID(),
637
+ name: 'subject_index',
638
+ type: 'subject',
639
+ fields: ['subject'],
640
+ selectivity: this._calculateSelectivity(index),
641
+ size: index.size,
642
+ createdAt: Date.now(),
643
+ lastUpdated: Date.now(),
644
+ data: index,
645
+ };
646
+ }
647
+
648
+ /**
649
+ * Create object index
650
+ * @param {Array} quads - RDF quads
651
+ * @returns {Object} Index
652
+ * @private
653
+ */
654
+ _createObjectIndex(quads) {
655
+ const index = new Map();
656
+
657
+ for (const quad of quads) {
658
+ const object = quad.object.value;
659
+ if (!index.has(object)) {
660
+ index.set(object, []);
661
+ }
662
+ index.get(object).push(quad);
663
+ }
664
+
665
+ return {
666
+ id: randomUUID(),
667
+ name: 'object_index',
668
+ type: 'object',
669
+ fields: ['object'],
670
+ selectivity: this._calculateSelectivity(index),
671
+ size: index.size,
672
+ createdAt: Date.now(),
673
+ lastUpdated: Date.now(),
674
+ data: index,
675
+ };
676
+ }
677
+
678
+ /**
679
+ * Create composite indexes
680
+ * @param {Array} quads - RDF quads
681
+ * @returns {Array} Indexes
682
+ * @private
683
+ */
684
+ _createCompositeIndexes(quads) {
685
+ const indexes = [];
686
+
687
+ // SPO index (most common pattern)
688
+ const spoIndex = new Map();
689
+ for (const quad of quads) {
690
+ const key = `${quad.subject.value}:${quad.predicate.value}:${quad.object.value}`;
691
+ if (!spoIndex.has(key)) {
692
+ spoIndex.set(key, []);
693
+ }
694
+ spoIndex.get(key).push(quad);
695
+ }
696
+
697
+ indexes.push({
698
+ id: randomUUID(),
699
+ name: 'spo_index',
700
+ type: 'composite',
701
+ fields: ['subject', 'predicate', 'object'],
702
+ selectivity: this._calculateSelectivity(spoIndex),
703
+ size: spoIndex.size,
704
+ createdAt: Date.now(),
705
+ lastUpdated: Date.now(),
706
+ data: spoIndex,
707
+ });
708
+
709
+ return indexes;
710
+ }
711
+
712
+ /**
713
+ * Calculate index selectivity
714
+ * @param {Map} index - Index data
715
+ * @returns {number} Selectivity
716
+ * @private
717
+ */
718
+ _calculateSelectivity(index) {
719
+ const totalEntries = Array.from(index.values()).reduce(
720
+ (sum, entries) => sum + entries.length,
721
+ 0
722
+ );
723
+ const uniqueKeys = index.size;
724
+ return uniqueKeys / totalEntries;
725
+ }
726
+
727
+ /**
728
+ * Parse SPARQL operations
729
+ * @param {string} query - SPARQL query
730
+ * @returns {Array} Operations
731
+ * @private
732
+ */
733
+ _parseSparqlOperations(query) {
734
+ // Simple parsing - in production this would use a proper SPARQL parser
735
+ const operations = [];
736
+
737
+ if (query.includes('WHERE')) {
738
+ operations.push({
739
+ type: 'where-clause',
740
+ cost: 100,
741
+ dependencies: [],
742
+ });
743
+ }
744
+
745
+ if (query.includes('FILTER')) {
746
+ operations.push({
747
+ type: 'filter',
748
+ cost: 50,
749
+ dependencies: ['where-clause'],
750
+ });
751
+ }
752
+
753
+ return operations;
754
+ }
755
+
756
+ /**
757
+ * Estimate operation cost
758
+ * @param {Object} operation - Operation
759
+ * @param {Store} graph - RDF graph
760
+ * @returns {number} Estimated cost
761
+ * @private
762
+ */
763
+ _estimateOperationCost(operation, graph) {
764
+ const baseCost = operation.cost || 100;
765
+ const graphSize = graph.size;
766
+ return baseCost * Math.log(graphSize + 1);
767
+ }
768
+
769
+ /**
770
+ * Estimate operation selectivity
771
+ * @param {Object} operation - Operation
772
+ * @param {Store} graph - RDF graph
773
+ * @returns {number} Estimated selectivity
774
+ * @private
775
+ */
776
+ _estimateSelectivity(operation, _graph) {
777
+ // Simple estimation - in production this would be more sophisticated
778
+ switch (operation.type) {
779
+ case 'where-clause':
780
+ return 0.1;
781
+ case 'filter':
782
+ return 0.5;
783
+ default:
784
+ return 0.3;
785
+ }
786
+ }
787
+
788
+ /**
789
+ * Create delta-aware plan
790
+ * @param {Array} operations - Operations
791
+ * @param {Object} delta - Delta
792
+ * @returns {Object} Delta-aware plan
793
+ * @private
794
+ */
795
+ _createDeltaAwarePlan(operations, delta) {
796
+ return {
797
+ affectedEntities: this._extractAffectedEntities(delta),
798
+ optimizedOperations: operations.filter(op => this._isOperationAffected(op, delta)),
799
+ skipFullScan: delta.additions.length + delta.removals.length < 100,
800
+ };
801
+ }
802
+
803
+ /**
804
+ * Extract affected entities from delta
805
+ * @param {Object} delta - Delta
806
+ * @returns {Object} Affected entities
807
+ * @private
808
+ */
809
+ _extractAffectedEntities(delta) {
810
+ const affected = {
811
+ subjects: new Set(),
812
+ predicates: new Set(),
813
+ objects: new Set(),
814
+ graphs: new Set(),
815
+ };
816
+
817
+ for (const quad of [...delta.additions, ...delta.removals]) {
818
+ affected.subjects.add(quad.subject.value);
819
+ affected.predicates.add(quad.predicate.value);
820
+ affected.objects.add(quad.object.value);
821
+ if (quad.graph) {
822
+ affected.graphs.add(quad.graph.value);
823
+ }
824
+ }
825
+
826
+ return affected;
827
+ }
828
+
829
+ /**
830
+ * Check if operation is affected by delta
831
+ * @param {Object} operation - Operation
832
+ * @param {Object} delta - Delta
833
+ * @returns {boolean} Is affected
834
+ * @private
835
+ */
836
+ _isOperationAffected(_operation, _delta) {
837
+ // Simple check - in production this would analyze the operation
838
+ return true;
839
+ }
840
+
841
+ /**
842
+ * Estimate result rows
843
+ * @param {string} query - Query
844
+ * @param {Store} graph - RDF graph
845
+ * @returns {number} Estimated rows
846
+ * @private
847
+ */
848
+ _estimateResultRows(query, graph) {
849
+ // Simple estimation
850
+ return Math.min(graph.size * 0.1, 1000);
851
+ }
852
+
853
+ /**
854
+ * Estimate SHACL cost
855
+ * @param {Object} shape - SHACL shape
856
+ * @param {Store} graph - RDF graph
857
+ * @returns {number} Estimated cost
858
+ * @private
859
+ */
860
+ _estimateShaclCost(shape, graph) {
861
+ return graph.size * 0.5; // SHACL validation is expensive
862
+ }
863
+
864
+ /**
865
+ * Parse SHACL shapes
866
+ * @param {string} query - SHACL shapes
867
+ * @returns {Array} Shapes
868
+ * @private
869
+ */
870
+ _parseShaclShapes(_query) {
871
+ // Simple parsing - in production this would use a proper SHACL parser
872
+ return [{ type: 'shacl-shape', cost: 100 }];
873
+ }
874
+
875
+ /**
876
+ * Add quad to index
877
+ * @param {Object} index - Index
878
+ * @param {Object} quad - RDF quad
879
+ * @private
880
+ */
881
+ _addToIndex(index, quad) {
882
+ if (!index.data || !(index.data instanceof Map)) {
883
+ return;
884
+ }
885
+
886
+ const key = this._getIndexKey(index, quad);
887
+ if (!key) {
888
+ return;
889
+ }
890
+
891
+ if (!index.data.has(key)) {
892
+ index.data.set(key, []);
893
+ }
894
+
895
+ const quads = index.data.get(key);
896
+ // Check if quad already exists to avoid duplicates
897
+ const exists = quads.some(
898
+ q =>
899
+ q.subject.value === quad.subject.value &&
900
+ q.predicate.value === quad.predicate.value &&
901
+ q.object.value === quad.object.value &&
902
+ (q.graph?.value || null) === (quad.graph?.value || null)
903
+ );
904
+
905
+ if (!exists) {
906
+ quads.push(quad);
907
+ index.size = index.data.size;
908
+ index.lastUpdated = Date.now();
909
+ }
910
+ }
911
+
912
+ /**
913
+ * Remove quad from index
914
+ * @param {Object} index - Index
915
+ * @param {Object} quad - RDF quad
916
+ * @private
917
+ */
918
+ _removeFromIndex(index, quad) {
919
+ if (!index.data || !(index.data instanceof Map)) {
920
+ return;
921
+ }
922
+
923
+ const key = this._getIndexKey(index, quad);
924
+ if (!key || !index.data.has(key)) {
925
+ return;
926
+ }
927
+
928
+ const quads = index.data.get(key);
929
+ const indexToRemove = quads.findIndex(
930
+ q =>
931
+ q.subject.value === quad.subject.value &&
932
+ q.predicate.value === quad.predicate.value &&
933
+ q.object.value === quad.object.value &&
934
+ (q.graph?.value || null) === (quad.graph?.value || null)
935
+ );
936
+
937
+ if (indexToRemove !== -1) {
938
+ quads.splice(indexToRemove, 1);
939
+ if (quads.length === 0) {
940
+ index.data.delete(key);
941
+ }
942
+ index.size = index.data.size;
943
+ index.lastUpdated = Date.now();
944
+ }
945
+ }
946
+
947
+ /**
948
+ * Get index key for a quad based on index type
949
+ * @param {Object} index - Index
950
+ * @param {Object} quad - RDF quad
951
+ * @returns {string|null} Index key
952
+ * @private
953
+ */
954
+ _getIndexKey(index, quad) {
955
+ switch (index.type) {
956
+ case 'predicate':
957
+ return quad.predicate.value;
958
+ case 'subject':
959
+ return quad.subject.value;
960
+ case 'object':
961
+ return quad.object.value;
962
+ case 'graph':
963
+ return quad.graph?.value || null;
964
+ case 'composite':
965
+ // For composite indexes, use the first field
966
+ if (index.fields && index.fields.length > 0) {
967
+ const field = index.fields[0];
968
+ if (field === 'subject') return quad.subject.value;
969
+ if (field === 'predicate') return quad.predicate.value;
970
+ if (field === 'object') return quad.object.value;
971
+ if (field === 'graph') return quad.graph?.value || null;
972
+ }
973
+ return null;
974
+ default:
975
+ return null;
976
+ }
977
+ }
978
+
979
+ /**
980
+ * Execute optimized delta-aware query
981
+ * @param {Object} plan - Query plan
982
+ * @param {Store} graph - RDF graph
983
+ * @param {Object} delta - Delta
984
+ * @param {Object} affectedEntities - Affected entities
985
+ * @returns {Promise<any>} Query result
986
+ * @private
987
+ */
988
+ async _executeOptimizedDeltaAware(plan, graph, delta, affectedEntities) {
989
+ // Use delta information to optimize execution
990
+ // Only re-execute if affected entities match query patterns
991
+ // Use provided affectedEntities if available, otherwise compute from delta
992
+ const affectedSubjects = affectedEntities?.subjects
993
+ ? new Set(affectedEntities.subjects)
994
+ : new Set(
995
+ delta.additions
996
+ .concat(delta.removals)
997
+ .map(q => q.subject?.value)
998
+ .filter(Boolean)
999
+ );
1000
+
1001
+ // Check if query would be affected by delta
1002
+ const queryAffected = plan.plan.operations.some(op => {
1003
+ // Simple heuristic: if operation references affected subjects
1004
+ if (op.patterns) {
1005
+ return op.patterns.some(pattern => {
1006
+ if (pattern.subject && affectedSubjects.has(pattern.subject)) {
1007
+ return true;
1008
+ }
1009
+ return false;
1010
+ });
1011
+ }
1012
+ return false;
1013
+ });
1014
+
1015
+ if (!queryAffected && delta.additions.length === 0 && delta.removals.length === 0) {
1016
+ // No changes, return cached result if available
1017
+ this.stats.deltaOptimizations++;
1018
+ return { result: 'unchanged', optimized: true };
1019
+ }
1020
+
1021
+ // Execute query with delta-aware optimization
1022
+ // For removals, filter them out; for additions, include them
1023
+ const result = await this._executeStandard(plan, graph);
1024
+
1025
+ // Mark as delta-optimized
1026
+ this.stats.deltaOptimizations++;
1027
+
1028
+ return {
1029
+ ...result,
1030
+ optimized: true,
1031
+ deltaApplied: {
1032
+ additions: delta.additions.length,
1033
+ removals: delta.removals.length,
1034
+ },
1035
+ };
1036
+ }
1037
+ }
1038
+
1039
+ /**
1040
+ * Create a query optimizer instance
1041
+ * @param {Object} [config] - Configuration
1042
+ * @returns {QueryOptimizer} Query optimizer
1043
+ */
1044
+ export function createQueryOptimizer(config = {}) {
1045
+ return new QueryOptimizer(config);
1046
+ }
1047
+
1048
+ /**
1049
+ * Default query optimizer instance
1050
+ */
1051
+ export const defaultQueryOptimizer = createQueryOptimizer();