@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,191 @@
1
+ import { eq } from 'drizzle-orm';
2
+ import { syncQueue } from '../db/schema.js';
3
+ export class WriteQueueManager {
4
+ db;
5
+ qwickbrainClient;
6
+ maxAttempts = 3;
7
+ isSyncing = false;
8
+ constructor(db, qwickbrainClient) {
9
+ this.db = db;
10
+ this.qwickbrainClient = qwickbrainClient;
11
+ }
12
+ /**
13
+ * Queue a write operation for later sync
14
+ */
15
+ async queueOperation(operation, payload) {
16
+ await this.db.insert(syncQueue).values({
17
+ operation,
18
+ payload: JSON.stringify(payload),
19
+ status: 'pending',
20
+ attempts: 0,
21
+ });
22
+ console.error(`Queued operation: ${operation}`);
23
+ }
24
+ /**
25
+ * Get count of pending operations
26
+ */
27
+ async getPendingCount() {
28
+ const result = await this.db
29
+ .select()
30
+ .from(syncQueue)
31
+ .where(eq(syncQueue.status, 'pending'));
32
+ return result.length;
33
+ }
34
+ /**
35
+ * Sync all pending operations
36
+ * Returns number of operations synced successfully
37
+ */
38
+ async syncPendingOperations() {
39
+ if (this.isSyncing) {
40
+ console.error('Sync already in progress, skipping');
41
+ return { synced: 0, failed: 0 };
42
+ }
43
+ this.isSyncing = true;
44
+ let synced = 0;
45
+ let failed = 0;
46
+ try {
47
+ // Get all pending operations, ordered by creation time (FIFO)
48
+ const pending = await this.db
49
+ .select()
50
+ .from(syncQueue)
51
+ .where(eq(syncQueue.status, 'pending'))
52
+ .orderBy(syncQueue.createdAt);
53
+ console.error(`Syncing ${pending.length} pending operations...`);
54
+ for (const item of pending) {
55
+ try {
56
+ await this.executeOperation(item);
57
+ // Mark as completed
58
+ await this.db
59
+ .update(syncQueue)
60
+ .set({ status: 'completed' })
61
+ .where(eq(syncQueue.id, item.id));
62
+ synced++;
63
+ console.error(`Synced operation ${item.id}: ${item.operation}`);
64
+ }
65
+ catch (error) {
66
+ const errorMessage = error instanceof Error ? error.message : String(error);
67
+ const newAttempts = item.attempts + 1;
68
+ if (newAttempts >= this.maxAttempts) {
69
+ // Max attempts reached, mark as failed
70
+ await this.db
71
+ .update(syncQueue)
72
+ .set({
73
+ status: 'failed',
74
+ error: errorMessage,
75
+ attempts: newAttempts,
76
+ lastAttemptAt: new Date(),
77
+ })
78
+ .where(eq(syncQueue.id, item.id));
79
+ failed++;
80
+ console.error(`Operation ${item.id} failed after ${newAttempts} attempts: ${errorMessage}`);
81
+ }
82
+ else {
83
+ // Increment attempts, keep as pending
84
+ await this.db
85
+ .update(syncQueue)
86
+ .set({
87
+ attempts: newAttempts,
88
+ lastAttemptAt: new Date(),
89
+ error: errorMessage,
90
+ })
91
+ .where(eq(syncQueue.id, item.id));
92
+ console.error(`Operation ${item.id} failed (attempt ${newAttempts}/${this.maxAttempts}): ${errorMessage}`);
93
+ }
94
+ }
95
+ }
96
+ // Clean up completed operations (keep failed ones for inspection)
97
+ await this.cleanupCompleted();
98
+ console.error(`Sync complete: ${synced} synced, ${failed} failed`);
99
+ return { synced, failed };
100
+ }
101
+ finally {
102
+ this.isSyncing = false;
103
+ }
104
+ }
105
+ /**
106
+ * Execute a single queued operation
107
+ */
108
+ async executeOperation(item) {
109
+ const payload = JSON.parse(item.payload);
110
+ switch (item.operation) {
111
+ case 'create_document':
112
+ case 'update_document': {
113
+ const p = payload;
114
+ await this.qwickbrainClient.createDocument(p.docType, p.name, p.content, p.project, p.metadata);
115
+ break;
116
+ }
117
+ case 'set_memory':
118
+ case 'update_memory': {
119
+ const p = payload;
120
+ await this.qwickbrainClient.setMemory(p.name, p.content, p.project, p.metadata);
121
+ break;
122
+ }
123
+ case 'delete_document': {
124
+ const p = payload;
125
+ await this.qwickbrainClient.deleteDocument(p.docType, p.name, p.project);
126
+ break;
127
+ }
128
+ case 'delete_memory': {
129
+ const p = payload;
130
+ await this.qwickbrainClient.deleteMemory(p.name, p.project);
131
+ break;
132
+ }
133
+ default:
134
+ throw new Error(`Unknown operation: ${item.operation}`);
135
+ }
136
+ }
137
+ /**
138
+ * Clean up completed operations
139
+ */
140
+ async cleanupCompleted() {
141
+ await this.db.delete(syncQueue).where(eq(syncQueue.status, 'completed'));
142
+ }
143
+ /**
144
+ * Get failed operations for inspection
145
+ */
146
+ async getFailedOperations() {
147
+ return await this.db
148
+ .select()
149
+ .from(syncQueue)
150
+ .where(eq(syncQueue.status, 'failed'))
151
+ .orderBy(syncQueue.createdAt);
152
+ }
153
+ /**
154
+ * Retry a specific failed operation
155
+ */
156
+ async retryOperation(id) {
157
+ await this.db
158
+ .update(syncQueue)
159
+ .set({
160
+ status: 'pending',
161
+ attempts: 0,
162
+ error: null,
163
+ lastAttemptAt: null,
164
+ })
165
+ .where(eq(syncQueue.id, id));
166
+ console.error(`Operation ${id} reset to pending for retry`);
167
+ }
168
+ /**
169
+ * Clear all failed operations
170
+ */
171
+ async clearFailed() {
172
+ const failed = await this.getFailedOperations();
173
+ await this.db.delete(syncQueue).where(eq(syncQueue.status, 'failed'));
174
+ console.error(`Cleared ${failed.length} failed operations`);
175
+ return failed.length;
176
+ }
177
+ /**
178
+ * Get queue statistics
179
+ */
180
+ async getQueueStats() {
181
+ const all = await this.db.select().from(syncQueue);
182
+ const pending = all.filter(item => item.status === 'pending').length;
183
+ const failed = all.filter(item => item.status === 'failed').length;
184
+ return {
185
+ pending,
186
+ failed,
187
+ total: all.length,
188
+ };
189
+ }
190
+ }
191
+ //# sourceMappingURL=write-queue-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-queue-manager.js","sourceRoot":"","sources":["../../src/lib/write-queue-manager.ts"],"names":[],"mappings":"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"}
@@ -21,40 +21,15 @@ export declare const ConfigSchema: z.ZodObject<{
21
21
  }>>;
