@jungjaehoon/mama-core 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -107,7 +107,7 @@ const mamaApi = require('@jungjaehoon/mama-core/mama-api');
107
107
  ## Dependencies
108
108
 
109
109
  - **@huggingface/transformers** - Local embedding generation
110
- - **better-sqlite3** - SQLite database
110
+ - **node:sqlite** - Built-in SQLite runtime (Node.js 22+)
111
111
  - **Pure-TS cosine similarity** - Vector search (no native extensions)
112
112
 
113
113
  ## Development
@@ -15,6 +15,7 @@
15
15
  export interface MAMAConfig {
16
16
  modelName: string;
17
17
  embeddingDim: number;
18
+ quantized: boolean;
18
19
  cacheDir: string;
19
20
  [key: string]: unknown;
20
21
  }
@@ -36,6 +37,10 @@ export declare function getModelName(): string;
36
37
  * Get current embedding dimension
37
38
  */
38
39
  export declare function getEmbeddingDim(): number;
40
+ /**
41
+ * Get whether to use quantized model
42
+ */
43
+ export declare function getQuantized(): boolean;
39
44
  /**
40
45
  * Get current cache directory
41
46
  */
@@ -43,6 +48,7 @@ export declare function getCacheDir(): string;
43
48
  export interface ConfigUpdates {
44
49
  modelName?: string;
45
50
  embeddingDim?: number;
51
+ quantized?: boolean;
46
52
  cacheDir?: string;
47
53
  }
48
54
  /**
@@ -21,6 +21,7 @@ exports.DEFAULT_CONFIG = void 0;
21
21
  exports.loadConfig = loadConfig;
22
22
  exports.getModelName = getModelName;
23
23
  exports.getEmbeddingDim = getEmbeddingDim;
24
+ exports.getQuantized = getQuantized;
24
25
  exports.getCacheDir = getCacheDir;
25
26
  exports.updateConfig = updateConfig;
26
27
  exports.getConfigPath = getConfigPath;
@@ -32,6 +33,7 @@ const debug_logger_js_1 = require("./debug-logger.js");
32
33
  exports.DEFAULT_CONFIG = {
33
34
  modelName: 'Xenova/multilingual-e5-small',
34
35
  embeddingDim: 384,
36
+ quantized: true,
35
37
  cacheDir: path_1.default.join(os_1.default.homedir(), '.cache', 'huggingface', 'transformers'),
36
38
  };
37
39
  // Config file path
@@ -129,6 +131,13 @@ function getEmbeddingDim() {
129
131
  const config = loadConfig();
130
132
  return config.embeddingDim;
131
133
  }
134
+ /**
135
+ * Get whether to use quantized model
136
+ */
137
+ function getQuantized() {
138
+ const config = loadConfig();
139
+ return config.quantized;
140
+ }
132
141
  /**
133
142
  * Get current cache directory
134
143
  */
@@ -7,9 +7,10 @@
7
7
  * @module db-adapter
8
8
  */
9
9
  import { SQLiteAdapter } from './sqlite-adapter.js';
10
+ import { NodeSQLiteAdapter } from './node-sqlite-adapter.js';
10
11
  import { DatabaseAdapter, type VectorSearchResult, type RunResult } from './base-adapter.js';
11
12
  import type { Statement } from './statement.js';
12
- export { DatabaseAdapter, SQLiteAdapter };
13
+ export { DatabaseAdapter, SQLiteAdapter, NodeSQLiteAdapter };
13
14
  export type { Statement, VectorSearchResult, RunResult };
