@qwickapps/qwickbrain-proxy 1.0.2 → 1.0.3
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/.claude/engineering/bugs/BUG-qwickbrain-proxy-cache-and-design.md +840 -0
- package/.github/workflows/publish.yml +13 -0
- package/CHANGELOG.md +17 -0
- package/dist/db/schema.d.ts +63 -6
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +17 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/lib/__tests__/cache-manager.test.js +146 -83
- package/dist/lib/__tests__/cache-manager.test.js.map +1 -1
- package/dist/lib/__tests__/proxy-server.test.js +16 -44
- package/dist/lib/__tests__/proxy-server.test.js.map +1 -1
- package/dist/lib/__tests__/sse-invalidation-listener.test.d.ts +2 -0
- package/dist/lib/__tests__/sse-invalidation-listener.test.d.ts.map +1 -0
- package/dist/lib/__tests__/sse-invalidation-listener.test.js +245 -0
- package/dist/lib/__tests__/sse-invalidation-listener.test.js.map +1 -0
- package/dist/lib/__tests__/write-queue-manager.test.d.ts +2 -0
- package/dist/lib/__tests__/write-queue-manager.test.d.ts.map +1 -0
- package/dist/lib/__tests__/write-queue-manager.test.js +291 -0
- package/dist/lib/__tests__/write-queue-manager.test.js.map +1 -0
- package/dist/lib/cache-manager.d.ts +35 -6
- package/dist/lib/cache-manager.d.ts.map +1 -1
- package/dist/lib/cache-manager.js +154 -41
- package/dist/lib/cache-manager.js.map +1 -1
- package/dist/lib/proxy-server.d.ts +5 -0
- package/dist/lib/proxy-server.d.ts.map +1 -1
- package/dist/lib/proxy-server.js +150 -84
- package/dist/lib/proxy-server.js.map +1 -1
- package/dist/lib/qwickbrain-client.d.ts +4 -0
- package/dist/lib/qwickbrain-client.d.ts.map +1 -1
- package/dist/lib/qwickbrain-client.js +131 -2
- package/dist/lib/qwickbrain-client.js.map +1 -1
- package/dist/lib/sse-invalidation-listener.d.ts +27 -0
- package/dist/lib/sse-invalidation-listener.d.ts.map +1 -0
- package/dist/lib/sse-invalidation-listener.js +145 -0
- package/dist/lib/sse-invalidation-listener.js.map +1 -0
- package/dist/lib/tools.d.ts +21 -0
- package/dist/lib/tools.d.ts.map +1 -0
- package/dist/lib/tools.js +488 -0
- package/dist/lib/tools.js.map +1 -0
- package/dist/lib/write-queue-manager.d.ts +88 -0
- package/dist/lib/write-queue-manager.d.ts.map +1 -0
- package/dist/lib/write-queue-manager.js +191 -0
- package/dist/lib/write-queue-manager.js.map +1 -0
- package/dist/types/config.d.ts +7 -42
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +1 -6
- package/dist/types/config.js.map +1 -1
- package/drizzle/0002_lru_cache_migration.sql +94 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +6 -2
- package/scripts/rebuild-sqlite.sh +26 -0
- package/src/db/schema.ts +17 -2
- package/src/lib/__tests__/cache-manager.test.ts +180 -90
- package/src/lib/__tests__/proxy-server.test.ts +16 -51
- package/src/lib/__tests__/sse-invalidation-listener.test.ts +326 -0
- package/src/lib/__tests__/write-queue-manager.test.ts +383 -0
- package/src/lib/cache-manager.ts +198 -46
- package/src/lib/proxy-server.ts +190 -86
- package/src/lib/qwickbrain-client.ts +142 -2
- package/src/lib/sse-invalidation-listener.ts +171 -0
- package/src/lib/tools.ts +500 -0
- package/src/lib/write-queue-manager.ts +271 -0
- package/src/types/config.ts +1 -6
|
@@ -6,7 +6,7 @@ import { createDatabase, runMigrations } from '../../db/client.js';
|
|
|
6
6
|
import { CacheManager } from '../cache-manager.js';
|
|
7
7
|
import type { Config } from '../../types/config.js';
|
|
8
8
|
|
|
9
|
-
describe('CacheManager', () => {
|
|
9
|
+
describe('CacheManager - LRU Two-Tier Storage', () => {
|
|
10
10
|
let tmpDir: string;
|
|
11
11
|
let cacheManager: CacheManager;
|
|
12
12
|
let db: ReturnType<typeof createDatabase>['db'];
|
|
@@ -22,12 +22,7 @@ describe('CacheManager', () => {
|
|
|
22
22
|
|
|
23
23
|
const config: Config['cache'] = {
|
|
24
24
|
dir: tmpDir,
|
|
25
|
-
|
|
26
|
-
workflows: 3600,
|
|
27
|
-
rules: 3600,
|
|
28
|
-
documents: 1800,
|
|
29
|
-
memories: 900,
|
|
30
|
-
},
|
|
25
|
+
maxCacheSizeBytes: 10 * 1024, // 10KB limit for testing
|
|
31
26
|
preload: [],
|
|
32
27
|
};
|
|
33
28
|
|
|
@@ -49,7 +44,7 @@ describe('CacheManager', () => {
|
|
|
49
44
|
expect(cached?.data.content).toBe('content here');
|
|
50
45
|
expect(cached?.data.doc_type).toBe('workflow');
|
|
51
46
|
expect(cached?.data.name).toBe('test-workflow');
|
|
52
|
-
expect(cached?.
|
|
47
|
+
expect(cached?.age).toBeGreaterThanOrEqual(0);
|
|
53
48
|
});
|
|
54
49
|
|
|
55
50
|
it('should store document with metadata', async () => {
|
|
@@ -97,29 +92,39 @@ describe('CacheManager', () => {
|
|
|
97
92
|
expect(cached?.data.content).toBe('version 2');
|
|
98
93
|
});
|
|
99
94
|
|
|
100
|
-
it('should mark document as
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
rules: 0,
|
|
107
|
-
documents: 0,
|
|
108
|
-
memories: 0,
|
|
109
|
-
},
|
|
110
|
-
preload: [],
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const shortCacheManager = new CacheManager(db, shortTTLConfig);
|
|
114
|
-
await shortCacheManager.setDocument('workflow', 'test', 'content');
|
|
115
|
-
|
|
116
|
-
// Wait to ensure timestamp difference
|
|
117
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
95
|
+
it('should mark critical document types as critical', async () => {
|
|
96
|
+
// Critical types: workflow, rule, agent, template
|
|
97
|
+
await cacheManager.setDocument('workflow', 'test-workflow', 'content');
|
|
98
|
+
await cacheManager.setDocument('rule', 'test-rule', 'content');
|
|
99
|
+
await cacheManager.setDocument('agent', 'test-agent', 'content');
|
|
100
|
+
await cacheManager.setDocument('template', 'test-template', 'content');
|
|
118
101
|
|
|
119
|
-
|
|
102
|
+
// Non-critical type
|
|
103
|
+
await cacheManager.setDocument('frd', 'test-frd', 'content');
|
|
120
104
|
|
|
121
|
-
|
|
122
|
-
|
|
105
|
+
const stats = await cacheManager.getCacheStats();
|
|
106
|
+
|
|
107
|
+
expect(stats.criticalCount).toBe(4);
|
|
108
|
+
expect(stats.dynamicCount).toBeGreaterThan(0); // FRD is dynamic
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should update lastAccessedAt on read', async () => {
|
|
112
|
+
await cacheManager.setDocument('workflow', 'test', 'content');
|
|
113
|
+
|
|
114
|
+
const cached1 = await cacheManager.getDocument('workflow', 'test');
|
|
115
|
+
const cachedAt1 = cached1?.cachedAt.getTime();
|
|
116
|
+
|
|
117
|
+
// Wait 200ms
|
|
118
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
119
|
+
|
|
120
|
+
const cached2 = await cacheManager.getDocument('workflow', 'test');
|
|
121
|
+
const cachedAt2 = cached2?.cachedAt.getTime();
|
|
122
|
+
|
|
123
|
+
// cachedAt should remain the same (shows when first cached)
|
|
124
|
+
expect(cachedAt2).toBe(cachedAt1);
|
|
125
|
+
|
|
126
|
+
// But age should increase over time
|
|
127
|
+
expect(cached2?.age).toBeGreaterThanOrEqual(0);
|
|
123
128
|
});
|
|
124
129
|
});
|
|
125
130
|
|
|
@@ -132,7 +137,6 @@ describe('CacheManager', () => {
|
|
|
132
137
|
expect(cached).not.toBeNull();
|
|
133
138
|
expect(cached?.data.content).toBe('memory content');
|
|
134
139
|
expect(cached?.data.name).toBe('test-memory');
|
|
135
|
-
expect(cached?.isExpired).toBe(false);
|
|
136
140
|
});
|
|
137
141
|
|
|
138
142
|
it('should store memory with metadata', async () => {
|
|
@@ -168,83 +172,169 @@ describe('CacheManager', () => {
|
|
|
168
172
|
const cached = await cacheManager.getMemory('non-existent');
|
|
169
173
|
expect(cached).toBeNull();
|
|
170
174
|
});
|
|
175
|
+
});
|
|
171
176
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
await
|
|
177
|
+
describe('LRU Eviction - Two-Tier Storage', () => {
|
|
178
|
+
it('should NOT evict critical documents when storage limit reached', async () => {
|
|
179
|
+
// Create large critical documents (workflows)
|
|
180
|
+
const largeContent = 'x'.repeat(3000); // 3KB each
|
|
181
|
+
|
|
182
|
+
// Add 4 critical documents = 12KB (exceeds 10KB limit)
|
|
183
|
+
await cacheManager.setDocument('workflow', 'critical1', largeContent);
|
|
184
|
+
await cacheManager.setDocument('workflow', 'critical2', largeContent);
|
|
185
|
+
await cacheManager.setDocument('workflow', 'critical3', largeContent);
|
|
186
|
+
await cacheManager.setDocument('workflow', 'critical4', largeContent);
|
|
187
|
+
|
|
188
|
+
// All should still be present (critical tier bypasses limit)
|
|
189
|
+
const doc1 = await cacheManager.getDocument('workflow', 'critical1');
|
|
190
|
+
const doc2 = await cacheManager.getDocument('workflow', 'critical2');
|
|
191
|
+
const doc3 = await cacheManager.getDocument('workflow', 'critical3');
|
|
192
|
+
const doc4 = await cacheManager.getDocument('workflow', 'critical4');
|
|
193
|
+
|
|
194
|
+
expect(doc1).not.toBeNull();
|
|
195
|
+
expect(doc2).not.toBeNull();
|
|
196
|
+
expect(doc3).not.toBeNull();
|
|
197
|
+
expect(doc4).not.toBeNull();
|
|
198
|
+
});
|
|
186
199
|
|
|
187
|
-
|
|
200
|
+
it('should evict oldest dynamic documents when storage limit reached', async () => {
|
|
201
|
+
const largeContent = 'x'.repeat(3000); // 3KB each
|
|
188
202
|
|
|
189
|
-
|
|
203
|
+
// Add dynamic documents
|
|
204
|
+
await cacheManager.setDocument('frd', 'dynamic1', largeContent);
|
|
205
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
190
206
|
|
|
191
|
-
|
|
192
|
-
|
|
207
|
+
await cacheManager.setDocument('frd', 'dynamic2', largeContent);
|
|
208
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
209
|
+
|
|
210
|
+
await cacheManager.setDocument('frd', 'dynamic3', largeContent);
|
|
211
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
212
|
+
|
|
213
|
+
// Now add another that pushes us over limit (9KB current, 3KB new = 12KB > 10KB)
|
|
214
|
+
// Should evict dynamic1 (oldest)
|
|
215
|
+
await cacheManager.setDocument('frd', 'dynamic4', largeContent);
|
|
216
|
+
|
|
217
|
+
// dynamic1 should be evicted
|
|
218
|
+
const doc1 = await cacheManager.getDocument('frd', 'dynamic1');
|
|
219
|
+
expect(doc1).toBeNull();
|
|
220
|
+
|
|
221
|
+
// dynamic2, dynamic3, dynamic4 should remain
|
|
222
|
+
const doc2 = await cacheManager.getDocument('frd', 'dynamic2');
|
|
223
|
+
const doc3 = await cacheManager.getDocument('frd', 'dynamic3');
|
|
224
|
+
const doc4 = await cacheManager.getDocument('frd', 'dynamic4');
|
|
225
|
+
|
|
226
|
+
expect(doc2).not.toBeNull();
|
|
227
|
+
expect(doc3).not.toBeNull();
|
|
228
|
+
expect(doc4).not.toBeNull();
|
|
193
229
|
});
|
|
194
|
-
});
|
|
195
230
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const shortTTLConfig: Config['cache'] = {
|
|
199
|
-
dir: tmpDir,
|
|
200
|
-
ttl: {
|
|
201
|
-
workflows: 0,
|
|
202
|
-
rules: 0,
|
|
203
|
-
documents: 0,
|
|
204
|
-
memories: 0,
|
|
205
|
-
},
|
|
206
|
-
preload: [],
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const shortCacheManager = new CacheManager(db, shortTTLConfig);
|
|
210
|
-
|
|
211
|
-
// Add some items that will immediately expire
|
|
212
|
-
await shortCacheManager.setDocument('workflow', 'test1', 'content1');
|
|
213
|
-
await shortCacheManager.setDocument('rule', 'test2', 'content2');
|
|
214
|
-
await shortCacheManager.setMemory('memory1', 'content3');
|
|
215
|
-
|
|
216
|
-
// Wait to ensure they're expired (need sufficient time for clock to advance)
|
|
217
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
231
|
+
it('should use LRU ordering based on lastAccessedAt', async () => {
|
|
232
|
+
const largeContent = 'x'.repeat(3000); // 3KB each
|
|
218
233
|
|
|
219
|
-
//
|
|
220
|
-
|
|
234
|
+
// Add 3 dynamic documents with delays to ensure distinct timestamps
|
|
235
|
+
await cacheManager.setDocument('frd', 'doc1', largeContent);
|
|
236
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
221
237
|
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
await cacheManager.setDocument('frd', 'doc2', largeContent);
|
|
239
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
224
240
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const mem1 = await shortCacheManager.getMemory('memory1');
|
|
241
|
+
await cacheManager.setDocument('frd', 'doc3', largeContent);
|
|
242
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
228
243
|
|
|
229
|
-
|
|
230
|
-
|
|
244
|
+
// Access doc2 and doc3 (updates their lastAccessedAt to be newest)
|
|
245
|
+
await cacheManager.getDocument('frd', 'doc2');
|
|
246
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
247
|
+
await cacheManager.getDocument('frd', 'doc3');
|
|
248
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
249
|
+
|
|
250
|
+
// Now doc1 has oldest lastAccessedAt, should be evicted first
|
|
251
|
+
// Add doc4, should evict doc1 (oldest access)
|
|
252
|
+
await cacheManager.setDocument('frd', 'doc4', largeContent);
|
|
253
|
+
|
|
254
|
+
const doc1 = await cacheManager.getDocument('frd', 'doc1');
|
|
255
|
+
const doc2 = await cacheManager.getDocument('frd', 'doc2');
|
|
256
|
+
const doc3 = await cacheManager.getDocument('frd', 'doc3');
|
|
257
|
+
const doc4 = await cacheManager.getDocument('frd', 'doc4');
|
|
258
|
+
|
|
259
|
+
expect(doc1).toBeNull(); // Oldest access, evicted
|
|
260
|
+
expect(doc2).not.toBeNull(); // Accessed recently, kept
|
|
261
|
+
expect(doc3).not.toBeNull(); // Accessed recently, kept
|
|
262
|
+
expect(doc4).not.toBeNull(); // Just added
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should evict memories when document eviction insufficient', async () => {
|
|
266
|
+
const largeContent = 'x'.repeat(3000); // 3KB each
|
|
267
|
+
|
|
268
|
+
// Add one dynamic document
|
|
269
|
+
await cacheManager.setDocument('frd', 'doc1', largeContent);
|
|
270
|
+
|
|
271
|
+
// Add memories
|
|
272
|
+
await cacheManager.setMemory('mem1', largeContent);
|
|
273
|
+
await cacheManager.setMemory('mem2', largeContent);
|
|
274
|
+
|
|
275
|
+
// Current: 9KB (over limit when we add another 3KB)
|
|
276
|
+
// Should evict doc1 and mem1 to make room
|
|
277
|
+
await cacheManager.setDocument('frd', 'doc2', largeContent);
|
|
278
|
+
|
|
279
|
+
const doc1 = await cacheManager.getDocument('frd', 'doc1');
|
|
280
|
+
const mem1 = await cacheManager.getMemory('mem1');
|
|
281
|
+
const mem2 = await cacheManager.getMemory('mem2');
|
|
282
|
+
|
|
283
|
+
// At least one should be evicted (LRU order)
|
|
284
|
+
const evictedCount = [doc1, mem1, mem2].filter(item => item === null).length;
|
|
285
|
+
expect(evictedCount).toBeGreaterThan(0);
|
|
231
286
|
});
|
|
287
|
+
});
|
|
232
288
|
|
|
233
|
-
|
|
289
|
+
describe('Cache invalidation', () => {
|
|
290
|
+
it('should invalidate specific document', async () => {
|
|
234
291
|
await cacheManager.setDocument('workflow', 'test', 'content');
|
|
235
|
-
await cacheManager.setMemory('memory', 'content');
|
|
236
292
|
|
|
237
|
-
const
|
|
293
|
+
const cached1 = await cacheManager.getDocument('workflow', 'test');
|
|
294
|
+
expect(cached1).not.toBeNull();
|
|
295
|
+
|
|
296
|
+
await cacheManager.invalidateDocument('workflow', 'test');
|
|
297
|
+
|
|
298
|
+
const cached2 = await cacheManager.getDocument('workflow', 'test');
|
|
299
|
+
expect(cached2).toBeNull();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should invalidate specific memory', async () => {
|
|
303
|
+
await cacheManager.setMemory('test', 'content');
|
|
304
|
+
|
|
305
|
+
const cached1 = await cacheManager.getMemory('test');
|
|
306
|
+
expect(cached1).not.toBeNull();
|
|
307
|
+
|
|
308
|
+
await cacheManager.invalidateMemory('test');
|
|
309
|
+
|
|
310
|
+
const cached2 = await cacheManager.getMemory('test');
|
|
311
|
+
expect(cached2).toBeNull();
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('Cache statistics', () => {
|
|
316
|
+
it('should return accurate cache statistics', async () => {
|
|
317
|
+
const content = 'x'.repeat(1000); // 1KB each
|
|
318
|
+
|
|
319
|
+
// Add critical documents
|
|
320
|
+
await cacheManager.setDocument('workflow', 'wf1', content);
|
|
321
|
+
await cacheManager.setDocument('rule', 'rule1', content);
|
|
322
|
+
|
|
323
|
+
// Add dynamic documents
|
|
324
|
+
await cacheManager.setDocument('frd', 'frd1', content);
|
|
238
325
|
|
|
239
|
-
|
|
240
|
-
|
|
326
|
+
// Add memories
|
|
327
|
+
await cacheManager.setMemory('mem1', content);
|
|
241
328
|
|
|
242
|
-
|
|
243
|
-
const doc = await cacheManager.getDocument('workflow', 'test');
|
|
244
|
-
const mem = await cacheManager.getMemory('memory');
|
|
329
|
+
const stats = await cacheManager.getCacheStats();
|
|
245
330
|
|
|
246
|
-
expect(
|
|
247
|
-
expect(
|
|
331
|
+
expect(stats.criticalCount).toBe(2);
|
|
332
|
+
expect(stats.dynamicCount).toBeGreaterThan(0);
|
|
333
|
+
expect(stats.memoryCount).toBe(1);
|
|
334
|
+
expect(stats.criticalSize).toBeGreaterThan(0);
|
|
335
|
+
expect(stats.dynamicSize).toBeGreaterThan(0);
|
|
336
|
+
expect(stats.totalSize).toBe(stats.criticalSize + stats.dynamicSize);
|
|
337
|
+
expect(stats.totalCount).toBe(stats.criticalCount + stats.dynamicCount);
|
|
248
338
|
});
|
|
249
339
|
});
|
|
250
340
|
|
|
@@ -53,12 +53,7 @@ describe('ProxyServer', () => {
|
|
|
53
53
|
},
|
|
54
54
|
cache: {
|
|
55
55
|
dir: tmpDir,
|
|
56
|
-
|
|
57
|
-
workflows: 3600,
|
|
58
|
-
rules: 3600,
|
|
59
|
-
documents: 1800,
|
|
60
|
-
memories: 900,
|
|
61
|
-
},
|
|
56
|
+
maxCacheSizeBytes: 100 * 1024 * 1024, // 100MB
|
|
62
57
|
preload: [],
|
|
63
58
|
},
|
|
64
59
|
connection: {
|
|
@@ -102,63 +97,33 @@ describe('ProxyServer', () => {
|
|
|
102
97
|
});
|
|
103
98
|
});
|
|
104
99
|
|
|
105
|
-
describe('cache
|
|
106
|
-
it('should
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
shortTTLConfig.cache.ttl = {
|
|
110
|
-
workflows: 0,
|
|
111
|
-
rules: 0,
|
|
112
|
-
documents: 0,
|
|
113
|
-
memories: 0,
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const tempServer = new ProxyServer(db, shortTTLConfig);
|
|
100
|
+
describe('cache behavior', () => {
|
|
101
|
+
it('should use LRU cache with no expiration', async () => {
|
|
102
|
+
// With LRU cache, items never expire by time
|
|
103
|
+
await server['cacheManager'].setDocument('workflow', 'test', 'content');
|
|
117
104
|
|
|
118
|
-
|
|
105
|
+
// Wait some time
|
|
119
106
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
120
107
|
|
|
121
|
-
//
|
|
122
|
-
const
|
|
123
|
-
expect(
|
|
124
|
-
|
|
125
|
-
await tempServer.start();
|
|
126
|
-
|
|
127
|
-
// Entry should be removed after cleanup
|
|
128
|
-
const afterCleanup = await tempServer['cacheManager'].getDocument('workflow', 'test');
|
|
129
|
-
expect(afterCleanup).toBeNull();
|
|
130
|
-
|
|
131
|
-
await tempServer.stop();
|
|
108
|
+
// Entry should still be present (no TTL expiration)
|
|
109
|
+
const cached = await server['cacheManager'].getDocument('workflow', 'test');
|
|
110
|
+
expect(cached).not.toBeNull();
|
|
111
|
+
expect(cached?.data.content).toBe('content');
|
|
132
112
|
});
|
|
133
113
|
});
|
|
134
114
|
|
|
135
115
|
describe('graceful degradation', () => {
|
|
136
|
-
it('should serve
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
shortTTLConfig.cache.ttl = {
|
|
140
|
-
workflows: 0,
|
|
141
|
-
rules: 0,
|
|
142
|
-
documents: 0,
|
|
143
|
-
memories: 0,
|
|
144
|
-
};
|
|
145
|
-
const tempServer = new ProxyServer(db, shortTTLConfig);
|
|
146
|
-
|
|
147
|
-
// Add item to cache
|
|
148
|
-
await tempServer['cacheManager'].setDocument('workflow', 'test', 'cached content');
|
|
149
|
-
|
|
150
|
-
// Wait for cache to expire
|
|
151
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
116
|
+
it('should serve cached data when disconnected', async () => {
|
|
117
|
+
// LRU cache always serves cached data (no staleness - always fresh)
|
|
118
|
+
await server['cacheManager'].setDocument('workflow', 'test', 'cached content');
|
|
152
119
|
|
|
153
120
|
// Simulate disconnected state
|
|
154
|
-
|
|
121
|
+
server['connectionManager']['state'] = 'disconnected';
|
|
155
122
|
|
|
156
|
-
const result = await
|
|
123
|
+
const result = await server['handleGetDocument']('workflow', 'test');
|
|
157
124
|
|
|
158
125
|
expect((result.data as any).content).toBe('cached content');
|
|
159
|
-
expect(result._metadata.source).toBe('
|
|
160
|
-
|
|
161
|
-
await tempServer.stop();
|
|
126
|
+
expect(result._metadata.source).toBe('cache'); // Fresh cache, not stale
|
|
162
127
|
});
|
|
163
128
|
|
|
164
129
|
it('should return error when no cache available and disconnected', async () => {
|