claude-memory-layer 1.0.0 → 1.0.2

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 (37) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/.history/package_20260201114632.json +46 -0
  3. package/dist/cli/index.js +360 -154
  4. package/dist/cli/index.js.map +4 -4
  5. package/dist/core/index.js +337 -161
  6. package/dist/core/index.js.map +3 -3
  7. package/dist/hooks/session-end.js +320 -130
  8. package/dist/hooks/session-end.js.map +4 -4
  9. package/dist/hooks/session-start.js +331 -138
  10. package/dist/hooks/session-start.js.map +4 -4
  11. package/dist/hooks/stop.js +320 -130
  12. package/dist/hooks/stop.js.map +4 -4
  13. package/dist/hooks/user-prompt-submit.js +320 -130
  14. package/dist/hooks/user-prompt-submit.js.map +4 -4
  15. package/dist/services/memory-service.js +349 -128
  16. package/dist/services/memory-service.js.map +4 -4
  17. package/package.json +1 -1
  18. package/src/cli/index.ts +84 -23
  19. package/src/core/consolidated-store.ts +33 -18
  20. package/src/core/continuity-manager.ts +12 -7
  21. package/src/core/db-wrapper.ts +112 -0
  22. package/src/core/edge-repo.ts +22 -13
  23. package/src/core/entity-repo.ts +23 -14
  24. package/src/core/event-store.ts +98 -72
  25. package/src/core/task/blocker-resolver.ts +17 -9
  26. package/src/core/task/task-matcher.ts +8 -6
  27. package/src/core/task/task-projector.ts +29 -16
  28. package/src/core/task/task-resolver.ts +17 -9
  29. package/src/core/vector-outbox.ts +29 -16
  30. package/src/core/vector-store.ts +23 -12
  31. package/src/core/vector-worker.ts +7 -4
  32. package/src/core/working-set-store.ts +31 -18
  33. package/src/hooks/session-end.ts +3 -2
  34. package/src/hooks/session-start.ts +12 -8
  35. package/src/hooks/stop.ts +3 -2
  36. package/src/hooks/user-prompt-submit.ts +3 -2
  37. package/src/services/memory-service.ts +158 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-layer",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Claude Code plugin that learns from conversations to provide personalized assistance",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -5,7 +5,10 @@
5
5
  */
6
6
 
7
7
  import { Command } from 'commander';
8
- import { getDefaultMemoryService } from '../services/memory-service.js';
8
+ import {
9
+ getDefaultMemoryService,
10
+ getMemoryServiceForProject
11
+ } from '../services/memory-service.js';
9
12
  import { createSessionHistoryImporter } from '../services/session-history-importer.js';
10
13
 
11
14
  const program = new Command();
@@ -24,8 +27,10 @@ program
24
27
  .option('-k, --top-k <number>', 'Number of results', '5')
25
28
  .option('-s, --min-score <number>', 'Minimum similarity score', '0.7')
26
29
  .option('--session <id>', 'Filter by session ID')
