@live-change/db 0.3.59 → 0.3.63
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/ChangeStream.js +19 -5
- package/lib/Database.js +32 -18
- package/lib/Index.js +25 -17
- package/lib/ScriptContext.js +4 -3
- package/lib/queryObservable.js +4 -1
- package/package.json +6 -6
package/lib/ChangeStream.js
CHANGED
|
@@ -5,25 +5,29 @@ class ChangeStream {
|
|
|
5
5
|
throw new Error("abstract method - not implemented")
|
|
6
6
|
}
|
|
7
7
|
to(output) {
|
|
8
|
-
this.onChange((obj, oldObj, id, timestamp) => output.change(obj, oldObj, id, timestamp))
|
|
8
|
+
return this.onChange((obj, oldObj, id, timestamp) => output.change(obj, oldObj, id, timestamp))
|
|
9
9
|
}
|
|
10
10
|
filter(func) {
|
|
11
11
|
const pipe = new ChangeStreamPipe()
|
|
12
|
-
this.onChange((obj, oldObj, id, timestamp) =>
|
|
12
|
+
const observerPromise = this.onChange((obj, oldObj, id, timestamp) =>
|
|
13
13
|
pipe.change(obj && func(obj) ? obj : null, oldObj && func(oldObj) ? oldObj : null, id, timestamp))
|
|
14
|
+
pipe.master = this
|
|
15
|
+
pipe.observerPromise = observerPromise
|
|
14
16
|
return pipe
|
|
15
17
|
}
|
|
16
18
|
map(func) {
|
|
17
19
|
const pipe = new ChangeStreamPipe()
|
|
18
|
-
this.onChange((obj, oldObj, id, timestamp) =>
|
|
20
|
+
const observerPromise = this.onChange((obj, oldObj, id, timestamp) =>
|
|
19
21
|
pipe.change(obj && func(obj), oldObj && func(oldObj), id, timestamp))
|
|
22
|
+
pipe.master = this
|
|
23
|
+
pipe.observerPromise = observerPromise
|
|
20
24
|
return pipe
|
|
21
25
|
}
|
|
22
26
|
indexBy(func) {
|
|
23
27
|
const pipe = new ChangeStreamPipe()
|
|
24
|
-
this.onChange((obj, oldObj, id, timestamp) => {
|
|
28
|
+
const observerPromise = this.onChange((obj, oldObj, id, timestamp) => {
|
|
25
29
|
const indList = obj && func(obj)
|
|
26
|
-
const oldIndList = oldObj && func(
|
|
30
|
+
const oldIndList = oldObj && func(oldObj)
|
|
27
31
|
const ind = indList && indList.map(v => JSON.stringify(v)).join(':')+'_'+id
|
|
28
32
|
const oldInd = oldIndList && oldIndList.map(v => JSON.stringify(v)).join(':')+'_'+id
|
|
29
33
|
if(ind == oldInd) return // no index change, ignore
|
|
@@ -34,6 +38,8 @@ class ChangeStream {
|
|
|
34
38
|
pipe.change(null, { id: oldInd, to: id }, oldInd, timestamp)
|
|
35
39
|
}
|
|
36
40
|
})
|
|
41
|
+
pipe.master = this
|
|
42
|
+
pipe.observerPromise = observerPromise
|
|
37
43
|
return pipe
|
|
38
44
|
}
|
|
39
45
|
}
|
|
@@ -45,6 +51,14 @@ class ChangeStreamPipe extends ChangeStream {
|
|
|
45
51
|
}
|
|
46
52
|
onChange(cb) {
|
|
47
53
|
this.callbacks.push(cb)
|
|
54
|
+
return cb
|
|
55
|
+
}
|
|
56
|
+
async unobserve(cb) {
|
|
57
|
+
const cbIndex = this.callbacks.indexOf(cb)
|
|
58
|
+
if(cbIndex == -1) throw new Error("observer not found")
|
|
59
|
+
if(this.callbacks.length == 0) {
|
|
60
|
+
this.master.unobservePromise = await this.observerPromise
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
async change(obj, oldObj, id, timestamp) {
|
|
50
64
|
for(const callback of this.callbacks) await callback(obj, oldObj, id, timestamp)
|
package/lib/Database.js
CHANGED
|
@@ -8,7 +8,7 @@ const getRandomValues = require('get-random-values')
|
|
|
8
8
|
const ReactiveDao = require("@live-change/dao")
|
|
9
9
|
|
|
10
10
|
class Database {
|
|
11
|
-
constructor(config, storeFactory, saveConfig, deleteStore, name) {
|
|
11
|
+
constructor(config, storeFactory, saveConfig, deleteStore, name, createScriptContext) {
|
|
12
12
|
this.name = name
|
|
13
13
|
this.config = {
|
|
14
14
|
tables: {},
|
|
@@ -23,6 +23,7 @@ class Database {
|
|
|
23
23
|
this.tables = new Map()
|
|
24
24
|
this.logs = new Map()
|
|
25
25
|
this.indexes = new Map()
|
|
26
|
+
this.createScriptContext = createScriptContext
|
|
26
27
|
|
|
27
28
|
this.configObservable = new ReactiveDao.ObservableValue(JSON.parse(JSON.stringify(this.config)))
|
|
28
29
|
this.tablesListObservable = new ReactiveDao.ObservableList(Object.keys(this.config.tables))
|
|
@@ -92,7 +93,7 @@ class Database {
|
|
|
92
93
|
this.tablesListObservable.remove(name)
|
|
93
94
|
this.tables.delete(name)
|
|
94
95
|
}
|
|
95
|
-
|
|
96
|
+
|
|
96
97
|
renameTable(name, newName) {
|
|
97
98
|
if(this.config.tables[newName]) throw new Error(`Table ${newName} already exists`)
|
|
98
99
|
const table = this.table(name)
|
|
@@ -226,25 +227,38 @@ class Database {
|
|
|
226
227
|
return summary
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
async openIndex(name) {
|
|
231
|
+
let config = this.config.indexes[name]
|
|
232
|
+
if(!config) {
|
|
233
|
+
console.log("INDEX", name, "NOT EXISTS - WAITING!")
|
|
234
|
+
await new Promise(r => setTimeout(r, 500))
|
|
235
|
+
config = this.config.indexes[name]
|
|
236
|
+
}
|
|
237
|
+
let index, code
|
|
238
|
+
if(!config) throw new Error(`Index ${name} not found`)
|
|
239
|
+
code = config.code
|
|
240
|
+
const params = config.parameters
|
|
241
|
+
index = new Index(this, name, code, params, config)
|
|
242
|
+
try {
|
|
243
|
+
console.log("STARTING INDEX", name)
|
|
244
|
+
await index.startIndex()
|
|
245
|
+
console.log("STARTED INDEX", name)
|
|
246
|
+
} catch(error) {
|
|
247
|
+
console.error("INDEX", name, "ERROR", error, "CODE:\n", code)
|
|
248
|
+
console.error("DELETING INDEX", name)
|
|
249
|
+
delete this.config.indexes[name]
|
|
250
|
+
this.indexesListObservable.remove(name)
|
|
251
|
+
if(this.onAutoRemoveIndex && config) this.onAutoRemoveIndex(name, config.uid)
|
|
252
|
+
await this.saveConfig(this.config)
|
|
253
|
+
throw error
|
|
254
|
+
}
|
|
255
|
+
this.indexes.set(name, index)
|
|
256
|
+
return index
|
|
257
|
+
}
|
|
229
258
|
async index(name) {
|
|
230
259
|
let index = this.indexes.get(name)
|
|
231
260
|
if(!index) {
|
|
232
|
-
|
|
233
|
-
if(!config) throw new Error(`Index ${name} not found`)
|
|
234
|
-
const code = config.code
|
|
235
|
-
const params = config.parameters
|
|
236
|
-
index = new Index(this, name, code, params, config)
|
|
237
|
-
try {
|
|
238
|
-
await index.startIndex()
|
|
239
|
-
} catch(error) {
|
|
240
|
-
console.error("INDEX", name, "ERROR", error, "CODE:\n", index.code)
|
|
241
|
-
console.error("DELETING INDEX", name)
|
|
242
|
-
delete this.config.indexes[name]
|
|
243
|
-
this.indexesListObservable.remove(name)
|
|
244
|
-
if(this.onAutoRemoveIndex) this.onAutoRemoveIndex(name, config.uid)
|
|
245
|
-
await this.saveConfig(this.config)
|
|
246
|
-
throw error
|
|
247
|
-
}
|
|
261
|
+
index = this.openIndex(name)
|
|
248
262
|
this.indexes.set(name, index)
|
|
249
263
|
}
|
|
250
264
|
return index
|
package/lib/Index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const IntervalTree = require('node-interval-tree').default
|
|
2
2
|
const ReactiveDao = require("@live-change/dao")
|
|
3
3
|
const Table = require('./Table.js')
|
|
4
|
-
const ScriptContext = require('./ScriptContext.js')
|
|
5
4
|
const queryGet = require('./queryGet.js')
|
|
6
5
|
const profileLog = require('./profileLog.js')
|
|
7
6
|
const queryObservable = require('./queryObservable.js')
|
|
@@ -64,6 +63,7 @@ class TableReader extends ChangeStream {
|
|
|
64
63
|
this.opLogReader = opLogReader
|
|
65
64
|
this.prefix = prefix
|
|
66
65
|
this.table = table
|
|
66
|
+
Promise.resolve(this.table).then(t=> {if(!t) throw new Error("TABLE NOT FOUND!!!")})
|
|
67
67
|
this.isLog = isLog
|
|
68
68
|
this.objectReaders = new Map()
|
|
69
69
|
this.rangeReaders = new Map()
|
|
@@ -86,7 +86,7 @@ class TableReader extends ChangeStream {
|
|
|
86
86
|
firstFull = 0
|
|
87
87
|
}
|
|
88
88
|
}, 1000)*/
|
|
89
|
-
/* if(this.prefix == 'table_triggers'){
|
|
89
|
+
/* //if(this.prefix == 'table_triggers'){
|
|
90
90
|
let triggersDataJson = ""
|
|
91
91
|
setInterval(() => {
|
|
92
92
|
if(JSON.stringify(this.opLogObservable && this.opLogObservable.list) != triggersDataJson) {
|
|
@@ -222,7 +222,7 @@ class TableReader extends ChangeStream {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
async readTo(endKey) {
|
|
225
|
-
//console.log("RT", endKey, "IN", this.opLogBuffer)
|
|
225
|
+
//if(this.prefix == 'table_triggers') console.log("RT", endKey, "IN", this.opLogBuffer)
|
|
226
226
|
let lastKey = null
|
|
227
227
|
while(this.opLogBuffer[0] && this.opLogBuffer[0].id <= endKey) {
|
|
228
228
|
const next = this.opLogBuffer.shift()
|
|
@@ -231,7 +231,7 @@ class TableReader extends ChangeStream {
|
|
|
231
231
|
await this.change(next, null, next, next.id)
|
|
232
232
|
} else {
|
|
233
233
|
const op = next.operation
|
|
234
|
-
//console.log("HANDLE OP LOG OPERATION", next)
|
|
234
|
+
//if(this.prefix == 'table_triggers') console.log("HANDLE OP LOG OPERATION", next)
|
|
235
235
|
if(op) {
|
|
236
236
|
if(op.type == 'put') {
|
|
237
237
|
await this.change(op.object, op.oldObject, op.object.id, next.id)
|
|
@@ -299,15 +299,16 @@ class OpLogReader {
|
|
|
299
299
|
|
|
300
300
|
handleSignal() {
|
|
301
301
|
if(this.readingMore) {
|
|
302
|
-
//console.log("STORE SIGNAL")
|
|
302
|
+
//if(this.indexName == 'triggers_new') console.log("STORE SIGNAL")
|
|
303
303
|
this.gotSignals = true
|
|
304
304
|
} else {
|
|
305
|
-
//console.log("READ MORE ON SIGNAL")
|
|
305
|
+
//if(this.indexName == 'triggers_new') console.log("READ MORE ON SIGNAL")
|
|
306
306
|
this.readMore()
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
async readMore() {
|
|
310
310
|
this.readingMore = true
|
|
311
|
+
//if(this.indexName == 'triggers_new') console.log("READING TRIGGERS STARTED!")
|
|
311
312
|
do {
|
|
312
313
|
while(true) {
|
|
313
314
|
this.gotSignals = false
|
|
@@ -317,9 +318,9 @@ class OpLogReader {
|
|
|
317
318
|
let possibleNextKeys = await Promise.all(
|
|
318
319
|
this.tableReaders.map(async tr => ({ reader: tr, key: await tr.nextKey() }))
|
|
319
320
|
)
|
|
320
|
-
//console.log("GOT NEXT KEYS")
|
|
321
|
+
//if(this.indexName == 'triggers_new') console.log("GOT NEXT KEYS")
|
|
321
322
|
if(this.disposed) return
|
|
322
|
-
//console.log("POSSIBLE NEXT KEYS", possibleNextKeys.map(({key, reader}) => [reader.prefix, key]))
|
|
323
|
+
//if(this.indexName == 'triggers_new') console.log("POSSIBLE NEXT KEYS", possibleNextKeys.map(({key, reader}) => [reader.prefix, key]))
|
|
323
324
|
if(possibleNextKeys.length == 0) { /// It could happen when oplog is cleared
|
|
324
325
|
return
|
|
325
326
|
}
|
|
@@ -329,9 +330,9 @@ class OpLogReader {
|
|
|
329
330
|
next = possibleKey
|
|
330
331
|
}
|
|
331
332
|
}
|
|
332
|
-
//console.log("NEXT KEY", next && next.reader && next.reader.prefix, next && next.key)
|
|
333
|
+
//if(this.indexName == 'triggers_new') console.log("NEXT KEY", next && next.reader && next.reader.prefix, next && next.key)
|
|
333
334
|
const lastKey = '\xFF\xFF\xFF\xFF'
|
|
334
|
-
//console.log("NEXT", !!next, "KEY", next && next.key, lastKey)
|
|
335
|
+
//if(this.indexName == 'triggers_new') console.log("NEXT", !!next, "KEY", next && next.key, lastKey)
|
|
335
336
|
if(!next || next.key == lastKey) break // nothing to read
|
|
336
337
|
let otherReaderNext = null
|
|
337
338
|
for(const possibleKey of possibleNextKeys) {
|
|
@@ -339,31 +340,38 @@ class OpLogReader {
|
|
|
339
340
|
&& (!otherReaderNext || possibleKey.key < otherReaderNext.key))
|
|
340
341
|
otherReaderNext = possibleKey
|
|
341
342
|
}
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
|
|
343
|
+
//if(this.indexName == 'triggers_new')
|
|
344
|
+
// console.log("OTHER READ NEXT", otherReaderNext && otherReaderNext.reader && otherReaderNext.reader.prefix,
|
|
345
|
+
// otherReaderNext && otherReaderNext.key)
|
|
346
|
+
let readEnd = (otherReaderNext && otherReaderNext.key) // Read to next other reader key
|
|
345
347
|
|| (((''+(now - 1))).padStart(16, '0'))+':' // or to current timestamp
|
|
348
|
+
if(readEnd < next) {
|
|
349
|
+
readEnd = next+'\xff'
|
|
350
|
+
}
|
|
346
351
|
|
|
347
352
|
if((next.key||'') < this.currentKey) {
|
|
348
353
|
//debugger
|
|
349
354
|
console.error("time travel", next.key, this.currentKey)
|
|
350
355
|
//process.exit(1) /// TODO: do something about it!
|
|
351
356
|
}
|
|
352
|
-
//console.log("CKN", this.currentKey, '=>', next.key)
|
|
357
|
+
//if(this.indexName == 'triggers_new') console.log("CKN", this.currentKey, '=>', next.key)
|
|
353
358
|
this.currentKey = next.key
|
|
359
|
+
//if(this.indexName == 'triggers_new') console.log("READ TO", readEnd)
|
|
354
360
|
const readKey = await next.reader.readTo(readEnd)
|
|
361
|
+
//if(this.indexName == 'triggers_new') console.log("READED")
|
|
355
362
|
if(readKey) {
|
|
356
363
|
if((readKey||'') < this.currentKey) {
|
|
357
364
|
//debugger
|
|
358
365
|
console.error("time travel", readKey, this.currentKey)
|
|
359
366
|
//process.exit(1) /// TODO: do something about it!
|
|
360
367
|
}
|
|
361
|
-
//console.log("CKR", this.currentKey, '=>', readKey)
|
|
368
|
+
//if(this.indexName == 'triggers_new') console.log("CKR", this.currentKey, '=>', readKey)
|
|
362
369
|
this.currentKey = readKey
|
|
363
370
|
}
|
|
364
371
|
}
|
|
365
372
|
} while(this.gotSignals)
|
|
366
373
|
this.readingMore = false
|
|
374
|
+
//if(this.indexName == 'triggers_new') console.log("READING TRIGGERS FINISHED!")
|
|
367
375
|
}
|
|
368
376
|
dispose() {
|
|
369
377
|
this.disposed = true
|
|
@@ -393,7 +401,7 @@ class IndexWriter {
|
|
|
393
401
|
this.index.update(id, ops)
|
|
394
402
|
}
|
|
395
403
|
change(obj, oldObj) {
|
|
396
|
-
//console.log("INDEX WRITE", obj, oldObj)
|
|
404
|
+
//if(this.index.name == 'triggers_new') console.log("INDEX WRITE", obj, oldObj)
|
|
397
405
|
if(obj) {
|
|
398
406
|
if(oldObj && oldObj.id != obj.id) {
|
|
399
407
|
this.index.delete(oldObj.id)
|
|
@@ -430,7 +438,7 @@ class Index extends Table {
|
|
|
430
438
|
}
|
|
431
439
|
async startIndex() {
|
|
432
440
|
console.log("EXECUTING INDEX CODE", this.name)
|
|
433
|
-
this.scriptContext =
|
|
441
|
+
this.scriptContext = this.database.createScriptContext({
|
|
434
442
|
/// TODO: script available routines
|
|
435
443
|
})
|
|
436
444
|
const queryFunction = this.scriptContext.run(this.code,`userCode:${this.database.name}/indexes/${this.name}`)
|
package/lib/ScriptContext.js
CHANGED
|
@@ -35,6 +35,7 @@ const defaultContext = {
|
|
|
35
35
|
return new ChangeStreamPipe()
|
|
36
36
|
},
|
|
37
37
|
'performance': require('perf_hooks').performance,
|
|
38
|
+
constructor: null
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
const filenameRE = /scriptFile:(\d+):(\d+)\)$/g
|
|
@@ -47,9 +48,9 @@ class ScriptContext {
|
|
|
47
48
|
}
|
|
48
49
|
this.context = vm.createContext({ ...defaultContext, ...context, ...userContext })
|
|
49
50
|
/* vm.runInContext(`
|
|
50
|
-
(function() {
|
|
51
|
-
const allowed = ${JSON.stringify(nativeGlobals.concat(Object.keys(userContext)))}
|
|
52
|
-
const keys = Object.getOwnPropertyNames(this)
|
|
51
|
+
(function() {
|
|
52
|
+
const allowed = ${JSON.stringify(nativeGlobals.concat(Object.keys(userContext)))}
|
|
53
|
+
const keys = Object.getOwnPropertyNames(this)
|
|
53
54
|
keys.forEach((key) => {
|
|
54
55
|
const item = this[key]
|
|
55
56
|
if(!item) return
|
package/lib/queryObservable.js
CHANGED
|
@@ -91,7 +91,10 @@ class Reader extends ChangeStream {
|
|
|
91
91
|
}
|
|
92
92
|
async unobserve(observer) {
|
|
93
93
|
const index = this.#observers.indexOf(observer)
|
|
94
|
-
if(index == -1)
|
|
94
|
+
if(index == -1) {
|
|
95
|
+
console.error("OBSERVER NOT FOUND", observer)
|
|
96
|
+
throw new Error("observer not found")
|
|
97
|
+
}
|
|
95
98
|
this.#observers.splice(index, 1)
|
|
96
99
|
;(await this.#observable).unobserve(observer)
|
|
97
100
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/db",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.63",
|
|
4
4
|
"description": "Database with observable data for live queries",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,24 +21,24 @@
|
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/live-change/db",
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@live-change/db-store-level": "^0.1.
|
|
24
|
+
"@live-change/db-store-level": "^0.1.14",
|
|
25
25
|
"@live-change/db-store-lmdb": "^0.1.13",
|
|
26
26
|
"encoding-down": "^6.3.0",
|
|
27
27
|
"level-rocksdb": "^4.0.0",
|
|
28
28
|
"leveldown": "^5.6.0",
|
|
29
29
|
"levelup": "^4.4.0",
|
|
30
30
|
"memdown": "^5.1.0",
|
|
31
|
+
"minimist": ">=1.2.3",
|
|
31
32
|
"node-lmdb": "^0.8.0",
|
|
32
33
|
"rimraf": "^3.0.2",
|
|
33
34
|
"rocksdb": "^4.1.0",
|
|
34
|
-
"sockjs": "^0.3.
|
|
35
|
+
"sockjs": "^0.3.21",
|
|
35
36
|
"subleveldown": "^4.1.4",
|
|
36
37
|
"tape": "^4.13.3",
|
|
37
|
-
"websocket-extensions": ">=0.1.4"
|
|
38
|
-
"minimist": ">=1.2.3"
|
|
38
|
+
"websocket-extensions": ">=0.1.4"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@live-change/dao": "^0.
|
|
41
|
+
"@live-change/dao": "^0.3.3",
|
|
42
42
|
"get-random-values": "^1.2.2",
|
|
43
43
|
"node-interval-tree": "^1.3.3"
|
|
44
44
|
}
|