grafeo-ogm 1.14.0 → 2.1.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/README.md +70 -0
  3. package/dist/cjs/compilers/fulltext.compiler.d.ts.map +1 -1
  4. package/dist/cjs/compilers/fulltext.compiler.js +30 -14
  5. package/dist/cjs/compilers/fulltext.compiler.js.map +1 -1
  6. package/dist/cjs/compilers/mutation.compiler.d.ts +7 -0
  7. package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
  8. package/dist/cjs/compilers/mutation.compiler.js +130 -36
  9. package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
  10. package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
  11. package/dist/cjs/compilers/where.compiler.js +13 -4
  12. package/dist/cjs/compilers/where.compiler.js.map +1 -1
  13. package/dist/cjs/generator/type-emitters/fulltext-emitter.js +24 -8
  14. package/dist/cjs/generator/type-emitters/fulltext-emitter.js.map +1 -1
  15. package/dist/cjs/generator/type-emitters/sort-options-emitter.d.ts +2 -1
  16. package/dist/cjs/generator/type-emitters/sort-options-emitter.d.ts.map +1 -1
  17. package/dist/cjs/generator/type-emitters/sort-options-emitter.js +12 -5
  18. package/dist/cjs/generator/type-emitters/sort-options-emitter.js.map +1 -1
  19. package/dist/cjs/generator/type-emitters/utility-types.d.ts.map +1 -1
  20. package/dist/cjs/generator/type-emitters/utility-types.js +21 -1
  21. package/dist/cjs/generator/type-emitters/utility-types.js.map +1 -1
  22. package/dist/cjs/utils/cypher-sort-projection.d.ts +3 -0
  23. package/dist/cjs/utils/cypher-sort-projection.d.ts.map +1 -1
  24. package/dist/cjs/utils/cypher-sort-projection.js +13 -1
  25. package/dist/cjs/utils/cypher-sort-projection.js.map +1 -1
  26. package/dist/cjs/utils/validation.d.ts +72 -0
  27. package/dist/cjs/utils/validation.d.ts.map +1 -1
  28. package/dist/cjs/utils/validation.js +133 -0
  29. package/dist/cjs/utils/validation.js.map +1 -1
  30. package/dist/cjs/utils/write-coercion.d.ts +61 -0
  31. package/dist/cjs/utils/write-coercion.d.ts.map +1 -0
  32. package/dist/cjs/utils/write-coercion.js +213 -0
  33. package/dist/cjs/utils/write-coercion.js.map +1 -0
  34. package/dist/esm/compilers/fulltext.compiler.d.ts.map +1 -1
  35. package/dist/esm/compilers/fulltext.compiler.js +31 -15
  36. package/dist/esm/compilers/fulltext.compiler.js.map +1 -1
  37. package/dist/esm/compilers/mutation.compiler.d.ts +7 -0
  38. package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
  39. package/dist/esm/compilers/mutation.compiler.js +130 -36
  40. package/dist/esm/compilers/mutation.compiler.js.map +1 -1
  41. package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
  42. package/dist/esm/compilers/where.compiler.js +13 -4
  43. package/dist/esm/compilers/where.compiler.js.map +1 -1
  44. package/dist/esm/generator/type-emitters/fulltext-emitter.js +24 -8
  45. package/dist/esm/generator/type-emitters/fulltext-emitter.js.map +1 -1
  46. package/dist/esm/generator/type-emitters/sort-options-emitter.d.ts +2 -1
  47. package/dist/esm/generator/type-emitters/sort-options-emitter.d.ts.map +1 -1
  48. package/dist/esm/generator/type-emitters/sort-options-emitter.js +12 -5
  49. package/dist/esm/generator/type-emitters/sort-options-emitter.js.map +1 -1
  50. package/dist/esm/generator/type-emitters/utility-types.d.ts.map +1 -1
  51. package/dist/esm/generator/type-emitters/utility-types.js +21 -1
  52. package/dist/esm/generator/type-emitters/utility-types.js.map +1 -1
  53. package/dist/esm/utils/cypher-sort-projection.d.ts +3 -0
  54. package/dist/esm/utils/cypher-sort-projection.d.ts.map +1 -1
  55. package/dist/esm/utils/cypher-sort-projection.js +14 -2
  56. package/dist/esm/utils/cypher-sort-projection.js.map +1 -1
  57. package/dist/esm/utils/validation.d.ts +72 -0
  58. package/dist/esm/utils/validation.d.ts.map +1 -1
  59. package/dist/esm/utils/validation.js +129 -0
  60. package/dist/esm/utils/validation.js.map +1 -1
  61. package/dist/esm/utils/write-coercion.d.ts +61 -0
  62. package/dist/esm/utils/write-coercion.d.ts.map +1 -0
  63. package/dist/esm/utils/write-coercion.js +206 -0
  64. package/dist/esm/utils/write-coercion.js.map +1 -0
  65. package/package.json +1 -1
