@pixagram/lacerta-db 0.0.1 → 0.0.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/index.js +113 -58
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -287,34 +287,41 @@ class IndexedDBUtility {
|
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
//
|
|
290
|
+
// Improved transaction handling with better retry logic
|
|
291
291
|
static async performTransaction(db, storeNames, mode, callback, retries = 3, operationId = null) {
|
|
292
292
|
let lastError = null;
|
|
293
|
-
|
|
293
|
+
|
|
294
294
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
295
295
|
try {
|
|
296
|
-
if (!db) {
|
|
297
|
-
throw new Error('Database connection is not available.');
|
|
296
|
+
if (!db || db.readyState !== 'done') {
|
|
297
|
+
throw new Error('Database connection is not available or ready.');
|
|
298
298
|
}
|
|
299
|
-
|
|
299
|
+
|
|
300
300
|
const tx = db.transaction(Array.isArray(storeNames) ? storeNames : [storeNames], mode);
|
|
301
|
-
const stores = Array.isArray(storeNames)
|
|
302
|
-
? storeNames.map(name => tx.objectStore(name))
|
|
301
|
+
const stores = Array.isArray(storeNames)
|
|
302
|
+
? storeNames.map(name => tx.objectStore(name))
|
|
303
303
|
: [tx.objectStore(storeNames)];
|
|
304
|
-
|
|
305
|
-
//
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
304
|
+
|
|
305
|
+
// Add transaction timeout
|
|
306
|
+
const timeoutId = setTimeout(() => {
|
|
307
|
+
tx.abort();
|
|
308
|
+
}, 30000); // 30 second timeout
|
|
309
|
+
|
|
310
|
+
const result = await callback(...stores, operationId);
|
|
309
311
|
|
|
310
312
|
return new Promise((resolve, reject) => {
|
|
311
|
-
tx.oncomplete = () =>
|
|
313
|
+
tx.oncomplete = () => {
|
|
314
|
+
clearTimeout(timeoutId);
|
|
315
|
+
resolve(result);
|
|
316
|
+
};
|
|
312
317
|
tx.onerror = () => {
|
|
318
|
+
clearTimeout(timeoutId);
|
|
313
319
|
lastError = new Error(`Transaction failed: ${tx.error ? tx.error.message : 'unknown error'}`);
|
|
314
320
|
reject(lastError);
|
|
315
321
|
};
|
|
316
322
|
tx.onabort = () => {
|
|
317
|
-
|
|
323
|
+
clearTimeout(timeoutId);
|
|
324
|
+
lastError = new Error(`Transaction aborted: ${tx.error ? tx.error.message : 'timeout or abort'}`);
|
|
318
325
|
reject(lastError);
|
|
319
326
|
};
|
|
320
327
|
});
|
|
@@ -322,12 +329,13 @@ class IndexedDBUtility {
|
|
|
322
329
|
lastError = error;
|
|
323
330
|
if (attempt < retries) {
|
|
324
331
|
console.warn(`Transaction failed, retrying... (${retries - attempt} attempts left): ${error.message}`);
|
|
325
|
-
// Exponential backoff
|
|
326
|
-
|
|
332
|
+
// Exponential backoff with jitter
|
|
333
|
+
const delay = Math.pow(2, attempt) * 100 + Math.random() * 100;
|
|
334
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
327
335
|
}
|
|
328
336
|
}
|
|
329
337
|
}
|
|
330
|
-
|
|
338
|
+
|
|
331
339
|
throw new Error(`Transaction ultimately failed after ${retries + 1} attempts: ${lastError.message}`);
|
|
332
340
|
}
|
|
333
341
|
|
|
@@ -410,6 +418,43 @@ class IndexedDBUtility {
|
|
|
410
418
|
request.onerror = event => reject(`Cursor iteration failed: ${event.target.error.message}`);
|
|
411
419
|
});
|
|
412
420
|
}
|
|
421
|
+
|
|
422
|
+
static iterateCursorSafe(store, processCallback, options = {}) {
|
|
423
|
+
return new Promise((resolve, reject) => {
|
|
424
|
+
const {
|
|
425
|
+
index = null,
|
|
426
|
+
direction = 'next',
|
|
427
|
+
range = null
|
|
428
|
+
} = options;
|
|
429
|
+
|
|
430
|
+
let source = store;
|
|
431
|
+
if (index && store.indexNames.contains(index)) {
|
|
432
|
+
source = store.index(index);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const request = source.openCursor(range, direction);
|
|
436
|
+
const results = [];
|
|
437
|
+
|
|
438
|
+
request.onsuccess = (event) => {
|
|
439
|
+
const cursor = event.target.result;
|
|
440
|
+
|
|
441
|
+
if (cursor) {
|
|
442
|
+
// FIXED: No async operations in cursor callback
|
|
443
|
+
const shouldContinue = processCallback(cursor.value, cursor.key, results);
|
|
444
|
+
|
|
445
|
+
if (shouldContinue !== false) {
|
|
446
|
+
cursor.continue();
|
|
447
|
+
} else {
|
|
448
|
+
resolve(results);
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
resolve(results);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
request.onerror = () => reject(request.error);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
413
458
|
|
|
414
459
|
toString() {
|
|
415
460
|
return '[IndexedDBUtility]';
|
|
@@ -1936,10 +1981,9 @@ export class Collection {
|
|
|
1936
1981
|
index = null
|
|
1937
1982
|
} = options;
|
|
1938
1983
|
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1984
|
+
// FIXED: Collect raw document data first, then process async operations after transaction
|
|
1985
|
+
const rawDocuments = [];
|
|
1986
|
+
|
|
1943
1987
|
await IndexedDBUtility.performTransaction(
|
|
1944
1988
|
this.database.db,
|
|
1945
1989
|
this.name,
|
|
@@ -1956,56 +2000,28 @@ export class Collection {
|
|
|
1956
2000
|
: source.openCursor();
|
|
1957
2001
|
|
|
1958
2002
|
return new Promise((resolve, reject) => {
|
|
1959
|
-
|
|
2003
|
+
let count = 0;
|
|
2004
|
+
let skipped = 0;
|
|
2005
|
+
|
|
2006
|
+
request.onsuccess = (event) => {
|
|
1960
2007
|
const cursor = event.target.result;
|
|
1961
2008
|
|
|
1962
2009
|
if (!cursor || count >= limit) {
|
|
1963
|
-
resolve(
|
|
2010
|
+
resolve();
|
|
1964
2011
|
return;
|
|
1965
2012
|
}
|
|
1966
2013
|
|
|
1967
|
-
const docData = cursor.value;
|
|
1968
|
-
|
|
1969
2014
|
if (skipped < offset) {
|
|
1970
2015
|
skipped++;
|
|
1971
2016
|
cursor.continue();
|
|
1972
2017
|
return;
|
|
1973
2018
|
}
|
|
1974
2019
|
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
cursor.continue();
|
|
1979
|
-
return;
|
|
1980
|
-
}
|
|
1981
|
-
document = new Document(docData, encryptionKey);
|
|
1982
|
-
} else {
|
|
1983
|
-
document = new Document(docData);
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
if (Object.keys(filter).length > 0) {
|
|
1987
|
-
const object = await document.objectOutput();
|
|
1988
|
-
let match = true;
|
|
1989
|
-
|
|
1990
|
-
for (const key in filter) {
|
|
1991
|
-
const value = key.includes('.')
|
|
1992
|
-
? getNestedValue(object.data, key)
|
|
1993
|
-
: object.data[key];
|
|
1994
|
-
|
|
1995
|
-
if (value !== filter[key]) {
|
|
1996
|
-
match = false;
|
|
1997
|
-
break;
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
if (!match) {
|
|
2002
|
-
cursor.continue();
|
|
2003
|
-
return;
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
results.push(await document.objectOutput());
|
|
2020
|
+
// FIXED: Just collect raw data, no async operations here
|
|
2021
|
+
const docData = cursor.value;
|
|
2022
|
+
rawDocuments.push(docData);
|
|
2008
2023
|
count++;
|
|
2024
|
+
|
|
2009
2025
|
cursor.continue();
|
|
2010
2026
|
};
|
|
2011
2027
|
|
|
@@ -2014,6 +2030,44 @@ export class Collection {
|
|
|
2014
2030
|
}
|
|
2015
2031
|
);
|
|
2016
2032
|
|
|
2033
|
+
// FIXED: Process documents after transaction is complete
|
|
2034
|
+
const results = [];
|
|
2035
|
+
|
|
2036
|
+
for (const docData of rawDocuments) {
|
|
2037
|
+
let document;
|
|
2038
|
+
if (Document.isEncrypted(docData)) {
|
|
2039
|
+
if (!encryptionKey) {
|
|
2040
|
+
continue; // Skip encrypted documents without key
|
|
2041
|
+
}
|
|
2042
|
+
document = new Document(docData, encryptionKey);
|
|
2043
|
+
} else {
|
|
2044
|
+
document = new Document(docData);
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
// Apply filters if any
|
|
2048
|
+
if (Object.keys(filter).length > 0) {
|
|
2049
|
+
const object = await document.objectOutput();
|
|
2050
|
+
let match = true;
|
|
2051
|
+
|
|
2052
|
+
for (const key in filter) {
|
|
2053
|
+
const value = key.includes('.')
|
|
2054
|
+
? getNestedValue(object.data, key)
|
|
2055
|
+
: object.data[key];
|
|
2056
|
+
|
|
2057
|
+
if (value !== filter[key]) {
|
|
2058
|
+
match = false;
|
|
2059
|
+
break;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
if (!match) {
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
results.push(await document.objectOutput());
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2017
2071
|
return results;
|
|
2018
2072
|
}
|
|
2019
2073
|
|
|
@@ -2029,3 +2083,4 @@ export class Collection {
|
|
|
2029
2083
|
return `[Collection: ${this.name} | Database: ${this.database.name} | Size: ${this.sizeKB.toFixed(2)}KB | Documents: ${this.length}]`;
|
|
2030
2084
|
}
|
|
2031
2085
|
}
|
|
2086
|
+
|