@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.
- package/IMPLEMENTATION_SUMMARY.json +150 -0
- package/PLUGIN_SYSTEM_SUMMARY.json +149 -0
- package/README.md +98 -0
- package/TRANSACTION_IMPLEMENTATION.json +119 -0
- package/capability-map.md +93 -0
- package/docs/api-stability.md +269 -0
- package/docs/extensions/plugin-development.md +382 -0
- package/package.json +40 -0
- package/plugins/registry.json +35 -0
- package/src/admission-gate.mjs +414 -0
- package/src/api-version.mjs +373 -0
- package/src/atomic-admission.mjs +310 -0
- package/src/bounds.mjs +289 -0
- package/src/bulkhead-manager.mjs +280 -0
- package/src/capsule.mjs +524 -0
- package/src/crdt.mjs +361 -0
- package/src/enhanced-bounds.mjs +614 -0
- package/src/executor.mjs +73 -0
- package/src/freeze-restore.mjs +521 -0
- package/src/index.mjs +62 -0
- package/src/materialized-views.mjs +371 -0
- package/src/merge.mjs +472 -0
- package/src/plugin-isolation.mjs +392 -0
- package/src/plugin-manager.mjs +441 -0
- package/src/projections-api.mjs +336 -0
- package/src/projections-cli.mjs +238 -0
- package/src/projections-docs.mjs +300 -0
- package/src/projections-ide.mjs +278 -0
- package/src/receipt.mjs +340 -0
- package/src/rollback.mjs +258 -0
- package/src/saga-orchestrator.mjs +355 -0
- package/src/schemas.mjs +1330 -0
- package/src/storage-optimization.mjs +359 -0
- package/src/tool-registry.mjs +272 -0
- package/src/transaction.mjs +466 -0
- package/src/validators.mjs +485 -0
- package/src/work-item.mjs +449 -0
- package/templates/plugin-template/README.md +58 -0
- package/templates/plugin-template/index.mjs +162 -0
- package/templates/plugin-template/plugin.json +19 -0
- package/test/admission-gate.test.mjs +583 -0
- package/test/api-version.test.mjs +74 -0
- package/test/atomic-admission.test.mjs +155 -0
- package/test/bounds.test.mjs +341 -0
- package/test/bulkhead-manager.test.mjs +236 -0
- package/test/capsule.test.mjs +625 -0
- package/test/crdt.test.mjs +215 -0
- package/test/enhanced-bounds.test.mjs +487 -0
- package/test/freeze-restore.test.mjs +472 -0
- package/test/materialized-views.test.mjs +243 -0
- package/test/merge.test.mjs +665 -0
- package/test/plugin-isolation.test.mjs +109 -0
- package/test/plugin-manager.test.mjs +208 -0
- package/test/projections-api.test.mjs +293 -0
- package/test/projections-cli.test.mjs +204 -0
- package/test/projections-docs.test.mjs +173 -0
- package/test/projections-ide.test.mjs +230 -0
- package/test/receipt.test.mjs +295 -0
- package/test/rollback.test.mjs +132 -0
- package/test/saga-orchestrator.test.mjs +279 -0
- package/test/schemas.test.mjs +716 -0
- package/test/storage-optimization.test.mjs +503 -0
- package/test/tool-registry.test.mjs +341 -0
- package/test/transaction.test.mjs +189 -0
- package/test/validators.test.mjs +463 -0
- package/test/work-item.test.mjs +548 -0
- package/test/work-item.test.mjs.bak +548 -0
- package/var/kgc/test-atomic-log.json +519 -0
- package/var/kgc/test-cascading-log.json +145 -0
- 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
|
+
});
|