@qwickapps/qwickbrain-proxy 1.0.1 → 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.
Files changed (67) hide show
  1. package/.claude/engineering/bugs/BUG-qwickbrain-proxy-cache-and-design.md +840 -0
  2. package/.github/workflows/publish.yml +13 -0
  3. package/CHANGELOG.md +54 -0
  4. package/dist/db/schema.d.ts +63 -6
  5. package/dist/db/schema.d.ts.map +1 -1
  6. package/dist/db/schema.js +17 -2
  7. package/dist/db/schema.js.map +1 -1
  8. package/dist/lib/__tests__/cache-manager.test.js +146 -83
  9. package/dist/lib/__tests__/cache-manager.test.js.map +1 -1
  10. package/dist/lib/__tests__/proxy-server.test.js +16 -44
  11. package/dist/lib/__tests__/proxy-server.test.js.map +1 -1
  12. package/dist/lib/__tests__/sse-invalidation-listener.test.d.ts +2 -0
  13. package/dist/lib/__tests__/sse-invalidation-listener.test.d.ts.map +1 -0
  14. package/dist/lib/__tests__/sse-invalidation-listener.test.js +245 -0
  15. package/dist/lib/__tests__/sse-invalidation-listener.test.js.map +1 -0
  16. package/dist/lib/__tests__/write-queue-manager.test.d.ts +2 -0
  17. package/dist/lib/__tests__/write-queue-manager.test.d.ts.map +1 -0
  18. package/dist/lib/__tests__/write-queue-manager.test.js +291 -0
  19. package/dist/lib/__tests__/write-queue-manager.test.js.map +1 -0
  20. package/dist/lib/cache-manager.d.ts +35 -6
  21. package/dist/lib/cache-manager.d.ts.map +1 -1
  22. package/dist/lib/cache-manager.js +154 -41
  23. package/dist/lib/cache-manager.js.map +1 -1
  24. package/dist/lib/connection-manager.d.ts.map +1 -1
  25. package/dist/lib/connection-manager.js +4 -1
  26. package/dist/lib/connection-manager.js.map +1 -1
  27. package/dist/lib/proxy-server.d.ts +6 -0
  28. package/dist/lib/proxy-server.d.ts.map +1 -1
  29. package/dist/lib/proxy-server.js +182 -87
  30. package/dist/lib/proxy-server.js.map +1 -1
  31. package/dist/lib/qwickbrain-client.d.ts +4 -0
  32. package/dist/lib/qwickbrain-client.d.ts.map +1 -1
  33. package/dist/lib/qwickbrain-client.js +133 -0
  34. package/dist/lib/qwickbrain-client.js.map +1 -1
  35. package/dist/lib/sse-invalidation-listener.d.ts +27 -0
  36. package/dist/lib/sse-invalidation-listener.d.ts.map +1 -0
  37. package/dist/lib/sse-invalidation-listener.js +145 -0
  38. package/dist/lib/sse-invalidation-listener.js.map +1 -0
  39. package/dist/lib/tools.d.ts +21 -0
  40. package/dist/lib/tools.d.ts.map +1 -0
  41. package/dist/lib/tools.js +488 -0
  42. package/dist/lib/tools.js.map +1 -0
  43. package/dist/lib/write-queue-manager.d.ts +88 -0
  44. package/dist/lib/write-queue-manager.d.ts.map +1 -0
  45. package/dist/lib/write-queue-manager.js +191 -0
  46. package/dist/lib/write-queue-manager.js.map +1 -0
  47. package/dist/types/config.d.ts +7 -42
  48. package/dist/types/config.d.ts.map +1 -1
  49. package/dist/types/config.js +1 -6
  50. package/dist/types/config.js.map +1 -1
  51. package/drizzle/0002_lru_cache_migration.sql +94 -0
  52. package/drizzle/meta/_journal.json +7 -0
  53. package/package.json +6 -2
  54. package/scripts/rebuild-sqlite.sh +26 -0
  55. package/src/db/schema.ts +17 -2
  56. package/src/lib/__tests__/cache-manager.test.ts +180 -90
  57. package/src/lib/__tests__/proxy-server.test.ts +16 -51
  58. package/src/lib/__tests__/sse-invalidation-listener.test.ts +326 -0
  59. package/src/lib/__tests__/write-queue-manager.test.ts +383 -0
  60. package/src/lib/cache-manager.ts +198 -46
  61. package/src/lib/connection-manager.ts +4 -1
  62. package/src/lib/proxy-server.ts +222 -90
  63. package/src/lib/qwickbrain-client.ts +145 -1
  64. package/src/lib/sse-invalidation-listener.ts +171 -0
  65. package/src/lib/tools.ts +500 -0
  66. package/src/lib/write-queue-manager.ts +271 -0
  67. package/src/types/config.ts +1 -6
