@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.
- package/lib/Store.js +154 -93
- 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
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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.
|
|
377
|
-
await this.
|
|
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
|
|
422
|
-
|
|
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(
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
460
|
-
|
|
482
|
+
cursorRequest.onerror = (event) => {
|
|
483
|
+
reject(event.target.error)
|
|
484
|
+
}
|
|
485
|
+
})
|
|
461
486
|
} else {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
|
|
550
|
-
|
|
592
|
+
cursorRequest.onerror = (event) => {
|
|
593
|
+
reject(event.target.error)
|
|
594
|
+
}
|
|
595
|
+
})
|
|
551
596
|
} else {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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": "
|
|
33
|
+
"gitHead": "2b63a796522f2a5bf7c4e0311331640b4152ba77"
|
|
34
34
|
}
|