@live-change/db-store-indexeddb 0.6.2 → 0.6.4

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/lib/Store.js +154 -93
  2. package/package.json +3 -3
package/lib/Store.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const IntervalTree = require('@live-change/interval-tree').default
2
2
  const ReactiveDao = require("@live-change/dao")
3
3
  const { BroadcastChannel, createLeaderElection } = require('broadcast-channel')
4
- const idb = require('idb')
4
+
5
5
 
6
6
 
7
7
  class ObjectObservable extends ReactiveDao.ObservableValue {
@@ -301,6 +301,18 @@ class CountObservable extends ReactiveDao.ObservableValue {
301
301
  }
302
302
  }
303
303
 
304
+ async function handleRequest(request, onUpgrade = ()=>{}) {
305
+ return new Promise((resolve, reject) => {
306
+ request.onerror = (event) => {
307
+ reject(request.error)
308
+ }
309
+ request.onsuccess = (event) => {
310
+ resolve(request.result)
311
+ }
312
+ request.onupgradeneeded = onUpgrade
313
+ })
314
+ }
315
+
304
316
  class Store {
305
317
  constructor(dbName, storeName, options = {}) {
306
318
  if(!dbName) throw new Error("dbName argument is required")
@@ -308,6 +320,7 @@ class Store {
308
320
 
309
321
  this.dbName = dbName
310
322
  this.storeName = storeName
323
+ this.idbName = dbName + '.' + storeName
311
324
 
312
325
  if(options.noSerialization) {
313
326
  this.serialization = {
@@ -328,6 +341,8 @@ class Store {
328
341
  this.dbPromise = null
329
342
  this.channel = null
330
343
 
344
+ this.openPromise = null
345
+
331
346
  this.objectObservables = new Map()
332
347
  this.rangeObservables = new Map()
333
348
  this.countObservables = new Map()
@@ -335,24 +350,23 @@ class Store {
335
350
  }
336
351
 
337
352
  async openDb() {
338
- this.dbPromise = idb.openDB(`lc-db-${this.dbName}-${this.storeName}`, 1, {
339
- upgrade: (db) => {
340
- const store = db.createObjectStore(this.storeName, { keyPath: 'id' })
341
- },
342
- terminated: async () => {
343
- console.log("IndexedDB terminated!")
344
- if(this.finished) return
345
- for(const [key, value] of this.objectObservables) value.dispose()
346
- for(const [key, value] of this.rangeObservables) value.dispose()
347
- await this.openDb()
348
- for(const [key, value] of this.objectObservables) value.respawn()
349
- for(const [key, value] of this.rangeObservables) value.respawn()
350
- }
353
+ console.log("Opening db", this.dbName, this.storeName)
354
+ const openRequest = globalThis.indexedDB.open(this.idbName, 1)
355
+ globalThis.lastOpenRequest = openRequest
356
+ this.dbPromise = handleRequest(openRequest, (event) => {
357
+ //console.error("Upgrading db", this.dbName, this.storeName)
358
+ const db = event.target.result
359
+ const store = db.createObjectStore(this.storeName, { keyPath: 'id' })
351
360
  })
352
361
  this.db = await this.dbPromise
362
+ this.db.addEventListener("close", (event) => {
363
+ console.log("database close event", this.idbName)
364
+ this.openDb()
365
+ })
366
+ console.log("Opened db", this.dbName, this.storeName)
353
367
  }
354
368
  async openChannel() {
355
- this.channel = new BroadcastChannel('lc-db-channel' + this.dbName, {
369
+ this.channel = new BroadcastChannel('lc-db-channel' + this.dbName + '-' + this.storeName, {
356
370
  idb: {
357
371
  onclose: () => {
358
372
  if(this.finished) return
@@ -373,13 +387,13 @@ class Store {
373
387
  ;(await this.dbPromise).close()
374
388
  }
375
389
  async ensureOpen() {
376
- if(!this.dbPromise) await this.open()
377
- await this.dbPromise
390
+ if(!this.openPromise) this.openPromise = this.open()
391
+ await this.openPromise
378
392
  }
379
393
 
380
394
  async deleteDb() {
395
+ ;(await this.dbPromise).deleteObjectStore(this.storeName)
381
396
  if(!this.finished) await this.close()
382
- await idb.deleteDB('lc-db-' + this.dbName)
383
397
  }
384
398
 
385
399
  async handleChannelMessage(message) {
@@ -418,8 +432,10 @@ class Store {
418
432
  await this.ensureOpen()
419
433
  if(!id) throw new Error("key is required")
420
434
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
421
- const json = await this.db.get(this.storeName, id) || null
422
- return json && this.serialization.parse(json)
435
+ const transaction = this.db.transaction([this.storeName], 'readonly')
436
+ const store = transaction.objectStore(this.storeName)
437
+ const json = await handleRequest(store.get(id) || null)
438
+ return json ? this.serialization.parse(json) : null
423
439
  }
424
440
 
425
441
  objectObservable(key) {
@@ -432,9 +448,6 @@ class Store {
432
448
  async rangeGet(range) {
433
449
  if(!range) throw new Error("range not defined")
434
450
  await this.ensureOpen()
435
- console.log("RANGE GET!")
436
-
437
- const txn = this.db.transaction(this.storeName, 'readonly')
438
451
  let data = []
439
452
  const min = range.gt || range.gte
440
453
  const max = range.lt || range.lte
@@ -444,34 +457,53 @@ class Store {
444
457
  } else if(min) {
445
458
  keyRange = IDBKeyRange.lowerBound(min, !!range.gt)
446
459
  } else if(max) {
447
- keyRange = IDBKeyRange.upperBound(min, !!range.gt)
460
+ keyRange = IDBKeyRange.upperBound(max, !!range.gt)
448
461
  }
449
462
 
463
+ const txn = this.db.transaction([this.storeName], 'readonly')
464
+ const store = txn.objectStore(this.storeName)
450
465
  if(range.reverse) {
451
- let cursor = await txn.store.openCursor(keyRange, 'prev')
452
- while ((!range.limit || data.length < range.limit) && cursor) {
453
- if (range.gt && cursor.key <= range.gt) break
454
- if (range.gte && cursor.key < range.gte) break
455
- if ((!range.lt || cursor.key < range.lt) && (!range.lte || cursor.key <= range.lte)) {
456
- const json = cursor.value
457
- data.push(this.serialization.parse(json))
466
+ await new Promise((resolve, reject) => {
467
+ const cursorRequest = store.openCursor(keyRange, 'prev')
468
+ cursorRequest.onsuccess = (event) => {
469
+ const cursor = event.target.result
470
+ if ((!range.limit || data.length < range.limit) && cursor) {
471
+ if (range.gt && cursor.key <= range.gt) return resolve()
472
+ if (range.gte && cursor.key < range.gte) return resolve()
473
+ if ((!range.lt || cursor.key < range.lt) && (!range.lte || cursor.key <= range.lte)) {
474
+ const json = cursor.value
475
+ data.push(this.serialization.parse(json))
476
+ }
477
+ cursor.continue()
478
+ } else {
479
+ return resolve()
480
+ }
458
481
  }
459
- cursor = await cursor.continue()
460
- }
482
+ cursorRequest.onerror = (event) => {
483
+ reject(event.target.error)
484
+ }
485
+ })
461
486
  } else {
462
- let cursor = await txn.store.openCursor(keyRange, 'next')
463
- //console.log("CURSOR", cursor)
464
- while((!range.limit || data.length < range.limit) && cursor) {
465
- if(range.lt && cursor.key >= range.lt) break
466
- if(range.lte && cursor.key > range.lte) break
467
- if((!range.gt || cursor.key > range.gt) && (!range.gte || cursor.key >= range.gte)) {
468
- const json = cursor.value
469
- data.push(this.serialization.parse(json))
487
+ await new Promise((resolve, reject) => {
488
+ const cursorRequest = store.openCursor(keyRange, 'next')
489
+ cursorRequest.onsuccess = (event) => {
490
+ const cursor = event.target.result
491
+ if ((!range.limit || data.length < range.limit) && cursor) {
492
+ if(range.lt && cursor.key >= range.lt) return resolve()
493
+ if(range.lte && cursor.key > range.lte) return resolve()
494
+ if((!range.gt || cursor.key > range.gt) && (!range.gte || cursor.key >= range.gte)) {
495
+ const json = cursor.value
496
+ data.push(this.serialization.parse(json))
497
+ }
498
+ cursor.continue()
499
+ } else {
500
+ return resolve()
501
+ }
470
502
  }
471
- cursor = await cursor.continue()
472
- //console.log("CURSOR C", cursor)
473
- }
474
- //console.log("CUR READ END", cursor)
503
+ cursorRequest.onerror = (event) => {
504
+ reject(event.target.error)
505
+ }
506
+ })
475
507
  }
476
508
  return data
477
509
  }
@@ -496,7 +528,9 @@ class Store {
496
528
  } else if(max) {
497
529
  keyRange = IDBKeyRange.upperBound(min, !!range.gt)
498
530
  }
499
- const count = await this.db.count(this.storeName, keyRange)
531
+ const txn = this.db.transaction([this.storeName], 'readonly')
532
+ const store = txn.objectStore(this.storeName)
533
+ const count = await handleRequest(store.count(keyRange))
500
534
  if(range.limit && count > range.limit) return range.limit
501
535
  return count
502
536
  }
@@ -512,7 +546,6 @@ class Store {
512
546
  if(!range) throw new Error("range not defined")
513
547
  await this.ensureOpen()
514
548
 
515
- const txn = this.db.transaction(this.storeName)
516
549
  let count = 0, last
517
550
  const min = range.gt || range.gte
518
551
  const max = range.lt || range.lte
@@ -525,52 +558,74 @@ class Store {
525
558
  keyRange = IDBKeyRange.upperBound(min, !!range.gt)
526
559
  }
527
560
 
561
+ const txn = this.db.transaction([this.storeName], 'readonly')
562
+ const store = txn.objectStore(this.storeName)
528
563
  if(range.reverse) {
529
- let cursor = await txn.store.openCursor(keyRange, 'prev')
530
- while ((!range.limit || data.length < range.limit) && cursor) {
531
- if (range.gt && cursor.key <= range.gt) break
532
- if (range.gte && cursor.key < range.gte) break
533
- if ((!range.lt || cursor.key < range.lt) && (!range.lte || cursor.key <= range.lte)) {
534
- count++
535
- const id = cursor.key
536
- const json = cursor.value
537
- const object = this.serialization.parse(json)
538
- last = id
539
- await cursor.delete()
540
-
541
- const objectObservable = this.objectObservables.get(id)
542
- if(objectObservable) objectObservable.set(null)
543
- const rangeObservables = this.rangeObservablesTree.search([id, id])
544
- for(const rangeObservable of rangeObservables) {
545
- rangeObservable.deleteObject(object)
564
+ await new Promise((resolve, reject) => {
565
+ const cursorRequest = store.openCursor(keyRange, 'prev')
566
+ cursorRequest.onsuccess = async (event) => {
567
+ const cursor = event.target.result
568
+ if ((!range.limit || count < range.limit) && cursor) {
569
+ if (range.gt && cursor.key <= range.gt) return resolve()
570
+ if (range.gte && cursor.key < range.gte) return resolve()
571
+ if ((!range.lt || cursor.key < range.lt) && (!range.lte || cursor.key <= range.lte)) {
572
+ count++
573
+ const id = cursor.key
574
+ const json = cursor.value
575
+ const object = this.serialization.parse(json)
576
+ last = id
577
+ await handleRequest(cursor.delete())
578
+
579
+ const objectObservable = this.objectObservables.get(id)
580
+ if(objectObservable) objectObservable.set(null)
581
+ const rangeObservables = this.rangeObservablesTree.search([id, id])
582
+ for(const rangeObservable of rangeObservables) {
583
+ rangeObservable.deleteObject(object)
584
+ }
585
+ this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
586
+ }
587
+ cursor.continue()
588
+ } else {
589
+ return resolve()
546
590
  }
547
- this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
548
591
  }
549
- cursor = await cursor.continue()
550
- }
592
+ cursorRequest.onerror = (event) => {
593
+ reject(event.target.error)
594
+ }
595
+ })
551
596
  } else {
552
- let cursor = await txn.store.openCursor(keyRange, 'prev')
553
- while((!range.limit || data.length < range.limit) && cursor) {
554
- if(range.lt && cursor.key >= range.lt) break
555
- if(range.lte && cursor.key > range.lte) break
556
- if((!range.gt || cursor.key > range.gt) && (!range.gte || cursor.key >= range.gte)) {
557
- count++
558
- const id = cursor.key
559
- const json = cursor.value
560
- const object = this.serialization.parse(json)
561
- last = id
562
- await cursor.delete()
563
-
564
- const objectObservable = this.objectObservables.get(id)
565
- if(objectObservable) objectObservable.set(null)
566
- const rangeObservables = this.rangeObservablesTree.search([id, id])
567
- for(const rangeObservable of rangeObservables) {
568
- rangeObservable.deleteObject(object)
597
+ await new Promise((resolve, reject) => {
598
+ const cursorRequest = store.openCursor(keyRange, 'next')
599
+ cursorRequest.onsuccess = async (event) => {
600
+ const cursor = event.target.result
601
+ if ((!range.limit || count < range.limit) && cursor) {
602
+ if(range.lt && cursor.key >= range.lt) return resolve()
603
+ if(range.lte && cursor.key > range.lte) return resolve()
604
+ if((!range.gt || cursor.key > range.gt) && (!range.gte || cursor.key >= range.gte)) {
605
+ count++
606
+ const id = cursor.key
607
+ const json = cursor.value
608
+ const object = this.serialization.parse(json)
609
+ last = id
610
+ await handleRequest(cursor.delete())
611
+
612
+ const objectObservable = this.objectObservables.get(id)
613
+ if(objectObservable) objectObservable.set(null)
614
+ const rangeObservables = this.rangeObservablesTree.search([id, id])
615
+ for(const rangeObservable of rangeObservables) {
616
+ rangeObservable.deleteObject(object)
617
+ }
618
+ this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
619
+ }
620
+ cursor.continue()
621
+ } else {
622
+ return resolve()
569
623
  }
570
- this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
571
624
  }
572
- cursor = await cursor.continue()
573
- }
625
+ cursorRequest.onerror = (event) => {
626
+ reject(event.target.error)
627
+ }
628
+ })
574
629
  }
575
630
  return { count, last }
576
631
  }
@@ -579,12 +634,15 @@ class Store {
579
634
  const id = object.id
580
635
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
581
636
  await this.ensureOpen()
582
-
583
- const oldObjectJson = await this.db.get(this.storeName, id)
637
+ //console.error("put", id, object, 'in', this.storeName)
638
+ //console.error("storeNames", this.db.objectStoreNames)
639
+ const transaction = this.db.transaction([this.storeName], 'readwrite')
640
+ const store = transaction.objectStore(this.storeName)
641
+ const oldObjectJson = await handleRequest(store.get(id))
584
642
  const oldObject = oldObjectJson ? this.serialization.parse(oldObjectJson) : null
585
- console.log("PUT", object)
643
+ //console.log("PUT", object)
586
644
  const json = this.serialization.stringify(object)
587
- await this.db.put(this.storeName, json)
645
+ await handleRequest(store.put(json))
588
646
  const objectObservable = this.objectObservables.get(id)
589
647
  if(objectObservable) objectObservable.set(object, oldObject)
590
648
  const rangeObservables = this.rangeObservablesTree.search([id, id])
@@ -602,9 +660,12 @@ class Store {
602
660
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
603
661
  await this.ensureOpen()
604
662
 
605
- const json = await this.db.get(this.storeName, id)
663
+ const transaction = this.db.transaction([this.storeName], 'readwrite')
664
+ const store = transaction.objectStore(this.storeName)
665
+
666
+ const json = await handleRequest(store.get(id))
606
667
  const object = json ? this.serialization.parse(json) : null
607
- await this.db.delete(this.storeName, id)
668
+ await handleRequest(store.delete(id))
608
669
  const objectObservable = this.objectObservables.get(id)
609
670
  if(objectObservable) objectObservable.set(null)
610
671
  const rangeObservables = this.rangeObservablesTree.search([id, id])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/db-store-indexeddb",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Database with observable data for live queries",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -26,9 +26,9 @@
26
26
  "tape": "^5.3.2"
27
27
  },
28
28
  "dependencies": {
29
- "@live-change/dao": "0.5.14",
29
+ "@live-change/dao": "0.5.15",
30
30
  "@live-change/interval-tree": "^1.0.12",
31
31
  "broadcast-channel": "^4.2.0"
32
32
  },
33
- "gitHead": "9a1b104864c08f3e35b009f191889e3308e3eeb0"
33
+ "gitHead": "2b63a796522f2a5bf7c4e0311331640b4152ba77"
34
34
  }