@@ -4,6 +4,7 @@ exports.MutationCompiler = void 0;
4
4
  const errors_1 = require("../errors");
5
5
  const utils_1 = require("../schema/utils");
6
6
  const validation_1 = require("../utils/validation");
7
+ const write_coercion_1 = require("../utils/write-coercion");
7
8
  const where_compiler_1 = require("./where.compiler");
8
9
  /**
9
10
  * Relationship filter suffixes that require EXISTS subquery patterns.
@@ -182,9 +183,11 @@ class MutationCompiler {
182
183
  if (value === undefined)
183
184
  continue;
184
185
  (0, validation_1.assertSafePropertyName)(key, 'update property');
186
+ const propDef = nodeDef.properties.get(key);
185
187
  const paramName = `update_${key}`;
186
- setClauses.push(`n.${(0, validation_1.escapeIdentifier)(key)} = $${paramName}`);
187
- params[paramName] = value;
188
+ const valueExpr = (0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, value, propDef);
189
+ setClauses.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${valueExpr}`);
190
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
188
191
  }
189
192
  if (setClauses.length > 0)
190
193
  lines.push(`SET ${setClauses.join(', ')}`);
@@ -262,10 +265,12 @@ class MutationCompiler {
262
265
  if (key.includes('_') && !nodeDef.properties.has(key))
263
266
  continue;
264
267
  (0, validation_1.assertSafePropertyName)(key, 'merge key');
268
+ const propDef = nodeDef.properties.get(key);
265
269
  const paramName = `merge_${key}`;
266
- mergeProps.push(`${(0, validation_1.escapeIdentifier)(key)}: $${paramName}`);
270
+ const valueExpr = (0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, value, propDef);
271
+ mergeProps.push(`${(0, validation_1.escapeIdentifier)(key)}: ${valueExpr}`);
267
272
  mergeKeyNames.push(key);
268
- params[paramName] = value;
273
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
269
274
  }
270
275
  if (mergeProps.length === 0)
271
276
  throw new errors_1.OGMError('upsert requires at least one scalar property in "where" for MERGE key');
@@ -301,9 +306,29 @@ class MutationCompiler {
301
306
  if (nodeDef.relationships.has(key))
302
307
  continue;
303
308
  (0, validation_1.assertSafePropertyName)(key, 'create property');
309
+ const propDef = nodeDef.properties.get(key);
304
310
  const paramName = `onCreate_${key}`;
305
- createSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = $${paramName}`);
306
- params[paramName] = value;
311
+ const valueExpr = (0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, value, propDef);
312
+ createSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${valueExpr}`);
313
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
314
+ }
315
+ // `@default` values apply to the ON CREATE branch — a defaulted
316
+ // property not supplied in `create` and not already fixed by the
317
+ // MERGE key gets its default, mirroring create().
318
+ for (const [, propDef] of nodeDef.properties) {
319
+ if (propDef.defaultValue === undefined)
320
+ continue;
321
+ if (propDef.isGenerated || propDef.isCypher)
322
+ continue;
323
+ if (create[propDef.name] !== undefined)
324
+ continue;
325
+ if (mergeKeyNames.includes(propDef.name))
326
+ continue;
327
+ const paramName = `onCreate_${propDef.name}`;
328
+ const bound = (0, write_coercion_1.resolveDefaultWriteValue)(propDef);
329
+ const valueExpr = (0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, bound, propDef);
330
+ createSets.push(`n.${(0, validation_1.escapeIdentifier)(propDef.name)} = ${valueExpr}`);
331
+ params[paramName] = bound;
307
332
  }
308
333
  if (createSets.length > 0)
309
334
  lines.push(`ON CREATE SET ${createSets.join(', ')}`);
@@ -315,9 +340,11 @@ class MutationCompiler {
315
340
  if (nodeDef.relationships.has(key))
316
341
  continue;
317
342
  (0, validation_1.assertSafePropertyName)(key, 'update property');
343
+ const propDef = nodeDef.properties.get(key);
318
344
  const paramName = `onMatch_${key}`;
319
- updateSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = $${paramName}`);
320
- params[paramName] = value;
345
+ const valueExpr = (0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, value, propDef);
346
+ updateSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${valueExpr}`);
347
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
321
348
  }
322
349
  if (updateSets.length > 0)
323
350
  lines.push(`ON MATCH SET ${updateSets.join(', ')}`);
@@ -389,12 +416,24 @@ class MutationCompiler {
389
416
  for (const [, propDef] of nodeDef.properties)
390
417
  if (propDef.isGenerated && !scalarKeys.includes(propDef.name))
391
418
  generatedFields.push(propDef.name);
419
+ // Whole-column `@default`s: applied only when NO item supplies the
420
+ // field. Per-item gaps inside a provided column stay null — one
421
+ // createMany batch shares a single Cypher text, and a per-item
422
+ // default would need coalesce(), which would also override explicit
423
+ // nulls.
424
+ const defaultedProps = [];
425
+ for (const [, propDef] of nodeDef.properties)
426
+ if (propDef.defaultValue !== undefined &&
427
+ !propDef.isGenerated &&
428
+ !propDef.isCypher &&
429
+ !scalarKeys.includes(propDef.name))
430
+ defaultedProps.push(propDef);
392
431
  // Build sanitized items (strip undefined values)
393
432
  const sanitizedItems = data.map((item) => {
394
433
  const sanitized = {};
395
434
  for (const key of scalarKeys)
396
435
  if (item[key] !== undefined)
397
- sanitized[key] = item[key];
436
+ sanitized[key] = (0, write_coercion_1.coerceWriteValue)(item[key], nodeDef.properties.get(key));
398
437
  return sanitized;
399
438
  });
400
439
  params.items = sanitizedItems;
@@ -411,16 +450,22 @@ class MutationCompiler {
411
450
  throw new errors_1.OGMError('createMany with skipDuplicates requires at least one @id or @unique field in data');
412
451
  // MERGE on unique key(s)
413
452
  const mergeProps = mergeKeys
414
- .map((k) => `${(0, validation_1.escapeIdentifier)(k)}: item.${(0, validation_1.escapeIdentifier)(k)}`)
453
+ .map((k) => `${(0, validation_1.escapeIdentifier)(k)}: ${(0, write_coercion_1.wrapTemporalListItemExpr)(`item.${(0, validation_1.escapeIdentifier)(k)}`, data.map((d) => d[k]), nodeDef.properties.get(k))}`)
415
454
  .join(', ');
416
455
  lines.push(`MERGE (n:${labelStr} { ${mergeProps} })`);
417
456
  // ON CREATE SET — remaining fields + generated IDs
418
457
  const onCreateSets = [];
419
458
  for (const key of scalarKeys)
420
459
  if (!mergeKeys.includes(key))
421
- onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = item.${(0, validation_1.escapeIdentifier)(key)}`);
460
+ onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${(0, write_coercion_1.wrapTemporalListItemExpr)(`item.${(0, validation_1.escapeIdentifier)(key)}`, data.map((d) => d[key]), nodeDef.properties.get(key))}`);
422
461
  for (const field of generatedFields)
423
462
  onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(field)} = randomUUID()`);
463
+ for (const propDef of defaultedProps) {
464
+ const paramName = `default_${propDef.name}`;
465
+ const bound = (0, write_coercion_1.resolveDefaultWriteValue)(propDef);
466
+ onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(propDef.name)} = ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, bound, propDef)}`);
467
+ params[paramName] = bound;
468
+ }
424
469
  if (onCreateSets.length > 0)
425
470
  lines.push(`ON CREATE SET ${onCreateSets.join(', ')}`);
426
471
  }
@@ -430,7 +475,13 @@ class MutationCompiler {
430
475
  for (const field of generatedFields)
431
476
  propParts.push(`${(0, validation_1.escapeIdentifier)(field)}: randomUUID()`);
432
477
  for (const key of scalarKeys)
433
- propParts.push(`${(0, validation_1.escapeIdentifier)(key)}: item.${(0, validation_1.escapeIdentifier)(key)}`);
478
+ propParts.push(`${(0, validation_1.escapeIdentifier)(key)}: ${(0, write_coercion_1.wrapTemporalListItemExpr)(`item.${(0, validation_1.escapeIdentifier)(key)}`, data.map((d) => d[key]), nodeDef.properties.get(key))}`);
479
+ for (const propDef of defaultedProps) {
480
+ const paramName = `default_${propDef.name}`;
481
+ const bound = (0, write_coercion_1.resolveDefaultWriteValue)(propDef);
482
+ propParts.push(`${(0, validation_1.escapeIdentifier)(propDef.name)}: ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, bound, propDef)}`);
483
+ params[paramName] = bound;
484
+ }
434
485
  lines.push(`CREATE (n:${labelStr} { ${propParts.join(', ')} })`);
435
486
  }
436
487
  // Extra labels
@@ -442,6 +493,19 @@ class MutationCompiler {
442
493
  return { cypher: lines.join('\n'), params };
443
494
  }
444
495
  // ─── Private helpers ──────────────────────────────────────────────
496
+ /**
497
+ * Resolve the PropertyDefinition of an edge (relationship) property so
498
+ * Int/BigInt edge writes get the same Integer coercion as node writes.
499
+ * Returns undefined when the relationship declares no properties type
500
+ * (the coercion then passes the value through untouched).
501
+ */
502
+ getEdgePropDef(relDef, propName) {
503
+ if (!relDef.properties)
504
+ return undefined;
505
+ return this.schema.relationshipProperties
506
+ .get(relDef.properties)
507
+ ?.properties.get(propName);
508
+ }
445
509
  buildCreateProperties(input, nodeDef, prefix) {
446
510
  const parts = [];
447
511
  const propParams = {};
@@ -456,9 +520,28 @@ class MutationCompiler {
456
520
  if (value === undefined)
457
521
  continue;
458
522
  (0, validation_1.assertSafePropertyName)(key, 'create property');
523
+ const propDef = nodeDef.properties.get(key);
459
524
  const paramName = `${prefix}_${key}`;
460
- parts.push(`${(0, validation_1.escapeIdentifier)(key)}: $${paramName}`);
461
- propParams[paramName] = value;
525
+ const valueExpr = (0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, value, propDef);
526
+ parts.push(`${(0, validation_1.escapeIdentifier)(key)}: ${valueExpr}`);
527
+ propParams[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
528
+ }
529
+ // `@default` values — applied for any property with a default that the
530
+ // input leaves undefined. Explicit null is respected as null: the user
531
+ // asked for null, a default would override intent. Generated (@id) and
532
+ // @cypher (computed, not stored) properties never take defaults.
533
+ for (const [, propDef] of nodeDef.properties) {
534
+ if (propDef.defaultValue === undefined)
535
+ continue;
536
+ if (propDef.isGenerated || propDef.isCypher)
537
+ continue;
538
+ if (input[propDef.name] !== undefined)
539
+ continue;
540
+ const paramName = `${prefix}_${propDef.name}`;
541
+ const bound = (0, write_coercion_1.resolveDefaultWriteValue)(propDef);
542
+ const valueExpr = (0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, bound, propDef);
543
+ parts.push(`${(0, validation_1.escapeIdentifier)(propDef.name)}: ${valueExpr}`);
544
+ propParams[paramName] = bound;
462
545
  }
463
546
  return { propString: parts.join(', '), propParams };
464
547
  }
@@ -563,9 +646,10 @@ class MutationCompiler {
563
646
  if (val === undefined)
564
647
  continue;
565
648
  (0, validation_1.assertSafePropertyName)(prop, 'edge property');
649
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
566
650
  const paramName = `${prefix}_create${ci}_edge_${prop}`;
567
- setItems.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = $${paramName}`);
568
- params[paramName] = val;
651
+ setItems.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
652
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
569
653
  }
570
654
  if (setItems.length > 0)
571
655
  lines.push(`SET ${setItems.join(', ')}`);
@@ -615,9 +699,10 @@ class MutationCompiler {
615
699
  if (val === undefined)
616
700
  continue;
617
701
  (0, validation_1.assertSafePropertyName)(prop, 'edge property');
702
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
618
703
  const paramName = `${prefix}_conn${ci}_edge_${prop}`;
619
- setItems.push(`r_conn_${startCounter + counter - 1}.${(0, validation_1.escapeIdentifier)(prop)} = $${paramName}`);
620
- params[paramName] = val;
704
+ setItems.push(`r_conn_${startCounter + counter - 1}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
705
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
621
706
  }
622
707
  if (setItems.length > 0)
623
708
  lines.push(`SET ${setItems.join(', ')}`);
@@ -686,9 +771,10 @@ class MutationCompiler {
686
771
  if (val === undefined)
687
772
  continue;
688
773
  (0, validation_1.assertSafePropertyName)(prop, 'edge property');
774
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
689
775
  const paramName = `connect_${fieldName}_${ci}_edge_${prop}`;
690
- setItems.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = $${paramName}`);
691
- params[paramName] = val;
776
+ setItems.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
777
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
692
778
  }
693
779
  if (setItems.length > 0)
694
780
  lines.push(`SET ${setItems.join(', ')}`);
@@ -716,7 +802,7 @@ class MutationCompiler {
716
802
  const cleaned = {};
717
803
  for (const [k, v] of Object.entries(edge))
718
804
  if (v !== undefined)
719
- cleaned[k] = v;
805
+ cleaned[k] = (0, write_coercion_1.coerceWriteValue)(v, this.getEdgePropDef(relDef, k));
720
806
  return { ...item, edge: cleaned };
721
807
  });
722
808
  lines.push('WITH n');
@@ -740,7 +826,7 @@ class MutationCompiler {
740
826
  `silently dropped. Split into separate connect calls, or normalise the items ` +
741
827
  `to share the same shape.`);
742
828
  }
743
- const whereConditions = this.extractConnectWhereConditions(firstItem, 'target', 'connItem');
829
+ const whereConditions = this.extractConnectWhereConditions(firstItem, 'target', 'connItem', targetNodeDef);
744
830
  const policyPredicate = this.buildTargetPolicyPredicate(targetNodeDef, 'target', params, policyContext, paramCounter);
745
831
  const unwindConditions = policyPredicate
746
832
  ? [...whereConditions, policyPredicate]
@@ -765,7 +851,7 @@ class MutationCompiler {
765
851
  // Replace MERGE line with one that has a variable
766
852
  lines[lines.length - 1] = `MERGE ${mergeWithVar}`;
767
853
  // r.prop is relationship property (escape), connItem.edge.prop is parameter map (no escape)
768
- const setItems = edgeProps.map((p) => `r.${(0, validation_1.escapeIdentifier)(p)} = connItem.edge.${p}`);
854
+ const setItems = edgeProps.map((p) => `r.${(0, validation_1.escapeIdentifier)(p)} = ${(0, write_coercion_1.wrapTemporalListItemExpr)(`connItem.edge.${p}`, spec.map((it) => it.edge?.[p]), this.getEdgePropDef(relDef, p))}`);
769
855
  lines.push(`SET ${setItems.join(', ')}`);
770
856
  }
771
857
  }
@@ -796,9 +882,10 @@ class MutationCompiler {
796
882
  for (const [prop, val] of Object.entries(edgeInput)) {
797
883
  if (val === undefined)
798
884
  continue;
885
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
799
886
  const paramName = `connect_${fieldName}_edge_${prop}`;
800
- setItems.push(`r.${(0, validation_1.escapeIdentifier)(prop)} = $${paramName}`);
801
- params[paramName] = val;
887
+ setItems.push(`r.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
888
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
802
889
  }
803
890
  if (setItems.length > 0)
804
891
  lines.push(`SET ${setItems.join(', ')}`);
@@ -1111,9 +1198,10 @@ class MutationCompiler {
1111
1198
  if (val === undefined)
1112
1199
  continue;
1113
1200
  (0, validation_1.assertSafePropertyName)(prop, 'edge property');
1201
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
1114
1202
  const paramName = `${itemPrefix}_conn${ci}_edge_${prop}`;
1115
- setItems.push(`r_conn_${key}_${ci}.${(0, validation_1.escapeIdentifier)(prop)} = $${paramName}`);
1116
- params[paramName] = val;
1203
+ setItems.push(`r_conn_${key}_${ci}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
1204
+ params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
1117
1205
  }
1118
1206
  if (setItems.length > 0)
1119
1207
  lines.push(`SET ${setItems.join(', ')}`);
@@ -1149,9 +1237,10 @@ class MutationCompiler {
1149
1237
  if (val === undefined)
1150
1238
  continue;
1151
1239
  (0, validation_1.assertSafePropertyName)(prop, 'nested update property');
1240
+ const nodePropDef = targetNodeDef.properties.get(prop);
1152
1241
  const paramName = `${itemPrefix}_set_${prop}`;
1153
- setClauses.push(`${updateVar}.${(0, validation_1.escapeIdentifier)(prop)} = $${paramName}`);
1154
- setParams[paramName] = val;
1242
+ setClauses.push(`${updateVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, val, nodePropDef)}`);
1243
+ setParams[paramName] = (0, write_coercion_1.coerceWriteValue)(val, nodePropDef);
1155
1244
  }
1156
1245
  // Pre-compute SET clauses for edge (relationship) properties
1157
1246
  const edgeClauses = [];
@@ -1161,9 +1250,10 @@ class MutationCompiler {
1161
1250
  if (val === undefined)
1162
1251
  continue;
1163
1252
  (0, validation_1.assertSafePropertyName)(prop, 'edge update property');
1253
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
1164
1254
  const paramName = `${itemPrefix}_edge_${prop}`;
1165
- edgeClauses.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = $${paramName}`);
1166
- edgeParams[paramName] = val;
1255
+ edgeClauses.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapTemporalWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
1256
+ edgeParams[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
1167
1257
  }
1168
1258
  // Pre-compute nested relationship operations
1169
1259
  const nestedParams = {};
@@ -1249,15 +1339,16 @@ class MutationCompiler {
1249
1339
  (0, validation_1.assertSafePropertyName)(baseProp, 'where property');
1250
1340
  const paramName = `${prefix}_NOT_${notProp}`;
1251
1341
  const escapedProp = (0, validation_1.escapeIdentifier)(baseProp);
1342
+ const paramRef = (0, write_coercion_1.wrapTemporalWhereParam)(`$${paramName}`, notVal, targetNodeDef?.properties.get(baseProp), notProp.slice(baseProp.length));
1252
1343
  let expr;
1253
1344
  if (template === '%v = $%p')
1254
1345
  // Simple equality → negate to <>
1255
- expr = `${targetVar}.${escapedProp} <> $${paramName}`;
1346
+ expr = `${targetVar}.${escapedProp} <> ${paramRef}`;
1256
1347
  else {
1257
1348
  // Operator suffix — wrap with NOT if not already negated
1258
1349
  const resolved = template
1259
1350
  .replace('%v', `${targetVar}.${escapedProp}`)
1260
- .replace('$%p', `$${paramName}`);
1351
+ .replace('$%p', paramRef);
1261
1352
  expr = resolved.startsWith('NOT ') ? resolved : `NOT ${resolved}`;
1262
1353
  }
1263
1354
  conditions.push(expr);
@@ -1274,9 +1365,10 @@ class MutationCompiler {
1274
1365
  const { baseProp, template } = this.parseOperatorSuffix(prop);
1275
1366
  (0, validation_1.assertSafePropertyName)(baseProp, 'where property');
1276
1367
  const paramName = `${prefix}_${prop}`;
1368
+ const paramRef = (0, write_coercion_1.wrapTemporalWhereParam)(`$${paramName}`, val, targetNodeDef?.properties.get(baseProp), prop.slice(baseProp.length));
1277
1369
  conditions.push(template
1278
1370
  .replace('%v', `${targetVar}.${(0, validation_1.escapeIdentifier)(baseProp)}`)
1279
- .replace('$%p', `$${paramName}`));
1371
+ .replace('$%p', paramRef));
1280
1372
  params[paramName] = val;
1281
1373
  }
1282
1374
  return conditions;
@@ -1441,7 +1533,7 @@ class MutationCompiler {
1441
1533
  }
1442
1534
  return false;
1443
1535
  }
