grafeo-ogm 2.0.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.
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.coerceWriteValue = coerceWriteValue;
4
+ exports.wrapTemporalWriteExpr = wrapTemporalWriteExpr;
5
+ exports.wrapTemporalListItemExpr = wrapTemporalListItemExpr;
6
+ exports.wrapTemporalWhereParam = wrapTemporalWhereParam;
7
+ exports.resolveDefaultWriteValue = resolveDefaultWriteValue;
8
+ const neo4j_driver_1 = require("neo4j-driver");
9
+ const errors_1 = require("../errors");
10
+ /**
11
+ * GraphQL scalar types whose Neo4j storage type is INTEGER (Long).
12
+ */
13
+ const INTEGER_SCALAR_TYPES = new Set(['Int', 'BigInt']);
14
+ /**
15
+ * Temporal scalar types → the Cypher constructor that parses their ISO
16
+ * string form into the native Neo4j temporal type. Writes wrap the
17
+ * *parameter* in the constructor instead of converting the value in JS,
18
+ * because Neo4j's own parsers handle everything JS `Date` cannot:
19
+ * nanosecond precision, bracket timezone names
20
+ * (`2024-01-15T10:30:00[America/New_York]`), and ISO-8601 durations.
21
+ */
22
+ const TEMPORAL_CONSTRUCTORS = {
23
+ DateTime: 'datetime',
24
+ LocalDateTime: 'localdatetime',
25
+ Date: 'date',
26
+ Time: 'time',
27
+ LocalTime: 'localtime',
28
+ Duration: 'duration',
29
+ };
30
+ /**
31
+ * Where-operator suffixes whose parameter is compared against the field
32
+ * value (`''` = plain equality) and therefore needs the same temporal
33
+ * constructor treatment as writes. String operators (`_CONTAINS`,
34
+ * `_STARTS_WITH`, `_MATCHES`, ...) are excluded — their params are
35
+ * strings by definition.
36
+ */
37
+ const TEMPORAL_COMPARABLE_SUFFIXES = new Set([
38
+ '',
39
+ '_NOT',
40
+ '_GT',
41
+ '_GTE',
42
+ '_LT',
43
+ '_LTE',
44
+ '_IN',
45
+ '_NOT_IN',
46
+ ]);
47
+ /**
48
+ * Coerce a JS write value bound for an `Int`/`BigInt`-typed schema field
49
+ * into a Neo4j Integer so the stored property is a Long, not a Double.
50
+ *
51
+ * The driver packs EVERY JS `number` as a Bolt FLOAT64 (packstream:
52
+ * `typeof x === 'number'` → `packFloat`) — `disableLosslessIntegers`
53
+ * only affects the read direction. Reads convert Integer → number
54
+ * (ResultMapper), so without write-side coercion the round-trip is
55
+ * asymmetric: every OGM-written Int property lands as a Double. Once a
56
+ * field holds a mixed Long/Double population, Long-typed consumers break
57
+ * (e.g. `apoc.coll.sortMulti` throws `java.lang.Double cannot be cast to
58
+ * class java.lang.Long`).
59
+ *
60
+ * Non-integral numbers and non-integer strings for Int/BigInt fields
61
+ * throw instead of truncating or storing a String property, matching the
62
+ * v1.14 contract of rejecting inputs that would be silently corrupted.
63
+ * Values the coercion doesn't own — null/undefined, driver Integer
64
+ * instances, fields of any other scalar type, or fields absent from the
65
+ * schema — pass through untouched.
66
+ */
67
+ function coerceWriteValue(value, propDef) {
68
+ if (propDef === undefined || value === null || value === undefined)
69
+ return value;
70
+ if (!INTEGER_SCALAR_TYPES.has(propDef.type))
71
+ return value;
72
+ if (propDef.isArray) {
73
+ if (!Array.isArray(value))
74
+ return value;
75
+ return value.map((element) => coerceIntegerElement(element, propDef));
76
+ }
77
+ return coerceIntegerElement(value, propDef);
78
+ }
79
+ function coerceIntegerElement(value, propDef) {
80
+ if (value === null || value === undefined)
81
+ return value;
82
+ // `int()` accepts strings in every driver major — `toString()` keeps
83
+ // bigint inputs working regardless of the installed driver's overloads.
84
+ if (typeof value === 'bigint')
85
+ return (0, neo4j_driver_1.int)(value.toString());
86
+ if (typeof value === 'number') {
87
+ if (!Number.isInteger(value))
88
+ throw new errors_1.OGMError(`Expected an integer for ${propDef.type} field "${propDef.name}" but got ${value}. ` +
89
+ `Storing it would corrupt the property's integer type; pass a whole number ` +
90
+ `(or declare the field as Float).`);
91
+ return (0, neo4j_driver_1.int)(value);
92
+ }
93
+ if (typeof value === 'string') {
94
+ // The generated BigInt scalar declares `input: string` (64-bit values
95
+ // don't fit a JS number), so integer strings are the CANONICAL input
96
+ // shape for BigInt fields — binding them raw would store a String
97
+ // property. Same reject-don't-corrupt rule as numbers for anything
98
+ // that isn't a plain base-10 integer literal.
99
+ if (!/^-?\d+$/.test(value))
100
+ throw new errors_1.OGMError(`Expected an integer for ${propDef.type} field "${propDef.name}" but got the string "${value}". ` +
101
+ `Pass a base-10 integer literal string, a whole number, or a bigint.`);
102
+ return (0, neo4j_driver_1.int)(value);
103
+ }
104
+ // Already a driver Integer — pass through unchanged.
105
+ if ((0, neo4j_driver_1.isInt)(value))
106
+ return value;
107
+ return value;
108
+ }
109
+ /**
110
+ * Wrap a write RHS expression (`$param`, or an UNWIND item reference) in
111
+ * the field's temporal Cypher constructor, so ISO-string inputs are
112
+ * stored as native temporal properties instead of Strings. Reads convert
113
+ * native temporals → ISO strings (ResultMapper), so this is the write
114
+ * half of the same round-trip symmetry issue #5 fixed for integers.
115
+ *
116
+ * Only string values are wrapped — a driver temporal object already
117
+ * packs natively and binds raw. Temporal LIST fields wrap element-wise
118
+ * via a list comprehension when any element is a string.
119
+ */
120
+ function wrapTemporalWriteExpr(expr, value, propDef) {
121
+ if (propDef === undefined)
122
+ return expr;
123
+ const ctor = TEMPORAL_CONSTRUCTORS[propDef.type];
124
+ if (ctor === undefined)
125
+ return expr;
126
+ if (propDef.isArray) {
127
+ if (!Array.isArray(value) || !value.some((el) => typeof el === 'string'))
128
+ return expr;
129
+ return `[wc_t IN ${expr} | ${ctor}(wc_t)]`;
130
+ }
131
+ if (typeof value !== 'string')
132
+ return expr;
133
+ return `${ctor}(${expr})`;
134
+ }
135
+ /**
136
+ * Temporal wrapping for UNWIND item references (`item.prop`,
137
+ * `connItem.edge.prop`), where ONE Cypher expression serves EVERY item in
138
+ * the batch: the wrap decision must hold for all items' values for the
139
+ * key. A mixed string/non-string column throws — a single expression
140
+ * cannot be correct for both representations.
141
+ */
142
+ function wrapTemporalListItemExpr(expr, values, propDef) {
143
+ if (propDef === undefined)
144
+ return expr;
145
+ const ctor = TEMPORAL_CONSTRUCTORS[propDef.type];
146
+ if (ctor === undefined)
147
+ return expr;
148
+ const defined = values.filter((v) => v !== null && v !== undefined);
149
+ if (defined.length === 0)
150
+ return expr;
151
+ if (propDef.isArray) {
152
+ const anyString = defined.some((v) => Array.isArray(v) && v.some((el) => typeof el === 'string'));
153
+ return anyString ? `[wc_t IN ${expr} | ${ctor}(wc_t)]` : expr;
154
+ }
155
+ const stringCount = defined.filter((v) => typeof v === 'string').length;
156
+ if (stringCount === 0)
157
+ return expr;
158
+ if (stringCount !== defined.length)
159
+ throw new errors_1.OGMError(`Mixed input types for ${propDef.type} field "${propDef.name}": some items pass ISO ` +
160
+ `strings, others pass non-string values. Batched writes share one Cypher expression ` +
161
+ `per field, so every item must use the same representation.`);
162
+ return `${ctor}(${expr})`;
163
+ }
164
+ /**
165
+ * Wrap a WHERE parameter reference for a temporal field. With properties
166
+ * stored as native temporals, comparing them to a raw string param is a
167
+ * cross-type comparison — Neo4j evaluates it to NULL and silently drops
168
+ * the row. `suffix` is the operator suffix (`''` for plain equality);
169
+ * only comparison-shaped operators wrap, and `_IN`/`_NOT_IN` wrap their
170
+ * list param element-wise.
171
+ */
172
+ function wrapTemporalWhereParam(paramRef, value, propDef, suffix) {
173
+ if (propDef === undefined)
174
+ return paramRef;
175
+ const ctor = TEMPORAL_CONSTRUCTORS[propDef.type];
176
+ if (ctor === undefined)
177
+ return paramRef;
178
+ if (!TEMPORAL_COMPARABLE_SUFFIXES.has(suffix))
179
+ return paramRef;
180
+ if (suffix === '_IN' || suffix === '_NOT_IN') {
181
+ if (!Array.isArray(value) || !value.some((el) => typeof el === 'string'))
182
+ return paramRef;
183
+ return `[wc_t IN ${paramRef} | ${ctor}(wc_t)]`;
184
+ }
185
+ if (typeof value !== 'string')
186
+ return paramRef;
187
+ return `${ctor}(${paramRef})`;
188
+ }
189
+ /**
190
+ * Materialize a parsed `@default` into a driver-ready bound value. The
191
+ * parser stores every default as a string (`'true'`, `'5'`, `'WELCOME'`);
192
+ * conversion is by declared scalar type, and Int/BigInt defaults route
193
+ * through the same Integer coercion as user input. Temporal defaults stay
194
+ * ISO strings here — they get their constructor wrapper at the binding
195
+ * site like any other temporal write.
196
+ */
197
+ function resolveDefaultWriteValue(propDef) {
198
+ const raw = propDef.defaultValue;
199
+ if (raw === undefined)
200
+ return undefined;
201
+ switch (propDef.type) {
202
+ case 'Boolean':
203
+ return raw === 'true';
204
+ case 'Float':
205
+ return Number(raw);
206
+ case 'Int':
207
+ case 'BigInt':
208
+ return coerceWriteValue(raw, propDef);
209
+ default:
210
+ return raw;
211
+ }
212
+ }
213
+ //# sourceMappingURL=write-coercion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-coercion.js","sourceRoot":"","sources":["../../../src/utils/write-coercion.ts"],"names":[],"mappings":"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"}
@@ -120,6 +120,13 @@ export declare class MutationCompiler {
120
120
  * Returns count of created nodes.
121
121
  */
122
122
  compileCreateMany(data: Record<string, unknown>[], nodeDef: NodeDefinition, skipDuplicates?: boolean, labels?: string[]): MutationResult;
123
+ /**
124
+ * Resolve the PropertyDefinition of an edge (relationship) property so
125
+ * Int/BigInt edge writes get the same Integer coercion as node writes.
126
+ * Returns undefined when the relationship declares no properties type
127
+ * (the coercion then passes the value through untouched).
128
+ */
129
+ private getEdgePropDef;
123
130
  private buildCreateProperties;
124
131
  private buildCreateRelationships;
125
132
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { OGMError } from '../errors';
2
2
  import { buildRelPattern, getTargetLabelString, resolveTargetDef, } from '../schema/utils';
3
3
  import { assertSafeIdentifier, assertSafeLabel, assertSafePropertyName, escapeIdentifier, mergeParams, } from '../utils/validation';
4
+ import { coerceWriteValue, resolveDefaultWriteValue, wrapTemporalListItemExpr, wrapTemporalWhereParam, wrapTemporalWriteExpr, } from '../utils/write-coercion';
4
5
  import { WhereCompiler } from './where.compiler';
5
6
  /**
6
7
  * Relationship filter suffixes that require EXISTS subquery patterns.
@@ -179,9 +180,11 @@ export class MutationCompiler {
179
180
  if (value === undefined)
180
181
  continue;
181
182
  assertSafePropertyName(key, 'update property');
183
+ const propDef = nodeDef.properties.get(key);
182
184
  const paramName = `update_${key}`;
183
- setClauses.push(`n.${escapeIdentifier(key)} = $${paramName}`);
184
- params[paramName] = value;
185
+ const valueExpr = wrapTemporalWriteExpr(`$${paramName}`, value, propDef);
186
+ setClauses.push(`n.${escapeIdentifier(key)} = ${valueExpr}`);
187
+ params[paramName] = coerceWriteValue(value, propDef);
185
188
  }
186
189
  if (setClauses.length > 0)
187
190
  lines.push(`SET ${setClauses.join(', ')}`);
@@ -259,10 +262,12 @@ export class MutationCompiler {
259
262
  if (key.includes('_') && !nodeDef.properties.has(key))
260
263
  continue;
261
264
  assertSafePropertyName(key, 'merge key');
265
+ const propDef = nodeDef.properties.get(key);
262
266
  const paramName = `merge_${key}`;
263
- mergeProps.push(`${escapeIdentifier(key)}: $${paramName}`);
267
+ const valueExpr = wrapTemporalWriteExpr(`$${paramName}`, value, propDef);
268
+ mergeProps.push(`${escapeIdentifier(key)}: ${valueExpr}`);
264
269
  mergeKeyNames.push(key);
265
- params[paramName] = value;
270
+ params[paramName] = coerceWriteValue(value, propDef);
266
271
  }
267
272
  if (mergeProps.length === 0)
268
273
  throw new OGMError('upsert requires at least one scalar property in "where" for MERGE key');
@@ -298,9 +303,29 @@ export class MutationCompiler {
298
303
  if (nodeDef.relationships.has(key))
299
304
  continue;
300
305
  assertSafePropertyName(key, 'create property');
306
+ const propDef = nodeDef.properties.get(key);
301
307
  const paramName = `onCreate_${key}`;
302
- createSets.push(`n.${escapeIdentifier(key)} = $${paramName}`);
303
- params[paramName] = value;
308
+ const valueExpr = wrapTemporalWriteExpr(`$${paramName}`, value, propDef);
309
+ createSets.push(`n.${escapeIdentifier(key)} = ${valueExpr}`);
310
+ params[paramName] = coerceWriteValue(value, propDef);
311
+ }
312
+ // `@default` values apply to the ON CREATE branch — a defaulted
313
+ // property not supplied in `create` and not already fixed by the
314
+ // MERGE key gets its default, mirroring create().
315
+ for (const [, propDef] of nodeDef.properties) {
316
+ if (propDef.defaultValue === undefined)
317
+ continue;
318
+ if (propDef.isGenerated || propDef.isCypher)
319
+ continue;
320
+ if (create[propDef.name] !== undefined)
321
+ continue;
322
+ if (mergeKeyNames.includes(propDef.name))
323
+ continue;
324
+ const paramName = `onCreate_${propDef.name}`;
325
+ const bound = resolveDefaultWriteValue(propDef);
326
+ const valueExpr = wrapTemporalWriteExpr(`$${paramName}`, bound, propDef);
327
+ createSets.push(`n.${escapeIdentifier(propDef.name)} = ${valueExpr}`);
328
+ params[paramName] = bound;
304
329
  }
305
330
  if (createSets.length > 0)
306
331
  lines.push(`ON CREATE SET ${createSets.join(', ')}`);
@@ -312,9 +337,11 @@ export class MutationCompiler {
312
337
  if (nodeDef.relationships.has(key))
313
338
  continue;
314
339
  assertSafePropertyName(key, 'update property');
340
+ const propDef = nodeDef.properties.get(key);
315
341
  const paramName = `onMatch_${key}`;
316
- updateSets.push(`n.${escapeIdentifier(key)} = $${paramName}`);
317
- params[paramName] = value;
342
+ const valueExpr = wrapTemporalWriteExpr(`$${paramName}`, value, propDef);
343
+ updateSets.push(`n.${escapeIdentifier(key)} = ${valueExpr}`);
344
+ params[paramName] = coerceWriteValue(value, propDef);
318
345
  }
319
346
  if (updateSets.length > 0)
320
347
  lines.push(`ON MATCH SET ${updateSets.join(', ')}`);
@@ -386,12 +413,24 @@ export class MutationCompiler {
386
413
  for (const [, propDef] of nodeDef.properties)
387
414
  if (propDef.isGenerated && !scalarKeys.includes(propDef.name))
388
415
  generatedFields.push(propDef.name);
416
+ // Whole-column `@default`s: applied only when NO item supplies the
417
+ // field. Per-item gaps inside a provided column stay null — one
418
+ // createMany batch shares a single Cypher text, and a per-item
419
+ // default would need coalesce(), which would also override explicit
420
+ // nulls.
421
+ const defaultedProps = [];
422
+ for (const [, propDef] of nodeDef.properties)
423
+ if (propDef.defaultValue !== undefined &&
424
+ !propDef.isGenerated &&
425
+ !propDef.isCypher &&
426
+ !scalarKeys.includes(propDef.name))
427
+ defaultedProps.push(propDef);
389
428
  // Build sanitized items (strip undefined values)
390
429
  const sanitizedItems = data.map((item) => {
391
430
  const sanitized = {};
392
431
  for (const key of scalarKeys)
393
432
  if (item[key] !== undefined)
394
- sanitized[key] = item[key];
433
+ sanitized[key] = coerceWriteValue(item[key], nodeDef.properties.get(key));
395
434
  return sanitized;
396
435
  });
397
436
  params.items = sanitizedItems;
@@ -408,16 +447,22 @@ export class MutationCompiler {
408
447
  throw new OGMError('createMany with skipDuplicates requires at least one @id or @unique field in data');
409
448
  // MERGE on unique key(s)
410
449
  const mergeProps = mergeKeys
411
- .map((k) => `${escapeIdentifier(k)}: item.${escapeIdentifier(k)}`)
450
+ .map((k) => `${escapeIdentifier(k)}: ${wrapTemporalListItemExpr(`item.${escapeIdentifier(k)}`, data.map((d) => d[k]), nodeDef.properties.get(k))}`)
412
451
  .join(', ');
413
452
  lines.push(`MERGE (n:${labelStr} { ${mergeProps} })`);
414
453
  // ON CREATE SET — remaining fields + generated IDs
415
454
  const onCreateSets = [];
416
455
  for (const key of scalarKeys)
417
456
  if (!mergeKeys.includes(key))
418
- onCreateSets.push(`n.${escapeIdentifier(key)} = item.${escapeIdentifier(key)}`);
457
+ onCreateSets.push(`n.${escapeIdentifier(key)} = ${wrapTemporalListItemExpr(`item.${escapeIdentifier(key)}`, data.map((d) => d[key]), nodeDef.properties.get(key))}`);
419
458
  for (const field of generatedFields)
420
459
  onCreateSets.push(`n.${escapeIdentifier(field)} = randomUUID()`);
460
+ for (const propDef of defaultedProps) {
461
+ const paramName = `default_${propDef.name}`;
462
+ const bound = resolveDefaultWriteValue(propDef);
463
+ onCreateSets.push(`n.${escapeIdentifier(propDef.name)} = ${wrapTemporalWriteExpr(`$${paramName}`, bound, propDef)}`);
464
+ params[paramName] = bound;
465
+ }
421
466
  if (onCreateSets.length > 0)
422
467
  lines.push(`ON CREATE SET ${onCreateSets.join(', ')}`);
423
468
  }
@@ -427,7 +472,13 @@ export class MutationCompiler {
427
472
  for (const field of generatedFields)
428
473
  propParts.push(`${escapeIdentifier(field)}: randomUUID()`);
429
474
  for (const key of scalarKeys)
430
- propParts.push(`${escapeIdentifier(key)}: item.${escapeIdentifier(key)}`);
475
+ propParts.push(`${escapeIdentifier(key)}: ${wrapTemporalListItemExpr(`item.${escapeIdentifier(key)}`, data.map((d) => d[key]), nodeDef.properties.get(key))}`);
476
+ for (const propDef of defaultedProps) {
477
+ const paramName = `default_${propDef.name}`;
478
+ const bound = resolveDefaultWriteValue(propDef);
479
+ propParts.push(`${escapeIdentifier(propDef.name)}: ${wrapTemporalWriteExpr(`$${paramName}`, bound, propDef)}`);
480
+ params[paramName] = bound;
481
+ }
431
482
  lines.push(`CREATE (n:${labelStr} { ${propParts.join(', ')} })`);
432
483
  }
433
484
  // Extra labels
@@ -439,6 +490,19 @@ export class MutationCompiler {
439
490
  return { cypher: lines.join('\n'), params };
440
491
  }
441
492
  // ─── Private helpers ──────────────────────────────────────────────
493
+ /**
494
+ * Resolve the PropertyDefinition of an edge (relationship) property so
495
+ * Int/BigInt edge writes get the same Integer coercion as node writes.
496
+ * Returns undefined when the relationship declares no properties type
497
+ * (the coercion then passes the value through untouched).
498
+ */
499
+ getEdgePropDef(relDef, propName) {
500
+ if (!relDef.properties)
501
+ return undefined;
502
+ return this.schema.relationshipProperties
503
+ .get(relDef.properties)
504
+ ?.properties.get(propName);
505
+ }
442
506
  buildCreateProperties(input, nodeDef, prefix) {
443
507
  const parts = [];
444
508
  const propParams = {};
@@ -453,9 +517,28 @@ export class MutationCompiler {
453
517
  if (value === undefined)
454
518
  continue;
455
519
  assertSafePropertyName(key, 'create property');
520
+ const propDef = nodeDef.properties.get(key);
456
521
  const paramName = `${prefix}_${key}`;
457
- parts.push(`${escapeIdentifier(key)}: $${paramName}`);
458
- propParams[paramName] = value;
522
+ const valueExpr = wrapTemporalWriteExpr(`$${paramName}`, value, propDef);
523
+ parts.push(`${escapeIdentifier(key)}: ${valueExpr}`);
524
+ propParams[paramName] = coerceWriteValue(value, propDef);
525
+ }
526
+ // `@default` values — applied for any property with a default that the
527
+ // input leaves undefined. Explicit null is respected as null: the user
528
+ // asked for null, a default would override intent. Generated (@id) and
529
+ // @cypher (computed, not stored) properties never take defaults.
530
+ for (const [, propDef] of nodeDef.properties) {
531
+ if (propDef.defaultValue === undefined)
532
+ continue;
533
+ if (propDef.isGenerated || propDef.isCypher)
534
+ continue;
535
+ if (input[propDef.name] !== undefined)
536
+ continue;
537
+ const paramName = `${prefix}_${propDef.name}`;
538
+ const bound = resolveDefaultWriteValue(propDef);
539
+ const valueExpr = wrapTemporalWriteExpr(`$${paramName}`, bound, propDef);
540
+ parts.push(`${escapeIdentifier(propDef.name)}: ${valueExpr}`);
541
+ propParams[paramName] = bound;
459
542
  }
460
543
  return { propString: parts.join(', '), propParams };
461
544
  }
@@ -560,9 +643,10 @@ export class MutationCompiler {
560
643
  if (val === undefined)
561
644
  continue;
562
645
  assertSafePropertyName(prop, 'edge property');
646
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
563
647
  const paramName = `${prefix}_create${ci}_edge_${prop}`;
564
- setItems.push(`${relVar}.${escapeIdentifier(prop)} = $${paramName}`);
565
- params[paramName] = val;
648
+ setItems.push(`${relVar}.${escapeIdentifier(prop)} = ${wrapTemporalWriteExpr(`$${paramName}`, val, edgePropDef)}`);
649
+ params[paramName] = coerceWriteValue(val, edgePropDef);
566
650
  }
567
651
  if (setItems.length > 0)
568
652
  lines.push(`SET ${setItems.join(', ')}`);
@@ -612,9 +696,10 @@ export class MutationCompiler {
612
696
  if (val === undefined)
613
697
  continue;
614
698
  assertSafePropertyName(prop, 'edge property');
699
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
615
700
  const paramName = `${prefix}_conn${ci}_edge_${prop}`;
616
- setItems.push(`r_conn_${startCounter + counter - 1}.${escapeIdentifier(prop)} = $${paramName}`);
617
- params[paramName] = val;
701
+ setItems.push(`r_conn_${startCounter + counter - 1}.${escapeIdentifier(prop)} = ${wrapTemporalWriteExpr(`$${paramName}`, val, edgePropDef)}`);
702
+ params[paramName] = coerceWriteValue(val, edgePropDef);
618
703
  }
619
704
  if (setItems.length > 0)
620
705
  lines.push(`SET ${setItems.join(', ')}`);
@@ -683,9 +768,10 @@ export class MutationCompiler {
683
768
  if (val === undefined)
684
769
  continue;
685
770
  assertSafePropertyName(prop, 'edge property');
771
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
686
772
  const paramName = `connect_${fieldName}_${ci}_edge_${prop}`;
687
- setItems.push(`${relVar}.${escapeIdentifier(prop)} = $${paramName}`);
688
- params[paramName] = val;
773
+ setItems.push(`${relVar}.${escapeIdentifier(prop)} = ${wrapTemporalWriteExpr(`$${paramName}`, val, edgePropDef)}`);
774
+ params[paramName] = coerceWriteValue(val, edgePropDef);
689
775
  }
690
776
  if (setItems.length > 0)
691
777
  lines.push(`SET ${setItems.join(', ')}`);
@@ -713,7 +799,7 @@ export class MutationCompiler {
713
799
  const cleaned = {};
714
800
  for (const [k, v] of Object.entries(edge))
715
801
  if (v !== undefined)
716
- cleaned[k] = v;
802
+ cleaned[k] = coerceWriteValue(v, this.getEdgePropDef(relDef, k));
717
803
  return { ...item, edge: cleaned };
718
804
  });
