@query-graph/core 1.0.0-next.1
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/LICENSE +21 -0
- package/README.md +620 -0
- package/definition.d.ts +87 -0
- package/definition.js +112 -0
- package/dsl.d.ts +687 -0
- package/dsl.js +742 -0
- package/index.d.ts +60 -0
- package/index.js +562 -0
- package/package.json +143 -0
package/dsl.js
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const GRAPH_DEFINITION_VERSION = 9
|
|
4
|
+
const SCALAR_TYPES = new Set([
|
|
5
|
+
'boolean',
|
|
6
|
+
'int32',
|
|
7
|
+
'int64',
|
|
8
|
+
'float64',
|
|
9
|
+
'decimal',
|
|
10
|
+
'string',
|
|
11
|
+
'date',
|
|
12
|
+
'dateTime',
|
|
13
|
+
'binary',
|
|
14
|
+
'json',
|
|
15
|
+
])
|
|
16
|
+
const GRAPH_MODULE_BRAND = Symbol('GraphModule')
|
|
17
|
+
const SUMMARY_FIELD_BRAND = Symbol('SummaryField')
|
|
18
|
+
|
|
19
|
+
const RELATION_CONFIGURATION_KEYS = new Set(['name', 'from', 'to', 'on', 'required', 'cardinality', 'selection'])
|
|
20
|
+
const CONSTRAINT_CONFIGURATION_KEYS = new Set(['predicate', 'when'])
|
|
21
|
+
const PROJECTION_CONFIGURATION_KEYS = new Set(['path', 'expression', 'default'])
|
|
22
|
+
const EXISTS_CONFIGURATION_KEYS = new Set(['from'])
|
|
23
|
+
const ORDERING_CONFIGURATION_KEYS = new Set(['name', 'by', 'default'])
|
|
24
|
+
|
|
25
|
+
function configurationOf(factory, value, allowedKeys) {
|
|
26
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
27
|
+
throw new TypeError(`${factory} expects a configuration object`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const unknownKeys = Reflect.ownKeys(value).filter((key) => typeof key !== 'string' || !allowedKeys.has(key))
|
|
31
|
+
if (unknownKeys.length > 0) {
|
|
32
|
+
const label = unknownKeys.length === 1 ? 'field' : 'fields'
|
|
33
|
+
const keys = unknownKeys.map((key) => JSON.stringify(String(key))).join(', ')
|
|
34
|
+
throw new TypeError(`${factory} received unknown configuration ${label} ${keys}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function fieldType(scalarType, options = {}) {
|
|
41
|
+
assertScalarType(scalarType)
|
|
42
|
+
|
|
43
|
+
const definition = { scalarType }
|
|
44
|
+
if (options.nullable === true) {
|
|
45
|
+
definition.nullable = true
|
|
46
|
+
}
|
|
47
|
+
if (options.selectable === false) {
|
|
48
|
+
definition.selectable = false
|
|
49
|
+
}
|
|
50
|
+
return definition
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function nullable(specification) {
|
|
54
|
+
return {
|
|
55
|
+
...normalizeFieldSpec(specification),
|
|
56
|
+
nullable: true,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hidden(specification) {
|
|
61
|
+
return {
|
|
62
|
+
...normalizeFieldSpec(specification),
|
|
63
|
+
selectable: false,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function source(key, fieldSpecifications) {
|
|
68
|
+
const fields = Object.entries(fieldSpecifications).map(([name, specification]) => ({
|
|
69
|
+
name,
|
|
70
|
+
...normalizeFieldSpec(specification),
|
|
71
|
+
}))
|
|
72
|
+
const sourceReference = { key, fields }
|
|
73
|
+
|
|
74
|
+
Object.defineProperty(sourceReference, 'field', {
|
|
75
|
+
enumerable: false,
|
|
76
|
+
value(name) {
|
|
77
|
+
return field(sourceReference, name)
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return sourceReference
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function field(sourceReference, name) {
|
|
85
|
+
const sourceKey = referenceName(sourceReference, 'source')
|
|
86
|
+
|
|
87
|
+
if (typeof sourceReference !== 'string' && !sourceReference.fields.some((candidate) => candidate.name === name)) {
|
|
88
|
+
throw new TypeError(`Source ${sourceKey} does not define field ${name}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
kind: 'field',
|
|
93
|
+
source: sourceKey,
|
|
94
|
+
field: name,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function requiredParameter(name, scalarType) {
|
|
99
|
+
return parameterDefinition(name, scalarType, true)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function optionalParameter(name, scalarType) {
|
|
103
|
+
return parameterDefinition(name, scalarType, false)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function requiredListParameter(name, scalarType) {
|
|
107
|
+
return parameterDefinition(name, scalarType, true, 'list')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function optionalListParameter(name, scalarType) {
|
|
111
|
+
return parameterDefinition(name, scalarType, false, 'list')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function param(parameterReference) {
|
|
115
|
+
if (typeof parameterReference !== 'string' && parameterReference?.shape === 'list') {
|
|
116
|
+
throw new TypeError(`List parameter ${parameterReference.name} cannot be used as a scalar expression`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
kind: 'parameter',
|
|
121
|
+
name: referenceName(parameterReference, 'parameter'),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function literal(value) {
|
|
126
|
+
if (value === null) {
|
|
127
|
+
return literalExpression({ kind: 'null' })
|
|
128
|
+
}
|
|
129
|
+
if (typeof value === 'boolean') {
|
|
130
|
+
return literalExpression({ kind: 'boolean', value })
|
|
131
|
+
}
|
|
132
|
+
if (typeof value === 'string') {
|
|
133
|
+
return literalExpression({ kind: 'string', value })
|
|
134
|
+
}
|
|
135
|
+
if (typeof value === 'number') {
|
|
136
|
+
return Number.isInteger(value) ? integer(value) : decimal(value)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
throw new TypeError('Literal must be null, boolean, string, or number')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function integer(value) {
|
|
143
|
+
if (!Number.isSafeInteger(value)) {
|
|
144
|
+
throw new TypeError('Integer literal must be a safe JavaScript integer')
|
|
145
|
+
}
|
|
146
|
+
return literalExpression({ kind: 'integer', value })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function decimal(value) {
|
|
150
|
+
const text = String(value)
|
|
151
|
+
if (!/^-?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) {
|
|
152
|
+
throw new TypeError(`Invalid decimal literal ${text}`)
|
|
153
|
+
}
|
|
154
|
+
return literalExpression({ kind: 'decimal', value: text })
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function eq(left, right) {
|
|
158
|
+
return binaryExpression('eq', left, right)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function neq(left, right) {
|
|
162
|
+
return binaryExpression('notEq', left, right)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function lt(left, right) {
|
|
166
|
+
return binaryExpression('lessThan', left, right)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function lte(left, right) {
|
|
170
|
+
return binaryExpression('lessThanOrEqual', left, right)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function gt(left, right) {
|
|
174
|
+
return binaryExpression('greaterThan', left, right)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function gte(left, right) {
|
|
178
|
+
return binaryExpression('greaterThanOrEqual', left, right)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function like(expression, pattern) {
|
|
182
|
+
return {
|
|
183
|
+
kind: 'like',
|
|
184
|
+
expression: asExpression(expression),
|
|
185
|
+
pattern: asExpression(pattern),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function inList(expression, values) {
|
|
190
|
+
return {
|
|
191
|
+
kind: 'in',
|
|
192
|
+
expression: asExpression(expression),
|
|
193
|
+
values: values.map(asExpression),
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function inParameter(expression, parameterReference) {
|
|
198
|
+
if (typeof parameterReference !== 'string' && parameterReference?.shape !== 'list') {
|
|
199
|
+
throw new TypeError(`Parameter ${parameterReference?.name ?? '<unknown>'} is not a list parameter`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
kind: 'inParameter',
|
|
204
|
+
expression: asExpression(expression),
|
|
205
|
+
parameter: referenceName(parameterReference, 'parameter'),
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function and(...expressions) {
|
|
209
|
+
return {
|
|
210
|
+
kind: 'and',
|
|
211
|
+
expressions: expressions.map(asExpression),
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function or(...expressions) {
|
|
216
|
+
return {
|
|
217
|
+
kind: 'or',
|
|
218
|
+
expressions: expressions.map(asExpression),
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function not(expression) {
|
|
223
|
+
return unaryExpression('not', expression)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isNull(expression) {
|
|
227
|
+
return unaryExpression('isNull', expression)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isNotNull(expression) {
|
|
231
|
+
return unaryExpression('isNotNull', expression)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function exists(sourceReference, predicate, options) {
|
|
235
|
+
const expression = {
|
|
236
|
+
kind: 'exists',
|
|
237
|
+
source: referenceName(sourceReference, 'source'),
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (options !== undefined) {
|
|
241
|
+
const configuration = configurationOf('exists', options, EXISTS_CONFIGURATION_KEYS)
|
|
242
|
+
if (configuration.from === undefined) {
|
|
243
|
+
throw new TypeError('exists requires configuration field "from"')
|
|
244
|
+
}
|
|
245
|
+
expression.from = referenceName(configuration.from, 'source')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (predicate !== undefined) {
|
|
249
|
+
expression.predicate = asExpression(predicate)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return expression
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function call(name, ...arguments_) {
|
|
256
|
+
return {
|
|
257
|
+
kind: 'function',
|
|
258
|
+
name,
|
|
259
|
+
arguments: arguments_.map(asExpression),
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function lower(expression) {
|
|
264
|
+
return call('lower', expression)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function upper(expression) {
|
|
268
|
+
return call('upper', expression)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function coalesce(first, second, ...rest) {
|
|
272
|
+
return call('coalesce', first, second, ...rest)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function concat(first, ...rest) {
|
|
276
|
+
return call('concat', first, ...rest)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function aggregate(functionName, expression) {
|
|
280
|
+
const definition = {
|
|
281
|
+
kind: 'aggregate',
|
|
282
|
+
function: functionName,
|
|
283
|
+
}
|
|
284
|
+
if (expression !== undefined) {
|
|
285
|
+
definition.expression = asExpression(expression)
|
|
286
|
+
}
|
|
287
|
+
return definition
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function count(expression) {
|
|
291
|
+
return aggregate('count', expression)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function countDistinct(expression) {
|
|
295
|
+
return aggregate('countDistinct', expression)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function sum(expression) {
|
|
299
|
+
return aggregate('sum', expression)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function average(expression) {
|
|
303
|
+
return aggregate('average', expression)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function minimum(expression) {
|
|
307
|
+
return aggregate('minimum', expression)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function maximum(expression) {
|
|
311
|
+
return aggregate('maximum', expression)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function relation(value) {
|
|
315
|
+
const configuration = configurationOf('relation', value, RELATION_CONFIGURATION_KEYS)
|
|
316
|
+
const definition = {
|
|
317
|
+
name: configuration.name,
|
|
318
|
+
from: referenceName(configuration.from, 'source'),
|
|
319
|
+
to: referenceName(configuration.to, 'source'),
|
|
320
|
+
on: asExpression(configuration.on),
|
|
321
|
+
}
|
|
322
|
+
if (configuration.required === true) {
|
|
323
|
+
definition.required = true
|
|
324
|
+
}
|
|
325
|
+
if (configuration.cardinality === 'many') {
|
|
326
|
+
definition.cardinality = 'many'
|
|
327
|
+
}
|
|
328
|
+
if (configuration.selection !== undefined) {
|
|
329
|
+
definition.selection = configuration.selection
|
|
330
|
+
}
|
|
331
|
+
return definition
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function constraint(value) {
|
|
335
|
+
const configuration = configurationOf('constraint', value, CONSTRAINT_CONFIGURATION_KEYS)
|
|
336
|
+
const definition = {
|
|
337
|
+
predicate: asExpression(configuration.predicate),
|
|
338
|
+
}
|
|
339
|
+
if (configuration.when !== undefined) {
|
|
340
|
+
definition.when = {
|
|
341
|
+
kind: 'parameterPresent',
|
|
342
|
+
parameter: referenceName(configuration.when, 'parameter'),
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return definition
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function project(value) {
|
|
349
|
+
const configuration = configurationOf('project', value, PROJECTION_CONFIGURATION_KEYS)
|
|
350
|
+
const definition = {
|
|
351
|
+
path: typeof configuration.path === 'string' ? configuration.path.split('.') : [...configuration.path],
|
|
352
|
+
expression: asExpression(configuration.expression),
|
|
353
|
+
}
|
|
354
|
+
if (configuration.default === true) {
|
|
355
|
+
definition.selectedByDefault = true
|
|
356
|
+
}
|
|
357
|
+
return definition
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function dimension(value) {
|
|
361
|
+
return summaryField('dimension', configurationOf('dimension', value, PROJECTION_CONFIGURATION_KEYS))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function measure(value) {
|
|
365
|
+
return summaryField('measure', configurationOf('measure', value, PROJECTION_CONFIGURATION_KEYS))
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function summaryField(role, configuration) {
|
|
369
|
+
const definition = {
|
|
370
|
+
...project(configuration),
|
|
371
|
+
role,
|
|
372
|
+
}
|
|
373
|
+
Object.defineProperty(definition, SUMMARY_FIELD_BRAND, {
|
|
374
|
+
value: true,
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
return definition
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function asc(expression, options = {}) {
|
|
381
|
+
return orderBy('asc', expression, options)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function desc(expression, options = {}) {
|
|
385
|
+
return orderBy('desc', expression, options)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function ordering(value) {
|
|
389
|
+
const configuration = configurationOf('ordering', value, ORDERING_CONFIGURATION_KEYS)
|
|
390
|
+
if (typeof configuration.name !== 'string' || configuration.name.trim() === '') {
|
|
391
|
+
throw new TypeError('ordering name must be a non-empty string')
|
|
392
|
+
}
|
|
393
|
+
if (!Array.isArray(configuration.by) || configuration.by.length === 0) {
|
|
394
|
+
throw new TypeError('ordering by must contain at least one order expression')
|
|
395
|
+
}
|
|
396
|
+
if (configuration.default !== undefined && typeof configuration.default !== 'boolean') {
|
|
397
|
+
throw new TypeError('ordering default must be a boolean')
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const definition = {
|
|
401
|
+
name: configuration.name,
|
|
402
|
+
orderBy: [...configuration.by],
|
|
403
|
+
}
|
|
404
|
+
if (configuration.default === true) {
|
|
405
|
+
definition.default = true
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return definition
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function firstBy(firstOrder, ...rest) {
|
|
412
|
+
if (firstOrder === undefined) {
|
|
413
|
+
throw new TypeError('firstBy requires at least one order expression')
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
kind: 'firstBy',
|
|
418
|
+
orderBy: [firstOrder, ...rest],
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function defineGraphModule(configuration) {
|
|
423
|
+
const content = composeDefinitionContent(configuration)
|
|
424
|
+
|
|
425
|
+
const module = {
|
|
426
|
+
name: configuration.name,
|
|
427
|
+
sources: content.sources,
|
|
428
|
+
parameters: content.parameters,
|
|
429
|
+
relations: content.relations,
|
|
430
|
+
constraints: content.constraints,
|
|
431
|
+
projection: content.projection,
|
|
432
|
+
orderings: content.orderings,
|
|
433
|
+
}
|
|
434
|
+
Object.defineProperty(module, GRAPH_MODULE_BRAND, { value: true })
|
|
435
|
+
|
|
436
|
+
return deepFreeze(module)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function defineGraph(configuration) {
|
|
440
|
+
return buildGraphDefinition(configuration, 'record')
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function defineSummaryGraph(configuration) {
|
|
444
|
+
if (configuration.projection !== undefined) {
|
|
445
|
+
throw new TypeError('Summary graph uses dimensions and measures instead of projection')
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return buildGraphDefinition(
|
|
449
|
+
{
|
|
450
|
+
...configuration,
|
|
451
|
+
projection: [...(configuration.dimensions ?? []), ...(configuration.measures ?? [])],
|
|
452
|
+
},
|
|
453
|
+
'summary',
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function buildGraphDefinition(configuration, mode) {
|
|
458
|
+
const content = composeDefinitionContent(configuration)
|
|
459
|
+
validateProjectionMode(content.projection, mode)
|
|
460
|
+
|
|
461
|
+
return deepFreeze({
|
|
462
|
+
schemaVersion: GRAPH_DEFINITION_VERSION,
|
|
463
|
+
name: configuration.name,
|
|
464
|
+
root: referenceName(configuration.root, 'source'),
|
|
465
|
+
sources: content.sources.map(copySource),
|
|
466
|
+
parameters: content.parameters,
|
|
467
|
+
relations: content.relations,
|
|
468
|
+
constraints: content.constraints,
|
|
469
|
+
projection: {
|
|
470
|
+
fields: content.projection,
|
|
471
|
+
},
|
|
472
|
+
orderings: content.orderings,
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function validateProjectionMode(projection, mode) {
|
|
477
|
+
if (mode === 'summary') {
|
|
478
|
+
if (projection.length === 0) {
|
|
479
|
+
throw new TypeError('Summary graph must define at least one dimension or measure')
|
|
480
|
+
}
|
|
481
|
+
const invalid = projection.find((field) => field.role !== 'dimension' && field.role !== 'measure')
|
|
482
|
+
if (invalid !== undefined) {
|
|
483
|
+
throw new TypeError(
|
|
484
|
+
`Summary graph projection field ${JSON.stringify(invalid.path.join('.'))} has no summary role`,
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
return
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const summaryField = projection.find((field) => field.role === 'dimension' || field.role === 'measure')
|
|
491
|
+
if (summaryField !== undefined) {
|
|
492
|
+
throw new TypeError(
|
|
493
|
+
`Record graph projection field ${JSON.stringify(summaryField.path.join('.'))} has a summary role`,
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function composeDefinitionContent(configuration) {
|
|
499
|
+
const content = {
|
|
500
|
+
sources: [],
|
|
501
|
+
parameters: [],
|
|
502
|
+
relations: [],
|
|
503
|
+
constraints: [],
|
|
504
|
+
projection: [],
|
|
505
|
+
orderings: [],
|
|
506
|
+
}
|
|
507
|
+
const indexes = {
|
|
508
|
+
sources: new Map(),
|
|
509
|
+
parameters: new Map(),
|
|
510
|
+
relations: new Map(),
|
|
511
|
+
constraints: new Set(),
|
|
512
|
+
projection: new Map(),
|
|
513
|
+
orderings: new Map(),
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
for (const [index, module] of (configuration.modules ?? []).entries()) {
|
|
517
|
+
if (module?.[GRAPH_MODULE_BRAND] !== true) {
|
|
518
|
+
throw new TypeError(`Graph module at index ${index} was not created by defineGraphModule`)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
addDefinitionPart(content, indexes, module, `graph module ${JSON.stringify(module.name)}`)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
addDefinitionPart(content, indexes, configuration, `definition ${JSON.stringify(configuration.name)}`)
|
|
525
|
+
return content
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function addDefinitionPart(content, indexes, part, owner) {
|
|
529
|
+
addNamedDefinitions(content.sources, indexes.sources, part.sources, 'source', owner, (source) => source.key)
|
|
530
|
+
addNamedDefinitions(
|
|
531
|
+
content.parameters,
|
|
532
|
+
indexes.parameters,
|
|
533
|
+
part.parameters,
|
|
534
|
+
'parameter',
|
|
535
|
+
owner,
|
|
536
|
+
(parameter) => parameter.name,
|
|
537
|
+
)
|
|
538
|
+
addNamedDefinitions(
|
|
539
|
+
content.relations,
|
|
540
|
+
indexes.relations,
|
|
541
|
+
part.relations,
|
|
542
|
+
'relation',
|
|
543
|
+
owner,
|
|
544
|
+
(relation) => relation.name,
|
|
545
|
+
)
|
|
546
|
+
addIdentityDefinitions(content.constraints, indexes.constraints, part.constraints)
|
|
547
|
+
addNamedDefinitions(content.projection, indexes.projection, part.projection, 'projection', owner, (projection) =>
|
|
548
|
+
projection.path.join('.'),
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
addNamedDefinitions(
|
|
552
|
+
content.orderings,
|
|
553
|
+
indexes.orderings,
|
|
554
|
+
part.orderings,
|
|
555
|
+
'ordering',
|
|
556
|
+
owner,
|
|
557
|
+
(orderingDefinition) => orderingDefinition.name,
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function addNamedDefinitions(target, index, definitions, type, owner, getKey) {
|
|
562
|
+
for (const definition of definitions ?? []) {
|
|
563
|
+
const key = getKey(definition)
|
|
564
|
+
const existing = index.get(key)
|
|
565
|
+
|
|
566
|
+
if (existing === undefined) {
|
|
567
|
+
index.set(key, { definition, owner })
|
|
568
|
+
target.push(definition)
|
|
569
|
+
continue
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (existing.definition !== definition) {
|
|
573
|
+
throw new TypeError(`Conflicting ${type} ${JSON.stringify(key)} from ${existing.owner} and ${owner}`)
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function addIdentityDefinitions(target, index, definitions) {
|
|
579
|
+
for (const definition of definitions ?? []) {
|
|
580
|
+
if (!index.has(definition)) {
|
|
581
|
+
index.add(definition)
|
|
582
|
+
target.push(definition)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function normalizeFieldSpec(specification) {
|
|
588
|
+
if (typeof specification === 'string') {
|
|
589
|
+
return fieldType(specification)
|
|
590
|
+
}
|
|
591
|
+
if (specification && typeof specification === 'object') {
|
|
592
|
+
return fieldType(specification.scalarType, specification)
|
|
593
|
+
}
|
|
594
|
+
throw new TypeError('Field specification must contain a scalar type')
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function parameterDefinition(name, scalarType, required, shape = 'scalar') {
|
|
598
|
+
assertScalarType(scalarType)
|
|
599
|
+
const definition = { name, scalarType }
|
|
600
|
+
if (required) {
|
|
601
|
+
definition.required = true
|
|
602
|
+
}
|
|
603
|
+
if (shape === 'list') {
|
|
604
|
+
definition.shape = 'list'
|
|
605
|
+
}
|
|
606
|
+
return definition
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function literalExpression(value) {
|
|
610
|
+
return {
|
|
611
|
+
kind: 'literal',
|
|
612
|
+
value,
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function binaryExpression(kind, left, right) {
|
|
617
|
+
return {
|
|
618
|
+
kind,
|
|
619
|
+
left: asExpression(left),
|
|
620
|
+
right: asExpression(right),
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function unaryExpression(kind, expression) {
|
|
625
|
+
return {
|
|
626
|
+
kind,
|
|
627
|
+
expression: asExpression(expression),
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function asExpression(value) {
|
|
632
|
+
if (value?.[SUMMARY_FIELD_BRAND] === true) {
|
|
633
|
+
return value.expression
|
|
634
|
+
}
|
|
635
|
+
if (value && typeof value === 'object' && typeof value.kind === 'string') {
|
|
636
|
+
return value
|
|
637
|
+
}
|
|
638
|
+
return literal(value)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function orderBy(direction, expression, options) {
|
|
642
|
+
const definition = {
|
|
643
|
+
expression: asExpression(expression),
|
|
644
|
+
direction,
|
|
645
|
+
}
|
|
646
|
+
if (options.nulls !== undefined) {
|
|
647
|
+
definition.nulls = options.nulls
|
|
648
|
+
}
|
|
649
|
+
return definition
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function referenceName(reference, type) {
|
|
653
|
+
if (typeof reference === 'string') {
|
|
654
|
+
return reference
|
|
655
|
+
}
|
|
656
|
+
if (type === 'source') {
|
|
657
|
+
if (reference && typeof reference.key === 'string') {
|
|
658
|
+
return reference.key
|
|
659
|
+
}
|
|
660
|
+
throw new TypeError('Invalid source reference')
|
|
661
|
+
}
|
|
662
|
+
if (reference && typeof reference.name === 'string') {
|
|
663
|
+
return reference.name
|
|
664
|
+
}
|
|
665
|
+
throw new TypeError(`Invalid ${type} reference`)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function copySource(sourceReference) {
|
|
669
|
+
return {
|
|
670
|
+
key: sourceReference.key,
|
|
671
|
+
fields: sourceReference.fields.map((fieldDefinition) => ({ ...fieldDefinition })),
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function deepFreeze(value, seen = new WeakSet()) {
|
|
676
|
+
if (value === null || typeof value !== 'object' || seen.has(value)) {
|
|
677
|
+
return value
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
seen.add(value)
|
|
681
|
+
for (const property of Reflect.ownKeys(value)) {
|
|
682
|
+
deepFreeze(value[property], seen)
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return Object.freeze(value)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function assertScalarType(scalarType) {
|
|
689
|
+
if (!SCALAR_TYPES.has(scalarType)) {
|
|
690
|
+
throw new TypeError(`Unknown scalar type ${scalarType}`)
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
exports.fieldType = fieldType
|
|
695
|
+
exports.nullable = nullable
|
|
696
|
+
exports.hidden = hidden
|
|
697
|
+
exports.source = source
|
|
698
|
+
exports.requiredParameter = requiredParameter
|
|
699
|
+
exports.optionalParameter = optionalParameter
|
|
700
|
+
exports.param = param
|
|
701
|
+
exports.literal = literal
|
|
702
|
+
exports.integer = integer
|
|
703
|
+
exports.decimal = decimal
|
|
704
|
+
exports.requiredListParameter = requiredListParameter
|
|
705
|
+
exports.optionalListParameter = optionalListParameter
|
|
706
|
+
exports.eq = eq
|
|
707
|
+
exports.neq = neq
|
|
708
|
+
exports.lt = lt
|
|
709
|
+
exports.lte = lte
|
|
710
|
+
exports.gt = gt
|
|
711
|
+
exports.gte = gte
|
|
712
|
+
exports.like = like
|
|
713
|
+
exports.inList = inList
|
|
714
|
+
exports.and = and
|
|
715
|
+
exports.or = or
|
|
716
|
+
exports.not = not
|
|
717
|
+
exports.isNull = isNull
|
|
718
|
+
exports.isNotNull = isNotNull
|
|
719
|
+
exports.inParameter = inParameter
|
|
720
|
+
exports.exists = exists
|
|
721
|
+
exports.lower = lower
|
|
722
|
+
exports.upper = upper
|
|
723
|
+
exports.coalesce = coalesce
|
|
724
|
+
exports.concat = concat
|
|
725
|
+
exports.count = count
|
|
726
|
+
exports.countDistinct = countDistinct
|
|
727
|
+
exports.sum = sum
|
|
728
|
+
exports.average = average
|
|
729
|
+
exports.minimum = minimum
|
|
730
|
+
exports.maximum = maximum
|
|
731
|
+
exports.relation = relation
|
|
732
|
+
exports.constraint = constraint
|
|
733
|
+
exports.ordering = ordering
|
|
734
|
+
exports.project = project
|
|
735
|
+
exports.dimension = dimension
|
|
736
|
+
exports.measure = measure
|
|
737
|
+
exports.asc = asc
|
|
738
|
+
exports.desc = desc
|
|
739
|
+
exports.defineGraphModule = defineGraphModule
|
|
740
|
+
exports.defineGraph = defineGraph
|
|
741
|
+
exports.defineSummaryGraph = defineSummaryGraph
|
|
742
|
+
exports.firstBy = firstBy
|