@live-change/db-store-indexeddb 0.6.2 → 0.6.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/lib/Store.js +150 -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,19 @@ 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
+ //console.log("Opened db", this.dbName, this.storeName)
353
363
  }
354
364
  async openChannel() {
355
- this.channel = new BroadcastChannel('lc-db-channel' + this.dbName, {
365
+ this.channel = new BroadcastChannel('lc-db-channel' + this.dbName + '-' + this.storeName, {
356
366
  idb: {
357
367
  onclose: () => {
358
368
  if(this.finished) return
@@ -373,13 +383,13 @@ class Store {
373
383
  ;(await this.dbPromise).close()
374
384
  }
375
385
  async ensureOpen() {
376
- if(!this.dbPromise) await this.open()
377
- await this.dbPromise
386
+ if(!this.openPromise) this.openPromise = this.open()
387
+ await this.openPromise
378
388
  }
379
389
 
380
390
  async deleteDb() {
391
+ ;(await this.dbPromise).deleteObjectStore(this.storeName)
381
392
  if(!this.finished) await this.close()
382
- await idb.deleteDB('lc-db-' + this.dbName)
383
393
  }
384
394
 
385
395
  async handleChannelMessage(message) {
@@ -418,8 +428,10 @@ class Store {
418
428
  await this.ensureOpen()
419
429
  if(!id) throw new Error("key is required")
420
430
  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)
431
+ const transaction = this.db.transaction([this.storeName], 'readonly')
432
+ const store = transaction.objectStore(this.storeName)
433
+ const json = await handleRequest(store.get(id) || null)
434
+ return json ? this.serialization.parse(json) : null
423
435
  }
424
436
 
425
437
  objectObservable(key) {
@@ -432,9 +444,6 @@ class Store {
432
444
  async rangeGet(range) {
433
445
  if(!range) throw new Error("range not defined")
434
446
  await this.ensureOpen()
435
- console.log("RANGE GET!")
436
-
437
- const txn = this.db.transaction(this.storeName, 'readonly')
438
447
  let data = []
439
448
  const min = range.gt || range.gte
440
449
  const max = range.lt || range.lte
@@ -444,34 +453,53 @@ class Store {
444
453
  } else if(min) {
445
454
  keyRange = IDBKeyRange.lowerBound(min, !!range.gt)
446
455
  } else if(max) {
447
- keyRange = IDBKeyRange.upperBound(min, !!range.gt)
456
+ keyRange = IDBKeyRange.upperBound(max, !!range.gt)
448
457
  }
449
458
 
459
+ const txn = this.db.transaction([this.storeName], 'readonly')
460
+ const store = txn.objectStore(this.storeName)
450
461
  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))
462
+ await new Promise((resolve, reject) => {
463
+ const cursorRequest = store.openCursor(keyRange, 'prev')
464
+ cursorRequest.onsuccess = (event) => {
465
+ const cursor = event.target.result
466
+ if ((!range.limit || data.length < range.limit) && cursor) {
467
+ if (range.gt && cursor.key <= range.gt) return resolve()
468
+ if (range.gte && cursor.key < range.gte) return resolve()
469
+ if ((!range.lt || cursor.key < range.lt) && (!range.lte || cursor.key <= range.lte)) {
470
+ const json = cursor.value
471
+ data.push(this.serialization.parse(json))
472
+ }
473
+ cursor.continue()
474
+ } else {
475
+ return resolve()
476
+ }
458
477
  }
459
- cursor = await cursor.continue()
460
- }
478
+ cursorRequest.onerror = (event) => {
479
+ reject(event.target.error)
480
+ }
481
+ })
461
482
  } 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))
483
+ await new Promise((resolve, reject) => {
484
+ const cursorRequest = store.openCursor(keyRange, 'next')
485
+ cursorRequest.onsuccess = (event) => {
486
+ const cursor = event.target.result
487
+ if ((!range.limit || data.length < range.limit) && cursor) {
488
+ if(range.lt && cursor.key >= range.lt) return resolve()
489
+ if(range.lte && cursor.key > range.lte) return resolve()
490
+ if((!range.gt || cursor.key > range.gt) && (!range.gte || cursor.key >= range.gte)) {
491
+ const json = cursor.value
492
+ data.push(this.serialization.parse(json))
493
+ }
494
+ cursor.continue()
495
+ } else {
496
+ return resolve()
497
+ }
470
498
  }
471
- cursor = await cursor.continue()
472
- //console.log("CURSOR C", cursor)
473
- }
474
- //console.log("CUR READ END", cursor)
499
+ cursorRequest.onerror = (event) => {
500
+ reject(event.target.error)
501
+ }
502
+ })
475
503
  }
476
504
  return data
477
505
  }
@@ -496,7 +524,9 @@ class Store {
496
524
  } else if(max) {
497
525
  keyRange = IDBKeyRange.upperBound(min, !!range.gt)
498
526
  }
499
- const count = await this.db.count(this.storeName, keyRange)
527
+ const txn = this.db.transaction([this.storeName], 'readonly')
528
+ const store = txn.objectStore(this.storeName)
529
+ const count = await handleRequest(store.count(keyRange))
500
530
  if(range.limit && count > range.limit) return range.limit
501
531
  return count
502
532
  }
