@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.
|
|
74
|
-
this.
|
|
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,
|
|
149
|
-
this.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
this.
|
|
185
|
-
if(
|
|
186
|
-
|
|
187
|
-
|
|
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.
|
|
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.
|
|
30
|
-
"@live-change/flatten-interval-tree": "^0.9.
|
|
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": "
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
+
})
|