@query-graph/core 1.1.4 → 1.1.5
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/README.md +95 -969
- package/definition.d.ts +11 -0
- package/definition.js +1 -2
- package/docs/authoring.md +161 -0
- package/docs/composition.md +100 -0
- package/docs/diagnostics.md +52 -0
- package/docs/sql.md +68 -0
- package/docs/type-system.md +86 -0
- package/dsl.d.ts +182 -1839
- package/dsl.js +57 -763
- package/index.d.ts +8 -100
- package/index.js +13 -568
- package/lib/authoring/definition.js +425 -0
- package/lib/authoring/primitives.js +433 -0
- package/lib/authoring/symbols.js +4 -0
- package/{composition.js → lib/composition.js} +9 -35
- package/lib/configuration.js +66 -0
- package/native.d.ts +98 -0
- package/native.js +714 -0
- package/package.json +16 -11
- package/types/authoring.d.ts +667 -0
- package/types/composition.d.ts +327 -0
- package/types/model.d.ts +425 -0
- package/types/result.d.ts +394 -0
- package/types/runtime.d.ts +168 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
configurationOf,
|
|
5
|
+
nonEmptyString,
|
|
6
|
+
objectOf,
|
|
7
|
+
optionalArray,
|
|
8
|
+
optionalBoolean,
|
|
9
|
+
optionalEnum,
|
|
10
|
+
} = require('../configuration.js')
|
|
11
|
+
const { asExpression, copySource, referenceName } = require('./primitives.js')
|
|
12
|
+
const { GRAPH_MODULE_BRAND, SUMMARY_FIELD_BRAND } = require('./symbols.js')
|
|
13
|
+
|
|
14
|
+
const GRAPH_DEFINITION_VERSION = 10
|
|
15
|
+
const RELATION_CONFIGURATION_KEYS = new Set(['name', 'from', 'to', 'on', 'required', 'cardinality', 'selection'])
|
|
16
|
+
const CONSTRAINT_CONFIGURATION_KEYS = new Set(['predicate', 'when'])
|
|
17
|
+
const PROJECTION_CONFIGURATION_KEYS = new Set(['path', 'expression', 'default'])
|
|
18
|
+
const PROJECTION_OBJECT_CONFIGURATION_KEYS = new Set(['path', 'presence'])
|
|
19
|
+
const ORDERING_CONFIGURATION_KEYS = new Set(['name', 'by', 'default'])
|
|
20
|
+
const ORDER_BY_OPTIONS_KEYS = new Set(['nulls'])
|
|
21
|
+
const GRAPH_MODULE_CONFIGURATION_KEYS = new Set([
|
|
22
|
+
'name',
|
|
23
|
+
'modules',
|
|
24
|
+
'sources',
|
|
25
|
+
'parameters',
|
|
26
|
+
'relations',
|
|
27
|
+
'constraints',
|
|
28
|
+
'projection',
|
|
29
|
+
'objects',
|
|
30
|
+
'orderings',
|
|
31
|
+
])
|
|
32
|
+
const GRAPH_CONFIGURATION_KEYS = new Set([...GRAPH_MODULE_CONFIGURATION_KEYS, 'root'])
|
|
33
|
+
const SUMMARY_GRAPH_CONFIGURATION_KEYS = new Set([
|
|
34
|
+
'name',
|
|
35
|
+
'root',
|
|
36
|
+
'modules',
|
|
37
|
+
'sources',
|
|
38
|
+
'parameters',
|
|
39
|
+
'relations',
|
|
40
|
+
'constraints',
|
|
41
|
+
'dimensions',
|
|
42
|
+
'measures',
|
|
43
|
+
'orderings',
|
|
44
|
+
])
|
|
45
|
+
const RELATION_CARDINALITIES = new Set(['one', 'many'])
|
|
46
|
+
const NULLS_ORDERS = new Set(['first', 'last'])
|
|
47
|
+
const DEFINITION_LIST_KEYS = [
|
|
48
|
+
'modules',
|
|
49
|
+
'sources',
|
|
50
|
+
'parameters',
|
|
51
|
+
'relations',
|
|
52
|
+
'constraints',
|
|
53
|
+
'projection',
|
|
54
|
+
'objects',
|
|
55
|
+
'orderings',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
function relation(value) {
|
|
59
|
+
const configuration = configurationOf('relation', value, RELATION_CONFIGURATION_KEYS)
|
|
60
|
+
nonEmptyString(configuration.name, 'relation name')
|
|
61
|
+
optionalBoolean(configuration.required, 'relation required')
|
|
62
|
+
optionalEnum(configuration.cardinality, RELATION_CARDINALITIES, 'relation cardinality')
|
|
63
|
+
|
|
64
|
+
const definition = {
|
|
65
|
+
name: configuration.name,
|
|
66
|
+
from: referenceName(configuration.from, 'source'),
|
|
67
|
+
to: referenceName(configuration.to, 'source'),
|
|
68
|
+
on: asExpression(configuration.on),
|
|
69
|
+
}
|
|
70
|
+
if (configuration.required === true) {
|
|
71
|
+
definition.required = true
|
|
72
|
+
}
|
|
73
|
+
if (configuration.cardinality === 'many') {
|
|
74
|
+
definition.cardinality = 'many'
|
|
75
|
+
}
|
|
76
|
+
if (configuration.selection !== undefined) {
|
|
77
|
+
definition.selection = configuration.selection
|
|
78
|
+
}
|
|
79
|
+
return definition
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function constraint(value) {
|
|
83
|
+
const configuration = configurationOf('constraint', value, CONSTRAINT_CONFIGURATION_KEYS)
|
|
84
|
+
const definition = {
|
|
85
|
+
predicate: asExpression(configuration.predicate),
|
|
86
|
+
}
|
|
87
|
+
if (configuration.when !== undefined) {
|
|
88
|
+
definition.when = {
|
|
89
|
+
kind: 'parameterPresent',
|
|
90
|
+
parameter: referenceName(configuration.when, 'parameter'),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return definition
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function project(value) {
|
|
97
|
+
const configuration = configurationOf('project', value, PROJECTION_CONFIGURATION_KEYS)
|
|
98
|
+
optionalBoolean(configuration.default, 'project default')
|
|
99
|
+
const definition = {
|
|
100
|
+
path: projectionPath(configuration.path, 'project path'),
|
|
101
|
+
expression: asExpression(configuration.expression),
|
|
102
|
+
}
|
|
103
|
+
if (configuration.default === true) {
|
|
104
|
+
definition.selectedByDefault = true
|
|
105
|
+
}
|
|
106
|
+
return definition
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function projectObject(value) {
|
|
110
|
+
const configuration = configurationOf('projectObject', value, PROJECTION_OBJECT_CONFIGURATION_KEYS)
|
|
111
|
+
return {
|
|
112
|
+
path: projectionPath(configuration.path, 'projectObject path'),
|
|
113
|
+
presence: asExpression(configuration.presence),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function dimension(value) {
|
|
118
|
+
return summaryField('dimension', configurationOf('dimension', value, PROJECTION_CONFIGURATION_KEYS))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function measure(value) {
|
|
122
|
+
return summaryField('measure', configurationOf('measure', value, PROJECTION_CONFIGURATION_KEYS))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function summaryField(role, configuration) {
|
|
126
|
+
const definition = {
|
|
127
|
+
...project(configuration),
|
|
128
|
+
role,
|
|
129
|
+
}
|
|
130
|
+
Object.defineProperty(definition, SUMMARY_FIELD_BRAND, {
|
|
131
|
+
value: true,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
return definition
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function asc(expression, options = {}) {
|
|
138
|
+
return orderBy('asc', expression, options)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function desc(expression, options = {}) {
|
|
142
|
+
return orderBy('desc', expression, options)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function ordering(value) {
|
|
146
|
+
const configuration = configurationOf('ordering', value, ORDERING_CONFIGURATION_KEYS)
|
|
147
|
+
nonEmptyString(configuration.name, 'ordering name')
|
|
148
|
+
if (!Array.isArray(configuration.by) || configuration.by.length === 0) {
|
|
149
|
+
throw new TypeError('ordering by must contain at least one order expression')
|
|
150
|
+
}
|
|
151
|
+
optionalBoolean(configuration.default, 'ordering default')
|
|
152
|
+
|
|
153
|
+
const definition = {
|
|
154
|
+
name: configuration.name,
|
|
155
|
+
orderBy: [...configuration.by],
|
|
156
|
+
}
|
|
157
|
+
if (configuration.default === true) {
|
|
158
|
+
definition.default = true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return definition
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function firstBy(firstOrder, ...rest) {
|
|
165
|
+
if (firstOrder === undefined) {
|
|
166
|
+
throw new TypeError('firstBy requires at least one order expression')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
kind: 'firstBy',
|
|
171
|
+
orderBy: [firstOrder, ...rest],
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function defineGraphModule(value) {
|
|
176
|
+
const configuration = definitionConfiguration('defineGraphModule', value, GRAPH_MODULE_CONFIGURATION_KEYS)
|
|
177
|
+
const content = composeDefinitionContent(configuration)
|
|
178
|
+
|
|
179
|
+
const module = {
|
|
180
|
+
name: configuration.name,
|
|
181
|
+
sources: content.sources,
|
|
182
|
+
parameters: content.parameters,
|
|
183
|
+
relations: content.relations,
|
|
184
|
+
constraints: content.constraints,
|
|
185
|
+
projection: content.projection,
|
|
186
|
+
objects: content.objects,
|
|
187
|
+
orderings: content.orderings,
|
|
188
|
+
}
|
|
189
|
+
Object.defineProperty(module, GRAPH_MODULE_BRAND, { value: true })
|
|
190
|
+
|
|
191
|
+
return deepFreeze(module)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function defineGraph(value) {
|
|
195
|
+
const configuration = definitionConfiguration('defineGraph', value, GRAPH_CONFIGURATION_KEYS, true)
|
|
196
|
+
return buildGraphDefinition(configuration, 'record')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function defineSummaryGraph(value) {
|
|
200
|
+
objectOf('defineSummaryGraph configuration', value)
|
|
201
|
+
if (Object.prototype.hasOwnProperty.call(value, 'projection')) {
|
|
202
|
+
throw new TypeError('Summary graph uses dimensions and measures instead of projection')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const configuration = configurationOf('defineSummaryGraph', value, SUMMARY_GRAPH_CONFIGURATION_KEYS)
|
|
206
|
+
validateCommonDefinitionConfiguration('defineSummaryGraph', configuration, true)
|
|
207
|
+
optionalArray(configuration.dimensions, 'defineSummaryGraph.dimensions')
|
|
208
|
+
optionalArray(configuration.measures, 'defineSummaryGraph.measures')
|
|
209
|
+
|
|
210
|
+
return buildGraphDefinition(
|
|
211
|
+
{
|
|
212
|
+
...configuration,
|
|
213
|
+
projection: [...(configuration.dimensions ?? []), ...(configuration.measures ?? [])],
|
|
214
|
+
},
|
|
215
|
+
'summary',
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildGraphDefinition(configuration, mode) {
|
|
220
|
+
const content = composeDefinitionContent(configuration)
|
|
221
|
+
validateProjectionMode(content.projection, content.objects, mode)
|
|
222
|
+
|
|
223
|
+
return deepFreeze({
|
|
224
|
+
schemaVersion: GRAPH_DEFINITION_VERSION,
|
|
225
|
+
name: configuration.name,
|
|
226
|
+
root: referenceName(configuration.root, 'source'),
|
|
227
|
+
sources: content.sources.map(copySource),
|
|
228
|
+
parameters: content.parameters,
|
|
229
|
+
relations: content.relations,
|
|
230
|
+
constraints: content.constraints,
|
|
231
|
+
projection: {
|
|
232
|
+
fields: content.projection,
|
|
233
|
+
objects: content.objects,
|
|
234
|
+
},
|
|
235
|
+
orderings: content.orderings,
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function definitionConfiguration(factory, value, allowedKeys, requiresRoot = false) {
|
|
240
|
+
const configuration = configurationOf(factory, value, allowedKeys)
|
|
241
|
+
validateCommonDefinitionConfiguration(factory, configuration, requiresRoot)
|
|
242
|
+
return configuration
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function validateCommonDefinitionConfiguration(factory, configuration, requiresRoot) {
|
|
246
|
+
nonEmptyString(configuration.name, `${factory} name`)
|
|
247
|
+
if (requiresRoot) {
|
|
248
|
+
referenceName(configuration.root, 'source')
|
|
249
|
+
}
|
|
250
|
+
for (const key of DEFINITION_LIST_KEYS) {
|
|
251
|
+
optionalArray(configuration[key], `${factory}.${key}`)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function validateProjectionMode(projection, objects, mode) {
|
|
256
|
+
if (mode === 'summary') {
|
|
257
|
+
if (objects.length > 0) {
|
|
258
|
+
throw new TypeError('Summary graph cannot define projection objects')
|
|
259
|
+
}
|
|
260
|
+
if (projection.length === 0) {
|
|
261
|
+
throw new TypeError('Summary graph must define at least one dimension or measure')
|
|
262
|
+
}
|
|
263
|
+
const invalid = projection.find((field) => field.role !== 'dimension' && field.role !== 'measure')
|
|
264
|
+
if (invalid !== undefined) {
|
|
265
|
+
throw new TypeError(
|
|
266
|
+
`Summary graph projection field ${JSON.stringify(invalid.path.join('.'))} has no summary role`,
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const summaryFieldDefinition = projection.find((field) => field.role === 'dimension' || field.role === 'measure')
|
|
273
|
+
if (summaryFieldDefinition !== undefined) {
|
|
274
|
+
throw new TypeError(
|
|
275
|
+
`Record graph projection field ${JSON.stringify(summaryFieldDefinition.path.join('.'))} has a summary role`,
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function composeDefinitionContent(configuration) {
|
|
281
|
+
const content = {
|
|
282
|
+
sources: [],
|
|
283
|
+
parameters: [],
|
|
284
|
+
relations: [],
|
|
285
|
+
constraints: [],
|
|
286
|
+
projection: [],
|
|
287
|
+
objects: [],
|
|
288
|
+
orderings: [],
|
|
289
|
+
}
|
|
290
|
+
const indexes = {
|
|
291
|
+
sources: new Map(),
|
|
292
|
+
parameters: new Map(),
|
|
293
|
+
relations: new Map(),
|
|
294
|
+
constraints: new Set(),
|
|
295
|
+
projection: new Map(),
|
|
296
|
+
objects: new Map(),
|
|
297
|
+
orderings: new Map(),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
for (const [index, module] of (configuration.modules ?? []).entries()) {
|
|
301
|
+
if (module?.[GRAPH_MODULE_BRAND] !== true) {
|
|
302
|
+
throw new TypeError(`Graph module at index ${index} was not created by defineGraphModule`)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
addDefinitionPart(content, indexes, module, `graph module ${JSON.stringify(module.name)}`)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
addDefinitionPart(content, indexes, configuration, `definition ${JSON.stringify(configuration.name)}`)
|
|
309
|
+
return content
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function addDefinitionPart(content, indexes, part, owner) {
|
|
313
|
+
addNamedDefinitions(content.sources, indexes.sources, part.sources, 'source', owner, (source) => source.key)
|
|
314
|
+
addNamedDefinitions(
|
|
315
|
+
content.parameters,
|
|
316
|
+
indexes.parameters,
|
|
317
|
+
part.parameters,
|
|
318
|
+
'parameter',
|
|
319
|
+
owner,
|
|
320
|
+
(parameter) => parameter.name,
|
|
321
|
+
)
|
|
322
|
+
addNamedDefinitions(
|
|
323
|
+
content.relations,
|
|
324
|
+
indexes.relations,
|
|
325
|
+
part.relations,
|
|
326
|
+
'relation',
|
|
327
|
+
owner,
|
|
328
|
+
(relationDefinition) => relationDefinition.name,
|
|
329
|
+
)
|
|
330
|
+
addIdentityDefinitions(content.constraints, indexes.constraints, part.constraints)
|
|
331
|
+
addNamedDefinitions(content.projection, indexes.projection, part.projection, 'projection', owner, (projection) =>
|
|
332
|
+
projection.path.join('.'),
|
|
333
|
+
)
|
|
334
|
+
addNamedDefinitions(content.objects, indexes.objects, part.objects, 'projection object', owner, (object) =>
|
|
335
|
+
object.path.join('.'),
|
|
336
|
+
)
|
|
337
|
+
addNamedDefinitions(
|
|
338
|
+
content.orderings,
|
|
339
|
+
indexes.orderings,
|
|
340
|
+
part.orderings,
|
|
341
|
+
'ordering',
|
|
342
|
+
owner,
|
|
343
|
+
(orderingDefinition) => orderingDefinition.name,
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function addNamedDefinitions(target, index, definitions, type, owner, getKey) {
|
|
348
|
+
for (const definition of definitions ?? []) {
|
|
349
|
+
const key = getKey(definition)
|
|
350
|
+
const existing = index.get(key)
|
|
351
|
+
|
|
352
|
+
if (existing === undefined) {
|
|
353
|
+
index.set(key, { definition, owner })
|
|
354
|
+
target.push(definition)
|
|
355
|
+
continue
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (existing.definition !== definition) {
|
|
359
|
+
throw new TypeError(`Conflicting ${type} ${JSON.stringify(key)} from ${existing.owner} and ${owner}`)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function addIdentityDefinitions(target, index, definitions) {
|
|
365
|
+
for (const definition of definitions ?? []) {
|
|
366
|
+
if (!index.has(definition)) {
|
|
367
|
+
index.add(definition)
|
|
368
|
+
target.push(definition)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function projectionPath(path, label) {
|
|
374
|
+
if (typeof path === 'string') {
|
|
375
|
+
return path.split('.')
|
|
376
|
+
}
|
|
377
|
+
if (!Array.isArray(path) || path.some((segment) => typeof segment !== 'string')) {
|
|
378
|
+
throw new TypeError(`${label} must be a string or an array of strings`)
|
|
379
|
+
}
|
|
380
|
+
return [...path]
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function orderBy(direction, expression, options) {
|
|
384
|
+
const configuration = configurationOf(`${direction} order`, options, ORDER_BY_OPTIONS_KEYS)
|
|
385
|
+
optionalEnum(configuration.nulls, NULLS_ORDERS, `${direction} nulls`)
|
|
386
|
+
|
|
387
|
+
const definition = {
|
|
388
|
+
expression: asExpression(expression),
|
|
389
|
+
direction,
|
|
390
|
+
}
|
|
391
|
+
if (configuration.nulls !== undefined) {
|
|
392
|
+
definition.nulls = configuration.nulls
|
|
393
|
+
}
|
|
394
|
+
return definition
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function deepFreeze(value, seen = new WeakSet()) {
|
|
398
|
+
if (value === null || typeof value !== 'object' || seen.has(value)) {
|
|
399
|
+
return value
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
seen.add(value)
|
|
403
|
+
for (const property of Reflect.ownKeys(value)) {
|
|
404
|
+
deepFreeze(value[property], seen)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return Object.freeze(value)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = {
|
|
411
|
+
GRAPH_DEFINITION_VERSION,
|
|
412
|
+
asc,
|
|
413
|
+
constraint,
|
|
414
|
+
defineGraph,
|
|
415
|
+
defineGraphModule,
|
|
416
|
+
defineSummaryGraph,
|
|
417
|
+
desc,
|
|
418
|
+
dimension,
|
|
419
|
+
firstBy,
|
|
420
|
+
measure,
|
|
421
|
+
ordering,
|
|
422
|
+
project,
|
|
423
|
+
projectObject,
|
|
424
|
+
relation,
|
|
425
|
+
}
|