@qwickapps/qwickbrain-proxy 1.0.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.
Files changed (90) hide show
  1. package/.github/workflows/publish.yml +92 -0
  2. package/CHANGELOG.md +47 -0
  3. package/LICENSE +45 -0
  4. package/README.md +165 -0
  5. package/dist/bin/cli.d.ts +3 -0
  6. package/dist/bin/cli.d.ts.map +1 -0
  7. package/dist/bin/cli.js +142 -0
  8. package/dist/bin/cli.js.map +1 -0
  9. package/dist/db/client.d.ts +10 -0
  10. package/dist/db/client.d.ts.map +1 -0
  11. package/dist/db/client.js +23 -0
  12. package/dist/db/client.js.map +1 -0
  13. package/dist/db/schema.d.ts +551 -0
  14. package/dist/db/schema.d.ts.map +1 -0
  15. package/dist/db/schema.js +65 -0
  16. package/dist/db/schema.js.map +1 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/lib/__tests__/cache-manager.test.d.ts +2 -0
  22. package/dist/lib/__tests__/cache-manager.test.d.ts.map +1 -0
  23. package/dist/lib/__tests__/cache-manager.test.js +202 -0
  24. package/dist/lib/__tests__/cache-manager.test.js.map +1 -0
  25. package/dist/lib/__tests__/connection-manager.test.d.ts +2 -0
  26. package/dist/lib/__tests__/connection-manager.test.d.ts.map +1 -0
  27. package/dist/lib/__tests__/connection-manager.test.js +188 -0
  28. package/dist/lib/__tests__/connection-manager.test.js.map +1 -0
  29. package/dist/lib/__tests__/proxy-server.test.d.ts +2 -0
  30. package/dist/lib/__tests__/proxy-server.test.d.ts.map +1 -0
  31. package/dist/lib/__tests__/proxy-server.test.js +205 -0
  32. package/dist/lib/__tests__/proxy-server.test.js.map +1 -0
  33. package/dist/lib/__tests__/qwickbrain-client.test.d.ts +2 -0
  34. package/dist/lib/__tests__/qwickbrain-client.test.d.ts.map +1 -0
  35. package/dist/lib/__tests__/qwickbrain-client.test.js +233 -0
  36. package/dist/lib/__tests__/qwickbrain-client.test.js.map +1 -0
  37. package/dist/lib/cache-manager.d.ts +25 -0
  38. package/dist/lib/cache-manager.d.ts.map +1 -0
  39. package/dist/lib/cache-manager.js +149 -0
  40. package/dist/lib/cache-manager.js.map +1 -0
  41. package/dist/lib/connection-manager.d.ts +26 -0
  42. package/dist/lib/connection-manager.d.ts.map +1 -0
  43. package/dist/lib/connection-manager.js +130 -0
  44. package/dist/lib/connection-manager.js.map +1 -0
  45. package/dist/lib/proxy-server.d.ts +19 -0
  46. package/dist/lib/proxy-server.d.ts.map +1 -0
  47. package/dist/lib/proxy-server.js +258 -0
  48. package/dist/lib/proxy-server.js.map +1 -0
  49. package/dist/lib/qwickbrain-client.d.ts +24 -0
  50. package/dist/lib/qwickbrain-client.d.ts.map +1 -0
  51. package/dist/lib/qwickbrain-client.js +197 -0
  52. package/dist/lib/qwickbrain-client.js.map +1 -0
  53. package/dist/types/config.d.ts +186 -0
  54. package/dist/types/config.d.ts.map +1 -0
  55. package/dist/types/config.js +42 -0
  56. package/dist/types/config.js.map +1 -0
  57. package/dist/types/mcp.d.ts +223 -0
  58. package/dist/types/mcp.d.ts.map +1 -0
  59. package/dist/types/mcp.js +78 -0
  60. package/dist/types/mcp.js.map +1 -0
  61. package/dist/version.d.ts +2 -0
  62. package/dist/version.d.ts.map +1 -0
  63. package/dist/version.js +9 -0
  64. package/dist/version.js.map +1 -0
  65. package/drizzle/0000_fat_rafael_vega.sql +41 -0
  66. package/drizzle/0001_goofy_invisible_woman.sql +2 -0
  67. package/drizzle/meta/0000_snapshot.json +276 -0
  68. package/drizzle/meta/0001_snapshot.json +295 -0
  69. package/drizzle/meta/_journal.json +20 -0
  70. package/drizzle.config.ts +12 -0
  71. package/package.json +65 -0
  72. package/src/bin/cli.ts +158 -0
  73. package/src/db/client.ts +34 -0
  74. package/src/db/schema.ts +68 -0
  75. package/src/index.ts +6 -0
  76. package/src/lib/__tests__/cache-manager.test.ts +264 -0
  77. package/src/lib/__tests__/connection-manager.test.ts +255 -0
  78. package/src/lib/__tests__/proxy-server.test.ts +261 -0
  79. package/src/lib/__tests__/qwickbrain-client.test.ts +310 -0
  80. package/src/lib/cache-manager.ts +201 -0
  81. package/src/lib/connection-manager.ts +156 -0
  82. package/src/lib/proxy-server.ts +320 -0
  83. package/src/lib/qwickbrain-client.ts +260 -0
  84. package/src/types/config.ts +47 -0
  85. package/src/types/mcp.ts +97 -0
  86. package/src/version.ts +11 -0
  87. package/test/fixtures/test-mcp.json +5 -0
  88. package/test-mcp-client.js +67 -0
  89. package/test-proxy.sh +25 -0
  90. package/tsconfig.json +22 -0
