@live-change/simple-query 0.9.162 → 0.9.164
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/package.json +3 -3
- package/src/query.ts +107 -29
- package/src/simpleIndex.db.js +1 -1
- package/src/simpleQuery.db.js +218 -67
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/simple-query",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.164",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@live-change/framework": "^0.9.
|
|
25
|
+
"@live-change/framework": "^0.9.164",
|
|
26
26
|
"pluralize": "^8.0.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"typedoc-plugin-markdown": "^4.6.3",
|
|
31
31
|
"typedoc-plugin-rename-defaults": "^0.7.3"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "38f9fb8b01a9527d8f6036e174edd1fa41443301"
|
|
34
34
|
}
|
package/src/query.ts
CHANGED
|
@@ -2,7 +2,16 @@ import {
|
|
|
2
2
|
PropertyDefinitionSpecification, ServiceDefinition, ServiceDefinitionSpecification
|
|
3
3
|
} from "@live-change/framework"
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ModelDefinition, ForeignModelDefinition, ContextBase, QueryParameters as FrameworkQueryParameters,
|
|
7
|
+
QueryDefinition as FrameworkQueryDefinition,
|
|
8
|
+
QueryDefinitionSpecification as FrameworkQueryDefinitionSpecification,
|
|
9
|
+
ViewDefinition,
|
|
10
|
+
EventDefinition,
|
|
11
|
+
TriggerDefinition,
|
|
12
|
+
ActionDefinition,
|
|
13
|
+
ViewDefinitionSpecificationDaoPath
|
|
14
|
+
} from "@live-change/framework"
|
|
6
15
|
|
|
7
16
|
import { PropertyDefinition } from "@live-change/framework"
|
|
8
17
|
|
|
@@ -17,7 +26,7 @@ const simpleQueryCode = readFileSync(simpleQueryCodePath, 'utf8')
|
|
|
17
26
|
|
|
18
27
|
|
|
19
28
|
interface Range {
|
|
20
|
-
gt?: string
|
|
29
|
+
gt?: string
|
|
21
30
|
gte?: string
|
|
22
31
|
lt?: string
|
|
23
32
|
lte?: string,
|
|
@@ -51,7 +60,6 @@ class RuleSource {
|
|
|
51
60
|
dependsOn: RuleSource[]
|
|
52
61
|
index: IndexInfo | null
|
|
53
62
|
|
|
54
|
-
|
|
55
63
|
constructor(rule: QueryRule, input: QueryInput, source: QuerySource, type: string) {
|
|
56
64
|
this.rule = rule
|
|
57
65
|
this.input = input
|
|
@@ -94,6 +102,14 @@ interface QueryDefinitionSpecification {
|
|
|
94
102
|
sources?: Record<string, QuerySource>,
|
|
95
103
|
code?: QueryCode,
|
|
96
104
|
update?: boolean,
|
|
105
|
+
id: Function,
|
|
106
|
+
timeout?: number,
|
|
107
|
+
requestTimeout?: number,
|
|
108
|
+
validation?: (parameters: FrameworkQueryParameters, context: ContextBase) => Promise<any>
|
|
109
|
+
view?: Record<string, any>
|
|
110
|
+
event?: Record<string, any>
|
|
111
|
+
trigger?: Record<string, any>
|
|
112
|
+
action?: Record<string, any>
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
class OutputMapping {
|
|
@@ -181,7 +197,8 @@ class IndexInfo {
|
|
|
181
197
|
$_createQuery(service: ServiceDefinition<any>) {
|
|
182
198
|
return new QueryDefinition(service, {
|
|
183
199
|
name: this.name,
|
|
184
|
-
properties: {}
|
|
200
|
+
properties: {},
|
|
201
|
+
id: (x:any) => x
|
|
185
202
|
}, this.rules)
|
|
186
203
|
}
|
|
187
204
|
}
|
|
@@ -199,6 +216,7 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
|
|
|
199
216
|
definition: QueryDefinitionSpecification
|
|
200
217
|
properties: Record<string, PropertyDefinition<any>>
|
|
201
218
|
rules: QueryRules
|
|
219
|
+
idFields: QueryInputLike[] | null
|
|
202
220
|
firstRule: QueryRule
|
|
203
221
|
rootSources: RuleSource[]
|
|
204
222
|
ruleSources: RuleSource[]
|
|
@@ -207,6 +225,8 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
|
|
|
207
225
|
executionPlan: ExecutionStep[]
|
|
208
226
|
indexPlan: ExecutionStep[]
|
|
209
227
|
|
|
228
|
+
preparedQuery: FrameworkQueryDefinition<FrameworkQueryDefinitionSpecification>
|
|
229
|
+
|
|
210
230
|
constructor(
|
|
211
231
|
serviceDefinition: ServiceDefinition<SDS>, definition: QueryDefinitionSpecification, rules: QueryRule[] = undefined
|
|
212
232
|
) {
|
|
@@ -246,6 +266,15 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
|
|
|
246
266
|
}
|
|
247
267
|
}
|
|
248
268
|
|
|
269
|
+
printIdFields() {
|
|
270
|
+
console.log("ID FIELDS:")
|
|
271
|
+
if(this.idFields) {
|
|
272
|
+
for(const idField of this.idFields) {
|
|
273
|
+
console.log(` `, queryDescription(idField, ' '))
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
249
278
|
computeRules() {
|
|
250
279
|
const queryProperties = {}
|
|
251
280
|
for(const propertyName in this.definition.properties) {
|
|
@@ -263,6 +292,7 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
|
|
|
263
292
|
|
|
264
293
|
// run the code to collect relations
|
|
265
294
|
this.rules = this.definition.code(queryProperties, queryInputs)
|
|
295
|
+
this.idFields = this.definition.id ? this.definition.id(queryInputs) : null
|
|
266
296
|
}
|
|
267
297
|
|
|
268
298
|
markStaticRules() {
|
|
@@ -340,7 +370,7 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
|
|
|
340
370
|
for(const key in this.rules) {
|
|
341
371
|
const rule = this.rules[key]
|
|
342
372
|
console.log(`${indent} RULE ${key}:`)
|
|
343
|
-
console.log(`${indent} ${queryDescription(rule, indent + ' ')}`)
|
|
373
|
+
console.log(`${indent} ${queryDescription(rule, indent + ' ')}`)
|
|
344
374
|
console.log(`${indent} SOURCES:`)
|
|
345
375
|
for(const ruleSource of this.ruleSources) {
|
|
346
376
|
if(ruleSource.rule !== rule) continue
|
|
@@ -377,46 +407,52 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
|
|
|
377
407
|
}
|
|
378
408
|
}
|
|
379
409
|
|
|
380
|
-
computeSourceExecutionPlan(source: RuleSource, resultParameters: string[]) {
|
|
381
|
-
/// TODO:
|
|
382
|
-
/// Poza tym przy pobieraniu wstecz nie koniecznie potrzeba indexów,
|
|
383
|
-
/// może trzeba wprowadzić index-forward i index-backward ?
|
|
384
|
-
/// A może trzeba wprowadzić analizę tego co mamy i co chcemy uzyskać w przyszłości ?
|
|
410
|
+
computeSourceExecutionPlan(source: RuleSource, resultParameters: string[]) {
|
|
411
|
+
/// TODO: Może trzeba wprowadzić analizę tego co mamy i co chcemy uzyskać w przyszłości ?
|
|
385
412
|
|
|
386
413
|
const next = source.dependentBy.map(dependent => {
|
|
387
414
|
const otherSource = this.ruleSources.find(s => s.rule === dependent.rule && s != dependent)
|
|
388
|
-
|
|
415
|
+
const nextStep = this.computeSourceExecutionPlan(otherSource, [...resultParameters, source.input.$alias])
|
|
416
|
+
return nextStep
|
|
389
417
|
})
|
|
390
418
|
|
|
391
419
|
console.log("COMPUTING SOURCE EXECUTION PLAN FOR", source.input.$_toQueryDescription(), "WITH", resultParameters)
|
|
392
420
|
console.log("RULE", queryDescription(source.rule, ' '))
|
|
393
421
|
|
|
394
422
|
const ruleParameters = JSON.parse(JSON.stringify(source.rule.$_parametersJSON(resultParameters)))
|
|
423
|
+
console.log("RULE PARAMETERS", ruleParameters)
|
|
424
|
+
const mandatory = this.idFields?.find(idField => source.input.$alias === idField.$alias) ? true : undefined
|
|
395
425
|
if(source.index) {
|
|
396
426
|
const indexExecution = {
|
|
397
427
|
...source.index.$_executionJSON(),
|
|
398
|
-
by: ruleParameters[Object.keys(ruleParameters)[0]]
|
|
428
|
+
by: parameterEnsureRange(ruleParameters[Object.keys(ruleParameters)[0]])
|
|
399
429
|
}
|
|
430
|
+
/// TODO: decide if id field is mandatory - if exports aliases that are required in id Fields or market mandatory
|
|
400
431
|
const indexNext = [{
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
432
|
+
execution: {
|
|
433
|
+
operation: 'object',
|
|
434
|
+
...source.input.$_executionJSON(),
|
|
435
|
+
by: {
|
|
436
|
+
type: 'result',
|
|
437
|
+
path: [indexExecution.alias, source.index.indexParts.at(-1).alias],
|
|
438
|
+
},
|
|
406
439
|
},
|
|
440
|
+
mandatory,
|
|
407
441
|
next
|
|
408
442
|
}]
|
|
409
443
|
return {
|
|
410
444
|
execution: indexExecution,
|
|
411
445
|
next: indexNext
|
|
412
446
|
}
|
|
413
|
-
}
|
|
447
|
+
}
|
|
448
|
+
|
|
414
449
|
const execution = {
|
|
415
|
-
...source.input.$_executionJSON(),
|
|
450
|
+
...source.input.$_executionJSON(),
|
|
416
451
|
by: ruleParameters[Object.keys(ruleParameters)[0]]
|
|
417
452
|
}
|
|
418
453
|
const executionPlan = {
|
|
419
454
|
execution,
|
|
455
|
+
mandatory,
|
|
420
456
|
next
|
|
421
457
|
}
|
|
422
458
|
return executionPlan
|
|
@@ -602,23 +638,46 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
|
|
|
602
638
|
prepareQuery() {
|
|
603
639
|
this.createIndexes()
|
|
604
640
|
|
|
605
|
-
console.log("########### PREPARING QUERY!!!!")
|
|
641
|
+
console.log("########### PREPARING QUERY!!!!")
|
|
606
642
|
|
|
607
643
|
this.printRules()
|
|
608
644
|
|
|
645
|
+
this.printIdFields()
|
|
646
|
+
|
|
609
647
|
this.printDependencies()
|
|
610
648
|
|
|
611
649
|
this.computeExecutionPlan()
|
|
612
650
|
console.log("EXECUTION PLAN:")
|
|
613
651
|
console.log(JSON.stringify(this.executionPlan, null, 2))
|
|
614
652
|
|
|
615
|
-
process.exit(0)
|
|
616
|
-
/// TODO: create indexes used by query
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
/// TODO: prepare query
|
|
653
|
+
//process.exit(0)
|
|
620
654
|
|
|
621
|
-
|
|
655
|
+
const idFunctionCode = (typeof this.definition.id === 'string')
|
|
656
|
+
? this.definition.id : `(${this.definition.id})`
|
|
657
|
+
|
|
658
|
+
console.log("ID FUNCTION", idFunctionCode)
|
|
659
|
+
|
|
660
|
+
this.preparedQuery = this.service.query({
|
|
661
|
+
name: this.definition.name,
|
|
662
|
+
properties: this.definition.properties,
|
|
663
|
+
returns: this.definition.returns,
|
|
664
|
+
code: simpleQueryCode,
|
|
665
|
+
sourceName: simpleQueryCodePath,
|
|
666
|
+
update: this.definition.update,
|
|
667
|
+
timeout: this.definition.timeout,
|
|
668
|
+
requestTimeout: this.definition.requestTimeout,
|
|
669
|
+
validation: this.definition.validation,
|
|
670
|
+
config: {
|
|
671
|
+
plan: this.executionPlan,
|
|
672
|
+
idFunction: idFunctionCode
|
|
673
|
+
},
|
|
674
|
+
view: this.definition.view,
|
|
675
|
+
trigger: this.definition.trigger,
|
|
676
|
+
action: this.definition.action,
|
|
677
|
+
event: this.definition.event
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
return this.preparedQuery
|
|
622
681
|
}
|
|
623
682
|
|
|
624
683
|
createIndex(name, mapping: OutputMapping[]) {
|
|
@@ -649,6 +708,22 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
|
|
|
649
708
|
}
|
|
650
709
|
}
|
|
651
710
|
|
|
711
|
+
function parameterIsRange(parameter: any) {
|
|
712
|
+
if(typeof parameter !== 'object' || parameter === null) return false
|
|
713
|
+
if(Array.isArray(parameter)) return parameterIsRange(parameter.at(-1))
|
|
714
|
+
if(parameter.type === 'object') return true
|
|
715
|
+
return false
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function parameterEnsureRange(parameter: any) {
|
|
719
|
+
if(parameterIsRange(parameter)) return parameter
|
|
720
|
+
if(Array.isArray(parameter)) return [...parameter, { type: 'object', properties: {} }]
|
|
721
|
+
return {
|
|
722
|
+
type: 'array',
|
|
723
|
+
items: [parameter, { type: 'object', properties: {} }]
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
652
727
|
|
|
653
728
|
export type QueryFactoryFunction<SDS extends ServiceDefinitionSpecification> =
|
|
654
729
|
(definition: QueryDefinitionSpecification) => QueryDefinition<SDS>
|
|
@@ -675,7 +750,9 @@ function getSource(input: RuleInput): QuerySource {
|
|
|
675
750
|
function isStatic(element: any) {
|
|
676
751
|
if(typeof element !== "object" || element === null) return true
|
|
677
752
|
return element instanceof CanBeStatic ? element.$_isStatic() :
|
|
678
|
-
(element.constructor.name === "Object"
|
|
753
|
+
((element.constructor.name === "Object" || element.constructor.name === "Array")
|
|
754
|
+
? element[staticRuleSymbol]
|
|
755
|
+
: false)
|
|
679
756
|
}
|
|
680
757
|
|
|
681
758
|
function queryDescription(element: any, indent: string = "") {
|
|
@@ -706,6 +783,7 @@ function markStatic(element: any) {
|
|
|
706
783
|
function parameterJSON(element: any) {
|
|
707
784
|
if(typeof element !== "object" || element === null) return element
|
|
708
785
|
if(element instanceof QueryPropertyBase) return { type: 'property', path: element.$path }
|
|
786
|
+
if(Array.isArray(element)) return element.map(item => parameterJSON(item))
|
|
709
787
|
const output = {
|
|
710
788
|
type: 'object',
|
|
711
789
|
properties: {}
|
|
@@ -768,7 +846,7 @@ export class RangeRule extends QueryRule {
|
|
|
768
846
|
$_getSources(): RuleSource[] {
|
|
769
847
|
return [
|
|
770
848
|
new RuleSource(this, this.$input, getSource(this.$input), 'range')
|
|
771
|
-
].filter(s => s.input != null)
|
|
849
|
+
].filter(s => s.input?.$source != null)
|
|
772
850
|
}
|
|
773
851
|
|
|
774
852
|
$_parametersJSON(resultParameters: string[]) {
|
|
@@ -814,7 +892,7 @@ export class EqualsRule extends QueryRule {
|
|
|
814
892
|
return [
|
|
815
893
|
new RuleSource(this, this.$inputA, getSource(this.$inputA), 'object'),
|
|
816
894
|
new RuleSource(this, this.$inputB, getSource(this.$inputB), 'object')
|
|
817
|
-
].filter(s => s.input != null)
|
|
895
|
+
].filter(s => s.input?.$source != null)
|
|
818
896
|
}
|
|
819
897
|
|
|
820
898
|
$_parametersJSON(resultParameters: string[]) {
|
package/src/simpleIndex.db.js
CHANGED
|
@@ -18,7 +18,7 @@ async function autoIndex(input, output, { plan, properties }) {
|
|
|
18
18
|
|
|
19
19
|
async function gatherOutputData(next, context, oldContext) {
|
|
20
20
|
const outputContext = { ...context }
|
|
21
|
-
const oldOutputContext = { ...
|
|
21
|
+
const oldOutputContext = { ...oldContext }
|
|
22
22
|
/// first execute next to gather all data
|
|
23
23
|
for(const nextStep of next) {
|
|
24
24
|
await fetch(nextStep, outputContext, oldOutputContext)
|
package/src/simpleQuery.db.js
CHANGED
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
async function simpleQuery(input, output, { _query, ...params }) {
|
|
2
2
|
|
|
3
3
|
const plan = _query.plan
|
|
4
|
-
const idFunction = idFunction && eval(`(${_query.idFunction})`)
|
|
4
|
+
const idFunction = _query.idFunction && eval(`(${_query.idFunction})`)
|
|
5
5
|
|
|
6
6
|
function sourceChangeStream(source, by) {
|
|
7
|
+
/// TODO: support arrays that have object - prefixed range queries
|
|
7
8
|
if(typeof by === 'string') return source.object(by)
|
|
8
|
-
if(Array.isArray(by))
|
|
9
|
+
if(Array.isArray(by)) {
|
|
10
|
+
if(typeof by.at(-1) === 'string') {
|
|
11
|
+
return source.object(serializeKey(by))
|
|
12
|
+
} else {
|
|
13
|
+
if(by.slice(0, -1).every(item => typeof item === 'string')) {
|
|
14
|
+
const prefix = serializeKeyData(by.slice(0, -1))
|
|
15
|
+
const range = by.at(-1)
|
|
16
|
+
const prefixedRange = {
|
|
17
|
+
gt: range.gt ? prefix + range.gt : undefined,
|
|
18
|
+
gte: range.gte ? prefix + range.gte : undefined,
|
|
19
|
+
lt: range.lt ? prefix + range.lt : undefined,
|
|
20
|
+
lte: range.lte ? prefix + range.lte : undefined,
|
|
21
|
+
reverse: range.reverse,
|
|
22
|
+
limit: range.limit,
|
|
23
|
+
}
|
|
24
|
+
if(!(prefixedRange.gt || prefixedRange.gte)) {
|
|
25
|
+
prefixedRange.gte = prefix
|
|
26
|
+
}
|
|
27
|
+
if(!(prefixedRange.lt || prefixedRange.lte)) {
|
|
28
|
+
prefixedRange.lte = prefix + "\xFF\xFF\xFF\xFF"
|
|
29
|
+
}
|
|
30
|
+
return source.range(prefixedRange)
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error("Impossible to compute range from array: " + JSON.stringify(by))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
9
36
|
return source.range(by)
|
|
10
37
|
}
|
|
11
38
|
|
|
@@ -37,6 +64,28 @@ async function simpleQuery(input, output, { _query, ...params }) {
|
|
|
37
64
|
}
|
|
38
65
|
}
|
|
39
66
|
|
|
67
|
+
function applyChange(results, obj, oldObj) {
|
|
68
|
+
if(oldObj) {
|
|
69
|
+
const index = results.findIndex(result => result.id === oldObj.id)
|
|
70
|
+
if(index !== -1) {
|
|
71
|
+
if(obj) {
|
|
72
|
+
results[index] = obj
|
|
73
|
+
} else {
|
|
74
|
+
results.splice(index, 1)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else if(obj) {
|
|
78
|
+
const index = results.findIndex(result => result.id >= obj.id)
|
|
79
|
+
if(index === -1) {
|
|
80
|
+
results.push(obj)
|
|
81
|
+
} else if(results[index].id === obj.id) {
|
|
82
|
+
results[index] = obj
|
|
83
|
+
} else {
|
|
84
|
+
results.splice(index, 0, obj)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
40
89
|
class DataObservation {
|
|
41
90
|
|
|
42
91
|
#planStep = null
|
|
@@ -44,20 +93,30 @@ async function simpleQuery(input, output, { _query, ...params }) {
|
|
|
44
93
|
#source = null
|
|
45
94
|
#by = null
|
|
46
95
|
#onChange = null
|
|
47
|
-
|
|
96
|
+
#disposed = false
|
|
97
|
+
|
|
48
98
|
#observation = null
|
|
49
99
|
|
|
50
|
-
#
|
|
100
|
+
#dependentObservationsByNext
|
|
51
101
|
|
|
52
102
|
#resultsPromise = null
|
|
53
|
-
#results =
|
|
103
|
+
#results = []
|
|
104
|
+
#rawResults = []
|
|
105
|
+
|
|
106
|
+
#idFunction = null
|
|
54
107
|
|
|
55
|
-
constructor(planStep, context, source, by, onChange) {
|
|
108
|
+
constructor(planStep, context, source, by, onChange, idFunction) {
|
|
56
109
|
this.#planStep = planStep
|
|
57
110
|
this.#context = context
|
|
58
111
|
this.#source = source
|
|
59
112
|
this.#by = by
|
|
60
113
|
this.#onChange = onChange
|
|
114
|
+
this.#idFunction = idFunction
|
|
115
|
+
|
|
116
|
+
this.#dependentObservationsByNext = new Array(planStep.next.length)
|
|
117
|
+
for(const nextIndex in planStep.next) {
|
|
118
|
+
this.#dependentObservationsByNext[nextIndex] = new Map()
|
|
119
|
+
}
|
|
61
120
|
}
|
|
62
121
|
|
|
63
122
|
async start() {
|
|
@@ -65,111 +124,203 @@ async function simpleQuery(input, output, { _query, ...params }) {
|
|
|
65
124
|
const context = this.#context
|
|
66
125
|
if(!this.#source) this.#source = await getSource(planStep.execution.sourceType, planStep.execution.name)
|
|
67
126
|
if(!this.#by) this.#by = decodeParameter(planStep.execution.by, context, params)
|
|
68
|
-
|
|
69
|
-
const
|
|
127
|
+
//output.debug("STARTING OBSERVATION", planStep.execution.sourceType, planStep.execution.name, "BY", this.#by)
|
|
128
|
+
const sourceStream = sourceChangeStream(this.#source, this.#by)
|
|
129
|
+
const observationPromise = sourceStream.onChange(async (obj, oldObj) => {
|
|
130
|
+
/* output.debug(this.#planStep.execution.alias, "!", planStep.execution.sourceType, planStep.execution.name, "BY", this.#by,
|
|
131
|
+
'CHANGE', obj, oldObj) */
|
|
70
132
|
const id = obj?.id || oldObj?.id
|
|
71
133
|
if(!id) return
|
|
72
134
|
|
|
73
|
-
|
|
74
|
-
results.push(obj)
|
|
75
|
-
} else { // already fetched - maintain memory list of results
|
|
76
|
-
if(oldObj) {
|
|
77
|
-
const index = this.#results.findIndex(result => result.id === oldObj.id)
|
|
78
|
-
if(index !== -1) {
|
|
79
|
-
if(obj) {
|
|
80
|
-
this.#results[index] = obj
|
|
81
|
-
} else {
|
|
82
|
-
this.#results.splice(index, 1)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
} else if(obj) {
|
|
86
|
-
const index = this.#results.findIndex(result => result.id >= oldObj.id) // idempotency check
|
|
87
|
-
if(index === -1) {
|
|
88
|
-
results.push(obj)
|
|
89
|
-
} else if(this.#results[index].id === oldObj.id) {
|
|
90
|
-
this.#results[index] = obj
|
|
91
|
-
} else {
|
|
92
|
-
this.#results.splice(index, 0, obj)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
135
|
+
applyChange(this.#rawResults, obj, oldObj)
|
|
96
136
|
|
|
97
137
|
const nextContext = { ...context, [planStep.execution.alias]: obj }
|
|
98
138
|
const nextOldContext = { ...context, [planStep.execution.alias]: oldObj }
|
|
99
139
|
|
|
100
|
-
const objectObservations = []
|
|
140
|
+
//const objectObservations = []
|
|
141
|
+
|
|
142
|
+
const oldJoinedResults = oldObj ? await this.#joinResults([ oldObj ]) : []
|
|
101
143
|
|
|
102
|
-
for(const
|
|
144
|
+
for(const nextStepIndex in planStep.next) {
|
|
145
|
+
const nextStep = planStep.next[nextStepIndex]
|
|
103
146
|
const nextSource = await getSource(nextStep.execution.sourceType, nextStep.execution.name)
|
|
104
|
-
const nextBy = decodeParameter(nextStep.execution.by, nextContext, params)
|
|
105
|
-
const nextOldBy = decodeParameter(nextStep.execution.by, nextOldContext, params)
|
|
106
|
-
const nextByKey = nextBy &&
|
|
107
|
-
const nextOldByKey = nextOldBy &&
|
|
147
|
+
const nextBy = obj && decodeParameter(nextStep.execution.by, nextContext, params)
|
|
148
|
+
const nextOldBy = oldObj && decodeParameter(nextStep.execution.by, nextOldContext, params)
|
|
149
|
+
const nextByKey = nextBy && serializeKeyData(nextBy)
|
|
150
|
+
const nextOldByKey = nextOldBy && serializeKeyData(nextOldBy)
|
|
108
151
|
if(nextByKey !== nextOldByKey) {
|
|
109
|
-
|
|
110
|
-
|
|
152
|
+
const nextDependentObservations = this.#dependentObservationsByNext[nextStepIndex]
|
|
153
|
+
if(nextOldBy && nextDependentObservations.has(nextOldByKey)) {
|
|
154
|
+
const dependentObservation = nextDependentObservations.get(nextOldByKey)
|
|
111
155
|
dependentObservation.dispose()
|
|
112
|
-
|
|
156
|
+
nextDependentObservations.delete(nextOldByKey)
|
|
113
157
|
}
|
|
114
|
-
if(!
|
|
158
|
+
if(nextBy && !nextDependentObservations.has(nextByKey)) {
|
|
115
159
|
const dependentObservation = new DataObservation(nextStep, nextContext, nextSource, nextBy,
|
|
116
160
|
(context, oldContext, observation) => this.handleDependentChange(context, oldContext, observation, id))
|
|
117
|
-
|
|
161
|
+
nextDependentObservations.set(nextByKey, dependentObservation)
|
|
118
162
|
await dependentObservation.start()
|
|
119
163
|
}
|
|
120
164
|
}
|
|
121
|
-
if(nextByKey) objectObservations.push(this.#dependentObservations.get(nextByKey))
|
|
122
|
-
}
|
|
165
|
+
//if(nextByKey) objectObservations.push(this.#dependentObservations.get(nextByKey))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const newJoinedResults = obj ? await this.#joinResults([ obj ]) : []
|
|
169
|
+
|
|
170
|
+
//output.debug(this.#planStep.execution.alias, "oldJoinedResults", oldJoinedResults, "from", oldObj)
|
|
171
|
+
//output.debug(this.#planStep.execution.alias, "newJoinedResults", newJoinedResults, "from", obj)
|
|
172
|
+
|
|
173
|
+
await this.#joinedChanges(newJoinedResults, oldJoinedResults)
|
|
123
174
|
|
|
124
|
-
/// TODO: do object observations cross product, add self, and push change to parent
|
|
125
175
|
})
|
|
126
176
|
this.#resultsPromise = observationPromise.then(() => {
|
|
127
|
-
this.#results
|
|
128
|
-
return results
|
|
177
|
+
return this.#results
|
|
129
178
|
})
|
|
130
179
|
this.#observation = await observationPromise
|
|
131
180
|
}
|
|
132
181
|
|
|
182
|
+
async #joinedChange(obj, oldObj) {
|
|
183
|
+
applyChange(this.#results, obj, oldObj)
|
|
184
|
+
await this.#onChange(obj, oldObj, this)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async #joinedChanges(newJoinedResults, oldJoinedResults) {
|
|
188
|
+
for(const oldJoinedResult of oldJoinedResults) {
|
|
189
|
+
if(!oldJoinedResult) throw new Error("oldJoinedResult is null")
|
|
190
|
+
if(!newJoinedResults.some(newResult => newResult.id === oldJoinedResult.id))
|
|
191
|
+
this.#joinedChange(null, oldJoinedResult)
|
|
192
|
+
}
|
|
193
|
+
for(const newJoinedResult of newJoinedResults) {
|
|
194
|
+
const oldJoinedResult = oldJoinedResults.find(oldResult => oldResult.id === newJoinedResult.id)
|
|
195
|
+
if(oldJoinedResult) {
|
|
196
|
+
if(JSON.stringify(newJoinedResult) !== JSON.stringify(oldJoinedResult)) {
|
|
197
|
+
this.#joinedChange(newJoinedResult, oldJoinedResult)
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
this.#joinedChange(newJoinedResult, null)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async #joinResults(results) {
|
|
206
|
+
let joinedResults = results.map(result => ({
|
|
207
|
+
__result_id_patrs: [result.id],
|
|
208
|
+
[this.#planStep.execution.alias]: result
|
|
209
|
+
}))
|
|
210
|
+
//output.debug("joined results", joinedResults)
|
|
211
|
+
for(const nextId in this.#planStep.next) {
|
|
212
|
+
const nextDependentObservations = this.#dependentObservationsByNext[nextId]
|
|
213
|
+
const nextStep = this.#planStep.next[nextId]
|
|
214
|
+
// output.debug(" dep results", dependentObservation.#planStep.execution.alias, dependentResults)
|
|
215
|
+
joinedResults = (await Promise.all(joinedResults.map(
|
|
216
|
+
async (joinedResult) => {
|
|
217
|
+
const nextBy = decodeParameter(nextStep.execution.by, joinedResult, params)
|
|
218
|
+
const nextByKey = nextBy && serializeKeyData(nextBy)
|
|
219
|
+
const dependentObservation = nextDependentObservations.get(nextByKey)
|
|
220
|
+
let dependentResults = []
|
|
221
|
+
if(dependentObservation) {
|
|
222
|
+
dependentResults = await dependentObservation.results()
|
|
223
|
+
}
|
|
224
|
+
if(dependentResults.length === 0) {
|
|
225
|
+
if(nextStep.mandatory) return []
|
|
226
|
+
return [
|
|
227
|
+
{ /// TODO: check if optional (not mandatory)
|
|
228
|
+
...joinedResult,
|
|
229
|
+
[nextStep.execution.alias]: null
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
return dependentResults.map(dependentJoinedResult => ({
|
|
234
|
+
...dependentJoinedResult,
|
|
235
|
+
...joinedResult,
|
|
236
|
+
__result_id_patrs: joinedResult.__result_id_patrs.concat(dependentJoinedResult.__result_id_patrs)
|
|
237
|
+
}))
|
|
238
|
+
}
|
|
239
|
+
))).flat()
|
|
240
|
+
}
|
|
241
|
+
//output.debug("joinedResultsAfterDependencies", JSON.stringify(joinedResults, null, 2))
|
|
242
|
+
for(const joinedResult of joinedResults) {
|
|
243
|
+
/* if(this.#idFunction) {
|
|
244
|
+
output.debug("callIdFunction", _query.idFunction, "on", JSON.stringify(joinedResult, null, 2))
|
|
245
|
+
} */
|
|
246
|
+
joinedResult.id = serializeKey(
|
|
247
|
+
this.#idFunction ? this.#idFunction(joinedResult) : joinedResult.__result_id_patrs
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
return joinedResults
|
|
251
|
+
}
|
|
252
|
+
|
|
133
253
|
async results() {
|
|
134
254
|
if(!this.#results) return this.#results
|
|
135
255
|
return await this.#resultsPromise
|
|
136
256
|
}
|
|
137
257
|
|
|
138
258
|
async handleDependentChange(context, oldContext, observation, id) {
|
|
139
|
-
|
|
140
|
-
if(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
259
|
+
if(!this.#results) return
|
|
260
|
+
if(observation.#disposed) return
|
|
261
|
+
|
|
262
|
+
/* console.log("handleDependentChange", context, oldContext, id,
|
|
263
|
+
"observation", observation.#planStep.execution.alias,
|
|
264
|
+
"to", this.#planStep.execution.alias) */
|
|
265
|
+
|
|
266
|
+
const observationByJson = JSON.stringify(observation.#by)
|
|
267
|
+
|
|
268
|
+
const affectedRawResults = this.#rawResults.filter(result =>
|
|
269
|
+
JSON.stringify(decodeParameter(observation.#planStep.execution.by, {
|
|
270
|
+
[this.#planStep.execution.alias]: result
|
|
271
|
+
}, params)) === observationByJson
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
//console.log("affectedRawResults", affectedRawResults)
|
|
275
|
+
|
|
276
|
+
for(const affectedRawResult of affectedRawResults) {
|
|
277
|
+
const id = affectedRawResult.id
|
|
278
|
+
const newJoinedResults = await this.#joinResults([ affectedRawResult ])
|
|
279
|
+
const oldJoinedResults = this.#results.filter(
|
|
280
|
+
joinedResult => joinedResult[this.#planStep.execution.alias].id === id
|
|
281
|
+
)
|
|
282
|
+
//console.log("newJoinedResults", newJoinedResults)
|
|
283
|
+
//console.log("oldJoinedResults", oldJoinedResults)
|
|
284
|
+
await this.#joinedChanges(newJoinedResults, oldJoinedResults)
|
|
285
|
+
}
|
|
151
286
|
}
|
|
152
287
|
|
|
153
288
|
dispose() {
|
|
154
289
|
this.#observation.dispose()
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
290
|
+
for(const nextIndex in this.#dependentObservationsByNext) {
|
|
291
|
+
const nextDependentObservations = this.#dependentObservationsByNext[nextIndex]
|
|
292
|
+
for(const dependentObservation of nextDependentObservations.values()) {
|
|
293
|
+
dependentObservation.dispose()
|
|
294
|
+
}
|
|
295
|
+
nextDependentObservations.clear()
|
|
296
|
+
}
|
|
297
|
+
this.#results = []
|
|
298
|
+
this.#rawResults = []
|
|
158
299
|
this.#resultsPromise = null
|
|
159
300
|
this.#observation = null
|
|
160
301
|
this.#source = null
|
|
161
302
|
this.#by = null
|
|
162
303
|
this.#planStep = null
|
|
163
304
|
this.#context = null
|
|
305
|
+
this.#disposed = true
|
|
164
306
|
}
|
|
165
307
|
}
|
|
166
308
|
|
|
167
|
-
const rootObservations = plan.map(step => new DataObservation(step, {}, null, null,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
309
|
+
const rootObservations = plan.map(step => new DataObservation(step, {}, null, null,
|
|
310
|
+
(result, oldResult, observation) => {
|
|
311
|
+
if(result) delete result.__result_id_patrs
|
|
312
|
+
if(oldResult) delete oldResult.__result_id_patrs
|
|
313
|
+
output.change(result, oldResult)
|
|
314
|
+
},
|
|
315
|
+
idFunction
|
|
316
|
+
/* async (newContext, oldContext, observation) => {
|
|
317
|
+
const idParts = idFunction ? idFunction(newContext) : Object.values(newContext).map(v => v?.id)
|
|
318
|
+
const id = serializeKey(idParts)
|
|
319
|
+
await output.change({ ...newContext, id }, { ...oldContext, id })
|
|
320
|
+
} */
|
|
321
|
+
))
|
|
172
322
|
|
|
173
323
|
await Promise.all(rootObservations.map(observation => observation.start()))
|
|
324
|
+
await Promise.all(rootObservations.map(observation => observation.results()))
|
|
174
325
|
|
|
175
326
|
}
|