1444
- extractConnectWhereConditions(item, targetVar, itemVar) {
1536
+ extractConnectWhereConditions(item, targetVar, itemVar, targetNodeDef) {
1445
1537
  const conditions = [];
1446
1538
  const whereSpec = item.where;
1447
1539
  if (!whereSpec)
@@ -1452,7 +1544,9 @@ class MutationCompiler {
1452
1544
  (0, validation_1.assertSafePropertyName)(baseProp, 'connect where property');
1453
1545
  // Replace template placeholders with dynamic UNWIND references
1454
1546
  // Note: itemVar map access is NOT escaped (parameter map keys, not Cypher identifiers)
1455
- const valueRef = `${itemVar}.where.node.${key}`;
1547
+ // Temporal wrap keys off the FIRST item's value type — the UNWIND
1548
+ // fast path already requires shape-homogeneous items.
1549
+ const valueRef = (0, write_coercion_1.wrapTemporalWhereParam)(`${itemVar}.where.node.${key}`, nodeWhere[key], targetNodeDef?.properties.get(baseProp), key.slice(baseProp.length));
1456
1550
  conditions.push(template
1457
1551
  .replace('%v', `${targetVar}.${(0, validation_1.escapeIdentifier)(baseProp)}`)
1458
1552
  .replace('$%p', valueRef));