@qwickapps/qwickbrain-proxy 1.0.2 → 1.1.0
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/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__/connection-manager.test.js +2 -2
- package/dist/lib/__tests__/connection-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__/qwickbrain-client.test.js +3 -1
- package/dist/lib/__tests__/qwickbrain-client.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/connection-manager.d.ts +7 -0
- package/dist/lib/connection-manager.d.ts.map +1 -1
- package/dist/lib/connection-manager.js +57 -8
- package/dist/lib/connection-manager.js.map +1 -1
- package/dist/lib/proxy-server.d.ts +12 -0
- package/dist/lib/proxy-server.d.ts.map +1 -1
- package/dist/lib/proxy-server.js +184 -87
- 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 +152 -13
- package/dist/lib/qwickbrain-client.js.map +1 -1
- package/dist/lib/sse-invalidation-listener.d.ts +31 -0
- package/dist/lib/sse-invalidation-listener.d.ts.map +1 -0
- package/dist/lib/sse-invalidation-listener.js +151 -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 +513 -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__/connection-manager.test.ts +2 -2
- package/src/lib/__tests__/proxy-server.test.ts +16 -51
- package/src/lib/__tests__/qwickbrain-client.test.ts +3 -1
- 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/connection-manager.ts +67 -8
- package/src/lib/proxy-server.ts +231 -90
- package/src/lib/qwickbrain-client.ts +166 -12
- package/src/lib/sse-invalidation-listener.ts +185 -0
- package/src/lib/tools.ts +525 -0
- package/src/lib/write-queue-manager.ts +271 -0
- package/src/types/config.ts +1 -6
- package/.github/workflows/publish.yml +0 -92
|
@@ -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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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;
|
|
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,
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
191
|
+
lastAccessedAt: now,
|
|
192
|
+
sizeBytes,
|
|
127
193
|
synced: true,
|
|
128
194
|
},
|
|
129
195
|
});
|
|
130
196
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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(
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
}
|