719
805
  lines.push('WITH n');
@@ -737,7 +823,7 @@ export class MutationCompiler {
737
823
  `silently dropped. Split into separate connect calls, or normalise the items ` +
738
824
  `to share the same shape.`);
739
825
  }
740
- const whereConditions = this.extractConnectWhereConditions(firstItem, 'target', 'connItem');
826
+ const whereConditions = this.extractConnectWhereConditions(firstItem, 'target', 'connItem', targetNodeDef);
741
827
  const policyPredicate = this.buildTargetPolicyPredicate(targetNodeDef, 'target', params, policyContext, paramCounter);
742
828
  const unwindConditions = policyPredicate
743
829
  ? [...whereConditions, policyPredicate]
@@ -762,7 +848,7 @@ export class MutationCompiler {
762
848
  // Replace MERGE line with one that has a variable
763
849
  lines[lines.length - 1] = `MERGE ${mergeWithVar}`;
764
850
  // r.prop is relationship property (escape), connItem.edge.prop is parameter map (no escape)
765
- const setItems = edgeProps.map((p) => `r.${escapeIdentifier(p)} = connItem.edge.${p}`);
851
+ const setItems = edgeProps.map((p) => `r.${escapeIdentifier(p)} = ${wrapTemporalListItemExpr(`connItem.edge.${p}`, spec.map((it) => it.edge?.[p]), this.getEdgePropDef(relDef, p))}`);
766
852
  lines.push(`SET ${setItems.join(', ')}`);
767
853
  }
