grafeo-ogm 2.2.0 β 2.3.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 +83 -0
- package/README.md +49 -1
- package/dist/cjs/compilers/mutation.compiler.d.ts +94 -31
- package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.js +381 -343
- package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
- package/dist/cjs/compilers/selection.compiler.d.ts +1 -1
- package/dist/cjs/compilers/selection.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/selection.compiler.js +33 -39
- package/dist/cjs/compilers/selection.compiler.js.map +1 -1
- package/dist/cjs/compilers/where.compiler.d.ts +124 -1
- package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/where.compiler.js +371 -102
- package/dist/cjs/compilers/where.compiler.js.map +1 -1
- package/dist/cjs/interface-model.d.ts +20 -10
- package/dist/cjs/interface-model.d.ts.map +1 -1
- package/dist/cjs/interface-model.js +47 -104
- package/dist/cjs/interface-model.js.map +1 -1
- package/dist/cjs/model.d.ts +4 -29
- package/dist/cjs/model.d.ts.map +1 -1
- package/dist/cjs/model.js +46 -129
- package/dist/cjs/model.js.map +1 -1
- package/dist/cjs/policy/nested-writes.d.ts +35 -0
- package/dist/cjs/policy/nested-writes.d.ts.map +1 -0
- package/dist/cjs/policy/nested-writes.js +199 -0
- package/dist/cjs/policy/nested-writes.js.map +1 -0
- package/dist/cjs/policy/types.d.ts +18 -0
- package/dist/cjs/policy/types.d.ts.map +1 -1
- package/dist/cjs/policy/types.js +24 -0
- package/dist/cjs/policy/types.js.map +1 -1
- package/dist/esm/compilers/mutation.compiler.d.ts +94 -31
- package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/mutation.compiler.js +381 -344
- package/dist/esm/compilers/mutation.compiler.js.map +1 -1
- package/dist/esm/compilers/selection.compiler.d.ts +1 -1
- package/dist/esm/compilers/selection.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/selection.compiler.js +33 -39
- package/dist/esm/compilers/selection.compiler.js.map +1 -1
- package/dist/esm/compilers/where.compiler.d.ts +124 -1
- package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/where.compiler.js +371 -104
- package/dist/esm/compilers/where.compiler.js.map +1 -1
- package/dist/esm/interface-model.d.ts +20 -10
- package/dist/esm/interface-model.d.ts.map +1 -1
- package/dist/esm/interface-model.js +47 -104
- package/dist/esm/interface-model.js.map +1 -1
- package/dist/esm/model.d.ts +4 -29
- package/dist/esm/model.d.ts.map +1 -1
- package/dist/esm/model.js +47 -130
- package/dist/esm/model.js.map +1 -1
- package/dist/esm/policy/nested-writes.d.ts +35 -0
- package/dist/esm/policy/nested-writes.d.ts.map +1 -0
- package/dist/esm/policy/nested-writes.js +193 -0
- package/dist/esm/policy/nested-writes.js.map +1 -0
- package/dist/esm/policy/types.d.ts +18 -0
- package/dist/esm/policy/types.d.ts.map +1 -1
- package/dist/esm/policy/types.js +23 -0
- package/dist/esm/policy/types.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.0 (2026-09-23) β π‘οΈ Security release: NLS enforcement through relationships, nested writes and cascade delete
|
|
4
|
+
|
|
5
|
+
> **Security release. Upgrade if you use `policies`.** Node-level-security (NLS) enforcement of the **root** type was sound, but enforcement of **other types reached through relationships** had several gaps. Together they let a bound `withContext(ctx)` read, link, update, create and delete nodes that the target type's policies forbid. One of them (cascade delete) deleted unrelated data with no policy involved at all. Every gap was reproduced before the fix and is pinned by a red-first regression test, both in the mock suite and in a new opt-in live-Neo4j suite.
|
|
6
|
+
>
|
|
7
|
+
> **Reads on OGMs without `policies` emit byte-identical Cypher.** Three changes apply to everyone: the cascade-delete fix, strict `null` filters, and nested-write `where` now being compiled by `WhereCompiler` (see *Behavior changes*).
|
|
8
|
+
|
|
9
|
+
### π‘οΈ Types without policies no longer bypass their neighbours' policies (critical)
|
|
10
|
+
|
|
11
|
+
When the root type of a query had no policies of its own, the policy context was dropped entirely. Every related type's policies were then skipped in nested selection, in relationship filters (`_SOME`/`_NONE`/`_ALL`/`_SINGLE`/`β¦Connection`), on `connect`/`disconnect`, and in `explainPolicies`. A policy-free type (a tag, a join node) was a gateway around its neighbours. The same happened one level down: a policy-free type in the **middle** of a traversal (`a_SOME: { b_SOME: { c_SOME } }`) switched enforcement off for everything beyond it.
|
|
12
|
+
|
|
13
|
+
A policy-bound model now always carries its policy context. A type without policies contributes no clause of its own, and every type it reaches still enforces its own. `count`/`aggregate` keep their fallback from `aggregate` to `read` policies.
|
|
14
|
+
|
|
15
|
+
### π‘οΈ Cascade delete honours its `where` and the target's `delete` policy (critical)
|
|
16
|
+
|
|
17
|
+
`Model.delete({ delete: { rel: [{ where }] } })` ignored the per-relationship `where`: the documented README example deleted **every** related node, including nodes shared with other records, and never checked the target type's policies. Each cascade item now deletes only the related nodes matching its `where` (`{}` still means all), and each cascaded node must pass its own type's `delete` policy. A `delete` nested inside a cascade item (multi-level cascade, previously ignored) and unknown item keys now throw `OGMError` before anything runs.
|
|
18
|
+
|
|
19
|
+
### π‘οΈ Nested writes enforce the target type's policies
|
|
20
|
+
|
|
21
|
+
A nested write now enforces exactly what the equivalent direct write on the target type would. Before, most nested writes enforced no target-type policy at all:
|
|
22
|
+
|
|
23
|
+
| Nested operation | Now enforces on the target type |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `connect` / `disconnect`, including `connect` inside `create` (previously unchecked) | `read` |
|
|
26
|
+
| nested `update` | `update` row filter, plus its write restrictives (`WITH CHECK`) on the nested input |
|
|
27
|
+
| nested `create`, in `create` or `update` | `create`: default deny, plus its write restrictives on the nested input |
|
|
28
|
+
| `delete` inside `update` | `delete` row filter |
|
|
29
|
+
|
|
30
|
+
Write restrictives and create default-deny are checked, at any depth, before the query runs. A rejected nested write throws `PolicyDeniedError` and nothing is written. The mutation compiler's separate `where` builder, which had drifted from `WhereCompiler` (it threaded no policy context into traversals and misparsed `null`), is removed. Nested-write `where` is now compiled by `WhereCompiler`, so traversals inside it enforce the traversed types' `read` policies too.
|
|
31
|
+
|
|
32
|
+
### π‘οΈ Interface and union relationship targets apply each member's policy (critical)
|
|
33
|
+
|
|
34
|
+
When a relationship pointed at an **interface** or **union**, enforcement looked up the abstract type's name. That name carries only interface-level policies (none for a union), so the concrete members' own policies were never applied through the relationship: not in nested selection, not in relationship filters, not in nested writes. Each related node is now checked against its concrete type's policy: `CASE WHEN n:Chart THEN <Chart policy> WHEN n:Drug THEN <Drug policy> β¦ ELSE false END`. One shared helper builds this clause for every consumer, including `InterfaceModel` (below).
|
|
35
|
+
|
|
36
|
+
### π‘οΈ Relationship filters no longer leak hidden nodes
|
|
37
|
+
|
|
38
|
+
The target type's policy was compiled **inside** the caller's filter, which is only correct for `_SOME`. Wherever the quantifier negates the filter, hidden nodes leaked into the result:
|
|
39
|
+
|
|
40
|
+
- `rel_ALL: { β¦ }` was falsified by hidden related nodes.
|
|
41
|
+
- `rel_ALL: {}` held only when no related node existed at all, which revealed whether hidden nodes exist.
|
|
42
|
+
- A connection `node_NOT` filter was satisfied by hidden nodes.
|
|
43
|
+
- An edge-only connection filter (`relConnection: { edge: { β¦ } }`) applied no target policy at all.
|
|
44
|
+
|
|
45
|
+
The filter and the policy are now composed separately, with the policy outside every negation. Quantifiers range over the related nodes the caller may see: a hidden node neither satisfies nor falsifies them. `_SOME` emits the same Cypher as before.
|
|
46
|
+
|
|
47
|
+
### π‘οΈ `null` filters no longer fail open (high)
|
|
48
|
+
|
|
49
|
+
`{ field_NOT: null }` compiled to `` n.`field_NOT` IS NULL ``, which is always true, so a restrictive policy such as `{ approvedAt_NOT: null }` matched every row. `null` is now interpreted after the operator suffix is split off:
|
|
50
|
+
|
|
51
|
+
- `field_NOT: null` β `IS NOT NULL`
|
|
52
|
+
- `rel_NOT: null` β `EXISTS`
|
|
53
|
+
- any other operator with `null`, or an unknown field with `null` (even without `strictWhere`), throws `OGMError`
|
|
54
|
+
|
|
55
|
+
This applies to every `where`, including policy partials and nested-write `where`.
|
|
56
|
+
|
|
57
|
+
### π‘οΈ `InterfaceModel` branches match each implementer's own model (medium)
|
|
58
|
+
|
|
59
|
+
An implementer whose `read` permissives were all gated off by `appliesWhen` got `THEN true` in `InterfaceModel`'s per-label `CASE`, while `Model(<Impl>).find` denies it. Each branch is now exactly the clause that the implementer's own model enforces, which also removes the interface predicates that previously appeared twice per branch. `aggregate`/`count` branches now use each implementer's own `aggregate` policies (falling back to `read`), as `Model.aggregate` does. The `onDeny: 'throw'` pre-check no longer throws when an implementer has no policies (it is visible under its own model).
|
|
60
|
+
|
|
61
|
+
### β οΈ Behavior changes
|
|
62
|
+
|
|
63
|
+
- **Cascade delete honours `where`.** If you relied on the old delete-everything behavior while passing a `where`, pass `{}` instead. Multi-level cascade and unknown cascade keys throw.
|
|
64
|
+
- **Nested writes into protected types can now be filtered or denied.** Add the matching policy for that operation on the target type, or perform the write directly on its model.
|
|
65
|
+
- **Relationship filters over protected types** now range over visible related nodes only (`_ALL` in particular).
|
|
66
|
+
- **Strict `null`:** `field_NOT: null` flips from always-true to `IS NOT NULL`; unsupported or unknown `null` filters throw.
|
|
67
|
+
- **Emitted Cypher changes** for: reads that reach protected types through policy-free types; every nested write with a `where` or a protected target (parameters now come from the shared `paramN` counter); `InterfaceModel` reads with policies; and cascade deletes (one `CALL { β¦ }` per item).
|
|
68
|
+
- Two edge cases now throw `OGMError` instead of emitting Cypher that fails at runtime: `@cypher`-projecting policies on more than one member of an interface/union target, and `@cypher` fields in both an abstract target's filter and a member policy.
|
|
69
|
+
|
|
70
|
+
### π§ Internal
|
|
71
|
+
|
|
72
|
+
- New `NO_ROOT_POLICY` resolution ("bound, but no root clause"). New `src/policy/nested-writes.ts`, where the root and nested create/update checks share one implementation.
|
|
73
|
+
- `WhereCompiler.compileTargetPolicyClause()` (concrete or abstract, optional operation fallback) and `compileTargetBody()` are the single construction behind every target-policy consumer. `compile()` accepts a caller-owned `@cypher` scope.
|
|
74
|
+
- `MutationCompiler.compileCreate` / `compileDelete` gain optional trailing `policyContext` / `paramCounter` parameters. The bulk-connect `UNWIND` fast path is kept for scalar, non-null, non-logical items; everything else compiles through `WhereCompiler`.
|
|
75
|
+
|
|
76
|
+
### Tests
|
|
77
|
+
|
|
78
|
+
- Mock red/green tests for every gap (`tests/policy/enforcement-gaps.spec.ts`).
|
|
79
|
+
- A 25-case invariant suite: each `InterfaceModel` branch equals its implementer's own `find`/`aggregate`/`count` clause, in text and parameter values.
|
|
80
|
+
- Against v2.2.0, 56 of these 75 new tests fail. The other 19 are byte-identity guards, positive controls, and invariant cases where the old construction already agreed.
|
|
81
|
+
- An opt-in live-Neo4j suite (`tests/integration/`, skipped unless `NEO4J_URI` is set) asserting database outcomes per gap.
|
|
82
|
+
- The 17-case policy golden suite is unchanged except for the deliberately re-pinned `InterfaceModel` case (deduplicated predicates).
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
3
86
|
## 2.2.0 (2026-09-23) β π Explain policy decisions
|
|
4
87
|
|
|
5
88
|
> **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)).
|
package/README.md
CHANGED
|
@@ -580,6 +580,8 @@ const book = await Book.findUniqueOrThrow({
|
|
|
580
580
|
| `_ALL` | Every related node matches | `{ categories_ALL: { name: 'Tech' } }` |
|
|
581
581
|
| `_SINGLE` | Exactly one related node matches | `{ categories_SINGLE: { name: 'Tech' } }` |
|
|
582
582
|
|
|
583
|
+
With [Node-Level Security](#node-level-security), quantifiers range over the related nodes the caller may **see**: a related node hidden by its type's `read` policy neither satisfies nor falsifies `_SOME`, `_NONE`, `_ALL` or `_SINGLE`.
|
|
584
|
+
|
|
583
585
|
**Logical operators:**
|
|
584
586
|
|
|
585
587
|
```typescript
|
|
@@ -599,11 +601,24 @@ await Book.find({
|
|
|
599
601
|
```typescript
|
|
600
602
|
// Scalar null check
|
|
601
603
|
await Book.find({ where: { isbn: null } }); // WHERE n.isbn IS NULL
|
|
604
|
+
await Book.find({ where: { isbn_NOT: null } }); // WHERE n.isbn IS NOT NULL
|
|
602
605
|
|
|
603
606
|
// Relationship existence
|
|
604
607
|
await Book.find({ where: { author: null } }); // NOT EXISTS pattern
|
|
608
|
+
await Book.find({ where: { author_NOT: null } }); // EXISTS pattern
|
|
605
609
|
```
|
|
606
610
|
|
|
611
|
+
| Input | Compiles to |
|
|
612
|
+
|---|---|
|
|
613
|
+
| `field: null` | `field IS NULL` |
|
|
614
|
+
| `field_NOT: null` | `field IS NOT NULL` |
|
|
615
|
+
| `relationship: null` | `NOT EXISTS { MATCH β¦ }` |
|
|
616
|
+
| `relationship_NOT: null` | `EXISTS { MATCH β¦ }` |
|
|
617
|
+
| any other operator with `null` (`_IN`, `_GT`, `_CONTAINS`, `_SOME`, β¦) | throws `OGMError` |
|
|
618
|
+
| an unknown field with `null` | throws `OGMError`, even without `strictWhere` |
|
|
619
|
+
|
|
620
|
+
The same rules apply everywhere a `where` is accepted: policy `when` partials, nested `select` filters, and nested-write `where`. Before v2.3.0, `{ field_NOT: null }` compiled to the always-true `` n.`field_NOT` IS NULL ``, so a filter such as a policy restrictive `{ approvedAt_NOT: null }` silently matched every row.
|
|
621
|
+
|
|
607
622
|
**Connection filters (with edge properties):**
|
|
608
623
|
|
|
609
624
|
```typescript
|
|
@@ -840,6 +855,15 @@ await Book.delete({
|
|
|
840
855
|
});
|
|
841
856
|
```
|
|
842
857
|
|
|
858
|
+
Cascade semantics:
|
|
859
|
+
|
|
860
|
+
- Each item deletes only the related nodes matching its `where`. An empty item (`{}`) deletes every related node of that relationship.
|
|
861
|
+
- An item may be a single object or an array of items.
|
|
862
|
+
- With [Node-Level Security](#node-level-security), each cascaded node must also pass its own type's `delete` policy. Nodes the policy rejects are kept.
|
|
863
|
+
- A `delete` nested inside a cascade item (multi-level cascade) throws `OGMError`, as do unknown keys in a cascade item.
|
|
864
|
+
|
|
865
|
+
> **Behavior change in v2.3.0:** before v2.3.0 the per-item `where` was ignored and the cascade deleted **every** related node, including nodes shared with other records. If you relied on that, pass `{}` explicitly.
|
|
866
|
+
|
|
843
867
|
### Delete Many
|
|
844
868
|
|
|
845
869
|
```typescript
|
|
@@ -1269,6 +1293,28 @@ policies: {
|
|
|
1269
1293
|
}
|
|
1270
1294
|
```
|
|
1271
1295
|
|
|
1296
|
+
#### Relationships and nested writes
|
|
1297
|
+
|
|
1298
|
+
Policies protect a **type**, wherever it is reached from. When a bound model reaches another type through a relationship, that type's own policies apply, whether or not the root type has any.
|
|
1299
|
+
|
|
1300
|
+
Reads:
|
|
1301
|
+
- **Nested selection**: related nodes are filtered by their type's `read` policy.
|
|
1302
|
+
- **Relationship filters** (`_SOME`, `_NONE`, `_ALL`, `_SINGLE`, `β¦Connection`): these range over the related nodes the caller may see. At any depth, a hidden node neither satisfies nor falsifies the quantifier.
|
|
1303
|
+
- **Interface and union targets**: each related node is checked against the policy of its concrete type (`CASE WHEN n:Chart THEN <Chart policy> WHEN n:Drug THEN <Drug policy> β¦ ELSE false END`).
|
|
1304
|
+
|
|
1305
|
+
Writes follow one principle: **a nested write enforces exactly what the equivalent direct write on the target type would.**
|
|
1306
|
+
|
|
1307
|
+
| Nested operation | Target-type policy applied |
|
|
1308
|
+
|---|---|
|
|
1309
|
+
| `connect` / `disconnect` (in `create` or `update`) | `read`: only visible nodes can be linked or unlinked |
|
|
1310
|
+
| nested `update` | `update` row filter, plus its write restrictives (`WITH CHECK`) on the nested input |
|
|
1311
|
+
| nested `create` (in `create` or `update`) | `create`: at least one permissive must apply, plus its write restrictives on the nested input |
|
|
1312
|
+
| `delete` inside `update`, cascade `delete` | `delete` row filter |
|
|
1313
|
+
|
|
1314
|
+
Write restrictives and create default-deny are checked before the query runs, so a rejected nested write throws `PolicyDeniedError` and nothing is written.
|
|
1315
|
+
|
|
1316
|
+
> **A type without policies is open, but not a gateway.** Before v2.3.0, reading or writing through a type with no policies of its own skipped the policies of the types beyond it, and nested writes enforced no target-type policy at all. If a nested write into a protected type now gets filtered or denied, add the matching policy for that operation on the target type, or perform the write directly on its model.
|
|
1317
|
+
|
|
1272
1318
|
#### Enforcement boundary
|
|
1273
1319
|
|
|
1274
1320
|
> **The OGM enforces policies at the compiler.** Policies do NOT apply to `ogm.$queryRaw`, `ogm.$executeRaw`, or to `@cypher` directive bodies that traverse from a stored field. If you need raw-Cypher enforcement, write the predicate into your raw Cypher manually or layer a Neo4j role/RBAC at the database.
|
|
@@ -1355,7 +1401,9 @@ How it works:
|
|
|
1355
1401
|
- **`@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.
|
|
1356
1402
|
- **`upsert`** evaluates create- and update-side policies at the application layer (MERGE has no WHERE). Documented limit; full MERGE-aware enforcement is not yet implemented.
|
|
1357
1403
|
- **Restrictives are split into read-side and write-side flavors.** A restrictive's `operations` array determines which `when` signature applies. Read-side ops (`read|delete|aggregate|count`) β `when(ctx)` returns a where-partial or boolean. Write-side ops (`create|update`) β `when(ctx, input)` returns a boolean only. Mixed arrays (e.g. `['read', 'create']`) are rejected at construction time β split into two restrictives. Each flavor is invoked exactly once per query (read-side at compile, write-side at the application layer). Use `isReadRestrictive` / `isWriteRestrictive` if you need to inspect a policy at runtime.
|
|
1358
|
-
- **InterfaceModel CASE
|
|
1404
|
+
- **InterfaceModel branches.** Each implementer's `CASE` branch is exactly the policy clause that implementer's own model enforces, including the interface-level policies it inherits. An implementer with no policies of its own therefore gets only the interface-level ones. The OGM emits a `logger.warn` at construction time when an interface has policies and one of its implementers does not. Silence it by registering an explicit policy on each implementer.
|
|
1405
|
+
- **`edge` filters in nested-write `where`** (connect, disconnect, nested update and delete) are not supported and throw.
|
|
1406
|
+
- **`@cypher` fields in the policies of more than one member of an interface or union** cannot be combined in one query and throw. Refactor all but one to stored properties.
|
|
1359
1407
|
- **No AsyncLocalStorage integration.** Context is supplied via explicit `withContext()` only β create one wrapper per request, discard after.
|
|
1360
1408
|
|
|
1361
1409
|
### Raw Cypher
|
|
@@ -55,6 +55,34 @@ export declare class MutationCompiler {
|
|
|
55
55
|
*/
|
|
56
56
|
private buildTargetPolicyPredicate;
|
|
57
57
|
private getPolicyWhereCompiler;
|
|
58
|
+
/**
|
|
59
|
+
* Compile a nested-write `where` (connect, disconnect, nested update,
|
|
60
|
+
* nested/cascade delete) against `targetVar` through `WhereCompiler` β
|
|
61
|
+
* the single where implementation (v2.3.0: the mutation compiler's
|
|
62
|
+
* parallel builder was removed after repeatedly drifting from it).
|
|
63
|
+
* Operators, null semantics, abstract relationship targets and β
|
|
64
|
+
* crucially β traversal-policy enforcement are identical to a direct
|
|
65
|
+
* `where`: a relationship filter inside it AND-stitches the traversed
|
|
66
|
+
* type's `read` policy. The target's OWN operation policy is added
|
|
67
|
+
* separately by `buildTargetPolicyPredicate`, so the root here is bound
|
|
68
|
+
* as `NO_ROOT_POLICY`.
|
|
69
|
+
*
|
|
70
|
+
* Returns `''` for an absent or empty where.
|
|
71
|
+
*/
|
|
72
|
+
private compileTargetWhere;
|
|
73
|
+
/**
|
|
74
|
+
* The bulk-connect UNWIND fast path compiles each where key against the
|
|
75
|
+
* ROW's own value (`connItem.where.node.<key>`), which `WhereCompiler`
|
|
76
|
+
* cannot express. It is therefore restricted to the one shape where
|
|
77
|
+
* that is exact: every item's where is `{ node: { β¦ } }` or bare
|
|
78
|
+
* properties, holding only declared, stored scalar properties of the
|
|
79
|
+
* target (registered operators allowed) with NON-NULL values β no
|
|
80
|
+
* `AND`/`OR`/`NOT`/`node_NOT`, no relationship traversal, no `@cypher`
|
|
81
|
+
* field. Every predicate it emits is then a scalar comparison against a
|
|
82
|
+
* concrete row value, so it can only narrow matches, never fail open.
|
|
83
|
+
* Anything else takes the per-item path through `WhereCompiler`.
|
|
84
|
+
*/
|
|
85
|
+
private isConnectFastPathEligible;
|
|
58
86
|
/**
|
|
59
87
|
* Surface a logical operator that contributed zero effective conditions.
|
|
60
88
|
* Mirrors WhereCompiler.warnEmptyLogical β fires only on the anomalous
|
|
@@ -68,7 +96,20 @@ export declare class MutationCompiler {
|
|
|
68
96
|
* Generate CREATE Cypher for one or more nodes.
|
|
69
97
|
* Handles scalar properties, nested relationship creates, and connects.
|
|
70
98
|
*/
|
|
71
|
-
compileCreate(inputs: Record<string, unknown>[], nodeDef: NodeDefinition, labels?: string[]
|
|
99
|
+
compileCreate(inputs: Record<string, unknown>[], nodeDef: NodeDefinition, labels?: string[],
|
|
100
|
+
/**
|
|
101
|
+
* The caller's `create` bundle (v2.3.0). Its `resolveForType` gates
|
|
102
|
+
* every connect target in the nested input by the TARGET type's `read`
|
|
103
|
+
* policy β pre-2.3.0 the create path threaded no policy at all.
|
|
104
|
+
*/
|
|
105
|
+
policyContext?: PolicyContextBundle,
|
|
106
|
+
/**
|
|
107
|
+
* Shared `param<N>` counter; continue it into the RETURN selection so
|
|
108
|
+
* nested-filter params never collide with the selection's.
|
|
109
|
+
*/
|
|
110
|
+
paramCounter?: {
|
|
111
|
+
count: number;
|
|
112
|
+
}): MutationResult;
|
|
72
113
|
/**
|
|
73
114
|
* Generate UPDATE Cypher (SET properties + connect/disconnect).
|
|
74
115
|
*/
|
|
@@ -95,12 +136,45 @@ export declare class MutationCompiler {
|
|
|
95
136
|
}): MutationResult;
|
|
96
137
|
/**
|
|
97
138
|
* Generate DELETE Cypher with optional cascade.
|
|
139
|
+
*
|
|
140
|
+
* v2.3.0 β each cascaded relationship honours its per-item `where` and
|
|
141
|
+
* is gated by the TARGET type's `delete` policy (`buildNestedDelete`).
|
|
142
|
+
* Pre-2.3.0 the cascade iterated only the relationship KEYS: the
|
|
143
|
+
* documented `{ where }` was ignored, so every related node β shared
|
|
144
|
+
* ones included β was deleted, with no policy check.
|
|
98
145
|
*/
|
|
99
146
|
compileDelete(nodeDef: NodeDefinition, whereResult: {
|
|
100
147
|
cypher: string;
|
|
101
148
|
params: Record<string, unknown>;
|
|
102
149
|
preludes?: string[];
|
|
103
|
-
}, deleteInput?: Record<string, unknown
|
|
150
|
+
}, deleteInput?: Record<string, unknown>,
|
|
151
|
+
/** The caller's `delete` bundle; gates each cascade target. */
|
|
152
|
+
policyContext?: PolicyContextBundle,
|
|
153
|
+
/** Shared `param<N>` counter, continued from the root WHERE. */
|
|
154
|
+
paramCounter?: {
|
|
155
|
+
count: number;
|
|
156
|
+
}): MutationResult;
|
|
157
|
+
/**
|
|
158
|
+
* Nested / cascade delete of ONE relationship's targets (v2.3.0), shared
|
|
159
|
+
* by `compileDelete` (`Model.delete`'s `delete` input) and delete-inside-
|
|
160
|
+
* `update`. One subquery per spec item:
|
|
161
|
+
*
|
|
162
|
+
* <withClause>
|
|
163
|
+
* CALL {
|
|
164
|
+
* <withClause>
|
|
165
|
+
* MATCH (src)-[:REL]->(t)
|
|
166
|
+
* WHERE <item.where via WhereCompiler> AND <target `delete` policy>
|
|
167
|
+
* DETACH DELETE t
|
|
168
|
+
* RETURN count(*) AS _del_<tag>_<i>
|
|
169
|
+
* }
|
|
170
|
+
*
|
|
171
|
+
* `spec` is one item or an array (singular vs list relationships);
|
|
172
|
+
* `null` means "no cascade" (GraphQL InputMaybe). `{}` / no `where`
|
|
173
|
+
* deletes every related node the target's `delete` policy permits.
|
|
174
|
+
* A multi-level cascade (`delete` inside an item) and unknown item keys
|
|
175
|
+
* are rejected instead of silently ignored.
|
|
176
|
+
*/
|
|
177
|
+
private buildNestedDelete;
|
|
104
178
|
/**
|
|
105
179
|
* Generate MERGE (upsert) Cypher with ON CREATE SET / ON MATCH SET.
|
|
106
180
|
* Scalar properties only β nested relationship ops are not supported.
|
|
@@ -157,37 +231,26 @@ export declare class MutationCompiler {
|
|
|
157
231
|
* Returns the base property name and the Cypher expression template.
|
|
158
232
|
*/
|
|
159
233
|
private parseOperatorSuffix;
|
|
160
|
-
/**
|
|
161
|
-
* Build WHERE conditions for connect/disconnect operations.
|
|
162
|
-
* Handles operator suffixes (_IN, _NOT, _NOT_IN, _CONTAINS, etc.),
|
|
163
|
-
* NOT blocks, and relationship filters (_SOME, _NONE, _ALL, _SINGLE).
|
|
164
|
-
*
|
|
165
|
-
* @param targetNodeDef - Optional node definition for the target node.
|
|
166
|
-
* Required to resolve relationship filters like `configurations_SOME`.
|
|
167
|
-
* When omitted, relationship filters are silently skipped.
|
|
168
|
-
*/
|
|
169
|
-
private buildNodeWhereConditions;
|
|
170
|
-
/**
|
|
171
|
-
* Build WHERE conditions from a connection-level WHERE spec.
|
|
172
|
-
* Handles `node`, `NOT`, `AND`, `OR` at the connection level.
|
|
173
|
-
*
|
|
174
|
-
* Connection WHERE format: `{ node: {...}, NOT: { node: {...} }, AND: [...], OR: [...] }`
|
|
175
|
-
*/
|
|
176
|
-
private buildConnectionWhereConditions;
|
|
177
|
-
/** Counter for unique relationship filter variables within a single WHERE clause. */
|
|
178
|
-
private relFilterCounter;
|
|
179
|
-
/**
|
|
180
|
-
* Try to compile a relationship filter condition (_SOME, _NONE, _ALL, _SINGLE).
|
|
181
|
-
* Returns the Cypher expression string or null if the key is not a relationship filter.
|
|
182
|
-
*/
|
|
183
|
-
private tryBuildRelationshipFilter;
|
|
184
234
|
private buildGeneratedIdClause;
|
|
185
|
-
/**
|
|
186
|
-
* Check if any item in an array connect spec has relationship filters in its WHERE clause.
|
|
187
|
-
* Relationship filters require EXISTS subqueries which can't use UNWIND param references.
|
|
188
|
-
*/
|
|
189
|
-
private arrayConnectHasRelationshipFilters;
|
|
190
235
|
private extractConnectWhereConditions;
|
|
191
236
|
private extractEdgeProperties;
|
|
192
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Map a nested-mutation connection where onto an equivalent NODE where on
|
|
240
|
+
* the target, for `WhereCompiler` (v2.3.0). Mutation `where` never supports
|
|
241
|
+
* edge filters, so the two shapes are interchangeable:
|
|
242
|
+
*
|
|
243
|
+
* { node: A } β A
|
|
244
|
+
* { node_NOT: A } β { NOT: A }
|
|
245
|
+
* { NOT: C } β { NOT: map(C) }
|
|
246
|
+
* { AND: [Cβ¦] } β { AND: [map(C)β¦] } (likewise OR; `OR: []`
|
|
247
|
+
* still matches nothing)
|
|
248
|
+
* bare properties β themselves (legacy `{ id: 'x' }` shape)
|
|
249
|
+
*
|
|
250
|
+
* `edge`/`edge_NOT` throw (unsupported in mutations). In a connection-
|
|
251
|
+
* shaped where, any other key throws too β the removed parallel builder
|
|
252
|
+
* silently IGNORED such keys (`{ id: 'x', NOT: {β¦} }` dropped `id`),
|
|
253
|
+
* widening the match.
|
|
254
|
+
*/
|
|
255
|
+
export declare function connectionWhereToNodeWhere(spec: Record<string, unknown>): Record<string, unknown>;
|
|
193
256
|
//# sourceMappingURL=mutation.compiler.d.ts.map
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"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"}
|