@live-change/simple-query 0.9.163 → 0.9.165

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/simple-query",
3
- "version": "0.9.163",
3
+ "version": "0.9.165",
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.163",
25
+ "@live-change/framework": "^0.9.165",
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": "c410e1dacd07daed9a5c55367abd7f19d9a575cc"
33
+ "gitHead": "687dab16bd0c6594544cdab70cd7eecc17bb510c"
34
34
  }
package/src/query.ts CHANGED
@@ -102,7 +102,7 @@ interface QueryDefinitionSpecification {
102
102
  sources?: Record<string, QuerySource>,
103
103
  code?: QueryCode,
104
104
  update?: boolean,
105
- id: Function | string,
105
+ id: Function,
106
106
  timeout?: number,
107
107
  requestTimeout?: number,
108
108
  validation?: (parameters: FrameworkQueryParameters, context: ContextBase) => Promise<any>
@@ -216,6 +216,7 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
216
216
  definition: QueryDefinitionSpecification
217
217
  properties: Record<string, PropertyDefinition<any>>
218
218
  rules: QueryRules
219
+ idFields: QueryInputLike[] | null
219
220
  firstRule: QueryRule
220
221
  rootSources: RuleSource[]
221
222
  ruleSources: RuleSource[]
@@ -265,6 +266,15 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
265
266
  }
266
267
  }
267
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
+
268
278
  computeRules() {
269
279
  const queryProperties = {}
270
280
  for(const propertyName in this.definition.properties) {
@@ -282,6 +292,7 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
282
292
 
283
293
  // run the code to collect relations
284
294
  this.rules = this.definition.code(queryProperties, queryInputs)
295
+ this.idFields = this.definition.id ? this.definition.id(queryInputs) : null
285
296
  }
286
297
 
287
298
  markStaticRules() {
@@ -410,33 +421,38 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
410
421
 
411
422
  const ruleParameters = JSON.parse(JSON.stringify(source.rule.$_parametersJSON(resultParameters)))
412
423
  console.log("RULE PARAMETERS", ruleParameters)
424
+ const mandatory = this.idFields?.find(idField => source.input.$alias === idField.$alias) ? true : undefined
413
425
  if(source.index) {
414
426
  const indexExecution = {
415
427
  ...source.index.$_executionJSON(),
416
428
  by: parameterEnsureRange(ruleParameters[Object.keys(ruleParameters)[0]])
417
429
  }
430
+ /// TODO: decide if id field is mandatory - if exports aliases that are required in id Fields or market mandatory
418
431
  const indexNext = [{
419
432
  execution: {
420
433
  operation: 'object',
421
- ...source.input.$_executionJSON(),
434
+ ...source.input.$_executionJSON(),
422
435
  by: {
423
436
  type: 'result',
424
437
  path: [indexExecution.alias, source.index.indexParts.at(-1).alias],
425
438
  },
426
439
  },
440
+ mandatory,
427
441
  next
428
442
  }]
429
443
  return {
430
444
  execution: indexExecution,
431
445
  next: indexNext
432
446
  }
433
- }
447
+ }
448
+
434
449
  const execution = {
435
- ...source.input.$_executionJSON(),
450
+ ...source.input.$_executionJSON(),
436
451
  by: ruleParameters[Object.keys(ruleParameters)[0]]
437
452
  }
438
453
  const executionPlan = {
439
454
  execution,
455
+ mandatory,
440
456
  next
441
457
  }
442
458
  return executionPlan
@@ -622,10 +638,12 @@ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
622
638
  prepareQuery() {
623
639
  this.createIndexes()
624
640
 
625
- console.log("########### PREPARING QUERY!!!!")
641
+ console.log("########### PREPARING QUERY!!!!")
626
642
 
627
643
  this.printRules()
628
644
 
645
+ this.printIdFields()
646
+
629
647
  this.printDependencies()
630
648
 
631
649
  this.computeExecutionPlan()
@@ -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 = { ...context }
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)
@@ -64,6 +64,28 @@ async function simpleQuery(input, output, { _query, ...params }) {
64
64
  }
65
65
  }
