grafeo-ogm 2.1.0 → 2.2.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 +71 -0
- package/README.md +49 -0
- package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.js +23 -23
- package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
- package/dist/cjs/compilers/where.compiler.d.ts +83 -6
- package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/where.compiler.js +156 -76
- package/dist/cjs/compilers/where.compiler.js.map +1 -1
- package/dist/cjs/execution/executor.d.ts +11 -1
- package/dist/cjs/execution/executor.d.ts.map +1 -1
- package/dist/cjs/execution/executor.js +6 -3
- package/dist/cjs/execution/executor.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/model.d.ts +38 -1
- package/dist/cjs/model.d.ts.map +1 -1
- package/dist/cjs/model.js +120 -2
- package/dist/cjs/model.js.map +1 -1
- package/dist/cjs/ogm.d.ts.map +1 -1
- package/dist/cjs/ogm.js +1 -0
- package/dist/cjs/ogm.js.map +1 -1
- package/dist/cjs/policy/explain.d.ts +59 -0
- package/dist/cjs/policy/explain.d.ts.map +1 -0
- package/dist/cjs/policy/explain.js +175 -0
- package/dist/cjs/policy/explain.js.map +1 -0
- package/dist/cjs/policy/resolver.d.ts +26 -2
- package/dist/cjs/policy/resolver.d.ts.map +1 -1
- package/dist/cjs/policy/resolver.js +103 -33
- package/dist/cjs/policy/resolver.js.map +1 -1
- package/dist/cjs/policy/types.d.ts +87 -0
- package/dist/cjs/policy/types.d.ts.map +1 -1
- package/dist/cjs/utils/write-coercion.d.ts +20 -19
- package/dist/cjs/utils/write-coercion.d.ts.map +1 -1
- package/dist/cjs/utils/write-coercion.js +120 -50
- 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 +24 -24
- package/dist/esm/compilers/mutation.compiler.js.map +1 -1
- package/dist/esm/compilers/where.compiler.d.ts +83 -6
- package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/where.compiler.js +156 -77
- package/dist/esm/compilers/where.compiler.js.map +1 -1
- package/dist/esm/execution/executor.d.ts +11 -1
- package/dist/esm/execution/executor.d.ts.map +1 -1
- package/dist/esm/execution/executor.js +6 -3
- package/dist/esm/execution/executor.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model.d.ts +38 -1
- package/dist/esm/model.d.ts.map +1 -1
- package/dist/esm/model.js +121 -3
- package/dist/esm/model.js.map +1 -1
- package/dist/esm/ogm.d.ts.map +1 -1
- package/dist/esm/ogm.js +1 -0
- package/dist/esm/ogm.js.map +1 -1
- package/dist/esm/policy/explain.d.ts +59 -0
- package/dist/esm/policy/explain.d.ts.map +1 -0
- package/dist/esm/policy/explain.js +169 -0
- package/dist/esm/policy/explain.js.map +1 -0
- package/dist/esm/policy/resolver.d.ts +26 -2
- package/dist/esm/policy/resolver.d.ts.map +1 -1
- package/dist/esm/policy/resolver.js +102 -33
- package/dist/esm/policy/resolver.js.map +1 -1
- package/dist/esm/policy/types.d.ts +87 -0
- package/dist/esm/policy/types.d.ts.map +1 -1
- package/dist/esm/utils/write-coercion.d.ts +20 -19
- package/dist/esm/utils/write-coercion.d.ts.map +1 -1
- package/dist/esm/utils/write-coercion.js +118 -48
- package/dist/esm/utils/write-coercion.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0 (2026-09-23) — 🔎 Explain policy decisions
|
|
4
|
+
|
|
5
|
+
> **Policy-bound reads can now explain themselves.** When a policy hides a node, `find` could only show *that* it disappeared, never *which* named clause removed it, because every clause is compiled into a single `WHERE` predicate. The new `Model.explainPolicies()` evaluates each `read` policy separately for every candidate node, covering both the declarative `when:` form and the raw `cypher:` form, and returns the hidden nodes too ([#6](https://github.com/neomodular/grafeo-ogm/issues/6)).
|
|
6
|
+
>
|
|
7
|
+
> **Additive minor release.** No existing signature changes, and every existing query emits byte-identical Cypher (pinned by a new 17-case golden suite). The one observable change is a correction to `policiesEvaluated` in the audit metadata (see below).
|
|
8
|
+
|
|
9
|
+
### ✨ `Model.explainPolicies()`
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const report = await ogm
|
|
13
|
+
.withContext(ctx)
|
|
14
|
+
.model('Chart')
|
|
15
|
+
.explainPolicies({ where: { id_IN: candidateIds }, select: { id: true } });
|
|
16
|
+
// → [{ node, visible, overriddenBy, permissiveGranted, failedRestrictives,
|
|
17
|
+
// policies: [{ name, named, kind, source, applied, outcome }] }]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- **Matches enforcement exactly.** Explain reuses the fragments `find` compiles, and `visible` is computed from the same composed predicate `find` puts in its `WHERE`. The verdict recomputed from the per-policy outcomes is checked against it, and a mismatch throws rather than returning a wrong explanation.
|
|
21
|
+
- **Outcomes:** `pass`, `fail`, `null` (NULL is reported separately from `false`), `abstain` (the policy emitted no predicate), `not-applied` (`appliesWhen` returned false), `skipped` (an override fired).
|
|
22
|
+
- **Candidates** are selected by `where` / `labels` exactly as in `find`. Full `select` / `selectionSet` is supported, and nested relationships are still filtered by their own policies.
|
|
23
|
+
- **Read-only:** when grafeo opens the session itself, it uses `READ` access mode. Candidates default to 100 per call; more than 1,000 is rejected.
|
|
24
|
+
- **An explicit bypass:** every call logs a `warn`, is tagged `explain: true` in the transaction metadata (even with `auditMetadata: false`), and throws on models without a policy binding and on policy-bypassed OGMs. `onDeny: 'throw'` is reported as `visible: false` instead of being thrown.
|
|
25
|
+
- Scope: `Model` and the `read` operation only. `InterfaceModel`, `count`, `aggregate`, and `delete` are not supported yet.
|
|
26
|
+
- New exported types: `PolicyExplanation`, `PolicyClauseExplanation`, `PolicyClauseOutcome`, and `ExecuteOptions`.
|
|
27
|
+
|
|
28
|
+
### 🐛 `policiesEvaluated` lists only policies that applied
|
|
29
|
+
|
|
30
|
+
The audit metadata field is documented as "names of policies that fired", but it also listed restrictives whose `appliesWhen(ctx)` returned `false`, even though those contributed nothing to the query. They are no longer listed. Emitted Cypher is unchanged.
|
|
31
|
+
|
|
32
|
+
To produce this list, the resolver now evaluates restrictive `appliesWhen` itself, and the compiler (or `evaluateWriteRestrictives`) still evaluates it too. As a result, **restrictive `appliesWhen` callbacks run twice per query**. They are documented as pure compile-time gates, so this only matters if yours have side effects (counters, logging).
|
|
33
|
+
|
|
34
|
+
### 🔧 Internal
|
|
35
|
+
|
|
36
|
+
- `WhereCompiler`'s policy clause is now built in two steps: `compilePolicyFragments()` (the per-policy parts) and `composePolicyClause()` (the combination). Output is byte-identical to 2.1.1. The new `compileForExplain()` returns the fragments without folding them into the `WHERE`.
|
|
37
|
+
- `PolicyResolver.resolveDetailed()` returns every operation-matching policy with its `applied` / `skipped` state, source, and registration index. `resolve()` is now a projection of it via `projectResolution()`.
|
|
38
|
+
- `Executor.execute()` accepts an optional 4th argument, `{ accessMode }`. It only affects sessions the executor opens itself; calls without it behave as before.
|
|
39
|
+
|
|
40
|
+
### Tests
|
|
41
|
+
|
|
42
|
+
- `tests/policy/explain-refactor-golden.spec.ts`: 17 goldens captured from the 2.1.1 compiler, covering every fragment shape, interface inheritance, `@cypher` preludes, traversal, nested selection, count, aggregate, delete, override, and `InterfaceModel`.
|
|
43
|
+
- `tests/policy/explain.spec.ts`: 27 tests covering guards, clause forms, no short-circuiting, applicability, the outcome vocabulary, fidelity with `find` (including a forced mismatch), identity, selection and prelude scoping, and security signalling.
|
|
44
|
+
- 10 new resolver tests, 2 new audit-metadata tests, and `tests/executor-access-mode.spec.ts` (5 tests).
|
|
45
|
+
|
|
46
|
+
## 2.1.1 (2026-08-13) — 📍 Point fields round-trip
|
|
47
|
+
|
|
48
|
+
> **The last member of the 2.1.0 type-faithfulness family.** Reads flatten a native Point into a plain `{ x, y[, z], srid }` object — but writing that object back bound it as a map property value, which Neo4j rejects at runtime ("Property values can only be of primitive types or arrays thereof"). A `Point` read through the OGM could not be written back through the OGM.
|
|
49
|
+
>
|
|
50
|
+
> **Pure patch:** nothing that previously worked changes behavior. Plain point writes previously failed loudly at the database; they now succeed as the README's scalar table always promised.
|
|
51
|
+
|
|
52
|
+
### 🐛 Plain point objects are wrapped in `point()`
|
|
53
|
+
|
|
54
|
+
`Point` / `CartesianPoint` writes now wrap the parameter in Cypher's `point()` constructor whenever the value is a plain point-shaped object — cartesian (`x`/`y`) or geographic (`longitude`/`latitude`). `point()` accepts the read shape back verbatim, `srid` included, so the read → write round-trip is exact:
|
|
55
|
+
|
|
56
|
+
```cypher
|
|
57
|
+
CREATE (n:`Event` { `location`: point($create0_location) })
|
|
58
|
+
// list fields wrap element-wise:
|
|
59
|
+
`route`: [wc_t IN $create0_route | point(wc_t)]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
WHERE equality and `_IN`/`_NOT_IN` params wrap the same way, so filters match natively-stored points instead of silently comparing a map against a POINT (`NULL` → row dropped). Driver `Point` instances still bind raw.
|
|
63
|
+
|
|
64
|
+
Unlike the temporal constructors, `point()` rejects an already-native POINT argument — so a list or batch column mixing plain maps with driver `Point` instances throws a clear `OGMError` at compile time, where it previously died as an opaque database error.
|
|
65
|
+
|
|
66
|
+
### 🔧 Internal
|
|
67
|
+
|
|
68
|
+
The write-constructor helpers are no longer temporal-specific and were renamed accordingly (`wrapWriteExpr`, `wrapListItemExpr`, `wrapWhereParam`); each constructor family now carries an explicit list rule (`arrayMode: 'any'` for temporals — `datetime(datetime)` is legal Cypher — vs `'all'` for points). 2.1.0 temporal behavior is byte-identical.
|
|
69
|
+
|
|
70
|
+
### Tests
|
|
71
|
+
|
|
72
|
+
- 7 new tests in `tests/type-faithful-writes.spec.ts` covering create/update wrapping, geographic + `CartesianPoint` input, driver-instance passthrough, list + mixed-list handling, WHERE `=`/`_IN`, and `createMany` columns.
|
|
73
|
+
|
|
3
74
|
## 2.1.0 (2026-08-13) — 🧭 Writes now store what the schema declares
|
|
4
75
|
|
|
5
76
|
> **Every write path now binds values as the Neo4j type the schema promises.** Until now the compilers bound raw JS values and left the rest to the driver — and the driver packs every JS `number` as a Bolt FLOAT64 and every string as a String. So `Int`/`BigInt` fields landed as **Doubles** ([#5](https://github.com/neomodular/grafeo-ogm/issues/5)), the six temporal types landed as **Strings**, and `BigInt`'s documented string input landed as a **String** — while reads converted every native value back to a JS primitive, so the corruption was invisible until something type-sensitive touched the property. `@default` rode along: parsed, documented in the README, and applied nowhere.
|
package/README.md
CHANGED
|
@@ -1302,6 +1302,54 @@ When `policies` is configured, every OGM-emitted query attaches transaction meta
|
|
|
1302
1302
|
|
|
1303
1303
|
Disable via `policyDefaults: { auditMetadata: false }`. The fingerprint is intentionally key-only — no ctx values are leaked.
|
|
1304
1304
|
|
|
1305
|
+
`policiesEvaluated` lists only the policies that **applied** to the query. A restrictive whose `appliesWhen(ctx)` returned `false` is omitted (before v2.2.0 it was listed even though it contributed nothing). `explainPolicies` calls add `explain: true` and are always recorded, even with `auditMetadata: false`.
|
|
1306
|
+
|
|
1307
|
+
#### Explaining policy decisions
|
|
1308
|
+
|
|
1309
|
+
When a policy-bound read hides a node, `find` can only show *that* the node disappeared. `explainPolicies` shows *which* named clause decided it. It evaluates every `read` policy for the type separately, for each candidate node, without short-circuiting. Both `when:` and `cypher:` clauses are covered, and nodes the policies reject are returned too.
|
|
1310
|
+
|
|
1311
|
+
```typescript
|
|
1312
|
+
const report = await ogm
|
|
1313
|
+
.withContext(ctx)
|
|
1314
|
+
.model('Chart')
|
|
1315
|
+
.explainPolicies({ where: { id_IN: candidateIds }, select: { id: true } });
|
|
1316
|
+
|
|
1317
|
+
// [{
|
|
1318
|
+
// node: { id: 'chart-8617' },
|
|
1319
|
+
// visible: false, // exactly what `find` would enforce
|
|
1320
|
+
// overriddenBy: null,
|
|
1321
|
+
// permissiveGranted: true,
|
|
1322
|
+
// failedRestrictives: ['chart.dose-translation-coverage'],
|
|
1323
|
+
// policies: [
|
|
1324
|
+
// { name: 'chart.read-grant', kind: 'permissive', source: 'Chart', applied: true, outcome: 'pass' },
|
|
1325
|
+
// { name: 'chart.weight-zone', kind: 'restrictive', source: 'Chart', applied: true, outcome: 'pass' },
|
|
1326
|
+
// { name: 'chart.text-field-scope', kind: 'restrictive', source: 'Chart', applied: false, outcome: 'not-applied' },
|
|
1327
|
+
// { name: 'chart.dose-translation-coverage', kind: 'restrictive', source: 'Chart', applied: true, outcome: 'fail' },
|
|
1328
|
+
// ],
|
|
1329
|
+
// }]
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
| `outcome` | Meaning |
|
|
1333
|
+
|---|---|
|
|
1334
|
+
| `pass` | Applied, and its predicate evaluated to `true`. |
|
|
1335
|
+
| `fail` | Applied, and it evaluated to `false`. This includes a restrictive hard deny (`when: () => false`). |
|
|
1336
|
+
| `null` | Applied, and it evaluated to NULL (for example, a missing property). Enforcement treats NULL as not-true. It is reported separately from `fail` because the fix is different. |
|
|
1337
|
+
| `abstain` | Applied, but it emitted no predicate. A permissive abstain grants nothing; a restrictive abstain restricts nothing. |
|
|
1338
|
+
| `not-applied` | `appliesWhen(ctx)` returned `false` (for an override: `when(ctx)` returned `false`). The policy was not evaluated. |
|
|
1339
|
+
| `skipped` | Not evaluated, because an earlier override fired. |
|
|
1340
|
+
|
|
1341
|
+
How it works:
|
|
1342
|
+
|
|
1343
|
+
- **Same predicates as `find`.** The explain query projects the exact compiled fragments `find` would enforce. `visible` is computed from the same composed predicate `find` puts in its `WHERE`. The verdict recomputed from the per-policy outcomes must agree with it; if it ever doesn't, the call throws instead of returning a wrong explanation.
|
|
1344
|
+
- **Candidate selection.** `where` and `labels` select candidates exactly as in `find`, including target-type policies on relationship filters. The root type's policy clause is reported, not used as a filter. Candidates that don't match `where` are simply absent from the result.
|
|
1345
|
+
- **Selection.** `select` / `selectionSet` accept everything `find` accepts. Relationships in the selection are still filtered by their own target-type policies. Only the root type's policies are explained.
|
|
1346
|
+
- **Read-only.** When grafeo opens the session itself, it opens it in `READ` access mode, so the server rejects any write. If you pass your own `transaction` or `session`, it is used as-is, and you control its access mode.
|
|
1347
|
+
- **Bounded.** `options.limit` defaults to 100, and values above 1,000 are rejected. Each candidate evaluates every policy, and the enforcement predicate is evaluated a second time for the cross-check, so explain costs more than a normal read. `sort` and `offset` are supported.
|
|
1348
|
+
- **Unnamed policies** are reported as `<source>.<kind>[<index>]` with `named: false`. Give every policy a `name` if you want stable, readable output.
|
|
1349
|
+
- **Default deny is reported, not thrown.** With `onDeny: 'throw'`, `explainPolicies` still returns rows with `visible: false` and `permissiveGranted: false`.
|
|
1350
|
+
|
|
1351
|
+
> **Security: `explainPolicies` bypasses policy filtering by design.** It returns nodes the bound context cannot see, together with their selected fields. Expose it only on admin-only diagnostic paths, and never let request input decide whether it runs. Every call logs a `warn` and is tagged `explain: true` in the transaction metadata. It is only available on policy-bound models (`ogm.withContext(ctx).model(...)`) and throws on a model with no policy binding or on a policy-bypassed OGM. In this release it covers `Model` and the `read` operation only: `InterfaceModel`, `count`, `aggregate`, and `delete` are not supported yet.
|
|
1352
|
+
|
|
1305
1353
|
#### Limitations
|
|
1306
1354
|
|
|
1307
1355
|
- **`@cypher` scalar fields inside a policy `where`-partial throw when the policy is injected into nested-selection enforcement.** Refactor the policy to use stored properties or a relationship traversal.
|
|
@@ -1879,6 +1927,7 @@ Open an issue on [GitHub](https://github.com/neomodular/grafeo-ogm/issues). For
|
|
|
1879
1927
|
| `count(options?)` | `number` | Count matching nodes |
|
|
1880
1928
|
| `aggregate(options)` | `AggregateResult` | Aggregate values (min, max, avg, count) |
|
|
1881
1929
|
| `setLabels(options)` | `void` | Add or remove labels on matching nodes |
|
|
1930
|
+
| `explainPolicies(options?)` | `PolicyExplanation<T>[]` | Per-clause `read` policy outcomes per candidate, including hidden nodes (policy-bound models only; see [Explaining policy decisions](#explaining-policy-decisions)) |
|
|
1882
1931
|
|
|
1883
1932
|
### InterfaceModel
|
|
1884
1933
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EACL,cAAc,EAGd,cAAc,EACf,MAAM,iBAAiB,CAAC;AAoBzB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AASjD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAmBzB,OAAO,CAAC,MAAM;IAlBhB;;;OAGG;IACH,OAAO,CAAC,UAAU,CAA6B;IAE/C,OAAO,CAAC,MAAM,CAAC,CAAY;IAE3B;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB,CAAC,CAAgB;gBAGlC,MAAM,EAAE,cAAc,EAC9B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,SAAS,CAAC;QAAC,aAAa,CAAC,EAAE,aAAa,CAAA;KAAE;IAMjE;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,0BAA0B;IA2ClC,OAAO,CAAC,sBAAsB;IAM9B;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAQxB,8EAA8E;IAC9E,WAAW,IAAI,IAAI;IAInB,OAAO,CAAC,oBAAoB;IAQ5B;;;OAGG;IACH,aAAa,CACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACjC,OAAO,EAAE,cAAc,EACvB,MAAM,CAAC,EAAE,MAAM,EAAE,GAChB,cAAc;IAuDjB;;OAEG;IACH,aAAa,CACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC3C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC/C,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,EACD,MAAM,CAAC,EAAE,MAAM,EAAE,EACjB,UAAU,GAAE,MAAM,GAAG,OAAgB;IACrC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,mBAAmB;IACnC;;;;OAIG;IACH,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAC/B,cAAc;
|
|
1
|
+
{"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"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"}
|
|
@@ -185,7 +185,7 @@ class MutationCompiler {
|
|
|
185
185
|
(0, validation_1.assertSafePropertyName)(key, 'update property');
|
|
186
186
|
const propDef = nodeDef.properties.get(key);
|
|
187
187
|
const paramName = `update_${key}`;
|
|
188
|
-
const valueExpr = (0, write_coercion_1.
|
|
188
|
+
const valueExpr = (0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, value, propDef);
|
|
189
189
|
setClauses.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${valueExpr}`);
|
|
190
190
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
|
|
191
191
|
}
|
|
@@ -267,7 +267,7 @@ class MutationCompiler {
|
|
|
267
267
|
(0, validation_1.assertSafePropertyName)(key, 'merge key');
|
|
268
268
|
const propDef = nodeDef.properties.get(key);
|
|
269
269
|
const paramName = `merge_${key}`;
|
|
270
|
-
const valueExpr = (0, write_coercion_1.
|
|
270
|
+
const valueExpr = (0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, value, propDef);
|
|
271
271
|
mergeProps.push(`${(0, validation_1.escapeIdentifier)(key)}: ${valueExpr}`);
|
|
272
272
|
mergeKeyNames.push(key);
|
|
273
273
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
|
|
@@ -308,7 +308,7 @@ class MutationCompiler {
|
|
|
308
308
|
(0, validation_1.assertSafePropertyName)(key, 'create property');
|
|
309
309
|
const propDef = nodeDef.properties.get(key);
|
|
310
310
|
const paramName = `onCreate_${key}`;
|
|
311
|
-
const valueExpr = (0, write_coercion_1.
|
|
311
|
+
const valueExpr = (0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, value, propDef);
|
|
312
312
|
createSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${valueExpr}`);
|
|
313
313
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
|
|
314
314
|
}
|
|
@@ -326,7 +326,7 @@ class MutationCompiler {
|
|
|
326
326
|
continue;
|
|
327
327
|
const paramName = `onCreate_${propDef.name}`;
|
|
328
328
|
const bound = (0, write_coercion_1.resolveDefaultWriteValue)(propDef);
|
|
329
|
-
const valueExpr = (0, write_coercion_1.
|
|
329
|
+
const valueExpr = (0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, bound, propDef);
|
|
330
330
|
createSets.push(`n.${(0, validation_1.escapeIdentifier)(propDef.name)} = ${valueExpr}`);
|
|
331
331
|
params[paramName] = bound;
|
|
332
332
|
}
|
|
@@ -342,7 +342,7 @@ class MutationCompiler {
|
|
|
342
342
|
(0, validation_1.assertSafePropertyName)(key, 'update property');
|
|
343
343
|
const propDef = nodeDef.properties.get(key);
|
|
344
344
|
const paramName = `onMatch_${key}`;
|
|
345
|
-
const valueExpr = (0, write_coercion_1.
|
|
345
|
+
const valueExpr = (0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, value, propDef);
|
|
346
346
|
updateSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${valueExpr}`);
|
|
347
347
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
|
|
348
348
|
}
|
|
@@ -450,20 +450,20 @@ class MutationCompiler {
|
|
|
450
450
|
throw new errors_1.OGMError('createMany with skipDuplicates requires at least one @id or @unique field in data');
|
|
451
451
|
// MERGE on unique key(s)
|
|
452
452
|
const mergeProps = mergeKeys
|
|
453
|
-
.map((k) => `${(0, validation_1.escapeIdentifier)(k)}: ${(0, write_coercion_1.
|
|
453
|
+
.map((k) => `${(0, validation_1.escapeIdentifier)(k)}: ${(0, write_coercion_1.wrapListItemExpr)(`item.${(0, validation_1.escapeIdentifier)(k)}`, data.map((d) => d[k]), nodeDef.properties.get(k))}`)
|
|
454
454
|
.join(', ');
|
|
455
455
|
lines.push(`MERGE (n:${labelStr} { ${mergeProps} })`);
|
|
456
456
|
// ON CREATE SET — remaining fields + generated IDs
|
|
457
457
|
const onCreateSets = [];
|
|
458
458
|
for (const key of scalarKeys)
|
|
459
459
|
if (!mergeKeys.includes(key))
|
|
460
|
-
onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${(0, write_coercion_1.
|
|
460
|
+
onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(key)} = ${(0, write_coercion_1.wrapListItemExpr)(`item.${(0, validation_1.escapeIdentifier)(key)}`, data.map((d) => d[key]), nodeDef.properties.get(key))}`);
|
|
461
461
|
for (const field of generatedFields)
|
|
462
462
|
onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(field)} = randomUUID()`);
|
|
463
463
|
for (const propDef of defaultedProps) {
|
|
464
464
|
const paramName = `default_${propDef.name}`;
|
|
465
465
|
const bound = (0, write_coercion_1.resolveDefaultWriteValue)(propDef);
|
|
466
|
-
onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(propDef.name)} = ${(0, write_coercion_1.
|
|
466
|
+
onCreateSets.push(`n.${(0, validation_1.escapeIdentifier)(propDef.name)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, bound, propDef)}`);
|
|
467
467
|
params[paramName] = bound;
|
|
468
468
|
}
|
|
469
469
|
if (onCreateSets.length > 0)
|
|
@@ -475,11 +475,11 @@ class MutationCompiler {
|
|
|
475
475
|
for (const field of generatedFields)
|
|
476
476
|
propParts.push(`${(0, validation_1.escapeIdentifier)(field)}: randomUUID()`);
|
|
477
477
|
for (const key of scalarKeys)
|
|
478
|
-
propParts.push(`${(0, validation_1.escapeIdentifier)(key)}: ${(0, write_coercion_1.
|
|
478
|
+
propParts.push(`${(0, validation_1.escapeIdentifier)(key)}: ${(0, write_coercion_1.wrapListItemExpr)(`item.${(0, validation_1.escapeIdentifier)(key)}`, data.map((d) => d[key]), nodeDef.properties.get(key))}`);
|
|
479
479
|
for (const propDef of defaultedProps) {
|
|
480
480
|
const paramName = `default_${propDef.name}`;
|
|
481
481
|
const bound = (0, write_coercion_1.resolveDefaultWriteValue)(propDef);
|
|
482
|
-
propParts.push(`${(0, validation_1.escapeIdentifier)(propDef.name)}: ${(0, write_coercion_1.
|
|
482
|
+
propParts.push(`${(0, validation_1.escapeIdentifier)(propDef.name)}: ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, bound, propDef)}`);
|
|
483
483
|
params[paramName] = bound;
|
|
484
484
|
}
|
|
485
485
|
lines.push(`CREATE (n:${labelStr} { ${propParts.join(', ')} })`);
|
|
@@ -522,7 +522,7 @@ class MutationCompiler {
|
|
|
522
522
|
(0, validation_1.assertSafePropertyName)(key, 'create property');
|
|
523
523
|
const propDef = nodeDef.properties.get(key);
|
|
524
524
|
const paramName = `${prefix}_${key}`;
|
|
525
|
-
const valueExpr = (0, write_coercion_1.
|
|
525
|
+
const valueExpr = (0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, value, propDef);
|
|
526
526
|
parts.push(`${(0, validation_1.escapeIdentifier)(key)}: ${valueExpr}`);
|
|
527
527
|
propParams[paramName] = (0, write_coercion_1.coerceWriteValue)(value, propDef);
|
|
528
528
|
}
|
|
@@ -539,7 +539,7 @@ class MutationCompiler {
|
|
|
539
539
|
continue;
|
|
540
540
|
const paramName = `${prefix}_${propDef.name}`;
|
|
541
541
|
const bound = (0, write_coercion_1.resolveDefaultWriteValue)(propDef);
|
|
542
|
-
const valueExpr = (0, write_coercion_1.
|
|
542
|
+
const valueExpr = (0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, bound, propDef);
|
|
543
543
|
parts.push(`${(0, validation_1.escapeIdentifier)(propDef.name)}: ${valueExpr}`);
|
|
544
544
|
propParams[paramName] = bound;
|
|
545
545
|
}
|
|
@@ -648,7 +648,7 @@ class MutationCompiler {
|
|
|
648
648
|
(0, validation_1.assertSafePropertyName)(prop, 'edge property');
|
|
649
649
|
const edgePropDef = this.getEdgePropDef(relDef, prop);
|
|
650
650
|
const paramName = `${prefix}_create${ci}_edge_${prop}`;
|
|
651
|
-
setItems.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.
|
|
651
|
+
setItems.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
|
|
652
652
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
|
|
653
653
|
}
|
|
654
654
|
if (setItems.length > 0)
|
|
@@ -701,7 +701,7 @@ class MutationCompiler {
|
|
|
701
701
|
(0, validation_1.assertSafePropertyName)(prop, 'edge property');
|
|
702
702
|
const edgePropDef = this.getEdgePropDef(relDef, prop);
|
|
703
703
|
const paramName = `${prefix}_conn${ci}_edge_${prop}`;
|
|
704
|
-
setItems.push(`r_conn_${startCounter + counter - 1}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.
|
|
704
|
+
setItems.push(`r_conn_${startCounter + counter - 1}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
|
|
705
705
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
|
|
706
706
|
}
|
|
707
707
|
if (setItems.length > 0)
|
|
@@ -773,7 +773,7 @@ class MutationCompiler {
|
|
|
773
773
|
(0, validation_1.assertSafePropertyName)(prop, 'edge property');
|
|
774
774
|
const edgePropDef = this.getEdgePropDef(relDef, prop);
|
|
775
775
|
const paramName = `connect_${fieldName}_${ci}_edge_${prop}`;
|
|
776
|
-
setItems.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.
|
|
776
|
+
setItems.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
|
|
777
777
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
|
|
778
778
|
}
|
|
779
779
|
if (setItems.length > 0)
|
|
@@ -851,7 +851,7 @@ class MutationCompiler {
|
|
|
851
851
|
// Replace MERGE line with one that has a variable
|
|
852
852
|
lines[lines.length - 1] = `MERGE ${mergeWithVar}`;
|
|
853
853
|
// r.prop is relationship property (escape), connItem.edge.prop is parameter map (no escape)
|
|
854
|
-
const setItems = edgeProps.map((p) => `r.${(0, validation_1.escapeIdentifier)(p)} = ${(0, write_coercion_1.
|
|
854
|
+
const setItems = edgeProps.map((p) => `r.${(0, validation_1.escapeIdentifier)(p)} = ${(0, write_coercion_1.wrapListItemExpr)(`connItem.edge.${p}`, spec.map((it) => it.edge?.[p]), this.getEdgePropDef(relDef, p))}`);
|
|
855
855
|
lines.push(`SET ${setItems.join(', ')}`);
|
|
856
856
|
}
|
|
857
857
|
}
|
|
@@ -884,7 +884,7 @@ class MutationCompiler {
|
|
|
884
884
|
continue;
|
|
885
885
|
const edgePropDef = this.getEdgePropDef(relDef, prop);
|
|
886
886
|
const paramName = `connect_${fieldName}_edge_${prop}`;
|
|
887
|
-
setItems.push(`r.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.
|
|
887
|
+
setItems.push(`r.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
|
|
888
888
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
|
|
889
889
|
}
|
|
890
890
|
if (setItems.length > 0)
|
|
@@ -1200,7 +1200,7 @@ class MutationCompiler {
|
|
|
1200
1200
|
(0, validation_1.assertSafePropertyName)(prop, 'edge property');
|
|
1201
1201
|
const edgePropDef = this.getEdgePropDef(relDef, prop);
|
|
1202
1202
|
const paramName = `${itemPrefix}_conn${ci}_edge_${prop}`;
|
|
1203
|
-
setItems.push(`r_conn_${key}_${ci}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.
|
|
1203
|
+
setItems.push(`r_conn_${key}_${ci}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
|
|
1204
1204
|
params[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
|
|
1205
1205
|
}
|
|
1206
1206
|
if (setItems.length > 0)
|
|
@@ -1239,7 +1239,7 @@ class MutationCompiler {
|
|
|
1239
1239
|
(0, validation_1.assertSafePropertyName)(prop, 'nested update property');
|
|
1240
1240
|
const nodePropDef = targetNodeDef.properties.get(prop);
|
|
1241
1241
|
const paramName = `${itemPrefix}_set_${prop}`;
|
|
1242
|
-
setClauses.push(`${updateVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.
|
|
1242
|
+
setClauses.push(`${updateVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, val, nodePropDef)}`);
|
|
1243
1243
|
setParams[paramName] = (0, write_coercion_1.coerceWriteValue)(val, nodePropDef);
|
|
1244
1244
|
}
|
|
1245
1245
|
// Pre-compute SET clauses for edge (relationship) properties
|
|
@@ -1252,7 +1252,7 @@ class MutationCompiler {
|
|
|
1252
1252
|
(0, validation_1.assertSafePropertyName)(prop, 'edge update property');
|
|
1253
1253
|
const edgePropDef = this.getEdgePropDef(relDef, prop);
|
|
1254
1254
|
const paramName = `${itemPrefix}_edge_${prop}`;
|
|
1255
|
-
edgeClauses.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.
|
|
1255
|
+
edgeClauses.push(`${relVar}.${(0, validation_1.escapeIdentifier)(prop)} = ${(0, write_coercion_1.wrapWriteExpr)(`$${paramName}`, val, edgePropDef)}`);
|
|
1256
1256
|
edgeParams[paramName] = (0, write_coercion_1.coerceWriteValue)(val, edgePropDef);
|
|
1257
1257
|
}
|
|
1258
1258
|
// Pre-compute nested relationship operations
|
|
@@ -1339,7 +1339,7 @@ class MutationCompiler {
|
|
|
1339
1339
|
(0, validation_1.assertSafePropertyName)(baseProp, 'where property');
|
|
1340
1340
|
const paramName = `${prefix}_NOT_${notProp}`;
|
|
1341
1341
|
const escapedProp = (0, validation_1.escapeIdentifier)(baseProp);
|
|
1342
|
-
const paramRef = (0, write_coercion_1.
|
|
1342
|
+
const paramRef = (0, write_coercion_1.wrapWhereParam)(`$${paramName}`, notVal, targetNodeDef?.properties.get(baseProp), notProp.slice(baseProp.length));
|
|
1343
1343
|
let expr;
|
|
1344
1344
|
if (template === '%v = $%p')
|
|
1345
1345
|
// Simple equality → negate to <>
|
|
@@ -1365,7 +1365,7 @@ class MutationCompiler {
|
|
|
1365
1365
|
const { baseProp, template } = this.parseOperatorSuffix(prop);
|
|
1366
1366
|
(0, validation_1.assertSafePropertyName)(baseProp, 'where property');
|
|
1367
1367
|
const paramName = `${prefix}_${prop}`;
|
|
1368
|
-
const paramRef = (0, write_coercion_1.
|
|
1368
|
+
const paramRef = (0, write_coercion_1.wrapWhereParam)(`$${paramName}`, val, targetNodeDef?.properties.get(baseProp), prop.slice(baseProp.length));
|
|
1369
1369
|
conditions.push(template
|
|
1370
1370
|
.replace('%v', `${targetVar}.${(0, validation_1.escapeIdentifier)(baseProp)}`)
|
|
1371
1371
|
.replace('$%p', paramRef));
|
|
@@ -1546,7 +1546,7 @@ class MutationCompiler {
|
|
|
1546
1546
|
// Note: itemVar map access is NOT escaped (parameter map keys, not Cypher identifiers)
|
|
1547
1547
|
// Temporal wrap keys off the FIRST item's value type — the UNWIND
|
|
1548
1548
|
// fast path already requires shape-homogeneous items.
|
|
1549
|
-
const valueRef = (0, write_coercion_1.
|
|
1549
|
+
const valueRef = (0, write_coercion_1.wrapWhereParam)(`${itemVar}.where.node.${key}`, nodeWhere[key], targetNodeDef?.properties.get(baseProp), key.slice(baseProp.length));
|
|
1550
1550
|
conditions.push(template
|
|
1551
1551
|
.replace('%v', `${targetVar}.${(0, validation_1.escapeIdentifier)(baseProp)}`)
|
|
1552
1552
|
.replace('$%p', valueRef));
|