@@ -0,0 +1,291 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdtempSync, rmSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ import { createDatabase, runMigrations } from '../../db/client.js';
6
+ import { WriteQueueManager } from '../write-queue-manager.js';
7
+ import { QwickBrainClient } from '../qwickbrain-client.js';
8
+ describe('WriteQueueManager', () => {
9
+ let tmpDir;
10
+ let writeQueueManager;
11
+ let qwickbrainClient;
12
+ let db;
13
+ beforeEach(() => {
14
+ // Create temporary directory for test database
15
+ tmpDir = mkdtempSync(join(tmpdir(), 'queue-test-'));
16
+ const dbResult = createDatabase(tmpDir);
17
+ db = dbResult.db;
18
+ // Run migrations to create tables
19
+ runMigrations(db);
20
+ // Create mock client
21
+ const config = {
22
+ mode: 'sse',
23
+ url: 'http://test.local:3000',
24
+ };
25
+ qwickbrainClient = new QwickBrainClient(config);
26
+ // Mock the write methods
27
+ vi.spyOn(qwickbrainClient, 'createDocument').mockResolvedValue();
28
+ vi.spyOn(qwickbrainClient, 'setMemory').mockResolvedValue();
29
+ vi.spyOn(qwickbrainClient, 'deleteDocument').mockResolvedValue();
30
+ vi.spyOn(qwickbrainClient, 'deleteMemory').mockResolvedValue();
31
+ writeQueueManager = new WriteQueueManager(db, qwickbrainClient);
32
+ });
33
+ afterEach(() => {
34
+ // Clean up temporary directory
35
+ rmSync(tmpDir, { recursive: true, force: true });
36
+ vi.restoreAllMocks();
37
+ });
38
+ describe('queueOperation', () => {
39
+ it('should queue a create_document operation', async () => {
40
+ await writeQueueManager.queueOperation('create_document', {
41
+ docType: 'workflow',
42
+ name: 'test-workflow',
43
+ content: 'workflow content',
44
+ });
45
+ const stats = await writeQueueManager.getQueueStats();
46
+ expect(stats.pending).toBe(1);
47
+ expect(stats.total).toBe(1);
48
+ });
49
+ it('should queue a set_memory operation', async () => {
50
+ await writeQueueManager.queueOperation('set_memory', {
51
+ name: 'test-memory',
52
+ content: 'memory content',
53
+ });
54
+ const stats = await writeQueueManager.getQueueStats();
55
+ expect(stats.pending).toBe(1);
56
+ });
57
+ it('should queue multiple operations', async () => {
58
+ await writeQueueManager.queueOperation('create_document', {
59
+ docType: 'workflow',
60
+ name: 'wf1',
61
+ content: 'content1',
62
+ });
63
+ await writeQueueManager.queueOperation('set_memory', {
64
+ name: 'mem1',
65
+ content: 'content2',
66
+ });
67
+ await writeQueueManager.queueOperation('create_document', {
68
+ docType: 'rule',
69
+ name: 'rule1',
70
+ content: 'content3',
71
+ });
72
+ const stats = await writeQueueManager.getQueueStats();
73
+ expect(stats.pending).toBe(3);
74
+ expect(stats.total).toBe(3);
75
+ });
76
+ });
77
+ describe('syncPendingOperations', () => {
78
+ it('should sync pending create_document operations', async () => {
79
+ await writeQueueManager.queueOperation('create_document', {
80
+ docType: 'workflow',
81
+ name: 'test-workflow',
82
+ content: 'workflow content',
83
+ project: 'my-project',
84
+ metadata: { author: 'test' },
85
+ });
86
+ const { synced, failed } = await writeQueueManager.syncPendingOperations();
87
+ expect(synced).toBe(1);
88
+ expect(failed).toBe(0);
89
+ expect(qwickbrainClient.createDocument).toHaveBeenCalledWith('workflow', 'test-workflow', 'workflow content', 'my-project', { author: 'test' });
90
+ const stats = await writeQueueManager.getQueueStats();
91
+ expect(stats.pending).toBe(0);
92
+ expect(stats.total).toBe(0); // Completed operations are cleaned up
93
+ });
94
+ it('should sync pending set_memory operations', async () => {
95
+ await writeQueueManager.queueOperation('set_memory', {
96
+ name: 'test-memory',
97
+ content: 'memory content',
98
+ project: 'my-project',
99
+ });
100
+ const { synced, failed } = await writeQueueManager.syncPendingOperations();
101
+ expect(synced).toBe(1);
102
+ expect(failed).toBe(0);
103
+ expect(qwickbrainClient.setMemory).toHaveBeenCalledWith('test-memory', 'memory content', 'my-project', undefined);
104
+ });
105
+ it('should sync multiple operations in order (FIFO)', async () => {
106
+ const callOrder = [];
107
+ vi.spyOn(qwickbrainClient, 'createDocument').mockImplementation(async (docType, name) => {
108
+ callOrder.push(`doc:${name}`);
109
+ });
110
+ vi.spyOn(qwickbrainClient, 'setMemory').mockImplementation(async (name) => {
111
+ callOrder.push(`mem:${name}`);
112
+ });
113
+ await writeQueueManager.queueOperation('create_document', {
114
+ docType: 'workflow',
115
+ name: 'first',
116
+ content: 'content',
117
+ });
118
+ await writeQueueManager.queueOperation('set_memory', {
119
+ name: 'second',
120
+ content: 'content',
121
+ });
122
+ await writeQueueManager.queueOperation('create_document', {
123
+ docType: 'rule',
124
+ name: 'third',
125
+ content: 'content',
126
+ });
127
+ const { synced } = await writeQueueManager.syncPendingOperations();
128
+ expect(synced).toBe(3);
129
+ expect(callOrder).toEqual(['doc:first', 'mem:second', 'doc:third']);
130
+ });
131
+ it('should handle operation failures and retry', async () => {
132
+ let callCount = 0;
133
+ vi.spyOn(qwickbrainClient, 'createDocument').mockImplementation(async () => {
134
+ callCount++;
135
+ if (callCount < 3) {
136
+ throw new Error('Network error');
137
+ }
138
+ });
139
+ await writeQueueManager.queueOperation('create_document', {
140
+ docType: 'workflow',
141
+ name: 'test',
142
+ content: 'content',
143
+ });
144
+ // First sync - should fail (attempt 1)
145
+ let result = await writeQueueManager.syncPendingOperations();
146
+ expect(result.synced).toBe(0);
147
+ expect(result.failed).toBe(0);
148
+ let stats = await writeQueueManager.getQueueStats();
149
+ expect(stats.pending).toBe(1); // Still pending
150
+ // Second sync - should fail (attempt 2)
151
+ result = await writeQueueManager.syncPendingOperations();
152
+ expect(result.synced).toBe(0);
153
+ expect(result.failed).toBe(0);
154
+ // Third sync - should succeed (attempt 3)
155
+ result = await writeQueueManager.syncPendingOperations();
156
+ expect(result.synced).toBe(1);
157
+ expect(result.failed).toBe(0);
158
+ stats = await writeQueueManager.getQueueStats();
159
+ expect(stats.pending).toBe(0);
160
+ });
161
+ it('should mark operation as failed after max attempts', async () => {
162
+ vi.spyOn(qwickbrainClient, 'createDocument').mockRejectedValue(new Error('Permanent error'));
163
+ await writeQueueManager.queueOperation('create_document', {
164
+ docType: 'workflow',
165
+ name: 'test',
166
+ content: 'content',
167
+ });
168
+ // Attempt 1
169
+ let result = await writeQueueManager.syncPendingOperations();
170
+ expect(result.failed).toBe(0);
171
+ // Attempt 2
172
+ result = await writeQueueManager.syncPendingOperations();
173
+ expect(result.failed).toBe(0);
174
+ // Attempt 3 - max reached, marked as failed
175
+ result = await writeQueueManager.syncPendingOperations();
176
+ expect(result.synced).toBe(0);
177
+ expect(result.failed).toBe(1);
178
+ const stats = await writeQueueManager.getQueueStats();
179
+ expect(stats.pending).toBe(0);
180
+ expect(stats.failed).toBe(1);
181
+ const failedOps = await writeQueueManager.getFailedOperations();
182
+ expect(failedOps.length).toBe(1);
183
+ expect(failedOps[0].error).toContain('Permanent error');
184
+ expect(failedOps[0].attempts).toBe(3);
185
+ });
186
+ it('should skip sync if already syncing', async () => {
187
+ // Queue an operation that takes time
188
+ let resolveSync;
189
+ const syncPromise = new Promise((resolve) => {
190
+ resolveSync = resolve;
191
+ });
192
+ vi.spyOn(qwickbrainClient, 'createDocument').mockImplementation(async () => {
193
+ await syncPromise;
194
+ });
195
+ await writeQueueManager.queueOperation('create_document', {
196
+ docType: 'workflow',
197
+ name: 'test',
198
+ content: 'content',
199
+ });
200
+ // Start first sync (won't complete)
201
+ const sync1 = writeQueueManager.syncPendingOperations();
202
+ // Start second sync while first is running
203
+ const sync2 = writeQueueManager.syncPendingOperations();
204
+ // Second should skip
205
+ const result2 = await sync2;
206
+ expect(result2.synced).toBe(0);
207
+ expect(result2.failed).toBe(0);
208
+ // Complete first sync
209
+ resolveSync();
210
+ const result1 = await sync1;
211
+ expect(result1.synced).toBe(1);
212
+ });
213
+ });
214
+ describe('retryOperation', () => {
215
+ it('should reset a failed operation for retry', async () => {
216
+ vi.spyOn(qwickbrainClient, 'createDocument').mockRejectedValue(new Error('Error'));
217
+ await writeQueueManager.queueOperation('create_document', {
218
+ docType: 'workflow',
219
+ name: 'test',
220
+ content: 'content',
221
+ });
222
+ // Fail 3 times to mark as failed
223
+ await writeQueueManager.syncPendingOperations();
224
+ await writeQueueManager.syncPendingOperations();
225
+ await writeQueueManager.syncPendingOperations();
226
+ let stats = await writeQueueManager.getQueueStats();
227
+ expect(stats.failed).toBe(1);
228
+ const failedOps = await writeQueueManager.getFailedOperations();
229
+ const opId = failedOps[0].id;
230
+ // Fix the mock
231
+ vi.spyOn(qwickbrainClient, 'createDocument').mockResolvedValue();
232
+ // Retry the operation
233
+ await writeQueueManager.retryOperation(opId);
234
+ stats = await writeQueueManager.getQueueStats();
235
+ expect(stats.pending).toBe(1);
236
+ expect(stats.failed).toBe(0);
237
+ // Sync should now succeed
238
+ const result = await writeQueueManager.syncPendingOperations();
239
+ expect(result.synced).toBe(1);
240
+ });
241
+ });
242
+ describe('clearFailed', () => {
243
+ it('should clear all failed operations', async () => {
244
+ vi.spyOn(qwickbrainClient, 'createDocument').mockRejectedValue(new Error('Error'));
245
+ // Queue and fail 2 operations
246
+ await writeQueueManager.queueOperation('create_document', {
247
+ docType: 'workflow',
248
+ name: 'test1',
249
+ content: 'content',
250
+ });
251
+ await writeQueueManager.queueOperation('create_document', {
252
+ docType: 'rule',
253
+ name: 'test2',
254
+ content: 'content',
255
+ });
256
+ // Fail them
257
+ for (let i = 0; i < 3; i++) {
258
+ await writeQueueManager.syncPendingOperations();
259
+ }
260
+ let stats = await writeQueueManager.getQueueStats();
261
+ expect(stats.failed).toBe(2);
262
+ const cleared = await writeQueueManager.clearFailed();
263
+ expect(cleared).toBe(2);
264
+ stats = await writeQueueManager.getQueueStats();
265
+ expect(stats.failed).toBe(0);
266
+ expect(stats.total).toBe(0);
267
+ });
268
+ });
269
+ describe('delete operations', () => {
270
+ it('should sync delete_document operations', async () => {
271
+ await writeQueueManager.queueOperation('delete_document', {
272
+ docType: 'workflow',
273
+ name: 'test-workflow',
274
+ project: 'my-project',
275
+ });
276
+ const { synced } = await writeQueueManager.syncPendingOperations();
277
+ expect(synced).toBe(1);
278
+ expect(qwickbrainClient.deleteDocument).toHaveBeenCalledWith('workflow', 'test-workflow', 'my-project');
279
+ });
280
+ it('should sync delete_memory operations', async () => {
281
+ await writeQueueManager.queueOperation('delete_memory', {
282
+ name: 'test-memory',
283
+ project: 'my-project',
284
+ });
285
+ const { synced } = await writeQueueManager.syncPendingOperations();
286
+ expect(synced).toBe(1);
287
+ expect(qwickbrainClient.deleteMemory).toHaveBeenCalledWith('test-memory', 'my-project');
288
+ });
289
+ });
290
+ });
291
+ //# sourceMappingURL=write-queue-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-queue-manager.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/write-queue-manager.test.ts"],"names":[],"mappings":"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"}
@@ -3,22 +3,51 @@ import type { Config } from '../types/config.js';
3
3
  interface CachedItem<T> {
4
4
  data: T;
5
5
  cachedAt: Date;
6
- expiresAt: Date;
7
6
  age: number;
8
- isExpired: boolean;
9
7
  }
