@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.
Files changed (2) hide show
  1. package/index.js +113 -58
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -287,34 +287,41 @@ class IndexedDBUtility {
287
287
  });
288
288
  }
289
289
 
290
- // FIXED: Improved transaction retry logic with idempotency check
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
- // Track if operation has been applied
306
- const operationKey = operationId || `${Date.now()}_${Math.random()}`;
307
-
308
- const result = await callback(...stores, operationKey);
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 = () => resolve(result);
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
- lastError = new Error(`Transaction aborted: ${tx.error ? tx.error.message : 'unknown reason'}`);
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
- await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100));
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
- const results = [];
1940
- let count = 0;
1941
- let skipped = 0;
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
- request.onsuccess = async (event) => {
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(results);
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
- let document;
1976
- if (Document.isEncrypted(docData)) {
1977
- if (!encryptionKey) {
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
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixagram/lacerta-db",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Lacerta-DB is a Javascript IndexedDB Database for Web Browsers. Simple, Fast, Secure.",
5
5
  "devDependencies": {
6
6
  "@babel/core": "^7.23.6",