66
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
+
67
89
  class DataObservation {
68
90
 
69
91
  #planStep = null
@@ -71,13 +93,16 @@ async function simpleQuery(input, output, { _query, ...params }) {
71
93
  #source = null
72
94
  #by = null
73
95
  #onChange = null
74
-
96
+ #disposed = false
97
+
75
98
  #observation = null
76
99
 
77
- #dependentObservations = new Map()
100
+ #dependentObservationsByNext
78
101
 
79
102
  #resultsPromise = null
80
103
  #results = []
104
+ #rawResults = []
105
+
81
106
  #idFunction = null
82
107
 
83
108
  constructor(planStep, context, source, by, onChange, idFunction) {
@@ -87,6 +112,11 @@ async function simpleQuery(input, output, { _query, ...params }) {
87
112
  this.#by = by
88
113
  this.#onChange = onChange
89
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
+ }
90
120
  }
91
121
 
92
122
  async start() {
@@ -102,6 +132,8 @@ async function simpleQuery(input, output, { _query, ...params }) {
102
132
  const id = obj?.id || oldObj?.id
103
133
  if(!id) return
104
134
 
135
+ applyChange(this.#rawResults, obj, oldObj)
136
+
105
137
  const nextContext = { ...context, [planStep.execution.alias]: obj }
106
138
  const nextOldContext = { ...context, [planStep.execution.alias]: oldObj }
107
139
 
@@ -109,22 +141,24 @@ async function simpleQuery(input, output, { _query, ...params }) {
109
141
 
110
142
  const oldJoinedResults = oldObj ? await this.#joinResults([ oldObj ]) : []
111
143
 
112
- for(const nextStep of planStep.next) {
144
+ for(const nextStepIndex in planStep.next) {
145
+ const nextStep = planStep.next[nextStepIndex]
113
146
  const nextSource = await getSource(nextStep.execution.sourceType, nextStep.execution.name)
114
- const nextBy = decodeParameter(nextStep.execution.by, nextContext, params)
115
- const nextOldBy = decodeParameter(nextStep.execution.by, nextOldContext, params)
116
- const nextByKey = nextBy && serializeKeyData([nextStep.execution.alias, nextBy])
117
- const nextOldByKey = nextOldBy && serializeKeyData([nextStep.execution.alias, 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)
118
151
  if(nextByKey !== nextOldByKey) {
119
- if(this.#dependentObservations.has(nextOldByKey)) {
120
- const dependentObservation = this.#dependentObservations.get(nextOldByKey)
152
+ const nextDependentObservations = this.#dependentObservationsByNext[nextStepIndex]
153
+ if(nextOldBy && nextDependentObservations.has(nextOldByKey)) {
154
+ const dependentObservation = nextDependentObservations.get(nextOldByKey)
121
155
  dependentObservation.dispose()
122
- this.#dependentObservations.delete(nextOldByKey)
156
+ nextDependentObservations.delete(nextOldByKey)
123
157
  }
124
- if(!this.#dependentObservations.has(nextByKey)) {
158
+ if(nextBy && !nextDependentObservations.has(nextByKey)) {
125
159
  const dependentObservation = new DataObservation(nextStep, nextContext, nextSource, nextBy,
126
160
  (context, oldContext, observation) => this.handleDependentChange(context, oldContext, observation, id))
127
- this.#dependentObservations.set(nextByKey, dependentObservation)
161
+ nextDependentObservations.set(nextByKey, dependentObservation)
128
162
  await dependentObservation.start()
129
163
  }
130
164
  }
@@ -136,21 +170,7 @@ async function simpleQuery(input, output, { _query, ...params }) {
136
170
  //output.debug(this.#planStep.execution.alias, "oldJoinedResults", oldJoinedResults, "from", oldObj)
137
171
  //output.debug(this.#planStep.execution.alias, "newJoinedResults", newJoinedResults, "from", obj)
138
172
 
139
- for(const oldJoinedResult of oldJoinedResults) {
140
- if(!oldJoinedResult) throw new Error("oldJoinedResult is null")
141
- if(!newJoinedResults.some(newResult => newResult.id === oldJoinedResult.id))
142
- this.#joinedChange(null, oldJoinedResult)
143
- }
144
- for(const newJoinedResult of newJoinedResults) {
145
- const oldJoinedResult = oldJoinedResults.find(oldResult => oldResult.id === newJoinedResult.id)
146
- if(oldJoinedResult) {
147
- if(JSON.stringify(newJoinedResult) !== JSON.stringify(oldJoinedResult)) {
148
- this.#joinedChange(newJoinedResult, oldJoinedResult)
149
- }
150
- } else {
151
- this.#joinedChange(newJoinedResult, null)
152
- }
153
- }
173
+ await this.#joinedChanges(newJoinedResults, oldJoinedResults)
154
174
 
155
175
  })
156
176
  this.#resultsPromise = observationPromise.then(() => {
@@ -159,27 +179,27 @@ async function simpleQuery(input, output, { _query, ...params }) {
159
179
  this.#observation = await observationPromise
160
180
  }
161
181
 
162
- async #joinedChange(obj, oldObj, observation) {
163
- if(oldObj) {
164
- const index = this.#results.findIndex(result => result.id === oldObj.id)
165
- if(index !== -1) {
166
- if(obj) {
167
- this.#results[index] = obj
168
- } else {
169
- this.#results.splice(index, 1)
170
- }
171
- }
172
- } else if(obj) {
173
- const index = this.#results.findIndex(result => result.id >= obj.id)
174
- if(index === -1) {
175
- this.#results.push(obj)
176
- } else if(this.#results[index].id === obj.id) {
177
- this.#results[index] = obj
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
+ }
178
199
  } else {
179
- this.#results.splice(index, 0, obj)
200
+ this.#joinedChange(newJoinedResult, null)
180
201
  }
181
202
  }
182
- await this.#onChange(obj, oldObj, this)
183
203
  }
184
204
 
185
205
  async #joinResults(results) {
@@ -187,28 +207,42 @@ async function simpleQuery(input, output, { _query, ...params }) {
187
207
  __result_id_patrs: [result.id],
188
208
  [this.#planStep.execution.alias]: result
189
209
  }))
190
- // output.debug("joined results", joinedResults)
191
- for(const dependentObservation of this.#dependentObservations.values()) {
192
- const dependentResults = await dependentObservation.results()
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]
193
214
  // output.debug(" dep results", dependentObservation.#planStep.execution.alias, dependentResults)
194
- joinedResults = joinedResults.flatMap(
195
- (joinedResult) => {
196
- if(dependentResults.length === 0) return [
197
- { /// TODO: check if optional (not mandatory)
198
- ...joinedResult,
199
- [dependentObservation.#planStep.execution.alias]: null
200
- }
201
- ]
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
+ }
202
233
  return dependentResults.map(dependentJoinedResult => ({
203
234
  ...dependentJoinedResult,
204
235
  ...joinedResult,
205
236
  __result_id_patrs: joinedResult.__result_id_patrs.concat(dependentJoinedResult.__result_id_patrs)
206
237
  }))
207
238
  }
208
- )
239
+ ))).flat()
209
240
  }
210
- //output.debug("joinedResultsAfterDependencies", joinedResults)
211
- for(const joinedResult of joinedResults) {
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
+ } */
212
246
  joinedResult.id = serializeKey(
213
247
  this.#idFunction ? this.#idFunction(joinedResult) : joinedResult.__result_id_patrs
214
248
  )
@@ -223,28 +257,52 @@ async function simpleQuery(input, output, { _query, ...params }) {
223
257
 
224
258
  async handleDependentChange(context, oldContext, observation, id) {
225
259
  if(!this.#results) return
226
- // we need to find thre results affected by the change
227
- for(const result of this.#results) { // bunary search can be used here for better performance
228
- if(result.__result_id_patrs[0] === id) {
229
- const oldResult = { ...result }
230
- const alias = observation.#planStep.execution.alias
231
- result[alias] = context[alias]
232
- this.#onChange(result, oldResult, observation) /// TODO: handle cases when part is mandatory
233
- }
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)
234
285
  }
235
286
  }
236
287
 
237
288
  dispose() {
238
289
  this.#observation.dispose()
239
- this.#dependentObservations.forEach(dependentObservation => dependentObservation.dispose())
240
- this.#dependentObservations.clear()
241
- this.#results = null
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 = []
242
299
  this.#resultsPromise = null
243
300
  this.#observation = null
244
301
  this.#source = null
245
302
  this.#by = null
246
303
  this.#planStep = null
247
304
  this.#context = null
305
+ this.#disposed = true
248
306
  }
249
307
  }
250
308