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

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 +136 -16
  2. package/package.json +2 -2
package/lib/Store.js CHANGED
@@ -221,14 +221,107 @@ class RangeObservable extends ReactiveDao.ObservableList {
221
221
  }
222
222
  }
223
223
 
224
+ class CountObservable extends ReactiveDao.ObservableValue {
225
+ constructor(store, range) {
226
+ super()
227
+ this.store = store
228
+ this.range = range
229
+
230
+ this.disposed = false
231
+ this.ready = false
232
+ this.respawnId = 0
233
+ this.refillId = 0
234
+ this.refillPromise = null
235
+
236
+ this.forward = null
237
+
238
+ this.rangeKey = JSON.stringify(this.range)
239
+ this.rangeDescr = [ this.range.gt || this.range.gte || '', this.range.lt || this.range.lte || '\xFF\xFF\xFF\xFF']
240
+
241
+ this.readPromise = this.startReading()
242
+ }
243
+
244
+ async startReading() {
245
+ this.store.rangeObservables.set(this.rangeKey, this)
246
+ const treeInsert = this.rangeDescr
247
+ this.store.rangeObservablesTree.insert(treeInsert, this)
248
+ this.set(await this.store.countGet(this.range))
249
+ }
250
+
251
+ async putObject(object, oldObject) {
252
+ const id = object.id
253
+ if(this.range.gt && !(id > this.range.gt)) return
254
+ if(this.range.lt && !(id < this.range.lt)) return
255
+ await this.readPromise
256
+ if(this.range.limit) {
257
+ this.set(await this.store.countGet(this.range))
258
+ } else {
259
+ if(object && !oldObject) {
260
+ this.set(this.value + 1)
261
+ } else if(!object && oldObject) {
262
+ this.set(this.value - 1)
263
+ }
264
+ }
265
+ }
266
+
267
+ async deleteObject(object) {
268
+ const id = object.id
269
+ if(this.range.gt && !(id > this.range.gt)) return
270
+ if(this.range.lt && !(id < this.range.lt)) return
271
+ this.set(this.value - 1)
272
+ }
273
+
274
+ dispose() {
275
+ if(this.forward) {
276
+ this.forward.unobserve(this)
277
+ this.forward = null
278
+ return
279
+ }
280
+
281
+ this.disposed = true
282
+ this.respawnId++
283
+ this.changesStream = null
284
+
285
+ this.store.rangeObservables.delete(this.rangeKey)
286
+ let removed = this.store.rangeObservablesTree.remove(this.rangeDescr, this)
287
+ }
288
+
289
+ respawn() {
290
+ const existingObservable = this.store.rangeObservables.get(JSON.stringify(this.range))
291
+ if(existingObservable) {
292
+ this.forward = existingObservable
293
+ this.forward.observe(this)
294
+ return
295
+ }
296
+
297
+ this.respawnId++
298
+ this.ready = false
299
+ this.disposed = false
300
+ this.startReading()
301
+ }
302
+ }
303
+
224
304
  class Store {
225
- constructor(dbName, storeName) {
305
+ constructor(dbName, storeName, options = {}) {
226
306
  if(!dbName) throw new Error("dbName argument is required")
227
307
  if(!storeName) throw new Error("storeName argument is required")
228
308
 
229
309
  this.dbName = dbName
230
310
  this.storeName = storeName
231
311
 
312
+ if(options.noSerialization) {
313
+ this.serialization = {
314
+ stringify: x => x,
315
+ parse: x => x
316
+ }
317
+ } else {
318
+ const serialization = options.serialization ?? JSON
319
+ this.serialization = {
320
+ stringify: x => x ? ({ id: x.id, data: serialization.stringify(x) }) : null,
321
+ parse: x => x?.data ? serialization.parse(x.data) : null
322
+ }
323
+ }
324
+
232
325
  this.finished = false
233
326
 
234
327
  this.db = null
@@ -237,6 +330,7 @@ class Store {
237
330
 
238
331
  this.objectObservables = new Map()
239
332
  this.rangeObservables = new Map()
333
+ this.countObservables = new Map()
240
334
  this.rangeObservablesTree = new IntervalTree()
241
335
  }
242
336
 
@@ -278,6 +372,10 @@ class Store {
278
372
  await this.channel.close()
279
373
  ;(await this.dbPromise).close()
280
374
  }
375
+ async ensureOpen() {
376
+ if(!this.dbPromise) await this.open()
377
+ await this.dbPromise
378
+ }
281
379
 
282
380
  async deleteDb() {
283
381
  if(!this.finished) await this.close()
@@ -287,7 +385,9 @@ class Store {
287
385
  async handleChannelMessage(message) {
288
386
  switch(message.type) {
289
387
  case 'put' : {
290
- const { object, oldObject } = message
388
+ const { object: objectJson, oldObjectJson } = message
389
+ const object = this.serialization.parse(objectJson)
390
+ const oldObject = this.serialization.parse(oldObjectJson)
291
391
  const id = object?.id || oldObject?.id
292
392
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
293
393
  const objectObservable = this.objectObservables.get(id)
@@ -298,7 +398,8 @@ class Store {
298
398
  }
299
399
  } break
300
400
  case 'delete' : {
301
- const { object } = message
401
+ const { object: objectJson } = message
402
+ const object = this.serialization.parse(objectJson)
302
403
  const id = object?.id
303
404
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
304
405
  const objectObservable = this.objectObservables.get(id)
@@ -314,9 +415,11 @@ class Store {
314
415
  }
315
416
 
316
417
  async objectGet(id) {
418
+ await this.ensureOpen()
317
419
  if(!id) throw new Error("key is required")
318
420
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
319
- return await this.db.get(this.storeName, id) || null
421
+ const json = await this.db.get(this.storeName, id) || null
422
+ return json && this.serialization.parse(json)
320
423
  }
321
424
 
322
425
  objectObservable(key) {
@@ -328,6 +431,7 @@ class Store {
328
431
 
329
432
  async rangeGet(range) {
330
433
  if(!range) throw new Error("range not defined")
434
+ await this.ensureOpen()
331
435
  console.log("RANGE GET!")
332
436
 
333
437
  const txn = this.db.transaction(this.storeName, 'readonly')
@@ -349,7 +453,8 @@ class Store {
349
453
  if (range.gt && cursor.key <= range.gt) break
350
454
  if (range.gte && cursor.key < range.gte) break
351
455
  if ((!range.lt || cursor.key < range.lt) && (!range.lte || cursor.key <= range.lte)) {
352
- data.push(cursor.value)
456
+ const json = cursor.value
457
+ data.push(this.serialization.parse(json))
353
458
  }
354
459
  cursor = await cursor.continue()
355
460
  }
@@ -360,7 +465,8 @@ class Store {
360
465
  if(range.lt && cursor.key >= range.lt) break
361
466
  if(range.lte && cursor.key > range.lte) break
362
467
  if((!range.gt || cursor.key > range.gt) && (!range.gte || cursor.key >= range.gte)) {
363
- data.push(cursor.value)
468
+ const json = cursor.value
469
+ data.push(this.serialization.parse(json))
364
470
  }
365
471
  cursor = await cursor.continue()
366
472
  //console.log("CURSOR C", cursor)
@@ -379,6 +485,7 @@ class Store {
379
485
 
380
486
  async countGet(range) {
381
487
  if(!range) throw new Error("range not defined")
488
+ await this.ensureOpen()
382
489
  const min = range.gt || range.gte
383
490
  const max = range.lt || range.lte
384
491
  let keyRange = undefined
@@ -389,7 +496,7 @@ class Store {
389
496
  } else if(max) {
390
497
  keyRange = IDBKeyRange.upperBound(min, !!range.gt)
391
498
  }
392
- const count = await this.count(this.storeName, keyRange)
499
+ const count = await this.db.count(this.storeName, keyRange)
393
500
  if(range.limit && count > range.limit) return range.limit
394
501
  return count
395
502
  }
@@ -403,6 +510,7 @@ class Store {
403
510
 
404
511
  async rangeDelete(range) {
405
512
  if(!range) throw new Error("range not defined")
513
+ await this.ensureOpen()
406
514
 
407
515
  const txn = this.db.transaction(this.storeName)
408
516
  let count = 0, last
@@ -425,7 +533,8 @@ class Store {
425
533
  if ((!range.lt || cursor.key < range.lt) && (!range.lte || cursor.key <= range.lte)) {
426
534
  count++
427
535
  const id = cursor.key
428
- const object = cursor.value
536
+ const json = cursor.value
537
+ const object = this.serialization.parse(json)
429
538
  last = id
430
539
  await cursor.delete()
431
540
 
@@ -435,7 +544,7 @@ class Store {
435
544
  for(const rangeObservable of rangeObservables) {
436
545
  rangeObservable.deleteObject(object)
437
546
  }
438
- this.channel.postMessage({ type: "delete", object })
547
+ this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
439
548
  }
440
549
  cursor = await cursor.continue()
441
550
  }
@@ -447,7 +556,8 @@ class Store {
447
556
  if((!range.gt || cursor.key > range.gt) && (!range.gte || cursor.key >= range.gte)) {
448
557
  count++
449
558
  const id = cursor.key
450
- const object = cursor.value
559
+ const json = cursor.value
560
+ const object = this.serialization.parse(json)
451
561
  last = id
452
562
  await cursor.delete()
453
563
 
@@ -457,7 +567,7 @@ class Store {
457
567
  for(const rangeObservable of rangeObservables) {
458
568
  rangeObservable.deleteObject(object)
459
569
  }
460
- this.channel.postMessage({ type: "delete", object })
570
+ this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
461
571
  }
462
572
  cursor = await cursor.continue()
463
573
  }
@@ -468,22 +578,32 @@ class Store {
468
578
  async put(object) {
469
579
  const id = object.id
470
580
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
471
- const oldObject = await this.db.get(this.storeName, id)
581
+ await this.ensureOpen()
582
+
583
+ const oldObjectJson = await this.db.get(this.storeName, id)
584
+ const oldObject = oldObjectJson ? this.serialization.parse(oldObjectJson) : null
472
585
  console.log("PUT", object)
473
- await this.db.put(this.storeName, object)
586
+ const json = this.serialization.stringify(object)
587
+ await this.db.put(this.storeName, json)
474
588
  const objectObservable = this.objectObservables.get(id)
475
589
  if(objectObservable) objectObservable.set(object, oldObject)
476
590
  const rangeObservables = this.rangeObservablesTree.search([id, id])
477
591
  for(const rangeObservable of rangeObservables) {
478
592
  rangeObservable.putObject(object, oldObject)
479
593
  }
480
- this.channel.postMessage({ type: "put", object, oldObject })
594
+ this.channel.postMessage({ type: "put",
595
+ object: this.serialization.stringify(object),
596
+ oldObject: this.serialization.stringify(oldObject)
597
+ })
481
598
  return oldObject
482
599
  }
483
600
 
484
601
  async delete(id) {
485
602
  if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
486
- const object = await this.db.get(this.storeName, id)
603
+ await this.ensureOpen()
604
+
605
+ const json = await this.db.get(this.storeName, id)
606
+ const object = json ? this.serialization.parse(json) : null
487
607
  await this.db.delete(this.storeName, id)
488
608
  const objectObservable = this.objectObservables.get(id)
489
609
  if(objectObservable) objectObservable.set(null)
@@ -491,7 +611,7 @@ class Store {
491
611
  for(const rangeObservable of rangeObservables) {
492
612
  rangeObservable.deleteObject(object)
493
613
  }
494
- this.channel.postMessage({ type: "delete", object })
614
+ this.channel.postMessage({ type: "delete", object: this.serialization.stringify(object) })
495
615
  return object
496
616
  }
497
617
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/db-store-indexeddb",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Database with observable data for live queries",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -30,5 +30,5 @@
30
30
  "@live-change/interval-tree": "^1.0.12",
31
31
  "broadcast-channel": "^4.2.0"
32
32
  },
33
- "gitHead": "358bc9b508a6e446b9286a01e0ceb0e77c1dec28"
33
+ "gitHead": "9a1b104864c08f3e35b009f191889e3308e3eeb0"
34
34
  }