10
8
  export declare class CacheManager {
11
9
  private db;
12
10
  private config;
11
+ private maxDynamicCacheSize;
13
12
  constructor(db: DB, config: Config['cache']);
14
- private getTTL;
13
+ /**
14
+ * Get current size of dynamic tier cache (excludes critical tier)
15
+ */
16
+ private getDynamicCacheSize;
17
+ /**
18
+ * Evict LRU entries from dynamic tier to free up space
19
+ * NEVER touches critical tier
20
+ */
21
+ private evictLRU;
22
+ /**
23
+ * Ensure sufficient cache space
24
+ * Critical items bypass this check
25
+ */
26
+ private ensureCacheSize;
15
27
  getDocument(docType: string, name: string, project?: string): Promise<CachedItem<any> | null>;
16
28
  setDocument(docType: string, name: string, content: string, project?: string, metadata?: Record<string, unknown>): Promise<void>;
17
29
  getMemory(name: string, project?: string): Promise<CachedItem<any> | null>;
18
30
  setMemory(name: string, content: string, project?: string, metadata?: Record<string, unknown>): Promise<void>;
19
- cleanupExpiredEntries(): Promise<{
20
- documentsDeleted: number;
21
- memoriesDeleted: number;
31
+ /**
32
+ * Invalidate a document from cache (for SSE-based cache invalidation)
33
+ */
34
+ invalidateDocument(docType: string, name: string, project?: string): Promise<void>;
35
+ /**
36
+ * Invalidate a memory from cache (for SSE-based cache invalidation)
37
+ */
38
+ invalidateMemory(name: string, project?: string): Promise<void>;
39
+ /**
40
+ * Get cache statistics
41
+ */
42
+ getCacheStats(): Promise<{
43
+ criticalSize: number;
44
+ criticalCount: number;
45
+ dynamicSize: number;
46
+ dynamicCount: number;
47
+ totalSize: number;
48
+ totalCount: number;
49
+ memorySize: number;
50
+ memoryCount: number;
22
51
  }>;
23
52
  }
24
53
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"cache-manager.d.ts","sourceRoot":"","sources":["../../src/lib/cache-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAE1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,UAAU,UAAU,CAAC,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,IAAI,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,MAAM;gBADN,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC;IAGjC,OAAO,CAAC,MAAM;IAUR,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAuC7F,WAAW,CACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IAiCV,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAqC1E,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IA+BV,qBAAqB,IAAI,OAAO,CAAC;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;CAoB9F"}
1
+ {"version":3,"file":"cache-manager.d.ts","sourceRoot":"","sources":["../../src/lib/cache-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAE1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAKjD,UAAU,UAAU,CAAC,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,IAAI,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,YAAY;IAIrB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,MAAM;IAJhB,OAAO,CAAC,mBAAmB,CAAS;gBAG1B,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC;IAMjC;;OAEG;YACW,mBAAmB;IASjC;;;OAGG;YACW,QAAQ;IAuCtB;;;OAGG;YACW,eAAe;IAkBvB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAwC7F,WAAW,CACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IAmCV,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAsC1E,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IAgChB;;OAEG;IACG,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxF;;OAEG;IACG,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAerE;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC;QAC7B,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CA6CH"}
@@ -1,19 +1,82 @@
1
- import { eq, and, lte } from 'drizzle-orm';
1
+ import { eq, and, sql } from 'drizzle-orm';
2
2
  import { documents, memories } from '../db/schema.js';
3
+ // Critical document types - never evicted, not counted toward storage limit
4
+ const CRITICAL_DOC_TYPES = ['workflow', 'rule', 'agent', 'template'];
3
5
  export class CacheManager {
4
6
  db;
5
7
  config;
8
+ maxDynamicCacheSize; // Storage limit for dynamic tier only (bytes)
6
9
  constructor(db, config) {
7
10
  this.db = db;
8
11
  this.config = config;
12
+ // Default to 100MB if not configured
13
+ this.maxDynamicCacheSize = config.maxCacheSizeBytes || 100 * 1024 * 1024;
9
14
  }
10
- getTTL(operation) {
11
- const ttlMap = {
12
- get_workflow: this.config.ttl.workflows,
13
- get_document: this.config.ttl.documents,
14
- get_memory: this.config.ttl.memories,
15
- };
16
- return ttlMap[operation] || 0;
15
+ /**
16
+ * Get current size of dynamic tier cache (excludes critical tier)
17
+ */
18
+ async getDynamicCacheSize() {
19
+ const result = await this.db
20
+ .select({ total: sql `COALESCE(sum(${documents.sizeBytes}), 0) + COALESCE((SELECT sum(${memories.sizeBytes}) FROM ${memories}), 0)` })
21
+ .from(documents)
22
+ .where(eq(documents.isCritical, false));
23
+ return result[0]?.total || 0;
24
+ }
25
+ /**
26
+ * Evict LRU entries from dynamic tier to free up space
27
+ * NEVER touches critical tier
28
+ */
29
+ async evictLRU(bytesToFree) {
30
+ let freed = 0;
31
+ // Evict from documents (dynamic tier only)
32
+ const docCandidates = await this.db
33
+ .select()
34
+ .from(documents)
35
+ .where(eq(documents.isCritical, false))
36
+ .orderBy(documents.lastAccessedAt) // ASC = oldest first
37
+ .limit(100);
38
+ for (const doc of docCandidates) {
39
+ if (freed >= bytesToFree)
40
+ break;
41
+ await this.db.delete(documents).where(eq(documents.id, doc.id));
42
+ freed += doc.sizeBytes;
43
+ console.error(`LRU evicted document: ${doc.docType}:${doc.name} (${doc.sizeBytes} bytes)`);
44
+ }
45
+ // Evict from memories if needed
46
+ if (freed < bytesToFree) {
47
+ const memCandidates = await this.db
48
+ .select()
49
+ .from(memories)
50
+ .orderBy(memories.lastAccessedAt) // ASC = oldest first
51
+ .limit(100);
52
+ for (const mem of memCandidates) {
53
+ if (freed >= bytesToFree)
54
+ break;
55
+ await this.db.delete(memories).where(eq(memories.id, mem.id));
56
+ freed += mem.sizeBytes;
57
+ console.error(`LRU evicted memory: ${mem.name} (${mem.sizeBytes} bytes)`);
58
+ }
59
+ }
60
+ console.error(`LRU eviction complete: freed ${freed} bytes`);
61
+ }
62
+ /**
63
+ * Ensure sufficient cache space
64
+ * Critical items bypass this check
65
+ */
66
+ async ensureCacheSize(requiredBytes, isCritical) {
67
+ // Critical files bypass storage limit check
68
+ if (isCritical) {
69
+ return;
70
+ }
71
+ // Only count dynamic tier toward storage limit
72
+ const currentSize = await this.getDynamicCacheSize();
73
+ if (currentSize + requiredBytes <= this.maxDynamicCacheSize) {
74
+ return;
75
+ }
76
+ // Evict LRU entries from dynamic tier only
77
+ const toEvict = currentSize + requiredBytes - this.maxDynamicCacheSize;
78
+ console.error(`Cache size limit reached: ${currentSize} + ${requiredBytes} > ${this.maxDynamicCacheSize}, evicting ${toEvict} bytes`);
79
+ await this.evictLRU(toEvict);
17
80
  }
18
81
  async getDocument(docType, name, project) {
19
82
  const projectValue = project || '';
@@ -25,10 +88,12 @@ export class CacheManager {
25
88
  if (!cached) {
26
89
  return null;
27
90
  }
91
+ // Update last accessed timestamp for LRU tracking
28
92
  const now = new Date();
93
+ await this.db.update(documents)
94
+ .set({ lastAccessedAt: now })
95
+ .where(eq(documents.id, cached.id));
29
96
  const age = Math.floor((now.getTime() - cached.cachedAt.getTime()) / 1000);
30
- // Fix: Compare timestamp values explicitly to avoid Date comparison issues
31
- const isExpired = now.getTime() > cached.expiresAt.getTime();
32
97
  return {
33
98
  data: {
34
99
  name: cached.name,
@@ -38,18 +103,16 @@ export class CacheManager {
38
103
  metadata: cached.metadata ? JSON.parse(cached.metadata) : {},
39
104
  },
40
105
  cachedAt: cached.cachedAt,
41
- expiresAt: cached.expiresAt,
42
106
  age,
43
- isExpired,
44
107
  };
45
108
  }
46
109
  async setDocument(docType, name, content, project, metadata) {
47
110
  const now = new Date();
48
- const ttl = this.getTTL('get_document');
49
- const expiresAt = new Date(now.getTime() + ttl * 1000);
50
- // Use empty string instead of null for project to make unique constraint work
51
- // SQLite treats NULL as distinct values in unique constraints
52
111
  const projectValue = project || '';
112
+ const isCritical = CRITICAL_DOC_TYPES.includes(docType);
113
+ const sizeBytes = Buffer.byteLength(content, 'utf8');
114
+ // Ensure space available (skips check if critical)
115
+ await this.ensureCacheSize(sizeBytes, isCritical);
53
116
  await this.db
54
117
  .insert(documents)
55
118
  .values({
@@ -59,7 +122,9 @@ export class CacheManager {
59
122
  content,
60
123
  metadata: metadata ? JSON.stringify(metadata) : null,
61
124
  cachedAt: now,
62
- expiresAt,
125
+ lastAccessedAt: now,
126
+ isCritical,
127
+ sizeBytes,
63
128
  synced: true,
64
129
  })
65
130
  .onConflictDoUpdate({
@@ -67,8 +132,8 @@ export class CacheManager {
67
132
  set: {
68
133
  content,
69
134
  metadata: metadata ? JSON.stringify(metadata) : null,
70
- cachedAt: now,
71
- expiresAt,
135
+ lastAccessedAt: now,
136
+ sizeBytes,
72
137
  synced: true,
73
138
  },
74
139
  });
@@ -83,10 +148,12 @@ export class CacheManager {
83
148
  if (!cached) {
84
149
  return null;
85
150
  }
151
+ // Update last accessed timestamp for LRU tracking
86
152
  const now = new Date();
153
+ await this.db.update(memories)
154
+ .set({ lastAccessedAt: now })
155
+ .where(eq(memories.id, cached.id));
87
156
  const age = Math.floor((now.getTime() - cached.cachedAt.getTime()) / 1000);
88
- // Fix: Compare timestamp values explicitly to avoid Date comparison issues
89
- const isExpired = now.getTime() > cached.expiresAt.getTime();
90
157
  return {
91
158
  data: {
92
159
  name: cached.name,
@@ -95,17 +162,15 @@ export class CacheManager {
95
162
  metadata: cached.metadata ? JSON.parse(cached.metadata) : {},
96
163
  },
97
164
  cachedAt: cached.cachedAt,
98
- expiresAt: cached.expiresAt,
99
165
  age,
100
- isExpired,
101
166
  };
102
167
  }
103
168
  async setMemory(name, content, project, metadata) {
104
169
  const now = new Date();
105
- const ttl = this.getTTL('get_memory');
106
- const expiresAt = new Date(now.getTime() + ttl * 1000);
107
- // Use empty string instead of null for project to make unique constraint work
108
170
  const projectValue = project || '';
171
+ const sizeBytes = Buffer.byteLength(content, 'utf8');
172
+ // Memories are always dynamic tier (not critical)
173
+ await this.ensureCacheSize(sizeBytes, false);
109
174
  await this.db
110
175
  .insert(memories)
111
176
  .values({
@@ -114,7 +179,8 @@ export class CacheManager {
114
179
  content,
115
180
  metadata: metadata ? JSON.stringify(metadata) : null,
116
181
  cachedAt: now,
117
- expiresAt,
182
+ lastAccessedAt: now,
183
+ sizeBytes,
118
184
  synced: true,
119
185
  })
120
186
  .onConflictDoUpdate({
@@ -122,27 +188,74 @@ export class CacheManager {
122
188
  set: {
123
189
  content,
124
190
  metadata: metadata ? JSON.stringify(metadata) : null,
125
- cachedAt: now,
126
- expiresAt,
191
+ lastAccessedAt: now,
192
+ sizeBytes,
127
193
  synced: true,
128
194
  },
129
195
  });
130
196
  }
131
- async cleanupExpiredEntries() {
132
- const now = new Date();
133
- // Delete expired documents (use lte to include items expiring exactly now)
134
- const deletedDocs = await this.db
197
+ /**
198
+ * Invalidate a document from cache (for SSE-based cache invalidation)
199
+ */
200
+ async invalidateDocument(docType, name, project) {
201
+ const projectValue = project || '';
202
+ await this.db
135
203
  .delete(documents)
136
- .where(lte(documents.expiresAt, now))
137
- .returning({ id: documents.id });
138
- // Delete expired memories (use lte to include items expiring exactly now)
139
- const deletedMems = await this.db
204
+ .where(and(eq(documents.docType, docType), eq(documents.name, name), eq(documents.project, projectValue)));
205
+ console.error(`Cache invalidated: ${docType}:${name}`);
206
+ }
207
+ /**
208
+ * Invalidate a memory from cache (for SSE-based cache invalidation)
209
+ */
210
+ async invalidateMemory(name, project) {
211
+ const projectValue = project || '';
212
+ await this.db
140
213
  .delete(memories)
141
- .where(lte(memories.expiresAt, now))
142
- .returning({ id: memories.id });
214
+ .where(and(eq(memories.name, name), eq(memories.project, projectValue)));
215
+ console.error(`Cache invalidated: memory:${name}`);
216
+ }
217
+ /**
218
+ * Get cache statistics
219
+ */
220
+ async getCacheStats() {
221
+ // Critical documents
222
+ const criticalResult = await this.db
223
+ .select({
224
+ size: sql `COALESCE(sum(${documents.sizeBytes}), 0)`,
225
+ count: sql `count(*)`
226
+ })
227
+ .from(documents)
228
+ .where(eq(documents.isCritical, true));
229
+ // Dynamic documents
230
+ const dynamicResult = await this.db
231
+ .select({
232
+ size: sql `COALESCE(sum(${documents.sizeBytes}), 0)`,
233
+ count: sql `count(*)`
234
+ })
235
+ .from(documents)
236
+ .where(eq(documents.isCritical, false));
237
+ // Memories
238
+ const memoryResult = await this.db
239
+ .select({
240
+ size: sql `COALESCE(sum(${memories.sizeBytes}), 0)`,
241
+ count: sql `count(*)`
242
+ })
243
+ .from(memories);
244
+ const criticalSize = criticalResult[0]?.size || 0;
245
+ const criticalCount = criticalResult[0]?.count || 0;
246
+ const dynamicSize = dynamicResult[0]?.size || 0;
247
+ const dynamicCount = dynamicResult[0]?.count || 0;
248
+ const memorySize = memoryResult[0]?.size || 0;
249
+ const memoryCount = memoryResult[0]?.count || 0;
143
250
  return {
144
- documentsDeleted: deletedDocs.length,
145
- memoriesDeleted: deletedMems.length,
251
+ criticalSize,
252
+ criticalCount,
253
+ dynamicSize: dynamicSize + memorySize,
254
+ dynamicCount: dynamicCount + memoryCount,
255
+ totalSize: criticalSize + dynamicSize + memorySize,
256
+ totalCount: criticalCount + dynamicCount + memoryCount,
257
+ memorySize,
258
+ memoryCount,
146
259
  };
147
260
  }
148
261
  }