@live-change/simple-query 0.9.141 → 0.9.143

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.141",
3
+ "version": "0.9.143",
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.138",
25
+ "@live-change/framework": "^0.9.143",
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": "2e34ef5af52ba93eaeb317a8a062b898db0673b8"
33
+ "gitHead": "5d1d50252b6abcf7149a961ac0b4c521c1c53b56"
34
34
  }
@@ -0,0 +1,3 @@
1
+ async function autoIndex(input, output, { }) {
2
+
3
+ }
package/src/query.ts ADDED
@@ -0,0 +1,864 @@
1
+ import {
2
+ PropertyDefinitionSpecification, ServiceDefinition, ServiceDefinitionSpecification
3
+ } from "@live-change/framework"
4
+
5
+ import { ModelDefinition, ForeignModelDefinition } from "@live-change/framework"
6
+
7
+ import { PropertyDefinition } from "@live-change/framework"
8
+
9
+ interface Range {
10
+ gt?: string
11
+ gte?: string
12
+ lt?: string
13
+ lte?: string,
14
+ reverse?: boolean,
15
+ limit?: number,
16
+ }
17
+
18
+ interface QueryParameters {
19
+ [key: string]: any
20
+ }
21
+
22
+ interface QueryInputs {
23
+ [key: string]: QueryInput
24
+ }
25
+
26
+ class CanBeStatic {
27
+ $_markStatic() {
28
+ this[staticRuleSymbol] = true
29
+ }
30
+
31
+ $_isStatic() {
32
+ return this[staticRuleSymbol]
33
+ }
34
+ }
35
+
36
+ class RuleSource {
37
+ rule: QueryRule
38
+ input: QueryInput
39
+ type: string
40
+ dependentBy: RuleSource[]
41
+ dependsOn: RuleSource[]
42
+ index: IndexInfo | null
43
+
44
+
45
+ constructor(rule: QueryRule, input: QueryInput, source: QuerySource, type: string) {
46
+ this.rule = rule
47
+ this.input = input
48
+ this.type = type
49
+ this.dependentBy = []
50
+ this.dependsOn = []
51
+ }
52
+ }
53
+
54
+
55
+ class QueryRule extends CanBeStatic {
56
+ $_hasStaticParameter() {
57
+ return false
58
+ }
59
+
60
+ $_getSources(): RuleSource[] {
61
+ return []
62
+ }
63
+
64
+ $_equals(other: QueryRule) {
65
+ return false
66
+ }
67
+
68
+ $_parametersJSON(resultParameters: string[]) {
69
+ return undefined
70
+ }
71
+ }
72
+
73
+ type QueryRules = QueryRule[]
74
+
75
+ type QueryCode = ((parameters: QueryParameters, inputs: QueryInputs) => QueryRules)
76
+
77
+ type QuerySource = ModelDefinition<any> | ForeignModelDefinition | any /// Query Definition will be recursive definition, so use any for now
78
+
79
+
80
+ interface QueryDefinitionSpecification {
81
+ name: string
82
+ properties: Record<string, PropertyDefinitionSpecification>
83
+ returns?: PropertyDefinitionSpecification,
84
+ sources?: Record<string, QuerySource>,
85
+ code?: QueryCode,
86
+ update?: boolean,
87
+ }
88
+
89
+ class OutputMapping {
90
+ result: string
91
+ path: string[]
92
+ alias: string
93
+
94
+ constructor(result: string, path: string[], alias: string) {
95
+ this.result = result
96
+ this.path = path
97
+ this.alias = alias
98
+ }
99
+
100
+ getDescription(indent: string = "") {
101
+ return `OutputMapping(${this.result}.${this.path.join(".")}, ${this.alias})`
102
+ }
103
+
104
+ equals(other: OutputMapping) {
105
+ return this.result === other.result && this.path.join(".") === other.path.join(".") && this.alias === other.alias
106
+ }
107
+ }
108
+
109
+ class IndexInfo {
110
+ singleSource: QuerySource | null = null
111
+ rules: QueryRule[]
112
+ indexParts: OutputMapping[]
113
+ name: string
114
+ sources: QuerySource[]
115
+
116
+ constructor(rules: QueryRule[], indexParts: OutputMapping[], service: ServiceDefinition<any>, name: string = undefined) {
117
+ this.rules = rules
118
+ this.indexParts = indexParts
119
+ this.sources = rules.map(rule => rule.$_getSources()).flat()
120
+ const firstSource = this.sources[0]
121
+ if(this.sources.every(source => source === firstSource)) this.singleSource = firstSource
122
+ if(this.singleSource) {
123
+ this.name = name || ''
124
+ + (this.singleSource.input.$source.serviceName === service.name ? "" : this.singleSource.input.$source.serviceName + "_")
125
+ + (
126
+ this.singleSource.input.$source.getTypeName() + "_" + (indexParts
127
+ .map(part => part.path.join(".")).join("_"))
128
+ )
129
+ } else {
130
+ this.name = name || ''
131
+ + (this.sources.every(source => source.input.$source.serviceName === service.name) ? "" : service.name + "_")
132
+ + (
133
+ indexParts.map(part => {
134
+ const source = this.sources.find(source => source.input.$alias === part.result)
135
+ if(!source) throw new Error("Source not found for index part: " + part)
136
+ return source.input.$source.getTypeName() + "_" + part.path.join(".")
137
+ }).join("_")
138
+ )
139
+ }
140
+ }
141
+
142
+ equals(other: IndexInfo) {
143
+ if(this.rules.length !== other.rules.length) return false
144
+ if(this.name !== other.name) return false
145
+ if(this.indexParts.length !== other.indexParts.length) return false
146
+ if(this.indexParts.some((part, i) => part !== other.indexParts[i])) return false
147
+ for(let i = 0; i < this.rules.length; i++) {
148
+ if(this.rules[i].$_equals(other.rules[i])) return false
149
+ }
150
+ return true
151
+ }
152
+
153
+ getDescription(indent: string = "") {
154
+ return `Index(${this.name}, `+
155
+ ((this.rules.length > 0)
156
+ ? `\n${indent} ${this.rules.map(rule => queryDescription(rule, indent + " ")).join("\n "+indent)}`
157
+ : "")+
158
+ `\n${indent} ${this.indexParts.map(part => part.getDescription(indent + " ")).join("\n "+indent)}`+
159
+ `\n${indent})`
160
+ }
161
+
162
+ $_executionJSON() {
163
+ if(!this.singleSource) throw new Error("Indexes with multiple sources are not supported")
164
+ return {
165
+ sourceType: "index",
166
+ name: this.name,
167
+ alias: this.singleSource.input.$alias + 'Indexed'
168
+ }
169
+ }
170
+
171
+ $_createQuery(service: ServiceDefinition<any>) {
172
+ return new QueryDefinition(service, {
173
+ name: this.name,
174
+ properties: {}
175
+ }, this.rules)
176
+ }
177
+ }
178
+
179
+ const staticRuleSymbol = Symbol("static")
180
+
181
+ type ExecutionStep = {
182
+ execution: any,
183
+ next: ExecutionStep[]
184
+ }
185
+
186
+ export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
187
+
188
+ service: ServiceDefinition<SDS>
189
+ definition: QueryDefinitionSpecification
190
+ properties: Record<string, PropertyDefinition<any>>
191
+ rules: QueryRules
192
+ firstRule: QueryRule
193
+ rootSources: RuleSource[]
194
+ ruleSources: RuleSource[]
195
+ indexes: IndexInfo[]
196
+
197
+ executionPlan: ExecutionStep[]
198
+ indexPlan: ExecutionStep[]
199
+
200
+ constructor(
201
+ serviceDefinition: ServiceDefinition<SDS>, definition: QueryDefinitionSpecification, rules: QueryRule[] = undefined
202
+ ) {
203
+ this.service = serviceDefinition
204
+ this.definition = definition
205
+
206
+ this.properties = Object.fromEntries(
207
+ Object.entries(definition.properties)
208
+ .map(
209
+ ([propertyName, propertyDefinition]) => [propertyName, new PropertyDefinition(propertyDefinition)]
210
+ )
211
+ )
212
+
213
+ if(rules) {
214
+ this.rules = rules
215
+ } else {
216
+ this.computeRules()
217
+ }
218
+
219
+ this.markStaticRules()
220
+
221
+ //this.printRules()
222
+
223
+ this.computeDependencies()
224
+ this.computeIndexes()
225
+
226
+ //this.printDependencies()
227
+
228
+ }
229
+
230
+ printRules() {
231
+ console.log("QUERY RULES:")
232
+ for(const key in this.rules) {
233
+ console.log(` ${key}:`, queryDescription(this.rules[key], ' '))
234
+ }
235
+ }
236
+
237
+ computeRules() {
238
+ const queryProperties = {}
239
+ for(const propertyName in this.definition.properties) {
240
+ const propertyDefinition = this.definition.properties[propertyName]
241
+ const base = new QueryPropertyBase([propertyName])
242
+ queryProperties[propertyName] = createQueryPropertyProxy(base, propertyName, this.properties[propertyName])
243
+ }
244
+
245
+ const queryInputs = {}
246
+ for(const sourceName in this.definition.sources) {
247
+ const propertyDefinition = this.definition.sources[sourceName]
248
+ const base = new QueryInputBase(this.definition.sources[sourceName], [], sourceName)
249
+ queryInputs[sourceName] = createQueryInputProxy(base)
250
+ }
251
+
252
+ // run the code to collect relations
253
+ this.rules = this.definition.code(queryProperties, queryInputs)
254
+ }
255
+
256
+ markStaticRules() {
257
+ for(const key in this.rules) {
258
+ const rule = this.rules[key]
259
+ markStatic(rule)
260
+ }
261
+ }
262
+
263
+ computeDependencies() {
264
+ const independentRules = this.rules.filter(rule => rule.$_hasStaticParameter())
265
+ if(independentRules.length > 1) {
266
+ console.error("Independent rules:")
267
+ for(const rule of independentRules) {
268
+ console.error(' ' + queryDescription(rule, ' '))
269
+ }
270
+ throw new Error("Multiple independent rules are not supported")
271
+ }
272
+ this.firstRule = independentRules[0] ?? this.rules[0]
273
+ this.rootSources = this.firstRule.$_getSources()
274
+ const providedSources = this.rootSources.slice()
275
+
276
+ const otherSources = []
277
+ for(const rule of this.rules) {
278
+ if(rule === this.firstRule) continue
279
+ const ruleSources = rule.$_getSources()
280
+ for(const ruleSource of ruleSources) {
281
+ otherSources.push(ruleSource)
282
+ }
283
+ }
284
+
285
+ this.ruleSources = this.rootSources.concat(otherSources)
286
+
287
+ while(otherSources.length > 0) {
288
+ /// find sources to link
289
+ let linked = false
290
+ for(const ruleSource of otherSources) {
291
+ for(const providedSource of providedSources) {
292
+ if(ruleSource == providedSource) {
293
+ throw new Error("Rule source is equal to provided source")
294
+ }
295
+ /* console.log("RULE SOURCE", queryDescription(ruleSource.source, ' '))
296
+ console.log("PROVIDED SOURCE", queryDescription(providedSource.source, ' '))
297
+ console.log("MATCH", ruleSource.source === providedSource.source) */
298
+ if(ruleSource.input.$_canBeUsedAsSource(providedSource.input)) {
299
+ ruleSource.dependsOn.push(providedSource)
300
+ providedSource.dependentBy.push(ruleSource)
301
+ otherSources.splice(otherSources.indexOf(ruleSource), 1)
302
+
303
+ for(const otherRuleSource of this.ruleSources) {
304
+ if(otherRuleSource.rule !== ruleSource.rule) continue
305
+ if(otherRuleSource === ruleSource) continue
306
+ if(providedSources.find(s => s === otherRuleSource)) continue
307
+ providedSources.push(otherRuleSource)
308
+ otherSources.splice(otherSources.indexOf(otherRuleSource), 1)
309
+ }
310
+ linked = true
311
+ break
312
+ }
313
+ if(linked) break;
314
+ }
315
+ }
316
+ if(!linked && otherSources.length > 0) {
317
+ console.error("Impossible to link query, found independent sources:")
318
+ for(const ruleSource of otherSources) {
319
+ console.error(' ' + queryDescription(ruleSource, ' '))
320
+ }
321
+ throw new Error("Impossible to link query, found independent sources")
322
+ }
323
+ }
324
+ }
325
+
326
+ printDependencies(indent: string = '') {
327
+ console.log('QUERY DEPENDENCIES:')
328
+ for(const key in this.rules) {
329
+ const rule = this.rules[key]
330
+ console.log(`${indent} RULE ${key}:`)
331
+ console.log(`${indent} ${queryDescription(rule, indent + ' ')}`)
332
+ console.log(`${indent} SOURCES:`)
333
+ for(const ruleSource of this.ruleSources) {
334
+ if(ruleSource.rule !== rule) continue
335
+ console.log(`${indent} ${queryDescription(ruleSource.input.$source, indent + ' ')} as ${ruleSource.input.$alias}`)
336
+ if(ruleSource.dependsOn.length > 0) {
337
+ console.log(`${indent} DEPENDS ON: `+
338
+ `${ruleSource.dependsOn.map(d => this.rules.indexOf(d.rule)).join(', ')}`
339
+ )
340
+ }
341
+ if(ruleSource.dependentBy.length > 0) {
342
+ console.log(`${indent} DEPENDENT BY: `+
343
+ `${ruleSource.dependentBy.map(d => this.rules.indexOf(d.rule)).join(', ')}`
344
+ )
345
+ }
346
+ if(ruleSource.index) {
347
+ console.log(`${indent} ${ruleSource.index.getDescription(indent + ' ')}`)
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ computeIndexes() {
354
+ this.indexes = []
355
+ for(const ruleSource of this.ruleSources) {
356
+ const potentialIndex = ruleSource.input.$_getIndexInfo(this.indexes, this.service)
357
+ if(!potentialIndex) continue
358
+ const existingIndex = this.indexes.find(index => index.equals(potentialIndex))
359
+ if(existingIndex) {
360
+ ruleSource.index = existingIndex
361
+ continue
362
+ }
363
+ this.indexes.push(potentialIndex)
364
+ ruleSource.index = potentialIndex
365
+ }
366
+ }
367
+
368
+ computeSourceExecutionPlan(source: RuleSource, resultParameters: string[]) {
369
+ /// TODO: W poniższej linii jest bug, powinno pobierać odmienne source, a nie te bezpośrednio zależne.
370
+ /// Poza tym przy pobieraniu wstecz nie koniecznie potrzeba indexów,
371
+ /// może trzeba wprowadzić index-forward i index-backward ?
372
+ /// A może trzeba wprowadzić analizę tego co mamy i co chcemy uzyskać w przyszłości ?
373
+ const next = source.dependentBy.map(dependent => {
374
+ const otherSource = this.ruleSources.find(s => s.rule === dependent.rule && s != dependent)
375
+ return this.computeSourceExecutionPlan(otherSource, [...resultParameters, source.input.$alias])
376
+ })
377
+ const ruleParameters = JSON.parse(JSON.stringify(source.rule.$_parametersJSON(resultParameters)))
378
+ if(source.index) {
379
+ const indexExecution = {
380
+ ...source.index.$_executionJSON(),
381
+ by: ruleParameters[Object.keys(ruleParameters)[0]]
382
+ }
383
+ const indexNext = [{
384
+ operation: 'object',
385
+ ...source.input.$_executionJSON(),
386
+ by: {
387
+ type: 'result',
388
+ path: [indexExecution.alias, source.index.indexParts.at(-1).alias],
389
+ },
390
+ next
391
+ }]
392
+ return {
393
+ execution: indexExecution,
394
+ next: indexNext
395
+ }
396
+ }
397
+ const execution = {
398
+ ...source.input.$_executionJSON(),
399
+ by: ruleParameters[Object.keys(ruleParameters)[0]]
400
+ }
401
+ const executionPlan = {
402
+ execution,
403
+ next
404
+ }
405
+ return executionPlan
406
+ }
407
+
408
+ computeExecutionPlan() {
409
+ this.executionPlan = []
410
+ for(const rootSource of this.rootSources) {
411
+ this.executionPlan.push(this.computeSourceExecutionPlan(rootSource, []))
412
+ }
413
+ }
414
+
415
+ computeSourceIndexPlan(source: RuleSource, resultParameters: string[], processed: RuleSource[], allProcessed: RuleSource[]) {
416
+ allProcessed.push(source)
417
+ const ruleSources = this.ruleSources
418
+ .filter(s => s.input.$source === source.input.$source && s != source && !processed.includes(s))
419
+ const next = ruleSources.map(ruleSource => {
420
+ const otherSource = this.ruleSources.find(s => s.rule === ruleSource.rule && s != ruleSource)
421
+ if(!otherSource) return null
422
+ if(processed.includes(otherSource)) return null
423
+ return this.computeSourceIndexPlan(
424
+ otherSource,
425
+ [...resultParameters, source.input.$alias],
426
+ [...processed, source],
427
+ allProcessed
428
+ )
429
+ }).filter(s => s !== null)
430
+ const ruleParameters = JSON.parse(JSON.stringify(source.rule.$_parametersJSON(resultParameters)))
431
+ if(source.index) {
432
+ const indexExecution = {
433
+ ...source.index.$_executionJSON(),
434
+ by: ruleParameters[Object.keys(ruleParameters)[0]]
435
+ }
436
+ const indexNext = [{
437
+ operation: 'object',
438
+ ...source.input.$_executionJSON(),
439
+ by: {
440
+ type: 'result',
441
+ path: [indexExecution.alias, source.index.indexParts.at(-1).alias],
442
+ },
443
+ next
444
+ }]
445
+ return {
446
+ execution: indexExecution,
447
+ next: indexNext
448
+ }
449
+ }
450
+ const execution = {
451
+ ...source.input.$_executionJSON(),
452
+ by: ruleParameters[Object.keys(ruleParameters)[0]],
453
+ }
454
+ const executionPlan = {
455
+ execution,
456
+ next
457
+ }
458
+ return executionPlan
459
+ }
460
+
461
+ computeIndexPlan(endMapping: OutputMapping[] = undefined) {
462
+ const indexSources = []
463
+ for(const ruleSource of this.ruleSources) {
464
+ const source = ruleSource.input.$source
465
+ if(indexSources.includes(source)) continue
466
+ indexSources.push(source)
467
+ }
468
+ this.indexPlan = indexSources.map(source => {
469
+ const firstFetch = {
470
+ sourceType: sourceType(source),
471
+ name: source.getTypeName(),
472
+ alias: source.getTypeName(),
473
+ by: { type: 'object', properties: {} } /// infinite range
474
+ }
475
+ const ruleSources = this.ruleSources.filter(s => s.input.$source === source)
476
+ const ruleSourcesAliases = Array.from(new Set(ruleSources.map(s => s.input.$alias)))
477
+ const next:ExecutionStep[] = ruleSourcesAliases.map((alias) => {
478
+ const processed = []
479
+
480
+ const aliasRuleSources = ruleSources.filter(s => s.input.$alias === alias)
481
+ const next:ExecutionStep[] = aliasRuleSources.map((input) => {
482
+ const rule = input.rule
483
+ const output = this.ruleSources.find(s => s.rule === rule && s != input)
484
+ if(!output) return null
485
+ const ignoredSources = ruleSources.filter(s => s.input.$source === source && s.input.$alias === input.input.$alias)
486
+ const executionPlan = this.computeSourceIndexPlan(output, [firstFetch.alias], [input, ...ignoredSources], processed)
487
+ executionPlan.execution.by = { type: 'result', path: [firstFetch.alias, ...input.input.$path] }
488
+ return {
489
+ ...executionPlan,
490
+ }
491
+ }).filter(s => s !== null)
492
+
493
+ const baseMapping = {
494
+ [alias]: [firstFetch.alias],
495
+ }
496
+ for(const processedSource of processed) {
497
+ baseMapping[processedSource.input.$alias] = [processedSource.input.$alias]
498
+ }
499
+ let mapping = baseMapping
500
+ if(endMapping) {
501
+ mapping = {}
502
+ for(const mp of endMapping) {
503
+ const base = baseMapping[mp.result]
504
+ if(!base) throw new Error("Base mapping "+mp.result+" not found")
505
+ mapping[mp.alias] = base.concat(mp.path)
506
+ }
507
+ }
508
+ const execution = {
509
+ operation: 'output',
510
+ mapping
511
+ }
512
+ return {
513
+ execution,
514
+ next
515
+ }
516
+ }).filter(s => s !== null)
517
+ return {
518
+ execution: firstFetch,
519
+ next
520
+ }
521
+ })
522
+ }
523
+
524
+ prepareQuery() {
525
+ console.log("CREATE INDEXES", this.indexes)
526
+
527
+ for(const index of this.indexes) {
528
+ const indexQuery = index.$_createQuery(this.service)
529
+ indexQuery.createIndex(index.name, index.indexParts)
530
+ }
531
+
532
+ process.exit(0)
533
+
534
+ this.computeExecutionPlan()
535
+ console.log("EXECUTION PLAN:")
536
+ console.log(JSON.stringify(this.executionPlan, null, 2))
537
+ /// TODO: create indexes used by query
538
+
539
+
540
+ /// TODO: prepare query
541
+
542
+ process.exit(0)
543
+ }
544
+
545
+ createIndex(name, mapping: OutputMapping[]) {
546
+ console.log("CREATE INDEX", name)
547
+ this.printRules()
548
+ console.log("OUTPUT MAPPINGS", mapping)
549
+ this.computeIndexPlan(mapping)
550
+ console.log("INDEX PLAN", JSON.stringify(this.indexPlan, null, 2))
551
+ /// TODO: create index from query
552
+
553
+ process.exit(0)
554
+ }
555
+ }
556
+
557
+
558
+ export type QueryFactoryFunction<SDS extends ServiceDefinitionSpecification> =
559
+ (definition: QueryDefinitionSpecification) => QueryDefinition<SDS>
560
+
561
+ export default function queryFactory<SDS extends ServiceDefinitionSpecification>(
562
+ serviceDefinition: ServiceDefinition<SDS>
563
+ ) {
564
+ const queryFactoryFunction: QueryFactoryFunction<SDS> =
565
+ (definition: QueryDefinitionSpecification) => {
566
+ const query = new QueryDefinition<SDS>(serviceDefinition, definition)
567
+ query.prepareQuery()
568
+ return query
569
+ }
570
+ return queryFactoryFunction
571
+ }
572
+
573
+ type RuleInput = QueryInputBase | QueryPropertyBase | any
574
+
575
+ function getSource(input: RuleInput): QuerySource {
576
+ if(input instanceof QueryInputBase) return input.$source
577
+ return null
578
+ }
579
+
580
+ function isStatic(element: any) {
581
+ if(typeof element !== "object" || element === null) return true
582
+ return element instanceof CanBeStatic ? element.$_isStatic() :
583
+ (element.constructor.name === "Object" ? element[staticRuleSymbol] : false)
584
+ }
585
+
586
+ function queryDescription(element: any, indent: string = "") {
587
+ const flags = isStatic(element) ? "static " : ""
588
+ if(typeof element.toQueryDescription === "function")
589
+ return flags + element.toQueryDescription(indent)
590
+ const fields = Object.entries(element)
591
+ .map(([key, value]) => `${indent} ${key}: ${queryDescription(value, indent + " ")}`)
592
+ .join("\n")
593
+ if(element.constructor.name !== "Object") return flags + `${element.constructor.name}(${fields})`
594
+ return flags + '{\n'+fields+`\n${indent}}`;
595
+ }
596
+
597
+ function markStatic(element: any) {
598
+ if(element instanceof CanBeStatic) return element.$_markStatic()
599
+ if(typeof element === "object" && element !== null) {
600
+ let allStatic = true
601
+ for(const key in element) {
602
+ markStatic(element[key])
603
+ if(!isStatic(element[key])) allStatic = false
604
+ }
605
+ if(allStatic) element[staticRuleSymbol] = true
606
+ return
607
+ }
608
+ }
609
+
610
+ function parameterJSON(element: any) {
611
+ if(typeof element !== "object" || element === null) return element
612
+ if(element instanceof QueryPropertyBase) return { property: element.$path }
613
+ const output = {
614
+ type: 'object',
615
+ properties: {}
616
+ }
617
+ for(const key in element) {
618
+ output.properties[key] = parameterJSON(element[key])
619
+ }
620
+ return output
621
+ }
622
+
623
+ function parametersJSONForInput(input: RuleInput, resultParameters: string[]) {
624
+ if(isStatic(input)) {
625
+ return parameterJSON(input)
626
+ } else {
627
+ const resultParameter = resultParameters.find(p => p === input.$alias)
628
+ if(resultParameter) {
629
+ return {
630
+ type: 'result',
631
+ path: [resultParameter, ...input.$path],
632
+ }
633
+ }
634
+ }
635
+ }
636
+
637
+ export class RangeRule extends QueryRule {
638
+ $input: RuleInput
639
+ $range: RuleInput
640
+
641
+ constructor(input: QueryInputBase, range: Range) {
642
+ super()
643
+ this.$input = input
644
+ this.$range = range
645
+ }
646
+
647
+ toQueryDescription(indent: string = "") {
648
+ return `Range(`+
649
+ `\n${indent} ${queryDescription(this.$input, indent + " ")}`+
650
+ `\n${indent} ${queryDescription(this.$range, indent + " ")}`+
651
+ `\n${indent})`
652
+ }
653
+
654
+ $_markStatic() {
655
+ markStatic(this.$input)
656
+ markStatic(this.$range)
657
+ //console.log("MARK STATIC", queryDescription(this.$input), queryDescription(this.$range), isStatic(this.$input), isStatic(this.$range))
658
+ this[staticRuleSymbol] = isStatic(this.$input) && isStatic(this.$range)
659
+ }
660
+
661
+ $_isStatic() {
662
+ return this[staticRuleSymbol]
663
+ }
664
+
665
+ $_hasStaticParameter() {
666
+ return isStatic(this.$input) || isStatic(this.$range)
667
+ }
668
+
669
+ $_getSources(): RuleSource[] {
670
+ return [
671
+ new RuleSource(this, this.$input, getSource(this.$input), 'range')
672
+ ].filter(s => s.input != null)
673
+ }
674
+
675
+ $_parametersJSON(resultParameters: string[]) {
676
+ return {
677
+ input: parametersJSONForInput(this.$input, resultParameters),
678
+ range: parametersJSONForInput(this.$range, resultParameters),
679
+ }
680
+ }
681
+ }
682
+
683
+ export class EqualsRule extends QueryRule {
684
+ $inputA: RuleInput
685
+ $inputB: RuleInput
686
+
687
+ constructor(inputA: RuleInput, inputB: RuleInput) {
688
+ super()
689
+ this.$inputA = inputA
690
+ this.$inputB = inputB
691
+ }
692
+
693
+ toQueryDescription(indent: string = "") {
694
+ return `Equals(`+
695
+ `\n${indent} ${queryDescription(this.$inputA, indent + " ")}`+
696
+ `\n${indent} ${queryDescription(this.$inputB, indent + " ")}`+
697
+ `\n${indent})`
698
+ }
699
+
700
+ $_markStatic() {
701
+ markStatic(this.$inputA)
702
+ markStatic(this.$inputB)
703
+ this[staticRuleSymbol] = isStatic(this.$inputA) && isStatic(this.$inputB)
704
+ }
705
+
706
+ $_isStatic() {
707
+ return this[staticRuleSymbol]
708
+ }
709
+
710
+ $_hasStaticParameter() {
711
+ return isStatic(this.$inputA) || isStatic(this.$inputB)
712
+ }
713
+
714
+ $_getSources(): RuleSource[] {
715
+ return [
716
+ new RuleSource(this, this.$inputA, getSource(this.$inputA), 'object'),
717
+ new RuleSource(this, this.$inputB, getSource(this.$inputB), 'object')
718
+ ].filter(s => s.input != null)
719
+ }
720
+
721
+ $_parametersJSON(resultParameters: string[]) {
722
+ return {
723
+ inputA: parametersJSONForInput(this.$inputA, resultParameters),
724
+ inputB: parametersJSONForInput(this.$inputB, resultParameters),
725
+ }
726
+ }
727
+ }
728
+
729
+ function sourceType(source: QuerySource) {
730
+ return (source instanceof ModelDefinition || source instanceof ForeignModelDefinition)
731
+ ? "table" : "index"
732
+ }
733
+
734
+ export class QueryInputBase extends CanBeStatic {
735
+ $source: QuerySource
736
+ $path: string[]
737
+ $alias: string
738
+
739
+ $inside(range: Range) {
740
+ return new RangeRule(this, range)
741
+ }
742
+
743
+ $equals(value: any) {
744
+ return new EqualsRule(this, value)
745
+ }
746
+
747
+ $as(alias: string) {
748
+ return createQueryInputProxy(new QueryInputBase(this.$source, this.$path, alias))
749
+ }
750
+
751
+ constructor(source: QuerySource, path: string[], alias: string = undefined) {
752
+ super()
753
+ this.$source = source
754
+ this.$path = path
755
+ this.$alias = alias
756
+ }
757
+
758
+ toQueryDescription(indent: string = "") {
759
+ return `QueryInput(\n${indent} source: ${queryDescription(this.$source, indent + " ")}`+
760
+ `\n${indent} path: ${this.$path.join(".")}`+
761
+ `\n${indent} alias: ${this.$alias}`+
762
+ `\n${indent})`
763
+ }
764
+
765
+ $_markStatic() {
766
+ /// ignore - QueryInput is not static
767
+ }
768
+
769
+ $_isStatic() {
770
+ return false
771
+ }
772
+
773
+ $_canBeUsedAsSource(input: QueryInputBase) {
774
+ return this.$source === input.$source && this.$alias === input.$alias
775
+ }
776
+
777
+ $_getIndexInfo(indexes: IndexInfo[], serviceDefinition: ServiceDefinition<any>): IndexInfo | null {
778
+ if(this.$path.length === 0) return null // id is used
779
+ if(this.$path.length === 1 && this.$path[0] === "id") return null // id is used
780
+ return new IndexInfo([
781
+ new RangeRule(this, {}),
782
+ ], [
783
+ new OutputMapping(this.$alias, this.$path, this.$path[this.$path.length - 1]),
784
+ new OutputMapping(this.$alias, ['id'], 'to')
785
+ ], serviceDefinition)
786
+ }
787
+
788
+ $_executionJSON() {
789
+ return {
790
+ sourceType: sourceType(this.$source),
791
+ name: this.$source.getTypeName(),
792
+ path: [...this.$path],
793
+ alias: this.$alias
794
+ }
795
+ }
796
+ $_equals(other: QueryInputBase) {
797
+ return this.$source === other.$source && this.$path.join(".") === other.$path.join(".") && this.$alias === other.$alias
798
+ }
799
+ }
800
+
801
+ export class QueryInput extends QueryInputBase {
802
+ [key: string]: QueryInputBase | any /// Proxy class will be added to this
803
+ }
804
+
805
+
806
+ export function createQueryInputProxy(
807
+ base: QueryInputBase
808
+ ) {
809
+ return new Proxy(base, {
810
+ get(target, prop, receiver) {
811
+ const foundInBase = Reflect.get(target, prop, receiver)
812
+ if(foundInBase) return foundInBase
813
+ const newBase = new QueryInputBase(base.$source, [...base.$path, prop as string], base.$alias)
814
+ const inputProxy = createQueryInputProxy(newBase)
815
+ return inputProxy
816
+ }
817
+ })
818
+ }
819
+
820
+ export class QueryPropertyBase extends CanBeStatic {
821
+ $path: string[]
822
+
823
+ constructor(path: string[]) {
824
+ super()
825
+ this.$path = path
826
+ }
827
+
828
+ toQueryDescription(indent: string = "") {
829
+ return `QueryProperty(${this.$path.join(".")})`
830
+ }
831
+
832
+ $_isStatic() {
833
+ return true
834
+ }
835
+
836
+ $_markStatic() {
837
+ /// ignore - QueryProperty is always static
838
+ }
839
+
840
+ $_executionJSON() {
841
+ return {
842
+ type: "property",
843
+ path: this.$path
844
+ }
845
+ }
846
+ }
847
+
848
+ export class QueryProperty extends QueryPropertyBase {
849
+ [key: string]: QueryPropertyBase | any /// Proxy class will be added to this
850
+ }
851
+
852
+ export function createQueryPropertyProxy(
853
+ base: QueryPropertyBase, propertyName: string, propertyDefinition: PropertyDefinition<any>
854
+ ) {
855
+ return new Proxy(base, {
856
+ get(target, prop, receiver) {
857
+ const foundInBase = Reflect.get(target, prop, receiver)
858
+ if(foundInBase) return foundInBase
859
+ const propertyBase = new QueryPropertyBase([...base.$path, propertyName])
860
+ const propertyProxy = createQueryPropertyProxy(propertyBase, propertyName, propertyDefinition)
861
+ return propertyProxy
862
+ }
863
+ })
864
+ }
package/query.ts DELETED
@@ -1,133 +0,0 @@
1
- import type {
2
- PropertyDefinitionSpecification, ServiceDefinition, ServiceDefinitionSpecification,
3
- ModelDefinition, ForeignModelDefinition
4
- } from "@live-change/framework"
5
-
6
- import { PropertyDefinition } from "@live-change/framework"
7
-
8
- interface QueryParameters {
9
- [key: string]: any
10
- }
11
-
12
- interface QueryInputs {
13
- [key: string]: QueryInput
14
- }
15
-
16
- type QueryCode = ((parameters: QueryParameters, inputs: QueryInputs) => any)
17
-
18
- interface QueryDefinitionSpecification {
19
- name: string
20
- properties: Record<string, PropertyDefinitionSpecification>
21
- returns?: PropertyDefinitionSpecification,
22
- code: QueryCode,
23
- sourceName: string,
24
- update: boolean,
25
- }
26
-
27
- export class QueryDefinition<SDS extends ServiceDefinitionSpecification> {
28
-
29
- service: ServiceDefinition<SDS>
30
- definition: QueryDefinitionSpecification
31
- properties: Record<string, PropertyDefinition<any>>
32
-
33
- constructor(serviceDefinition: ServiceDefinition<SDS>, definition: QueryDefinitionSpecification) {
34
- this.service = serviceDefinition
35
- this.definition = definition
36
-
37
- this.properties = Object.fromEntries(
38
- Object.entries(definition.properties)
39
- .map(
40
- ([propertyName, propertyDefinition]) => [propertyName, new PropertyDefinition(propertyDefinition)]
41
- )
42
- )
43
-
44
- const queryProperties = {}
45
- for(const propertyName in definition.properties) {
46
- const propertyDefinition = definition.properties[propertyName]
47
- const base = new QueryPropertyBase([propertyName])
48
- queryProperties[propertyName] = createQueryPropertyProxy(base, propertyName, this.properties[propertyName])
49
- }
50
-
51
- const queryInputs = {}
52
- for(const propertyName in definition.properties) {
53
- const propertyDefinition = definition.properties[propertyName]
54
- const base = new QueryInputBase(this, [propertyName])
55
- queryInputs[propertyName] = createQueryInputProxy(base, propertyName, this.properties[propertyName])
56
- }
57
-
58
- // run the code to collect relations
59
- this.definition.code(queryProperties, queryInputs)
60
-
61
- /// TODO: use collected relations to create indexes and prepared query
62
- }
63
- }
64
-
65
-
66
- export type QueryFactoryFunction<SDS extends ServiceDefinitionSpecification> =
67
- (definition: QueryDefinitionSpecification) => QueryDefinition<SDS>
68
-
69
- export default function queryFactory<SDS extends ServiceDefinitionSpecification>(
70
- serviceDefinition: ServiceDefinition<SDS>
71
- ) {
72
- const queryFactoryFunction: QueryFactoryFunction<SDS> =
73
- (definition: QueryDefinitionSpecification) => new QueryDefinition<SDS>(serviceDefinition, definition)
74
- return queryFactoryFunction
75
- }
76
-
77
-
78
- type QuerySource = ModelDefinition<any> | ForeignModelDefinition | any /// Query Definition will be recursive definition, so use any for now
79
-
80
- export class QueryInputBase {
81
- $source: QuerySource
82
- $path: string[]
83
-
84
- constructor(source: QuerySource, path: string[]) {
85
- this.$source = source
86
- this.$path = path
87
- }
88
- }
89
-
90
- export class QueryInput extends QueryInputBase {
91
- [key: string]: QueryInputBase | any /// Proxy class will be added to this
92
- }
93
-
94
-
95
- export function createQueryInputProxy(
96
- base: QueryInputBase, propertyName: string, propertyDefinition: PropertyDefinition<any>
97
- ) {
98
- return new Proxy(base, {
99
- get(target, prop, receiver) {
100
- const foundInBase = Reflect.get(target, prop, receiver)
101
- if(foundInBase) return foundInBase
102
- const propertyBase = new QueryInputBase(base.$source, [...base.$path, propertyName])
103
- const propertyProxy = createQueryInputProxy(propertyBase, propertyName, propertyDefinition)
104
- return propertyProxy
105
- }
106
- })
107
- }
108
-
109
- export class QueryPropertyBase {
110
- $path: string[]
111
-
112
- constructor(path: string[]) {
113
- this.$path = path
114
- }
115
- }
116
-
117
- export class QueryProperty extends QueryPropertyBase {
118
- [key: string]: QueryPropertyBase | any /// Proxy class will be added to this
119
- }
120
-
121
- export function createQueryPropertyProxy(
122
- base: QueryPropertyBase, propertyName: string, propertyDefinition: PropertyDefinition<any>
123
- ) {
124
- return new Proxy(base, {
125
- get(target, prop, receiver) {
126
- const foundInBase = Reflect.get(target, prop, receiver)
127
- if(foundInBase) return foundInBase
128
- const propertyBase = new QueryPropertyBase([...base.$path, propertyName])
129
- const propertyProxy = createQueryPropertyProxy(propertyBase, propertyName, propertyDefinition)
130
- return propertyProxy
131
- }
132
- })
133
- }
@@ -1,2 +0,0 @@
1
- import type { QueryDefinition, QueryInputBase } from "./query.js"
2
- import type { PropertyDefinition } from "@live-change/framework"
File without changes