grafeo-ogm 2.5.2 → 2.5.4
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/CHANGELOG.md +52 -0
- package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.js +8 -3
- package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
- package/dist/cjs/compilers/select-normalizer.d.ts +5 -0
- package/dist/cjs/compilers/select-normalizer.d.ts.map +1 -1
- package/dist/cjs/compilers/select-normalizer.js +54 -3
- package/dist/cjs/compilers/select-normalizer.js.map +1 -1
- package/dist/cjs/compilers/selection.compiler.d.ts +47 -0
- package/dist/cjs/compilers/selection.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/selection.compiler.js +248 -56
- package/dist/cjs/compilers/selection.compiler.js.map +1 -1
- package/dist/cjs/compilers/where.compiler.d.ts +17 -7
- package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/where.compiler.js +45 -13
- package/dist/cjs/compilers/where.compiler.js.map +1 -1
- package/dist/cjs/execution/executor.d.ts +7 -2
- package/dist/cjs/execution/executor.d.ts.map +1 -1
- package/dist/cjs/execution/executor.js +22 -5
- package/dist/cjs/execution/executor.js.map +1 -1
- package/dist/cjs/generator/generate-types.d.ts.map +1 -1
- package/dist/cjs/generator/generate-types.js +19 -2
- package/dist/cjs/generator/generate-types.js.map +1 -1
- package/dist/cjs/generator/type-emitters/model-map-emitter.d.ts +2 -1
- package/dist/cjs/generator/type-emitters/model-map-emitter.d.ts.map +1 -1
- package/dist/cjs/generator/type-emitters/model-map-emitter.js +9 -7
- package/dist/cjs/generator/type-emitters/model-map-emitter.js.map +1 -1
- package/dist/cjs/generator/type-emitters/utility-types.d.ts +13 -1
- package/dist/cjs/generator/type-emitters/utility-types.d.ts.map +1 -1
- package/dist/cjs/generator/type-emitters/utility-types.js +21 -3
- package/dist/cjs/generator/type-emitters/utility-types.js.map +1 -1
- package/dist/cjs/interface-model.d.ts +14 -10
- package/dist/cjs/interface-model.d.ts.map +1 -1
- package/dist/cjs/interface-model.js +22 -14
- package/dist/cjs/interface-model.js.map +1 -1
- package/dist/cjs/ogm.js +1 -1
- package/dist/cjs/ogm.js.map +1 -1
- package/dist/cjs/schema/parser.d.ts.map +1 -1
- package/dist/cjs/schema/parser.js +7 -2
- package/dist/cjs/schema/parser.js.map +1 -1
- package/dist/cjs/testing/cypher-assert.d.ts +2 -2
- package/dist/cjs/testing/cypher-assert.d.ts.map +1 -1
- package/dist/cjs/testing/cypher-assert.js +54 -6
- package/dist/cjs/testing/cypher-assert.js.map +1 -1
- package/dist/cjs/testing/neo4j-record-factory.d.ts +5 -0
- package/dist/cjs/testing/neo4j-record-factory.d.ts.map +1 -1
- package/dist/cjs/testing/neo4j-record-factory.js +82 -27
- package/dist/cjs/testing/neo4j-record-factory.js.map +1 -1
- package/dist/cjs/utils/cypher-field-projection.d.ts +9 -1
- package/dist/cjs/utils/cypher-field-projection.d.ts.map +1 -1
- package/dist/cjs/utils/cypher-field-projection.js +39 -9
- package/dist/cjs/utils/cypher-field-projection.js.map +1 -1
- package/dist/cjs/utils/cypher-sort-projection.d.ts +6 -4
- package/dist/cjs/utils/cypher-sort-projection.d.ts.map +1 -1
- package/dist/cjs/utils/cypher-sort-projection.js +9 -7
- package/dist/cjs/utils/cypher-sort-projection.js.map +1 -1
- package/dist/cjs/utils/write-coercion.d.ts.map +1 -1
- package/dist/cjs/utils/write-coercion.js +12 -1
- package/dist/cjs/utils/write-coercion.js.map +1 -1
- package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/mutation.compiler.js +8 -3
- package/dist/esm/compilers/mutation.compiler.js.map +1 -1
- package/dist/esm/compilers/select-normalizer.d.ts +5 -0
- package/dist/esm/compilers/select-normalizer.d.ts.map +1 -1
- package/dist/esm/compilers/select-normalizer.js +54 -3
- package/dist/esm/compilers/select-normalizer.js.map +1 -1
- package/dist/esm/compilers/selection.compiler.d.ts +47 -0
- package/dist/esm/compilers/selection.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/selection.compiler.js +249 -57
- package/dist/esm/compilers/selection.compiler.js.map +1 -1
- package/dist/esm/compilers/where.compiler.d.ts +17 -7
- package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/where.compiler.js +45 -13
- package/dist/esm/compilers/where.compiler.js.map +1 -1
- package/dist/esm/execution/executor.d.ts +7 -2
- package/dist/esm/execution/executor.d.ts.map +1 -1
- package/dist/esm/execution/executor.js +22 -5
- package/dist/esm/execution/executor.js.map +1 -1
- package/dist/esm/generator/generate-types.d.ts.map +1 -1
- package/dist/esm/generator/generate-types.js +20 -3
- package/dist/esm/generator/generate-types.js.map +1 -1
- package/dist/esm/generator/type-emitters/model-map-emitter.d.ts +2 -1
- package/dist/esm/generator/type-emitters/model-map-emitter.d.ts.map +1 -1
- package/dist/esm/generator/type-emitters/model-map-emitter.js +9 -7
- package/dist/esm/generator/type-emitters/model-map-emitter.js.map +1 -1
- package/dist/esm/generator/type-emitters/utility-types.d.ts +13 -1
- package/dist/esm/generator/type-emitters/utility-types.d.ts.map +1 -1
- package/dist/esm/generator/type-emitters/utility-types.js +19 -3
- package/dist/esm/generator/type-emitters/utility-types.js.map +1 -1
- package/dist/esm/interface-model.d.ts +14 -10
- package/dist/esm/interface-model.d.ts.map +1 -1
- package/dist/esm/interface-model.js +22 -14
- package/dist/esm/interface-model.js.map +1 -1
- package/dist/esm/ogm.js +1 -1
- package/dist/esm/ogm.js.map +1 -1
- package/dist/esm/schema/parser.d.ts.map +1 -1
- package/dist/esm/schema/parser.js +7 -2
- package/dist/esm/schema/parser.js.map +1 -1
- package/dist/esm/testing/cypher-assert.d.ts +2 -2
- package/dist/esm/testing/cypher-assert.d.ts.map +1 -1
- package/dist/esm/testing/cypher-assert.js +54 -6
- package/dist/esm/testing/cypher-assert.js.map +1 -1
- package/dist/esm/testing/neo4j-record-factory.d.ts +5 -0
- package/dist/esm/testing/neo4j-record-factory.d.ts.map +1 -1
- package/dist/esm/testing/neo4j-record-factory.js +82 -27
- package/dist/esm/testing/neo4j-record-factory.js.map +1 -1
- package/dist/esm/utils/cypher-field-projection.d.ts +9 -1
- package/dist/esm/utils/cypher-field-projection.d.ts.map +1 -1
- package/dist/esm/utils/cypher-field-projection.js +39 -9
- package/dist/esm/utils/cypher-field-projection.js.map +1 -1
- package/dist/esm/utils/cypher-sort-projection.d.ts +6 -4
- package/dist/esm/utils/cypher-sort-projection.d.ts.map +1 -1
- package/dist/esm/utils/cypher-sort-projection.js +9 -7
- package/dist/esm/utils/cypher-sort-projection.js.map +1 -1
- package/dist/esm/utils/write-coercion.d.ts.map +1 -1
- package/dist/esm/utils/write-coercion.js +12 -1
- package/dist/esm/utils/write-coercion.js.map +1 -1
- package/package.json +1 -1
|
@@ -58,6 +58,23 @@ export interface SelectionNode {
|
|
|
58
58
|
* can't honour them throw instead of dropping them.
|
|
59
59
|
*/
|
|
60
60
|
hasArguments?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* v2.5.3 — the same response key selected on DIFFERENT fields by sibling
|
|
63
|
+
* inline fragments (`... on A { x: title } ... on B { x: body }`): the
|
|
64
|
+
* field each fragment type selected. Projected as a per-type CASE.
|
|
65
|
+
*/
|
|
66
|
+
fieldByType?: Record<string, string>;
|
|
67
|
+
/**
|
|
68
|
+
* Connection only (v2.5.3): selectionSet aliases of the connection's own
|
|
69
|
+
* keys. Pre-2.5.3 they were ignored and the keys always came back as
|
|
70
|
+
* `edges`, `node`, `properties` and `totalCount`.
|
|
71
|
+
*/
|
|
72
|
+
connectionAliases?: {
|
|
73
|
+
totalCount?: string;
|
|
74
|
+
edges?: string;
|
|
75
|
+
node?: string;
|
|
76
|
+
properties?: string;
|
|
77
|
+
};
|
|
61
78
|
}
|
|
62
79
|
/**
|
|
63
80
|
* Compiles a selection set (parsed SelectionNode[]) into a Cypher RETURN map projection.
|
|
@@ -132,8 +149,22 @@ export declare class SelectionCompiler {
|
|
|
132
149
|
* when the abstract type declares the field as `@cypher` itself, when a
|
|
133
150
|
* condition isn't a concrete type, or when no fragment type declares it
|
|
134
151
|
* as `@cypher`.
|
|
152
|
+
*
|
|
153
|
+
* v2.5.4 — under a policy context, each `@cypher` branch is also guarded
|
|
154
|
+
* by that member's own `read` clause (`WHEN n:B AND <B read> THEN …`),
|
|
155
|
+
* so a member's statement only projects for a node that passes the
|
|
156
|
+
* member's policy. The node filter (`compileTargetPolicyClause`) already
|
|
157
|
+
* requires every carried member's clause; this is defense in depth.
|
|
158
|
+
* Without a policy context the branches are unchanged.
|
|
135
159
|
*/
|
|
136
160
|
private compileFragmentScalar;
|
|
161
|
+
/**
|
|
162
|
+
* ` AND <member read clause>` guarding a fragment `@cypher` branch
|
|
163
|
+
* (v2.5.4); `''` without a policy context, when the member has no read
|
|
164
|
+
* clause, or when that clause needs `@cypher` preludes a projection
|
|
165
|
+
* cannot host (the node filter still enforces it).
|
|
166
|
+
*/
|
|
167
|
+
private memberReadGuard;
|
|
137
168
|
/**
|
|
138
169
|
* Parse a GraphQL selection set string into SelectionNode[].
|
|
139
170
|
*
|
|
@@ -149,6 +180,22 @@ export declare class SelectionCompiler {
|
|
|
149
180
|
* Parse the children of a connection field, extracting node and edge children.
|
|
150
181
|
*/
|
|
151
182
|
private parseConnectionChildren;
|
|
183
|
+
/**
|
|
184
|
+
* Keep the selectionSet alias of a connection key (v2.5.3). The first
|
|
185
|
+
* alias given for a key wins.
|
|
186
|
+
*/
|
|
187
|
+
private recordConnectionAlias;
|
|
188
|
+
/**
|
|
189
|
+
* Compile a relationship or connection field selected through inline
|
|
190
|
+
* fragments on an abstract type that does not declare it (v2.5.3):
|
|
191
|
+
* `CASE WHEN n:D THEN <D's projection> … END`, each branch compiled
|
|
192
|
+
* against the implementer — with its arguments and the target type's
|
|
193
|
+
* read policy. Pre-2.5.3 the field was looked up on the abstract type,
|
|
194
|
+
* not found, and silently dropped together with its arguments.
|
|
195
|
+
*
|
|
196
|
+
* Returns `null` (unchanged behaviour) when no fragment type declares it.
|
|
197
|
+
*/
|
|
198
|
+
private compileFragmentRelationship;
|
|
152
199
|
/**
|
|
153
200
|
* Compile a relationship field into a Cypher pattern comprehension.
|
|
154
201
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/selection.compiler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selection.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/selection.compiler.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,92 @@ const utils_1 = require("../schema/utils");
|
|
|
8
8
|
const cypher_field_projection_1 = require("../utils/cypher-field-projection");
|
|
9
9
|
const validation_1 = require("../utils/validation");
|
|
10
10
|
const where_compiler_1 = require("./where.compiler");
|
|
11
|
+
/**
|
|
12
|
+
* v2.5.3 — the printed arguments of a parsed selectionSet field, so a
|
|
13
|
+
* response key selected twice merges its sub-selections only when both
|
|
14
|
+
* selections carry the same arguments.
|
|
15
|
+
*/
|
|
16
|
+
const argumentKeys = new WeakMap();
|
|
17
|
+
function sameArguments(a, b) {
|
|
18
|
+
return (argumentKeys.get(a) ?? '') === (argumentKeys.get(b) ?? '');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Merge `incoming` into `list` by response key (v2.5.3), as GraphQL merges
|
|
22
|
+
* a field selected more than once: sub-selections are unioned. Pre-2.5.3
|
|
23
|
+
* the second selection of a key was dropped with its sub-fields.
|
|
24
|
+
*
|
|
25
|
+
* - Same field, same arguments: children, edge children and `totalCount`
|
|
26
|
+
* merge recursively.
|
|
27
|
+
* - Same field, different arguments: the first selection wins (unchanged).
|
|
28
|
+
* - Different fields under one key, each selected by inline fragments on
|
|
29
|
+
* different types: recorded in `fieldByType` for a per-type projection.
|
|
30
|
+
* - A selection outside any fragment makes the field unconditional.
|
|
31
|
+
*/
|
|
32
|
+
function mergeSelection(list, seen, incoming) {
|
|
33
|
+
const key = incoming.alias ?? incoming.fieldName;
|
|
34
|
+
const existing = seen.get(key);
|
|
35
|
+
if (!existing) {
|
|
36
|
+
seen.set(key, incoming);
|
|
37
|
+
list.push(incoming);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (existing.fieldName !== incoming.fieldName || existing.fieldByType) {
|
|
41
|
+
mergeFragmentField(existing, incoming);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (existing.typeConditions && incoming.typeConditions)
|
|
45
|
+
existing.typeConditions = [
|
|
46
|
+
...new Set([...existing.typeConditions, ...incoming.typeConditions]),
|
|
47
|
+
];
|
|
48
|
+
// Also selected outside any fragment → unconditional.
|
|
49
|
+
else
|
|
50
|
+
delete existing.typeConditions;
|
|
51
|
+
if (!sameArguments(existing, incoming))
|
|
52
|
+
return;
|
|
53
|
+
if (incoming.children)
|
|
54
|
+
existing.children = mergeSelectionLists(existing.children, incoming.children);
|
|
55
|
+
if (incoming.edgeChildren)
|
|
56
|
+
existing.edgeChildren = mergeSelectionLists(existing.edgeChildren, incoming.edgeChildren);
|
|
57
|
+
if (existing.isConnection) {
|
|
58
|
+
if (incoming.totalCount)
|
|
59
|
+
existing.totalCount = true;
|
|
60
|
+
if (existing.totalCountOnly && !incoming.totalCountOnly)
|
|
61
|
+
delete existing.totalCountOnly;
|
|
62
|
+
if (incoming.connectionAliases)
|
|
63
|
+
existing.connectionAliases = {
|
|
64
|
+
...incoming.connectionAliases,
|
|
65
|
+
...existing.connectionAliases,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Two scalars under one key, from fragments on different types (v2.5.3). */
|
|
70
|
+
function mergeFragmentField(existing, incoming) {
|
|
71
|
+
// Only scalars selected by inline fragments can differ per type; any
|
|
72
|
+
// other clash keeps the first selection (unchanged).
|
|
73
|
+
if (!existing.isScalar ||
|
|
74
|
+
!incoming.isScalar ||
|
|
75
|
+
!existing.typeConditions ||
|
|
76
|
+
!incoming.typeConditions)
|
|
77
|
+
return;
|
|
78
|
+
const byType = existing.fieldByType ?? {};
|
|
79
|
+
if (!existing.fieldByType)
|
|
80
|
+
for (const t of existing.typeConditions)
|
|
81
|
+
byType[t] = existing.fieldName;
|
|
82
|
+
for (const t of incoming.typeConditions)
|
|
83
|
+
if (byType[t] === undefined)
|
|
84
|
+
byType[t] = incoming.fieldName;
|
|
85
|
+
existing.fieldByType = byType;
|
|
86
|
+
existing.typeConditions = [
|
|
87
|
+
...new Set([...existing.typeConditions, ...incoming.typeConditions]),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
function mergeSelectionLists(base, extra) {
|
|
91
|
+
const list = [];
|
|
92
|
+
const seen = new Map();
|
|
93
|
+
for (const node of [...(base ?? []), ...extra])
|
|
94
|
+
mergeSelection(list, seen, node);
|
|
95
|
+
return list;
|
|
96
|
+
}
|
|
11
97
|
/** selectionSet field arguments the OGM honours, by field kind (v2.5.0). */
|
|
12
98
|
const RELATIONSHIP_ARGUMENTS = new Set(['where', 'options']);
|
|
13
99
|
const CONNECTION_ARGUMENTS = new Set(['where']);
|
|
@@ -202,7 +288,7 @@ class SelectionCompiler {
|
|
|
202
288
|
}
|
|
203
289
|
(0, validation_1.assertSafeIdentifier)(node.fieldName, 'selection field');
|
|
204
290
|
if (isAbstract && node.typeConditions) {
|
|
205
|
-
const conditional = this.compileFragmentScalar(node, key, nodeVar, nodeDef, cypherScope);
|
|
291
|
+
const conditional = this.compileFragmentScalar(node, key, nodeVar, nodeDef, cypherScope, params, paramCounter, policyContext);
|
|
206
292
|
if (conditional) {
|
|
207
293
|
parts.push(conditional);
|
|
208
294
|
continue;
|
|
@@ -229,6 +315,21 @@ class SelectionCompiler {
|
|
|
229
315
|
: `.${(0, validation_1.escapeIdentifier)(node.fieldName)}`);
|
|
230
316
|
continue;
|
|
231
317
|
}
|
|
318
|
+
// v2.5.3 — a relationship / connection field selected through an
|
|
319
|
+
// inline fragment on an abstract type that doesn't declare it.
|
|
320
|
+
// (Union relationship fields keep their per-member CASE below.)
|
|
321
|
+
if (isAbstract &&
|
|
322
|
+
node.typeConditions &&
|
|
323
|
+
(node.isConnection || !(unionMembers && unionMembers.length > 0)) &&
|
|
324
|
+
!nodeDef.relationships.has(node.isConnection
|
|
325
|
+
? node.fieldName.replace(/Connection$/, '')
|
|
326
|
+
: node.fieldName)) {
|
|
327
|
+
const fragmentCypher = this.compileFragmentRelationship(node, nodeVar, maxDepth, currentDepth, params, paramCounter, policyContext);
|
|
328
|
+
if (fragmentCypher) {
|
|
329
|
+
parts.push(`${key}: ${fragmentCypher}`);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
232
333
|
if (node.isConnection) {
|
|
233
334
|
const connectionCypher = this.compileConnection(node, nodeVar, nodeDef, maxDepth, currentDepth, params, paramCounter, policyContext);
|
|
234
335
|
if (connectionCypher)
|
|
@@ -263,33 +364,64 @@ class SelectionCompiler {
|
|
|
263
364
|
* when the abstract type declares the field as `@cypher` itself, when a
|
|
264
365
|
* condition isn't a concrete type, or when no fragment type declares it
|
|
265
366
|
* as `@cypher`.
|
|
367
|
+
*
|
|
368
|
+
* v2.5.4 — under a policy context, each `@cypher` branch is also guarded
|
|
369
|
+
* by that member's own `read` clause (`WHEN n:B AND <B read> THEN …`),
|
|
370
|
+
* so a member's statement only projects for a node that passes the
|
|
371
|
+
* member's policy. The node filter (`compileTargetPolicyClause`) already
|
|
372
|
+
* requires every carried member's clause; this is defense in depth.
|
|
373
|
+
* Without a policy context the branches are unchanged.
|
|
266
374
|
*/
|
|
267
|
-
compileFragmentScalar(node, key, nodeVar, nodeDef, cypherScope) {
|
|
268
|
-
|
|
375
|
+
compileFragmentScalar(node, key, nodeVar, nodeDef, cypherScope, params, paramCounter, policyContext = null) {
|
|
376
|
+
// v2.5.3 — `fieldByType`: sibling fragments selected different fields
|
|
377
|
+
// under this key, so each type projects its own field.
|
|
378
|
+
const perType = node.fieldByType;
|
|
379
|
+
if (!perType && nodeDef.properties.get(node.fieldName)?.isCypher)
|
|
269
380
|
return null;
|
|
270
381
|
const members = [];
|
|
271
382
|
for (const typeName of node.typeConditions ?? []) {
|
|
272
383
|
const def = this.schema.nodes.get(typeName);
|
|
273
384
|
if (!def)
|
|
274
385
|
return null;
|
|
275
|
-
const
|
|
386
|
+
const field = perType?.[typeName] ?? node.fieldName;
|
|
387
|
+
(0, validation_1.assertSafeIdentifier)(field, 'selection field');
|
|
388
|
+
const prop = def.properties.get(field);
|
|
276
389
|
if (prop)
|
|
277
|
-
members.push({ def, prop });
|
|
390
|
+
members.push({ def, prop, field });
|
|
278
391
|
}
|
|
279
|
-
if (!
|
|
392
|
+
if (!perType &&
|
|
393
|
+
!members.some((m) => m.prop.isCypher && m.prop.cypherStatement))
|
|
280
394
|
return null;
|
|
281
|
-
const branches = members.map(({ def, prop }) => {
|
|
395
|
+
const branches = members.map(({ def, prop, field }) => {
|
|
282
396
|
let expr;
|
|
283
397
|
if (prop.isCypher && prop.cypherStatement)
|
|
284
398
|
expr = cypherScope
|
|
285
|
-
? cypherScope.register(
|
|
286
|
-
: (0, cypher_field_projection_1.buildInlineCypherFieldExpr)(prop.cypherStatement, nodeVar, `${def.typeName}.${
|
|
399
|
+
? cypherScope.register(field, prop, def.label)
|
|
400
|
+
: (0, cypher_field_projection_1.buildInlineCypherFieldExpr)(prop.cypherStatement, nodeVar, `${def.typeName}.${field}`);
|
|
287
401
|
else
|
|
288
|
-
expr = `${nodeVar}.${(0, validation_1.escapeIdentifier)(
|
|
289
|
-
|
|
402
|
+
expr = `${nodeVar}.${(0, validation_1.escapeIdentifier)(field)}`;
|
|
403
|
+
const guard = prop.isCypher && prop.cypherStatement
|
|
404
|
+
? this.memberReadGuard(def, nodeVar, params, paramCounter, policyContext)
|
|
405
|
+
: '';
|
|
406
|
+
return `WHEN ${nodeVar}:${(0, validation_1.escapeIdentifier)(def.label)}${guard} THEN ${expr}`;
|
|
290
407
|
});
|
|
291
408
|
return `${(0, validation_1.escapeIdentifier)(key)}: CASE ${branches.join(' ')} END`;
|
|
292
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* ` AND <member read clause>` guarding a fragment `@cypher` branch
|
|
412
|
+
* (v2.5.4); `''` without a policy context, when the member has no read
|
|
413
|
+
* clause, or when that clause needs `@cypher` preludes a projection
|
|
414
|
+
* cannot host (the node filter still enforces it).
|
|
415
|
+
*/
|
|
416
|
+
memberReadGuard(def, nodeVar, params, paramCounter, policyContext) {
|
|
417
|
+
if (!policyContext || !this.whereCompiler || !params || !paramCounter)
|
|
418
|
+
return '';
|
|
419
|
+
const clause = this.whereCompiler.compileTargetPolicyClause(def.typeName, nodeVar, 'read', policyContext, paramCounter);
|
|
420
|
+
if (clause.cypher === null || clause.preludes.length > 0)
|
|
421
|
+
return '';
|
|
422
|
+
(0, validation_1.mergeParams)(params, clause.params);
|
|
423
|
+
return ` AND ${clause.cypher}`;
|
|
424
|
+
}
|
|
293
425
|
/**
|
|
294
426
|
* Parse a GraphQL selection set string into SelectionNode[].
|
|
295
427
|
*
|
|
@@ -351,23 +483,8 @@ class SelectionCompiler {
|
|
|
351
483
|
const fragment = sel;
|
|
352
484
|
const condition = fragment.typeCondition?.name.value ?? typeCondition;
|
|
353
485
|
if (fragment.selectionSet)
|
|
354
|
-
for (const merged of this.convertSelectionSet(fragment.selectionSet, condition))
|
|
355
|
-
|
|
356
|
-
const existing = seen.get(mergedKey);
|
|
357
|
-
if (!existing) {
|
|
358
|
-
seen.set(mergedKey, merged);
|
|
359
|
-
nodes.push(merged);
|
|
360
|
-
}
|
|
361
|
-
else if (existing.typeConditions)
|
|
362
|
-
existing.typeConditions = merged.typeConditions
|
|
363
|
-
? [
|
|
364
|
-
...new Set([
|
|
365
|
-
...existing.typeConditions,
|
|
366
|
-
...merged.typeConditions,
|
|
367
|
-
]),
|
|
368
|
-
]
|
|
369
|
-
: undefined;
|
|
370
|
-
}
|
|
486
|
+
for (const merged of this.convertSelectionSet(fragment.selectionSet, condition))
|
|
487
|
+
mergeSelection(nodes, seen, merged);
|
|
371
488
|
continue;
|
|
372
489
|
}
|
|
373
490
|
if (sel.kind !== graphql_1.Kind.FIELD)
|
|
@@ -377,14 +494,9 @@ class SelectionCompiler {
|
|
|
377
494
|
// v2.5.0 — fields are keyed by their OUTPUT name, so the same field
|
|
378
495
|
// under two aliases (`a: kids { .. } b: kids { .. }`) stays two
|
|
379
496
|
// selections. Pre-2.5.0 they collapsed into one.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// Also selected outside any fragment → unconditional.
|
|
384
|
-
if (typeCondition === undefined)
|
|
385
|
-
delete existing.typeConditions;
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
497
|
+
// v2.5.3 — a key selected twice is merged (see `mergeSelection`);
|
|
498
|
+
// the repeat's arguments are compared, not re-validated.
|
|
499
|
+
const existing = seen.get(field.alias?.value ?? fieldName);
|
|
388
500
|
const hasChildren = !!field.selectionSet;
|
|
389
501
|
const isConnection = fieldName.endsWith('Connection');
|
|
390
502
|
const node = {
|
|
@@ -396,16 +508,18 @@ class SelectionCompiler {
|
|
|
396
508
|
};
|
|
397
509
|
if (typeCondition !== undefined)
|
|
398
510
|
node.typeConditions = [typeCondition];
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
511
|
+
if (field.arguments && field.arguments.length > 0) {
|
|
512
|
+
argumentKeys.set(node, field.arguments.map((a) => (0, graphql_1.print)(a)).join(','));
|
|
513
|
+
if (!existing)
|
|
514
|
+
applyFieldArguments(field, node);
|
|
515
|
+
}
|
|
402
516
|
if (hasChildren && field.selectionSet)
|
|
403
517
|
if (isConnection)
|
|
404
518
|
// For connections, parse edges -> node and edges -> properties
|
|
405
519
|
this.parseConnectionChildren(field.selectionSet, node);
|
|
406
520
|
else
|
|
407
521
|
node.children = this.convertSelectionSet(field.selectionSet);
|
|
408
|
-
nodes
|
|
522
|
+
mergeSelection(nodes, seen, node);
|
|
409
523
|
}
|
|
410
524
|
return nodes;
|
|
411
525
|
}
|
|
@@ -419,25 +533,81 @@ class SelectionCompiler {
|
|
|
419
533
|
continue;
|
|
420
534
|
const field = sel;
|
|
421
535
|
assertNoArguments(field, `${parentNode.fieldName}.${field.name.value}`);
|
|
422
|
-
if (field.name.value === 'totalCount')
|
|
536
|
+
if (field.name.value === 'totalCount') {
|
|
423
537
|
parentNode.totalCount = true;
|
|
424
|
-
|
|
538
|
+
this.recordConnectionAlias(parentNode, 'totalCount', field);
|
|
539
|
+
}
|
|
540
|
+
if (field.name.value === 'edges') {
|
|
425
541
|
selectsEdges = true;
|
|
542
|
+
this.recordConnectionAlias(parentNode, 'edges', field);
|
|
543
|
+
}
|
|
426
544
|
if (field.name.value === 'edges' && field.selectionSet)
|
|
427
545
|
for (const edgeSel of field.selectionSet.selections) {
|
|
428
546
|
if (edgeSel.kind !== graphql_1.Kind.FIELD)
|
|
429
547
|
continue;
|
|
430
548
|
const edgeField = edgeSel;
|
|
431
549
|
assertNoArguments(edgeField, `${parentNode.fieldName}.edges.${edgeField.name.value}`);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (edgeField.name.value === '
|
|
435
|
-
parentNode
|
|
550
|
+
// v2.5.3 — a repeated `edges { node { … } }` merges its
|
|
551
|
+
// sub-selections; pre-2.5.3 the last one replaced the others.
|
|
552
|
+
if (edgeField.name.value === 'node' && edgeField.selectionSet) {
|
|
553
|
+
this.recordConnectionAlias(parentNode, 'node', edgeField);
|
|
554
|
+
parentNode.children = mergeSelectionLists(parentNode.children, this.convertSelectionSet(edgeField.selectionSet));
|
|
555
|
+
}
|
|
556
|
+
if (edgeField.name.value === 'properties' && edgeField.selectionSet) {
|
|
557
|
+
this.recordConnectionAlias(parentNode, 'properties', edgeField);
|
|
558
|
+
parentNode.edgeChildren = mergeSelectionLists(parentNode.edgeChildren, this.convertSelectionSet(edgeField.selectionSet));
|
|
559
|
+
}
|
|
436
560
|
}
|
|
437
561
|
}
|
|
438
562
|
if (parentNode.totalCount && !selectsEdges)
|
|
439
563
|
parentNode.totalCountOnly = true;
|
|
440
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* Keep the selectionSet alias of a connection key (v2.5.3). The first
|
|
567
|
+
* alias given for a key wins.
|
|
568
|
+
*/
|
|
569
|
+
recordConnectionAlias(node, key, field) {
|
|
570
|
+
var _a;
|
|
571
|
+
const alias = field.alias?.value;
|
|
572
|
+
if (alias === undefined || alias === key)
|
|
573
|
+
return;
|
|
574
|
+
(0, validation_1.assertSafeIdentifier)(alias, 'selection alias');
|
|
575
|
+
node.connectionAliases ?? (node.connectionAliases = {});
|
|
576
|
+
(_a = node.connectionAliases)[key] ?? (_a[key] = alias);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Compile a relationship or connection field selected through inline
|
|
580
|
+
* fragments on an abstract type that does not declare it (v2.5.3):
|
|
581
|
+
* `CASE WHEN n:D THEN <D's projection> … END`, each branch compiled
|
|
582
|
+
* against the implementer — with its arguments and the target type's
|
|
583
|
+
* read policy. Pre-2.5.3 the field was looked up on the abstract type,
|
|
584
|
+
* not found, and silently dropped together with its arguments.
|
|
585
|
+
*
|
|
586
|
+
* Returns `null` (unchanged behaviour) when no fragment type declares it.
|
|
587
|
+
*/
|
|
588
|
+
compileFragmentRelationship(node, nodeVar, maxDepth, currentDepth, params, paramCounter, policyContext = null) {
|
|
589
|
+
const relField = node.isConnection
|
|
590
|
+
? node.fieldName.replace(/Connection$/, '')
|
|
591
|
+
: node.fieldName;
|
|
592
|
+
const branches = [];
|
|
593
|
+
let singular = node.isConnection;
|
|
594
|
+
for (const typeName of node.typeConditions ?? []) {
|
|
595
|
+
const def = this.schema.nodes.get(typeName);
|
|
596
|
+
const relDef = def?.relationships.get(relField);
|
|
597
|
+
if (!def || !relDef)
|
|
598
|
+
continue;
|
|
599
|
+
if (!relDef.isArray)
|
|
600
|
+
singular = true;
|
|
601
|
+
const expr = node.isConnection
|
|
602
|
+
? this.compileConnection(node, nodeVar, def, maxDepth, currentDepth, params, paramCounter, policyContext)
|
|
603
|
+
: this.compileRelationship(node, nodeVar, def, maxDepth, currentDepth, params, paramCounter, policyContext);
|
|
604
|
+
if (expr)
|
|
605
|
+
branches.push(`WHEN ${nodeVar}:${(0, validation_1.escapeIdentifier)(def.label)} THEN ${expr}`);
|
|
606
|
+
}
|
|
607
|
+
if (branches.length === 0)
|
|
608
|
+
return null;
|
|
609
|
+
return `CASE ${branches.join(' ')} ELSE ${singular ? 'null' : '[]'} END`;
|
|
610
|
+
}
|
|
441
611
|
/**
|
|
442
612
|
* Compile a relationship field into a Cypher pattern comprehension.
|
|
443
613
|
*/
|
|
@@ -502,9 +672,15 @@ class SelectionCompiler {
|
|
|
502
672
|
params[name] = (0, neo4j_driver_1.int)(value);
|
|
503
673
|
return `$${name}`;
|
|
504
674
|
};
|
|
505
|
-
|
|
675
|
+
// v2.5.3 — a list slice bound must fit in 32 bits (and a JS number
|
|
676
|
+
// past 2^63 overflows the driver's Integer). No list is that long, so
|
|
677
|
+
// clamping keeps the result; pre-2.5.3 the query failed.
|
|
678
|
+
const INT32_MAX = 2147483647;
|
|
679
|
+
const start = Math.min(node.offset ?? 0, INT32_MAX);
|
|
506
680
|
const startRef = node.offset !== undefined ? bind(start) : '';
|
|
507
|
-
const endRef = node.limit !== undefined
|
|
681
|
+
const endRef = node.limit !== undefined
|
|
682
|
+
? bind(Math.min(start + node.limit, INT32_MAX))
|
|
683
|
+
: '';
|
|
508
684
|
return `[${startRef}..${endRef}]`;
|
|
509
685
|
}
|
|
510
686
|
/**
|
|
@@ -632,20 +808,36 @@ class SelectionCompiler {
|
|
|
632
808
|
});
|
|
633
809
|
const mapParts = [];
|
|
634
810
|
const projectionKeys = [];
|
|
811
|
+
// v2.5.3 — selectionSet aliases of the connection keys (validated when
|
|
812
|
+
// parsed); unaliased output is byte-identical.
|
|
813
|
+
const aliases = node.connectionAliases ?? {};
|
|
814
|
+
const nodeKey = aliases.node ? (0, validation_1.escapeIdentifier)(aliases.node) : 'node';
|
|
815
|
+
const propertiesKey = aliases.properties
|
|
816
|
+
? (0, validation_1.escapeIdentifier)(aliases.properties)
|
|
817
|
+
: 'properties';
|
|
818
|
+
const edgesKey = aliases.edges ? (0, validation_1.escapeIdentifier)(aliases.edges) : 'edges';
|
|
819
|
+
const totalCountKey = aliases.totalCount
|
|
820
|
+
? (0, validation_1.escapeIdentifier)(aliases.totalCount)
|
|
821
|
+
: 'totalCount';
|
|
635
822
|
// Compile node projection
|
|
636
823
|
if (node.children && node.children.length > 0) {
|
|
637
824
|
const nodeProjection = this.compile(node.children, childVar, targetDef, maxDepth, currentDepth + 1, params, paramCounter, null, policyContext);
|
|
638
|
-
mapParts.push(
|
|
639
|
-
projectionKeys.push(
|
|
825
|
+
mapParts.push(`${nodeKey}: ${nodeProjection}`);
|
|
826
|
+
projectionKeys.push(nodeKey);
|
|
640
827
|
}
|
|
641
828
|
// Compile edge properties projection
|
|
642
829
|
if (node.edgeChildren && node.edgeChildren.length > 0) {
|
|
643
830
|
const edgeFields = node.edgeChildren
|
|
644
831
|
.filter((c) => c.isScalar)
|
|
645
|
-
.map((c) =>
|
|
832
|
+
.map((c) => {
|
|
833
|
+
if (c.alias === undefined || c.alias === c.fieldName)
|
|
834
|
+
return `.${(0, validation_1.escapeIdentifier)(c.fieldName)}`;
|
|
835
|
+
(0, validation_1.assertSafeIdentifier)(c.alias, 'selection alias');
|
|
836
|
+
return `${(0, validation_1.escapeIdentifier)(c.alias)}: ${edgeVar}.${(0, validation_1.escapeIdentifier)(c.fieldName)}`;
|
|
837
|
+
})
|
|
646
838
|
.join(', ');
|
|
647
|
-
mapParts.push(
|
|
648
|
-
projectionKeys.push(
|
|
839
|
+
mapParts.push(`${propertiesKey}: ${edgeVar} { ${edgeFields} }`);
|
|
840
|
+
projectionKeys.push(propertiesKey);
|
|
649
841
|
}
|
|
650
842
|
// Inject synthetic sort scalars at the top level of the edge map so
|
|
651
843
|
// apoc.coll.sortMulti (which only accepts top-level keys) can order the
|
|
@@ -789,19 +981,19 @@ class SelectionCompiler {
|
|
|
789
981
|
// (same pattern, same `where`, same target `read` policy), so it can
|
|
790
982
|
// never count a node the caller cannot see. Pre-2.4.8 it was dropped.
|
|
791
983
|
const totalCount = node.totalCount
|
|
792
|
-
?
|
|
984
|
+
? `${totalCountKey}: size([${pattern}${whereClause} | 1])`
|
|
793
985
|
: '';
|
|
794
986
|
if (node.totalCountOnly)
|
|
795
987
|
return `{ ${totalCount} }`;
|
|
796
988
|
const withTotal = totalCount ? `, ${totalCount}` : '';
|
|
797
989
|
if (sortKeyTokens.length === 0)
|
|
798
|
-
return `{
|
|
990
|
+
return `{ ${edgesKey}: [${pattern}${whereClause} | ${innerMap}]${withTotal} }`;
|
|
799
991
|
// Re-project only the user-facing keys so synthetic __skN scalars don't
|
|
800
992
|
// leak out of the projection.
|
|
801
993
|
const outerProjection = projectionKeys.length > 0
|
|
802
994
|
? `{ ${projectionKeys.map((k) => `${k}: x.${k}`).join(', ')} }`
|
|
803
995
|
: `{}`;
|
|
804
|
-
return `{
|
|
996
|
+
return `{ ${edgesKey}: [x IN apoc.coll.sortMulti([${pattern}${whereClause} | ${innerMap}], [${sortKeyTokens.join(', ')}]) | ${outerProjection}]${withTotal} }`;
|
|
805
997
|
}
|
|
806
998
|
/**
|
|
807
999
|
* AND-combine an optional user where (`select.where` / `connectionWhere`)
|