@live-change/db-store-level 0.9.227 → 0.9.228
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/.node-version +1 -0
- package/lib/Store.js +373 -94
- package/package.json +6 -9
- package/tests/count-observable.js +138 -0
- package/tests/limited-range-observable.js +7 -15
- package/tests/limited-reverse-range-observable.js +8 -16
- package/tests/object-observable.js +20 -17
- package/tests/prefix-isolation.js +50 -0
- package/tests/range-observable.js +8 -13
- package/tests/reverse-range-observable.js +8 -13
- package/tests/store-non-reactive.js +43 -14
- package/tests/store-stat.js +33 -0
- package/tests/utils.js +21 -0
package/.node-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20.20.2
|
package/lib/Store.js
CHANGED
|
@@ -1,7 +1,64 @@
|
|
|
1
1
|
import IntervalTreeLib from '@live-change/flatten-interval-tree'
|
|
2
|
-
const IntervalTree = IntervalTreeLib.default
|
|
2
|
+
const IntervalTree = IntervalTreeLib.default ?? IntervalTreeLib
|
|
3
3
|
import ReactiveDao from "@live-change/dao"
|
|
4
4
|
|
|
5
|
+
function isNotFound(err) {
|
|
6
|
+
if(!err) return false
|
|
7
|
+
if(err.notFound) return true
|
|
8
|
+
if(err.code === 'LEVEL_NOT_FOUND') return true
|
|
9
|
+
if(err.name === 'NotFoundError') return true
|
|
10
|
+
const text = String(err.message || err)
|
|
11
|
+
return /notfound/i.test(text)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function keyToString(key) {
|
|
15
|
+
if(key == null) return ''
|
|
16
|
+
if(typeof key === 'string') return key
|
|
17
|
+
if(Buffer.isBuffer(key)) return key.toString('utf8')
|
|
18
|
+
return String(key)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function valueToString(value) {
|
|
22
|
+
if(value == null) return null
|
|
23
|
+
if(typeof value === 'string') return value
|
|
24
|
+
if(Buffer.isBuffer(value)) return value.toString('utf8')
|
|
25
|
+
return String(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseValue(value) {
|
|
29
|
+
const s = valueToString(value)
|
|
30
|
+
if(s == null || s === '') return null
|
|
31
|
+
return JSON.parse(s)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function prefixEnd(prefix) {
|
|
35
|
+
if(!prefix) return undefined
|
|
36
|
+
const buf = Buffer.from(prefix, 'utf8')
|
|
37
|
+
for(let i = buf.length - 1; i >= 0; i--) {
|
|
38
|
+
if(buf[i] < 255) {
|
|
39
|
+
buf[i]++
|
|
40
|
+
return buf.slice(0, i + 1).toString('utf8')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return prefix + '\xFF'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function iteratorNext(iterator) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
iterator.next((err, key, value) => {
|
|
49
|
+
if(err) return reject(err)
|
|
50
|
+
if(key === undefined && value === undefined) return resolve(null)
|
|
51
|
+
resolve({ key, value })
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function iteratorEnd(iterator) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
iterator.end(err => err ? reject(err) : resolve())
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
5
62
|
class ObjectObservable extends ReactiveDao.ObservableValue {
|
|
6
63
|
constructor(store, key) {
|
|
7
64
|
super()
|
|
@@ -88,7 +145,6 @@ class RangeObservable extends ReactiveDao.ObservableList {
|
|
|
88
145
|
|
|
89
146
|
async putObject(object, oldObject) {
|
|
90
147
|
await this.readPromise
|
|
91
|
-
//console.log("OBSERVABLE PUT OBJECT", object)
|
|
92
148
|
const id = object.id
|
|
93
149
|
if(this.range.gt && !(id > this.range.gt)) return
|
|
94
150
|
if(this.range.lt && !(id < this.range.lt)) return
|
|
@@ -118,7 +174,7 @@ class RangeObservable extends ReactiveDao.ObservableList {
|
|
|
118
174
|
this.fireObservers('putByField', 'id', id, object, true, oldObject)
|
|
119
175
|
return
|
|
120
176
|
} else if(this.list[i].id > id) {
|
|
121
|
-
if(i == this.list.length - 1) return
|
|
177
|
+
if(i == this.list.length - 1) return
|
|
122
178
|
this.list.splice(i + 1, 0, object)
|
|
123
179
|
this.fireObservers('putByField', 'id', id, object, true, oldObject)
|
|
124
180
|
const popped = this.list.pop()
|
|
@@ -160,6 +216,7 @@ class RangeObservable extends ReactiveDao.ObservableList {
|
|
|
160
216
|
}
|
|
161
217
|
|
|
162
218
|
async deleteObject(object) {
|
|
219
|
+
if(!object) return
|
|
163
220
|
await this.readPromise
|
|
164
221
|
const id = object.id
|
|
165
222
|
if(this.range.gt && !(id > this.range.gt)) return
|
|
@@ -192,7 +249,7 @@ class RangeObservable extends ReactiveDao.ObservableList {
|
|
|
192
249
|
this.changesStream = null
|
|
193
250
|
|
|
194
251
|
this.store.rangeObservables.delete(this.rangeKey)
|
|
195
|
-
|
|
252
|
+
this.store.rangeObservablesTree.remove(this.rangeDescr, this)
|
|
196
253
|
}
|
|
197
254
|
|
|
198
255
|
respawn() {
|
|
@@ -210,22 +267,194 @@ class RangeObservable extends ReactiveDao.ObservableList {
|
|
|
210
267
|
}
|
|
211
268
|
}
|
|
212
269
|
|
|
270
|
+
class CountObservable extends ReactiveDao.ObservableValue {
|
|
271
|
+
constructor(store, range) {
|
|
272
|
+
super()
|
|
273
|
+
this.store = store
|
|
274
|
+
this.range = range
|
|
275
|
+
|
|
276
|
+
this.disposed = false
|
|
277
|
+
this.ready = false
|
|
278
|
+
this.respawnId = 0
|
|
279
|
+
this.refillId = 0
|
|
280
|
+
this.refillPromise = null
|
|
281
|
+
|
|
282
|
+
this.forward = null
|
|
283
|
+
|
|
284
|
+
this.rangeKey = JSON.stringify(this.range)
|
|
285
|
+
this.rangeDescr = [ this.range.gt || this.range.gte || '', this.range.lt || this.range.lte || '\xFF\xFF\xFF\xFF']
|
|
286
|
+
|
|
287
|
+
this.readPromise = this.startReading()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async startReading() {
|
|
291
|
+
this.store.countObservables.set(this.rangeKey, this)
|
|
292
|
+
this.store.rangeObservablesTree.insert(this.rangeDescr, this)
|
|
293
|
+
this.set(await this.store.countGet(this.range))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async putObject(object, oldObject) {
|
|
297
|
+
const id = object.id
|
|
298
|
+
if(this.range.gt && !(id > this.range.gt)) return
|
|
299
|
+
if(this.range.lt && !(id < this.range.lt)) return
|
|
300
|
+
await this.readPromise
|
|
301
|
+
if(this.range.limit) {
|
|
302
|
+
this.set(await this.store.countGet(this.range))
|
|
303
|
+
} else {
|
|
304
|
+
if(object && !oldObject) {
|
|
305
|
+
this.set(this.value + 1)
|
|
306
|
+
} else if(!object && oldObject) {
|
|
307
|
+
this.set(this.value - 1)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async deleteObject(object) {
|
|
313
|
+
if(!object) return
|
|
314
|
+
const id = object.id
|
|
315
|
+
if(this.range.gt && !(id > this.range.gt)) return
|
|
316
|
+
if(this.range.lt && !(id < this.range.lt)) return
|
|
317
|
+
this.set(this.value - 1)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
dispose() {
|
|
321
|
+
if(this.forward) {
|
|
322
|
+
this.forward.unobserve(this)
|
|
323
|
+
this.forward = null
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.disposed = true
|
|
328
|
+
this.respawnId++
|
|
329
|
+
this.changesStream = null
|
|
330
|
+
|
|
331
|
+
this.store.countObservables.delete(this.rangeKey)
|
|
332
|
+
this.store.rangeObservablesTree.remove(this.rangeDescr, this)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
respawn() {
|
|
336
|
+
const existingObservable = this.store.countObservables.get(JSON.stringify(this.range))
|
|
337
|
+
if(existingObservable) {
|
|
338
|
+
this.forward = existingObservable
|
|
339
|
+
this.forward.observe(this)
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
this.respawnId++
|
|
344
|
+
this.ready = false
|
|
345
|
+
this.disposed = false
|
|
346
|
+
this.startReading()
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
213
350
|
class Store {
|
|
214
|
-
constructor(
|
|
215
|
-
this.
|
|
351
|
+
constructor(down, options = {}) {
|
|
352
|
+
this.down = down
|
|
353
|
+
this.prefix = options.prefix || ''
|
|
354
|
+
this.prefixEnd = this.prefix ? prefixEnd(this.prefix) : undefined
|
|
216
355
|
this.objectObservables = new Map()
|
|
217
356
|
this.rangeObservables = new Map()
|
|
357
|
+
this.countObservables = new Map()
|
|
218
358
|
this.rangeObservablesTree = new IntervalTree()
|
|
219
359
|
this.locks = new Map()
|
|
220
360
|
}
|
|
221
361
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
362
|
+
async ready() {
|
|
363
|
+
if(this.down && this.down._opened) await this.down._opened
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
encodeKey(id) {
|
|
367
|
+
return this.prefix + id
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
decodeKey(key) {
|
|
371
|
+
const s = keyToString(key)
|
|
372
|
+
if(this.prefix && s.startsWith(this.prefix)) return s.slice(this.prefix.length)
|
|
373
|
+
return s
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
iteratorRange(range, extra = {}) {
|
|
377
|
+
const opts = {
|
|
378
|
+
reverse: !!range.reverse,
|
|
379
|
+
keyAsBuffer: false,
|
|
380
|
+
valueAsBuffer: false,
|
|
381
|
+
...extra
|
|
382
|
+
}
|
|
383
|
+
if(range.limit != null) opts.limit = range.limit
|
|
384
|
+
|
|
385
|
+
if(range.gt != null) opts.gt = this.encodeKey(range.gt)
|
|
386
|
+
else if(range.gte != null) opts.gte = this.encodeKey(range.gte)
|
|
387
|
+
else if(this.prefix) opts.gte = this.prefix
|
|
388
|
+
|
|
389
|
+
if(range.lt != null) opts.lt = this.encodeKey(range.lt)
|
|
390
|
+
else if(range.lte != null) opts.lte = this.encodeKey(range.lte)
|
|
391
|
+
else if(this.prefixEnd) opts.lt = this.prefixEnd
|
|
392
|
+
|
|
393
|
+
return opts
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async iterate(opts, onItem) {
|
|
397
|
+
await this.ready()
|
|
398
|
+
const iterator = this.down.iterator(opts)
|
|
399
|
+
try {
|
|
400
|
+
while(true) {
|
|
401
|
+
const pair = await iteratorNext(iterator)
|
|
402
|
+
if(!pair) break
|
|
403
|
+
await onItem(pair)
|
|
404
|
+
}
|
|
405
|
+
} finally {
|
|
406
|
+
await iteratorEnd(iterator)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
downGet(key) {
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
this.down.get(key, (err, value) => {
|
|
413
|
+
if(err) {
|
|
414
|
+
if(isNotFound(err)) return resolve(null)
|
|
415
|
+
return reject(err)
|
|
416
|
+
}
|
|
417
|
+
resolve(value)
|
|
418
|
+
})
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
downPut(key, value) {
|
|
423
|
+
return new Promise((resolve, reject) => {
|
|
424
|
+
this.down.put(key, value, err => err ? reject(err) : resolve())
|
|
226
425
|
})
|
|
227
426
|
}
|
|
228
427
|
|
|
428
|
+
downDel(key) {
|
|
429
|
+
return new Promise((resolve, reject) => {
|
|
430
|
+
this.down.del(key, err => {
|
|
431
|
+
if(err && !isNotFound(err)) return reject(err)
|
|
432
|
+
resolve()
|
|
433
|
+
})
|
|
434
|
+
})
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
downBatch(ops) {
|
|
438
|
+
if(!ops.length) return Promise.resolve()
|
|
439
|
+
return new Promise((resolve, reject) => {
|
|
440
|
+
this.down.batch(ops, err => err ? reject(err) : resolve())
|
|
441
|
+
})
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
downClear(opts) {
|
|
445
|
+
return new Promise((resolve, reject) => {
|
|
446
|
+
this.down.clear(opts, err => err ? reject(err) : resolve())
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async objectGet(key) {
|
|
451
|
+
if(!key) throw new Error("key is required")
|
|
452
|
+
await this.ready()
|
|
453
|
+
const raw = await this.downGet(this.encodeKey(key))
|
|
454
|
+
if(raw == null) return null
|
|
455
|
+
return parseValue(raw)
|
|
456
|
+
}
|
|
457
|
+
|
|
229
458
|
objectObservable(key) {
|
|
230
459
|
let observable = this.objectObservables.get(key)
|
|
231
460
|
if(observable) return observable
|
|
@@ -233,23 +462,16 @@ class Store {
|
|
|
233
462
|
return observable
|
|
234
463
|
}
|
|
235
464
|
|
|
236
|
-
rangeGet(range) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
reject(err)
|
|
245
|
-
})
|
|
246
|
-
.on('close', function () {
|
|
247
|
-
reject('closed')
|
|
248
|
-
})
|
|
249
|
-
.on('end', function () {
|
|
250
|
-
resolve(data)
|
|
251
|
-
})
|
|
465
|
+
async rangeGet(range) {
|
|
466
|
+
if(!range) throw new Error("range not defined")
|
|
467
|
+
const data = []
|
|
468
|
+
await this.iterate(this.iteratorRange(range, { keys: true, values: true }), ({ key, value }) => {
|
|
469
|
+
const id = this.decodeKey(key)
|
|
470
|
+
const object = parseValue(value)
|
|
471
|
+
if(object && object.id == null) object.id = id
|
|
472
|
+
data.push(object)
|
|
252
473
|
})
|
|
474
|
+
return data
|
|
253
475
|
}
|
|
254
476
|
|
|
255
477
|
rangeObservable(range) {
|
|
@@ -260,96 +482,153 @@ class Store {
|
|
|
260
482
|
}
|
|
261
483
|
|
|
262
484
|
async rangeDelete(range, options = {}) {
|
|
485
|
+
if(!range) throw new Error("range not defined")
|
|
263
486
|
const keysOnly = options.keysOnly === true
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
487
|
+
const rows = []
|
|
488
|
+
await this.iterate(
|
|
489
|
+
this.iteratorRange(range, { keys: true, values: !keysOnly }),
|
|
490
|
+
({ key, value }) => {
|
|
491
|
+
const id = this.decodeKey(key)
|
|
492
|
+
rows.push({
|
|
493
|
+
id,
|
|
494
|
+
encoded: this.encodeKey(id),
|
|
495
|
+
object: keysOnly ? { id } : parseValue(value)
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
await this.downBatch(rows.map(row => ({ type: 'del', key: row.encoded })))
|
|
500
|
+
for(const row of rows) {
|
|
501
|
+
const objectObservable = this.objectObservables.get(row.id)
|
|
502
|
+
if(objectObservable) objectObservable.set(null)
|
|
503
|
+
const rangeObservables = this.rangeObservablesTree.search([row.id, row.id])
|
|
504
|
+
for(const rangeObservable of rangeObservables) {
|
|
505
|
+
await rangeObservable.deleteObject(row.object || { id: row.id })
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
count: rows.length,
|
|
510
|
+
last: rows.length ? rows[rows.length - 1].id : undefined
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async countGet(range) {
|
|
515
|
+
if(!range) throw new Error("range not defined")
|
|
516
|
+
let count = 0
|
|
517
|
+
await this.iterate(this.iteratorRange(range, { keys: true, values: false }), () => {
|
|
518
|
+
count++
|
|
292
519
|
})
|
|
520
|
+
return count
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
countObservable(range) {
|
|
524
|
+
let observable = this.countObservables.get(JSON.stringify(range))
|
|
525
|
+
if(observable) return observable
|
|
526
|
+
observable = new CountObservable(this, range)
|
|
527
|
+
return observable
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async clear() {
|
|
531
|
+
await this.ready()
|
|
532
|
+
const opts = {}
|
|
533
|
+
if(this.prefix) {
|
|
534
|
+
opts.gte = this.prefix
|
|
535
|
+
if(this.prefixEnd) opts.lt = this.prefixEnd
|
|
536
|
+
}
|
|
537
|
+
await this.downClear(opts)
|
|
293
538
|
}
|
|
294
539
|
|
|
295
540
|
async put(object) {
|
|
296
541
|
const id = object.id
|
|
542
|
+
if(typeof id != 'string') throw new Error(`ID is not string: ${JSON.stringify(id)}`)
|
|
543
|
+
if(!id) throw new Error("ID must not be empty string!")
|
|
297
544
|
let lock
|
|
298
|
-
while(lock = this.locks.get(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
545
|
+
while(lock = this.locks.get(id)) await lock
|
|
546
|
+
const updateLock = (async () => {
|
|
547
|
+
try {
|
|
548
|
+
await this.ready()
|
|
549
|
+
const raw = await this.downGet(this.encodeKey(id))
|
|
550
|
+
const oldObject = raw == null ? null : parseValue(raw)
|
|
551
|
+
await this.downPut(this.encodeKey(id), JSON.stringify(object))
|
|
552
|
+
const objectObservable = this.objectObservables.get(id)
|
|
553
|
+
if(objectObservable) objectObservable.set(object, oldObject)
|
|
554
|
+
const rangeObservables = this.rangeObservablesTree.search([id, id])
|
|
555
|
+
for(const rangeObservable of rangeObservables) {
|
|
556
|
+
if(rangeObservable.rangeDescr[0] > id || rangeObservable.rangeDescr[1] < id) {
|
|
557
|
+
console.error("TREE LEAKING", "ID", id, "IS OUT OF", rangeObservable.rangeDescr)
|
|
558
|
+
continue
|
|
559
|
+
}
|
|
560
|
+
await rangeObservable.putObject(object, oldObject)
|
|
561
|
+
}
|
|
562
|
+
return oldObject
|
|
563
|
+
} finally {
|
|
305
564
|
this.locks.delete(id)
|
|
306
|
-
throw new Error("id must not be empty")
|
|
307
565
|
}
|
|
308
|
-
await this.level.put(id, object)
|
|
309
|
-
const objectObservable = this.objectObservables.get(id)
|
|
310
|
-
if (objectObservable) objectObservable.set(object, oldObject)
|
|
311
|
-
const rangeObservables = this.rangeObservablesTree.search([id, id])
|
|
312
|
-
for (const rangeObservable of rangeObservables) {
|
|
313
|
-
rangeObservable.putObject(object, oldObject)
|
|
314
|
-
}
|
|
315
|
-
this.locks.delete(id)
|
|
316
|
-
return oldObject
|
|
317
566
|
})()
|
|
318
567
|
this.locks.set(id, updateLock)
|
|
319
568
|
return await updateLock
|
|
320
569
|
}
|
|
321
570
|
|
|
322
571
|
async delete(id) {
|
|
323
|
-
let
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
572
|
+
let lock
|
|
573
|
+
while(lock = this.locks.get(id)) await lock
|
|
574
|
+
const deleteLock = (async () => {
|
|
575
|
+
try {
|
|
576
|
+
await this.ready()
|
|
577
|
+
const raw = await this.downGet(this.encodeKey(id))
|
|
578
|
+
const object = raw == null ? null : parseValue(raw)
|
|
579
|
+
if(!object) return null
|
|
580
|
+
await this.downDel(this.encodeKey(id))
|
|
581
|
+
const objectObservable = this.objectObservables.get(id)
|
|
582
|
+
if(objectObservable) objectObservable.set(null)
|
|
583
|
+
const rangeObservables = this.rangeObservablesTree.search([id, id])
|
|
584
|
+
for(const rangeObservable of rangeObservables) {
|
|
585
|
+
await rangeObservable.deleteObject(object)
|
|
586
|
+
}
|
|
587
|
+
return object
|
|
588
|
+
} finally {
|
|
329
589
|
this.locks.delete(id)
|
|
330
|
-
return null
|
|
331
590
|
}
|
|
332
|
-
await this.level.del(id)
|
|
333
|
-
const objectObservable = this.objectObservables.get(id)
|
|
334
|
-
if(objectObservable) objectObservable.set(null)
|
|
335
|
-
const rangeObservables = this.rangeObservablesTree.search([id, id])
|
|
336
|
-
for(const rangeObservable of rangeObservables) {
|
|
337
|
-
rangeObservable.deleteObject(object)
|
|
338
|
-
}
|
|
339
|
-
this.locks.delete(id)
|
|
340
|
-
return object
|
|
341
591
|
})()
|
|
342
592
|
this.locks.set(id, deleteLock)
|
|
343
|
-
|
|
344
|
-
return result
|
|
593
|
+
return await deleteLock
|
|
345
594
|
}
|
|
346
595
|
|
|
347
596
|
stat() {
|
|
348
|
-
return {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
597
|
+
return new Promise(async (resolve) => {
|
|
598
|
+
try {
|
|
599
|
+
await this.ready()
|
|
600
|
+
if(typeof this.down.approximateSize !== 'function') {
|
|
601
|
+
return resolve({
|
|
602
|
+
available: false,
|
|
603
|
+
entryCount: null,
|
|
604
|
+
usedBytes: null
|
|
605
|
+
})
|
|
606
|
+
}
|
|
607
|
+
const start = this.prefix || '\x00'
|
|
608
|
+
const end = this.prefixEnd || '\xFF'
|
|
609
|
+
this.down.approximateSize(start, end, (err, size) => {
|
|
610
|
+
if(err) {
|
|
611
|
+
resolve({
|
|
612
|
+
available: false,
|
|
613
|
+
entryCount: null,
|
|
614
|
+
usedBytes: null
|
|
615
|
+
})
|
|
616
|
+
return
|
|
617
|
+
}
|
|
618
|
+
resolve({
|
|
619
|
+
available: true,
|
|
620
|
+
entryCount: null,
|
|
621
|
+
usedBytes: size
|
|
622
|
+
})
|
|
623
|
+
})
|
|
624
|
+
} catch(e) {
|
|
625
|
+
resolve({
|
|
626
|
+
available: false,
|
|
627
|
+
entryCount: null,
|
|
628
|
+
usedBytes: null
|
|
629
|
+
})
|
|
630
|
+
}
|
|
631
|
+
})
|
|
353
632
|
}
|
|
354
633
|
}
|
|
355
634
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/db-store-level",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.228",
|
|
4
4
|
"description": "Database with observable data for live queries",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "NODE_ENV=test tape tests/*"
|
|
7
|
+
"test": "NODE_ENV=test tape tests/*observable.js tests/store-*.js tests/prefix-isolation.js"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -22,17 +22,14 @@
|
|
|
22
22
|
"type": "module",
|
|
23
23
|
"homepage": "https://github.com/live-change/live-change-stack",
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"encoding-down": "^7.1.0",
|
|
26
25
|
"leveldown": "^6.1.0",
|
|
27
|
-
"
|
|
28
|
-
"memdown": "^6.1.1",
|
|
26
|
+
"p-queue": "^8.1.1",
|
|
29
27
|
"rimraf": "^5.0.5",
|
|
30
|
-
"subleveldown": "^6.0.1",
|
|
31
28
|
"tape": "^5.7.4"
|
|
32
29
|
},
|
|
33
30
|
"dependencies": {
|
|
34
|
-
"@live-change/dao": "^0.9.
|
|
35
|
-
"@live-change/flatten-interval-tree": "^0.9.
|
|
31
|
+
"@live-change/dao": "^0.9.228",
|
|
32
|
+
"@live-change/flatten-interval-tree": "^0.9.228"
|
|
36
33
|
},
|
|
37
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "fd47aa55100c5a25c3f52c899a79a663421037ff"
|
|
38
35
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import test from 'tape'
|
|
2
|
+
import Store from '../lib/Store.js'
|
|
3
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
4
|
+
import PQueue from 'p-queue'
|
|
5
|
+
|
|
6
|
+
const testQueue = new PQueue({ concurrency: 1 })
|
|
7
|
+
function queued(fn) {
|
|
8
|
+
return async t => {
|
|
9
|
+
return await testQueue.add(() => fn(t))
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const dbPath = `./test.co.db`
|
|
14
|
+
|
|
15
|
+
test("store count observable", t => {
|
|
16
|
+
t.plan(5)
|
|
17
|
+
|
|
18
|
+
let down
|
|
19
|
+
let store
|
|
20
|
+
|
|
21
|
+
t.test("create store", async t => {
|
|
22
|
+
t.plan(1)
|
|
23
|
+
down = await openTestDown(dbPath)
|
|
24
|
+
store = new Store(down, { prefix: 't\x00' })
|
|
25
|
+
t.pass('store created')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
t.test("put objects", async t => {
|
|
29
|
+
t.plan(1)
|
|
30
|
+
await store.put({ v: 1, id: 'a' })
|
|
31
|
+
await store.put({ v: 3, id: 'c' })
|
|
32
|
+
t.pass('objects written')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
let nextValueResolve
|
|
36
|
+
let gotNextValue
|
|
37
|
+
const getNextValue = () => {
|
|
38
|
+
if(gotNextValue) {
|
|
39
|
+
gotNextValue = false
|
|
40
|
+
return countObservable.value
|
|
41
|
+
}
|
|
42
|
+
return new Promise((resolve, reject) => nextValueResolve = resolve)
|
|
43
|
+
}
|
|
44
|
+
let countObservable
|
|
45
|
+
const countObserver = (signal, value, ...rest) => {
|
|
46
|
+
console.log("SIGNAL", signal, value, ...rest)
|
|
47
|
+
gotNextValue = true
|
|
48
|
+
if(nextValueResolve) nextValueResolve(countObservable.value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
t.test("observe count [a,z]", async t => {
|
|
52
|
+
t.plan(6)
|
|
53
|
+
countObservable = store.countObservable({ gte: 'a', lte: 'z' })
|
|
54
|
+
countObservable.observe(countObserver)
|
|
55
|
+
let values = await getNextValue()
|
|
56
|
+
t.deepEqual(values, 2, 'count value' )
|
|
57
|
+
|
|
58
|
+
t.test("remove object 'a' from observed count", queued(async t => {
|
|
59
|
+
t.plan(1)
|
|
60
|
+
await store.delete('a')
|
|
61
|
+
let values = await getNextValue()
|
|
62
|
+
t.deepEqual(values, 1, 'count value' )
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
t.test("add object 'a' to observed count", queued(async t => {
|
|
66
|
+
t.plan(1)
|
|
67
|
+
await store.put({ id: 'a', v: 4 })
|
|
68
|
+
let values = await getNextValue()
|
|
69
|
+
t.deepEqual(values, 2, 'count value' )
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
t.test("add object 'b' to observed count", queued(async t => {
|
|
73
|
+
t.plan(1)
|
|
74
|
+
await store.put({ id: 'b', v: 5 })
|
|
75
|
+
let values = await getNextValue()
|
|
76
|
+
t.deepEqual(values, 3, 'count value' )
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
t.test("add object 'd' to observed count", queued(async t => {
|
|
80
|
+
t.plan(1)
|
|
81
|
+
await store.put({ id: 'd', v: 6 })
|
|
82
|
+
let values = await getNextValue()
|
|
83
|
+
t.deepEqual(values, 4, 'count value' )
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
t.test("unobserve count", queued(async t => {
|
|
87
|
+
t.plan(1)
|
|
88
|
+
countObservable.unobserve(countObserver)
|
|
89
|
+
t.pass('unobserved')
|
|
90
|
+
}))
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
t.test("observe count (a,d)", async t => {
|
|
94
|
+
t.plan(6)
|
|
95
|
+
countObservable = store.countObservable({ gt: 'a', lt: 'd' })
|
|
96
|
+
countObservable.observe(countObserver)
|
|
97
|
+
let values = await getNextValue()
|
|
98
|
+
t.deepEqual(values, 2, 'count value' )
|
|
99
|
+
|
|
100
|
+
t.test("remove object 'd' outside observed count", queued(async t => {
|
|
101
|
+
t.plan(1)
|
|
102
|
+
await store.delete('d')
|
|
103
|
+
t.pass('deleted')
|
|
104
|
+
}))
|
|
105
|
+
|
|
106
|
+
t.test("remove object 'a' outside observed count", queued(async t => {
|
|
107
|
+
t.plan(1)
|
|
108
|
+
await store.delete('a')
|
|
109
|
+
t.pass('deleted')
|
|
110
|
+
}))
|
|
111
|
+
|
|
112
|
+
t.test("add object 'ab' to observed count", queued(async t => {
|
|
113
|
+
t.plan(1)
|
|
114
|
+
await store.put({ id: 'ab', v: 7 })
|
|
115
|
+
let values = await getNextValue()
|
|
116
|
+
t.deepEqual(values, 3, 'count value' )
|
|
117
|
+
}))
|
|
118
|
+
|
|
119
|
+
t.test("remove object 'ab' from observed count", queued(async t => {
|
|
120
|
+
t.plan(1)
|
|
121
|
+
await store.delete('ab')
|
|
122
|
+
let values = await getNextValue()
|
|
123
|
+
t.deepEqual(values, 2, 'count value' )
|
|
124
|
+
}))
|
|
125
|
+
|
|
126
|
+
t.test("unobserve count", queued(async t => {
|
|
127
|
+
t.plan(1)
|
|
128
|
+
countObservable.unobserve(countObserver)
|
|
129
|
+
t.pass('unobserved')
|
|
130
|
+
}))
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
t.test("close and remove database", queued(async t => {
|
|
134
|
+
t.plan(1)
|
|
135
|
+
await closeTestDown(down, dbPath)
|
|
136
|
+
t.pass('removed')
|
|
137
|
+
}))
|
|
138
|
+
})
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import test from 'tape'
|
|
2
|
-
import levelup from 'levelup'
|
|
3
|
-
import leveldown from 'leveldown'
|
|
4
|
-
import encoding from 'encoding-down'
|
|
5
|
-
import { rimraf } from 'rimraf'
|
|
6
2
|
import PQueue from 'p-queue'
|
|
7
|
-
|
|
8
3
|
import Store from '../lib/Store.js'
|
|
4
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
9
5
|
|
|
10
6
|
const dbPath = `./test.lro.db`
|
|
11
|
-
rimraf.sync(dbPath)
|
|
12
|
-
const level = levelup(encoding(leveldown(dbPath), { keyEncoding: 'ascii', valueEncoding: 'json' }))
|
|
13
7
|
|
|
14
8
|
const testQueue = new PQueue({ concurrency: 1 })
|
|
15
9
|
function queued(fn) {
|
|
@@ -21,11 +15,13 @@ function queued(fn) {
|
|
|
21
15
|
test("store range observable", t => {
|
|
22
16
|
t.plan(5)
|
|
23
17
|
|
|
18
|
+
let down
|
|
24
19
|
let store
|
|
25
20
|
|
|
26
21
|
t.test("create store", async t => {
|
|
27
22
|
t.plan(1)
|
|
28
|
-
|
|
23
|
+
down = await openTestDown(dbPath)
|
|
24
|
+
store = new Store(down, { prefix: 't\x00' })
|
|
29
25
|
t.pass('store created')
|
|
30
26
|
})
|
|
31
27
|
|
|
@@ -71,7 +67,6 @@ test("store range observable", t => {
|
|
|
71
67
|
console.error("TEST STARTED")
|
|
72
68
|
await store.delete('b_3')
|
|
73
69
|
objects.splice(3, 1)
|
|
74
|
-
await getNextValue() // one shorter
|
|
75
70
|
let values = await getNextValue()
|
|
76
71
|
t.deepEqual(values, objects.slice(0, 5), 'range value' )
|
|
77
72
|
}))
|
|
@@ -106,7 +101,6 @@ test("store range observable", t => {
|
|
|
106
101
|
t.plan(1)
|
|
107
102
|
await store.delete('b_32')
|
|
108
103
|
objects.splice(4, 1)
|
|
109
|
-
await getNextValue()
|
|
110
104
|
let values = await getNextValue()
|
|
111
105
|
t.deepEqual(values, objects.slice(0, 5), 'range value' )
|
|
112
106
|
}))
|
|
@@ -157,7 +151,6 @@ test("store range observable", t => {
|
|
|
157
151
|
t.plan(1)
|
|
158
152
|
await store.delete('b_12')
|
|
159
153
|
objects.splice(2, 1)
|
|
160
|
-
await getNextValue()
|
|
161
154
|
let values = await getNextValue()
|
|
162
155
|
t.deepEqual(values, objects.slice(1,4) , 'range value' )
|
|
163
156
|
}))
|
|
@@ -169,10 +162,9 @@ test("store range observable", t => {
|
|
|
169
162
|
}))
|
|
170
163
|
})
|
|
171
164
|
|
|
172
|
-
t.test("close and remove database", async t => {
|
|
165
|
+
t.test("close and remove database", queued(async t => {
|
|
173
166
|
t.plan(1)
|
|
174
|
-
await
|
|
175
|
-
await rimraf(dbPath)
|
|
167
|
+
await closeTestDown(down, dbPath)
|
|
176
168
|
t.pass('removed')
|
|
177
|
-
})
|
|
169
|
+
}))
|
|
178
170
|
})
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import test from 'tape'
|
|
2
|
-
import levelup from 'levelup'
|
|
3
|
-
import leveldown from 'leveldown'
|
|
4
|
-
import encoding from 'encoding-down'
|
|
5
|
-
import { rimraf } from 'rimraf'
|
|
6
|
-
|
|
7
2
|
import Store from '../lib/Store.js'
|
|
3
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
4
|
+
import PQueue from 'p-queue'
|
|
8
5
|
|
|
9
6
|
const dbPath = `./test.lrro.db`
|
|
10
|
-
rimraf.sync(dbPath)
|
|
11
|
-
const level = levelup(encoding(leveldown(dbPath), { keyEncoding: 'ascii', valueEncoding: 'json' }))
|
|
12
7
|
|
|
13
|
-
import PQueue from 'p-queue'
|
|
14
8
|
const testQueue = new PQueue({ concurrency: 1 })
|
|
15
9
|
function queued(fn) {
|
|
16
10
|
return async t => {
|
|
@@ -21,11 +15,13 @@ function queued(fn) {
|
|
|
21
15
|
test("store range observable", t => {
|
|
22
16
|
t.plan(5)
|
|
23
17
|
|
|
18
|
+
let down
|
|
24
19
|
let store
|
|
25
20
|
|
|
26
21
|
t.test("create store", async t => {
|
|
27
22
|
t.plan(1)
|
|
28
|
-
|
|
23
|
+
down = await openTestDown(dbPath)
|
|
24
|
+
store = new Store(down, { prefix: 't\x00' })
|
|
29
25
|
t.pass('store created')
|
|
30
26
|
})
|
|
31
27
|
|
|
@@ -70,7 +66,6 @@ test("store range observable", t => {
|
|
|
70
66
|
t.plan(1)
|
|
71
67
|
await store.delete('b_6')
|
|
72
68
|
objects.splice(3, 1)
|
|
73
|
-
await getNextValue() // one shorter
|
|
74
69
|
let values = await getNextValue()
|
|
75
70
|
t.deepEqual(values, objects.slice(0, 5), 'range value' )
|
|
76
71
|
}))
|
|
@@ -105,7 +100,6 @@ test("store range observable", t => {
|
|
|
105
100
|
t.plan(1)
|
|
106
101
|
await store.delete('b_58')
|
|
107
102
|
objects.splice(4, 1)
|
|
108
|
-
await getNextValue()
|
|
109
103
|
let values = await getNextValue()
|
|
110
104
|
t.deepEqual(values, objects.slice(0, 5), 'range value' )
|
|
111
105
|
}))
|
|
@@ -156,7 +150,6 @@ test("store range observable", t => {
|
|
|
156
150
|
t.plan(1)
|
|
157
151
|
await store.delete('b_48')
|
|
158
152
|
objects.splice(6, 1)
|
|
159
|
-
await getNextValue()
|
|
160
153
|
let values = await getNextValue()
|
|
161
154
|
t.deepEqual(values, objects.slice(6,9) , 'range value' )
|
|
162
155
|
}))
|
|
@@ -168,10 +161,9 @@ test("store range observable", t => {
|
|
|
168
161
|
}))
|
|
169
162
|
})
|
|
170
163
|
|
|
171
|
-
t.test("close and remove database", async t => {
|
|
164
|
+
t.test("close and remove database", queued(async t => {
|
|
172
165
|
t.plan(1)
|
|
173
|
-
await
|
|
174
|
-
await rimraf(dbPath)
|
|
166
|
+
await closeTestDown(down, dbPath)
|
|
175
167
|
t.pass('removed')
|
|
176
|
-
})
|
|
168
|
+
}))
|
|
177
169
|
})
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import test from 'tape'
|
|
2
|
-
import levelup from 'levelup'
|
|
3
|
-
import leveldown from 'leveldown'
|
|
4
|
-
import encoding from 'encoding-down'
|
|
5
|
-
import { rimraf } from 'rimraf'
|
|
6
|
-
|
|
7
2
|
import Store from '../lib/Store.js'
|
|
3
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
4
|
+
import PQueue from 'p-queue'
|
|
5
|
+
|
|
6
|
+
const testQueue = new PQueue({ concurrency: 1 })
|
|
7
|
+
function queued(fn) {
|
|
8
|
+
return async t => {
|
|
9
|
+
return await testQueue.add(() => fn(t))
|
|
10
|
+
}
|
|
11
|
+
}
|
|
8
12
|
|
|
9
13
|
const dbPath = `./test.oo.db`
|
|
10
|
-
rimraf.sync(dbPath)
|
|
11
|
-
const level = levelup(encoding(leveldown(dbPath), { keyEncoding: 'ascii', valueEncoding: 'json' }))
|
|
12
14
|
|
|
13
15
|
test("store object observable", t => {
|
|
14
16
|
t.plan(3)
|
|
15
17
|
|
|
18
|
+
let down
|
|
16
19
|
let store
|
|
17
20
|
|
|
18
21
|
t.test("create store", async t => {
|
|
19
22
|
t.plan(1)
|
|
20
|
-
|
|
23
|
+
down = await openTestDown(dbPath)
|
|
24
|
+
store = new Store(down, { prefix: 't\x00' })
|
|
21
25
|
t.pass('store created')
|
|
22
26
|
})
|
|
23
27
|
|
|
@@ -45,25 +49,24 @@ test("store object observable", t => {
|
|
|
45
49
|
let value = await getNextValue()
|
|
46
50
|
t.deepEqual(value, null, 'found null')
|
|
47
51
|
|
|
48
|
-
t.test("add object A", async t => {
|
|
52
|
+
t.test("add object A", queued(async t => {
|
|
49
53
|
t.plan(1)
|
|
50
54
|
await store.put({ id: 'A', a: 1 })
|
|
51
55
|
let value = await getNextValue()
|
|
52
56
|
t.deepEqual(value, { id: 'A', a: 1 } , 'found object' )
|
|
53
|
-
})
|
|
57
|
+
}))
|
|
54
58
|
|
|
55
|
-
t.test("delete object A", async t => {
|
|
59
|
+
t.test("delete object A", queued(async t => {
|
|
56
60
|
t.plan(1)
|
|
57
61
|
await store.delete('A')
|
|
58
62
|
let value = await getNextValue()
|
|
59
63
|
t.deepEqual(value, null , 'found null' )
|
|
60
|
-
})
|
|
64
|
+
}))
|
|
61
65
|
})
|
|
62
66
|
|
|
63
|
-
t.test("close and remove database", async t => {
|
|
67
|
+
t.test("close and remove database", queued(async t => {
|
|
64
68
|
t.plan(1)
|
|
65
|
-
await
|
|
66
|
-
await rimraf(dbPath)
|
|
69
|
+
await closeTestDown(down, dbPath)
|
|
67
70
|
t.pass('removed')
|
|
68
|
-
})
|
|
69
|
-
})
|
|
71
|
+
}))
|
|
72
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import test from 'tape'
|
|
2
|
+
import Store from '../lib/Store.js'
|
|
3
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
4
|
+
import PQueue from 'p-queue'
|
|
5
|
+
|
|
6
|
+
const testQueue = new PQueue({ concurrency: 1 })
|
|
7
|
+
function queued(fn) {
|
|
8
|
+
return async t => {
|
|
9
|
+
return await testQueue.add(() => fn(t))
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const dbPath = `./test.px.db`
|
|
14
|
+
|
|
15
|
+
test("store prefix isolation and clear", t => {
|
|
16
|
+
t.plan(3)
|
|
17
|
+
|
|
18
|
+
let down
|
|
19
|
+
let storeA
|
|
20
|
+
let storeB
|
|
21
|
+
|
|
22
|
+
t.test("create stores", queued(async t => {
|
|
23
|
+
t.plan(1)
|
|
24
|
+
down = await openTestDown(dbPath)
|
|
25
|
+
storeA = new Store(down, { prefix: 'a\x00' })
|
|
26
|
+
storeB = new Store(down, { prefix: 'b\x00' })
|
|
27
|
+
t.pass('stores created')
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
t.test("prefix isolation", queued(async t => {
|
|
31
|
+
t.plan(8)
|
|
32
|
+
await storeA.put({ id: 'k', v: 1 })
|
|
33
|
+
await storeB.put({ id: 'k', v: 2 })
|
|
34
|
+
t.deepEqual(await storeA.objectGet('k'), { id: 'k', v: 1 }, 'store A own value')
|
|
35
|
+
t.deepEqual(await storeB.objectGet('k'), { id: 'k', v: 2 }, 'store B own value')
|
|
36
|
+
t.deepEqual(await storeA.rangeGet({}), [{ id: 'k', v: 1 }], 'store A range')
|
|
37
|
+
t.deepEqual(await storeB.rangeGet({}), [{ id: 'k', v: 2 }], 'store B range')
|
|
38
|
+
t.equal(await storeA.countGet({}), 1, 'store A count')
|
|
39
|
+
t.equal(await storeB.countGet({}), 1, 'store B count')
|
|
40
|
+
await storeA.clear()
|
|
41
|
+
t.equal(await storeA.objectGet('k'), null, 'store A cleared')
|
|
42
|
+
t.deepEqual(await storeB.objectGet('k'), { id: 'k', v: 2 }, 'store B intact after A clear')
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
t.test("close and remove database", queued(async t => {
|
|
46
|
+
t.plan(1)
|
|
47
|
+
await closeTestDown(down, dbPath)
|
|
48
|
+
t.pass('removed')
|
|
49
|
+
}))
|
|
50
|
+
})
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import test from 'tape'
|
|
2
|
-
import levelup from 'levelup'
|
|
3
|
-
import leveldown from 'leveldown'
|
|
4
|
-
import encoding from 'encoding-down'
|
|
5
|
-
import { rimraf } from 'rimraf'
|
|
6
|
-
|
|
7
2
|
import Store from '../lib/Store.js'
|
|
8
|
-
|
|
3
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
9
4
|
import PQueue from 'p-queue'
|
|
5
|
+
|
|
10
6
|
const testQueue = new PQueue({ concurrency: 1 })
|
|
11
7
|
function queued(fn) {
|
|
12
8
|
return async t => {
|
|
@@ -15,17 +11,17 @@ function queued(fn) {
|
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
const dbPath = `./test.ro.db`
|
|
18
|
-
rimraf.sync(dbPath)
|
|
19
|
-
const level = levelup(encoding(leveldown(dbPath), { keyEncoding: 'ascii', valueEncoding: 'json' }))
|
|
20
14
|
|
|
21
15
|
test("store range observable", t => {
|
|
22
16
|
t.plan(5)
|
|
23
17
|
|
|
18
|
+
let down
|
|
24
19
|
let store
|
|
25
20
|
|
|
26
21
|
t.test("create store", async t => {
|
|
27
22
|
t.plan(1)
|
|
28
|
-
|
|
23
|
+
down = await openTestDown(dbPath)
|
|
24
|
+
store = new Store(down, { prefix: 't\x00' })
|
|
29
25
|
t.pass('store created')
|
|
30
26
|
})
|
|
31
27
|
|
|
@@ -136,10 +132,9 @@ test("store range observable", t => {
|
|
|
136
132
|
|
|
137
133
|
})
|
|
138
134
|
|
|
139
|
-
t.test("close and remove database", async t => {
|
|
135
|
+
t.test("close and remove database", queued(async t => {
|
|
140
136
|
t.plan(1)
|
|
141
|
-
await
|
|
142
|
-
await rimraf(dbPath)
|
|
137
|
+
await closeTestDown(down, dbPath)
|
|
143
138
|
t.pass('removed')
|
|
144
|
-
})
|
|
139
|
+
}))
|
|
145
140
|
})
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import test from 'tape'
|
|
2
|
-
import levelup from 'levelup'
|
|
3
|
-
import leveldown from 'leveldown'
|
|
4
|
-
import encoding from 'encoding-down'
|
|
5
|
-
import { rimraf } from 'rimraf'
|
|
6
|
-
|
|
7
2
|
import Store from '../lib/Store.js'
|
|
8
|
-
|
|
3
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
9
4
|
import PQueue from 'p-queue'
|
|
5
|
+
|
|
10
6
|
const testQueue = new PQueue({ concurrency: 1 })
|
|
11
7
|
function queued(fn) {
|
|
12
8
|
return async t => {
|
|
@@ -15,17 +11,17 @@ function queued(fn) {
|
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
const dbPath = `./test.rro.db`
|
|
18
|
-
rimraf.sync(dbPath)
|
|
19
|
-
const level = levelup(encoding(leveldown(dbPath), { keyEncoding: 'ascii', valueEncoding: 'json' }))
|
|
20
14
|
|
|
21
15
|
test("store range observable", t => {
|
|
22
16
|
t.plan(5)
|
|
23
17
|
|
|
18
|
+
let down
|
|
24
19
|
let store
|
|
25
20
|
|
|
26
21
|
t.test("create store", async t => {
|
|
27
22
|
t.plan(1)
|
|
28
|
-
|
|
23
|
+
down = await openTestDown(dbPath)
|
|
24
|
+
store = new Store(down, { prefix: 't\x00' })
|
|
29
25
|
t.pass('store created')
|
|
30
26
|
})
|
|
31
27
|
|
|
@@ -135,10 +131,9 @@ test("store range observable", t => {
|
|
|
135
131
|
}))
|
|
136
132
|
})
|
|
137
133
|
|
|
138
|
-
t.test("close and remove database", async t => {
|
|
134
|
+
t.test("close and remove database", queued(async t => {
|
|
139
135
|
t.plan(1)
|
|
140
|
-
await
|
|
141
|
-
await rimraf(dbPath)
|
|
136
|
+
await closeTestDown(down, dbPath)
|
|
142
137
|
t.pass('removed')
|
|
143
|
-
})
|
|
138
|
+
}))
|
|
144
139
|
})
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import test from 'tape'
|
|
2
|
-
import levelup from 'levelup'
|
|
3
|
-
import leveldown from 'leveldown'
|
|
4
|
-
import encoding from 'encoding-down'
|
|
5
|
-
import { rimraf } from 'rimraf'
|
|
6
|
-
|
|
7
2
|
import Store from '../lib/Store.js'
|
|
8
|
-
|
|
3
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
9
4
|
import PQueue from 'p-queue'
|
|
5
|
+
|
|
10
6
|
const testQueue = new PQueue({ concurrency: 1 })
|
|
11
7
|
function queued(fn) {
|
|
12
8
|
return async t => {
|
|
@@ -15,22 +11,22 @@ function queued(fn) {
|
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
const dbPath = `./test.nr.db`
|
|
18
|
-
rimraf.sync(dbPath)
|
|
19
|
-
const level = levelup(encoding(leveldown(dbPath), { keyEncoding: 'ascii', valueEncoding: 'json' }))
|
|
20
14
|
|
|
21
15
|
test("store non-reactive properties", t => {
|
|
22
16
|
t.plan(3)
|
|
23
17
|
|
|
18
|
+
let down
|
|
24
19
|
let store
|
|
25
20
|
|
|
26
21
|
t.test("create store", async t => {
|
|
27
22
|
t.plan(1)
|
|
28
|
-
|
|
23
|
+
down = await openTestDown(dbPath)
|
|
24
|
+
store = new Store(down, { prefix: 't\x00' })
|
|
29
25
|
t.pass('store created')
|
|
30
26
|
})
|
|
31
27
|
|
|
32
28
|
t.test("non reactive operations", async t => {
|
|
33
|
-
t.plan(
|
|
29
|
+
t.plan(20)
|
|
34
30
|
|
|
35
31
|
t.test("put value", queued(async t => {
|
|
36
32
|
t.plan(1)
|
|
@@ -57,24 +53,48 @@ test("store non-reactive properties", t => {
|
|
|
57
53
|
t.deepEqual(values, [ { v: 1, id: 'a' }, { v: 2, id: 'b' }, { v: 3, id: 'c' } ], 'range read' )
|
|
58
54
|
}))
|
|
59
55
|
|
|
56
|
+
t.test("count range [a,c]", queued(async t => {
|
|
57
|
+
t.plan(1)
|
|
58
|
+
let values = await store.countGet({ gte: 'a', lte: 'c' })
|
|
59
|
+
t.deepEqual(values, 3, 'range count' )
|
|
60
|
+
}))
|
|
61
|
+
|
|
60
62
|
t.test("get reverse range [c,a]", queued(async t => {
|
|
61
63
|
t.plan(1)
|
|
62
64
|
let values = await store.rangeGet({ gte: 'a', lte: 'c', reverse: true })
|
|
63
65
|
t.deepEqual(values, [ { v: 3, id: 'c' }, { v: 2, id: 'b' }, { v: 1, id: 'a' } ], 'range read' )
|
|
64
66
|
}))
|
|
65
67
|
|
|
68
|
+
t.test("count reverse range [a,c]", queued(async t => {
|
|
69
|
+
t.plan(1)
|
|
70
|
+
let values = await store.countGet({ gte: 'a', lte: 'c', reverse: true })
|
|
71
|
+
t.deepEqual(values, 3, 'range count' )
|
|
72
|
+
}))
|
|
73
|
+
|
|
66
74
|
t.test("get range [a,c] with limit 2", queued(async t => {
|
|
67
75
|
t.plan(1)
|
|
68
76
|
let values = await store.rangeGet({ gte: 'a', lte: 'c', limit: 2 })
|
|
69
77
|
t.deepEqual(values, [ { v: 1, id: 'a' }, { v: 2, id: 'b' } ], 'range read' )
|
|
70
78
|
}))
|
|
71
79
|
|
|
80
|
+
t.test("count range [a,c] with limit 2", queued(async t => {
|
|
81
|
+
t.plan(1)
|
|
82
|
+
let values = await store.countGet({ gte: 'a', lte: 'c', limit: 2 })
|
|
83
|
+
t.deepEqual(values, 2, 'range read' )
|
|
84
|
+
}))
|
|
85
|
+
|
|
72
86
|
t.test("get reverse range [c,a] with limit 2", queued(async t => {
|
|
73
87
|
t.plan(1)
|
|
74
88
|
let values = await store.rangeGet({ gte: 'a', lte: 'c', reverse: true, limit: 2 })
|
|
75
89
|
t.deepEqual(values, [ { v: 3, id: 'c' }, { v: 2, id: 'b' } ], 'range read' )
|
|
76
90
|
}))
|
|
77
91
|
|
|
92
|
+
t.test("get reverse count [c,a] with limit 2", queued(async t => {
|
|
93
|
+
t.plan(1)
|
|
94
|
+
let values = await store.countGet({ gte: 'a', lte: 'c', reverse: true, limit: 2 })
|
|
95
|
+
t.deepEqual(values, 2, 'range read' )
|
|
96
|
+
}))
|
|
97
|
+
|
|
78
98
|
t.test("get range (a,c]", queued(async t => {
|
|
79
99
|
t.plan(1)
|
|
80
100
|
let values = await store.rangeGet({ gt: 'a', lte: 'c' })
|
|
@@ -122,12 +142,21 @@ test("store non-reactive properties", t => {
|
|
|
122
142
|
let values = await store.rangeGet({ gte: 'a', lte: 'c' })
|
|
123
143
|
t.deepEqual(values, [ { v: 1, id: 'a' }, { v: 3, id: 'c' } ], 'range read' )
|
|
124
144
|
}))
|
|
145
|
+
|
|
146
|
+
t.test("rangeDelete keysOnly", queued(async t => {
|
|
147
|
+
t.plan(3)
|
|
148
|
+
await store.put({ id: 'd', v: 4 })
|
|
149
|
+
await store.put({ id: 'e', v: 5 })
|
|
150
|
+
const result = await store.rangeDelete({ gte: 'd', lt: 'f' }, { keysOnly: true })
|
|
151
|
+
t.equal(result.count, 2, 'deleted 2 keys without reading values')
|
|
152
|
+
t.equal(await store.objectGet('d'), null, 'd gone')
|
|
153
|
+
t.equal(await store.objectGet('e'), null, 'e gone')
|
|
154
|
+
}))
|
|
125
155
|
})
|
|
126
156
|
|
|
127
|
-
t.test("close and remove database", async t => {
|
|
157
|
+
t.test("close and remove database", queued(async t => {
|
|
128
158
|
t.plan(1)
|
|
129
|
-
await
|
|
130
|
-
await rimraf(dbPath)
|
|
159
|
+
await closeTestDown(down, dbPath)
|
|
131
160
|
t.pass('removed')
|
|
132
|
-
})
|
|
161
|
+
}))
|
|
133
162
|
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import test from 'tape'
|
|
2
|
+
import Store from '../lib/Store.js'
|
|
3
|
+
import { openTestDown, closeTestDown } from './utils.js'
|
|
4
|
+
|
|
5
|
+
const dbPath = `./test.st.db`
|
|
6
|
+
|
|
7
|
+
test("store stat", t => {
|
|
8
|
+
t.plan(3)
|
|
9
|
+
|
|
10
|
+
let down
|
|
11
|
+
let store
|
|
12
|
+
|
|
13
|
+
t.test("create store", async t => {
|
|
14
|
+
t.plan(1)
|
|
15
|
+
down = await openTestDown(dbPath)
|
|
16
|
+
store = new Store(down, { prefix: 't\x00' })
|
|
17
|
+
t.pass('store created')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
t.test("approximate size after put", async t => {
|
|
21
|
+
t.plan(2)
|
|
22
|
+
await store.put({ id: 'a', v: 1 })
|
|
23
|
+
const s = await store.stat()
|
|
24
|
+
t.equal(s.available, true, 'stat available')
|
|
25
|
+
t.equal(typeof s.usedBytes, 'number', 'usedBytes is a number')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
t.test("close and remove database", async t => {
|
|
29
|
+
t.plan(1)
|
|
30
|
+
await closeTestDown(down, dbPath)
|
|
31
|
+
t.pass('removed')
|
|
32
|
+
})
|
|
33
|
+
})
|
package/tests/utils.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createRequire } from 'module'
|
|
2
|
+
import { rimraf } from 'rimraf'
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url)
|
|
5
|
+
const leveldown = require('leveldown')
|
|
6
|
+
|
|
7
|
+
export async function openTestDown(dbPath) {
|
|
8
|
+
await rimraf(dbPath)
|
|
9
|
+
const down = leveldown(dbPath)
|
|
10
|
+
await new Promise((resolve, reject) => {
|
|
11
|
+
down.open({ createIfMissing: true }, err => err ? reject(err) : resolve())
|
|
12
|
+
})
|
|
13
|
+
return down
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function closeTestDown(down, dbPath) {
|
|
17
|
+
await new Promise((resolve, reject) => {
|
|
18
|
+
down.close(err => err ? reject(err) : resolve())
|
|
19
|
+
})
|
|
20
|
+
await rimraf(dbPath)
|
|
21
|
+
}
|