@live-change/framework 0.7.18 → 0.7.19
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/index.js +3 -3
- package/lib/App.js +125 -36
- package/lib/definition/IndexDefinition.js +6 -0
- package/lib/definition/ModelDefinition.js +10 -1
- package/lib/processes/commandExecutor.js +36 -14
- package/lib/processes/eventListener.js +4 -5
- package/lib/processes/triggerExecutor.js +11 -1
- package/lib/runtime/Action.js +2 -2
- package/lib/runtime/Dao.js +6 -6
- package/lib/runtime/Model.js +59 -0
- package/lib/runtime/ReaderModel.js +11 -29
- package/lib/runtime/Service.js +3 -0
- package/lib/updaters/database.js +27 -20
- package/lib/utils/CommandQueue.js +6 -3
- package/lib/utils/EventSourcing.js +4 -2
- package/lib/utils/ExecutionQueue.js +1 -1
- package/package.json +9 -9
package/index.js
CHANGED
|
@@ -3,10 +3,10 @@ const App = require('./lib/App.js')
|
|
|
3
3
|
module.exports = App
|
|
4
4
|
|
|
5
5
|
module.exports.app = () => {
|
|
6
|
-
if(!
|
|
7
|
-
|
|
6
|
+
if(!globalThis.liveChangeFrameworkApp) {
|
|
7
|
+
globalThis.liveChangeFrameworkApp = new App()
|
|
8
8
|
}
|
|
9
|
-
return
|
|
9
|
+
return globalThis.liveChangeFrameworkApp
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
module.exports.utils = require('./lib/utils.js')
|
package/lib/App.js
CHANGED
|
@@ -33,6 +33,8 @@ const triggerExecutor = require("./processes/triggerExecutor.js")
|
|
|
33
33
|
const eventListener = require('./processes/eventListener.js')
|
|
34
34
|
|
|
35
35
|
const utils = require('./utils.js')
|
|
36
|
+
const SplitEmitQueue = require("./utils/SplitEmitQueue.js");
|
|
37
|
+
const SingleEmitQueue = require("./utils/SingleEmitQueue.js");
|
|
36
38
|
|
|
37
39
|
const debug = require('debug')('framework')
|
|
38
40
|
|
|
@@ -41,6 +43,9 @@ class App {
|
|
|
41
43
|
constructor(config = {}) {
|
|
42
44
|
this.config = config
|
|
43
45
|
this.splitEvents = false
|
|
46
|
+
this.shortEvents = false
|
|
47
|
+
this.shortCommands = false
|
|
48
|
+
this.shortTriggers = false
|
|
44
49
|
|
|
45
50
|
this.requestTimeout = config?.db?.requestTimeout || 10*1000
|
|
46
51
|
|
|
@@ -78,6 +83,9 @@ class App {
|
|
|
78
83
|
this.uidGenerator = uidGenerator(this.instanceId, this.config.uidBorders)
|
|
79
84
|
|
|
80
85
|
this.activeTimeouts = new Set()
|
|
86
|
+
|
|
87
|
+
this.startedServices = {}
|
|
88
|
+
this.triggerRoutes = {}
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
createServiceDefinition( definition ) {
|
|
@@ -142,6 +150,9 @@ class App {
|
|
|
142
150
|
oldServiceJson = this.createServiceDefinition({name: service.name}).toJSON()
|
|
143
151
|
}
|
|
144
152
|
let changes = this.computeChanges(oldServiceJson, service)
|
|
153
|
+
//console.log("OLD SERVICE", JSON.stringify(oldServiceJson, null, ' '))
|
|
154
|
+
//console.log("NEW SERVICE", JSON.stringify(service.toJSON(), null, ' '))
|
|
155
|
+
//console.log("CHANGES", JSON.stringify(changes, null, ' '))
|
|
145
156
|
|
|
146
157
|
/// TODO: chceck for overwriting renames, solve by addeding temporary names
|
|
147
158
|
|
|
@@ -164,6 +175,7 @@ class App {
|
|
|
164
175
|
await service.start(config)
|
|
165
176
|
console.log("service started", serviceDefinition.name, "!")
|
|
166
177
|
await this.profileLog.end(profileOp)
|
|
178
|
+
this.startedServices[serviceDefinition.name] = service
|
|
167
179
|
return service
|
|
168
180
|
}
|
|
169
181
|
|
|
@@ -204,6 +216,10 @@ class App {
|
|
|
204
216
|
}
|
|
205
217
|
|
|
206
218
|
async trigger(data) {
|
|
219
|
+
if(this.shortTriggers) {
|
|
220
|
+
const triggers = this.triggerRoutes[data.route]
|
|
221
|
+
return await Promise.all(triggers.map(t => t.execute(data)))
|
|
222
|
+
}
|
|
207
223
|
const profileOp = await this.profileLog.begin({
|
|
208
224
|
operation: "callTrigger", triggerType: data.type, id: data.id, by: data.by
|
|
209
225
|
})
|
|
@@ -222,7 +238,14 @@ class App {
|
|
|
222
238
|
}
|
|
223
239
|
|
|
224
240
|
async triggerService(service, data, returnArray = false) {
|
|
225
|
-
|
|
241
|
+
if(this.shortTriggers) {
|
|
242
|
+
const service = this.startedServices[service]
|
|
243
|
+
const triggers = service.triggers[data.type]
|
|
244
|
+
if(!triggers) return []
|
|
245
|
+
const result = await Promise.all(triggers.map(t => t.execute(data)))
|
|
246
|
+
if(!returnArray && Array.isArray(result) && result.length == 1) return result[0]
|
|
247
|
+
return result
|
|
248
|
+
}
|
|
226
249
|
if(!data.id) data.id = this.generateUid()
|
|
227
250
|
data.service = service
|
|
228
251
|
data.state = 'new'
|
|
@@ -266,45 +289,111 @@ class App {
|
|
|
266
289
|
if(!data.timestamp) data.timestamp = (new Date()).toISOString()
|
|
267
290
|
data.state = 'new'
|
|
268
291
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
292
|
+
if(this.shortCommands) {
|
|
293
|
+
const command = data
|
|
294
|
+
const service = this.startedServices[data.service]
|
|
295
|
+
const action = service.actions[data.type]
|
|
296
|
+
const reportFinished = action.definition.waitForEvents ? 'command_' + command.id : undefined
|
|
297
|
+
const flags = {commandId: command.id, reportFinished}
|
|
298
|
+
const emit = (!this.splitEvents || this.shortEvents)
|
|
299
|
+
? new SingleEmitQueue(service, flags)
|
|
300
|
+
: new SplitEmitQueue(service, flags)
|
|
301
|
+
const queuedBy = action.definition.queuedBy
|
|
302
|
+
if(queuedBy) {
|
|
303
|
+
const profileOp = await service.profileLog.begin({
|
|
304
|
+
operation: 'queueCommand', commandType: actionName,
|
|
305
|
+
commandId: command.id, client: command.client
|
|
306
|
+
})
|
|
307
|
+
const keyFunction = typeof queuedBy == 'function' ? queuedBy : (
|
|
308
|
+
Array.isArray(queuedBy) ? (c) => JSON.stringify(queuedBy.map(k => c[k])) :
|
|
309
|
+
(c) => JSON.stringify(c[queuedBy]))
|
|
310
|
+
|
|
311
|
+
const routine = () => service.profileLog.profile({
|
|
312
|
+
operation: 'runCommand', commandType: actionName,
|
|
313
|
+
commandId: command.id, client: command.client
|
|
314
|
+
}, async () => {
|
|
315
|
+
const result = await service.app.assertTime('command ' + action.definition.name,
|
|
316
|
+
action.definition.timeout || 10000,
|
|
317
|
+
() => action.runCommand(command, (...args) => emit.emit(...args)), command)
|
|
318
|
+
if(this.shortEvents) {
|
|
319
|
+
const bucket = {}
|
|
320
|
+
const eventsPromise = Promise.all(emit.emittedEvents.map(event => {
|
|
321
|
+
const service = this.startedServices[event.service]
|
|
322
|
+
const handler = service.events[event.type]
|
|
323
|
+
service.exentQueue.queue(() => handler.execute(event, bucket))
|
|
324
|
+
}))
|
|
325
|
+
if (action.definition.waitForEvents) await eventsPromise
|
|
326
|
+
} else {
|
|
327
|
+
const events = await emit.commit()
|
|
328
|
+
if (action.definition.waitForEvents)
|
|
329
|
+
await service.app.waitForEvents(reportFinished, events, action.definition.waitForEvents)
|
|
330
|
+
}
|
|
331
|
+
return result
|
|
332
|
+
})
|
|
333
|
+
routine.key = keyFunction(command)
|
|
334
|
+
const promise = service.keyBasedExecutionQueues.queue(routine)
|
|
335
|
+
await service.profileLog.endPromise(profileOp, promise)
|
|
336
|
+
return promise
|
|
337
|
+
} else {
|
|
338
|
+
const result = await service.app.assertTime('command ' + action.definition.name,
|
|
339
|
+
action.definition.timeout || 10000,
|
|
340
|
+
() => action.runCommand(command, (...args) => emit.emit(...args)), command)
|
|
341
|
+
if(this.shortEvents) {
|
|
342
|
+
const bucket = {}
|
|
343
|
+
console.log("emit", emit)
|
|
344
|
+
const eventsPromise = Promise.all(emit.emittedEvents.map(event => {
|
|
345
|
+
const service = this.startedServices[event.service]
|
|
346
|
+
const handler = service.events[event.type]
|
|
347
|
+
service.exentQueue.queue(() => handler.execute(event, bucket))
|
|
348
|
+
}))
|
|
349
|
+
if (action.definition.waitForEvents) await eventsPromise
|
|
350
|
+
} else {
|
|
351
|
+
const events = await emit.commit()
|
|
352
|
+
if (action.definition.waitForEvents)
|
|
353
|
+
await service.app.waitForEvents(reportFinished, events, action.definition.waitForEvents)
|
|
354
|
+
}
|
|
355
|
+
return result
|
|
356
|
+
}
|
|
357
|
+
} else { // queue and observe command execution
|
|
358
|
+
const profileOp = await this.profileLog.begin({
|
|
359
|
+
operation: "callCommand", commandType: data.type, service: data.service,
|
|
360
|
+
commandId: data.id, by: data.by, client: data.client
|
|
361
|
+
})
|
|
273
362
|
|
|
274
|
-
|
|
275
|
-
|
|
363
|
+
const commandsTable = this.splitCommands ? `${data.service}_commands` : 'commands'
|
|
364
|
+
const objectObservable = this.dao.observable(
|
|
276
365
|
['database', 'tableObject', this.databaseName, commandsTable, data.id],
|
|
277
366
|
ReactiveDao.ObservableValue
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
await this.profileLog.endPromise(profileOp, promise)
|
|
367
|
+
)
|
|
368
|
+
await this.dao.request(['database', 'update', this.databaseName, commandsTable, data.id, [
|
|
369
|
+
{op: 'reverseMerge', value: data}
|
|
370
|
+
]])
|
|
371
|
+
let observer
|
|
372
|
+
const promise = new Promise((resolve, reject) => {
|
|
373
|
+
observer = (signal, value) => {
|
|
374
|
+
if (signal != 'set') return reject('unknownSignal')
|
|
375
|
+
if (!value) return
|
|
376
|
+
if (value.state == 'done') return resolve(value.result)
|
|
377
|
+
if (value.state == 'failed') return reject(value.error)
|
|
378
|
+
}
|
|
379
|
+
objectObservable.observe(observer)
|
|
380
|
+
if (!requestTimeout) {
|
|
381
|
+
requestTimeout = this.requestTimeout
|
|
382
|
+
}
|
|
383
|
+
if (requestTimeout) {
|
|
384
|
+
const timeout = setTimeout(() => {
|
|
385
|
+
this.activeTimeouts.delete(timeout)
|
|
386
|
+
reject('timeout')
|
|
387
|
+
}, requestTimeout)
|
|
388
|
+
this.activeTimeouts.add(timeout)
|
|
389
|
+
}
|
|
390
|
+
}).finally(() => {
|
|
391
|
+
objectObservable.unobserve(observer)
|
|
392
|
+
})
|
|
306
393
|
|
|
307
|
-
|
|
394
|
+
await this.profileLog.endPromise(profileOp, promise)
|
|
395
|
+
return promise
|
|
396
|
+
}
|
|
308
397
|
}
|
|
309
398
|
|
|
310
399
|
async emitEvents(service, events, flags = {}) {
|
|
@@ -28,6 +28,12 @@ class IndexDefinition {
|
|
|
28
28
|
if(oldIndex.search && this.search && JSON.stringify(oldIndex.search) != JSON.stringify(this.search))
|
|
29
29
|
changes.push({ operation: "indexSearchUpdated", index: this.name })
|
|
30
30
|
|
|
31
|
+
const oldStorage = oldIndex.storage || {}
|
|
32
|
+
const newStorage = this.storage || {}
|
|
33
|
+
if(JSON.stringify(oldStorage) != JSON.stringify(newStorage)) {
|
|
34
|
+
changes.push({ operation: "indexStorageChanged", index: this.name, oldStorage, storage: newStorage })
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
return changes
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -58,12 +58,21 @@ class ModelDefinition {
|
|
|
58
58
|
changes.push(...utils.crudChanges(oldModel.properties || {}, json.properties || {},
|
|
59
59
|
"Property", "property", { model: this.name }))
|
|
60
60
|
changes.push(...utils.crudChanges(oldModel.indexes || {}, json.indexes || {},
|
|
61
|
-
"Index", "index", { model: this.name }))
|
|
61
|
+
"Index", "index", { model: this.name, storage: this.storage }))
|
|
62
62
|
if(oldModel.search && !this.search) changes.push({ operation: "searchDisabled", model: this.name })
|
|
63
63
|
if(!oldModel.search && this.search) changes.push({ operation: "searchEnabled", model: this.name })
|
|
64
64
|
if(oldModel.search && this.search && JSON.stringify(oldModel.search) != JSON.stringify(this.search))
|
|
65
65
|
changes.push({ operation: "searchUpdated", model: this.name })
|
|
66
66
|
|
|
67
|
+
const oldStorage = oldModel.storage || {}
|
|
68
|
+
const newStorage = this.storage || {}
|
|
69
|
+
if(JSON.stringify(oldStorage) != JSON.stringify(newStorage)) {
|
|
70
|
+
changes.push({ operation: "storageChanged", model: this.name, oldStorage, storage: newStorage })
|
|
71
|
+
for(let indexName in this.indexes) {
|
|
72
|
+
changes.push({ operation: "indexStorageChanged", index: indexName, oldStorage, storage: newStorage })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
return changes
|
|
68
77
|
}
|
|
69
78
|
|
|
@@ -7,9 +7,11 @@ async function startCommandExecutor(service, config) {
|
|
|
7
7
|
if(!config.runCommands) return
|
|
8
8
|
|
|
9
9
|
service.keyBasedExecutionQueues = service.keyBasedExecutionQueues || new KeyBasedExecutionQueues(r => r.key)
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
if(service.app.shortCommands) return
|
|
12
|
+
|
|
11
13
|
service.commandQueue = new CommandQueue(service.dao, service.databaseName,
|
|
12
|
-
service.app.splitCommands ? `${service.name}_commands` : 'commands', service.name)
|
|
14
|
+
service.app.splitCommands ? `${service.name}_commands` : 'commands', service.name, config.runCommands)
|
|
13
15
|
for (let actionName in service.actions) {
|
|
14
16
|
const action = service.actions[actionName]
|
|
15
17
|
if (action.definition.queuedBy) {
|
|
@@ -24,9 +26,9 @@ async function startCommandExecutor(service, config) {
|
|
|
24
26
|
})
|
|
25
27
|
const reportFinished = action.definition.waitForEvents ? 'command_' + command.id : undefined
|
|
26
28
|
const flags = {commandId: command.id, reportFinished}
|
|
27
|
-
const emit = service.app.splitEvents
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const emit = (!service.app.splitEvents || this.shortEvents)
|
|
30
|
+
? new SingleEmitQueue(service, flags)
|
|
31
|
+
: new SplitEmitQueue(service, flags)
|
|
30
32
|
const routine = () => service.profileLog.profile({
|
|
31
33
|
operation: 'runCommand', commandType: actionName,
|
|
32
34
|
commandId: command.id, client: command.client
|
|
@@ -34,9 +36,19 @@ async function startCommandExecutor(service, config) {
|
|
|
34
36
|
const result = await service.app.assertTime('command ' + action.definition.name,
|
|
35
37
|
action.definition.timeout || 10000,
|
|
36
38
|
() => action.runCommand(command, (...args) => emit.emit(...args)), command)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
if(service.app.shortEvents) {
|
|
40
|
+
const bucket = {}
|
|
41
|
+
const eventsPromise = Promise.all(emit.emittedEvents.map(event => {
|
|
42
|
+
const handlerService = service.app.startedServices[event.service]
|
|
43
|
+
const handler = handlerService.events[event.type]
|
|
44
|
+
handlerService.exentQueue.queue(() => handler.execute(event, bucket))
|
|
45
|
+
}))
|
|
46
|
+
if (action.definition.waitForEvents) await eventsPromise
|
|
47
|
+
} else {
|
|
48
|
+
const events = await emit.commit()
|
|
49
|
+
if (action.definition.waitForEvents)
|
|
50
|
+
await service.app.waitForEvents(reportFinished, events, action.definition.waitForEvents)
|
|
51
|
+
}
|
|
40
52
|
return result
|
|
41
53
|
})
|
|
42
54
|
routine.key = keyFunction(command)
|
|
@@ -52,15 +64,25 @@ async function startCommandExecutor(service, config) {
|
|
|
52
64
|
}, async () => {
|
|
53
65
|
const reportFinished = action.definition.waitForEvents ? 'command_' + command.id : undefined
|
|
54
66
|
const flags = {commandId: command.id, reportFinished}
|
|
55
|
-
const emit = service.app.splitEvents
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
const emit = (!service.app.splitEvents || this.shortEvents)
|
|
68
|
+
? new SingleEmitQueue(service, flags)
|
|
69
|
+
: new SplitEmitQueue(service, flags)
|
|
58
70
|
const result = await service.app.assertTime('command ' + action.definition.name,
|
|
59
71
|
action.definition.timeout || 10000,
|
|
60
72
|
() => action.runCommand(command, (...args) => emit.emit(...args)), command)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
if(service.app.shortEvents) {
|
|
74
|
+
const bucket = {}
|
|
75
|
+
const eventsPromise = Promise.all(emit.emittedEvents.map(event => {
|
|
76
|
+
const handlerService = service.app.startedServices[event.service]
|
|
77
|
+
const handler = handlerService.events[event.type]
|
|
78
|
+
handlerService.exentQueue.queue(() => handler.execute(event, bucket))
|
|
79
|
+
}))
|
|
80
|
+
if (action.definition.waitForEvents) await eventsPromise
|
|
81
|
+
} else {
|
|
82
|
+
const events = await emit.commit()
|
|
83
|
+
if (action.definition.waitForEvents)
|
|
84
|
+
await service.app.waitForEvents(reportFinished, events, action.definition.waitForEvents)
|
|
85
|
+
}
|
|
64
86
|
return result
|
|
65
87
|
})
|
|
66
88
|
)
|
|
@@ -2,18 +2,17 @@ const EventSourcing = require('../utils/EventSourcing.js')
|
|
|
2
2
|
|
|
3
3
|
async function startEventListener(service, config) {
|
|
4
4
|
if(!config.handleEvents) return
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
if(service.app.splitEvents) {
|
|
7
7
|
service.eventSourcing = new EventSourcing(service.dao, service.databaseName,
|
|
8
8
|
'events_'+service.name, service.name,
|
|
9
|
-
{ filter: (event) => event.service == service.name })
|
|
9
|
+
{ filter: (event) => event.service == service.name }, config.handleEvents)
|
|
10
10
|
} else {
|
|
11
11
|
service.eventSourcing = new EventSourcing(service.dao, service.databaseName,
|
|
12
12
|
'events', service.name,
|
|
13
|
-
{ filter: (event) => event.service == service.name })
|
|
13
|
+
{ filter: (event) => event.service == service.name }, config.handleEvents)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
for (let eventName in service.events) {
|
|
18
17
|
const event = service.events[eventName]
|
|
19
18
|
service.eventSourcing.addEventHandler(eventName, async (ev, bucket) => {
|
|
@@ -37,4 +36,4 @@ async function startEventListener(service, config) {
|
|
|
37
36
|
service.eventSourcing.start()
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
module.exports = startEventListener
|
|
39
|
+
module.exports = startEventListener
|
|
@@ -8,10 +8,20 @@ async function startTriggerExecutor(service, config) {
|
|
|
8
8
|
|
|
9
9
|
service.keyBasedExecutionQueues = service.keyBasedExecutionQueues || new KeyBasedExecutionQueues(r => r.key)
|
|
10
10
|
|
|
11
|
+
if(service.app.shortTriggers) {
|
|
12
|
+
for (let triggerName in service.triggers) {
|
|
13
|
+
service.app.triggerRoutes[triggerName] = service.app.triggerRoutes[triggerName] || []
|
|
14
|
+
for(let trigger of service.triggers[triggerName]) {
|
|
15
|
+
service.app.triggerRoutes[triggerName].push({ service, trigger })
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
await service.dao.request(['database', 'createTable'], service.databaseName, 'triggerRoutes').catch(e => 'ok')
|
|
12
22
|
|
|
13
23
|
service.triggerQueue = new CommandQueue(service.dao, service.databaseName,
|
|
14
|
-
service.app.splitTriggers ? `${service.name}_triggers` : 'triggers', service.name )
|
|
24
|
+
service.app.splitTriggers ? `${service.name}_triggers` : 'triggers', service.name, config.runCommands)
|
|
15
25
|
for (let triggerName in service.triggers) {
|
|
16
26
|
const triggers = service.triggers[triggerName]
|
|
17
27
|
await service.dao.request(['database', 'put'], service.databaseName, 'triggerRoutes',
|
package/lib/runtime/Action.js
CHANGED
|
@@ -35,7 +35,7 @@ class Action {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async callCommand(parameters, clientData) {
|
|
38
|
-
if(!clientData.roles) throw new Error("no roles")
|
|
38
|
+
// if(!clientData.roles) throw new Error("no roles") - roles are not required in frontend
|
|
39
39
|
const command = {
|
|
40
40
|
type: this.definition.name,
|
|
41
41
|
service: this.service.name,
|
|
@@ -48,4 +48,4 @@ class Action {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
module.exports = Action
|
|
51
|
+
module.exports = Action
|
package/lib/runtime/Dao.js
CHANGED
|
@@ -8,7 +8,7 @@ function promiseMap(promise, fn) {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function prepareReactiveDaoDefinition(config, clientData) {
|
|
11
|
-
if(!clientData.roles) throw new Error("no roles")
|
|
11
|
+
//if(!clientData.roles) throw new Error("no roles") - roles not required for local dao
|
|
12
12
|
let dao = {}
|
|
13
13
|
let staticDefinitions = []
|
|
14
14
|
if(config.remote) {
|
|
@@ -43,7 +43,7 @@ function prepareReactiveDaoDefinition(config, clientData) {
|
|
|
43
43
|
let methods = {}, values = {}
|
|
44
44
|
for(let actionName in service.actions) {
|
|
45
45
|
let action = service.actions[actionName]
|
|
46
|
-
if(!clientData.roles) throw new Error("no roles")
|
|
46
|
+
//if(!clientData.roles) throw new Error("no roles") - roles not required for local dao
|
|
47
47
|
methods[actionName] = (params) => action.callCommand(params, clientData)
|
|
48
48
|
}
|
|
49
49
|
for(let viewName in service.views) {
|
|
@@ -172,13 +172,13 @@ function prepareReactiveDaoDefinition(config, clientData) {
|
|
|
172
172
|
return dao
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
class
|
|
175
|
+
class Dao extends ReactiveDao {
|
|
176
176
|
constructor(config, clientData) {
|
|
177
|
-
console.log("CD", clientData)
|
|
177
|
+
//console.log("CD", clientData)
|
|
178
178
|
super(clientData, prepareReactiveDaoDefinition(config, clientData))
|
|
179
179
|
//console.log("Created dao with clientData",clientData)
|
|
180
|
-
if( !clientData.roles ) throw new Error("NO ROLES!!")
|
|
180
|
+
// if( !clientData.roles ) throw new Error("NO ROLES!!") - roles not required for local dao
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
module.exports =
|
|
184
|
+
module.exports = Dao
|
package/lib/runtime/Model.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const ReaderModel = require("./ReaderModel.js")
|
|
2
|
+
const utils = require("../utils.js");
|
|
2
3
|
|
|
3
4
|
class Model extends ReaderModel {
|
|
4
5
|
|
|
@@ -38,6 +39,64 @@ class Model extends ReaderModel {
|
|
|
38
39
|
return res
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
rangeDelete(range = {}, pathRange = null) {
|
|
43
|
+
if(typeof range != 'object' || Array.isArray(range)) {
|
|
44
|
+
const values = Array.isArray(range) ? range : [range]
|
|
45
|
+
const prefix = values.map(value => value === undefined ? '' : JSON.stringify(value)).join(':')
|
|
46
|
+
if(pathRange) {
|
|
47
|
+
return this.rangePath(utils.prefixRange(pathRange, prefix, prefix))
|
|
48
|
+
}
|
|
49
|
+
return this.rangePath({ gte: prefix+':', lte: prefix+'_\xFF\xFF\xFF\xFF' })
|
|
50
|
+
}
|
|
51
|
+
if(Array.isArray(range)) this.rangePath(range.join(','))
|
|
52
|
+
this.service.dao.request(['database', 'query'], this.service.databaseName, `(${
|
|
53
|
+
async (input, output, { tableName, range }) => {
|
|
54
|
+
await (await input.table(tableName)).range(range).onChange(async (obj, oldObj) => {
|
|
55
|
+
output.table(tableName).delete(obj.id)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
})`, { tableName: this.tableName, range })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
indexRangeDelete(index, range = {}, pathRange = null) {
|
|
62
|
+
if(typeof range != 'object' || Array.isArray(range)) {
|
|
63
|
+
const values = Array.isArray(range) ? range : [range]
|
|
64
|
+
const prefix = values.map(value => value === undefined ? '' : JSON.stringify(value)).join(':')
|
|
65
|
+
if(pathRange) {
|
|
66
|
+
return this.indexRangeDelete(index, utils.prefixRange(pathRange, prefix, prefix))
|
|
67
|
+
}
|
|
68
|
+
return this.indexRangeDelete(index,{ gte: prefix+':', lte: prefix+'_\xFF\xFF\xFF\xFF' })
|
|
69
|
+
}
|
|
70
|
+
this.service.dao.request(['database', 'query'], this.service.databaseName, `${
|
|
71
|
+
async (input, output, { tableName, indexName, range }) => {
|
|
72
|
+
await (await input.index(indexName)).range(range).onChange(async (ind, oldInd) => {
|
|
73
|
+
output.table(tableName).delete(ind.to)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
})`, { indexName: this.tableName+'_'+index, tableName: this.tableName, range })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
indexRangeUpdate(index, update, range = {}, pathRange = null) {
|
|
80
|
+
if(typeof range != 'object' || Array.isArray(range)) {
|
|
81
|
+
const values = Array.isArray(range) ? range : [range]
|
|
82
|
+
const prefix = values.map(value => value === undefined ? '' : JSON.stringify(value)).join(':')
|
|
83
|
+
if(pathRange) {
|
|
84
|
+
return this.indexRangeUpdate(index, update, utils.prefixRange(pathRange, prefix, prefix))
|
|
85
|
+
}
|
|
86
|
+
return this.indexRangeUpdate(index, update,{ gte: prefix+':', lte: prefix+'_\xFF\xFF\xFF\xFF' })
|
|
87
|
+
}
|
|
88
|
+
const operations = Array.isArray(update) ? update : [{ op:'merge', property: null, value: update }]
|
|
89
|
+
this.service.dao.request(['database', 'query'], this.service.databaseName, `(${
|
|
90
|
+
async (input, output, { tableName, indexName, range, operations }) => {
|
|
91
|
+
await (await input.index(indexName)).range(range).onChange(async (ind, oldInd) => {
|
|
92
|
+
output.table(tableName).update(ind.to, operations)
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
})`, { indexName: this.tableName+'_'+index, tableName: this.tableName, range, operations })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
41
100
|
}
|
|
42
101
|
|
|
43
102
|
module.exports = Model
|
|
@@ -211,41 +211,17 @@ class ReaderModel {
|
|
|
211
211
|
})`, { indexName: this.tableName+'_'+index, tableName: this.tableName, range }]
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
countPath(range = {}, pathRange = null) {
|
|
215
215
|
if(typeof range != 'object' || Array.isArray(range)) {
|
|
216
216
|
const values = Array.isArray(range) ? range : [range]
|
|
217
217
|
const prefix = values.map(value => value === undefined ? '' : JSON.stringify(value)).join(':')
|
|
218
218
|
if(pathRange) {
|
|
219
|
-
return this.
|
|
219
|
+
return this.countPath(utils.prefixRange(pathRange, prefix, prefix))
|
|
220
220
|
}
|
|
221
|
-
return this.
|
|
221
|
+
return this.countPath({ gte: prefix+':', lte: prefix+'_\xFF\xFF\xFF\xFF' })
|
|
222
222
|
}
|
|
223
|
-
this.
|
|
224
|
-
|
|
225
|
-
await (await input.index(indexName)).range(range).onChange(async (ind, oldInd) => {
|
|
226
|
-
output.table(tableName).delete(ind.to)
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
})`, { indexName: this.tableName+'_'+index, tableName: this.tableName, range })
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
indexRangeUpdate(index, update, range = {}, pathRange = null) {
|
|
233
|
-
if(typeof range != 'object' || Array.isArray(range)) {
|
|
234
|
-
const values = Array.isArray(range) ? range : [range]
|
|
235
|
-
const prefix = values.map(value => value === undefined ? '' : JSON.stringify(value)).join(':')
|
|
236
|
-
if(pathRange) {
|
|
237
|
-
return this.indexRangeUpdate(index, update, utils.prefixRange(pathRange, prefix, prefix))
|
|
238
|
-
}
|
|
239
|
-
return this.indexRangeUpdate(index, update,{ gte: prefix+':', lte: prefix+'_\xFF\xFF\xFF\xFF' })
|
|
240
|
-
}
|
|
241
|
-
const operations = Array.isArray(update) ? update : [{ op:'merge', property: null, value: update }]
|
|
242
|
-
this.service.dao.request(['database', 'query'], this.service.databaseName, `(${
|
|
243
|
-
async (input, output, { tableName, indexName, range, operations }) => {
|
|
244
|
-
await (await input.index(indexName)).range(range).onChange(async (ind, oldInd) => {
|
|
245
|
-
output.table(tableName).update(ind.to, operations)
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
|
-
})`, { indexName: this.tableName+'_'+index, tableName: this.tableName, range, operations })
|
|
223
|
+
if(Array.isArray(range)) this.rangePath(range.join(','))
|
|
224
|
+
return ['database', 'tableCount', this.service.databaseName, this.tableName, range]
|
|
249
225
|
}
|
|
250
226
|
|
|
251
227
|
observable(id) {
|
|
@@ -278,6 +254,12 @@ class ReaderModel {
|
|
|
278
254
|
async sortedIndexRangeGet(index, range, pathRange = null) {
|
|
279
255
|
return this.service.dao.get(this.sortedIndexRangePath(index, range, pathRange), ReactiveDao.ObservableList)
|
|
280
256
|
}
|
|
257
|
+
countObservable(range) {
|
|
258
|
+
return this.service.dao.observable(this.countPath(range), ReactiveDao.ObservableList)
|
|
259
|
+
}
|
|
260
|
+
async countGet(range) {
|
|
261
|
+
return this.service.dao.get(this.countPath(range), ReactiveDao.ObservableList)
|
|
262
|
+
}
|
|
281
263
|
|
|
282
264
|
condition(id, condition = x => !!x, timeout = 10000) {
|
|
283
265
|
return new Promise((resolve, reject) => {
|
package/lib/runtime/Service.js
CHANGED
|
@@ -5,6 +5,7 @@ const View = require("./View.js")
|
|
|
5
5
|
const Action = require("./Action.js")
|
|
6
6
|
const EventHandler = require("./EventHandler.js")
|
|
7
7
|
const TriggerHandler = require("./TriggerHandler.js")
|
|
8
|
+
const ExecutionQueue = require("../utils/ExecutionQueue.js")
|
|
8
9
|
|
|
9
10
|
class Service {
|
|
10
11
|
|
|
@@ -13,6 +14,8 @@ class Service {
|
|
|
13
14
|
this.app = app
|
|
14
15
|
this.name = definition.name
|
|
15
16
|
|
|
17
|
+
if(app.shortEvents) this.eventQueue = new ExecutionQueue()
|
|
18
|
+
|
|
16
19
|
this.profileLog = app.profileLog
|
|
17
20
|
|
|
18
21
|
this.dao = definition.daoFactory ? definition.daoFactory(app) : app.dao
|
package/lib/updaters/database.js
CHANGED
|
@@ -5,23 +5,30 @@ async function update(changes, service, app, force) {
|
|
|
5
5
|
const dao = app.dao
|
|
6
6
|
const database = app.databaseName
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
if(!app.shortEvents) {
|
|
9
|
+
dao.request(['database', 'createTable'], database, 'eventConsumers').catch(e => 'ok')
|
|
10
|
+
dao.request(['database', 'createTable'], database, 'eventReports').catch(e => 'ok')
|
|
11
|
+
if (app.splitEvents) {
|
|
12
|
+
dao.request(['database', 'createLog'], database, service.name + '_events').catch(e => 'ok')
|
|
13
|
+
} else {
|
|
14
|
+
dao.request(['database', 'createLog'], database, 'events').catch(e => 'ok')
|
|
15
|
+
}
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
|
|
18
|
+
if(!app.shortCommands) {
|
|
19
|
+
if (app.splitCommands) {
|
|
20
|
+
dao.request(['database', 'createTable'], database, service.name + '_commands').catch(e => 'ok')
|
|
21
|
+
} else {
|
|
22
|
+
dao.request(['database', 'createTable'], database, 'commands').catch(e => 'ok')
|
|
23
|
+
}
|
|
20
24
|
}
|
|
21
|
-
if(app.
|
|
22
|
-
dao.request(['database', 'createTable'], database,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
if(!app.shortTriggers) {
|
|
26
|
+
dao.request(['database', 'createTable'], database, 'triggerRoutes').catch(e => 'ok')
|
|
27
|
+
if (app.splitTriggers) {
|
|
28
|
+
dao.request(['database', 'createTable'], database, service.name + '_triggers').catch(e => 'ok')
|
|
29
|
+
} else {
|
|
30
|
+
dao.request(['database', 'createTable'], database, 'triggers').catch(e => 'ok')
|
|
31
|
+
}
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
const generateTableName = (modelName) => {
|
|
@@ -47,7 +54,7 @@ async function update(changes, service, app, force) {
|
|
|
47
54
|
|
|
48
55
|
if(index.function) {
|
|
49
56
|
await dao.request(['database', 'createIndex'], database, indexName,
|
|
50
|
-
`(${index.function})`, { ...(index.parameters || {}) })
|
|
57
|
+
`(${index.function})`, { ...(index.parameters || {}) }, index.storage ?? {})
|
|
51
58
|
} else {
|
|
52
59
|
if(!table) throw new Error("only function indexes are possible without table")
|
|
53
60
|
if(index.multi) {
|
|
@@ -78,7 +85,7 @@ async function update(changes, service, app, force) {
|
|
|
78
85
|
}
|
|
79
86
|
})
|
|
80
87
|
}
|
|
81
|
-
})`, { property: index.property.split('.'), table })
|
|
88
|
+
})`, { property: index.property.split('.'), table }, index.storage ?? {})
|
|
82
89
|
} else {
|
|
83
90
|
if(!table) throw new Error("only function indexes are possible without table")
|
|
84
91
|
const properties = (Array.isArray(index.property) ? index.property : [index.property]).map(p => p.split('.'))
|
|
@@ -95,7 +102,7 @@ async function update(changes, service, app, force) {
|
|
|
95
102
|
await input.table(table).onChange((obj, oldObj) =>
|
|
96
103
|
output.change(obj && mapper(obj), oldObj && mapper(oldObj)) )
|
|
97
104
|
}
|
|
98
|
-
})`, { properties, table })
|
|
105
|
+
})`, { properties, table }, index.storage ?? {})
|
|
99
106
|
}
|
|
100
107
|
}
|
|
101
108
|
|
|
@@ -110,7 +117,7 @@ async function update(changes, service, app, force) {
|
|
|
110
117
|
case "createModel": {
|
|
111
118
|
const model = change.model
|
|
112
119
|
const tableName = generateTableName(model.name)
|
|
113
|
-
await dao.request(['database', 'createTable'], database, tableName)
|
|
120
|
+
await dao.request(['database', 'createTable'], database, tableName, model.storage ?? {})
|
|
114
121
|
debug("TABLE CREATED!", tableName)
|
|
115
122
|
for(let indexName in model.indexes) {
|
|
116
123
|
const index = model.indexes[indexName]
|
|
@@ -154,7 +161,7 @@ async function update(changes, service, app, force) {
|
|
|
154
161
|
case "createIndex": {
|
|
155
162
|
const table = change.model ? generateTableName(change.model) : null
|
|
156
163
|
const index = change.index
|
|
157
|
-
await createIndex(table, change.name, index)
|
|
164
|
+
await createIndex(table, change.name, index, change.model)
|
|
158
165
|
} break
|
|
159
166
|
case "renameIndex": {
|
|
160
167
|
const table = change.model ? generateTableName(change.model) : null
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
const ReactiveDao = require('@live-change/dao')
|
|
2
2
|
|
|
3
3
|
class CommandQueue {
|
|
4
|
-
constructor(connection, database, tableName, serviceName) {
|
|
4
|
+
constructor(connection, database, tableName, serviceName, config) {
|
|
5
5
|
this.connection = connection
|
|
6
6
|
this.database = database
|
|
7
7
|
this.tableName = tableName
|
|
8
8
|
this.indexName = tableName + '_new'
|
|
9
9
|
this.serviceName = serviceName
|
|
10
|
+
this.config = config || {}
|
|
10
11
|
this.observable = null
|
|
11
12
|
this.disposed = false
|
|
12
13
|
this.resolveStart = null
|
|
@@ -22,8 +23,10 @@ class CommandQueue {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
async start() {
|
|
26
|
+
console.log("START COMMAND QUEUE", this.database, this.tableName, this.indexName, this.config)
|
|
25
27
|
//console.log("START QUEUE", this.tableName, this.indexName)
|
|
26
|
-
await this.connection.request(['database', 'createTable'], this.database, this.tableName
|
|
28
|
+
await this.connection.request(['database', 'createTable'], this.database, this.tableName,
|
|
29
|
+
this.config.storage ?? {}).catch(e => 'ok')
|
|
27
30
|
await this.connection.request(['database', 'createIndex'], this.database, this.indexName, `(${
|
|
28
31
|
async function(input, output, { tableName }) {
|
|
29
32
|
await input.table(tableName).onChange(async (obj, oldObj) => {
|
|
@@ -33,7 +36,7 @@ class CommandQueue {
|
|
|
33
36
|
await output.change(res, oldRes)
|
|
34
37
|
})
|
|
35
38
|
}
|
|
36
|
-
})`, { tableName: this.tableName }).catch(e => 'ok')
|
|
39
|
+
})`, { tableName: this.tableName }, this.config.storage ?? {}).catch(e => 'ok')
|
|
37
40
|
this.observable = this.connection.observable(
|
|
38
41
|
['database', 'indexRange', this.database, this.indexName, {
|
|
39
42
|
gt: this.serviceName+'_',
|
|
@@ -25,8 +25,10 @@ class EventSourcing {
|
|
|
25
25
|
await this.saveState()
|
|
26
26
|
}
|
|
27
27
|
async start() {
|
|
28
|
-
await this.connection.request(['database', 'createTable'], this.database, 'eventConsumers'
|
|
29
|
-
|
|
28
|
+
await this.connection.request(['database', 'createTable'], this.database, 'eventConsumers', this.config.storage ?? {})
|
|
29
|
+
.catch(e => 'ok')
|
|
30
|
+
await this.connection.request(['database', 'createLog'], this.database, this.logName, this.config.storage ?? {})
|
|
31
|
+
.catch(e => 'ok')
|
|
30
32
|
this.state = await this.connection.get(
|
|
31
33
|
['database', 'tableObject', this.database, 'eventConsumers', this.consumerId])
|
|
32
34
|
//console.log("GOT CONSUMER STATE", this.state)
|
|
@@ -30,7 +30,7 @@ class ExecutionQueue {
|
|
|
30
30
|
runNext() {
|
|
31
31
|
if(this.nextRoutines.length == 0) {
|
|
32
32
|
this.running = false
|
|
33
|
-
setTimeout(() => { if(!this.running) this.queues.delete(this.key) }, 500)
|
|
33
|
+
setTimeout(() => { if(!this.running && this.queues) this.queues.delete(this.key) }, 500)
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
this.running = true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/framework",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.19",
|
|
4
4
|
"description": "Live Change Framework - ultimate solution for real time mobile/web apps",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/live-change/live-change-framework",
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@live-change/dao": "
|
|
25
|
-
"@live-change/dao-websocket": "
|
|
26
|
-
"@live-change/db": "
|
|
27
|
-
"@live-change/db-store-level": "
|
|
28
|
-
"@live-change/db-store-lmdb": "
|
|
29
|
-
"@live-change/sockjs": "
|
|
30
|
-
"@live-change/uid": "
|
|
24
|
+
"@live-change/dao": "0.5.15",
|
|
25
|
+
"@live-change/dao-websocket": "0.5.15",
|
|
26
|
+
"@live-change/db": "0.6.3",
|
|
27
|
+
"@live-change/db-store-level": "0.6.3",
|
|
28
|
+
"@live-change/db-store-lmdb": "0.6.3",
|
|
29
|
+
"@live-change/sockjs": "0.4.1",
|
|
30
|
+
"@live-change/uid": "0.7.18",
|
|
31
31
|
"cookie": "^0.4.1",
|
|
32
32
|
"express": "^4.18.1",
|
|
33
33
|
"os-service": "^2.2.0",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"tape": "^5.3.2",
|
|
36
36
|
"websocket": "^1.0.34"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "bc09aeeb64724fdfcf4e944d1858a2a00f08edcc"
|
|
39
39
|
}
|