@live-change/db-store-indexeddb 0.9.229 → 0.9.231

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 CHANGED
@@ -70,8 +70,8 @@ class RangeObservable extends ReactiveDao.ObservableList {
70
70
  this.disposed = false
71
71
  this.ready = false
72
72
  this.respawnId = 0
73
- this.refillId = 0
74
- this.refillPromise = null
73
+ this.refillQueue = []
74
+ this.refillRunning = false
75
75
 
76
76
  this.forward = null
77
77
 
@@ -145,48 +145,78 @@ class RangeObservable extends ReactiveDao.ObservableList {
145
145
  }
146
146
  }
147
147
 
148
- refillDeleted(from, limit) {
149
- this.refillId ++
150
- const refillId = this.refillId
151
- let promise = (async () => {
152
- let req
153
- if(!this.range.reverse) {
154
- req = { gt: from, limit }
155
- if(this.range.lt) req.lt = this.range.lt
156
- if(this.range.lte) req.lte = this.range.lte
157
- } else {
158
- req = { lt: from, limit, reverse: true }
159
- if(this.range.gt) req.gt = this.range.gt
160
- if(this.range.gte) req.gte = this.range.gte
148
+ refillDeleted(from, need) {
149
+ if(this.disposed) return Promise.resolve()
150
+ return new Promise((resolve, reject) => {
151
+ this.refillQueue.push({ from, need, resolve, reject })
152
+ this._drainRefill()
153
+ })
154
+ }
155
+
156
+ async _syncWindow() {
157
+ if(this.disposed || !this.range.limit) return
158
+ const reverse = !!this.range.reverse
159
+ const truth = await this.store.rangeGet(this.range)
160
+ if(this.disposed) return
161
+ const same = truth.length === this.list.length &&
162
+ truth.every((o, i) => this.list[i] && this.list[i].id === o.id)
163
+ if(same) return
164
+ const truthIds = new Set(truth.map(o => o.id))
165
+ for(const obj of this.list.slice()) {
166
+ if(!truthIds.has(obj.id)) this.removeByField('id', obj.id, obj)
167
+ }
168
+ for(const object of truth) {
169
+ this.putByField('id', object.id, object, reverse)
170
+ }
171
+ while(this.list.length > truth.length) {
172
+ const popped = this.list.pop()
173
+ this.fireObservers('removeByField', 'id', popped.id, popped)
174
+ }
175
+ while(this.range.limit && this.list.length > this.range.limit) {
176
+ const popped = this.list.pop()
177
+ this.fireObservers('removeByField', 'id', popped.id, popped)
178
+ }
179
+ }
180
+
181
+ async _drainRefill() {
182
+ if(this.refillRunning) return
183
+ this.refillRunning = true
184
+ const allJobs = []
185
+ try {
186
+ while(this.refillQueue.length) {
187
+ allJobs.push(...this.refillQueue.splice(0))
188
+ await this._syncWindow()
161
189
  }
162
- const objects = await this.store.rangeGet(req)
163
- if(this.refillId != refillId) return this.refillPromise
164
- for(let object of objects) this.push(object)
165
- this.refillPromise = null
166
- })()
167
- this.refillPromise = promise
168
- return promise
190
+ await this._syncWindow()
191
+ } catch(err) {
192
+ this.refillRunning = false
193
+ for(const job of allJobs) job.reject(err)
194
+ for(const job of this.refillQueue.splice(0)) job.reject(err)
195
+ return
196
+ }
197
+ this.refillRunning = false
198
+ for(const job of allJobs) job.resolve()
199
+ if(this.refillQueue.length) this._drainRefill()
169
200
  }
170
201
 
171
202
  async deleteObject(object) {
172
- if(!object) return;
203
+ if(!object) return
173
204
  await this.readPromise
205
+ if(this.disposed) return
174
206
  const id = object.id
175
207
  if(this.range.gt && !(id > this.range.gt)) return
176
208
  if(this.range.lt && !(id < this.range.lt)) return
177
- if(this.range.limit && (this.list.length == this.range.limit || this.refillPromise)) {
178
- let exists
179
- let last
180
- for(let obj of this.list) {
181
- if(obj.id == id) exists = obj
182
- else last = obj
183
- }
184
- this.removeByField('id', id, object)
185
- if(exists) await this.refillDeleted(
186
- last && last.id || (this.reverse ? this.range.lt || this.range.lte : this.range.gt || this.range.gte),
187
- this.range.limit - this.list.length)
188
- } else {
189
- this.removeByField('id', id, object)
209
+ let exists = false, last = null
210
+ for(let obj of this.list) {
211
+ if(obj.id == id) exists = obj
212
+ else last = obj
213
+ }
214
+ this.removeByField('id', id, object)
215
+ if(exists && this.range.limit) {
216
+ const need = this.range.limit - this.list.length
217
+ if(need > 0) await this.refillDeleted(
218
+ last && last.id || (this.range.reverse ? this.range.lt || this.range.lte : this.range.gt || this.range.gte),
219
+ need)
190
220
  }
191
221
  }
192
222
 
@@ -198,6 +228,8 @@ class RangeObservable extends ReactiveDao.ObservableList {
198
228
  }
199
229
 
200
230
  this.disposed = true
231
+ const pending = this.refillQueue.splice(0)
232
+ for(const job of pending) job.resolve()
201
233
  this.respawnId++
202
234
  this.changesStream = null
203
235
 
@@ -216,6 +248,8 @@ class RangeObservable extends ReactiveDao.ObservableList {
216
248
  this.respawnId++
217
249
  this.ready = false
218
250
  this.disposed = false
251
+ this.refillQueue = []
252
+ this.refillRunning = false
219
253
  this.startReading()
220
254
  }
221
255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/db-store-indexeddb",
3
- "version": "0.9.229",
3
+ "version": "0.9.231",
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.7.4"
27
27
  },
28
28
  "dependencies": {
29
- "@live-change/dao": "^0.9.229",
30
- "@live-change/flatten-interval-tree": "^0.9.229",
29
+ "@live-change/dao": "^0.9.231",
30
+ "@live-change/flatten-interval-tree": "^0.9.231",
31
31
  "broadcast-channel": "^4.2.0"
32
32
  },
33
- "gitHead": "10de6af051ea567a0ae917d34ac08378da5465ea"
33
+ "gitHead": "d49b1369964dafe82610664dd56255b6458d0cae"
34
34
  }
@@ -4,6 +4,13 @@ require('fake-indexeddb/auto.js')
4
4
  const idb = require('idb')
5
5
  const Store = require('../lib/Store.js')
6
6
 
7
+
8
+ function assertLimit(t, obs) {
9
+ var limit = obs.range && obs.range.limit
10
+ t.ok(!limit || obs.list.length <= limit, 'within limit')
11
+ t.equal(new Set(obs.list.map(function (o) { return o.id })).size, obs.list.length, 'no dup')
12
+ }
13
+
7
14
  test("store limited range observable", t => {
8
15
  t.plan(5)
9
16
 
@@ -47,60 +54,66 @@ test("store limited range observable", t => {
47
54
  }
48
55
 
49
56
  t.test("observe range [b_0,b_9]", t => {
50
- t.plan(7)
57
+ t.plan(9)
51
58
  rangeObservable = store.rangeObservable({ gte: 'b_0', lte: 'b_9', limit: 5 })
52
59
  rangeObservable.observe(rangeObserver)
53
60
 
54
61
  let values
55
62
 
56
63
  t.test("get values", async t => {
57
- t.plan(1)
64
+ t.plan(3)
58
65
  values = await getNextValue()
59
66
  t.deepEqual(values, objects.slice(0,5), 'range value')
67
+ assertLimit(t, rangeObservable)
60
68
  })
61
69
 
62
70
  t.test("remove object 'b_3' from observed range", async t => {
63
- t.plan(1)
71
+ t.plan(3)
64
72
  await store.delete('b_3')
65
73
  objects.splice(3, 1)
66
74
  await getNextValue() // one shorter
67
75
  let values = await getNextValue()
68
76
  t.deepEqual(values, objects.slice(0, 5), 'range value' )
77
+ assertLimit(t, rangeObservable)
69
78
  })
70
79
 
71
80
  t.test("add object 'b_3' to observed range", async t => {
72
- t.plan(1)
81
+ t.plan(3)
73
82
  const obj = { id: 'b_3', v: 10 }
74
83
  await store.put(obj)
75
84
  objects.splice(3, 0, obj)
76
85
  let values = await getNextValue()
77
86
  t.deepEqual(values, objects.slice(0, 5), 'range value' )
87
+ assertLimit(t, rangeObservable)
78
88
  })
79
89
 
80
90
  t.test("add object 'b_32' to observed range", async t => {
81
- t.plan(1)
91
+ t.plan(3)
82
92
  const obj = { id: 'b_32', v: 11 }
83
93
  await store.put(obj)
84
94
  objects.splice(4, 0, obj)
85
95
  let values = await getNextValue()
86
96
  t.deepEqual(values, objects.slice(0, 5), 'range value' )
97
+ assertLimit(t, rangeObservable)
87
98
  })
88
99
 
89
100
  t.test("add object 'b_35' outside observed range limits", async t => {
90
- t.plan(1)
101
+ t.plan(3)
91
102
  const obj = { id: 'b_35', v: 11 }
92
103
  await store.put(obj)
93
104
  objects.splice(5, 0, obj)
94
105
  t.pass('added')
106
+ assertLimit(t, rangeObservable)
95
107
  })
96
108
 
97
109
  t.test("delete object 'b_32' so b_35 will enter into range limits", async t => {
98
- t.plan(1)
110
+ t.plan(3)
99
111
  await store.delete('b_32')
100
112
  objects.splice(4, 1)
101
113
  await getNextValue()
102
114
  let values = await getNextValue()
103
115
  t.deepEqual(values, objects.slice(0, 5), 'range value' )
116
+ assertLimit(t, rangeObservable)
104
117
  })
105
118
 
106
119
  t.test("unobserve range", async t => {
@@ -111,7 +124,7 @@ test("store limited range observable", t => {
111
124
  })
112
125
 
113
126
  t.test("observe range (b_0,b_5)", t => {
114
- t.plan(7)
127
+ t.plan(9)
115
128
  gotNextValue = false
116
129
  rangeObservable = store.rangeObservable({ gt: 'b_0', lt: 'b_5', limit: 3 })
117
130
  rangeObservable.observe(rangeObserver)
@@ -119,45 +132,51 @@ test("store limited range observable", t => {
119
132
  let values
120
133
 
121
134
  t.test("get values", async t => {
122
- t.plan(1)
135
+ t.plan(3)
123
136
  values = await getNextValue()
124
137
  t.deepEqual(values, objects.slice(1,4), 'range value')
138
+ assertLimit(t, rangeObservable)
125
139
  })
126
140
 
127
141
  t.test("remove object 'b_0' outside observed range", async t => {
128
- t.plan(1)
142
+ t.plan(3)
129
143
  await store.delete('b_0')
130
144
  t.pass('deleted')
145
+ assertLimit(t, rangeObservable)
131
146
  })
132
147
 
133
148
  t.test("remove object 'b_5' outside observed range", async t => {
134
- t.plan(1)
149
+ t.plan(3)
135
150
  await store.delete('b_5')
136
151
  t.pass('deleted')
152
+ assertLimit(t, rangeObservable)
137
153
  })
138
154
 
139
155
  t.test("remove object 'b_4' outside observed range limits", async t => {
140
- t.plan(1)
156
+ t.plan(3)
141
157
  await store.delete('b_4')
142
158
  t.pass('deleted')
159
+ assertLimit(t, rangeObservable)
143
160
  })
144
161
 
145
162
  t.test("add object 'b_12' to observed range", async t => {
146
- t.plan(1)
163
+ t.plan(3)
147
164
  const obj = { id: 'b_12', v: 7 }
148
165
  await store.put(obj)
149
166
  objects.splice(2, 0, obj)
150
167
  let values = await getNextValue()
151
168
  t.deepEqual(values, objects.slice(1,4), 'range value' )
169
+ assertLimit(t, rangeObservable)
152
170
  })
153
171
 
154
172
  t.test("remove object 'b_12' from observed range", async t => {
155
- t.plan(1)
173
+ t.plan(3)
156
174
  await store.delete('b_12')
157
175
  objects.splice(2, 1)
158
176
  await getNextValue()
159
177
  let values = await getNextValue()
160
178
  t.deepEqual(values, objects.slice(1,4) , 'range value' )
179
+ assertLimit(t, rangeObservable)
161
180
  })
162
181
 
163
182
  t.test("unobserve range", async t => {
@@ -4,6 +4,13 @@ require('fake-indexeddb/auto.js')
4
4
  const idb = require('idb')
5
5
  const Store = require('../lib/Store.js')
6
6
 
7
+
8
+ function assertLimit(t, obs) {
9
+ var limit = obs.range && obs.range.limit
10
+ t.ok(!limit || obs.list.length <= limit, 'within limit')
11
+ t.equal(new Set(obs.list.map(function (o) { return o.id })).size, obs.list.length, 'no dup')
12
+ }
13
+
7
14
  test("store limited reverse range observable", t => {
8
15
  t.plan(5)
9
16
 
@@ -47,60 +54,66 @@ test("store limited reverse range observable", t => {
47
54
  }
48
55
 
49
56
  t.test("observe reverse range [b_9,b_0]", t => {
50
- t.plan(7)
57
+ t.plan(9)
51
58
  rangeObservable = store.rangeObservable({ gte: 'b_0', lte: 'b_9', limit: 5, reverse: true })
52
59
  rangeObservable.observe(rangeObserver)
53
60
 
54
61
  let values
55
62
 
56
63
  t.test("get values", async t => {
57
- t.plan(1)
64
+ t.plan(3)
58
65
  values = await getNextValue()
59
66
  t.deepEqual(values, objects.slice(0,5), 'range value')
67
+ assertLimit(t, rangeObservable)
60
68
  })
61
69
 
62
70
  t.test("remove object 'b_6' from observed range", async t => {
63
- t.plan(1)
71
+ t.plan(3)
64
72
  await store.delete('b_6')
65
73
  objects.splice(3, 1)
66
74
  await getNextValue() // one shorter
67
75
  let values = await getNextValue()
68
76
  t.deepEqual(values, objects.slice(0, 5), 'range value' )
77
+ assertLimit(t, rangeObservable)
69
78
  })
70
79
 
71
80
  t.test("add object 'b_6' to observed range", async t => {
72
- t.plan(1)
81
+ t.plan(3)
73
82
  const obj = { id: 'b_6', v: 10 }
74
83
  await store.put(obj)
75
84
  objects.splice(3, 0, obj)
76
85
  let values = await getNextValue()
77
86
  t.deepEqual(values, objects.slice(0, 5), 'range value' )
87
+ assertLimit(t, rangeObservable)
78
88
  })
79
89
 
80
90
  t.test("add object 'b_58' to observed range", async t => {
81
- t.plan(1)
91
+ t.plan(3)
82
92
  const obj = { id: 'b_58', v: 11 }
83
93
  await store.put(obj)
84
94
  objects.splice(4, 0, obj)
85
95
  let values = await getNextValue()
86
96
  t.deepEqual(values, objects.slice(0, 5), 'range value' )
97
+ assertLimit(t, rangeObservable)
87
98
  })
88
99
 
89
100
  t.test("add object 'b_52' outside observed range limits", async t => {
90
- t.plan(1)
101
+ t.plan(3)
91
102
  const obj = { id: 'b_52', v: 11 }
92
103
  await store.put(obj)
93
104
  objects.splice(5, 0, obj)
94
105
  t.pass('added')
106
+ assertLimit(t, rangeObservable)
95
107
  })
96
108
 
97
109
  t.test("delete object 'b_58' so b_52 will enter into range limits", async t => {
98
- t.plan(1)
110
+ t.plan(3)
99
111
  await store.delete('b_58')
100
112
  objects.splice(4, 1)
101
113
  await getNextValue()
102
114
  let values = await getNextValue()
103
115
  t.deepEqual(values, objects.slice(0, 5), 'range value' )
116
+ assertLimit(t, rangeObservable)
104
117
  })
105
118
 
106
119
  t.test("unobserve range", async t => {
@@ -111,52 +124,58 @@ test("store limited reverse range observable", t => {
111
124
  })
112
125
 
113
126
  t.test("observe reverse range (b_5,b_0)", t => {
114
- t.plan(7)
127
+ t.plan(9)
115
128
  gotNextValue = false
116
129
  rangeObservable = store.rangeObservable({ gt: 'b_0', lt: 'b_5', limit: 3, reverse: true })
117
130
  rangeObservable.observe(rangeObserver)
118
131
 
119
132
  let values
120
133
  t.test("get values", async t => {
121
- t.plan(1)
134
+ t.plan(3)
122
135
  values = await getNextValue()
123
136
  t.deepEqual(values, objects.slice(6,9), 'range value')
137
+ assertLimit(t, rangeObservable)
124
138
  })
125
139
 
126
140
  t.test("remove object 'b_5' outside observed range", async t => {
127
- t.plan(1)
141
+ t.plan(3)
128
142
  await store.delete('b_5')
129
143
  t.pass('deleted')
144
+ assertLimit(t, rangeObservable)
130
145
  })
131
146
 
132
147
  t.test("remove object 'b_0' outside observed range", async t => {
133
- t.plan(1)
148
+ t.plan(3)
134
149
  await store.delete('b_0')
135
150
  t.pass('deleted')
151
+ assertLimit(t, rangeObservable)
136
152
  })
137
153
 
138
154
  t.test("remove object 'b_1' outside observed range limits", async t => {
139
- t.plan(1)
155
+ t.plan(3)
140
156
  await store.delete('b_1')
141
157
  t.pass('deleted')
158
+ assertLimit(t, rangeObservable)
142
159
  })
143
160
 
144
161
  t.test("add object 'b_48' to observed range", async t => {
145
- t.plan(1)
162
+ t.plan(3)
146
163
  const obj = { id: 'b_48', v: 7 }
147
164
  await store.put(obj)
148
165
  objects.splice(6, 0, obj)
149
166
  let values = await getNextValue()
150
167
  t.deepEqual(values, objects.slice(6,9), 'range value' )
168
+ assertLimit(t, rangeObservable)
151
169
  })
152
170
 
153
171
  t.test("remove object 'b_48' from observed range", async t => {
154
- t.plan(1)
172
+ t.plan(3)
155
173
  await store.delete('b_48')
156
174
  objects.splice(6, 1)
157
175
  await getNextValue()
158
176
  let values = await getNextValue()
159
177
  t.deepEqual(values, objects.slice(6,9) , 'range value' )
178
+ assertLimit(t, rangeObservable)
160
179
  })
161
180
 
162
181
  t.test("unobserve range", async t => {
@@ -0,0 +1,9 @@
1
+ // browser-only: this package is CJS in Node; the shared suite is ESM.
2
+ // CI Node skips the real stress (fake-indexeddb). RangeObservable still has
3
+ // the same refill queue fix as the other stores.
4
+ const test = require('tape')
5
+
6
+ test('indexeddb range observable stress (browser-only)', t => {
7
+ t.skip('browser-only store')
8
+ t.end()
9
+ })