14
15
  export interface AdapterConfig {
15
16
  dbPath?: string;
@@ -8,11 +8,13 @@
8
8
  * @module db-adapter
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.SQLiteAdapter = exports.DatabaseAdapter = void 0;
11
+ exports.NodeSQLiteAdapter = exports.SQLiteAdapter = exports.DatabaseAdapter = void 0;
12
12
  exports.createAdapter = createAdapter;
13
13
  const debug_logger_js_1 = require("../debug-logger.js");
14
14
  const sqlite_adapter_js_1 = require("./sqlite-adapter.js");
15
15
  Object.defineProperty(exports, "SQLiteAdapter", { enumerable: true, get: function () { return sqlite_adapter_js_1.SQLiteAdapter; } });
16
+ const node_sqlite_adapter_js_1 = require("./node-sqlite-adapter.js");
17
+ Object.defineProperty(exports, "NodeSQLiteAdapter", { enumerable: true, get: function () { return node_sqlite_adapter_js_1.NodeSQLiteAdapter; } });
16
18
  const base_adapter_js_1 = require("./base-adapter.js");
17
19
  Object.defineProperty(exports, "DatabaseAdapter", { enumerable: true, get: function () { return base_adapter_js_1.DatabaseAdapter; } });
18
20
  /**
@@ -22,7 +24,14 @@ Object.defineProperty(exports, "DatabaseAdapter", { enumerable: true, get: funct
22
24
  * @returns Configured SQLite adapter instance
23
25
  */
24
26
  function createAdapter(config = {}) {
25
- (0, debug_logger_js_1.info)('[db-adapter] Using SQLite adapter (plugin mode)');
27
+ const configuredDriver = process.env.MAMA_SQLITE_DRIVER;
28
+ if (configuredDriver &&
29
+ configuredDriver !== 'node-sqlite' &&
30
+ configuredDriver !== 'node:sqlite' &&
31
+ configuredDriver !== 'auto') {
32
+ throw new Error(`Unsupported SQLite driver "${configuredDriver}". MAMA now requires node:sqlite.`);
33
+ }
34
+ (0, debug_logger_js_1.info)('[db-adapter] Using node:sqlite adapter');
26
35
  const dbPath = config.dbPath || process.env.MAMA_DB_PATH;
27
36
  return new sqlite_adapter_js_1.SQLiteAdapter({ dbPath });
28
37
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * SQLite Database Adapter using node:sqlite
3
+ *
4
+ * Keeps the existing DatabaseAdapter surface while avoiding external native addon installation.
5
+ */
6
+ import { DatabaseAdapter, type VectorSearchResult, type RunResult } from './base-adapter.js';
7
+ import { type Statement } from './statement.js';
8
+ interface SQLiteAdapterConfig {
9
+ dbPath?: string;
10
+ }
11
+ interface NodeSQLiteDatabaseLike {
12
+ exec(sql: string): void;
13
+ close(): void;
14
+ prepare(sql: string): NodeSQLiteStatementLike;
15
+ }
16
+ interface NodeSQLiteStatementLike {
17
+ all: (...params: unknown[]) => unknown[];
18
+ get: (...params: unknown[]) => unknown;
19
+ run: (...params: unknown[]) => {
20
+ changes: number;
21
+ lastInsertRowid: number | bigint;
22
+ };
23
+ }
24
+ declare class NodeSQLiteConnection {
25
+ private db;
26
+ private connected;
27
+ constructor(db: NodeSQLiteDatabaseLike);
28
+ prepare(sql: string): NodeSQLiteStatementLike;
29
+ exec(sql: string): void;
30
+ pragma(sql: string, options?: {
31
+ simple?: boolean;
32
+ }): unknown;
33
+ close(): void;
34
+ get open(): boolean;
35
+ }
36
+ export declare class NodeSQLiteAdapter extends DatabaseAdapter {
37
+ private config;
38
+ private db;
39
+ private _vectorSearchEnabled;
40
+ constructor(config?: SQLiteAdapterConfig);
41
+ get vectorSearchEnabled(): boolean;
42
+ getDbPath(): string;
43
+ connect(): NodeSQLiteConnection;
44
+ disconnect(): void;
45
+ isConnected(): boolean;
46
+ prepare(sql: string): Statement;
47
+ exec(sql: string): void;
48
+ transaction<T>(fn: () => T): T;
49
+ vectorSearch(embedding: Float32Array | number[], limit?: number): VectorSearchResult[] | null;
50
+ insertEmbedding(rowid: number, embedding: Float32Array | number[]): RunResult | null;
51
+ getLastInsertRowid(): number;
52
+ runMigrations(migrationsDir: string): void;
53
+ private migrateFromVssMemories;
54
+ }
55
+ export default NodeSQLiteAdapter;
56
+ //# sourceMappingURL=node-sqlite-adapter.d.ts.map
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ /**
3
+ * SQLite Database Adapter using node:sqlite
4
+ *
5
+ * Keeps the existing DatabaseAdapter surface while avoiding external native addon installation.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.NodeSQLiteAdapter = void 0;
12
+ const path_1 = __importDefault(require("path"));
13
+ const os_1 = __importDefault(require("os"));
14
+ const fs_1 = __importDefault(require("fs"));
15
+ const base_adapter_js_1 = require("./base-adapter.js");
16
+ const node_sqlite_statement_js_1 = require("./node-sqlite-statement.js");
17
+ const debug_logger_js_1 = require("../debug-logger.js");
18
+ const embeddings_js_1 = require("../embeddings.js");
19
+ const LEGACY_DB_PATH = path_1.default.join(os_1.default.homedir(), '.spinelift', 'memories.db');
20
+ const DEFAULT_DB_PATH = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-memory.db');
21
+ let DatabaseSync = null;
22
+ try {
23
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
24
+ ({ DatabaseSync } = require('node:sqlite'));
25
+ }
26
+ catch {
27
+ DatabaseSync = null;
28
+ }
29
+ class NodeSQLiteConnection {
30
+ db;
31
+ connected = true;
32
+ constructor(db) {
33
+ this.db = db;
34
+ }
35
+ prepare(sql) {
36
+ return this.db.prepare(sql);
37
+ }
38
+ exec(sql) {
39
+ this.db.exec(sql);
40
+ }
41
+ pragma(sql, options) {
42
+ const query = sql.trim().replace(/^PRAGMA\s+/i, '');
43
+ const stmt = this.db.prepare(`PRAGMA ${query}`);
44
+ if (options?.simple) {
45
+ const row = stmt.get();
46
+ if (!row) {
47
+ return undefined;
48
+ }
49
+ return Object.values(row)[0];
50
+ }
51
+ return stmt.all();
52
+ }
53
+ close() {
54
+ if (!this.connected) {
55
+ return;
56
+ }
57
+ this.db.close();
58
+ this.connected = false;
59
+ }
60
+ get open() {
61
+ return this.connected;
62
+ }
63
+ }
64
+ class NodeSQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
65
+ config;
66
+ db = null;
67
+ _vectorSearchEnabled = true;
68
+ constructor(config = {}) {
69
+ super();
70
+ this.config = config;
71
+ }
72
+ get vectorSearchEnabled() {
73
+ return this._vectorSearchEnabled;
74
+ }
75
+ getDbPath() {
76
+ const envPath = process.env.MAMA_DB_PATH || process.env.MAMA_DATABASE_PATH;
77
+ const configPath = this.config.dbPath;
78
+ let expandedEnvPath = envPath;
79
+ if (envPath) {
80
+ expandedEnvPath = envPath.replace(/\$\{HOME\}/g, os_1.default.homedir()).replace(/^~/, os_1.default.homedir());
81
+ }
82
+ const targetPath = configPath || expandedEnvPath || DEFAULT_DB_PATH;
83
+ if (!configPath && !envPath && fs_1.default.existsSync(LEGACY_DB_PATH)) {
84
+ (0, debug_logger_js_1.info)('[node-sqlite-adapter] Found legacy database at ~/.spinelift/memories.db, using it for backward compatibility');
85
+ return LEGACY_DB_PATH;
86
+ }
87
+ return targetPath;
88
+ }
89
+ connect() {
90
+ if (this.db) {
91
+ return this.db;
92
+ }
93
+ if (!DatabaseSync) {
94
+ throw new Error('node:sqlite is not available in this Node.js runtime. Use Node 22.13+.');
95
+ }
96
+ const dbPath = this.getDbPath();
97
+ const dbDir = path_1.default.dirname(dbPath);
98
+ if (!fs_1.default.existsSync(dbDir)) {
99
+ fs_1.default.mkdirSync(dbDir, { recursive: true });
100
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Created database directory: ${dbDir}`);
101
+ }
102
+ const database = new DatabaseSync(dbPath);
103
+ this.db = new NodeSQLiteConnection(database);
104
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Opened database at: ${dbPath}`);
105
+ this.db.pragma('journal_mode = WAL');
106
+ this.db.pragma('busy_timeout = 5000');
107
+ this.db.pragma('synchronous = NORMAL');
108
+ this.db.pragma('cache_size = -64000');
109
+ this.db.pragma('temp_store = MEMORY');
110
+ this.db.pragma('foreign_keys = ON');
111
+ (0, debug_logger_js_1.info)('[node-sqlite-adapter] Vector search: pure TS cosine similarity (no native extensions)');
112
+ return this.db;
113
+ }
114
+ disconnect() {
115
+ if (this.db) {
116
+ this.db.close();
117
+ this.db = null;
118
+ (0, debug_logger_js_1.info)('[node-sqlite-adapter] Disconnected from database');
119
+ }
120
+ }
121
+ isConnected() {
122
+ return this.db !== null && this.db.open;
123
+ }
124
+ prepare(sql) {
125
+ if (!this.isConnected() || !this.db) {
126
+ throw new Error('Database not connected');
127
+ }
128
+ return new node_sqlite_statement_js_1.NodeSQLiteStatement(this.db.prepare(sql));
129
+ }
130
+ exec(sql) {
131
+ if (!this.isConnected() || !this.db) {
132
+ throw new Error('Database not connected');
133
+ }
134
+ this.db.exec(sql);
135
+ }
136
+ transaction(fn) {
137
+ if (!this.isConnected()) {
138
+ throw new Error('Database not connected');
139
+ }
140
+ this.exec('BEGIN TRANSACTION');
141
+ try {
142
+ const result = fn();
143
+ if (((typeof result === 'object' && result !== null) || typeof result === 'function') &&
144
+ typeof result.then === 'function') {
145
+ throw new Error('DatabaseAdapter.transaction() callbacks must be synchronous');
146
+ }
147
+ this.exec('COMMIT');
148
+ return result;
149
+ }
150
+ catch (error) {
151
+ try {
152
+ this.exec('ROLLBACK');
153
+ }
154
+ catch {
155
+ // Preserve the original transaction failure when rollback also fails.
156
+ }
157
+ throw error;
158
+ }
159
+ }
160
+ vectorSearch(embedding, limit = 5) {
161
+ if (!this.isConnected()) {
162
+ throw new Error('Database not connected');
163
+ }
164
+ const queryVector = embedding instanceof Float32Array ? embedding : Float32Array.from(embedding);
165
+ const tableCheck = this.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='embeddings'`).all();
166
+ if (tableCheck.length === 0) {
167
+ throw new Error('Embeddings table missing');
168
+ }
169
+ const effectiveLimit = Math.max(limit, 1);
170
+ const batchSize = 500;
171
+ const bestMatches = [];
172
+ let offset = 0;
173
+ let hasMoreRows = true;
174
+ while (hasMoreRows) {
175
+ const rows = this.prepare('SELECT rowid, embedding FROM embeddings LIMIT ? OFFSET ?').all(batchSize, offset);
176
+ if (rows.length === 0) {
177
+ hasMoreRows = false;
178
+ continue;
179
+ }
180
+ for (const row of rows) {
181
+ const candidate = bytesToVector(row.embedding);
182
+ if (!candidate)
183
+ continue;
184
+ if (candidate.length !== queryVector.length) {
185
+ (0, debug_logger_js_1.warn)(`Skipping rowid ${row.rowid}: dimension mismatch (${candidate.length} vs ${queryVector.length})`);
186
+ continue;
187
+ }
188
+ const similarity = (0, embeddings_js_1.cosineSimilarity)(candidate, queryVector);
189
+ const scoredRow = {
190
+ rowid: row.rowid,
191
+ similarity,
192
+ distance: 1 - similarity,
193
+ };
194
+ if (bestMatches.length < effectiveLimit) {
195
+ bestMatches.push(scoredRow);
196
+ bestMatches.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
197
+ continue;
198
+ }
199
+ const weakestMatch = bestMatches[bestMatches.length - 1];
200
+ if ((scoredRow.similarity ?? 0) > (weakestMatch.similarity ?? 0)) {
201
+ bestMatches.pop();
202
+ bestMatches.push(scoredRow);
203
+ bestMatches.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
204
+ }
205
+ }
206
+ offset += rows.length;
207
+ }
208
+ return bestMatches;
209
+ }
210
+ insertEmbedding(rowid, embedding) {
211
+ if (!this.isConnected()) {
212
+ throw new Error('Database not connected');
213
+ }
214
+ const vec = embedding instanceof Float32Array ? embedding : Float32Array.from(embedding);
215
+ const buffer = Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
216
+ const stmt = this.prepare(`
217
+ INSERT OR REPLACE INTO embeddings(rowid, embedding)
218
+ VALUES (?, ?)
219
+ `);
220
+ return stmt.run(rowid, buffer);
221
+ }
222
+ getLastInsertRowid() {
223
+ if (!this.isConnected()) {
224
+ throw new Error('Database not connected');
225
+ }
226
+ const result = this.prepare('SELECT last_insert_rowid() as rowid').get();
227
+ return result.rowid;
228
+ }
229
+ runMigrations(migrationsDir) {
230
+ if (!this.isConnected()) {
231
+ throw new Error('Database not connected');
232
+ }
233
+ const tables = this.prepare(`
234
+ SELECT name FROM sqlite_master
235
+ WHERE type='table' AND name='schema_version'
236
+ `).all();
237
+ let currentVersion = 0;
238
+ if (tables.length > 0) {
239
+ const version = this.prepare('SELECT MAX(version) as version FROM schema_version').get();
240
+ currentVersion = version?.version || 0;
241
+ }
242
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Current schema version: ${currentVersion}`);
243
+ const migrationFiles = fs_1.default
244
+ .readdirSync(migrationsDir)
245
+ .filter((file) => file.endsWith('.sql'))
246
+ .sort();
247
+ for (const file of migrationFiles) {
248
+ const versionMatch = file.match(/^(\d+)-/);
249
+ if (!versionMatch) {
250
+ continue;
251
+ }
252
+ const version = parseInt(versionMatch[1], 10);
253
+ if (version <= currentVersion) {
254
+ continue;
255
+ }
256
+ const migrationPath = path_1.default.join(migrationsDir, file);
257
+ const migrationSQL = fs_1.default.readFileSync(migrationPath, 'utf8');
258
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Applying migration: ${file}`);
259
+ try {
260
+ this.exec('BEGIN TRANSACTION');
261
+ this.exec(migrationSQL);
262
+ this.exec('COMMIT');
263
+ this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
264
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Migration ${file} applied successfully`);
265
+ }
266
+ catch (err) {
267
+ this.exec('ROLLBACK');
268
+ const message = err instanceof Error ? err.message : String(err);
269
+ if (message.includes('duplicate column')) {
270
+ (0, debug_logger_js_1.warn)(`[node-sqlite-adapter] Migration ${file} skipped (duplicate column - already applied)`);
271
+ this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
272
+ continue;
273
+ }
274
+ if (message.includes('no such table')) {
275
+ const hasAlterTable = migrationSQL.toUpperCase().includes('ALTER TABLE');
276
+ if (!hasAlterTable) {
277
+ (0, debug_logger_js_1.error)(`[node-sqlite-adapter] Migration ${file} failed (missing required table):`, err);
278
+ throw new Error(`Migration ${file} failed: ${message}`);
279
+ }
280
+ (0, debug_logger_js_1.warn)(`[node-sqlite-adapter] Migration ${file} skipped: ALTER TABLE on non-existent table (${message})`);
281
+ this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
282
+ continue;
283
+ }
284
+ (0, debug_logger_js_1.error)(`[node-sqlite-adapter] Migration ${file} failed:`, err);
285
+ throw new Error(`Migration ${file} failed: ${message}`);
286
+ }
287
+ }
288
+ const embeddingsTables = this.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='embeddings'`).all();
289
+ if (embeddingsTables.length === 0) {
290
+ throw new Error('Embeddings table is missing after migrations. Add the required embeddings migration.');
291
+ }
292
+ this.migrateFromVssMemories();
293
+ }
294
+ migrateFromVssMemories() {
295
+ try {
296
+ const vssTables = this.prepare(`SELECT name FROM sqlite_master WHERE name='vss_memories'`).all();
297
+ if (vssTables.length === 0) {
298
+ return;
299
+ }
300
+ const rows = this.prepare('SELECT rowid, embedding FROM vss_memories').all();
301
+ if (rows.length > 0) {
302
+ const insertStmt = this.prepare('INSERT OR IGNORE INTO embeddings (rowid, embedding) VALUES (?, ?)');
303
+ let migrated = 0;
304
+ for (const row of rows) {
305
+ const res = insertStmt.run(row.rowid, Buffer.from(row.embedding));
306
+ if (res.changes > 0) {
307
+ migrated++;
308
+ }
309
+ }
310
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Migrated ${migrated} embeddings from vss_memories to embeddings`);
311
+ }
312
+ }
313
+ catch (err) {
314
+ const message = err instanceof Error ? err.message : String(err);
315
+ (0, debug_logger_js_1.warn)(`[node-sqlite-adapter] Could not migrate from vss_memories (expected if sqlite-vec not installed): ${message}`);
316
+ }
317
+ }
318
+ }
319
+ exports.NodeSQLiteAdapter = NodeSQLiteAdapter;
320
+ exports.default = NodeSQLiteAdapter;
321
+ function bytesToVector(buffer) {
322
+ if (!buffer || buffer.byteLength % 4 !== 0) {
323
+ return null;
324
+ }
325
+ try {
326
+ const typedBuffer = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
327
+ const arrayBuffer = typedBuffer.buffer.slice(typedBuffer.byteOffset, typedBuffer.byteOffset + typedBuffer.byteLength);
328
+ return new Float32Array(arrayBuffer);
329
+ }
330
+ catch {
331
+ return null;
332
+ }
333
+ }
334
+ //# sourceMappingURL=node-sqlite-adapter.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Node SQLite statement wrapper
3
+ *
4
+ * Wraps node:sqlite StatementSync to provide the existing Statement API.
5
+ */
6
+ import { Statement, type RunResult } from './statement.js';
7
+ interface NodeSQLiteStatementLike {
8
+ all: (...params: unknown[]) => unknown[];
9
+ get: (...params: unknown[]) => unknown;
10
+ run: (...params: unknown[]) => {
11
+ changes: number;
12
+ lastInsertRowid: number | bigint;
13
+ };
14
+ }
15
+ export declare class NodeSQLiteStatement extends Statement {
16
+ private stmt;
17
+ constructor(stmt: NodeSQLiteStatementLike);
18
+ all(...params: unknown[]): object[];
19
+ get(...params: unknown[]): object | undefined;
20
+ run(...params: unknown[]): RunResult;
21
+ }
22
+ export {};
23
+ //# sourceMappingURL=node-sqlite-statement.d.ts.map
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * Node SQLite statement wrapper
4
+ *
5
+ * Wraps node:sqlite StatementSync to provide the existing Statement API.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.NodeSQLiteStatement = void 0;
9
+ const statement_js_1 = require("./statement.js");
10
+ class NodeSQLiteStatement extends statement_js_1.Statement {
11
+ stmt;
12
+ constructor(stmt) {
13
+ super();
14
+ this.stmt = stmt;
15
+ }
16
+ all(...params) {
17
+ return this.stmt.all(...params);
18
+ }
19
+ get(...params) {
20
+ return this.stmt.get(...params);
21
+ }
22
+ run(...params) {
23
+ return this.stmt.run(...params);
24
+ }
25
+ }
26
+ exports.NodeSQLiteStatement = NodeSQLiteStatement;
27
+ //# sourceMappingURL=node-sqlite-statement.js.map
@@ -1,80 +1,15 @@
1
1
  /**
2
- * SQLite Database Adapter
2
+ * SQLite database adapter compatibility export.
3
3
  *
4
- * Implements DatabaseAdapter interface using better-sqlite3
5
- * Vector search uses pure TypeScript brute-force cosine similarity (no native extensions)
6
- *
7
- * @module sqlite-adapter
8
- */
9
- import Database from 'better-sqlite3';
10
- import { DatabaseAdapter, type VectorSearchResult, type RunResult } from './base-adapter.js';
11
- import { type Statement } from './statement.js';
12
- /**
13
- * Adapter configuration
4
+ * MAMA now standardizes on Node's built-in node:sqlite runtime. Keep the
5
+ * legacy SQLiteAdapter symbol so existing imports continue to work.
14
6
  */
7
+ import { NodeSQLiteAdapter } from './node-sqlite-adapter.js';
15
8
  interface SQLiteAdapterConfig {
16
9
  dbPath?: string;
17
10
  }
18
- export declare class SQLiteAdapter extends DatabaseAdapter {
19
- private config;
20
- private db;
21
- private _vectorSearchEnabled;
11
+ export declare class SQLiteAdapter extends NodeSQLiteAdapter {
22
12
  constructor(config?: SQLiteAdapterConfig);
23
- /**
24
- * Check if vector search is enabled
25
- */
26
- get vectorSearchEnabled(): boolean;
27
- /**
28
- * Get database path with backward compatibility
29
- */
30
- getDbPath(): string;
31
- /**
32
- * Connect to SQLite database
33
- */
34
- connect(): Database.Database;
35
- /**
36
- * Disconnect from database
37
- */
38
- disconnect(): void;
39
- /**
40
- * Check if connected
41
- */
42
- isConnected(): boolean;
43
- /**
44
- * Prepare a SQL statement
45
- */
46
- prepare(sql: string): Statement;
47
- /**
48
- * Execute raw SQL
49
- */
50
- exec(sql: string): void;
51
- /**
52
- * Execute function in transaction
53
- */
54
- transaction<T>(fn: () => T): T;
55
- /**
56
- * Vector similarity search using brute-force cosine similarity
57
- * Loads all embeddings from the embeddings table, computes similarity in JS,
58
- * and returns top-N results sorted by similarity descending.
59
- */
60
- vectorSearch(embedding: Float32Array | number[], limit?: number): VectorSearchResult[] | null;
61
- /**
62
- * Insert vector embedding into the embeddings table (plain BLOB storage)
63
- */
64
- insertEmbedding(rowid: number, embedding: Float32Array | number[]): RunResult | null;
65
- /**
66
- * Get last inserted row ID
67
- */
68
- getLastInsertRowid(): number;
69
- /**
70
- * Run migrations
71
- */
72
- runMigrations(migrationsDir: string): void;
73
- /**
74
- * Attempt to migrate data from legacy vss_memories (sqlite-vec) to embeddings table.
75
- * This is best-effort: if sqlite-vec is not loaded, the vss_memories table won't be readable.
76
- */
77
- private _migrateFromVssMemories;
78
13
  }
79
14
  export default SQLiteAdapter;
80
15
  //# sourceMappingURL=sqlite-adapter.d.ts.map
@@ -1,323 +1,18 @@
1
1
  "use strict";
2
2
  /**
3
- * SQLite Database Adapter
3
+ * SQLite database adapter compatibility export.
4
4
  *
5
- * Implements DatabaseAdapter interface using better-sqlite3
6
- * Vector search uses pure TypeScript brute-force cosine similarity (no native extensions)
7
- *
8
- * @module sqlite-adapter
5
+ * MAMA now standardizes on Node's built-in node:sqlite runtime. Keep the
6
+ * legacy SQLiteAdapter symbol so existing imports continue to work.
9
7
  */
10
- var __importDefault = (this && this.__importDefault) || function (mod) {
11
- return (mod && mod.__esModule) ? mod : { "default": mod };
12
- };
13
8
  Object.defineProperty(exports, "__esModule", { value: true });
14
9
  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
- // Database paths
24
- const LEGACY_DB_PATH = path_1.default.join(os_1.default.homedir(), '.spinelift', 'memories.db');
25
- // Default to ~/.claude/mama-memory.db for Claude Code/Desktop compatibility
26
- const DEFAULT_DB_PATH = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-memory.db');
27
- class SQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
28
- config;
29
- db = null;
30
- _vectorSearchEnabled = true; // Always true: pure TS cosine similarity
10
+ const node_sqlite_adapter_js_1 = require("./node-sqlite-adapter.js");
11
+ class SQLiteAdapter extends node_sqlite_adapter_js_1.NodeSQLiteAdapter {
31
12
  constructor(config = {}) {
32
- super();
33
- this.config = config;
34
- }
35
- /**
36
- * Check if vector search is enabled
37
- */
38
- get vectorSearchEnabled() {
39
- return this._vectorSearchEnabled;
40
- }
41
- /**
42
- * Get database path with backward compatibility
43
- */
44
- getDbPath() {
45
- // Support both MAMA_DB_PATH and MAMA_DATABASE_PATH for backward compatibility
46
- const envPath = process.env.MAMA_DB_PATH || process.env.MAMA_DATABASE_PATH;
47
- const configPath = this.config.dbPath;
48
- // Expand ${HOME} or ~ in environment variable
49
- let expandedEnvPath = envPath;
50
- if (envPath) {
51
- expandedEnvPath = envPath.replace(/\$\{HOME\}/g, os_1.default.homedir()).replace(/^~/, os_1.default.homedir());
52
- }
53
- // Priority: config > env > default
54
- const targetPath = configPath || expandedEnvPath || DEFAULT_DB_PATH;
55
- // Backward compatibility: Check legacy path if not explicitly set
56
- if (!configPath && !envPath && fs_1.default.existsSync(LEGACY_DB_PATH)) {
57
- (0, debug_logger_js_1.info)('[sqlite-adapter] Found legacy database at ~/.spinelift/memories.db, using it for backward compatibility');
58
- return LEGACY_DB_PATH;
59
- }
60
- return targetPath;
61
- }
62
- /**
63
- * Connect to SQLite database
64
- */
65
- connect() {
66
- if (this.db) {
67
- return this.db;
68
- }
69
- const dbPath = this.getDbPath();
70
- const dbDir = path_1.default.dirname(dbPath);
71
- // Ensure directory exists
72
- if (!fs_1.default.existsSync(dbDir)) {
73
- fs_1.default.mkdirSync(dbDir, { recursive: true });
74
- (0, debug_logger_js_1.info)(`[sqlite-adapter] Created database directory: ${dbDir}`);
75
- }
76
- // Open database
77
- this.db = new better_sqlite3_1.default(dbPath, { verbose: undefined });
78
- (0, debug_logger_js_1.info)(`[sqlite-adapter] Opened database at: ${dbPath}`);
79
- // Production configuration
80
- this.db.pragma('journal_mode = WAL');
81
- this.db.pragma('busy_timeout = 5000');
82
- this.db.pragma('synchronous = NORMAL');
83
- this.db.pragma('cache_size = -64000'); // 64MB cache
84
- this.db.pragma('temp_store = MEMORY');
85
- this.db.pragma('foreign_keys = ON');
86
- (0, debug_logger_js_1.info)('[sqlite-adapter] Vector search: pure TS cosine similarity (no native extensions)');
87
- return this.db;
88
- }
89
- /**
90
- * Disconnect from database
91
- */
92
- disconnect() {
93
- if (this.db) {
94
- this.db.close();
95
- this.db = null;
96
- (0, debug_logger_js_1.info)('[sqlite-adapter] Disconnected from database');
97
- }
98
- }
99
- /**
100
- * Check if connected
101
- */
102
- isConnected() {
103
- return this.db !== null && this.db.open;
104
- }
105
- /**
106
- * Prepare a SQL statement
107
- */
108
- prepare(sql) {
109
- if (!this.isConnected() || !this.db) {
110
- throw new Error('Database not connected');
111
- }
112
- const stmt = this.db.prepare(sql);
113
- return new statement_js_1.SQLiteStatement(stmt);
114
- }
115
- /**
116
- * Execute raw SQL
117
- */
118
- exec(sql) {
119
- if (!this.isConnected() || !this.db) {
120
- throw new Error('Database not connected');
121
- }
122
- this.db.exec(sql);
123
- }
124
- /**
125
- * Execute function in transaction
126
- */
127
- transaction(fn) {
128
- if (!this.isConnected() || !this.db) {
129
- throw new Error('Database not connected');
130
- }
131
- const txn = this.db.transaction(fn);
132
- return txn();
133
- }
134
- /**
135
- * Vector similarity search using brute-force cosine similarity
136
- * Loads all embeddings from the embeddings table, computes similarity in JS,
137
- * and returns top-N results sorted by similarity descending.
138
- */
139
- vectorSearch(embedding, limit = 5) {
140
- if (!this.isConnected()) {
141
- throw new Error('Database not connected');
142
- }
143
- const queryVector = embedding instanceof Float32Array ? embedding : Float32Array.from(embedding);
144
- // Check if embeddings table exists
145
- const tableCheck = this.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='embeddings'`).all();
146
- if (tableCheck.length === 0) {
147
- throw new Error('Embeddings table missing');
148
- }
149
- const rows = this.prepare('SELECT rowid, embedding FROM embeddings').all();
150
- const scored = [];
151
- for (const row of rows) {
152
- const candidate = bufferToVector(row.embedding);
153
- if (!candidate)
154
- continue;
155
- if (candidate.length !== queryVector.length) {
156
- (0, debug_logger_js_1.warn)(`Skipping rowid ${row.rowid}: dimension mismatch (${candidate.length} vs ${queryVector.length})`);
157
- continue;
158
- }
159
- const similarity = (0, embeddings_js_1.cosineSimilarity)(candidate, queryVector);
160
- scored.push({
161
- rowid: row.rowid,
162
- similarity,
163
- distance: 1 - similarity,
164
- });
165
- }
166
- // Sort by similarity descending, return top-N
167
- scored.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
168
- return scored.slice(0, Math.max(limit, 1));
169
- }
170
- /**
171
- * Insert vector embedding into the embeddings table (plain BLOB storage)
172
- */
173
- insertEmbedding(rowid, embedding) {
174
- if (!this.isConnected()) {
175
- throw new Error('Database not connected');
176
- }
177
- const vec = embedding instanceof Float32Array ? embedding : Float32Array.from(embedding);
178
- const buffer = Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
179
- const stmt = this.prepare(`
180
- INSERT OR REPLACE INTO embeddings(rowid, embedding)
181
- VALUES (?, ?)
182
- `);
183
- return stmt.run(rowid, buffer);
184
- }
185
- /**
186
- * Get last inserted row ID
187
- */
188
- getLastInsertRowid() {
189
- if (!this.isConnected() || !this.db) {
190
- throw new Error('Database not connected');
191
- }
192
- // better-sqlite3 provides this via Database instance
193
- const result = this.db.prepare('SELECT last_insert_rowid() as rowid').get();
194
- return result.rowid;
195
- }
196
- /**
197
- * Run migrations
198
- */
199
- runMigrations(migrationsDir) {
200
- if (!this.isConnected()) {
201
- throw new Error('Database not connected');
202
- }
203
- // Check if schema_version table exists
204
- const tables = this.prepare(`
205
- SELECT name FROM sqlite_master
206
- WHERE type='table' AND name='schema_version'
207
- `).all();
208
- let currentVersion = 0;
209
- if (tables.length > 0) {
210
- const version = this.prepare('SELECT MAX(version) as version FROM schema_version').get();
211
- currentVersion = version?.version || 0;
212
- }
213
- (0, debug_logger_js_1.info)(`[sqlite-adapter] Current schema version: ${currentVersion}`);
214
- // Get all migration files
215
- const migrationFiles = fs_1.default
216
- .readdirSync(migrationsDir)
217
- .filter((file) => file.endsWith('.sql'))
218
- .sort();
219
- // Apply migrations
220
- for (const file of migrationFiles) {
221
- const versionMatch = file.match(/^(\d+)-/);
222
- if (!versionMatch) {
223
- continue;
224
- }
225
- const version = parseInt(versionMatch[1], 10);
226
- if (version <= currentVersion) {
227
- continue;
228
- }
229
- const migrationPath = path_1.default.join(migrationsDir, file);
230
- const migrationSQL = fs_1.default.readFileSync(migrationPath, 'utf8');
231
- (0, debug_logger_js_1.info)(`[sqlite-adapter] Applying migration: ${file}`);
232
- try {
233
- this.exec('BEGIN TRANSACTION');
234
- this.exec(migrationSQL);
235
- this.exec('COMMIT');
236
- // Record migration in schema_version table (outside transaction for idempotency)
237
- this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
238
- (0, debug_logger_js_1.info)(`[sqlite-adapter] Migration ${file} applied successfully`);
239
- }
240
- catch (err) {
241
- this.exec('ROLLBACK');
242
- const message = err instanceof Error ? err.message : String(err);
243
- // Handle duplicate column errors as idempotent (migration 003)
244
- if (message.includes('duplicate column')) {
245
- (0, debug_logger_js_1.warn)(`[sqlite-adapter] Migration ${file} skipped (duplicate column - already applied)`);
246
- // Record migration as applied
247
- this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
248
- continue;
249
- }
250
- if (message.includes('no such table')) {
251
- // Only skip if this migration contains an ALTER TABLE (adding column to optional table)
252
- // Note: migrationSQL may contain comments, so check if ALTER TABLE exists anywhere
253
- const hasAlterTable = migrationSQL.toUpperCase().includes('ALTER TABLE');
254
- if (!hasAlterTable) {
255
- (0, debug_logger_js_1.error)(`[sqlite-adapter] Migration ${file} failed (missing required table):`, err);
256
- throw new Error(`Migration ${file} failed: ${message}`);
257
- }
258
- (0, debug_logger_js_1.warn)(`[sqlite-adapter] Migration ${file} skipped: ALTER TABLE on non-existent table (${message})`);
259
- this.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(version);
260
- continue;
261
- }
262
- (0, debug_logger_js_1.error)(`[sqlite-adapter] Migration ${file} failed:`, err);
263
- throw new Error(`Migration ${file} failed: ${message}`);
264
- }
265
- }
266
- // Ensure embeddings table exists (plain table, no native extensions)
267
- const embeddingsTables = this.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='embeddings'`).all();
268
- if (embeddingsTables.length === 0) {
269
- (0, debug_logger_js_1.info)('[sqlite-adapter] Creating embeddings table');
270
- this.exec(`
271
- CREATE TABLE embeddings (
272
- rowid INTEGER PRIMARY KEY,
273
- embedding BLOB NOT NULL
274
- )
275
- `);
276
- }
277
- // Always attempt migration — uses INSERT OR IGNORE so safe to run multiple times
278
- this._migrateFromVssMemories();
279
- }
280
- /**
281
- * Attempt to migrate data from legacy vss_memories (sqlite-vec) to embeddings table.
282
- * This is best-effort: if sqlite-vec is not loaded, the vss_memories table won't be readable.
283
- */
284
- _migrateFromVssMemories() {
285
- try {
286
- const vssTables = this.prepare(`SELECT name FROM sqlite_master WHERE name='vss_memories'`).all();
287
- if (vssTables.length === 0) {
288
- return; // No legacy table
289
- }
290
- // Try to read from vss_memories — will fail if sqlite-vec extension isn't loaded
291
- const rows = this.prepare('SELECT rowid, embedding FROM vss_memories').all();
292
- if (rows.length > 0) {
293
- const insertStmt = this.prepare('INSERT OR IGNORE INTO embeddings (rowid, embedding) VALUES (?, ?)');
294
- let migrated = 0;
295
- for (const row of rows) {
296
- const res = insertStmt.run(row.rowid, row.embedding);
297
- if (res.changes > 0)
298
- migrated++;
299
- }
300
- (0, debug_logger_js_1.info)(`[sqlite-adapter] Migrated ${migrated} embeddings from vss_memories to embeddings`);
301
- }
302
- }
303
- catch (err) {
304
- const message = err instanceof Error ? err.message : String(err);
305
- (0, debug_logger_js_1.warn)(`[sqlite-adapter] Could not migrate from vss_memories (expected if sqlite-vec not installed): ${message}`);
306
- }
13
+ super(config);
307
14
  }
308
15
  }
309
16
  exports.SQLiteAdapter = SQLiteAdapter;
310
17
  exports.default = SQLiteAdapter;
311
- function bufferToVector(buffer) {
312
- if (!buffer || buffer.byteLength % 4 !== 0) {
313
- return null;
314
- }
315
- try {
316
- const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
317
- return new Float32Array(arrayBuffer);
318
- }
319
- catch {
320
- return null;
321
- }
322
- }
323
18
  //# sourceMappingURL=sqlite-adapter.js.map
@@ -81,6 +81,7 @@ export declare class SQLiteStatement extends Statement {
81
81
  run(...params: unknown[]): RunResult;
82
82
  finalize(): void;
83
83
  }
84
+ export { NodeSQLiteStatement } from './node-sqlite-statement.js';
84
85
  /**
85
86
  * PostgreSQL statement wrapper (pg)
86
87
  *
@@ -115,5 +116,4 @@ export declare class PostgreSQLStatement extends Statement {
115
116
  runAsync(...params: unknown[]): Promise<RunResult>;
116
117
  finalize(): void;
117
118
  }
118
- export {};
119
119
  //# sourceMappingURL=statement.d.ts.map
@@ -8,7 +8,7 @@
8
8
  * @module statement
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.PostgreSQLStatement = exports.SQLiteStatement = exports.Statement = void 0;
11
+ exports.PostgreSQLStatement = exports.NodeSQLiteStatement = exports.SQLiteStatement = exports.Statement = void 0;
12
12
  /**
13
13
  * Base statement interface
14
14
  * All statement wrappers must implement these methods
@@ -45,6 +45,8 @@ class SQLiteStatement extends Statement {
45
45
  }
46
46
  }
47
47
  exports.SQLiteStatement = SQLiteStatement;
48
+ var node_sqlite_statement_js_1 = require("./node-sqlite-statement.js");
49
+ Object.defineProperty(exports, "NodeSQLiteStatement", { enumerable: true, get: function () { return node_sqlite_statement_js_1.NodeSQLiteStatement; } });
48
50
  /**
49
51
  * PostgreSQL statement wrapper (pg)
50
52
  *
@@ -20,6 +20,7 @@ exports.isLocalhost = isLocalhost;
20
20
  exports.authenticate = authenticate;
21
21
  exports.authenticateWebSocket = authenticateWebSocket;
22
22
  exports.createAuthMiddleware = createAuthMiddleware;
23
+ const node_crypto_1 = require("node:crypto");
23
24
  /**
24
25
  * Environment variable for auth token
25
26
  */
@@ -28,6 +29,24 @@ exports.AUTH_TOKEN = process.env.MAMA_AUTH_TOKEN;
28
29
  * Track if we've warned about missing auth token
29
30
  */
30
31
  let hasWarnedAboutToken = false;
32
+ /**
33
+ * Track if we've warned about query parameter token usage
34
+ */
35
+ let hasWarnedAboutQueryToken = false;
36
+ /**
37
+ * HMAC key for timing-safe token comparison
38
+ */
39
+ const hmacKey = (0, node_crypto_1.randomBytes)(32);
40
+ /**
41
+ * Timing-safe string comparison using HMAC to prevent timing attacks.
42
+ * Both strings are hashed with HMAC-SHA256 before comparison,
43
+ * ensuring constant-time evaluation regardless of input.
44
+ */
45
+ function safeTokenEqual(a, b) {
46
+ const hmacA = (0, node_crypto_1.createHmac)('sha256', hmacKey).update(a).digest();
47
+ const hmacB = (0, node_crypto_1.createHmac)('sha256', hmacKey).update(b).digest();
48
+ return (0, node_crypto_1.timingSafeEqual)(hmacA, hmacB);
49
+ }
31
50
  /**
32
51
  * Check if request is from localhost
33
52
  * @param req - HTTP request
@@ -95,7 +114,7 @@ function authenticate(req) {
95
114
  const authHeader = req.headers['authorization'];
96
115
  if (authHeader?.startsWith('Bearer ')) {
97
116
  const token = authHeader.slice(7);
98
- if (token === exports.AUTH_TOKEN) {
117
+ if (safeTokenEqual(token, exports.AUTH_TOKEN)) {
99
118
  console.error(`[Auth] External access granted via Bearer token from ${remoteAddress}`);
100
119
  return true;
101
120
  }
@@ -103,7 +122,13 @@ function authenticate(req) {
103
122
  // Check URL query parameter
104
123
  const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
105
124
  const queryToken = url.searchParams.get('token');
106
- if (queryToken === exports.AUTH_TOKEN) {
125
+ if (queryToken && safeTokenEqual(queryToken, exports.AUTH_TOKEN)) {
126
+ if (!hasWarnedAboutQueryToken) {
127
+ console.warn('[Auth] DEPRECATION WARNING: Authentication via query parameter (?token=...) is deprecated. ' +
128
+ 'Token in URL may leak through server logs, browser history, and Referer headers. ' +
129
+ 'Please use the Authorization header instead: "Authorization: Bearer <token>"');
130
+ hasWarnedAboutQueryToken = true;
131
+ }
107
132
  console.error(`[Auth] External access granted via query token from ${remoteAddress}`);
108
133
  return true;
109
134
  }
@@ -58,6 +58,9 @@ export declare class SessionManager {
58
58
  private sessions;
59
59
  private db;
60
60
  private initialized;
61
+ private cleanupPromise;
62
+ private cleanupHandlersRegistered;
63
+ private handleProcessExit;
61
64
  /**
62
65
  * Create a new SessionManager instance
63
66
  * @param dbPath - Path to SQLite database
@@ -53,6 +53,9 @@ class SessionManager {
53
53
  sessions;
54
54
  db;
55
55
  initialized;
56
+ cleanupPromise;
57
+ cleanupHandlersRegistered;
58
+ handleProcessExit;
56
59
  /**
57
60
  * Create a new SessionManager instance
58
61
  * @param dbPath - Path to SQLite database
@@ -62,6 +65,9 @@ class SessionManager {
62
65
  this.sessions = new Map();
63
66
  this.db = null;
64
67
  this.initialized = false;
68
+ this.cleanupPromise = null;
69
+ this.cleanupHandlersRegistered = false;
70
+ this.handleProcessExit = null;
65
71
  }
66
72
  /**
67
73
  * Initialize the SQLite database and create sessions table
@@ -71,10 +77,9 @@ class SessionManager {
71
77
  return;
72
78
  }
73
79
  try {
74
- // Dynamically import better-sqlite3 to handle cases where it's not installed
75
80
  // eslint-disable-next-line @typescript-eslint/no-require-imports
76
- const Database = require('better-sqlite3');
77
- this.db = new Database(this.dbPath);
81
+ const { DatabaseSync } = require('node:sqlite');
82
+ this.db = new DatabaseSync(this.dbPath);
78
83
  // Create sessions table if not exists
79
84
  this.db.exec(exports.CREATE_SESSIONS_TABLE);
80
85
  this.db.exec(CREATE_STATUS_INDEX);
@@ -307,25 +312,50 @@ class SessionManager {
307
312
  * @private
308
313
  */
309
314
  _setupProcessCleanup() {
310
- const cleanup = async () => {
315
+ if (this.cleanupHandlersRegistered) {
316
+ return;
317
+ }
318
+ const cleanup = () => {
319
+ if (this.cleanupPromise) {
320
+ return;
321
+ }
322
+ if (this.handleProcessExit) {
323
+ process.off('exit', this.handleProcessExit);
324
+ }
325
+ this.cleanupPromise = Promise.resolve();
311
326
  console.error('[SessionManager] Process exiting, cleaning up sessions...');
312
- await this.terminateAll();
313
- if (this.db) {
314
- this.db.close();
327
+ if (this.db && this.initialized) {
328
+ for (const [sessionId, session] of this.sessions.entries()) {
329
+ try {
330
+ session.daemon.kill();
331
+ }
332
+ catch {
333
+ // Best effort - process is already exiting.
334
+ }
335
+ this.sessions.delete(sessionId);
336
+ const stmt = this.db.prepare(`
337
+ UPDATE sessions
338
+ SET status = 'terminated', last_active = datetime('now')
339
+ WHERE id = ?
340
+ `);
341
+ stmt.run(sessionId);
342
+ }
315
343
  }
344
+ this.close();
316
345
  };
317
- process.on('exit', () => void cleanup());
318
- process.on('SIGINT', () => void cleanup());
319
- process.on('SIGTERM', () => void cleanup());
346
+ this.handleProcessExit = cleanup;
347
+ process.on('exit', this.handleProcessExit);
348
+ this.cleanupHandlersRegistered = true;
320
349
  }
321
350
  /**
322
351
  * Close the database connection
323
352
  */
324
353
  close() {
325
354
  if (this.db) {
326
- this.db.close();
355
+ const db = this.db;
327
356
  this.db = null;
328
357
  this.initialized = false;
358
+ db.close();
329
359
  }
330
360
  }
331
361
  }
@@ -10,7 +10,7 @@
10
10
  * @date 2025-11-20
11
11
  */
12
12
  import { embeddingCache } from './embedding-cache.js';
13
- import { loadConfig, getModelName, getEmbeddingDim } from './config-loader.js';
13
+ import { loadConfig, getModelName, getEmbeddingDim, getQuantized } from './config-loader.js';
14
14
  /**
15
15
  * Decision object for enhanced embedding generation
16
16
  */
@@ -71,5 +71,5 @@ export declare function cosineSimilarity(embA: Float32Array, embB: Float32Array)
71
71
  export { embeddingCache };
72
72
  export declare const EMBEDDING_DIM: number;
73
73
  export declare const MODEL_NAME: string;
74
- export { loadConfig, getModelName, getEmbeddingDim };
74
+ export { loadConfig, getModelName, getEmbeddingDim, getQuantized };
75
75
  //# sourceMappingURL=embeddings.d.ts.map
@@ -14,7 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.getEmbeddingDim = exports.getModelName = exports.loadConfig = exports.MODEL_NAME = exports.EMBEDDING_DIM = exports.embeddingCache = void 0;
17
+ exports.getQuantized = exports.getEmbeddingDim = exports.getModelName = exports.loadConfig = exports.MODEL_NAME = exports.EMBEDDING_DIM = exports.embeddingCache = void 0;
18
18
  exports.generateEmbedding = generateEmbedding;
19
19
  exports.generateEnhancedEmbedding = generateEnhancedEmbedding;
20
20
  exports.generateBatchEmbeddings = generateBatchEmbeddings;
@@ -29,6 +29,7 @@ const config_loader_js_1 = require("./config-loader.js");
29
29
  Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return config_loader_js_1.loadConfig; } });
