@cccarv82/freya 2.7.0 → 2.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js && node scripts/validate-structure.js",
@@ -72,7 +72,10 @@ class PreparedStatement {
72
72
  const db = this._wrapper._db;
73
73
  db.run(this._sql, this._flattenParams(params));
74
74
  const changes = db.getRowsModified();
75
- this._wrapper._save();
75
+ // Only save to disk if NOT inside a transaction (save happens at COMMIT)
76
+ if (!this._wrapper._inTransaction) {
77
+ this._wrapper._save();
78
+ }
76
79
  return { changes };
77
80
  }
78
81
 
@@ -92,7 +95,7 @@ class SqlJsDatabase {
92
95
  constructor(sqlJsDb, filePath) {
93
96
  this._db = sqlJsDb;
94
97
  this._filePath = filePath;
95
- this._saveScheduled = false;
98
+ this._inTransaction = false;
96
99
  }
97
100
 
98
101
  /**
@@ -126,7 +129,9 @@ class SqlJsDatabase {
126
129
  */
127
130
  exec(sql) {
128
131
  this._db.run(sql);
129
- this._save();
132
+ if (!this._inTransaction) {
133
+ this._save();
134
+ }
130
135
  }
131
136
 
132
137
  /**
@@ -144,13 +149,16 @@ class SqlJsDatabase {
144
149
  transaction(fn) {
145
150
  const self = this;
146
151
  return function (...args) {
152
+ self._inTransaction = true;
147
153
  self._db.run('BEGIN TRANSACTION');
148
154
  try {
149
155
  const result = fn(...args);
150
156
  self._db.run('COMMIT');
157
+ self._inTransaction = false;
151
158
  self._save();
152
159
  return result;
153
160
  } catch (e) {
161
+ self._inTransaction = false;
154
162
  try { self._db.run('ROLLBACK'); } catch { /* already rolled back */ }
155
163
  throw e;
156
164
  }