768
854
  }
@@ -793,9 +879,10 @@ export class MutationCompiler {
793
879
  for (const [prop, val] of Object.entries(edgeInput)) {
794
880
  if (val === undefined)
795
881
  continue;
882
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
796
883
  const paramName = `connect_${fieldName}_edge_${prop}`;
797
- setItems.push(`r.${escapeIdentifier(prop)} = $${paramName}`);
798
- params[paramName] = val;
884
+ setItems.push(`r.${escapeIdentifier(prop)} = ${wrapTemporalWriteExpr(`$${paramName}`, val, edgePropDef)}`);
885
+ params[paramName] = coerceWriteValue(val, edgePropDef);
799
886
  }
800
887
  if (setItems.length > 0)
801
888
  lines.push(`SET ${setItems.join(', ')}`);
@@ -1108,9 +1195,10 @@ export class MutationCompiler {
1108
1195
  if (val === undefined)
1109
1196
  continue;
1110
1197
  assertSafePropertyName(prop, 'edge property');
1198
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
1111
1199
  const paramName = `${itemPrefix}_conn${ci}_edge_${prop}`;
1112
- setItems.push(`r_conn_${key}_${ci}.${escapeIdentifier(prop)} = $${paramName}`);
1113
- params[paramName] = val;
1200
+ setItems.push(`r_conn_${key}_${ci}.${escapeIdentifier(prop)} = ${wrapTemporalWriteExpr(`$${paramName}`, val, edgePropDef)}`);
1201
+ params[paramName] = coerceWriteValue(val, edgePropDef);
1114
1202
  }
1115
1203
  if (setItems.length > 0)
1116
1204
  lines.push(`SET ${setItems.join(', ')}`);
@@ -1146,9 +1234,10 @@ export class MutationCompiler {
1146
1234
  if (val === undefined)
1147
1235
  continue;
1148
1236
  assertSafePropertyName(prop, 'nested update property');
1237
+ const nodePropDef = targetNodeDef.properties.get(prop);
1149
1238
  const paramName = `${itemPrefix}_set_${prop}`;
1150
- setClauses.push(`${updateVar}.${escapeIdentifier(prop)} = $${paramName}`);
1151
- setParams[paramName] = val;
1239
+ setClauses.push(`${updateVar}.${escapeIdentifier(prop)} = ${wrapTemporalWriteExpr(`$${paramName}`, val, nodePropDef)}`);
1240
+ setParams[paramName] = coerceWriteValue(val, nodePropDef);
1152
1241
  }
1153
1242
  // Pre-compute SET clauses for edge (relationship) properties
1154
1243
  const edgeClauses = [];
@@ -1158,9 +1247,10 @@ export class MutationCompiler {
1158
1247
  if (val === undefined)
1159
1248
  continue;
1160
1249
  assertSafePropertyName(prop, 'edge update property');
1250
+ const edgePropDef = this.getEdgePropDef(relDef, prop);
1161
1251
  const paramName = `${itemPrefix}_edge_${prop}`;
1162
- edgeClauses.push(`${relVar}.${escapeIdentifier(prop)} = $${paramName}`);
1163
- edgeParams[paramName] = val;
1252
+ edgeClauses.push(`${relVar}.${escapeIdentifier(prop)} = ${wrapTemporalWriteExpr(`$${paramName}`, val, edgePropDef)}`);
1253
+ edgeParams[paramName] = coerceWriteValue(val, edgePropDef);
1164
1254
  }
1165
1255
  // Pre-compute nested relationship operations
1166
1256
  const nestedParams = {};
@@ -1246,15 +1336,16 @@ export class MutationCompiler {
1246
1336
  assertSafePropertyName(baseProp, 'where property');
1247
1337
  const paramName = `${prefix}_NOT_${notProp}`;
1248
1338
  const escapedProp = escapeIdentifier(baseProp);
1339
+ const paramRef = wrapTemporalWhereParam(`$${paramName}`, notVal, targetNodeDef?.properties.get(baseProp), notProp.slice(baseProp.length));
1249
1340
  let expr;
1250
1341
  if (template === '%v = $%p')
1251
1342
  // Simple equality → negate to <>
1252
- expr = `${targetVar}.${escapedProp} <> $${paramName}`;
1343
+ expr = `${targetVar}.${escapedProp} <> ${paramRef}`;
1253
1344
  else {
1254
1345
  // Operator suffix — wrap with NOT if not already negated
1255
1346
  const resolved = template
1256
1347
  .replace('%v', `${targetVar}.${escapedProp}`)
1257
- .replace('$%p', `$${paramName}`);
1348
+ .replace('$%p', paramRef);
1258
1349
  expr = resolved.startsWith('NOT ') ? resolved : `NOT ${resolved}`;
1259
1350
  }
1260
1351
  conditions.push(expr);
@@ -1271,9 +1362,10 @@ export class MutationCompiler {
1271
1362
  const { baseProp, template } = this.parseOperatorSuffix(prop);
1272
1363
  assertSafePropertyName(baseProp, 'where property');
1273
1364
  const paramName = `${prefix}_${prop}`;
1365
+ const paramRef = wrapTemporalWhereParam(`$${paramName}`, val, targetNodeDef?.properties.get(baseProp), prop.slice(baseProp.length));
1274
1366
  conditions.push(template
1275
1367
  .replace('%v', `${targetVar}.${escapeIdentifier(baseProp)}`)
1276
- .replace('$%p', `$${paramName}`));
1368
+ .replace('$%p', paramRef));
1277
1369
  params[paramName] = val;
1278
1370
  }
1279
1371
  return conditions;
@@ -1438,7 +1530,7 @@ export class MutationCompiler {
1438
1530
  }
1439
1531
  return false;
1440
1532
  }
1441
- extractConnectWhereConditions(item, targetVar, itemVar) {
1533
+ extractConnectWhereConditions(item, targetVar, itemVar, targetNodeDef) {
1442
1534
  const conditions = [];
1443
1535
  const whereSpec = item.where;
1444
1536
  if (!whereSpec)
@@ -1449,7 +1541,9 @@ export class MutationCompiler {
1449
1541
  assertSafePropertyName(baseProp, 'connect where property');
1450
1542
  // Replace template placeholders with dynamic UNWIND references
1451
1543
  // Note: itemVar map access is NOT escaped (parameter map keys, not Cypher identifiers)
1452
- const valueRef = `${itemVar}.where.node.${key}`;
1544
+ // Temporal wrap keys off the FIRST item's value type — the UNWIND
1545
+ // fast path already requires shape-homogeneous items.
1546
+ const valueRef = wrapTemporalWhereParam(`${itemVar}.where.node.${key}`, nodeWhere[key], targetNodeDef?.properties.get(baseProp), key.slice(baseProp.length));
1453
1547
  conditions.push(template
1454
1548
  .replace('%v', `${targetVar}.${escapeIdentifier(baseProp)}`)
1455
1549
  .replace('$%p', valueRef));