@unrdf/kgc-runtime 26.4.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 (70) hide show
  1. package/IMPLEMENTATION_SUMMARY.json +150 -0
  2. package/PLUGIN_SYSTEM_SUMMARY.json +149 -0
  3. package/README.md +98 -0
  4. package/TRANSACTION_IMPLEMENTATION.json +119 -0
  5. package/capability-map.md +93 -0
  6. package/docs/api-stability.md +269 -0
  7. package/docs/extensions/plugin-development.md +382 -0
  8. package/package.json +40 -0
  9. package/plugins/registry.json +35 -0
  10. package/src/admission-gate.mjs +414 -0
  11. package/src/api-version.mjs +373 -0
  12. package/src/atomic-admission.mjs +310 -0
  13. package/src/bounds.mjs +289 -0
  14. package/src/bulkhead-manager.mjs +280 -0
  15. package/src/capsule.mjs +524 -0
  16. package/src/crdt.mjs +361 -0
  17. package/src/enhanced-bounds.mjs +614 -0
  18. package/src/executor.mjs +73 -0
  19. package/src/freeze-restore.mjs +521 -0
  20. package/src/index.mjs +62 -0
  21. package/src/materialized-views.mjs +371 -0
  22. package/src/merge.mjs +472 -0
  23. package/src/plugin-isolation.mjs +392 -0
  24. package/src/plugin-manager.mjs +441 -0
  25. package/src/projections-api.mjs +336 -0
  26. package/src/projections-cli.mjs +238 -0
  27. package/src/projections-docs.mjs +300 -0
  28. package/src/projections-ide.mjs +278 -0
  29. package/src/receipt.mjs +340 -0
  30. package/src/rollback.mjs +258 -0
  31. package/src/saga-orchestrator.mjs +355 -0
  32. package/src/schemas.mjs +1330 -0
  33. package/src/storage-optimization.mjs +359 -0
  34. package/src/tool-registry.mjs +272 -0
  35. package/src/transaction.mjs +466 -0
  36. package/src/validators.mjs +485 -0
  37. package/src/work-item.mjs +449 -0
  38. package/templates/plugin-template/README.md +58 -0
  39. package/templates/plugin-template/index.mjs +162 -0
  40. package/templates/plugin-template/plugin.json +19 -0
  41. package/test/admission-gate.test.mjs +583 -0
  42. package/test/api-version.test.mjs +74 -0
  43. package/test/atomic-admission.test.mjs +155 -0
  44. package/test/bounds.test.mjs +341 -0
  45. package/test/bulkhead-manager.test.mjs +236 -0
  46. package/test/capsule.test.mjs +625 -0
  47. package/test/crdt.test.mjs +215 -0
  48. package/test/enhanced-bounds.test.mjs +487 -0
  49. package/test/freeze-restore.test.mjs +472 -0
  50. package/test/materialized-views.test.mjs +243 -0
  51. package/test/merge.test.mjs +665 -0
  52. package/test/plugin-isolation.test.mjs +109 -0
  53. package/test/plugin-manager.test.mjs +208 -0
  54. package/test/projections-api.test.mjs +293 -0
  55. package/test/projections-cli.test.mjs +204 -0
  56. package/test/projections-docs.test.mjs +173 -0
  57. package/test/projections-ide.test.mjs +230 -0
  58. package/test/receipt.test.mjs +295 -0
  59. package/test/rollback.test.mjs +132 -0
  60. package/test/saga-orchestrator.test.mjs +279 -0
  61. package/test/schemas.test.mjs +716 -0
  62. package/test/storage-optimization.test.mjs +503 -0
  63. package/test/tool-registry.test.mjs +341 -0
  64. package/test/transaction.test.mjs +189 -0
  65. package/test/validators.test.mjs +463 -0
  66. package/test/work-item.test.mjs +548 -0
  67. package/test/work-item.test.mjs.bak +548 -0
  68. package/var/kgc/test-atomic-log.json +519 -0
  69. package/var/kgc/test-cascading-log.json +145 -0
  70. package/vitest.config.mjs +18 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Tests for Materialized Views - Cached SPARQL Results
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach } from 'vitest';
6
+ import {
7
+ ViewCache,
8
+ createAutoCleanupCache,
9
+ MaterializedViewSchema,
10
+ } from '../src/materialized-views.mjs';
11
+
12
+ describe('Materialized Views - Cached SPARQL Results', () => {
13
+ describe('ViewCache', () => {
14
+ let cache;
15
+
16
+ beforeEach(() => {
17
+ cache = new ViewCache({
18
+ defaultTTL: 1000, // 1 second for testing
19
+ maxSize: 5,
20
+ evictionPolicy: 'lru',
21
+ });
22
+ });
23
+
24
+ it('should materialize view with projection', () => {
25
+ const view = cache.materialize(
26
+ 'view-001',
27
+ 'SELECT * WHERE { ?s ?p ?o }',
28
+ { results: [{ s: 'a', p: 'b', o: 'c' }] }
29
+ );
30
+
31
+ expect(view.id).toBe('view-001');
32
+ expect(view.query).toContain('SELECT');
33
+ expect(view.projection.results).toHaveLength(1);
34
+ expect(view.hits).toBe(0);
35
+
36
+ // Validate schema
37
+ MaterializedViewSchema.parse(view);
38
+ });
39
+
40
+ it('should get materialized view and track hits', () => {
41
+ cache.materialize('view-001', 'SELECT', { data: 'test' });
42
+
43
+ const view1 = cache.get('view-001');
44
+ const view2 = cache.get('view-001');
45
+
46
+ expect(view1).toBeDefined();
47
+ expect(view1.hits).toBe(1);
48
+ expect(view2.hits).toBe(2);
49
+ expect(view2.lastAccessedAt).toBeDefined();
50
+ });
51
+
52
+ it('should return null for expired views', async () => {
53
+ cache.materialize('view-001', 'SELECT', { data: 'test' }, { ttl: 50 });
54
+
55
+ // Wait for expiration
56
+ await new Promise(resolve => setTimeout(resolve, 100));
57
+
58
+ const view = cache.get('view-001');
59
+
60
+ expect(view).toBeNull();
61
+ });
62
+
63
+ it('should get projection directly', () => {
64
+ cache.materialize('view-001', 'SELECT', { result: 'data' });
65
+
66
+ const projection = cache.getProjection('view-001');
67
+
68
+ expect(projection).toEqual({ result: 'data' });
69
+ });
70
+
71
+ it('should invalidate view by ID', () => {
72
+ cache.materialize('view-001', 'SELECT', { data: 'test' });
73
+
74
+ const invalidated = cache.invalidate('view-001');
75
+
76
+ expect(invalidated).toBe(true);
77
+ expect(cache.get('view-001')).toBeNull();
78
+ });
79
+
80
+ it('should invalidate views by resource dependency', () => {
81
+ cache.materialize('view-001', 'SELECT', { data: 'a' }, {
82
+ dependsOn: ['resource-1', 'resource-2'],
83
+ });
84
+
85
+ cache.materialize('view-002', 'SELECT', { data: 'b' }, {
86
+ dependsOn: ['resource-1'],
87
+ });
88
+
89
+ cache.materialize('view-003', 'SELECT', { data: 'c' }, {
90
+ dependsOn: ['resource-3'],
91
+ });
92
+
93
+ const count = cache.invalidateByResource('resource-1');
94
+
95
+ expect(count).toBe(2); // view-001 and view-002
96
+ expect(cache.get('view-001')).toBeNull();
97
+ expect(cache.get('view-002')).toBeNull();
98
+ expect(cache.get('view-003')).toBeDefined(); // Not invalidated
99
+ });
100
+
101
+ it('should invalidate all views', () => {
102
+ cache.materialize('view-001', 'SELECT', { data: '1' });
103
+ cache.materialize('view-002', 'SELECT', { data: '2' });
104
+ cache.materialize('view-003', 'SELECT', { data: '3' });
105
+
106
+ const count = cache.invalidateAll();
107
+
108
+ expect(count).toBe(3);
109
+ expect(cache.get('view-001')).toBeNull();
110
+ });
111
+
112
+ it('should refresh view with new projection', () => {
113
+ cache.materialize('view-001', 'SELECT', { data: 'old' });
114
+
115
+ const refreshed = cache.refresh('view-001', { data: 'new' });
116
+
117
+ expect(refreshed).toBeDefined();
118
+ expect(refreshed.projection.data).toBe('new');
119
+
120
+ const view = cache.get('view-001');
121
+ expect(view.projection.data).toBe('new');
122
+ });
123
+
124
+ it('should evict LRU when at capacity', () => {
125
+ // Fill cache to capacity (5)
126
+ for (let i = 1; i <= 5; i++) {
127
+ cache.materialize(`view-${i}`, 'SELECT', { data: i });
128
+ }
129
+
130
+ // Access view-1 and view-2 to make them recently used
131
+ cache.get('view-1');
132
+ cache.get('view-2');
133
+
134
+ // Add new view (should evict least recently used, not view-1 or view-2)
135
+ cache.materialize('view-6', 'SELECT', { data: 6 });
136
+
137
+ expect(cache.get('view-1')).toBeDefined();
138
+ expect(cache.get('view-2')).toBeDefined();
139
+ expect(cache.get('view-3')).toBeNull(); // Evicted (LRU)
140
+ });
141
+
142
+ it('should evict LFU when configured', () => {
143
+ const lfuCache = new ViewCache({
144
+ maxSize: 3,
145
+ evictionPolicy: 'lfu',
146
+ });
147
+
148
+ lfuCache.materialize('view-1', 'SELECT', { data: 1 });
149
+ lfuCache.materialize('view-2', 'SELECT', { data: 2 });
150
+ lfuCache.materialize('view-3', 'SELECT', { data: 3 });
151
+
152
+ // Access view-1 and view-2 multiple times
153
+ lfuCache.get('view-1');
154
+ lfuCache.get('view-1');
155
+ lfuCache.get('view-2');
156
+
157
+ // Add new view (should evict view-3 with lowest hits)
158
+ lfuCache.materialize('view-4', 'SELECT', { data: 4 });
159
+
160
+ expect(lfuCache.get('view-1')).toBeDefined();
161
+ expect(lfuCache.get('view-2')).toBeDefined();
162
+ expect(lfuCache.get('view-3')).toBeNull(); // Evicted (LFU)
163
+ expect(lfuCache.get('view-4')).toBeDefined();
164
+ });
165
+
166
+ it('should evict FIFO when configured', () => {
167
+ const fifoCache = new ViewCache({
168
+ maxSize: 3,
169
+ evictionPolicy: 'fifo',
170
+ });
171
+
172
+ fifoCache.materialize('view-1', 'SELECT', { data: 1 });
173
+ fifoCache.materialize('view-2', 'SELECT', { data: 2 });
174
+ fifoCache.materialize('view-3', 'SELECT', { data: 3 });
175
+
176
+ // Add new view (should evict view-1, first in)
177
+ fifoCache.materialize('view-4', 'SELECT', { data: 4 });
178
+
179
+ expect(fifoCache.get('view-1')).toBeNull(); // Evicted (FIFO)
180
+ expect(fifoCache.get('view-2')).toBeDefined();
181
+ });
182
+
183
+ it('should cleanup expired views', async () => {
184
+ cache.materialize('view-1', 'SELECT', { data: 1 }, { ttl: 50 });
185
+ cache.materialize('view-2', 'SELECT', { data: 2 }, { ttl: 200 });
186
+
187
+ // Wait for view-1 to expire
188
+ await new Promise(resolve => setTimeout(resolve, 100));
189
+
190
+ const cleaned = cache.cleanupExpired();
191
+
192
+ expect(cleaned).toBe(1);
193
+ expect(cache.get('view-1')).toBeNull();
194
+ expect(cache.get('view-2')).toBeDefined();
195
+ });
196
+
197
+ it('should provide cache statistics', () => {
198
+ cache.materialize('view-1', 'SELECT', { data: 1 });
199
+ cache.materialize('view-2', 'SELECT', { data: 2 });
200
+ cache.get('view-1');
201
+ cache.get('view-1');
202
+
203
+ const stats = cache.getStats();
204
+
205
+ expect(stats.size).toBe(2);
206
+ expect(stats.maxSize).toBe(5);
207
+ expect(stats.totalHits).toBe(2);
208
+ expect(stats.evictionPolicy).toBe('lru');
209
+ });
210
+
211
+ it('should generate receipt for cache operations', async () => {
212
+ cache.materialize('view-1', 'SELECT', { data: 1 });
213
+
214
+ const receipt = await cache.generateReceipt(
215
+ 'materialize',
216
+ { viewId: 'view-1' },
217
+ { success: true }
218
+ );
219
+
220
+ expect(receipt.operation).toContain('view-cache:materialize');
221
+ expect(receipt.outputs.stats).toBeDefined();
222
+ });
223
+ });
224
+
225
+ describe('createAutoCleanupCache', () => {
226
+ it('should create cache with auto-cleanup', async () => {
227
+ const autoCache = createAutoCleanupCache(
228
+ { defaultTTL: 50, maxSize: 10 },
229
+ 100 // Cleanup every 100ms
230
+ );
231
+
232
+ autoCache.materialize('view-1', 'SELECT', { data: 1 }, { ttl: 50 });
233
+
234
+ // Wait for cleanup to run
235
+ await new Promise(resolve => setTimeout(resolve, 150));
236
+
237
+ expect(autoCache.get('view-1')).toBeNull();
238
+
239
+ // Stop cleanup
240
+ autoCache.stopAutoCleanup();
241
+ });
242
+ });
243
+ });