@vibesdotdev/localdb 0.0.1

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 (130) hide show
  1. package/README.md +121 -0
  2. package/SPEC.md +119 -0
  3. package/dist/cli/commands/_shared.consumer.d.ts +5 -0
  4. package/dist/cli/commands/_shared.consumer.d.ts.map +1 -0
  5. package/dist/cli/commands/_shared.consumer.js +17 -0
  6. package/dist/cli/commands/_shared.consumer.js.map +1 -0
  7. package/dist/cli/commands/migrate/dev.localdb.migrate.cli-command.descriptor.d.ts +19 -0
  8. package/dist/cli/commands/migrate/dev.localdb.migrate.cli-command.descriptor.d.ts.map +1 -0
  9. package/dist/cli/commands/migrate/dev.localdb.migrate.cli-command.descriptor.js +19 -0
  10. package/dist/cli/commands/migrate/dev.localdb.migrate.cli-command.descriptor.js.map +1 -0
  11. package/dist/cli/commands/migrate/dev.localdb.migrate.cli-command.impl.consumer.d.ts +5 -0
  12. package/dist/cli/commands/migrate/dev.localdb.migrate.cli-command.impl.consumer.d.ts.map +1 -0
  13. package/dist/cli/commands/migrate/dev.localdb.migrate.cli-command.impl.consumer.js +80 -0
  14. package/dist/cli/commands/migrate/dev.localdb.migrate.cli-command.impl.consumer.js.map +1 -0
  15. package/dist/cli/commands/namespaces/dev.localdb.namespaces.cli-command.descriptor.d.ts +19 -0
  16. package/dist/cli/commands/namespaces/dev.localdb.namespaces.cli-command.descriptor.d.ts.map +1 -0
  17. package/dist/cli/commands/namespaces/dev.localdb.namespaces.cli-command.descriptor.js +16 -0
  18. package/dist/cli/commands/namespaces/dev.localdb.namespaces.cli-command.descriptor.js.map +1 -0
  19. package/dist/cli/commands/namespaces/dev.localdb.namespaces.cli-command.impl.d.ts +5 -0
  20. package/dist/cli/commands/namespaces/dev.localdb.namespaces.cli-command.impl.d.ts.map +1 -0
  21. package/dist/cli/commands/namespaces/dev.localdb.namespaces.cli-command.impl.js +33 -0
  22. package/dist/cli/commands/namespaces/dev.localdb.namespaces.cli-command.impl.js.map +1 -0
  23. package/dist/cli/commands/pools/dev.localdb.pools.cli-command.descriptor.d.ts +15 -0
  24. package/dist/cli/commands/pools/dev.localdb.pools.cli-command.descriptor.d.ts.map +1 -0
  25. package/dist/cli/commands/pools/dev.localdb.pools.cli-command.descriptor.js +15 -0
  26. package/dist/cli/commands/pools/dev.localdb.pools.cli-command.descriptor.js.map +1 -0
  27. package/dist/cli/commands/pools/dev.localdb.pools.cli-command.impl.d.ts +5 -0
  28. package/dist/cli/commands/pools/dev.localdb.pools.cli-command.impl.d.ts.map +1 -0
  29. package/dist/cli/commands/pools/dev.localdb.pools.cli-command.impl.js +28 -0
  30. package/dist/cli/commands/pools/dev.localdb.pools.cli-command.impl.js.map +1 -0
  31. package/dist/cli/commands/query/dev.localdb.query.cli-command.descriptor.d.ts +24 -0
  32. package/dist/cli/commands/query/dev.localdb.query.cli-command.descriptor.d.ts.map +1 -0
  33. package/dist/cli/commands/query/dev.localdb.query.cli-command.descriptor.js +20 -0
  34. package/dist/cli/commands/query/dev.localdb.query.cli-command.descriptor.js.map +1 -0
  35. package/dist/cli/commands/query/dev.localdb.query.cli-command.impl.consumer.d.ts +5 -0
  36. package/dist/cli/commands/query/dev.localdb.query.cli-command.impl.consumer.d.ts.map +1 -0
  37. package/dist/cli/commands/query/dev.localdb.query.cli-command.impl.consumer.js +63 -0
  38. package/dist/cli/commands/query/dev.localdb.query.cli-command.impl.consumer.js.map +1 -0
  39. package/dist/cli/commands/status/dev.localdb.status.cli-command.descriptor.d.ts +19 -0
  40. package/dist/cli/commands/status/dev.localdb.status.cli-command.descriptor.d.ts.map +1 -0
  41. package/dist/cli/commands/status/dev.localdb.status.cli-command.descriptor.js +19 -0
  42. package/dist/cli/commands/status/dev.localdb.status.cli-command.descriptor.js.map +1 -0
  43. package/dist/cli/commands/status/dev.localdb.status.cli-command.impl.consumer.d.ts +5 -0
  44. package/dist/cli/commands/status/dev.localdb.status.cli-command.impl.consumer.d.ts.map +1 -0
  45. package/dist/cli/commands/status/dev.localdb.status.cli-command.impl.consumer.js +73 -0
  46. package/dist/cli/commands/status/dev.localdb.status.cli-command.impl.consumer.js.map +1 -0
  47. package/dist/core/connection-pool.d.ts +84 -0
  48. package/dist/core/connection-pool.d.ts.map +1 -0
  49. package/dist/core/connection-pool.js +191 -0
  50. package/dist/core/connection-pool.js.map +1 -0
  51. package/dist/core/database.d.ts +137 -0
  52. package/dist/core/database.d.ts.map +1 -0
  53. package/dist/core/database.js +347 -0
  54. package/dist/core/database.js.map +1 -0
  55. package/dist/core/error-context.d.ts +2 -0
  56. package/dist/core/error-context.d.ts.map +1 -0
  57. package/dist/core/error-context.js +17 -0
  58. package/dist/core/error-context.js.map +1 -0
  59. package/dist/core/migration-registry.d.ts +89 -0
  60. package/dist/core/migration-registry.d.ts.map +1 -0
  61. package/dist/core/migration-registry.js +226 -0
  62. package/dist/core/migration-registry.js.map +1 -0
  63. package/dist/core/runtime.d.ts +3 -0
  64. package/dist/core/runtime.d.ts.map +1 -0
  65. package/dist/core/runtime.js +17 -0
  66. package/dist/core/runtime.js.map +1 -0
  67. package/dist/dev.localdb.cli-group.descriptor.d.ts +9 -0
  68. package/dist/dev.localdb.cli-group.descriptor.d.ts.map +1 -0
  69. package/dist/dev.localdb.cli-group.descriptor.js +17 -0
  70. package/dist/dev.localdb.cli-group.descriptor.js.map +1 -0
  71. package/dist/dev.localdb.context.descriptor.d.ts +21 -0
  72. package/dist/dev.localdb.context.descriptor.d.ts.map +1 -0
  73. package/dist/dev.localdb.context.descriptor.js +12 -0
  74. package/dist/dev.localdb.context.descriptor.js.map +1 -0
  75. package/dist/dev.localdb.context.impl.consumer.d.ts +9 -0
  76. package/dist/dev.localdb.context.impl.consumer.d.ts.map +1 -0
  77. package/dist/dev.localdb.context.impl.consumer.js +10 -0
  78. package/dist/dev.localdb.context.impl.consumer.js.map +1 -0
  79. package/dist/index.d.ts +12 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +11 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/localdb.cloud.plugin.d.ts +17 -0
  84. package/dist/localdb.cloud.plugin.d.ts.map +1 -0
  85. package/dist/localdb.cloud.plugin.js +23 -0
  86. package/dist/localdb.cloud.plugin.js.map +1 -0
  87. package/dist/localdb.plugin.d.ts +8 -0
  88. package/dist/localdb.plugin.d.ts.map +1 -0
  89. package/dist/localdb.plugin.js +38 -0
  90. package/dist/localdb.plugin.js.map +1 -0
  91. package/dist/migrations/load-sql.d.ts +45 -0
  92. package/dist/migrations/load-sql.d.ts.map +1 -0
  93. package/dist/migrations/load-sql.js +117 -0
  94. package/dist/migrations/load-sql.js.map +1 -0
  95. package/dist/schemas/api.d.ts +82 -0
  96. package/dist/schemas/api.d.ts.map +1 -0
  97. package/dist/schemas/api.js +2 -0
  98. package/dist/schemas/api.js.map +1 -0
  99. package/package.json +146 -0
  100. package/src/cli/commands/_shared.consumer.ts +20 -0
  101. package/src/cli/commands/migrate/dev.localdb.migrate.cli-command.descriptor.ts +20 -0
  102. package/src/cli/commands/migrate/dev.localdb.migrate.cli-command.impl.consumer.ts +97 -0
  103. package/src/cli/commands/namespaces/dev.localdb.namespaces.cli-command.descriptor.ts +17 -0
  104. package/src/cli/commands/namespaces/dev.localdb.namespaces.cli-command.impl.ts +46 -0
  105. package/src/cli/commands/pools/dev.localdb.pools.cli-command.descriptor.ts +16 -0
  106. package/src/cli/commands/pools/dev.localdb.pools.cli-command.impl.ts +37 -0
  107. package/src/cli/commands/query/dev.localdb.query.cli-command.descriptor.ts +21 -0
  108. package/src/cli/commands/query/dev.localdb.query.cli-command.impl.consumer.ts +69 -0
  109. package/src/cli/commands/status/dev.localdb.status.cli-command.descriptor.ts +20 -0
  110. package/src/cli/commands/status/dev.localdb.status.cli-command.impl.consumer.ts +93 -0
  111. package/src/core/connection-pool.ts +240 -0
  112. package/src/core/database.ts +419 -0
  113. package/src/core/error-context.ts +19 -0
  114. package/src/core/migration-registry.ts +321 -0
  115. package/src/core/runtime.ts +17 -0
  116. package/src/dev.localdb.cli-group.descriptor.ts +20 -0
  117. package/src/dev.localdb.context.descriptor.ts +13 -0
  118. package/src/dev.localdb.context.impl.consumer.ts +12 -0
  119. package/src/index.ts +28 -0
  120. package/src/localdb.cloud.plugin.ts +24 -0
  121. package/src/localdb.plugin.ts +43 -0
  122. package/src/migrations/atlas/001-initial-schema.sql +173 -0
  123. package/src/migrations/atlas/002-lang-server-fields.sql +12 -0
  124. package/src/migrations/atlas/003-config-id-dedup.sql +31 -0
  125. package/src/migrations/atlas/004-fix-on-conflict-constraints.sql +25 -0
  126. package/src/migrations/atlas/005-diagnostics.sql +66 -0
  127. package/src/migrations/atlas/006-diagnostic-summaries.sql +65 -0
  128. package/src/migrations/atlas/007-diagnostic-slice.sql +83 -0
  129. package/src/migrations/load-sql.ts +133 -0
  130. package/src/schemas/api.ts +92 -0
