@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.
@@ -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(obj)
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
- const config = this.config.indexes[name]
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
- //console.log("OTHER READ NEXT", otherReaderNext && otherReaderNext.reader && otherReaderNext.reader.prefix,
343
- // otherReaderNext && otherReaderNext.key)
344
- const readEnd = (otherReaderNext && otherReaderNext.key) // Read to next other reader key
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 = new 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}`)
@@ -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
@@ -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) throw new Error("observer not found")
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.59",
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.9",
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.20",
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.2.34",
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
  }