30
30
  Object.defineProperty(exports, "getModelName", { enumerable: true, get: function () { return config_loader_js_1.getModelName; } });
31
31
  Object.defineProperty(exports, "getEmbeddingDim", { enumerable: true, get: function () { return config_loader_js_1.getEmbeddingDim; } });
32
+ Object.defineProperty(exports, "getQuantized", { enumerable: true, get: function () { return config_loader_js_1.getQuantized; } });
32
33
  // Shared cache directory (not in node_modules)
33
34
  const DEFAULT_CACHE_DIR = path_1.default.join(os_1.default.homedir(), '.cache', 'huggingface', 'transformers');
34
35
  // Singleton pattern for model loading
@@ -73,11 +74,14 @@ async function loadModel() {
73
74
  const cacheDir = process.env.HF_HOME || process.env.TRANSFORMERS_CACHE || DEFAULT_CACHE_DIR;
74
75
  env.cacheDir = cacheDir;
75
76
  (0, debug_logger_js_1.info)(`[MAMA] Model cache directory: ${cacheDir}`);
76
- embeddingPipeline = (await pipeline('feature-extraction', modelName));
77
+ const quantized = (0, config_loader_js_1.getQuantized)();
78
+ embeddingPipeline = (await pipeline('feature-extraction', modelName, {
79
+ dtype: quantized ? 'q8' : 'fp32',
80
+ }));
77
81
  currentModelName = modelName;
78
82
  const loadTime = Date.now() - startTime;
79
83
  const config = (0, config_loader_js_1.loadConfig)();
80
- (0, progress_indicator_js_1.logComplete)(`Embedding model ready (${loadTime}ms, ${config.embeddingDim}-dim)`);
84
+ (0, progress_indicator_js_1.logComplete)(`Embedding model ready (${loadTime}ms, ${config.embeddingDim}-dim, ${quantized ? 'q8' : 'fp32'})`);
81
85
  }
