@jungjaehoon/mama-core 1.1.2 → 1.1.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.
- package/dist/config-loader.d.ts +61 -0
- package/dist/config-loader.js +187 -0
- package/dist/db-adapter/base-adapter.d.ts +76 -0
- package/dist/db-adapter/base-adapter.js +11 -0
- package/dist/db-adapter/index.d.ts +24 -0
- package/dist/db-adapter/index.js +29 -0
- package/dist/db-adapter/sqlite-adapter.d.ts +73 -0
- package/dist/db-adapter/sqlite-adapter.js +330 -0
- package/dist/db-adapter/statement.d.ts +119 -0
- package/dist/db-adapter/statement.js +113 -0
- package/dist/db-manager.d.ts +252 -0
- package/dist/db-manager.js +622 -0
- package/dist/debug-logger.d.ts +32 -0
- package/dist/debug-logger.js +83 -0
- package/dist/decision-formatter.d.ts +172 -0
- package/dist/decision-formatter.js +894 -0
- package/dist/decision-tracker.d.ts +218 -0
- package/dist/decision-tracker.js +531 -0
- package/dist/embedding-cache.d.ts +83 -0
- package/dist/embedding-cache.js +182 -0
- package/dist/embedding-client.d.ts +43 -0
- package/dist/embedding-client.js +132 -0
- package/dist/embedding-server/index.d.ts +65 -0
- package/dist/embedding-server/index.js +397 -0
- package/dist/embedding-server/mobile/auth.d.ts +53 -0
- package/dist/embedding-server/mobile/auth.js +140 -0
- package/dist/embedding-server/mobile/daemon.d.ts +128 -0
- package/dist/embedding-server/mobile/daemon.js +303 -0
- package/dist/embedding-server/mobile/output-parser.d.ts +115 -0
- package/dist/embedding-server/mobile/output-parser.js +241 -0
- package/dist/embedding-server/mobile/session-api.d.ts +57 -0
- package/dist/embedding-server/mobile/session-api.js +261 -0
- package/dist/embedding-server/mobile/session-manager.d.ts +135 -0
- package/dist/embedding-server/mobile/session-manager.js +333 -0
- package/dist/embedding-server/mobile/websocket-handler.d.ts +127 -0
- package/dist/embedding-server/mobile/websocket-handler.js +435 -0
- package/dist/embeddings.d.ts +75 -0
- package/dist/embeddings.js +262 -0
- package/dist/errors.d.ts +131 -0
- package/dist/errors.js +225 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +193 -0
- package/dist/mama-api.d.ts +954 -0
- package/dist/mama-api.js +2210 -0
- package/dist/memory-inject.d.ts +24 -0
- package/dist/memory-inject.js +116 -0
- package/dist/memory-store.d.ts +103 -0
- package/dist/memory-store.js +129 -0
- package/dist/notification-manager.d.ts +7 -0
- package/dist/notification-manager.js +12 -0
- package/dist/ollama-client.d.ts +51 -0
- package/dist/ollama-client.js +308 -0
- package/dist/outcome-tracker.d.ts +165 -0
- package/dist/outcome-tracker.js +315 -0
- package/dist/progress-indicator.d.ts +48 -0
- package/dist/progress-indicator.js +82 -0
- package/dist/query-intent.d.ts +27 -0
- package/dist/query-intent.js +144 -0
- package/dist/relevance-scorer.d.ts +124 -0
- package/dist/relevance-scorer.js +243 -0
- package/dist/test-utils.d.ts +66 -0
- package/dist/test-utils.js +166 -0
- package/dist/tier-validator.d.ts +55 -0
- package/dist/tier-validator.js +216 -0
- package/dist/time-formatter.d.ts +25 -0
- package/dist/time-formatter.js +93 -0
- package/package.json +3 -2
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SQLite Database Adapter
|
|
4
|
+
*
|
|
5
|
+
* Implements DatabaseAdapter interface using better-sqlite3 + sqlite-vec
|
|
6
|
+
* This is the current production implementation extracted from memory-store.js
|
|
7
|
+
*
|
|
8
|
+
* @module sqlite-adapter
|
|
9
|
+
*/
|
|
10
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.SQLiteAdapter = void 0;
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
19
|
+
const base_adapter_js_1 = require("./base-adapter.js");
|
|
20
|
+
const statement_js_1 = require("./statement.js");
|
|
21
|
+
const debug_logger_js_1 = require("../debug-logger.js");
|
|
22
|
+
const embeddings_js_1 = require("../embeddings.js");
|
|
23
|
+
// Try to load sqlite-vec
|
|
24
|
+
let sqliteVec = null;
|
|
25
|
+
try {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
|
+
sqliteVec = require('sqlite-vec');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Defer logging until connect() so we have logger context initialized
|
|
31
|
+
sqliteVec = null;
|
|
32
|
+
}
|
|
33
|
+
// Database paths
|
|
34
|
+
const LEGACY_DB_PATH = path_1.default.join(os_1.default.homedir(), '.spinelift', 'memories.db');
|
|
35
|
+
// Default to ~/.claude/mama-memory.db for Claude Code/Desktop compatibility
|
|
36
|
+
const DEFAULT_DB_PATH = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-memory.db');
|
|
37
|
+
class SQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
|
|
38
|
+
config;
|
|
39
|
+
db = null;
|
|
40
|
+
_vectorSearchEnabled = false;
|
|
41
|
+
constructor(config = {}) {
|
|
42
|
+
super();
|
|
43
|
+
this.config = config;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if vector search is enabled
|
|
47
|
+
*/
|
|
48
|
+
get vectorSearchEnabled() {
|
|
49
|
+
return this._vectorSearchEnabled;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get database path with backward compatibility
|
|
53
|
+
*/
|
|
54
|
+
getDbPath() {
|
|
55
|
+
// Support both MAMA_DB_PATH and MAMA_DATABASE_PATH for backward compatibility
|
|
56
|
+
const envPath = process.env.MAMA_DB_PATH || process.env.MAMA_DATABASE_PATH;
|
|
57
|
+
const configPath = this.config.dbPath;
|
|
58
|
+
// Expand ${HOME} or ~ in environment variable
|
|
59
|
+
let expandedEnvPath = envPath;
|
|
60
|
+
if (envPath) {
|
|
61
|
+
expandedEnvPath = envPath.replace(/\$\{HOME\}/g, os_1.default.homedir()).replace(/^~/, os_1.default.homedir());
|
|
62
|
+
}
|
|
63
|
+
// Priority: config > env > default
|
|
64
|
+
const targetPath = configPath || expandedEnvPath || DEFAULT_DB_PATH;
|
|
65
|
+
// Backward compatibility: Check legacy path if not explicitly set
|
|
66
|
+
if (!configPath && !envPath && fs_1.default.existsSync(LEGACY_DB_PATH)) {
|
|
67
|
+
(0, debug_logger_js_1.info)('[sqlite-adapter] Found legacy database at ~/.spinelift/memories.db, using it for backward compatibility');
|
|
68
|
+
return LEGACY_DB_PATH;
|
|
69
|
+
}
|
|
70
|
+
return targetPath;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Connect to SQLite database
|
|
74
|
+
*/
|
|
75
|
+
connect() {
|
|
76
|
+
if (this.db) {
|
|
77
|
+
return this.db;
|
|
78
|
+
}
|
|
79
|
+
const dbPath = this.getDbPath();
|
|
80
|
+
const dbDir = path_1.default.dirname(dbPath);
|
|
81
|
+
// Ensure directory exists
|
|
82
|
+
if (!fs_1.default.existsSync(dbDir)) {
|
|
83
|
+
fs_1.default.mkdirSync(dbDir, { recursive: true });
|
|
84
|
+
(0, debug_logger_js_1.info)(`[sqlite-adapter] Created database directory: ${dbDir}`);
|
|
85
|
+
}
|
|
86
|
+
// Open database
|
|
87
|
+
this.db = new better_sqlite3_1.default(dbPath, { verbose: undefined });
|
|
88
|
+
(0, debug_logger_js_1.info)(`[sqlite-adapter] Opened database at: ${dbPath}`);
|
|
89
|
+
// Production configuration
|
|
90
|
+
this.db.pragma('journal_mode = WAL');
|
|
91
|
+
this.db.pragma('busy_timeout = 5000');
|
|
92
|
+
this.db.pragma('synchronous = NORMAL');
|
|
93
|
+
this.db.pragma('cache_size = -64000'); // 64MB cache
|
|
94
|
+
this.db.pragma('temp_store = MEMORY');
|
|
95
|
+
this.db.pragma('foreign_keys = ON');
|
|
96
|
+
// Load sqlite-vec extension (graceful degradation if unavailable)
|
|
97
|
+
if (sqliteVec) {
|
|
98
|
+
try {
|
|
99
|
+
sqliteVec.load(this.db);
|
|
100
|
+
this._vectorSearchEnabled = true;
|
|
101
|
+
(0, debug_logger_js_1.info)('[sqlite-adapter] Loaded sqlite-vec extension');
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
this._vectorSearchEnabled = false;
|
|
105
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
106
|
+
(0, debug_logger_js_1.warn)(`[sqlite-adapter] sqlite-vec unavailable (Tier 2 fallback): ${message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this._vectorSearchEnabled = false;
|
|
111
|
+
(0, debug_logger_js_1.warn)('[sqlite-adapter] sqlite-vec package not installed; vector search disabled');
|
|
112
|
+
}
|
|
113
|
+
return this.db;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Disconnect from database
|
|
117
|
+
*/
|
|
118
|
+
disconnect() {
|
|
119
|
+
if (this.db) {
|
|
120
|
+
this.db.close();
|
|
121
|
+
this.db = null;
|
|
122
|
+
(0, debug_logger_js_1.info)('[sqlite-adapter] Disconnected from database');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if connected
|
|
127
|
+
*/
|
|
128
|
+
isConnected() {
|
|
129
|
+
return this.db !== null && this.db.open;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Prepare a SQL statement
|
|
133
|
+
*/
|
|
134
|
+
prepare(sql) {
|
|
135
|
+
if (!this.isConnected() || !this.db) {
|
|
136
|
+
throw new Error('Database not connected');
|
|
137
|
+
}
|
|
138
|
+
const stmt = this.db.prepare(sql);
|
|
139
|
+
return new statement_js_1.SQLiteStatement(stmt);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Execute raw SQL
|
|
143
|
+
*/
|
|
144
|
+
exec(sql) {
|
|
145
|
+
if (!this.isConnected() || !this.db) {
|
|
146
|
+
throw new Error('Database not connected');
|
|
147
|
+
}
|
|
148
|
+
this.db.exec(sql);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Execute function in transaction
|
|
152
|
+
*/
|
|
153
|
+
transaction(fn) {
|
|
154
|
+
if (!this.isConnected() || !this.db) {
|
|
155
|
+
throw new Error('Database not connected');
|
|
156
|
+
}
|
|
157
|
+
const txn = this.db.transaction(fn);
|
|
158
|
+
return txn();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Vector similarity search using sqlite-vec (vec0 virtual table)
|
|
162
|
+
*/
|
|
163
|
+
vectorSearch(embedding, limit = 5) {
|
|
164
|
+
if (!this.isConnected()) {
|
|
165
|
+
throw new Error('Database not connected');
|
|
166
|
+
}
|
|
167
|
+
if (!this._vectorSearchEnabled) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const embeddingJson = JSON.stringify(Array.from(embedding));
|
|
171
|
+
const stmt = this.prepare(`
|
|
172
|
+
SELECT
|
|
173
|
+
rowid,
|
|
174
|
+
embedding,
|
|
175
|
+
distance
|
|
176
|
+
FROM vss_memories
|
|
177
|
+
WHERE embedding MATCH vec_f32(?)
|
|
178
|
+
LIMIT ?
|
|
179
|
+
`);
|
|
180
|
+
const queryVector = embedding instanceof Float32Array ? embedding : Float32Array.from(embedding);
|
|
181
|
+
const results = stmt.all(embeddingJson, Math.max(limit, 1));
|
|
182
|
+
return results
|
|
183
|
+
.map((row) => {
|
|
184
|
+
const candidate = bufferToVector(row.embedding);
|
|
185
|
+
if (!candidate) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const similarity = (0, embeddings_js_1.cosineSimilarity)(candidate, queryVector);
|
|
189
|
+
return {
|
|
190
|
+
rowid: row.rowid,
|
|
191
|
+
similarity,
|
|
192
|
+
distance: 1 - similarity,
|
|
193
|
+
};
|
|
194
|
+
})
|
|
195
|
+
.filter((r) => r !== null);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Insert vector embedding
|
|
199
|
+
*/
|
|
200
|
+
insertEmbedding(rowid, embedding) {
|
|
201
|
+
if (!this.isConnected()) {
|
|
202
|
+
throw new Error('Database not connected');
|
|
203
|
+
}
|
|
204
|
+
if (!this._vectorSearchEnabled) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const embeddingJson = JSON.stringify(Array.from(embedding));
|
|
208
|
+
// CRITICAL FIX: sqlite-vec virtual tables accept rowid as literal but not via ? placeholder
|
|
209
|
+
// Using template literal with Number() cast for safety (prevents SQL injection)
|
|
210
|
+
const safeRowid = Number(rowid);
|
|
211
|
+
if (!Number.isInteger(safeRowid) || safeRowid < 1) {
|
|
212
|
+
throw new Error(`Invalid rowid: ${rowid}`);
|
|
213
|
+
}
|
|
214
|
+
const stmt = this.prepare(`
|
|
215
|
+
INSERT OR REPLACE INTO vss_memories(rowid, embedding)
|
|
216
|
+
VALUES (${safeRowid}, ?)
|
|
217
|
+
`);
|
|
218
|
+
return stmt.run(embeddingJson);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get last inserted row ID
|
|
222
|
+
*/
|
|
223
|
+
getLastInsertRowid() {
|
|
224
|
+
if (!this.isConnected() || !this.db) {
|
|
225
|
+
throw new Error('Database not connected');
|
|
226
|
+
}
|
|
227
|
+
// better-sqlite3 provides this via Database instance
|
|
228
|
+
const result = this.db.prepare('SELECT last_insert_rowid() as rowid').get();
|
|
229
|
+
return result.rowid;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Run migrations
|
|
233
|
+
*/
|
|
234
|
+
runMigrations(migrationsDir) {
|
|
235
|
+
if (!this.isConnected()) {
|
|
236
|
+
throw new Error('Database not connected');
|
|
237
|
+
}
|
|
238
|
+
// Check if schema_version table exists
|
|
239
|
+
const tables = this.prepare(`
|
|
240
|
+
SELECT name FROM sqlite_master
|
|
241
|
+
WHERE type='table' AND name='schema_version'
|
|
242
|
+
`).all();
|
|
243
|
+
let currentVersion = 0;
|
|
244
|
+
if (tables.length > 0) {
|
|
245
|
+
const version = this.prepare('SELECT MAX(version) as version FROM schema_version').get();
|
|
246
|
+
currentVersion = version?.version || 0;
|
|
247
|
+
}
|
|
248
|
+
(0, debug_logger_js_1.info)(`[sqlite-adapter] Current schema version: ${currentVersion}`);
|
|
249
|
+
// Get all migration files
|
|
250
|
+
const migrationFiles = fs_1.default
|
|
251
|
+
.readdirSync(migrationsDir)
|
|
252
|
+
.filter((file) => file.endsWith('.sql'))
|
|
253
|
+
.sort();
|
|
254
|
+
// Apply migrations
|
|
255
|
+
for (const file of migrationFiles) {
|
|
256
|
+
const versionMatch = file.match(/^(\d+)-/);
|
|
257
|
+
if (!versionMatch) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const version = parseInt(versionMatch[1], 10);
|
|
261
|
+
if (version <= currentVersion) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const migrationPath = path_1.default.join(migrationsDir, file);
|
|
265
|
+
const migrationSQL = fs_1.default.readFileSync(migrationPath, 'utf8');
|
|
266
|
+
(0, debug_logger_js_1.info)(`[sqlite-adapter] Applying migration: ${file}`);
|
|
267
|
+
try {
|
|
268
|
+
this.exec('BEGIN TRANSACTION');
|
|
269
|
+
this.exec(migrationSQL);
|
|
270
|
+
this.exec('COMMIT');
|
|
271
|
+
// Record migration in schema_version table (outside transaction for idempotency)
|
|
272
|
+
this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
|
|
273
|
+
(0, debug_logger_js_1.info)(`[sqlite-adapter] Migration ${file} applied successfully`);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
this.exec('ROLLBACK');
|
|
277
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
278
|
+
// Handle duplicate column errors as idempotent (migration 003)
|
|
279
|
+
if (message.includes('duplicate column')) {
|
|
280
|
+
(0, debug_logger_js_1.warn)(`[sqlite-adapter] Migration ${file} skipped (duplicate column - already applied)`);
|
|
281
|
+
// Record migration as applied
|
|
282
|
+
this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (message.includes('no such table')) {
|
|
286
|
+
// Only skip if this migration contains an ALTER TABLE (adding column to optional table)
|
|
287
|
+
// Note: migrationSQL may contain comments, so check if ALTER TABLE exists anywhere
|
|
288
|
+
const hasAlterTable = migrationSQL.toUpperCase().includes('ALTER TABLE');
|
|
289
|
+
if (!hasAlterTable) {
|
|
290
|
+
(0, debug_logger_js_1.error)(`[sqlite-adapter] Migration ${file} failed (missing required table):`, err);
|
|
291
|
+
throw new Error(`Migration ${file} failed: ${message}`);
|
|
292
|
+
}
|
|
293
|
+
(0, debug_logger_js_1.warn)(`[sqlite-adapter] Migration ${file} skipped: ALTER TABLE on non-existent table (${message})`);
|
|
294
|
+
this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
(0, debug_logger_js_1.error)(`[sqlite-adapter] Migration ${file} failed:`, err);
|
|
298
|
+
throw new Error(`Migration ${file} failed: ${message}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Create vss_memories table if not exists
|
|
302
|
+
if (this._vectorSearchEnabled) {
|
|
303
|
+
const vssTables = this.prepare(`
|
|
304
|
+
SELECT name FROM sqlite_master
|
|
305
|
+
WHERE type='table' AND name='vss_memories'
|
|
306
|
+
`).all();
|
|
307
|
+
if (vssTables.length === 0) {
|
|
308
|
+
(0, debug_logger_js_1.info)('[sqlite-adapter] Creating vss_memories virtual table via sqlite-vec');
|
|
309
|
+
this.exec(`
|
|
310
|
+
CREATE VIRTUAL TABLE vss_memories USING vec0(
|
|
311
|
+
embedding float[384]
|
|
312
|
+
)
|
|
313
|
+
`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
(0, debug_logger_js_1.warn)('[sqlite-adapter] Skipping vss_memories creation (sqlite-vec unavailable)');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
exports.SQLiteAdapter = SQLiteAdapter;
|
|
322
|
+
exports.default = SQLiteAdapter;
|
|
323
|
+
function bufferToVector(buffer) {
|
|
324
|
+
if (!buffer) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
328
|
+
return new Float32Array(arrayBuffer);
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=sqlite-adapter.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Statement Interface
|
|
3
|
+
*
|
|
4
|
+
* Wraps database-specific prepared statements to provide consistent API
|
|
5
|
+
* Compatible with better-sqlite3 and pg
|
|
6
|
+
*
|
|
7
|
+
* @module statement
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Run result from statement execution
|
|
11
|
+
*/
|
|
12
|
+
export interface RunResult {
|
|
13
|
+
changes: number;
|
|
14
|
+
lastInsertRowid: number | bigint;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Prepared statement interface
|
|
18
|
+
* Common interface used across all database modules
|
|
19
|
+
*/
|
|
20
|
+
export interface PreparedStatement {
|
|
21
|
+
run: (...args: unknown[]) => RunResult;
|
|
22
|
+
get: (...args: unknown[]) => unknown;
|
|
23
|
+
all: (...args: unknown[]) => unknown[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* better-sqlite3 native statement type
|
|
27
|
+
*/
|
|
28
|
+
interface BetterSQLiteStatement {
|
|
29
|
+
all: (...params: unknown[]) => unknown[];
|
|
30
|
+
get: (...params: unknown[]) => unknown;
|
|
31
|
+
run: (...params: unknown[]) => {
|
|
32
|
+
changes: number;
|
|
33
|
+
lastInsertRowid: number | bigint;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* pg client type
|
|
38
|
+
*/
|
|
39
|
+
interface PgClient {
|
|
40
|
+
query: (sql: string, params: unknown[]) => Promise<{
|
|
41
|
+
rows: unknown[];
|
|
42
|
+
rowCount: number | null;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Base statement interface
|
|
47
|
+
* All statement wrappers must implement these methods
|
|
48
|
+
*/
|
|
49
|
+
export declare abstract class Statement {
|
|
50
|
+
/**
|
|
51
|
+
* Execute statement and return all rows
|
|
52
|
+
* @param params - Query parameters
|
|
53
|
+
* @returns All matching rows
|
|
54
|
+
*/
|
|
55
|
+
abstract all(...params: unknown[]): object[];
|
|
56
|
+
/**
|
|
57
|
+
* Execute statement and return first row
|
|
58
|
+
* @param params - Query parameters
|
|
59
|
+
* @returns First matching row or undefined
|
|
60
|
+
*/
|
|
61
|
+
abstract get(...params: unknown[]): object | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Execute statement without returning rows
|
|
64
|
+
* @param params - Query parameters
|
|
65
|
+
* @returns Execution info (changes, lastInsertRowid)
|
|
66
|
+
*/
|
|
67
|
+
abstract run(...params: unknown[]): RunResult;
|
|
68
|
+
/**
|
|
69
|
+
* Release statement resources
|
|
70
|
+
*/
|
|
71
|
+
finalize(): void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* SQLite statement wrapper (better-sqlite3)
|
|
75
|
+
*/
|
|
76
|
+
export declare class SQLiteStatement extends Statement {
|
|
77
|
+
private stmt;
|
|
78
|
+
constructor(stmt: BetterSQLiteStatement);
|
|
79
|
+
all(...params: unknown[]): object[];
|
|
80
|
+
get(...params: unknown[]): object | undefined;
|
|
81
|
+
run(...params: unknown[]): RunResult;
|
|
82
|
+
finalize(): void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* PostgreSQL statement wrapper (pg)
|
|
86
|
+
*
|
|
87
|
+
* Maps pg's async query interface to synchronous-like API
|
|
88
|
+
* Note: This requires careful handling in the adapter
|
|
89
|
+
*/
|
|
90
|
+
export declare class PostgreSQLStatement extends Statement {
|
|
91
|
+
private client;
|
|
92
|
+
private sql;
|
|
93
|
+
constructor(client: PgClient, sql: string);
|
|
94
|
+
/**
|
|
95
|
+
* Convert SQLite ? placeholders to PostgreSQL $1, $2, ...
|
|
96
|
+
* @param sql - SQL with ? placeholders
|
|
97
|
+
* @returns SQL with $N placeholders
|
|
98
|
+
*
|
|
99
|
+
* Note: This naive implementation replaces all '?' characters.
|
|
100
|
+
* It does not handle '?' inside SQL string literals or comments.
|
|
101
|
+
* For production use with complex SQL, consider a proper SQL parser.
|
|
102
|
+
*/
|
|
103
|
+
static convertPlaceholders(sql: string): string;
|
|
104
|
+
all(..._params: unknown[]): object[];
|
|
105
|
+
get(..._params: unknown[]): object | undefined;
|
|
106
|
+
run(..._params: unknown[]): RunResult;
|
|
107
|
+
allAsync(...params: unknown[]): Promise<unknown[]>;
|
|
108
|
+
getAsync(...params: unknown[]): Promise<unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* Execute statement asynchronously without returning rows
|
|
111
|
+
*
|
|
112
|
+
* Note: lastInsertRowid requires the SQL to include 'RETURNING id'.
|
|
113
|
+
* PostgreSQL does not have SQLite's last_insert_rowid() equivalent.
|
|
114
|
+
*/
|
|
115
|
+
runAsync(...params: unknown[]): Promise<RunResult>;
|
|
116
|
+
finalize(): void;
|
|
117
|
+
}
|
|
118
|
+
export {};
|
|
119
|
+
//# sourceMappingURL=statement.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unified Statement Interface
|
|
4
|
+
*
|
|
5
|
+
* Wraps database-specific prepared statements to provide consistent API
|
|
6
|
+
* Compatible with better-sqlite3 and pg
|
|
7
|
+
*
|
|
8
|
+
* @module statement
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.PostgreSQLStatement = exports.SQLiteStatement = exports.Statement = void 0;
|
|
12
|
+
/**
|
|
13
|
+
* Base statement interface
|
|
14
|
+
* All statement wrappers must implement these methods
|
|
15
|
+
*/
|
|
16
|
+
class Statement {
|
|
17
|
+
/**
|
|
18
|
+
* Release statement resources
|
|
19
|
+
*/
|
|
20
|
+
finalize() {
|
|
21
|
+
// Optional: Some drivers don't require cleanup
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.Statement = Statement;
|
|
25
|
+
/**
|
|
26
|
+
* SQLite statement wrapper (better-sqlite3)
|
|
27
|
+
*/
|
|
28
|
+
class SQLiteStatement extends Statement {
|
|
29
|
+
stmt;
|
|
30
|
+
constructor(stmt) {
|
|
31
|
+
super();
|
|
32
|
+
this.stmt = stmt;
|
|
33
|
+
}
|
|
34
|
+
all(...params) {
|
|
35
|
+
return this.stmt.all(...params);
|
|
36
|
+
}
|
|
37
|
+
get(...params) {
|
|
38
|
+
return this.stmt.get(...params);
|
|
39
|
+
}
|
|
40
|
+
run(...params) {
|
|
41
|
+
return this.stmt.run(...params);
|
|
42
|
+
}
|
|
43
|
+
finalize() {
|
|
44
|
+
// better-sqlite3 statements don't need explicit cleanup
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.SQLiteStatement = SQLiteStatement;
|
|
48
|
+
/**
|
|
49
|
+
* PostgreSQL statement wrapper (pg)
|
|
50
|
+
*
|
|
51
|
+
* Maps pg's async query interface to synchronous-like API
|
|
52
|
+
* Note: This requires careful handling in the adapter
|
|
53
|
+
*/
|
|
54
|
+
class PostgreSQLStatement extends Statement {
|
|
55
|
+
client;
|
|
56
|
+
sql;
|
|
57
|
+
constructor(client, sql) {
|
|
58
|
+
super();
|
|
59
|
+
this.client = client;
|
|
60
|
+
this.sql = sql;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Convert SQLite ? placeholders to PostgreSQL $1, $2, ...
|
|
64
|
+
* @param sql - SQL with ? placeholders
|
|
65
|
+
* @returns SQL with $N placeholders
|
|
66
|
+
*
|
|
67
|
+
* Note: This naive implementation replaces all '?' characters.
|
|
68
|
+
* It does not handle '?' inside SQL string literals or comments.
|
|
69
|
+
* For production use with complex SQL, consider a proper SQL parser.
|
|
70
|
+
*/
|
|
71
|
+
static convertPlaceholders(sql) {
|
|
72
|
+
let index = 0;
|
|
73
|
+
return sql.replace(/\?/g, () => `$${++index}`);
|
|
74
|
+
}
|
|
75
|
+
// Note: PostgreSQL methods are async but the interface expects sync
|
|
76
|
+
// In practice, these would need to be wrapped or the adapter would handle async
|
|
77
|
+
all(..._params) {
|
|
78
|
+
// This is a simplified sync interface - actual pg usage would be async
|
|
79
|
+
throw new Error('PostgreSQLStatement requires async usage - use allAsync() instead');
|
|
80
|
+
}
|
|
81
|
+
get(..._params) {
|
|
82
|
+
throw new Error('PostgreSQLStatement requires async usage - use getAsync() instead');
|
|
83
|
+
}
|
|
84
|
+
run(..._params) {
|
|
85
|
+
throw new Error('PostgreSQLStatement requires async usage - use runAsync() instead');
|
|
86
|
+
}
|
|
87
|
+
async allAsync(...params) {
|
|
88
|
+
const result = await this.client.query(this.sql, params);
|
|
89
|
+
return result.rows;
|
|
90
|
+
}
|
|
91
|
+
async getAsync(...params) {
|
|
92
|
+
const result = await this.client.query(this.sql, params);
|
|
93
|
+
return result.rows[0];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Execute statement asynchronously without returning rows
|
|
97
|
+
*
|
|
98
|
+
* Note: lastInsertRowid requires the SQL to include 'RETURNING id'.
|
|
99
|
+
* PostgreSQL does not have SQLite's last_insert_rowid() equivalent.
|
|
100
|
+
*/
|
|
101
|
+
async runAsync(...params) {
|
|
102
|
+
const result = await this.client.query(this.sql, params);
|
|
103
|
+
return {
|
|
104
|
+
changes: result.rowCount ?? 0,
|
|
105
|
+
lastInsertRowid: result.rows[0]?.id ?? 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
finalize() {
|
|
109
|
+
// pg statements don't need explicit cleanup
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.PostgreSQLStatement = PostgreSQLStatement;
|
|
113
|
+
//# sourceMappingURL=statement.js.map
|