@@ -0,0 +1,69 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { getVibesRuntime } from '@vibesdotdev/runtime';
3
+ import type { UIContext } from '@vibesdotdev/cli/providers';
4
+ import type { LocaldbApi } from '../../..';
5
+ import { resolveFromCwd } from '../_shared.consumer';
6
+
7
+ export default {
8
+ async execute(args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
9
+ const runtime = getVibesRuntime();
10
+ const ui = (await runtime.context('cli/ui')) as UIContext;
11
+ const localdb = (await runtime.context('dev/localdb')) as LocaldbApi;
12
+
13
+ const sql = typeof args.sql === 'string' ? args.sql : '';
14
+ const dbOpt = typeof opts.db === 'string' ? opts.db : '';
15
+
16
+ if (!sql.trim()) {
17
+ ui.error('Missing SQL statement');
18
+ process.exit(1);
19
+ }
20
+ if (!dbOpt.trim()) {
21
+ ui.error('Missing required option: --db <path>');
22
+ process.exit(1);
23
+ }
24
+
25
+ const fullPath = resolveFromCwd(dbOpt);
26
+ if (!existsSync(fullPath)) {
27
+ ui.error(`Database not found: ${dbOpt}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ try {
32
+ const db = localdb.getRawDatabase(fullPath, 'localdb-query');
33
+ const isSelect = sql.trim().toLowerCase().startsWith('select');
34
+
35
+ if (isSelect) {
36
+ const results = db.query(sql).all();
37
+ if (opts.json) {
38
+ ui.json(results);
39
+ } else if (results.length === 0) {
40
+ ui.info('No results.');
41
+ } else {
42
+ ui.table(results);
43
+ ui.info(`(${results.length} row${results.length !== 1 ? 's' : ''})`);
44
+ }
45
+ return;
46
+ }
47
+
48
+ const stmt = db.prepare(sql);
49
+ const result = stmt.run();
50
+
51
+ const payload = {
52
+ changes: result.changes,
53
+ lastInsertRowid: result.lastInsertRowid
54
+ };
55
+
56
+ if (opts.json) {
57
+ ui.json(payload);
58
+ } else {
59
+ ui.success('Query executed successfully.');
60
+ ui.log(`Changes: ${payload.changes}`);
61
+ if (payload.lastInsertRowid)
62
+ ui.log(`Last Insert Row ID: ${String(payload.lastInsertRowid)}`);
63
+ }
64
+ } catch (error) {
65
+ ui.error(`Error executing query: ${String(error)}`);
66
+ process.exit(1);
67
+ }
68
+ }
69
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Dev LocalDB Status CLI Command Descriptor
3
+ */
4
+
5
+ import { createRuntimeAsset } from '@vibesdotdev/runtime';
6
+
7
+ export default createRuntimeAsset({
8
+ id: 'dev.localdb.status',
9
+ kind: 'cli/command',
10
+ name: 'status',
11
+ description: 'Show database and migration status',
12
+ group: 'dev.localdb',
13
+ surfaces: ['cli'],
14
+ hardware: ['consumer'],
15
+ enabled: true,
16
+ options: [
17
+ { flags: '--namespace <name>', description: 'Show status for specific namespace' },
18
+ { flags: '--verbose', description: 'Show detailed migration and connection information' }
19
+ ]
20
+ });
@@ -0,0 +1,93 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { getVibesRuntime } from '@vibesdotdev/runtime';
3
+ import { requestCLIExit, type UIContext } from '@vibesdotdev/cli/providers';
4
+ import type { LocaldbApi, MigrationRegistryApi } from '../../..';
5
+ import { formatAgeMs, resolveDatabasePathForNamespace, resolveFromCwd } from '../_shared.consumer';
6
+
7
+ export default {
8
+ async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
9
+ const runtime = getVibesRuntime();
10
+ const ui = (await runtime.context('cli/ui')) as UIContext;
11
+ const localdb = (await runtime.context('dev/localdb')) as LocaldbApi;
12
+
13
+ const namespaceOpt = typeof opts.namespace === 'string' ? opts.namespace : undefined;
14
+ const verbose = Boolean(opts.verbose);
15
+
16
+ ui.log('\n=== LocalDB Status ===\n');
17
+
18
+ const namespaces = localdb.listNamespaces();
19
+ if (namespaces.length === 0) {
20
+ ui.info('No migration namespaces registered.');
21
+ return;
22
+ }
23
+
24
+ const namespacesToShow = namespaceOpt
25
+ ? namespaces.filter((ns) => ns === namespaceOpt)
26
+ : namespaces;
27
+
28
+ if (namespacesToShow.length === 0) {
29
+ ui.error(`Namespace '${namespaceOpt}' not found.`);
30
+ ui.info(`Available namespaces: ${namespaces.join(', ')}`);
31
+ requestCLIExit(1);
32
+ }
33
+
34
+ const { migrationRegistry } =
35
+ (await import('../../../core/migration-registry')) as {
36
+ migrationRegistry: MigrationRegistryApi;
37
+ };
38
+
39
+ for (const ns of namespacesToShow) {
40
+ ui.log(`\nNamespace: ${ns}`);
41
+ ui.log('─'.repeat(50));
42
+
43
+ const dbPath = resolveDatabasePathForNamespace(ns);
44
+ if (!dbPath) {
45
+ ui.warn('No known database path for this namespace');
46
+ continue;
47
+ }
48
+
49
+ const fullPath = resolveFromCwd(dbPath);
50
+ const exists = existsSync(fullPath);
51
+
52
+ ui.log(`Database: ${dbPath}`);
53
+ ui.log(`Exists: ${exists ? 'yes' : 'no'}`);
54
+
55
+ if (!exists) continue;
56
+
57
+ try {
58
+ const db = localdb.getRawDatabase(fullPath, 'localdb-status');
59
+ const status = migrationRegistry.getDatabaseStatus(db, ns);
60
+
61
+ ui.log(`Schema Version: v${status.currentVersion}`);
62
+ ui.log(`Target Version: v${status.targetVersion}`);
63
+ ui.log(`Status: ${status.pendingMigrations > 0 ? 'migrations pending' : 'up to date'}`);
64
+
65
+ if (verbose && status.migrations.length > 0) {
66
+ ui.log('\nMigrations:');
67
+ for (const m of status.migrations) {
68
+ ui.log(` ${m.applied ? '✓' : '○'} v${m.version}: ${m.name}`);
69
+ }
70
+ }
71
+ } catch (error) {
72
+ ui.error(`Error reading migration status: ${String(error)}`);
73
+ }
74
+ }
75
+
76
+ if (verbose) {
77
+ ui.log('\n\n=== Connection Pool ===\n');
78
+ const poolStats = localdb.getPoolStats();
79
+ ui.log(`Total Connections: ${poolStats.total}`);
80
+ ui.log(`Active: ${poolStats.active}`);
81
+ ui.log(`Idle: ${poolStats.idle}`);
82
+
83
+ if (poolStats.connections.length > 0) {
84
+ ui.log('\nConnections:');
85
+ for (const conn of poolStats.connections) {
86
+ ui.log(
87
+ ` • ${conn.path} (refs: ${conn.refCount}, age: ${formatAgeMs(conn.ageMs)}, consumer: ${conn.consumer})`
88
+ );
89
+ }
90
+ }
91
+ }
92
+ }
93
+ };
@@ -0,0 +1,240 @@
1
+ /**
2
+ * SQLite Connection Pool
3
+ *
4
+ * Centralized connection management for all SQLite databases in vibes dev.
5
+ * Ensures only one connection per database file to prevent SQLITE_BUSY errors.
6
+ *
7
+ * Features:
8
+ * - Singleton pattern per database path
9
+ * - Automatic WAL mode and busy timeout configuration
10
+ * - Connection reuse across consumers
11
+ * - Safe cleanup on process exit
12
+ * - Connection statistics and monitoring
13
+ */
14
+
15
+ import { Database } from 'bun:sqlite';
16
+ import { getLogger } from '@vibesdotdev/logging';
17
+ import { toErrorContext } from './error-context';
18
+
19
+ const logger = getLogger('localdb:pool');
20
+
21
+ export interface PooledConnection {
22
+ db: Database;
23
+ refCount: number;
24
+ lastUsed: number;
25
+ createdAt: number;
26
+ consumer: string; // First consumer that opened this connection
27
+ }
28
+
29
+ export interface ConnectionStats {
30
+ total: number;
31
+ active: number;
32
+ idle: number;
33
+ connections: Array<{
34
+ path: string;
35
+ refCount: number;
36
+ ageMs: number;
37
+ consumer: string;
38
+ }>;
39
+ }
40
+
41
+ class SQLiteConnectionPool {
42
+ private connections = new Map<string, PooledConnection>();
43
+ private cleanupInterval?: Timer;
44
+ private readonly STALE_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes
45
+ private readonly CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
46
+
47
+ constructor() {
48
+ // Periodic cleanup of stale connections
49
+ this.startCleanup();
50
+
51
+ // Ensure proper cleanup on process exit
52
+ this.registerExitHandlers();
53
+ }
54
+
55
+ /**
56
+ * Get or create a database connection with optimal configuration
57
+ */
58
+ getConnection(dbPath: string, consumer = 'unknown'): Database {
59
+ let pooled = this.connections.get(dbPath);
60
+
61
+ if (!pooled) {
62
+ const db = new Database(dbPath);
63
+
64
+ // Configure for optimal concurrent access
65
+ this.configureDatabase(db);
66
+
67
+ pooled = {
68
+ db,
69
+ refCount: 0,
70
+ lastUsed: Date.now(),
71
+ createdAt: Date.now(),
72
+ consumer
73
+ };
74
+
75
+ this.connections.set(dbPath, pooled);
76
+ logger.debug(`Created connection: ${dbPath} (${consumer})`);
77
+ }
78
+
79
+ pooled.refCount++;
80
+ pooled.lastUsed = Date.now();
81
+
82
+ return pooled.db;
83
+ }
84
+
85
+ /**
86
+ * Configure database with optimal settings for concurrent access
87
+ */
88
+ private configureDatabase(db: Database): void {
89
+ // WAL mode: Readers don't block writers, writers don't block readers
90
+ db.exec('PRAGMA journal_mode = WAL;');
91
+
92
+ // Busy timeout: Wait up to 5 seconds for locks instead of failing immediately
93
+ db.exec('PRAGMA busy_timeout = 5000;');
94
+
95
+ // Foreign keys: Enable referential integrity
96
+ db.exec('PRAGMA foreign_keys = ON;');
97
+
98
+ // Synchronous: NORMAL is safe with WAL and much faster
99
+ db.exec('PRAGMA synchronous = NORMAL;');
100
+
101
+ // Temp store: Use memory for temp tables (faster)
102
+ db.exec('PRAGMA temp_store = MEMORY;');
103
+ }
104
+
105
+ /**
106
+ * Release a connection (decrement reference count)
107
+ * Connection remains in pool for reuse
108
+ */
109
+ releaseConnection(dbPath: string): void {
110
+ const pooled = this.connections.get(dbPath);
111
+ if (pooled) {
112
+ pooled.refCount = Math.max(0, pooled.refCount - 1);
113
+ pooled.lastUsed = Date.now();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Close a specific connection immediately
119
+ * Only closes if reference count is zero (unless forced)
120
+ */
121
+ closeConnection(dbPath: string, force = false): boolean {
122
+ const pooled = this.connections.get(dbPath);
123
+ if (!pooled) return false;
124
+
125
+ if (force || pooled.refCount === 0) {
126
+ try {
127
+ pooled.db.close();
128
+ this.connections.delete(dbPath);
129
+ // Use stderr to avoid breaking MCP stdio protocol
130
+ logger.debug(`[LocalDB] Closed connection: ${dbPath}`);
131
+ return true;
132
+ } catch (error) {
133
+ logger.error(`[LocalDB] Error closing ${dbPath}:`, toErrorContext(error));
134
+ return false;
135
+ }
136
+ }
137
+
138
+ return false;
139
+ }
140
+
141
+ /**
142
+ * Clean up stale connections (unused for 30+ minutes with zero references)
143
+ */
144
+ private cleanupStale(): void {
145
+ const now = Date.now();
146
+
147
+ for (const [dbPath, pooled] of this.connections.entries()) {
148
+ const age = now - pooled.lastUsed;
149
+ if (pooled.refCount === 0 && age > this.STALE_THRESHOLD_MS) {
150
+ this.closeConnection(dbPath, true);
151
+ }
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Start periodic cleanup
157
+ */
158
+ private startCleanup(): void {
159
+ this.cleanupInterval = setInterval(() => this.cleanupStale(), this.CLEANUP_INTERVAL_MS);
160
+ }
161
+
162
+ /**
163
+ * Stop periodic cleanup
164
+ */
165
+ private stopCleanup(): void {
166
+ if (this.cleanupInterval) {
167
+ clearInterval(this.cleanupInterval);
168
+ this.cleanupInterval = undefined;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Close all connections (typically on shutdown)
174
+ */
175
+ closeAll(): void {
176
+ this.stopCleanup();
177
+
178
+ for (const [dbPath, pooled] of this.connections.entries()) {
179
+ try {
180
+ pooled.db.close();
181
+ // Use stderr to avoid breaking MCP stdio protocol
182
+ logger.debug(`[LocalDB] Closed connection on shutdown: ${dbPath}`);
183
+ } catch (error) {
184
+ logger.error(`[LocalDB] Error closing ${dbPath}:`, toErrorContext(error));
185
+ }
186
+ }
187
+
188
+ this.connections.clear();
189
+ }
190
+
191
+ /**
192
+ * Get pool statistics
193
+ */
194
+ getStats(): ConnectionStats {
195
+ let active = 0;
196
+ let idle = 0;
197
+ const connections: ConnectionStats['connections'] = [];
198
+ const now = Date.now();
199
+
200
+ for (const [path, pooled] of this.connections.entries()) {
201
+ if (pooled.refCount > 0) {
202
+ active++;
203
+ } else {
204
+ idle++;
205
+ }
206
+
207
+ connections.push({
208
+ path,
209
+ refCount: pooled.refCount,
210
+ ageMs: now - pooled.createdAt,
211
+ consumer: pooled.consumer
212
+ });
213
+ }
214
+
215
+ return {
216
+ total: this.connections.size,
217
+ active,
218
+ idle,
219
+ connections
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Register process exit handlers for cleanup
225
+ */
226
+ private registerExitHandlers(): void {
227
+ process.on('beforeExit', () => this.closeAll());
228
+ process.on('SIGINT', () => {
229
+ this.closeAll();
230
+ process.exit(0);
231
+ });
232
+ process.on('SIGTERM', () => {
233
+ this.closeAll();
234
+ process.exit(0);
235
+ });
236
+ }
237
+ }
238
+
239
+ // Singleton instance
240
+ export const connectionPool = new SQLiteConnectionPool();