22
22
  cache: z.ZodDefault<z.ZodObject<{
23
23
  dir: z.ZodOptional<z.ZodString>;
24
- ttl: z.ZodDefault<z.ZodObject<{
25
- workflows: z.ZodDefault<z.ZodNumber>;
26
- rules: z.ZodDefault<z.ZodNumber>;
27
- documents: z.ZodDefault<z.ZodNumber>;
28
- memories: z.ZodDefault<z.ZodNumber>;
29
- }, "strip", z.ZodTypeAny, {
30
- workflows: number;
31
- rules: number;
32
- documents: number;
33
- memories: number;
34
- }, {
35
- workflows?: number | undefined;
36
- rules?: number | undefined;
37
- documents?: number | undefined;
38
- memories?: number | undefined;
39
- }>>;
24
+ maxCacheSizeBytes: z.ZodDefault<z.ZodNumber>;
40
25
  preload: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
41
26
  }, "strip", z.ZodTypeAny, {
42
- ttl: {
43
- workflows: number;
44
- rules: number;
45
- documents: number;
46
- memories: number;
47
- };
27
+ maxCacheSizeBytes: number;
48
28
  preload: string[];
49
29
  dir?: string | undefined;
50
30
  }, {
51
31
  dir?: string | undefined;
52
- ttl?: {
53
- workflows?: number | undefined;
54
- rules?: number | undefined;
55
- documents?: number | undefined;
56
- memories?: number | undefined;
57
- } | undefined;
32
+ maxCacheSizeBytes?: number | undefined;
58
33
  preload?: string[] | undefined;
59
34
  }>>;