82
86
  catch (loadErr) {
83
87
  modelLoadFailed = true;
@@ -4,7 +4,7 @@
4
4
  * Centralized tier validation module for MAMA.
5
5
  * Validates system requirements and determines tier status (1 or 2).
6
6
  *
7
- * Tier 1: Full features (Node.js 18+, SQLite, Embeddings, Database)
7
+ * Tier 1: Full features (Node.js 22.13+, SQLite, Embeddings, Database)
8
8
  * Tier 2: Degraded mode (missing one or more requirements)
9
9
  *
10
10
  * @module tier-validator
@@ -25,7 +25,7 @@ export interface TierValidation {
25
25
  */
26
26
  export declare function checkNodeVersion(): CheckResult;
27
27
  /**
28
- * Validates SQLite (better-sqlite3) availability
28
+ * Validates SQLite availability (node:sqlite)
29
29
  */
30
30
  export declare function checkSQLite(): CheckResult;
31
31
  /**
@@ -5,7 +5,7 @@
5
5
  * Centralized tier validation module for MAMA.
6
6
  * Validates system requirements and determines tier status (1 or 2).
7
7
  *
8
- * Tier 1: Full features (Node.js 18+, SQLite, Embeddings, Database)
8
+ * Tier 1: Full features (Node.js 22.13+, SQLite, Embeddings, Database)
9
9
  * Tier 2: Degraded mode (missing one or more requirements)
10
10
  *
11
11
  * @module tier-validator
@@ -30,8 +30,8 @@ const os_1 = __importDefault(require("os"));
30
30
  function checkNodeVersion() {
31
31
  try {
32
32
  const nodeVersion = process.versions.node;
33
- const majorVersion = parseInt(nodeVersion.split('.')[0], 10);
34
- if (majorVersion >= 18) {
33
+ const [majorVersion, minorVersion] = nodeVersion.split('.').map((part) => parseInt(part, 10));
34
+ if (majorVersion > 22 || (majorVersion === 22 && minorVersion >= 13)) {
35
35
  return {
36
36
  status: 'pass',
37
37
  details: `v${nodeVersion}`,
@@ -39,7 +39,7 @@ function checkNodeVersion() {
39
39
  }
40
40
  return {
41
41
  status: 'fail',
42
- details: `v${nodeVersion} (requires 18+)`,
42
+ details: `v${nodeVersion} (requires 22.13+)`,
43
43
  };
44
44
  }
45
45
  catch (error) {
@@ -51,25 +51,24 @@ function checkNodeVersion() {
51
51
  }
52
52
  }
53
53
  /**
54
- * Validates SQLite (better-sqlite3) availability
54
+ * Validates SQLite availability (node:sqlite)
55
55
  */
56
56
  function checkSQLite() {
57
57
  try {
58
58
  // eslint-disable-next-line @typescript-eslint/no-require-imports
59
- const Database = require('better-sqlite3');
60
- // Test instantiation with in-memory database
61
- const testDb = new Database(':memory:');
59
+ const { DatabaseSync } = require('node:sqlite');
60
+ const testDb = new DatabaseSync(':memory:');
62
61
  testDb.close();
63
62
  return {
64
63
  status: 'pass',
65
- details: 'better-sqlite3 native module ready',
64
+ details: 'node:sqlite built-in driver ready',
66
65
  };
67
66
  }
68
67
  catch (error) {
69
68
  const message = error instanceof Error ? error.message : String(error);
70
69
  return {
71
70
  status: 'fail',
72
- details: `better-sqlite3 not available: ${message}`,
71
+ details: `node:sqlite not available: ${message}`,
73
72
  };
74
73
  }
75
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jungjaehoon/mama-core",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "MAMA Core - Shared modules for Memory-Augmented MCP Assistant",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -59,15 +59,13 @@
59
59
  "access": "public"
60
60
  },
61
61
  "engines": {
62
- "node": ">=18.0.0"
62
+ "node": ">=22.13.0"
63
63
  },
64
64
  "dependencies": {
65
65
  "@huggingface/transformers": "^3.8.1",
66
- "better-sqlite3": "^11.0.0",
67
66
  "ws": "^8.19.0"
68
67
  },
69
68
  "devDependencies": {
70
- "@types/better-sqlite3": "^7.6.0",
71
69
  "@types/node": "^22.0.0",
72
70
  "@types/ws": "^8.18.1",
73
71
  "typescript": "^5.4.0",