30
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
27
31
  .action(async (query: string, options) => {
28
- const service = getDefaultMemoryService();
32
+ const projectPath = options.project || process.cwd();
33
+ const service = getMemoryServiceForProject(projectPath);
29
34
 
30
35
  try {
31
36
  const result = await service.retrieveMemories(query, {
@@ -64,8 +69,10 @@ program
64
69
  .option('-l, --limit <number>', 'Number of events', '20')
65
70
  .option('--session <id>', 'Filter by session ID')
66
71
  .option('--type <type>', 'Filter by event type')
72
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
67
73
  .action(async (options) => {
68
- const service = getDefaultMemoryService();
74
+ const projectPath = options.project || process.cwd();
75
+ const service = getMemoryServiceForProject(projectPath);
69
76
 
70
77
  try {
71
78
  let events;
@@ -107,8 +114,10 @@ program
107
114
  program
108
115
  .command('stats')
109
116
  .description('View memory statistics')
110
- .action(async () => {
111
- const service = getDefaultMemoryService();
117
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
118
+ .action(async (options) => {
119
+ const projectPath = options.project || process.cwd();
120
+ const service = getMemoryServiceForProject(projectPath);
112
121
 
113
122
  try {
114
123
  const stats = await service.getStats();
@@ -139,8 +148,10 @@ program
139
148
  .option('--session <id>', 'Forget all events from a session')
140
149
  .option('--before <date>', 'Forget events before date (YYYY-MM-DD)')
141
150
  .option('--confirm', 'Skip confirmation')
151
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
142
152
  .action(async (eventId: string | undefined, options) => {
143
- const service = getDefaultMemoryService();
153
+ const projectPath = options.project || process.cwd();
154
+ const service = getMemoryServiceForProject(projectPath);
144
155
 
145
156
  try {
146
157
  if (!eventId && !options.session && !options.before) {
@@ -171,8 +182,10 @@ program
171
182
  program
172
183
  .command('process')
173
184
  .description('Process pending embeddings')
174
- .action(async () => {
175
- const service = getDefaultMemoryService();
185
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
186
+ .action(async (options) => {
187
+ const projectPath = options.project || process.cwd();
188
+ const service = getMemoryServiceForProject(projectPath);
176
189
 
177
190
  try {
178
191
  console.log('⏳ Processing pending embeddings...');
@@ -198,7 +211,11 @@ program
198
211
  .option('-l, --limit <number>', 'Limit messages per session')
199
212
  .option('-v, --verbose', 'Show detailed progress')
200
213
  .action(async (options) => {
201
- const service = getDefaultMemoryService();
214
+ // Determine target project path for storage
215
+ const targetProjectPath = options.project || process.cwd();
216
+
217
+ // Use project-specific memory service
218
+ const service = getMemoryServiceForProject(targetProjectPath);
202
219
  const importer = createSessionHistoryImporter(service);
203
220
 
204
221
  try {
@@ -208,9 +225,10 @@ program
208
225
 
209
226
  if (options.session) {
210
227
  // Import specific session file
211
- console.log(`\n📥 Importing session: ${options.session}\n`);
228
+ console.log(`\n📥 Importing session: ${options.session}`);
229
+ console.log(` Target project: ${targetProjectPath}\n`);
212
230
  result = await importer.importSessionFile(options.session, {
213
- projectPath: options.project,
231
+ projectPath: targetProjectPath,
214
232
  limit: options.limit ? parseInt(options.limit) : undefined,
215
233
  verbose: options.verbose
216
234
  });
@@ -223,11 +241,42 @@ program
223
241
  });
224
242
  } else if (options.all) {
225
243
  // Import all sessions from all projects
226
- console.log('\n📥 Importing all sessions from all projects\n');
227
- result = await importer.importAll({
244
+ // Note: --all imports to global storage for backward compatibility
245
+ console.log('\n📥 Importing all sessions from all projects');
246
+ console.log(' ⚠️ Using global storage (use -p for project-specific)\n');
247
+ const globalService = getDefaultMemoryService();
248
+ const globalImporter = createSessionHistoryImporter(globalService);
249
+ await globalService.initialize();
250
+ result = await globalImporter.importAll({
228
251
  limit: options.limit ? parseInt(options.limit) : undefined,
229
252
  verbose: options.verbose
230
253
  });
254
+
255
+ // Process embeddings
256
+ console.log('\n⏳ Processing embeddings...');
257
+ const embedCount = await globalService.processPendingEmbeddings();
258
+
259
+ // Show results
260
+ console.log('\n✅ Import Complete\n');
261
+ console.log(`Sessions processed: ${result.totalSessions}`);
262
+ console.log(`Total messages: ${result.totalMessages}`);
263
+ console.log(`Imported prompts: ${result.importedPrompts}`);
264
+ console.log(`Imported responses: ${result.importedResponses}`);
265
+ console.log(`Skipped duplicates: ${result.skippedDuplicates}`);
266
+ console.log(`Embeddings processed: ${embedCount}`);
267
+
268
+ if (result.errors.length > 0) {
269
+ console.log(`\n⚠️ Errors (${result.errors.length}):`);
270
+ for (const error of result.errors.slice(0, 5)) {
271
+ console.log(` - ${error}`);
272
+ }
273
+ if (result.errors.length > 5) {
274
+ console.log(` ... and ${result.errors.length - 5} more`);
275
+ }
276
+ }
277
+
278
+ await globalService.shutdown();
279
+ return;
231
280
  } else {
232
281
  // Default: import current project
233
282
  const cwd = process.cwd();
@@ -324,8 +373,10 @@ const endlessCmd = program
324
373
  endlessCmd
325
374
  .command('enable')
326
375
  .description('Enable Endless Mode')
327
- .action(async () => {
328
- const service = getDefaultMemoryService();
376
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
377
+ .action(async (options) => {
378
+ const projectPath = options.project || process.cwd();
379
+ const service = getMemoryServiceForProject(projectPath);
329
380
 
330
381
  try {
331
382
  await service.initialize();
@@ -353,8 +404,10 @@ endlessCmd
353
404
  endlessCmd
354
405
  .command('disable')
355
406
  .description('Disable Endless Mode (return to Session Mode)')
356
- .action(async () => {
357
- const service = getDefaultMemoryService();
407
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
408
+ .action(async (options) => {
409
+ const projectPath = options.project || process.cwd();
410
+ const service = getMemoryServiceForProject(projectPath);
358
411
 
359
412
  try {
360
413
  await service.initialize();
@@ -377,8 +430,10 @@ endlessCmd
377
430
  endlessCmd
378
431
  .command('status')
379
432
  .description('Show Endless Mode status')
380
- .action(async () => {
381
- const service = getDefaultMemoryService();
433
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
434
+ .action(async (options) => {
435
+ const projectPath = options.project || process.cwd();
436
+ const service = getMemoryServiceForProject(projectPath);
382
437
 
383
438
  try {
384
439
  await service.initialize();
@@ -423,8 +478,10 @@ endlessCmd
423
478
  endlessCmd
424
479
  .command('consolidate')
425
480
  .description('Manually trigger memory consolidation')
426
- .action(async () => {
427
- const service = getDefaultMemoryService();
481
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
482
+ .action(async (options) => {
483
+ const projectPath = options.project || process.cwd();
484
+ const service = getMemoryServiceForProject(projectPath);
428
485
 
429
486
  try {
430
487
  await service.initialize();
@@ -460,8 +517,10 @@ endlessCmd
460
517
  .alias('ws')
461
518
  .description('View current working set')
462
519
  .option('-l, --limit <number>', 'Number of events to show', '10')
520
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
463
521
  .action(async (options) => {
464
- const service = getDefaultMemoryService();
522
+ const projectPath = options.project || process.cwd();
523
+ const service = getMemoryServiceForProject(projectPath);
465
524
 
466
525
  try {
467
526
  await service.initialize();
@@ -519,8 +578,10 @@ endlessCmd
519
578
  .description('View consolidated memories')
520
579
  .option('-l, --limit <number>', 'Number of memories to show', '10')
521
580
  .option('-q, --query <text>', 'Search consolidated memories')
581
+ .option('-p, --project <path>', 'Project path (defaults to cwd)')
522
582
  .action(async (options) => {
523
- const service = getDefaultMemoryService();
583
+ const projectPath = options.project || process.cwd();
584
+ const service = getMemoryServiceForProject(projectPath);
524
585
 
525
586
  try {
526
587
  await service.initialize();
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { randomUUID } from 'crypto';
8
- import { Database } from 'duckdb';
8
+ import { dbRun, dbAll, toDate, type Database } from './db-wrapper.js';
9
9
  import type {
10
10
  ConsolidatedMemory,
11
11
  ConsolidatedMemoryInput
@@ -25,7 +25,8 @@ export class ConsolidatedStore {
25
25
  async create(input: ConsolidatedMemoryInput): Promise<string> {
26
26
  const memoryId = randomUUID();
27
27
 
28
- await this.db.run(
28
+ await dbRun(
29
+ this.db,
29
30
  `INSERT INTO consolidated_memories
30
31
  (memory_id, summary, topics, source_events, confidence, created_at)
31
32
  VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
@@ -45,7 +46,8 @@ export class ConsolidatedStore {
45
46
  * Get a consolidated memory by ID
46
47
  */
47
48
  async get(memoryId: string): Promise<ConsolidatedMemory | null> {
48
- const rows = await this.db.all<Array<Record<string, unknown>>>(
49
+ const rows = await dbAll<Record<string, unknown>>(
50
+ this.db,
49
51
  `SELECT * FROM consolidated_memories WHERE memory_id = ?`,
50
52
  [memoryId]
51
53
  );
@@ -60,7 +62,8 @@ export class ConsolidatedStore {
60
62
  async search(query: string, options?: { topK?: number }): Promise<ConsolidatedMemory[]> {
61
63
  const topK = options?.topK || 5;
62
64
 
63
- const rows = await this.db.all<Array<Record<string, unknown>>>(
65
+ const rows = await dbAll<Record<string, unknown>>(
66
+ this.db,
64
67
  `SELECT * FROM consolidated_memories
65
68
  WHERE summary LIKE ?
66
69
  ORDER BY confidence DESC
@@ -81,7 +84,8 @@ export class ConsolidatedStore {
81
84
  const topicConditions = topics.map(() => `topics LIKE ?`).join(' OR ');
82
85
  const topicParams = topics.map(t => `%"${t}"%`);
83
86
 
84
- const rows = await this.db.all<Array<Record<string, unknown>>>(
87
+ const rows = await dbAll<Record<string, unknown>>(
88
+ this.db,
85
89
  `SELECT * FROM consolidated_memories
86
90
  WHERE ${topicConditions}
87
91
  ORDER BY confidence DESC
@@ -98,7 +102,8 @@ export class ConsolidatedStore {
98
102
  async getAll(options?: { limit?: number }): Promise<ConsolidatedMemory[]> {
99
103
  const limit = options?.limit || 100;
100
104
 
101
- const rows = await this.db.all<Array<Record<string, unknown>>>(
105
+ const rows = await dbAll<Record<string, unknown>>(
106
+ this.db,
102
107
  `SELECT * FROM consolidated_memories
103
108
  ORDER BY confidence DESC, created_at DESC
104
109
  LIMIT ?`,
@@ -115,7 +120,8 @@ export class ConsolidatedStore {
115
120
  const limit = options?.limit || 10;
116
121
  const hours = options?.hours || 24;
117
122
 
118
- const rows = await this.db.all<Array<Record<string, unknown>>>(
123
+ const rows = await dbAll<Record<string, unknown>>(
124
+ this.db,
119
125
  `SELECT * FROM consolidated_memories
120
126
  WHERE created_at > datetime('now', '-${hours} hours')
121
127
  ORDER BY created_at DESC
@@ -130,7 +136,8 @@ export class ConsolidatedStore {
130
136
  * Mark a memory as accessed (tracks usage for importance scoring)
131
137
  */
132
138
  async markAccessed(memoryId: string): Promise<void> {
133
- await this.db.run(
139
+ await dbRun(
140
+ this.db,
134
141
  `UPDATE consolidated_memories
135
142
  SET accessed_at = CURRENT_TIMESTAMP,
136
143
  access_count = access_count + 1
@@ -143,7 +150,8 @@ export class ConsolidatedStore {
143
150
  * Update confidence score for a memory
144
151
  */
145
152
  async updateConfidence(memoryId: string, confidence: number): Promise<void> {
146
- await this.db.run(
153
+ await dbRun(
154
+ this.db,
147
155
  `UPDATE consolidated_memories
148
156
  SET confidence = ?
149
157
  WHERE memory_id = ?`,
@@ -155,7 +163,8 @@ export class ConsolidatedStore {
155
163
  * Delete a consolidated memory
156
164
  */
157
165
  async delete(memoryId: string): Promise<void> {
158
- await this.db.run(
166
+ await dbRun(
167
+ this.db,
159
168
  `DELETE FROM consolidated_memories WHERE memory_id = ?`,
160
169
  [memoryId]
161
170
  );
@@ -165,7 +174,8 @@ export class ConsolidatedStore {
165
174
  * Get count of consolidated memories
166
175
  */
167
176
  async count(): Promise<number> {
168
- const result = await this.db.all<Array<{ count: number }>>(
177
+ const result = await dbAll<{ count: number }>(
178
+ this.db,
169
179
  `SELECT COUNT(*) as count FROM consolidated_memories`
170
180
  );
171
181
  return result[0]?.count || 0;
@@ -175,7 +185,8 @@ export class ConsolidatedStore {
175
185
  * Get most accessed memories (for importance scoring)
176
186
  */
177
187
  async getMostAccessed(limit: number = 10): Promise<ConsolidatedMemory[]> {
178
- const rows = await this.db.all<Array<Record<string, unknown>>>(
188
+ const rows = await dbAll<Record<string, unknown>>(
189
+ this.db,
179
190
  `SELECT * FROM consolidated_memories
180
191
  WHERE access_count > 0
181
192
  ORDER BY access_count DESC
@@ -197,12 +208,14 @@ export class ConsolidatedStore {
197
208
  }> {
198
209
  const total = await this.count();
199
210
 
200
- const avgResult = await this.db.all<Array<{ avg: number | null }>>(
211
+ const avgResult = await dbAll<{ avg: number | null }>(
212
+ this.db,
201
213
  `SELECT AVG(confidence) as avg FROM consolidated_memories`
202
214
  );
203
215
  const averageConfidence = avgResult[0]?.avg || 0;
204
216
 
205
- const recentResult = await this.db.all<Array<{ count: number }>>(
217
+ const recentResult = await dbAll<{ count: number }>(
218
+ this.db,
206
219
  `SELECT COUNT(*) as count FROM consolidated_memories
207
220
  WHERE created_at > datetime('now', '-24 hours')`
208
221
  );
@@ -230,7 +243,8 @@ export class ConsolidatedStore {
230
243
  */
231
244
  async isAlreadyConsolidated(eventIds: string[]): Promise<boolean> {
232
245
  for (const eventId of eventIds) {
233
- const result = await this.db.all<Array<{ count: number }>>(
246
+ const result = await dbAll<{ count: number }>(
247
+ this.db,
234
248
  `SELECT COUNT(*) as count FROM consolidated_memories
235
249
  WHERE source_events LIKE ?`,
236
250
  [`%"${eventId}"%`]
@@ -244,7 +258,8 @@ export class ConsolidatedStore {
244
258
  * Get the last consolidation time
245
259
  */
246
260
  async getLastConsolidationTime(): Promise<Date | null> {
247
- const result = await this.db.all<Array<{ created_at: string }>>(
261
+ const result = await dbAll<{ created_at: string }>(
262
+ this.db,
248
263
  `SELECT created_at FROM consolidated_memories
249
264
  ORDER BY created_at DESC
250
265
  LIMIT 1`
@@ -264,8 +279,8 @@ export class ConsolidatedStore {
264
279
  topics: JSON.parse(row.topics as string || '[]'),
265
280
  sourceEvents: JSON.parse(row.source_events as string || '[]'),
266
281
  confidence: row.confidence as number,
267
- createdAt: new Date(row.created_at as string),
268
- accessedAt: row.accessed_at ? new Date(row.accessed_at as string) : undefined,
282
+ createdAt: toDate(row.created_at),
283
+ accessedAt: row.accessed_at ? toDate(row.accessed_at) : undefined,
269
284
  accessCount: row.access_count as number || 0
270
285
  };
271
286
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { randomUUID } from 'crypto';
8
- import { Database } from 'duckdb';
8
+ import { dbRun, dbAll, toDate, type Database } from './db-wrapper.js';
9
9
  import type {
10
10
  EndlessModeConfig,
11
11
  ContextSnapshot,
@@ -107,7 +107,8 @@ export class ContinuityManager {
107
107
  * Get recent continuity logs
108
108
  */
109
109
  async getRecentLogs(limit: number = 10): Promise<ContinuityLog[]> {
110
- const rows = await this.db.all<Array<Record<string, unknown>>>(
110
+ const rows = await dbAll<Record<string, unknown>>(
111
+ this.db,
111
112
  `SELECT * FROM continuity_log
112
113
  ORDER BY created_at DESC
113
114
  LIMIT ?`,
@@ -120,7 +121,7 @@ export class ContinuityManager {
120
121
  toContextId: row.to_context_id as string | undefined,
121
122
  continuityScore: row.continuity_score as number,
122
123
  transitionType: row.transition_type as TransitionType,
123
- createdAt: new Date(row.created_at as string)
124
+ createdAt: toDate(row.created_at)
124
125
  }));
125
126
  }
126
127
 
@@ -128,7 +129,8 @@ export class ContinuityManager {
128
129
  * Get average continuity score over time period
129
130
  */
130
131
  async getAverageScore(hours: number = 1): Promise<number> {
131
- const result = await this.db.all<Array<{ avg_score: number | null }>>(
132
+ const result = await dbAll<{ avg_score: number | null }>(
133
+ this.db,
132
134
  `SELECT AVG(continuity_score) as avg_score
133
135
  FROM continuity_log
134
136
  WHERE created_at > datetime('now', '-${hours} hours')`
@@ -141,7 +143,8 @@ export class ContinuityManager {
141
143
  * Get transition type distribution
142
144
  */
143
145
  async getTransitionStats(hours: number = 24): Promise<Record<TransitionType, number>> {
144
- const rows = await this.db.all<Array<{ transition_type: string; count: number }>>(
146
+ const rows = await dbAll<{ transition_type: string; count: number }>(
147
+ this.db,
145
148
  `SELECT transition_type, COUNT(*) as count
146
149
  FROM continuity_log
147
150
  WHERE created_at > datetime('now', '-${hours} hours')
@@ -165,7 +168,8 @@ export class ContinuityManager {
165
168
  * Clear old continuity logs
166
169
  */
167
170
  async cleanup(olderThanDays: number = 7): Promise<number> {
168
- const result = await this.db.all<Array<{ changes: number }>>(
171
+ const result = await dbAll<{ changes: number }>(
172
+ this.db,
169
173
  `DELETE FROM continuity_log
170
174
  WHERE created_at < datetime('now', '-${olderThanDays} days')
171
175
  RETURNING COUNT(*) as changes`
@@ -211,7 +215,8 @@ export class ContinuityManager {
211
215
  score: number,
212
216
  type: TransitionType
213
217
  ): Promise<void> {
214
- await this.db.run(
218
+ await dbRun(
219
+ this.db,
215
220
  `INSERT INTO continuity_log
216
221
  (log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
217
222
  VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
@@ -0,0 +1,112 @@
1
+ /**
2
+ * DuckDB Promise Wrapper
3
+ * Wraps the callback-based DuckDB API with Promise-based async/await interface
4
+ */
5
+
6
+ import duckdb from 'duckdb';
7
+
8
+ export type Database = duckdb.Database;
9
+
10
+ /**
11
+ * Converts BigInt values to Number in an object
12
+ * DuckDB returns BigInt for COUNT(*) and other aggregate functions
13
+ */
14
+ function convertBigInts<T>(obj: T): T {
15
+ if (obj === null || obj === undefined) return obj;
16
+ if (typeof obj === 'bigint') return Number(obj) as unknown as T;
17
+ if (obj instanceof Date) return obj; // Preserve Date objects
18
+ if (Array.isArray(obj)) return obj.map(convertBigInts) as unknown as T;
19
+ if (typeof obj === 'object') {
20
+ const result: Record<string, unknown> = {};
21
+ for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
22
+ result[key] = convertBigInts(value);
23
+ }
24
+ return result as T;
25
+ }
26
+ return obj;
27
+ }
28
+
29
+ /**
30
+ * Safely converts a value to a Date object
31
+ * Handles both Date objects and string timestamps from DuckDB
32
+ */
33
+ export function toDate(value: unknown): Date {
34
+ if (value instanceof Date) return value;
35
+ if (typeof value === 'string') return new Date(value);
36
+ if (typeof value === 'number') return new Date(value);
37
+ return new Date(String(value));
38
+ }
39
+
40
+ /**
41
+ * Creates a new DuckDB database with Promise-based API
42
+ */
43
+ export function createDatabase(path: string): Database {
44
+ return new duckdb.Database(path);
45
+ }
46
+
47
+ /**
48
+ * Promisified db.run() - executes a statement that doesn't return rows
49
+ */
50
+ export function dbRun(db: Database, sql: string, params: unknown[] = []): Promise<void> {
51
+ return new Promise((resolve, reject) => {
52
+ if (params.length === 0) {
53
+ db.run(sql, (err: Error | null) => {
54
+ if (err) reject(err);
55
+ else resolve();
56
+ });
57
+ } else {
58
+ db.run(sql, ...params, (err: Error | null) => {
59
+ if (err) reject(err);
60
+ else resolve();
61
+ });
62
+ }
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Promisified db.all() - executes a query and returns all rows
68
+ * Automatically converts BigInt values to Number
69
+ */
70
+ export function dbAll<T = Record<string, unknown>>(
71
+ db: Database,
72
+ sql: string,
73
+ params: unknown[] = []
74
+ ): Promise<T[]> {
75
+ return new Promise((resolve, reject) => {
76
+ if (params.length === 0) {
77
+ db.all(sql, (err: Error | null, rows: T[]) => {
78
+ if (err) reject(err);
79
+ else resolve(convertBigInts(rows || []));
80
+ });
81
+ } else {
82
+ db.all(sql, ...params, (err: Error | null, rows: T[]) => {
83
+ if (err) reject(err);
84
+ else resolve(convertBigInts(rows || []));
85
+ });
86
+ }
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Promisified db.close() - closes the database connection
92
+ */
93
+ export function dbClose(db: Database): Promise<void> {
94
+ return new Promise((resolve, reject) => {
95
+ db.close((err: Error | null) => {
96
+ if (err) reject(err);
97
+ else resolve();
98
+ });
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Promisified db.exec() - executes multiple statements
104
+ */
105
+ export function dbExec(db: Database, sql: string): Promise<void> {
106
+ return new Promise((resolve, reject) => {
107
+ db.exec(sql, (err: Error | null) => {
108
+ if (err) reject(err);
109
+ else resolve();
110
+ });
111
+ });
112
+ }