@@ -0,0 +1,202 @@
1
+ import { describe, it, expect, beforeEach, afterEach } 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 { CacheManager } from '../cache-manager.js';
7
+ describe('CacheManager', () => {
8
+ let tmpDir;
9
+ let cacheManager;
10
+ let db;
11
+ beforeEach(() => {
12
+ // Create temporary directory for test database
13
+ tmpDir = mkdtempSync(join(tmpdir(), 'cache-test-'));
14
+ const dbResult = createDatabase(tmpDir);
15
+ db = dbResult.db;
16
+ // Run migrations to create tables
17
+ runMigrations(db);
18
+ const config = {
19
+ dir: tmpDir,
20
+ ttl: {
21
+ workflows: 3600,
22
+ rules: 3600,
23
+ documents: 1800,
24
+ memories: 900,
25
+ },
26
+ preload: [],
27
+ };
28
+ cacheManager = new CacheManager(db, config);
29
+ });
30
+ afterEach(() => {
31
+ // Clean up temporary directory
32
+ rmSync(tmpDir, { recursive: true, force: true });
33
+ });
34
+ describe('setDocument and getDocument', () => {
35
+ it('should store and retrieve a document', async () => {
36
+ await cacheManager.setDocument('workflow', 'test-workflow', 'content here');
37
+ const cached = await cacheManager.getDocument('workflow', 'test-workflow');
38
+ expect(cached).not.toBeNull();
39
+ expect(cached?.data.content).toBe('content here');
40
+ expect(cached?.data.doc_type).toBe('workflow');
41
+ expect(cached?.data.name).toBe('test-workflow');
42
+ expect(cached?.isExpired).toBe(false);
43
+ });
44
+ it('should store document with metadata', async () => {
45
+ const metadata = { author: 'test', version: 1 };
46
+ await cacheManager.setDocument('frd', 'test-frd', 'frd content', undefined, metadata);
47
+ const cached = await cacheManager.getDocument('frd', 'test-frd');
48
+ expect(cached?.data.metadata).toEqual(metadata);
49
+ });
50
+ it('should store document with project scope', async () => {
51
+ await cacheManager.setDocument('design', 'test-design', 'design content', 'my-project');
52
+ const cached = await cacheManager.getDocument('design', 'test-design', 'my-project');
53
+ expect(cached).not.toBeNull();
54
+ expect(cached?.data.project).toBe('my-project');
55
+ });
56
+ it('should distinguish between global and project-scoped documents', async () => {
57
+ await cacheManager.setDocument('rule', 'test-rule', 'global rule');
58
+ await cacheManager.setDocument('rule', 'test-rule', 'project rule', 'my-project');
59
+ const globalDoc = await cacheManager.getDocument('rule', 'test-rule');
60
+ const projectDoc = await cacheManager.getDocument('rule', 'test-rule', 'my-project');
61
+ expect(globalDoc?.data.content).toBe('global rule');
62
+ expect(globalDoc?.data.project).toBe('');
63
+ expect(projectDoc?.data.content).toBe('project rule');
64
+ expect(projectDoc?.data.project).toBe('my-project');
65
+ });
66
+ it('should return null for non-existent document', async () => {
67
+ const cached = await cacheManager.getDocument('workflow', 'non-existent');
68
+ expect(cached).toBeNull();
69
+ });
70
+ it('should update existing document on conflict', async () => {
71
+ await cacheManager.setDocument('workflow', 'test', 'version 1');
72
+ await cacheManager.setDocument('workflow', 'test', 'version 2');
73
+ const cached = await cacheManager.getDocument('workflow', 'test');
74
+ expect(cached?.data.content).toBe('version 2');
75
+ });
76
+ it('should mark document as expired after TTL', async () => {
77
+ // Override config to have very short TTL for testing
78
+ const shortTTLConfig = {
79
+ dir: tmpDir,
80
+ ttl: {
81
+ workflows: 0, // Expire immediately
82
+ rules: 0,
83
+ documents: 0,
84
+ memories: 0,
85
+ },
86
+ preload: [],
87
+ };
88
+ const shortCacheManager = new CacheManager(db, shortTTLConfig);
89
+ await shortCacheManager.setDocument('workflow', 'test', 'content');
90
+ // Wait to ensure timestamp difference
91
+ await new Promise(resolve => setTimeout(resolve, 100));
92
+ const cached = await shortCacheManager.getDocument('workflow', 'test');
93
+ expect(cached).not.toBeNull();
94
+ expect(cached?.isExpired).toBe(true);
95
+ });
96
+ });
97
+ describe('setMemory and getMemory', () => {
98
+ it('should store and retrieve a memory', async () => {
99
+ await cacheManager.setMemory('test-memory', 'memory content');
100
+ const cached = await cacheManager.getMemory('test-memory');
101
+ expect(cached).not.toBeNull();
102
+ expect(cached?.data.content).toBe('memory content');
103
+ expect(cached?.data.name).toBe('test-memory');
104
+ expect(cached?.isExpired).toBe(false);
105
+ });
106
+ it('should store memory with metadata', async () => {
107
+ const metadata = { lastUpdated: Date.now() };
108
+ await cacheManager.setMemory('test-memory', 'content', undefined, metadata);
109
+ const cached = await cacheManager.getMemory('test-memory');
110
+ expect(cached?.data.metadata).toEqual(metadata);
111
+ });
112
+ it('should store memory with project scope', async () => {
113
+ await cacheManager.setMemory('patterns', 'project patterns', 'my-project');
114
+ const cached = await cacheManager.getMemory('patterns', 'my-project');
115
+ expect(cached).not.toBeNull();
116
+ expect(cached?.data.project).toBe('my-project');
117
+ });
118
+ it('should distinguish between global and project-scoped memories', async () => {
119
+ await cacheManager.setMemory('context', 'global context');
120
+ await cacheManager.setMemory('context', 'project context', 'my-project');
121
+ const globalMem = await cacheManager.getMemory('context');
122
+ const projectMem = await cacheManager.getMemory('context', 'my-project');
123
+ expect(globalMem?.data.content).toBe('global context');
124
+ expect(projectMem?.data.content).toBe('project context');
125
+ });
126
+ it('should return null for non-existent memory', async () => {
127
+ const cached = await cacheManager.getMemory('non-existent');
128
+ expect(cached).toBeNull();
129
+ });
130
+ it('should mark memory as expired after TTL', async () => {
131
+ const shortTTLConfig = {
132
+ dir: tmpDir,
133
+ ttl: {
134
+ workflows: 0,
135
+ rules: 0,
136
+ documents: 0,
137
+ memories: 0,
138
+ },
139
+ preload: [],
140
+ };
141
+ const shortCacheManager = new CacheManager(db, shortTTLConfig);
142
+ await shortCacheManager.setMemory('test', 'content');
143
+ await new Promise(resolve => setTimeout(resolve, 100));
144
+ const cached = await shortCacheManager.getMemory('test');
145
+ expect(cached).not.toBeNull();
146
+ expect(cached?.isExpired).toBe(true);
147
+ });
148
+ });
149
+ describe('cleanupExpiredEntries', () => {
150
+ it('should delete expired documents and memories', async () => {
151
+ const shortTTLConfig = {
152
+ dir: tmpDir,
153
+ ttl: {
154
+ workflows: 0,
155
+ rules: 0,
156
+ documents: 0,
157
+ memories: 0,
158
+ },
159
+ preload: [],
160
+ };
161
+ const shortCacheManager = new CacheManager(db, shortTTLConfig);
162
+ // Add some items that will immediately expire
163
+ await shortCacheManager.setDocument('workflow', 'test1', 'content1');
164
+ await shortCacheManager.setDocument('rule', 'test2', 'content2');
165
+ await shortCacheManager.setMemory('memory1', 'content3');
166
+ // Wait to ensure they're expired (need sufficient time for clock to advance)
167
+ await new Promise(resolve => setTimeout(resolve, 100));
168
+ // Clean up
169
+ const result = await shortCacheManager.cleanupExpiredEntries();
170
+ expect(result.documentsDeleted).toBe(2);
171
+ expect(result.memoriesDeleted).toBe(1);
172
+ // Verify they're gone
173
+ const doc1 = await shortCacheManager.getDocument('workflow', 'test1');
174
+ const mem1 = await shortCacheManager.getMemory('memory1');
175
+ expect(doc1).toBeNull();
176
+ expect(mem1).toBeNull();
177
+ });
178
+ it('should not delete non-expired items', async () => {
179
+ await cacheManager.setDocument('workflow', 'test', 'content');
180
+ await cacheManager.setMemory('memory', 'content');
181
+ const result = await cacheManager.cleanupExpiredEntries();
182
+ expect(result.documentsDeleted).toBe(0);
183
+ expect(result.memoriesDeleted).toBe(0);
184
+ // Verify they're still there
185
+ const doc = await cacheManager.getDocument('workflow', 'test');
186
+ const mem = await cacheManager.getMemory('memory');
187
+ expect(doc).not.toBeNull();
188
+ expect(mem).not.toBeNull();
189
+ });
190
+ });
191
+ describe('cache age calculation', () => {
192
+ it('should calculate age correctly', async () => {
193
+ await cacheManager.setDocument('workflow', 'test', 'content');
194
+ // Wait 100ms
195
+ await new Promise(resolve => setTimeout(resolve, 100));
196
+ const cached = await cacheManager.getDocument('workflow', 'test');
197
+ expect(cached?.age).toBeGreaterThanOrEqual(0);
198
+ expect(cached?.age).toBeLessThan(1); // Less than 1 second
199
+ });
200
+ });
201
+ });
202
+ //# sourceMappingURL=cache-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-manager.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/cache-manager.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=connection-manager.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-manager.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/connection-manager.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { ConnectionManager } from '../connection-manager.js';
3
+ describe('ConnectionManager', () => {
4
+ let mockClient;
5
+ let connectionManager;
6
+ let config;
7
+ beforeEach(() => {
8
+ config = {
9
+ healthCheckInterval: 100, // Short interval for testing
10
+ timeout: 5000,
11
+ maxReconnectAttempts: 3,
12
+ reconnectBackoff: {
13
+ initial: 50,
14
+ multiplier: 2,
15
+ max: 500,
16
+ },
17
+ };
18
+ mockClient = {
19
+ healthCheck: vi.fn().mockResolvedValue(true),
20
+ };
21
+ connectionManager = new ConnectionManager(mockClient, config);
22
+ });
23
+ afterEach(() => {
24
+ connectionManager.stop();
25
+ });
26
+ describe('getState', () => {
27
+ it('should return initial state as disconnected', () => {
28
+ expect(connectionManager.getState()).toBe('disconnected');
29
+ });
30
+ });
31
+ describe('start and healthCheck', () => {
32
+ it('should transition to connected on successful health check', async () => {
33
+ await connectionManager.start();
34
+ expect(connectionManager.getState()).toBe('connected');
35
+ expect(mockClient.healthCheck).toHaveBeenCalled();
36
+ });
37
+ it('should transition to reconnecting after failed health check', async () => {
38
+ mockClient.healthCheck = vi.fn().mockRejectedValue(new Error('Connection failed'));
39
+ await connectionManager.start();
40
+ // After health check fails, it immediately schedules reconnect
41
+ expect(connectionManager.getState()).toBe('reconnecting');
42
+ });
43
+ it('should emit stateChange event on state transition', async () => {
44
+ const stateChangeListener = vi.fn();
45
+ connectionManager.on('stateChange', stateChangeListener);
46
+ await connectionManager.start();
47
+ expect(stateChangeListener).toHaveBeenCalledWith({
48
+ from: 'disconnected',
49
+ to: 'connected',
50
+ });
51
+ });
52
+ it('should emit connected event with latency', async () => {
53
+ const connectedListener = vi.fn();
54
+ connectionManager.on('connected', connectedListener);
55
+ await connectionManager.start();
56
+ expect(connectedListener).toHaveBeenCalled();
57
+ const call = connectedListener.mock.calls[0][0];
58
+ expect(call).toHaveProperty('latencyMs');
59
+ expect(typeof call.latencyMs).toBe('number');
60
+ });
61
+ it('should emit disconnected event on failure', async () => {
62
+ mockClient.healthCheck = vi.fn().mockRejectedValue(new Error('Failed'));
63
+ const disconnectedListener = vi.fn();
64
+ connectionManager.on('disconnected', disconnectedListener);
65
+ await connectionManager.start();
66
+ expect(disconnectedListener).toHaveBeenCalled();
67
+ expect(disconnectedListener.mock.calls[0][0]).toHaveProperty('error');
68
+ });
69
+ });
70
+ describe('stop', () => {
71
+ it('should transition to offline state', () => {
72
+ connectionManager.stop();
73
+ expect(connectionManager.getState()).toBe('offline');
74
+ });
75
+ it('should stop health check timer', async () => {
76
+ await connectionManager.start();
77
+ const healthCheckCount = mockClient.healthCheck.mock.calls.length;
78
+ connectionManager.stop();
79
+ // Wait longer than health check interval
80
+ await new Promise(resolve => setTimeout(resolve, 300));
81
+ // Health check should not have been called again
82
+ expect(mockClient.healthCheck.mock.calls.length).toBe(healthCheckCount);
83
+ });
84
+ });
85
+ describe('reconnection logic', () => {
86
+ it('should attempt to reconnect on health check failure', async () => {
87
+ let callCount = 0;
88
+ mockClient.healthCheck = vi.fn().mockImplementation(() => {
89
+ callCount++;
90
+ if (callCount === 1) {
91
+ return Promise.reject(new Error('Failed'));
92
+ }
93
+ return Promise.resolve(true);
94
+ });
95
+ const reconnectingListener = vi.fn();
96
+ connectionManager.on('reconnecting', reconnectingListener);
97
+ await connectionManager.start();
98
+ // Wait for reconnect attempt
99
+ await new Promise(resolve => setTimeout(resolve, 100));
100
+ expect(reconnectingListener).toHaveBeenCalled();
101
+ expect(reconnectingListener.mock.calls[0][0]).toHaveProperty('attempt');
102
+ expect(reconnectingListener.mock.calls[0][0]).toHaveProperty('delay');
103
+ });
104
+ it('should use exponential backoff for reconnection', async () => {
105
+ mockClient.healthCheck = vi.fn().mockRejectedValue(new Error('Failed'));
106
+ const reconnectingListener = vi.fn();
107
+ connectionManager.on('reconnecting', reconnectingListener);
108
+ await connectionManager.start();
109
+ // Wait for multiple reconnect attempts
110
+ await new Promise(resolve => setTimeout(resolve, 500));
111
+ const delays = reconnectingListener.mock.calls.map(call => call[0].delay);
112
+ // Delays should increase exponentially
113
+ for (let i = 1; i < delays.length; i++) {
114
+ expect(delays[i]).toBeGreaterThan(delays[i - 1]);
115
+ }
116
+ });
117
+ it('should emit maxReconnectAttemptsReached after max attempts', async () => {
118
+ mockClient.healthCheck = vi.fn().mockRejectedValue(new Error('Failed'));
119
+ const maxAttemptsListener = vi.fn();
120
+ connectionManager.on('maxReconnectAttemptsReached', maxAttemptsListener);
121
+ await connectionManager.start();
122
+ // Wait for all reconnect attempts
123
+ await new Promise(resolve => setTimeout(resolve, 2000));
124
+ expect(maxAttemptsListener).toHaveBeenCalled();
125
+ expect(connectionManager.getState()).toBe('offline');
126
+ });
127
+ it('should reset reconnect attempts on successful connection', async () => {
128
+ let callCount = 0;
129
+ mockClient.healthCheck = vi.fn().mockImplementation(() => {
130
+ callCount++;
131
+ // Fail first two, then succeed
132
+ if (callCount <= 2) {
133
+ return Promise.reject(new Error('Failed'));
134
+ }
135
+ return Promise.resolve(true);
136
+ });
137
+ await connectionManager.start();
138
+ // Wait for reconnection
139
+ await new Promise(resolve => setTimeout(resolve, 300));
140
+ expect(connectionManager.getState()).toBe('connected');
141
+ });
142
+ });
143
+ describe('execute', () => {
144
+ it('should execute function when connected', async () => {
145
+ await connectionManager.start();
146
+ const testFn = vi.fn().mockResolvedValue('result');
147
+ const result = await connectionManager.execute(testFn);
148
+ expect(result).toBe('result');
149
+ expect(testFn).toHaveBeenCalled();
150
+ });
151
+ it('should throw error when not connected', async () => {
152
+ const testFn = vi.fn();
153
+ await expect(connectionManager.execute(testFn)).rejects.toThrow('QwickBrain unavailable');
154
+ expect(testFn).not.toHaveBeenCalled();
155
+ });
156
+ it('should record failure if execution throws', async () => {
157
+ await connectionManager.start();
158
+ const testFn = vi.fn().mockRejectedValue(new Error('Execution failed'));
159
+ await expect(connectionManager.execute(testFn)).rejects.toThrow('Execution failed');
160
+ // Should transition to reconnecting after failure
161
+ expect(connectionManager.getState()).toBe('reconnecting');
162
+ });
163
+ it('should wait for state transitions to complete (race condition test)', async () => {
164
+ await connectionManager.start();
165
+ // Simulate rapid state changes
166
+ connectionManager.setState('disconnected');
167
+ connectionManager.setState('connected');
168
+ const testFn = vi.fn().mockResolvedValue('result');
169
+ // This should wait for state transitions to settle
170
+ const result = await connectionManager.execute(testFn);
171
+ expect(result).toBe('result');
172
+ });
173
+ });
174
+ describe('recordFailure', () => {
175
+ it('should transition to reconnecting when connected', () => {
176
+ connectionManager['state'] = 'connected';
177
+ connectionManager.recordFailure();
178
+ // After failure, it immediately schedules reconnect
179
+ expect(connectionManager.getState()).toBe('reconnecting');
180
+ });
181
+ it('should not affect offline state', () => {
182
+ connectionManager.stop();
183
+ connectionManager.recordFailure();
184
+ expect(connectionManager.getState()).toBe('offline');
185
+ });
186
+ });
187
+ });
188
+ //# sourceMappingURL=connection-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-manager.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/connection-manager.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAI7D,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,IAAI,UAA4B,CAAC;IACjC,IAAI,iBAAoC,CAAC;IACzC,IAAI,MAA4B,CAAC;IAEjC,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,GAAG;YACP,mBAAmB,EAAE,GAAG,EAAE,6BAA6B;YACvD,OAAO,EAAE,IAAI;YACb,oBAAoB,EAAE,CAAC;YACvB,gBAAgB,EAAE;gBAChB,OAAO,EAAE,EAAE;gBACX,UAAU,EAAE,CAAC;gBACb,GAAG,EAAE,GAAG;aACT;SACF,CAAC;QAEF,UAAU,GAAG;YACX,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC;SACd,CAAC;QAEjC,iBAAiB,GAAG,IAAI,iBAAiB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,iBAAiB,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;YACzE,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACvD,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;YAC3E,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;YAEnF,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,+DAA+D;YAC/D,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;YACjE,MAAM,mBAAmB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;YAEzD,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,MAAM,CAAC,mBAAmB,CAAC,CAAC,oBAAoB,CAAC;gBAC/C,IAAI,EAAE,cAAc;gBACpB,EAAE,EAAE,WAAW;aAChB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;YACxD,MAAM,iBAAiB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;YAErD,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,MAAM,CAAC,iBAAiB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YACzC,MAAM,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YACxE,MAAM,oBAAoB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;YAE3D,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,MAAM,CAAC,oBAAoB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAChD,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAEzB,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;YAC9C,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,gBAAgB,GAAI,UAAU,CAAC,WAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;YAE3E,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAEzB,yCAAyC;YACzC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAEvD,iDAAiD;YACjD,MAAM,CAAE,UAAU,CAAC,WAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACnF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;YACnE,IAAI,SAAS,GAAG,CAAC,CAAC;YAClB,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE;gBACvD,SAAS,EAAE,CAAC;gBACZ,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;oBACpB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7C,CAAC;gBACD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC,CAAC,CAAC;YAEH,MAAM,oBAAoB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;YAE3D,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,6BAA6B;YAC7B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAEvD,MAAM,CAAC,oBAAoB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAChD,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;YACxE,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;YAC/D,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAExE,MAAM,oBAAoB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;YAE3D,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,uCAAuC;YACvC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAEvD,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAE1E,uCAAuC;YACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACvC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACnD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;YAC1E,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAExE,MAAM,mBAAmB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,iBAAiB,CAAC,EAAE,CAAC,6BAA6B,EAAE,mBAAmB,CAAC,CAAC;YAEzE,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,kCAAkC;YAClC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAExD,MAAM,CAAC,mBAAmB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAC/C,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;YACxE,IAAI,SAAS,GAAG,CAAC,CAAC;YAClB,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE;gBACvD,SAAS,EAAE,CAAC;gBACZ,+BAA+B;gBAC/B,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;oBACnB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7C,CAAC;gBACD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC,CAAC,CAAC;YAEH,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,wBAAwB;YACxB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAEvD,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;QACvB,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAEvD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAEvB,MAAM,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;YAC1F,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAExE,MAAM,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;YAEpF,kDAAkD;YAClD,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;YACnF,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAEhC,+BAA+B;YAC/B,iBAAiB,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;YAC3C,iBAAiB,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAExC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YAEnD,mDAAmD;YACnD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAEvD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,iBAAiB,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC;YAEzC,iBAAiB,CAAC,aAAa,EAAE,CAAC;YAElC,oDAAoD;YACpD,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAEzB,iBAAiB,CAAC,aAAa,EAAE,CAAC;YAElC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=proxy-server.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-server.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/proxy-server.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,205 @@
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 { ProxyServer } from '../proxy-server.js';
7
+ // Mock MCP SDK
8
+ vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
9
+ Client: vi.fn().mockImplementation(() => ({
10
+ connect: vi.fn().mockResolvedValue(undefined),
11
+ close: vi.fn().mockResolvedValue(undefined),
12
+ callTool: vi.fn(),
13
+ listTools: vi.fn().mockResolvedValue({ tools: [] }),
14
+ })),
15
+ }));
16
+ vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
17
+ Server: vi.fn().mockImplementation(() => ({
18
+ setRequestHandler: vi.fn(),
19
+ connect: vi.fn().mockResolvedValue(undefined),
20
+ close: vi.fn().mockResolvedValue(undefined),
21
+ })),
22
+ }));
23
+ vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
24
+ StdioServerTransport: vi.fn(),
25
+ }));
26
+ vi.mock('@modelcontextprotocol/sdk/client/sse.js', () => ({
27
+ SSEClientTransport: vi.fn(),
28
+ }));
29
+ describe('ProxyServer', () => {
30
+ let tmpDir;
31
+ let config;
32
+ let db;
33
+ let server;
34
+ beforeEach(() => {
35
+ tmpDir = mkdtempSync(join(tmpdir(), 'proxy-test-'));
36
+ const dbResult = createDatabase(tmpDir);
37
+ db = dbResult.db;
38
+ // Run migrations to create tables
39
+ runMigrations(db);
40
+ config = {
41
+ qwickbrain: {
42
+ mode: 'sse',
43
+ url: 'http://test.local:3000',
44
+ },
45
+ cache: {
46
+ dir: tmpDir,
47
+ ttl: {
48
+ workflows: 3600,
49
+ rules: 3600,
50
+ documents: 1800,
51
+ memories: 900,
52
+ },
53
+ preload: [],
54
+ },
55
+ connection: {
56
+ healthCheckInterval: 30000,
57
+ timeout: 5000,
58
+ maxReconnectAttempts: 5,
59
+ reconnectBackoff: {
60
+ initial: 1000,
61
+ multiplier: 2,
62
+ max: 30000,
63
+ },
64
+ },
65
+ sync: {
66
+ interval: 60000,
67
+ batchSize: 10,
68
+ },
69
+ };
70
+ server = new ProxyServer(db, config);
71
+ });
72
+ afterEach(async () => {
73
+ await server.stop();
74
+ rmSync(tmpDir, { recursive: true, force: true });
75
+ });
76
+ describe('initialization', () => {
77
+ it('should initialize with all components', () => {
78
+ expect(server['server']).toBeDefined();
79
+ expect(server['connectionManager']).toBeDefined();
80
+ expect(server['cacheManager']).toBeDefined();
81
+ expect(server['qwickbrainClient']).toBeDefined();
82
+ });
83
+ it('should set up MCP request handlers', () => {
84
+ const setRequestHandlerMock = server['server'].setRequestHandler;
85
+ // Should have set up handlers for ListTools and CallTool
86
+ expect(setRequestHandlerMock).toHaveBeenCalled();
87
+ expect(setRequestHandlerMock.mock.calls.length).toBeGreaterThanOrEqual(2);
88
+ });
89
+ });
90
+ describe('cache cleanup on startup', () => {
91
+ it('should clean up expired cache entries on start', async () => {
92
+ // Add some expired entries
93
+ const shortTTLConfig = { ...config };
94
+ shortTTLConfig.cache.ttl = {
95
+ workflows: 0,
96
+ rules: 0,
97
+ documents: 0,
98
+ memories: 0,
99
+ };
100
+ const tempServer = new ProxyServer(db, shortTTLConfig);
101
+ await tempServer['cacheManager'].setDocument('workflow', 'test', 'content');
102
+ await new Promise(resolve => setTimeout(resolve, 100));
103
+ // Verify entry exists before cleanup
104
+ const beforeCleanup = await tempServer['cacheManager'].getDocument('workflow', 'test');
105
+ expect(beforeCleanup).not.toBeNull();
106
+ await tempServer.start();
107
+ // Entry should be removed after cleanup
108
+ const afterCleanup = await tempServer['cacheManager'].getDocument('workflow', 'test');
109
+ expect(afterCleanup).toBeNull();
110
+ await tempServer.stop();
111
+ });
112
+ });
113
+ describe('graceful degradation', () => {
114
+ it('should serve stale cache when disconnected', async () => {
115
+ // Create server with short TTL to make cache expire
116
+ const shortTTLConfig = { ...config };
117
+ shortTTLConfig.cache.ttl = {
118
+ workflows: 0,
119
+ rules: 0,
120
+ documents: 0,
121
+ memories: 0,
122
+ };
123
+ const tempServer = new ProxyServer(db, shortTTLConfig);
124
+ // Add item to cache
125
+ await tempServer['cacheManager'].setDocument('workflow', 'test', 'cached content');
126
+ // Wait for cache to expire
127
+ await new Promise(resolve => setTimeout(resolve, 100));
128
+ // Simulate disconnected state
129
+ tempServer['connectionManager']['state'] = 'disconnected';
130
+ const result = await tempServer['handleGetDocument']('workflow', 'test');
131
+ expect(result.data.content).toBe('cached content');
132
+ expect(result._metadata.source).toBe('stale_cache');
133
+ await tempServer.stop();
134
+ });
135
+ it('should return error when no cache available and disconnected', async () => {
136
+ // Simulate disconnected state with no cache
137
+ server['connectionManager']['state'] = 'disconnected';
138
+ const result = await server['handleGetDocument']('workflow', 'non-existent');
139
+ expect(result.error).toBeDefined();
140
+ expect(result.error?.code).toBe('UNAVAILABLE');
141
+ expect(result.error?.message).toContain('QwickBrain unavailable');
142
+ });
143
+ });
144
+ describe('metadata', () => {
145
+ it('should include correct metadata for cache hit', async () => {
146
+ await server['cacheManager'].setDocument('workflow', 'test', 'content');
147
+ // Simulate connected state
148
+ server['connectionManager']['state'] = 'connected';
149
+ const result = await server['handleGetDocument']('workflow', 'test');
150
+ expect(result._metadata.source).toBe('cache');
151
+ expect(result._metadata.age_seconds).toBeDefined();
152
+ expect(typeof result._metadata.age_seconds).toBe('number');
153
+ });
154
+ it('should create metadata with correct source', () => {
155
+ const cacheMeta = server['createMetadata']('cache', 100);
156
+ expect(cacheMeta).toEqual({
157
+ source: 'cache',
158
+ age_seconds: 100,
159
+ status: server['connectionManager'].getState(),
160
+ });
161
+ const liveMeta = server['createMetadata']('live');
162
+ expect(liveMeta).toEqual({
163
+ source: 'live',
164
+ status: server['connectionManager'].getState(),
165
+ });
166
+ const staleMeta = server['createMetadata']('stale_cache', 500);
167
+ expect(staleMeta).toEqual({
168
+ source: 'stale_cache',
169
+ age_seconds: 500,
170
+ status: server['connectionManager'].getState(),
171
+ });
172
+ });
173
+ });
174
+ describe('error handling', () => {
175
+ it('should handle QwickBrain errors gracefully', async () => {
176
+ // Mock client to throw error
177
+ server['qwickbrainClient'].getDocument = vi.fn().mockRejectedValue(new Error('Network error'));
178
+ // Simulate connected state
179
+ server['connectionManager']['state'] = 'connected';
180
+ const result = await server['handleGetDocument']('workflow', 'test');
181
+ // Should fall back to error response when remote fails and no cache
182
+ expect(result.error).toBeDefined();
183
+ expect(result.error?.code).toBe('UNAVAILABLE');
184
+ });
185
+ });
186
+ describe('project scoping', () => {
187
+ it('should handle project-scoped documents', async () => {
188
+ await server['cacheManager'].setDocument('design', 'test-design', 'design content', 'my-project');
189
+ server['connectionManager']['state'] = 'connected';
190
+ const result = await server['handleGetDocument']('design', 'test-design', 'my-project');
191
+ expect(result.data.content).toBe('design content');
192
+ expect(result.data.project).toBe('my-project');
193
+ });
194
+ it('should distinguish global vs project-scoped items', async () => {
195
+ await server['cacheManager'].setDocument('rule', 'test', 'global rule');
196
+ await server['cacheManager'].setDocument('rule', 'test', 'project rule', 'proj1');
197
+ server['connectionManager']['state'] = 'connected';
198
+ const globalResult = await server['handleGetDocument']('rule', 'test');
199
+ const projectResult = await server['handleGetDocument']('rule', 'test', 'proj1');
200
+ expect(globalResult.data.content).toBe('global rule');
201
+ expect(projectResult.data.content).toBe('project rule');
202
+ });
203
+ });
204
+ });
205
+ //# sourceMappingURL=proxy-server.test.js.map