@@ -512,7 +542,6 @@ class Store {
512
542
  if(!range) throw new Error("range not defined")
513
543
  await this.ensureOpen()
514
544
 
515
- const txn = this.db.transaction(this.storeName)
516
545
  let count = 0, last
517
546
  const min = range.gt || range.gte
518
547
  const max = range.lt || range.lte
@@ -525,52 +554,74 @@ class Store {
525
554
  keyRange = IDBKeyRange.upperBound(min, !!range.gt)
526
555
  }
527
556
 
557
+ const txn = this.db.transaction([this.storeName], 'readonly')
558
+ const store = txn.objectStore(this.storeName)
528
559
  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)
560
+ await new Promise((resolve, reject) => {
561
+ const cursorRequest = store.openCursor(keyRange, 'prev')
562
+ cursorRequest.onsuccess = async (event) => {
563
+ const cursor = event.target.result
564
+ if ((!range.limit || count < range.limit) && cursor) {
565
+ if (range.gt && cursor.key <= range.gt) return resolve()
566
+ if (range.gte && cursor.key < range.gte) return resolve()
567
+ if ((!range.lt || cursor.key < range.lt) && (!range.lte || cursor.key <= range.lte)) {
568
+ count++
569
+ const id = cursor.key
570
+ const json = cursor.value
571
+ const object = this.serialization.parse(json)
572
+ last = id
573
+ await handleRequest(cursor.delete())
574
+
575
+ const objectObservable = this.objectObservables.get(id)
576
+ if(objectObservable) objectObservable.set(null)
577
+ const rangeObservables = this.rangeObservablesTree.search([id, id])
578
+ for(const rangeObservable of rangeObservables) {
579
+ rangeObservable.deleteObject(object)
580
+ }
581
+ this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
582
+ }
583
+ cursor.continue()
584
+ } else {
585
+ return resolve()
546
586
  }
547
- this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
548
587
  }
549
- cursor = await cursor.continue()
550
- }
588
+ cursorRequest.onerror = (event) => {
589
+ reject(event.target.error)
590
+ }
591
+ })
551
592
  } 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)
593
+ await new Promise((resolve, reject) => {
594
+ const cursorRequest = store.openCursor(keyRange, 'next')
595
+ cursorRequest.onsuccess = async (event) => {
596
+ const cursor = event.target.result
597
+ if ((!range.limit || count < range.limit) && cursor) {
598
+ if(range.lt && cursor.key >= range.lt) return resolve()
599
+ if(range.lte && cursor.key > range.lte) return resolve()
600
+ if((!range.gt || cursor.key > range.gt) && (!range.gte || cursor.key >= range.gte)) {
601
+ count++
602
+ const id = cursor.key
603
+ const json = cursor.value
604
+ const object = this.serialization.parse(json)
605
+ last = id
606
+ await handleRequest(cursor.delete())
607
+
608
+ const objectObservable = this.objectObservables.get(id)
609
+ if(objectObservable) objectObservable.set(null)
610
+ const rangeObservables = this.rangeObservablesTree.search([id, id])
611
+ for(const rangeObservable of rangeObservables) {
612
+ rangeObservable.deleteObject(object)
613
+ }
614
+ this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
615
+ }
616
+ cursor.continue()
617
+ } else {
618
+ return resolve()
569
619
  }
570
- this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
571
620
  }
572
- cursor = await cursor.continue()
573
- }
621
+ cursorRequest.onerror = (event) => {
622
+ reject(event.target.error)
623
+ }
624
+ })
574
625
  }
575
626
  return { count, last }
576
627
  }
@@ -579,12 +630,15 @@ class Store {
579
630
  const id = object.id
580
631
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
581
632
  await this.ensureOpen()
582
-
583
- const oldObjectJson = await this.db.get(this.storeName, id)
633
+ //console.error("put", id, object, 'in', this.storeName)
634
+ //console.error("storeNames", this.db.objectStoreNames)
635
+ const transaction = this.db.transaction([this.storeName], 'readwrite')
636
+ const store = transaction.objectStore(this.storeName)
637
+ const oldObjectJson = await handleRequest(store.get(id))
584
638
  const oldObject = oldObjectJson ? this.serialization.parse(oldObjectJson) : null
585
- console.log("PUT", object)
639
+ //console.log("PUT", object)
586
640
  const json = this.serialization.stringify(object)
587
- await this.db.put(this.storeName, json)
641
+ await handleRequest(store.put(json))
588
642
  const objectObservable = this.objectObservables.get(id)
589
643
  if(objectObservable) objectObservable.set(object, oldObject)
590
644
  const rangeObservables = this.rangeObservablesTree.search([id, id])
@@ -602,9 +656,12 @@ class Store {
602
656
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
603
657
  await this.ensureOpen()
604
658
 
605
- const json = await this.db.get(this.storeName, id)
659
+ const transaction = this.db.transaction([this.storeName], 'readwrite')
660
+ const store = transaction.objectStore(this.storeName)
661
+
662
+ const json = await handleRequest(store.get(id))
606
663
  const object = json ? this.serialization.parse(json) : null
607
- await this.db.delete(this.storeName, id)
664
+ await handleRequest(store.delete(id))
608
665
  const objectObservable = this.objectObservables.get(id)
609
666
  if(objectObservable) objectObservable.set(null)
610
667
  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.3",
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": "8dc4ac726243970c9f1431bf67b4390f7845ce76"
34
34
  }