grafeo-ogm 2.5.4 → 2.6.0
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 +54 -0
- package/README.md +123 -0
- package/dist/cjs/compilers/fulltext.compiler.d.ts +17 -1
- package/dist/cjs/compilers/fulltext.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/fulltext.compiler.js +35 -3
- package/dist/cjs/compilers/fulltext.compiler.js.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.d.ts +39 -0
- package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.js +161 -11
- package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
- package/dist/cjs/compilers/selection.compiler.d.ts +65 -1
- package/dist/cjs/compilers/selection.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/selection.compiler.js +521 -25
- package/dist/cjs/compilers/selection.compiler.js.map +1 -1
- package/dist/cjs/compilers/where.compiler.d.ts +4 -0
- package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/where.compiler.js +23 -0
- package/dist/cjs/compilers/where.compiler.js.map +1 -1
- package/dist/cjs/interface-model.d.ts.map +1 -1
- package/dist/cjs/interface-model.js +8 -1
- package/dist/cjs/interface-model.js.map +1 -1
- package/dist/cjs/model.d.ts +7 -0
- package/dist/cjs/model.d.ts.map +1 -1
- package/dist/cjs/model.js +20 -4
- package/dist/cjs/model.js.map +1 -1
- package/dist/cjs/ogm.d.ts +14 -0
- package/dist/cjs/ogm.d.ts.map +1 -1
- package/dist/cjs/ogm.js +36 -2
- package/dist/cjs/ogm.js.map +1 -1
- package/dist/cjs/utils/strict-mode.d.ts +39 -0
- package/dist/cjs/utils/strict-mode.d.ts.map +1 -0
- package/dist/cjs/utils/strict-mode.js +59 -0
- package/dist/cjs/utils/strict-mode.js.map +1 -0
- package/dist/esm/compilers/fulltext.compiler.d.ts +17 -1
- package/dist/esm/compilers/fulltext.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/fulltext.compiler.js +35 -3
- package/dist/esm/compilers/fulltext.compiler.js.map +1 -1
- package/dist/esm/compilers/mutation.compiler.d.ts +39 -0
- package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/mutation.compiler.js +161 -11
- package/dist/esm/compilers/mutation.compiler.js.map +1 -1
- package/dist/esm/compilers/selection.compiler.d.ts +65 -1
- package/dist/esm/compilers/selection.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/selection.compiler.js +521 -25
- package/dist/esm/compilers/selection.compiler.js.map +1 -1
- package/dist/esm/compilers/where.compiler.d.ts +4 -0
- package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/where.compiler.js +23 -0
- package/dist/esm/compilers/where.compiler.js.map +1 -1
- package/dist/esm/interface-model.d.ts.map +1 -1
- package/dist/esm/interface-model.js +8 -1
- package/dist/esm/interface-model.js.map +1 -1
- package/dist/esm/model.d.ts +7 -0
- package/dist/esm/model.d.ts.map +1 -1
- package/dist/esm/model.js +20 -4
- package/dist/esm/model.js.map +1 -1
- package/dist/esm/ogm.d.ts +14 -0
- package/dist/esm/ogm.d.ts.map +1 -1
- package/dist/esm/ogm.js +36 -2
- package/dist/esm/ogm.js.map +1 -1
- package/dist/esm/utils/strict-mode.d.ts +39 -0
- package/dist/esm/utils/strict-mode.d.ts.map +1 -0
- package/dist/esm/utils/strict-mode.js +54 -0
- package/dist/esm/utils/strict-mode.js.map +1 -0
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ const types_1 = require("../policy/types");
|
|
|
11
11
|
const utils_1 = require("../schema/utils");
|
|
12
12
|
const validation_1 = require("../utils/validation");
|
|
13
13
|
const write_coercion_1 = require("../utils/write-coercion");
|
|
14
|
+
const strict_mode_1 = require("../utils/strict-mode");
|
|
14
15
|
const where_compiler_1 = require("./where.compiler");
|
|
15
16
|
/** The root node `n` of an update — the pre-2.4.8 names, unprefixed. */
|
|
16
17
|
const TOP_SCOPE = {
|
|
@@ -67,6 +68,7 @@ class MutationCompiler {
|
|
|
67
68
|
this.narrowedRelDefs = new WeakSet();
|
|
68
69
|
this.logger = options?.logger;
|
|
69
70
|
this.policyWhereCompiler = options?.whereCompiler;
|
|
71
|
+
this.strictMode = options?.strictMode ?? (0, strict_mode_1.lenientStrictMode)();
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
72
74
|
* Resolve and compile the TARGET type's `read` policy predicate for a
|
|
@@ -285,6 +287,68 @@ class MutationCompiler {
|
|
|
285
287
|
throw new errors_1.OGMError(`Cannot resolve the target type "${relDef.target}" of relationship "${relDef.fieldName}".`);
|
|
286
288
|
return targetNodeDef;
|
|
287
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* v2.6.0 — keys of a connect / disconnect item that the compiler does not
|
|
292
|
+
* read. Pre-2.6.0 (and still by default) they are ignored, so a typo such
|
|
293
|
+
* as `{ wehre: … }` compiles to an item with NO filter: a connect links
|
|
294
|
+
* every target node, a disconnect unlinks every related one. Default: one
|
|
295
|
+
* warning per OGM; strict: `OGMError`, like an unknown write field.
|
|
296
|
+
*/
|
|
297
|
+
checkLinkItemKeys(item, kind, relDef) {
|
|
298
|
+
const allowed = kind === 'connect' ? CONNECT_ITEM_KEYS : DISCONNECT_ITEM_KEYS;
|
|
299
|
+
const unknown = Object.keys(item).filter((k) => item[k] !== undefined && !allowed.has(k));
|
|
300
|
+
if (unknown.length === 0)
|
|
301
|
+
return;
|
|
302
|
+
const list = unknown.map((k) => `"${k}"`).join(', ');
|
|
303
|
+
const allowedList = [...allowed].map((k) => `"${k}"`).join(', ');
|
|
304
|
+
if (this.strictMode.strict)
|
|
305
|
+
throw new errors_1.OGMError(`Unknown key(s) ${list} in a ${kind} item on "${relDef.fieldName}". Allowed: ${allowedList}.`);
|
|
306
|
+
const unfiltered = item.where == null;
|
|
307
|
+
this.strictMode.warn(`${kind}-unknown-key`, `${relDef.fieldName}\u0000${unknown.join(',')}`, `Unknown key(s) ${list} in a ${kind} item on "${relDef.fieldName}" ` +
|
|
308
|
+
`${unknown.length > 1 ? 'are' : 'is'} ignored` +
|
|
309
|
+
(unfiltered
|
|
310
|
+
? `; with no "where", the item ${kind === 'connect'
|
|
311
|
+
? `connects EVERY "${relDef.target}" node`
|
|
312
|
+
: `unlinks EVERY related "${relDef.target}" node`}.`
|
|
313
|
+
: '.') +
|
|
314
|
+
` Allowed: ${allowedList}.`, `throw an OGMError for unknown ${kind} item keys`);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* v2.6.0 — `labels` on a MERGE path (upsert, createMany with
|
|
318
|
+
* skipDuplicates) are SET after the MERGE, so they are also added to an
|
|
319
|
+
* EXISTING node it matched. Default: unchanged, one warning; strict: the
|
|
320
|
+
* labels go in `ON CREATE SET`, onto newly created nodes only.
|
|
321
|
+
*/
|
|
322
|
+
labelsOnMatchedNodes(nodeDef, op) {
|
|
323
|
+
const call = op === 'upsert' ? 'upsert' : 'createMany with skipDuplicates';
|
|
324
|
+
this.strictMode.warn('labels-on-matched-nodes', `${nodeDef.typeName}\u0000${op}`, `"labels" on ${call} (${nodeDef.typeName}) are added to every node the MERGE returns, so an existing node ` +
|
|
325
|
+
`it matched is relabelled too.`, 'apply "labels" only to the nodes it creates');
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* v2.6.0 — `edge` keys that are not properties of the relationship's
|
|
329
|
+
* `@relationshipProperties` type. Pre-2.6.0 (and still by default) each
|
|
330
|
+
* is written to the relationship as a stray property. Default: one
|
|
331
|
+
* warning per OGM; strict: `OGMError` (`Unknown field …`).
|
|
332
|
+
*/
|
|
333
|
+
checkEdgeKeys(relDef, edge, input) {
|
|
334
|
+
if (!(0, validation_1.isPlainObject)(edge))
|
|
335
|
+
return;
|
|
336
|
+
const declared = relDef.properties
|
|
337
|
+
? this.schema.relationshipProperties.get(relDef.properties)?.properties
|
|
338
|
+
: undefined;
|
|
339
|
+
for (const key of Object.keys(edge)) {
|
|
340
|
+
if (edge[key] === undefined || declared?.has(key))
|
|
341
|
+
continue;
|
|
342
|
+
const owner = relDef.properties
|
|
343
|
+
? `the relationship properties type "${relDef.properties}"`
|
|
344
|
+
: `the relationship, which declares no @relationshipProperties`;
|
|
345
|
+
if (this.strictMode.strict)
|
|
346
|
+
throw new errors_1.OGMError(`Unknown field "${key}" in the edge input (${input}) of "${relDef.fieldName}": it is not a property of ` +
|
|
347
|
+
`${owner}. Declare it in the schema, or fix the key.`);
|
|
348
|
+
this.strictMode.warn('edge-unknown-key', `${relDef.fieldName}\u0000${key}`, `Unknown field "${key}" in the edge input of "${relDef.fieldName}" is not a property of ${owner}; ` +
|
|
349
|
+
`it is written to the relationship as a stray property.`, `throw an OGMError for unknown edge fields`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
288
352
|
/**
|
|
289
353
|
* Generate CREATE Cypher for one or more nodes.
|
|
290
354
|
* Handles scalar properties, nested relationship creates, and connects.
|
|
@@ -578,6 +642,19 @@ class MutationCompiler {
|
|
|
578
642
|
if (mergeProps.length === 0)
|
|
579
643
|
throw new errors_1.OGMError(`upsert requires at least one scalar property in "where" for MERGE key: ` +
|
|
580
644
|
`an @id or @unique field of ${nodeDef.typeName}.`);
|
|
645
|
+
// v2.6.0 — MERGE matches ALL its keys together, so `{ id, email }`
|
|
646
|
+
// misses a node that has the email under another id and creates a
|
|
647
|
+
// duplicate beside it (or fails on the unique constraint). Default:
|
|
648
|
+
// compiled as before, with one warning; strict: exactly one key.
|
|
649
|
+
if (mergeKeyNames.length > 1) {
|
|
650
|
+
const keys = mergeKeyNames.map((k) => `"${k}"`).join(', ');
|
|
651
|
+
if (this.strictMode.strict)
|
|
652
|
+
throw new errors_1.OGMError(`upsert "where" must name exactly one @id / @unique field of ${nodeDef.typeName}; got ${keys}. ` +
|
|
653
|
+
`MERGE matches all of them together, so a node that has one but not the others is not found ` +
|
|
654
|
+
`and a duplicate is created. Pass a single unique key.`);
|
|
655
|
+
this.strictMode.warn('upsert-multiple-unique-keys', `${nodeDef.typeName}\u0000${mergeKeyNames.join(',')}`, `upsert on ${nodeDef.typeName} with several unique keys in "where" (${keys}) MERGEs on all of them ` +
|
|
656
|
+
`together: a node that has one but not the others is not matched and a duplicate is created.`, 'require exactly one unique key in an upsert "where"');
|
|
657
|
+
}
|
|
581
658
|
lines.push(`MERGE (n:${labelStr} { ${mergeProps.join(', ')} })`);
|
|
582
659
|
// ON CREATE SET — all properties from create input (scalar only)
|
|
583
660
|
const createSets = [];
|
|
@@ -628,6 +705,11 @@ class MutationCompiler {
|
|
|
628
705
|
continue;
|
|
629
706
|
createSets.push(`n.${(0, validation_1.escapeIdentifier)(propDef.name)} = randomUUID()`);
|
|
630
707
|
}
|
|
708
|
+
// v2.6.0 — strict: runtime labels go on the node only when MERGE creates
|
|
709
|
+
// it (see `labelsOnMatchedNodes`).
|
|
710
|
+
const labelsOnCreate = this.strictMode.strict && labels && labels.length > 0;
|
|
711
|
+
if (labelsOnCreate)
|
|
712
|
+
createSets.push(`n:${labels.map((l) => (0, validation_1.assertSafeLabel)(l)).join(':')}`);
|
|
631
713
|
if (createSets.length > 0)
|
|
632
714
|
lines.push(`ON CREATE SET ${createSets.join(', ')}`);
|
|
633
715
|
// ON MATCH SET — all properties from update input (scalar only)
|
|
@@ -651,9 +733,10 @@ class MutationCompiler {
|
|
|
651
733
|
if (updateSets.length > 0)
|
|
652
734
|
lines.push(`ON MATCH SET ${updateSets.join(', ')}`);
|
|
653
735
|
// Extra labels
|
|
654
|
-
if (labels && labels.length > 0) {
|
|
736
|
+
if (labels && labels.length > 0 && !labelsOnCreate) {
|
|
655
737
|
const validatedLabels = labels.map((l) => (0, validation_1.assertSafeLabel)(l)).join(':');
|
|
656
738
|
lines.push(`SET n:${validatedLabels}`);
|
|
739
|
+
this.labelsOnMatchedNodes(nodeDef, 'upsert');
|
|
657
740
|
}
|
|
658
741
|
lines.push('RETURN n');
|
|
659
742
|
return { cypher: lines.join('\n'), params };
|
|
@@ -803,6 +886,9 @@ class MutationCompiler {
|
|
|
803
886
|
onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(propDef.name)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, bound, propDef)}`);
|
|
804
887
|
params[paramName] = bound;
|
|
805
888
|
}
|
|
889
|
+
// v2.6.0 — strict: labels only on the nodes MERGE creates.
|
|
890
|
+
if (this.strictMode.strict && labels && labels.length > 0)
|
|
891
|
+
onCreateSets.push(`n:${labels.map((l) => (0, validation_1.assertSafeLabel)(l)).join(':')}`);
|
|
806
892
|
if (onCreateSets.length > 0)
|
|
807
893
|
lines.push(`ON CREATE SET ${onCreateSets.join(', ')}`);
|
|
808
894
|
}
|
|
@@ -822,9 +908,13 @@ class MutationCompiler {
|
|
|
822
908
|
lines.push(`CREATE (n:${labelStr} { ${propParts.join(', ')} })`);
|
|
823
909
|
}
|
|
824
910
|
// Extra labels
|
|
825
|
-
if (labels &&
|
|
911
|
+
if (labels &&
|
|
912
|
+
labels.length > 0 &&
|
|
913
|
+
!(skipDuplicates && this.strictMode.strict)) {
|
|
826
914
|
const validatedLabels = labels.map((l) => (0, validation_1.assertSafeLabel)(l)).join(':');
|
|
827
915
|
lines.push(`SET n:${validatedLabels}`);
|
|
916
|
+
if (skipDuplicates)
|
|
917
|
+
this.labelsOnMatchedNodes(nodeDef, 'createMany');
|
|
828
918
|
}
|
|
829
919
|
lines.push('RETURN count(n) AS count');
|
|
830
920
|
return { cypher: lines.join('\n'), params };
|
|
@@ -974,6 +1064,7 @@ class MutationCompiler {
|
|
|
974
1064
|
(0, validation_1.mergeParams)(params, propParams);
|
|
975
1065
|
lines.push(`CREATE ${relPattern}`);
|
|
976
1066
|
// Set edge properties if present
|
|
1067
|
+
this.checkEdgeKeys(relDef, edgeInput, 'nested create');
|
|
977
1068
|
if (edgeInput && Object.keys(edgeInput).length > 0) {
|
|
978
1069
|
const relVar = `r_edge_${startCounter + counter - 1}`;
|
|
979
1070
|
// Re-match the relationship to set edge props
|
|
@@ -1112,6 +1203,11 @@ class MutationCompiler {
|
|
|
1112
1203
|
scope,
|
|
1113
1204
|
}));
|
|
1114
1205
|
else {
|
|
1206
|
+
// Every item is a plain object here (fast-path eligibility).
|
|
1207
|
+
for (const item of spec) {
|
|
1208
|
+
this.checkLinkItemKeys(item, 'connect', relDef);
|
|
1209
|
+
this.checkEdgeKeys(relDef, item.edge, 'connect');
|
|
1210
|
+
}
|
|
1115
1211
|
const targetVar = `${varPrefix}target`;
|
|
1116
1212
|
const relVar = `${varPrefix}r`;
|
|
1117
1213
|
const targetLabelStr = this.getCachedLabelString(targetNodeDef);
|
|
@@ -1238,6 +1334,8 @@ class MutationCompiler {
|
|
|
1238
1334
|
// compiling it would MATCH, and link, every target node.
|
|
1239
1335
|
if (!(0, validation_1.isPlainObject)(connectSpec))
|
|
1240
1336
|
throw new errors_1.OGMError(`A connect item on "${relDef.fieldName}" must be an object such as { where: { node: { … } } }; got ${connectSpec === null ? 'null' : typeof connectSpec}.`);
|
|
1337
|
+
this.checkLinkItemKeys(connectSpec, 'connect', relDef);
|
|
1338
|
+
this.checkEdgeKeys(relDef, connectSpec.edge, 'connect');
|
|
1241
1339
|
const lines = [];
|
|
1242
1340
|
const whereSpec = connectSpec.where;
|
|
1243
1341
|
const edgeInput = connectSpec.edge;
|
|
@@ -1379,21 +1477,25 @@ class MutationCompiler {
|
|
|
1379
1477
|
const predicate = targetNodeDef
|
|
1380
1478
|
? this.buildTargetPolicyPredicate(targetNodeDef, blanketTarget, params, policyContext, paramCounter)
|
|
1381
1479
|
: undefined;
|
|
1480
|
+
const { labelRaw, labelPredicate } = this.blanketRestriction(relDef, targetNodeDef, blanketTarget);
|
|
1382
1481
|
const pattern = (0, utils_1.buildRelPattern)({
|
|
1383
1482
|
sourceVar,
|
|
1384
1483
|
relDef,
|
|
1385
|
-
targetVar: predicate ? blanketTarget : '',
|
|
1484
|
+
targetVar: predicate || labelPredicate ? blanketTarget : '',
|
|
1386
1485
|
edgeVar: relVar,
|
|
1387
|
-
targetLabelRaw:
|
|
1486
|
+
targetLabelRaw: labelRaw,
|
|
1388
1487
|
});
|
|
1389
1488
|
lines.push(`OPTIONAL MATCH ${pattern}`);
|
|
1390
|
-
|
|
1391
|
-
|
|
1489
|
+
const blanketConditions = [labelPredicate, predicate].filter(Boolean);
|
|
1490
|
+
if (blanketConditions.length > 0)
|
|
1491
|
+
lines.push(`WHERE ${blanketConditions.join(' AND ')}`);
|
|
1392
1492
|
lines.push(`DELETE ${relVar}`);
|
|
1393
1493
|
}
|
|
1394
1494
|
else {
|
|
1395
1495
|
// Specific disconnect with conditions
|
|
1396
1496
|
const disconnectSpec = item;
|
|
1497
|
+
if ((0, validation_1.isPlainObject)(disconnectSpec))
|
|
1498
|
+
this.checkLinkItemKeys(disconnectSpec, 'disconnect', relDef);
|
|
1397
1499
|
const whereSpec = disconnectSpec.where;
|
|
1398
1500
|
const targetVar = `${varPrefix}target_${tag}_${si}`;
|
|
1399
1501
|
const pattern = (0, utils_1.buildRelPattern)({
|
|
@@ -1432,6 +1534,33 @@ class MutationCompiler {
|
|
|
1432
1534
|
this.narrowedRelDefs.add(narrowed);
|
|
1433
1535
|
return narrowed;
|
|
1434
1536
|
}
|
|
1537
|
+
/**
|
|
1538
|
+
* v2.6.0 — how a blanket disconnect (`null` / `{}`) restricts its target.
|
|
1539
|
+
*
|
|
1540
|
+
* Default (v2.5.3): only a relationship narrowed to a union / interface
|
|
1541
|
+
* member keeps that member's label; otherwise the target is anonymous,
|
|
1542
|
+
* so with two fields on one relationship type and direction
|
|
1543
|
+
* (`pets: [Pet]`, `toys: [Toy]`, both `HAS`) a blanket disconnect of one
|
|
1544
|
+
* also unlinks the other's nodes (warned once at OGM construction).
|
|
1545
|
+
*
|
|
1546
|
+
* Strict: the field's own target type — its label, or for an abstract
|
|
1547
|
+
* target a predicate over its members' labels on `targetVar`.
|
|
1548
|
+
*/
|
|
1549
|
+
blanketRestriction(relDef, targetNodeDef, targetVar) {
|
|
1550
|
+
const narrowed = this.blanketTargetLabel(relDef);
|
|
1551
|
+
if (narrowed || !this.strictMode.strict || !targetNodeDef)
|
|
1552
|
+
return { labelRaw: narrowed, labelPredicate: null };
|
|
1553
|
+
if (abstractTarget(targetNodeDef.typeName, this.schema))
|
|
1554
|
+
return {
|
|
1555
|
+
labelRaw: undefined,
|
|
1556
|
+
labelPredicate: this.targetMatch(targetNodeDef, targetVar)
|
|
1557
|
+
.labelPredicate,
|
|
1558
|
+
};
|
|
1559
|
+
return {
|
|
1560
|
+
labelRaw: this.getCachedLabelString(targetNodeDef),
|
|
1561
|
+
labelPredicate: null,
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1435
1564
|
/** The label a blanket disconnect over `relDef` must keep (see above). */
|
|
1436
1565
|
blanketTargetLabel(relDef) {
|
|
1437
1566
|
if (!this.narrowedRelDefs.has(relDef))
|
|
@@ -1522,6 +1651,8 @@ class MutationCompiler {
|
|
|
1522
1651
|
: [item.disconnect];
|
|
1523
1652
|
for (let di = 0; di < disconnectItems.length; di++) {
|
|
1524
1653
|
const disconnectSpec = disconnectItems[di];
|
|
1654
|
+
if ((0, validation_1.isPlainObject)(disconnectSpec))
|
|
1655
|
+
this.checkLinkItemKeys(disconnectSpec, 'disconnect', relDef);
|
|
1525
1656
|
const whereSpec = disconnectSpec.where;
|
|
1526
1657
|
// A nested `disconnect` needs the target bound, so an item that
|
|
1527
1658
|
// carries one is never the anonymous blanket form.
|
|
@@ -1538,18 +1669,20 @@ class MutationCompiler {
|
|
|
1538
1669
|
// disconnect: only unlink targets the caller can read.
|
|
1539
1670
|
const blanketTarget = `${sourceVar}_disc${varCounter}`;
|
|
1540
1671
|
const predicate = this.buildTargetPolicyPredicate(targetNodeDef, blanketTarget, params, policyContext, paramCounter);
|
|
1541
|
-
|
|
1672
|
+
const { labelRaw, labelPredicate } = this.blanketRestriction(relDef, targetNodeDef, blanketTarget);
|
|
1673
|
+
if (predicate || labelPredicate)
|
|
1542
1674
|
varCounter++;
|
|
1543
1675
|
const pattern = (0, utils_1.buildRelPattern)({
|
|
1544
1676
|
sourceVar,
|
|
1545
1677
|
relDef,
|
|
1546
|
-
targetVar: predicate ? blanketTarget : '',
|
|
1678
|
+
targetVar: predicate || labelPredicate ? blanketTarget : '',
|
|
1547
1679
|
edgeVar: relVar,
|
|
1548
|
-
targetLabelRaw:
|
|
1680
|
+
targetLabelRaw: labelRaw,
|
|
1549
1681
|
});
|
|
1550
1682
|
lines.push(`OPTIONAL MATCH ${pattern}`);
|
|
1551
|
-
|
|
1552
|
-
|
|
1683
|
+
const blanketConditions = [labelPredicate, predicate].filter(Boolean);
|
|
1684
|
+
if (blanketConditions.length > 0)
|
|
1685
|
+
lines.push(`WHERE ${blanketConditions.join(' AND ')}`);
|
|
1553
1686
|
lines.push(`DELETE ${relVar}`);
|
|
1554
1687
|
}
|
|
1555
1688
|
else {
|
|
@@ -1657,6 +1790,7 @@ class MutationCompiler {
|
|
|
1657
1790
|
// create() accepts. Pre-2.4.5 this path never read
|
|
1658
1791
|
// `createSpec.edge`, so they were silently dropped.
|
|
1659
1792
|
const edgeInput = createSpec.edge;
|
|
1793
|
+
this.checkEdgeKeys(relDef, edgeInput, 'nested create');
|
|
1660
1794
|
const edgeEntries = Object.entries(edgeInput ?? {}).filter(([, v]) => v !== undefined);
|
|
1661
1795
|
const edgeVar = edgeEntries.length ? `${createVar}_e` : undefined;
|
|
1662
1796
|
const relPattern = (0, utils_1.buildRelPattern)({
|
|
@@ -1727,6 +1861,7 @@ class MutationCompiler {
|
|
|
1727
1861
|
// Pre-compute SET clauses for edge (relationship) properties
|
|
1728
1862
|
const edgeClauses = [];
|
|
1729
1863
|
const edgeParams = {};
|
|
1864
|
+
this.checkEdgeKeys(relDef, edgeUpdate, 'nested update');
|
|
1730
1865
|
if (edgeUpdate)
|
|
1731
1866
|
for (const [prop, val] of Object.entries(edgeUpdate)) {
|
|
1732
1867
|
if (val === undefined)
|
|
@@ -1875,6 +2010,21 @@ MutationCompiler.OPERATOR_SUFFIXES = [
|
|
|
1875
2010
|
* path to validate that every item shares the same shape; mismatched
|
|
1876
2011
|
* shapes silently drop keys from the compiled WHERE / SET.
|
|
1877
2012
|
*/
|
|
2013
|
+
/**
|
|
2014
|
+
* v2.6.0 — the keys a connect / disconnect item is compiled from. `overwrite`
|
|
2015
|
+
* is offered by the generated `ConnectFieldInput` and accepted as before
|
|
2016
|
+
* (MERGE + SET always overwrites the edge properties it is given).
|
|
2017
|
+
*/
|
|
2018
|
+
const CONNECT_ITEM_KEYS = new Set([
|
|
2019
|
+
'where',
|
|
2020
|
+
'edge',
|
|
2021
|
+
'overwrite',
|
|
2022
|
+
'connect',
|
|
2023
|
+
]);
|
|
2024
|
+
const DISCONNECT_ITEM_KEYS = new Set([
|
|
2025
|
+
'where',
|
|
2026
|
+
'disconnect',
|
|
2027
|
+
]);
|
|
1878
2028
|
/** Keys that make a nested-mutation `where` connection-shaped. */
|
|
1879
2029
|
const CONNECTION_WHERE_KEYS = new Set([
|
|
1880
2030
|
'node',
|