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