60
35
  connection: z.ZodDefault<z.ZodObject<{
@@ -105,12 +80,7 @@ export declare const ConfigSchema: z.ZodObject<{
105
80
  }>>;
106
81
  }, "strip", z.ZodTypeAny, {
107
82
  cache: {
108
- ttl: {
109
- workflows: number;
110
- rules: number;
111
- documents: number;
112
- memories: number;
113
- };
83
+ maxCacheSizeBytes: number;
114
84
  preload: string[];
115
85
  dir?: string | undefined;
116
86
  };
@@ -138,12 +108,7 @@ export declare const ConfigSchema: z.ZodObject<{
138
108
  }, {
139
109
  cache?: {
140
110
  dir?: string | undefined;
141
- ttl?: {
142
- workflows?: number | undefined;
143
- rules?: number | undefined;
144
- documents?: number | undefined;
145
- memories?: number | undefined;
146
- } | undefined;
111
+ maxCacheSizeBytes?: number | undefined;
147
112
  preload?: string[] | undefined;
148
113
  } | undefined;
149
114
  qwickbrain?: {
@@ -175,12 +140,12 @@ export declare const CacheConfigSchema: z.ZodObject<{
175
140
  preload: z.ZodBoolean;
176
141
  }, "strip", z.ZodTypeAny, {
177
142
  offline: boolean;
178
- ttl: number;
179
143
  preload: boolean;
144
+ ttl: number;
180
145
  }, {
181
146
  offline: boolean;
182
- ttl: number;
183
147
  preload: boolean;
148
+ ttl: number;
184
149
  }>;
185
150
  export type CacheConfig = z.infer<typeof CacheConfigSchema>;
186
151
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkCvB,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;EAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BvB,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;EAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
@@ -11,12 +11,7 @@ export const ConfigSchema = z.object({
11
11
  }).default({}),
12
12
  cache: z.object({
13
13
  dir: z.string().optional(),
14
- ttl: z.object({
15
- workflows: z.number().default(86400), // 24 hours
16
- rules: z.number().default(86400), // 24 hours
17
- documents: z.number().default(21600), // 6 hours
18
- memories: z.number().default(3600), // 1 hour
19
- }).default({}),
14
+ maxCacheSizeBytes: z.number().default(100 * 1024 * 1024), // 100MB default for dynamic tier
20
15
  preload: z.array(z.string()).default(['workflows', 'rules']),
21
16
  }).default({}),
22
17
  connection: z.object({
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;QACnD,6BAA6B;QAC7B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC9B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;QACpC,2CAA2C;QAC3C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;QAChC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC9B,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACd,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC1B,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,EAAE,iCAAiC;QAC3F,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;KAC7D,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACd,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnB,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,aAAa;QAC7D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,YAAY;QAC/C,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5C,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC;YACzB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW;YAC9C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,aAAa;YAC7C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;SAClC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;KACf,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY;QAClD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;KAClC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACf,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;CACrB,CAAC,CAAC"}
@@ -0,0 +1,94 @@
1
+ -- Migration: Remove TTL, add LRU fields (two-tier storage)
2
+ -- Phase 2: LRU Cache Implementation
3
+
4
+ -- Create new documents table with LRU fields
5
+ CREATE TABLE `documents_new` (
6
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
7
+ `doc_type` text NOT NULL,
8
+ `name` text NOT NULL,
9
+ `project` text,
10
+ `content` text NOT NULL,
11
+ `metadata` text,
12
+ `cached_at` integer DEFAULT (unixepoch()) NOT NULL,
13
+ `last_accessed_at` integer DEFAULT (unixepoch()) NOT NULL,
14
+ `is_critical` integer DEFAULT false NOT NULL,
15
+ `size_bytes` integer DEFAULT 0 NOT NULL,
16
+ `synced` integer DEFAULT true NOT NULL
17
+ );
18
+ --> statement-breakpoint
19
+
20
+ -- Copy data from old table, calculating size and critical flag
21
+ INSERT INTO `documents_new` (
22
+ id, doc_type, name, project, content, metadata, cached_at, last_accessed_at, is_critical, size_bytes, synced
23
+ )
24
+ SELECT
25
+ id,
26
+ doc_type,
27
+ name,
28
+ project,
29
+ content,
30
+ metadata,
31
+ cached_at,
32
+ cached_at as last_accessed_at, -- Initialize with cached_at
33
+ CASE
34
+ WHEN doc_type IN ('workflow', 'rule', 'agent', 'template') THEN true
35
+ ELSE false
36
+ END as is_critical,
37
+ length(content) as size_bytes, -- Calculate size in bytes
38
+ synced
39
+ FROM `documents`;
40
+ --> statement-breakpoint
41
+
42
+ -- Drop old table
43
+ DROP TABLE `documents`;
44
+ --> statement-breakpoint
45
+
46
+ -- Rename new table
47
+ ALTER TABLE `documents_new` RENAME TO `documents`;
48
+ --> statement-breakpoint
49
+
50
+ -- Recreate unique index
51
+ CREATE UNIQUE INDEX `documents_doc_type_name_project_unique` ON `documents` (`doc_type`,`name`,`project`);
52
+ --> statement-breakpoint
53
+
54
+ -- Create new memories table with LRU fields
55
+ CREATE TABLE `memories_new` (
56
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
57
+ `name` text NOT NULL,
58
+ `project` text,
59
+ `content` text NOT NULL,
60
+ `metadata` text,
61
+ `cached_at` integer DEFAULT (unixepoch()) NOT NULL,
62
+ `last_accessed_at` integer DEFAULT (unixepoch()) NOT NULL,
63
+ `size_bytes` integer DEFAULT 0 NOT NULL,
64
+ `synced` integer DEFAULT true NOT NULL
65
+ );
66
+ --> statement-breakpoint
67
+
68
+ -- Copy data from old table, calculating size
69
+ INSERT INTO `memories_new` (
70
+ id, name, project, content, metadata, cached_at, last_accessed_at, size_bytes, synced
71
+ )
72
+ SELECT
73
+ id,
74
+ name,
75
+ project,
76
+ content,
77
+ metadata,
78
+ cached_at,
79
+ cached_at as last_accessed_at, -- Initialize with cached_at
80
+ length(content) as size_bytes, -- Calculate size in bytes
81
+ synced
82
+ FROM `memories`;
83
+ --> statement-breakpoint
84
+
85
+ -- Drop old table
86
+ DROP TABLE `memories`;
87
+ --> statement-breakpoint
88
+
89
+ -- Rename new table
90
+ ALTER TABLE `memories_new` RENAME TO `memories`;
91
+ --> statement-breakpoint
92
+
93
+ -- Recreate unique index
94
+ CREATE UNIQUE INDEX `memories_name_project_unique` ON `memories` (`name`,`project`);
@@ -15,6 +15,13 @@
15
15
  "when": 1767846667620,
16
16
  "tag": "0001_goofy_invisible_woman",
17
17
  "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "6",
22
+ "when": 1736478000000,
23
+ "tag": "0002_lru_cache_migration",
24
+ "breakpoints": true
18
25
  }
19
26
  ]
20
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qwickapps/qwickbrain-proxy",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Local MCP proxy for QwickBrain with caching and resilience",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,9 @@
18
18
  "typecheck": "tsc --noEmit",
19
19
  "db:generate": "drizzle-kit generate",
20
20
  "db:migrate": "drizzle-kit migrate",
21
- "db:studio": "drizzle-kit studio"
21
+ "db:studio": "drizzle-kit studio",
22
+ "rebuild-sqlite": "bash scripts/rebuild-sqlite.sh",
23
+ "postinstall": "bash scripts/rebuild-sqlite.sh || true"
22
24
  },
23
25
  "keywords": [
24
26
  "mcp",
@@ -52,11 +54,13 @@
52
54
  },
53
55
  "devDependencies": {
54
56
  "@types/better-sqlite3": "^7.6.0",
57
+ "@types/eventsource": "^3.0.0",
55
58
  "@types/node": "^20.0.0",
56
59
  "@typescript-eslint/eslint-plugin": "^7.0.0",
57
60
  "@typescript-eslint/parser": "^7.0.0",
58
61
  "drizzle-kit": "^0.31.8",
59
62
  "eslint": "^8.57.0",
63
+ "eventsource": "^4.0.0",
60
64
  "prettier": "^3.0.0",
61
65
  "tsx": "^4.0.0",
62
66
  "typescript": "^5.3.0",
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "Rebuilding better-sqlite3 native bindings..."
5
+
6
+ # Ensure Python has setuptools (needed for node-gyp with Python 3.13+)
7
+ python3 -m pip install --quiet setuptools --break-system-packages 2>/dev/null || true
8
+
9
+ # Navigate to monorepo root and rebuild better-sqlite3
10
+ cd "$(dirname "$0")/../../.."
11
+
12
+ # Rebuild better-sqlite3 for the entire monorepo
13
+ pnpm exec node-gyp rebuild --directory node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3 --release 2>/dev/null || {
14
+ # Fallback: find and rebuild better-sqlite3 directly
15
+ SQLITE_DIR=$(find node_modules/.pnpm -type d -path "*/better-sqlite3@*/node_modules/better-sqlite3" -print -quit)
16
+ if [ -n "$SQLITE_DIR" ]; then
17
+ echo "Building in: $SQLITE_DIR"
18
+ cd "$SQLITE_DIR"
19
+ npm run build-release
20
+ else
21
+ echo "Warning: Could not find better-sqlite3 to rebuild"
22
+ exit 1
23
+ fi
24
+ }
25
+
26
+ echo "✓ better-sqlite3 rebuilt successfully"
package/src/db/schema.ts CHANGED
@@ -3,6 +3,10 @@ import { integer, sqliteTable, text, unique } from 'drizzle-orm/sqlite-core';
3
3
 
4
4
  /**
5
5
  * Cached documents (workflows, rules, FRDs, designs, etc.)
6
+ *
7
+ * Two-tier storage:
8
+ * - Critical tier (isCritical=true): workflows, rules, agents, templates - never evicted, not counted toward limit
9
+ * - Dynamic tier (isCritical=false): other documents - LRU eviction when storage limit reached
6
10
  */
7
11
  export const documents = sqliteTable('documents', {
8
12
  id: integer('id').primaryKey({ autoIncrement: true }),
@@ -14,7 +18,13 @@ export const documents = sqliteTable('documents', {
14
18
  cachedAt: integer('cached_at', { mode: 'timestamp' })
15
19
  .notNull()
16
20
  .default(sql`(unixepoch())`),
17
- expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
21
+ lastAccessedAt: integer('last_accessed_at', { mode: 'timestamp' })
22
+ .notNull()
23
+ .default(sql`(unixepoch())`),
24
+ isCritical: integer('is_critical', { mode: 'boolean' })
25
+ .notNull()
26
+ .default(false),
27
+ sizeBytes: integer('size_bytes').notNull().default(0),
18
28
  synced: integer('synced', { mode: 'boolean' }).notNull().default(true),
19
29
  }, (table) => ({
20
30
  uniqueDocument: unique().on(table.docType, table.name, table.project),
@@ -22,6 +32,8 @@ export const documents = sqliteTable('documents', {
22
32
 
23
33
  /**
24
34
  * Cached memories (project context, patterns, decisions)
35
+ *
36
+ * Memories are always dynamic tier - LRU eviction applies
25
37
  */
26
38
  export const memories = sqliteTable('memories', {
27
39
  id: integer('id').primaryKey({ autoIncrement: true }),
@@ -32,7 +44,10 @@ export const memories = sqliteTable('memories', {
32
44
  cachedAt: integer('cached_at', { mode: 'timestamp' })
33
45
  .notNull()
34
46
  .default(sql`(unixepoch())`),
35
- expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
47
+ lastAccessedAt: integer('last_accessed_at', { mode: 'timestamp' })
48
+ .notNull()
49
+ .default(sql`(unixepoch())`),
50
+ sizeBytes: integer('size_bytes').notNull().default(0),
36
51
  synced: integer('synced', { mode: 'boolean' }).notNull().default(true),
37
52
  }, (table) => ({
38
53
  uniqueMemory: unique().on(table.name, table.project),