@@ -192,6 +200,40 @@ class DataLayer {
192
200
  const initSqlJs = require('sql.js');
193
201
  const SQL = await initSqlJs();
194
202
 
203
+ // --- WAL consolidation (migration from better-sqlite3) ---
204
+ // better-sqlite3 used WAL mode, which may leave a -wal file with uncommitted data.
205
+ // sql.js cannot read WAL files directly, so we need to checkpoint first.
206
+ const walPath = this._dbPath + '-wal';
207
+ const shmPath = this._dbPath + '-shm';
208
+ if (fs.existsSync(this._dbPath) && fs.existsSync(walPath)) {
209
+ try {
210
+ console.log('[DataLayer] Consolidating WAL file from previous better-sqlite3 usage...');
211
+ // Open the database including the WAL data
212
+ const fileBuffer = fs.readFileSync(this._dbPath);
213
+ const tempDb = new SQL.Database(fileBuffer);
214
+
215
+ // Try to checkpoint the WAL (merge pending writes into main file)
216
+ try { tempDb.run('PRAGMA wal_checkpoint(TRUNCATE)'); } catch { /* may not work in WASM */ }
217
+
218
+ // Switch to DELETE journal mode (fully compatible with sql.js)
219
+ try { tempDb.run('PRAGMA journal_mode = DELETE'); } catch { /* ignore */ }
220
+
221
+ // Export the consolidated database and save it
222
+ const consolidated = tempDb.export();
223
+ fs.writeFileSync(this._dbPath, Buffer.from(consolidated));
224
+ tempDb.close();
225
+
226
+ // Clean up WAL/SHM files
227
+ try { fs.unlinkSync(walPath); } catch { /* ignore */ }
228
+ try { fs.unlinkSync(shmPath); } catch { /* ignore */ }
229
+
230
+ console.log('[DataLayer] WAL consolidation complete.');
231
+ } catch (err) {
232
+ console.error('[DataLayer] Warning: WAL consolidation failed:', err.message);
233
+ // Continue anyway — the main .sqlite file may still be usable
234
+ }
235
+ }
236
+
195
237
  // Load existing database from disk, or create new one
196
238
  let sqlJsDb;
197
239
  if (fs.existsSync(this._dbPath)) {
@@ -206,8 +248,8 @@ class DataLayer {
206
248
  }
207
249
 
208
250
  _initSchema() {
209
- // WAL mode may not work in WASM — we try, but it's silently ignored if unsupported
210
- this.db.pragma('journal_mode = WAL');
251
+ // Use DELETE journal mode (compatible with sql.js WASM)
252
+ this.db.pragma('journal_mode = DELETE');
211
253
 
212
254
  this.db.exec(`
213
255
  CREATE TABLE IF NOT EXISTS projects (
@@ -72,7 +72,10 @@ class PreparedStatement {
72
72
  const db = this._wrapper._db;
73
73
  db.run(this._sql, this._flattenParams(params));
74
74
  const changes = db.getRowsModified();
75
- this._wrapper._save();
75
+ // Only save to disk if NOT inside a transaction (save happens at COMMIT)
76
+ if (!this._wrapper._inTransaction) {
77
+ this._wrapper._save();
78
+ }
76
79
  return { changes };
77
80
  }
78
81
 
@@ -92,7 +95,7 @@ class SqlJsDatabase {
92
95
  constructor(sqlJsDb, filePath) {
93
96
  this._db = sqlJsDb;
94
97
  this._filePath = filePath;
95
- this._saveScheduled = false;
98
+ this._inTransaction = false;
96
99
  }
97
100
 
98
101
  /**
@@ -126,7 +129,9 @@ class SqlJsDatabase {
126
129
  */
127
130
  exec(sql) {
128
131
  this._db.run(sql);
129
- this._save();
132
+ if (!this._inTransaction) {
133
+ this._save();
134
+ }
130
135
  }
131
136
 
132
137
  /**
@@ -144,13 +149,16 @@ class SqlJsDatabase {
144
149
  transaction(fn) {
145
150
  const self = this;
146
151
  return function (...args) {
152
+ self._inTransaction = true;
147
153
  self._db.run('BEGIN TRANSACTION');
148
154
  try {
149
155
  const result = fn(...args);
150
156
  self._db.run('COMMIT');
157
+ self._inTransaction = false;
151
158
  self._save();
152
159
  return result;
153
160
  } catch (e) {
161
+ self._inTransaction = false;
154
162
  try { self._db.run('ROLLBACK'); } catch { /* already rolled back */ }
155
163
  throw e;
156
164
  }
@@ -192,6 +200,40 @@ class DataLayer {
192
200
  const initSqlJs = require('sql.js');
193
201
  const SQL = await initSqlJs();
194
202
 
203
+ // --- WAL consolidation (migration from better-sqlite3) ---
204
+ // better-sqlite3 used WAL mode, which may leave a -wal file with uncommitted data.
205
+ // sql.js cannot read WAL files directly, so we need to checkpoint first.
206
+ const walPath = this._dbPath + '-wal';
207
+ const shmPath = this._dbPath + '-shm';
208
+ if (fs.existsSync(this._dbPath) && fs.existsSync(walPath)) {
209
+ try {
210
+ console.log('[DataLayer] Consolidating WAL file from previous better-sqlite3 usage...');
211
+ // Open the database including the WAL data
212
+ const fileBuffer = fs.readFileSync(this._dbPath);
213
+ const tempDb = new SQL.Database(fileBuffer);
214
+
215
+ // Try to checkpoint the WAL (merge pending writes into main file)
216
+ try { tempDb.run('PRAGMA wal_checkpoint(TRUNCATE)'); } catch { /* may not work in WASM */ }
217
+
218
+ // Switch to DELETE journal mode (fully compatible with sql.js)
219
+ try { tempDb.run('PRAGMA journal_mode = DELETE'); } catch { /* ignore */ }
220
+
221
+ // Export the consolidated database and save it
222
+ const consolidated = tempDb.export();
223
+ fs.writeFileSync(this._dbPath, Buffer.from(consolidated));
224
+ tempDb.close();
225
+
226
+ // Clean up WAL/SHM files
227
+ try { fs.unlinkSync(walPath); } catch { /* ignore */ }
228
+ try { fs.unlinkSync(shmPath); } catch { /* ignore */ }
229
+
230
+ console.log('[DataLayer] WAL consolidation complete.');
231
+ } catch (err) {
232
+ console.error('[DataLayer] Warning: WAL consolidation failed:', err.message);
233
+ // Continue anyway — the main .sqlite file may still be usable
234
+ }
235
+ }
236
+
195
237
  // Load existing database from disk, or create new one
196
238
  let sqlJsDb;
197
239
  if (fs.existsSync(this._dbPath)) {
@@ -206,8 +248,8 @@ class DataLayer {
206
248
  }
207
249
 
208
250
  _initSchema() {
209
- // WAL mode may not work in WASM — we try, but it's silently ignored if unsupported
210
- this.db.pragma('journal_mode = WAL');
251
+ // Use DELETE journal mode (compatible with sql.js WASM)
252
+ this.db.pragma('journal_mode = DELETE');
211
253
 
212
254
  this.db.exec(`
213
255
  CREATE TABLE IF NOT EXISTS projects (