@cccarv82/freya 3.8.0 → 3.8.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": "3.8.0",
3
+ "version": "3.8.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",
@@ -100,15 +100,37 @@ class SqlJsDatabase {
100
100
 
101
101
  /**
102
102
  * Persist the in-memory database to disk.
103
+ * Retries with backoff on EBUSY (OneDrive/cloud sync locking).
103
104
  */
104
105
  _save() {
105
106
  if (!this._filePath) return;
106
- try {
107
- const data = this._db.export();
108
- const buffer = Buffer.from(data);
109
- fs.writeFileSync(this._filePath, buffer);
110
- } catch (err) {
111
- console.error('[DataLayer] Failed to save database:', err.message);
107
+ const data = this._db.export();
108
+ const buffer = Buffer.from(data);
109
+ const maxRetries = 5;
110
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
111
+ try {
112
+ // Write to temp file + rename for atomic operation
113
+ const tmpPath = this._filePath + '.tmp';
114
+ fs.writeFileSync(tmpPath, buffer);
115
+ try {
116
+ fs.renameSync(tmpPath, this._filePath);
117
+ } catch (renameErr) {
118
+ // On Windows, rename can fail if target is locked; fall back to direct write
119
+ try { fs.unlinkSync(tmpPath); } catch { }
120
+ fs.writeFileSync(this._filePath, buffer);
121
+ }
122
+ return; // success
123
+ } catch (err) {
124
+ if ((err.code === 'EBUSY' || err.code === 'EPERM') && attempt < maxRetries) {
125
+ // Wait with exponential backoff: 100ms, 200ms, 400ms, 800ms, 1600ms
126
+ const delay = 100 * Math.pow(2, attempt);
127
+ const start = Date.now();
128
+ while (Date.now() - start < delay) { /* busy wait — sync context */ }
129
+ continue;
130
+ }
131
+ console.error('[DataLayer] Failed to save database:', err.message);
132
+ return;
133
+ }
112
134
  }
113
135
  }
114
136
 
@@ -100,15 +100,37 @@ class SqlJsDatabase {
100
100
 
101
101
  /**
102
102
  * Persist the in-memory database to disk.
103
+ * Retries with backoff on EBUSY (OneDrive/cloud sync locking).
103
104
  */
104
105
  _save() {
105
106
  if (!this._filePath) return;
106
- try {
107
- const data = this._db.export();
108
- const buffer = Buffer.from(data);
109
- fs.writeFileSync(this._filePath, buffer);
110
- } catch (err) {
111
- console.error('[DataLayer] Failed to save database:', err.message);
107
+ const data = this._db.export();
108
+ const buffer = Buffer.from(data);
109
+ const maxRetries = 5;
110
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
111
+ try {
112
+ // Write to temp file + rename for atomic operation
113
+ const tmpPath = this._filePath + '.tmp';
114
+ fs.writeFileSync(tmpPath, buffer);
115
+ try {
116
+ fs.renameSync(tmpPath, this._filePath);
117
+ } catch (renameErr) {
118
+ // On Windows, rename can fail if target is locked; fall back to direct write
119
+ try { fs.unlinkSync(tmpPath); } catch { }
120
+ fs.writeFileSync(this._filePath, buffer);
121
+ }
122
+ return; // success
123
+ } catch (err) {
124
+ if ((err.code === 'EBUSY' || err.code === 'EPERM') && attempt < maxRetries) {
125
+ // Wait with exponential backoff: 100ms, 200ms, 400ms, 800ms, 1600ms
126
+ const delay = 100 * Math.pow(2, attempt);
127
+ const start = Date.now();
128
+ while (Date.now() - start < delay) { /* busy wait — sync context */ }
129
+ continue;
130
+ }
131
+ console.error('[DataLayer] Failed to save database:', err.message);
132
+ return;
133
+ }
112
134
  }
113
135
  }
114
136