grafeo-ogm 1.13.0 → 2.0.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 +221 -0
- package/README.md +70 -0
- package/dist/cjs/compilers/fulltext.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/fulltext.compiler.js +30 -14
- package/dist/cjs/compilers/fulltext.compiler.js.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.d.ts +54 -1
- package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.js +134 -32
- package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
- package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/where.compiler.js +7 -0
- package/dist/cjs/compilers/where.compiler.js.map +1 -1
- package/dist/cjs/generator/type-emitters/fulltext-emitter.js +24 -8
- package/dist/cjs/generator/type-emitters/fulltext-emitter.js.map +1 -1
- package/dist/cjs/generator/type-emitters/sort-options-emitter.d.ts +2 -1
- package/dist/cjs/generator/type-emitters/sort-options-emitter.d.ts.map +1 -1
- package/dist/cjs/generator/type-emitters/sort-options-emitter.js +12 -5
- package/dist/cjs/generator/type-emitters/sort-options-emitter.js.map +1 -1
- package/dist/cjs/generator/type-emitters/utility-types.d.ts.map +1 -1
- package/dist/cjs/generator/type-emitters/utility-types.js +20 -0
- package/dist/cjs/generator/type-emitters/utility-types.js.map +1 -1
- package/dist/cjs/interface-model.d.ts.map +1 -1
- package/dist/cjs/interface-model.js +9 -1
- package/dist/cjs/interface-model.js.map +1 -1
- package/dist/cjs/model.d.ts.map +1 -1
- package/dist/cjs/model.js +12 -3
- package/dist/cjs/model.js.map +1 -1
- package/dist/cjs/ogm.d.ts.map +1 -1
- package/dist/cjs/ogm.js +6 -1
- package/dist/cjs/ogm.js.map +1 -1
- package/dist/cjs/utils/cypher-sort-projection.d.ts +3 -0
- package/dist/cjs/utils/cypher-sort-projection.d.ts.map +1 -1
- package/dist/cjs/utils/cypher-sort-projection.js +13 -1
- package/dist/cjs/utils/cypher-sort-projection.js.map +1 -1
- package/dist/cjs/utils/validation.d.ts +79 -0
- package/dist/cjs/utils/validation.d.ts.map +1 -1
- package/dist/cjs/utils/validation.js +554 -0
- package/dist/cjs/utils/validation.js.map +1 -1
- package/dist/esm/compilers/fulltext.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/fulltext.compiler.js +31 -15
- package/dist/esm/compilers/fulltext.compiler.js.map +1 -1
- package/dist/esm/compilers/mutation.compiler.d.ts +54 -1
- package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/mutation.compiler.js +134 -32
- package/dist/esm/compilers/mutation.compiler.js.map +1 -1
- package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/where.compiler.js +8 -1
- package/dist/esm/compilers/where.compiler.js.map +1 -1
- package/dist/esm/generator/type-emitters/fulltext-emitter.js +24 -8
- package/dist/esm/generator/type-emitters/fulltext-emitter.js.map +1 -1
- package/dist/esm/generator/type-emitters/sort-options-emitter.d.ts +2 -1
- package/dist/esm/generator/type-emitters/sort-options-emitter.d.ts.map +1 -1
- package/dist/esm/generator/type-emitters/sort-options-emitter.js +12 -5
- package/dist/esm/generator/type-emitters/sort-options-emitter.js.map +1 -1
- package/dist/esm/generator/type-emitters/utility-types.d.ts.map +1 -1
- package/dist/esm/generator/type-emitters/utility-types.js +20 -0
- package/dist/esm/generator/type-emitters/utility-types.js.map +1 -1
- package/dist/esm/interface-model.d.ts.map +1 -1
- package/dist/esm/interface-model.js +9 -1
- package/dist/esm/interface-model.js.map +1 -1
- package/dist/esm/model.d.ts.map +1 -1
- package/dist/esm/model.js +12 -3
- package/dist/esm/model.js.map +1 -1
- package/dist/esm/ogm.d.ts.map +1 -1
- package/dist/esm/ogm.js +6 -1
- package/dist/esm/ogm.js.map +1 -1
- package/dist/esm/utils/cypher-sort-projection.d.ts +3 -0
- package/dist/esm/utils/cypher-sort-projection.d.ts.map +1 -1
- package/dist/esm/utils/cypher-sort-projection.js +14 -2
- package/dist/esm/utils/cypher-sort-projection.js.map +1 -1
- package/dist/esm/utils/validation.d.ts +79 -0
- package/dist/esm/utils/validation.d.ts.map +1 -1
- package/dist/esm/utils/validation.js +549 -0
- package/dist/esm/utils/validation.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,226 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.0 (2026-08-04) — 💥 Inputs that were silently truncated are now rejected
|
|
4
|
+
|
|
5
|
+
> **A major release with one theme: grafeo used to read the first key of certain input objects and throw the rest away without a word.** `sort` entries, fulltext leaves, and fulltext logical operators all did it. Every one produced a *successful* query answering a different question than the caller asked — no exception, no log, nothing in the Cypher to suggest anything was missing.
|
|
6
|
+
>
|
|
7
|
+
> **This is a major version because the fix is a behavior change on inputs that previously "worked."** Code that relied on the truncated shapes now throws at runtime, and regenerating types turns most of those shapes into compile errors. If your `sort` arrays and `fulltext` inputs already carry one key per object — the documented form throughout — **you are unaffected and can upgrade directly**.
|
|
8
|
+
|
|
9
|
+
### 💥 Breaking changes at a glance
|
|
10
|
+
|
|
11
|
+
| Shape | Before | Now |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `sort: [{ a: 'ASC', b: 'DESC' }]` | `ORDER BY n.a ASC` — `b` dropped | throws; compile error after regen |
|
|
14
|
+
| `fulltext: { IdxA: {…}, IdxB: {…} }` | searched `IdxA` only | throws; compile error after regen |
|
|
15
|
+
| `fulltext: { OR: […], AND: […] }` | `OR` only | throws; compile error after regen |
|
|
16
|
+
| `fulltext: { NOT: {…}, AND: […] }` | **`AND` only — exclusion lost** | throws; compile error after regen |
|
|
17
|
+
| `fulltext: { OR: […], IdxA: {…} }` | `OR` only | throws at runtime |
|
|
18
|
+
| `sort: [null]`, `fulltext: null` | bare `TypeError` | `OGMError` |
|
|
19
|
+
| `const s: XSort = {}; s.a = 'ASC'` | compiled | compile error — push array entries |
|
|
20
|
+
|
|
21
|
+
Migration for each is mechanical and shown in the sections below. The short version: **one key per object; use the array (`sort`) or the `AND`/`OR` operators (`fulltext`) to express more than one.**
|
|
22
|
+
|
|
23
|
+
> **Origin.** Reported by the community ([#4](https://github.com/neomodular/grafeo-ogm/issues/4)) against `sort`, found in production where an admin-curated `position` column had been silently ignored for every client. Auditing for the same defect class turned up three more sites in `FulltextCompiler`, and reviewing *that* fix turned up a fourth at the operator dispatch.
|
|
24
|
+
|
|
25
|
+
### 🐛 A sort entry must now carry exactly one ordering key
|
|
26
|
+
|
|
27
|
+
`ORDER BY` precedence comes from an entry's **position in the array**, never from key order inside an object. The documented form was always correct and is unchanged:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// ✅ ORDER BY n.isUrgent DESC, n.position ASC
|
|
31
|
+
sort: [{ isUrgent: 'DESC' }, { position: 'ASC' }];
|
|
32
|
+
|
|
33
|
+
// ❌ compile error, and throws at runtime
|
|
34
|
+
sort: [{ isUrgent: 'DESC', position: 'ASC' }];
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Why reject rather than honour every key?** Because object key order is not a reliable carrier for precedence at a transport boundary. `graphql-js` `coerceInputValue` rebuilds input objects by iterating the **schema's** field definitions, not the order the client wrote them — so a client sending `{ position: ASC, isUrgent: DESC }` hands the resolver `{ isUrgent, position }` in SDL declaration order. Honouring both keys would have traded one silent wrong-order bug for another that only reproduces behind a GraphQL layer, and would have looked correct in direct TypeScript use. Array position survives that trip; key order does not.
|
|
38
|
+
|
|
39
|
+
### 🐛 A fulltext leaf must now reference exactly one index
|
|
40
|
+
|
|
41
|
+
The same defect lived at three sites in `FulltextCompiler` — `compileLeaf`, `compileRelationshipIndex`, and `compileRelationship` — each guarding the empty case and then reading `Object.keys(input)[0]`:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// ❌ before: searched CategoryNameIndex only, dropped CategoryBlurbIndex
|
|
45
|
+
// → CALL db.index.fulltext.queryNodes('CategoryNameIndex', $ft_phrase)
|
|
46
|
+
// → params: { ft_phrase: 'graph' } // 'neo4j' gone entirely
|
|
47
|
+
fulltext: {
|
|
48
|
+
CategoryNameIndex: { phrase: 'graph' },
|
|
49
|
+
CategoryBlurbIndex: { phrase: 'neo4j' },
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ✅ AND / OR already existed for this, and compile correctly
|
|
53
|
+
fulltext: {
|
|
54
|
+
AND: [
|
|
55
|
+
{ CategoryNameIndex: { phrase: 'graph' } },
|
|
56
|
+
{ CategoryBlurbIndex: { phrase: 'neo4j' } },
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**This one is more serious than the sort defect.** A dropped sort key returns the right rows in the wrong order; a dropped *search term* returns the **wrong rows** — a strictly wider result set, because one index was searched instead of two. A filter that silently stops filtering.
|
|
62
|
+
|
|
63
|
+
Phrase values are never echoed into the error message: they are user data and may carry control characters or sensitive content. Only index names — already identifier-validated — appear.
|
|
64
|
+
|
|
65
|
+
> **Note:** a characterization test, `should only use the first index when multiple are provided`, previously pinned the dropping behaviour. It described the defect rather than defending it (no rationale, while the sibling `SelectNormalizer` throws for the identical situation), and has been replaced with specs asserting rejection.
|
|
66
|
+
|
|
67
|
+
### 🐛 `AND` / `OR` / `NOT` must each be the only key in their object
|
|
68
|
+
|
|
69
|
+
Reviewing the leaf fix surfaced the same defect one level up, in the operator dispatch. `compileNode` tests `OR`, then `AND`, then `NOT`, and returns at the first hit — so anything else in the object was discarded without a word:
|
|
70
|
+
|
|
71
|
+
| Input | Compiled | Effect |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `{ OR: […], AND: […] }` | `OR` only | a filter disappears |
|
|
74
|
+
| `{ OR: […], SomeIndex: {…} }` | `OR` only | a filter disappears |
|
|
75
|
+
| `{ NOT: {…}, AND: […] }` | **`AND` only** | **the exclusion disappears** |
|
|
76
|
+
|
|
77
|
+
The third row is the dangerous one. `AND` is tested before `NOT`, so a `NOT` written alongside it vanished completely — the caller asked to *exclude* something and got no exclusion at all. That inverts intent rather than merely widening the result set.
|
|
78
|
+
|
|
79
|
+
Operators nest; they do not stack:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// ✅ correct
|
|
83
|
+
fulltext: { AND: [{ OR: [ /* … */ ] }, { CategoryBlurbIndex: { phrase: 'x' } }] }
|
|
84
|
+
|
|
85
|
+
// ❌ rejected
|
|
86
|
+
fulltext: { OR: [ /* … */ ], AND: [ /* … */ ] }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Sibling keys are **counted, never echoed** — unlike leaf index names they have not been identifier-validated at dispatch time, so interpolating them could forge log lines.
|
|
90
|
+
|
|
91
|
+
### 💥 BREAKING — generated `<Type>Sort` and `<Type>FulltextLeaf` types are now single-key
|
|
92
|
+
|
|
93
|
+
Both are wrapped in a new `ExactlyOneKey<T>` utility type, so a two-key entry is a **compile error** rather than a silent truncation. Unknown fields and invalid directions are rejected as before.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
export type SafedoseCategorySort = ExactlyOneKey<{
|
|
97
|
+
isUrgent?: InputMaybe<SortDirection>;
|
|
98
|
+
position?: InputMaybe<SortDirection>;
|
|
99
|
+
}>;
|
|
100
|
+
|
|
101
|
+
export type SafedoseCategoryFulltextLeaf = ExactlyOneKey<{
|
|
102
|
+
CategoryNameIndex?: FulltextIndexEntry;
|
|
103
|
+
CategoryBlurbIndex?: FulltextIndexEntry;
|
|
104
|
+
}>;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Nested relationship index objects are wrapped too, since `compileRelationshipIndex` reads a single key as well.
|
|
108
|
+
|
|
109
|
+
`<Node>FulltextInput` keeps its `leaf | OR | AND | NOT` shape, but each operator member now marks the other two `?: never`:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
export type SafedoseCategoryFulltextInput =
|
|
113
|
+
| SafedoseCategoryFulltextLeaf
|
|
114
|
+
| { OR: SafedoseCategoryFulltextInput[]; AND?: never; NOT?: never }
|
|
115
|
+
| { AND: SafedoseCategoryFulltextInput[]; OR?: never; NOT?: never }
|
|
116
|
+
| { NOT: SafedoseCategoryFulltextInput; OR?: never; AND?: never };
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This closes operator-vs-operator stacking at compile time. It deliberately does **not** close an operator mixed with a bare index key (`{ OR: [...], SomeIndex: {...} }`) — TypeScript permits a literal property that exists on any member of a union, so that shape stays a runtime rejection.
|
|
120
|
+
|
|
121
|
+
This lands when you **regenerate types**, and it breaks conditional sort building from an empty object literal. Push entries instead:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// before — no longer compiles
|
|
125
|
+
const s: BookSort = {};
|
|
126
|
+
if (byTitle) s.title = 'ASC';
|
|
127
|
+
|
|
128
|
+
// after
|
|
129
|
+
const sort: BookSort[] = [];
|
|
130
|
+
if (byTitle) sort.push({ title: 'ASC' });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Types with no sortable fields emit `Record<string, never>` rather than collapsing to `never`. `<Type>Options` is unchanged.
|
|
134
|
+
|
|
135
|
+
### 🛡️ Runtime guards for the paths types cannot reach
|
|
136
|
+
|
|
137
|
+
Types alone are not sufficient, so every surface validates arity at compile time too — `compileSortClause` via `assertSingleSortKey`, the three `FulltextCompiler` leaf sites via `assertSingleFulltextKey`, and the operator dispatch via `assertSingleFulltextOperator` — alongside the `assertSafeIdentifier` checks already in those loops. This matters because `FindOptions<TSort>` defaults to `Record<string, 'ASC' | 'DESC'>` — an index signature — so the untyped `ogm.model('X')` path admits multi-key entries regardless, and "exactly one arbitrary key" is not expressible over an open record. The GraphQL-resolver path erases the types too, and operator-plus-index-key is not expressible in the union at all. The errors name the offending keys and show the correct form:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
Sort entry has 2 keys (isUrgent, position); each sort entry must carry exactly
|
|
141
|
+
one ordering key, because ORDER BY precedence comes from array position and not
|
|
142
|
+
object key order. Use one entry per key: [{ isUrgent: 'DESC' }, { position: 'ASC' }].
|
|
143
|
+
|
|
144
|
+
Fulltext leaf has 2 keys (CategoryNameIndex, CategoryBlurbIndex); a fulltext entry
|
|
145
|
+
must reference exactly one index. Combine indexes with the AND / OR operators
|
|
146
|
+
instead: { AND: [{ CategoryNameIndex: … }, { CategoryBlurbIndex: … }] }.
|
|
147
|
+
|
|
148
|
+
Fulltext input carries AND + NOT; AND / OR / NOT must each be the only key in
|
|
149
|
+
their object, because the compiler reads the first one and ignores the rest.
|
|
150
|
+
Nest them instead: { AND: [{ OR: [...] }, { ... }] }.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Every key is identifier-validated (not just the first), so an unsafe identifier in a non-surviving key is now caught too. Only recognised sort directions are echoed into the error string, and fulltext phrases never are. **Zero-key sort entries (`{}`) keep their existing runtime skip** — they contribute no ordering rather than a wrong one, so there is no silent-wrong-answer to close, though the new types do reject the literal. Empty fulltext inputs still throw, unchanged.
|
|
154
|
+
|
|
155
|
+
### 🛡️ Malformed inputs now throw `OGMError` instead of a bare `TypeError`
|
|
156
|
+
|
|
157
|
+
`sort: [null]` surfaced as `TypeError: Cannot convert undefined or null to object` thrown from inside `Object.entries`, and fulltext inputs failed the same way on `Object.keys(input)` / `'OR' in input`. Both escaped `catch (e) { if (e instanceof OGMError) … }`, even though every other malformed input on those paths was already an `OGMError`.
|
|
158
|
+
|
|
159
|
+
A new `assertPlainObject` guards each enumeration point: sort entries, fulltext inputs (in `compileNode`, so operator recursion such as `{ OR: [null] }` is covered too), index entry values (`{ BookSearch: null }`), and the standalone relationship fulltext entry. Only the value's **kind** is reported — never the value:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Sort entry must be an object, got null.
|
|
163
|
+
Fulltext entry "BookTitleSearch" must be an object, got null.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Tests
|
|
167
|
+
|
|
168
|
+
Nine compiler specs covering multi-key rejection, the error's key list and suggested form, three-key arity, a multi-key entry in a non-first position, multi-key `@cypher` entries failing before any `CALL` is emitted, unsafe identifiers in non-surviving keys, refusal to echo unrecognised directions, the documented form still compiling to both `ORDER BY` terms, and zero-key entries still skipped.
|
|
169
|
+
|
|
170
|
+
Eight **integration** specs (`tests/sort-entry-arity.spec.ts`) drive the public API rather than the compiler, proving the guard is actually reached from `Model.find` / `Model.findFirst` / `InterfaceModel.find` — and that it fires **before any query reaches the driver**, so there is no round-trip returning wrongly-ordered rows. Verified as genuine regression coverage: with the guard disabled, exactly the 11 arity specs fail and every pre-existing sort spec still passes.
|
|
171
|
+
|
|
172
|
+
Eight non-object specs guard the `OGMError` contract — five sort-entry kinds (`null`, `undefined`, array, string, number), a bad entry in a non-first position, and refusal to echo the offending value; plus four on the fulltext side covering a non-object top level, one nested inside an `OR` branch, a non-object index entry value, and kind-without-value reporting.
|
|
173
|
+
|
|
174
|
+
Fourteen fulltext compiler specs: four on leaf arity (rejection, the AND/OR remedy appearing while phrases never do, both indexes compiling under `AND`, single-index leaves unchanged) and six on operator arity (two stacked operators, `NOT` alongside `AND`, an operator mixed with a bare index key, sibling keys counted rather than echoed, a single operator unchanged, and properly nested operators). Disabling either guard fails exactly its own specs and no others.
|
|
175
|
+
|
|
176
|
+
Seven emitter specs cover the `ExactlyOneKey` wrapper for node and interface `Sort` types, the no-sortable-fields fallback, `<Type>Options` referencing `<Type>Sort` unchanged, the fulltext leaf wrapper, the nested relationship index wrapper, and the mutually-exclusive operator members on `<Node>FulltextInput`.
|
|
177
|
+
|
|
178
|
+
Generated output was additionally verified end-to-end under `tsc --strict` against a schema carrying an interface, a `@cypher` sort field, two fulltext indexes, and a relationship-only type. Failing to compile: the reported sort shape, multi-key `@cypher` entries, three-key entries, multi-key interface entries, unknown sort fields, invalid directions, empty entries, sorting an unsortable type, two-index fulltext leaves, unknown index names, `OR` + `AND`, and `NOT` + `AND`. Compiling cleanly: the documented sort form, `@cypher` and interface sorts, incremental array building, single-index leaves, scores, `AND` / `OR` / `NOT` composition — and, as documented above, operator-plus-index-key, which the runtime rejects instead. Full suite green (1673); lint and format clean.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 1.14.0 (2026-07-24) — 🛡️ Security release: NLS enforcement on interface reads & relationship writes, and a `_MATCHES` ReDoS guard
|
|
183
|
+
|
|
184
|
+
> **Three security fixes.** Two are object-level authorization gaps: grafeo's node-level-security (NLS) policies were threaded correctly through the concrete-model read and write paths, but two paths silently skipped the **target type's** policy — nested relationship projections on **interface** reads, and **connect/disconnect** relationship writes — so a caller whose own access was correctly scoped could still read, or link/unlink, related nodes their policy forbids. The third hardens the `_MATCHES` filter operator against server-side ReDoS. If you use `policies` with interface models or nested `connect`/`disconnect`, or expose `_MATCHES` to untrusted input, upgrade.
|
|
185
|
+
|
|
186
|
+
### 🛡️ Interface reads now apply the target type's `read` policy to nested projections
|
|
187
|
+
|
|
188
|
+
`InterfaceModel.find` (and its `findFirst` / `findUnique` / `*OrThrow` siblings, which all route through it) applied the interface's source-side policy and the WHERE-side traversal policy — but compiled the **RETURN projection** without the read `policyContext` that `Model.find` passes. Every nested relationship pattern comprehension was therefore emitted with **no target-type policy**, so an interface query could project fields of related, policy-protected node types (e.g. another tenant's `author { email }`) that the target type's `read` policy is meant to filter out. The leak was silent and spanned every row the interface query returned.
|
|
189
|
+
|
|
190
|
+
The fix threads the resolved read `policyContext` into the interface selection compile, exactly as the concrete-model path already does, so every nested projection AND-stitches the target type's read policy — including union and connection branches. **When no policy layer is configured the emitted Cypher is byte-identical** to before; the guard engages only when a policy actually resolves for the projected type.
|
|
191
|
+
|
|
192
|
+
### 🛡️ Nested `connect` / `disconnect` now enforce the target type's `read` policy
|
|
193
|
+
|
|
194
|
+
`MutationCompiler` was policy-unaware: the target `MATCH` emitted for a nested `connect` (MERGE) or `disconnect` selected the target node by the caller's `where` alone, with no policy. A caller operating under a bound `withContext(ctx)` — whose *source* node was correctly policy-filtered — could therefore link or unlink relationships to arbitrary target nodes their policy should forbid (cross-tenant / cross-owner relationship writes):
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// `Book` has a read policy scoped to the owner. Previously this linked a
|
|
198
|
+
// foreign Book anyway — the target MATCH carried no Book policy. Now it
|
|
199
|
+
// AND-stitches the Book read policy, so an unreadable target fails to
|
|
200
|
+
// match and the MERGE is a no-op.
|
|
201
|
+
ogm.withContext({ uid }).model('User').update({
|
|
202
|
+
where: { id: myId },
|
|
203
|
+
connect: { ownedBooks: { where: { id: someBookId } } },
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The fix threads the resolved policy context into `MutationCompiler` and AND-stitches the target type's `read` policy into every connect/disconnect target `MATCH` — all seven sites (single / array / `UNWIND` connect, specific disconnect, nested-in-update connect and disconnect, union targets), reached from both `update` and `updateMany`. `read` is the correct gate: row-level target visibility is expressed as the `read` policy, while `create`/`update` restrictives are WITH-CHECK predicates over a mutation-input bag that a referenced existing node has none of. **No-policy, bypass (`unsafe.bypassPolicies`), and no-target-policy paths stay byte-identical**; `create` and `upsert` (which never emits a relationship `MATCH`) are untouched. Policy predicates use the mutation's shared, monotonic parameter counter, so they never collide with the caller's `connect`/`disconnect` `where` params.
|
|
208
|
+
|
|
209
|
+
### 🛡️ `_MATCHES` is now guarded against catastrophic-backtracking regexes (ReDoS)
|
|
210
|
+
|
|
211
|
+
The `_MATCHES` filter operator forwards its value into a Neo4j `=~` predicate, which the server evaluates with Java's **backtracking** regex engine. An attacker able to influence a `where` filter could supply a catastrophic pattern (e.g. `(a+)+$`, `([a-z]+)+$`, `(a?)+$`) and pin shared Neo4j CPU — a server-side ReDoS ([CWE-1333](https://cwe.mitre.org/data/definitions/1333.html)). Parameterization stops injection but not this: the payload *is* the regex.
|
|
212
|
+
|
|
213
|
+
Every `_MATCHES` value is now validated at compile time by a **sound-by-construction** guard. Rather than blocklisting known-bad shapes (always incomplete), it accepts a pattern **only when it can prove the pattern is linear**, and refuses everything it cannot model — alternation, quantified groups, lookaround, backreferences, unsupported escapes. The check is single-pass and bounded, so it cannot itself become a DoS vector, and Java-flavor divergences are modeled soundly (`\v` is the vertical-whitespace class; `\0`/octal escapes are refused; only inline flags that don't change the analysis — `i`/`m`/`s`/`d` — are honored, so `(?x)` COMMENTS mode and `(?u)`/`(?U)` Unicode widening are refused).
|
|
214
|
+
|
|
215
|
+
- **Precision cost (intended, fail-closed).** Some linear-but-unprovable patterns are refused — notably **all alternation** (`(cat|dog)`) and **contains-style** `.*x.*` shapes. Reach for the dedicated, always-safe operators (`_CONTAINS` / `_STARTS_WITH` / `_ENDS_WITH` / `_IN`), simplify the pattern, or disable `_MATCHES` entirely via `features.filters.String.MATCHES` if you must run arbitrary regexes and accept the risk. The refusal error names these options.
|
|
216
|
+
- **Common patterns keep working** — anchors, character classes, single wildcards (`^foo.*bar$`, `[A-Za-z0-9_]+`, `\d{3}-\d{4}`), fenced quantifiers (`[a-z]+@[a-z]+\.[a-z]+`), and inline-flag prefixes (`(?i)…`).
|
|
217
|
+
|
|
218
|
+
### Tests
|
|
219
|
+
|
|
220
|
+
Interface-read policy: two specs asserting the nested projection now carries the target `read` predicate for interface reads, and that the no-policy path stays byte-identical. Connect/disconnect policy: six specs covering connect and disconnect target MATCHes carrying the target `read` policy, the update-op-not-stitched case, byte-identical no-policy/bypass Cypher, and non-colliding parameter numbering. ReDoS guard: a 100+-case battery covering every known catastrophic family (nested quantifiers, nullable/overlapping alternation under repeat, custom-class and nested-group bases, unrolled adjacent optionals), fail-closed refusal of unmodeled constructs, acceptance of provably-linear patterns, and the three Java-flavor soundness regressions found and closed during adversarial review (`(?x)`, `\v`, `\0`). Full suite green (1629); lint and format clean.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
3
224
|
## 1.13.0 (2026-07-14) — ✨ `$cloneSubgraph` returns `nodesByLabel`
|
|
4
225
|
|
|
5
226
|
> **First community-requested feature** ([#1](https://github.com/neomodular/grafeo-ogm/issues/1)). After deep-cloning a content subgraph, a caller often needs to post-process only the nodes of a **specific label** (re-normalize, re-stamp, re-index). Until now `nodeMapping` held every cloned id across all labels, forcing a broad-`IN`-plus-label-filter dance on the caller. The clone already visits every node with its labels in hand — so now it just tells you.
|
package/README.md
CHANGED
|
@@ -732,6 +732,36 @@ const books = await Book.find({
|
|
|
732
732
|
offset: 40,
|
|
733
733
|
},
|
|
734
734
|
});
|
|
735
|
+
// ORDER BY n.title ASC, n.published DESC
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
**Each sort entry carries exactly one ordering key.** `ORDER BY` precedence
|
|
739
|
+
comes from an entry's position in the array, never from key order inside an
|
|
740
|
+
object:
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
// ✅ correct — precedence is unambiguous
|
|
744
|
+
sort: [{ title: 'ASC' }, { published: 'DESC' }];
|
|
745
|
+
|
|
746
|
+
// ❌ rejected — compile error, and throws at runtime
|
|
747
|
+
sort: [{ title: 'ASC', published: 'DESC' }];
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
Why not just honour both keys? Because object key order is not a reliable
|
|
751
|
+
carrier for precedence. When sort input arrives through a GraphQL layer,
|
|
752
|
+
`graphql-js` rebuilds input objects by iterating the schema's field
|
|
753
|
+
definitions, so the order a client wrote is already gone before the OGM sees
|
|
754
|
+
it. Array position survives that trip; key order does not.
|
|
755
|
+
|
|
756
|
+
Building a sort list conditionally works the same way — push entries rather
|
|
757
|
+
than assembling one object:
|
|
758
|
+
|
|
759
|
+
```typescript
|
|
760
|
+
const sort: BookSort[] = [];
|
|
761
|
+
if (byTitle) sort.push({ title: 'ASC' });
|
|
762
|
+
if (byDate) sort.push({ published: 'DESC' });
|
|
763
|
+
|
|
764
|
+
await Book.find({ options: { sort } });
|
|
735
765
|
```
|
|
736
766
|
|
|
737
767
|
---
|
|
@@ -930,6 +960,46 @@ await Book.find({ fulltext: { BookSearch: { phrase: 'graph' } } });
|
|
|
930
960
|
|
|
931
961
|
The runtime compiler is unchanged; this is a purely ergonomic type-level improvement. The global `FulltextInput`, `FulltextLeaf`, and `FulltextIndexEntry` exports remain for writing generic helpers across models.
|
|
932
962
|
|
|
963
|
+
**One index per leaf (v2.0.0+).** A fulltext leaf references exactly one index. To search several, compose them with `AND` / `OR` — which is what those operators are for:
|
|
964
|
+
|
|
965
|
+
```typescript
|
|
966
|
+
// ✅ correct — both indexes are searched
|
|
967
|
+
fulltext: {
|
|
968
|
+
AND: [
|
|
969
|
+
{ BookSearch: { phrase: 'graph' } },
|
|
970
|
+
{ BlurbSearch: { phrase: 'neo4j' } },
|
|
971
|
+
],
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// ❌ rejected — compile error, and throws at runtime
|
|
975
|
+
fulltext: {
|
|
976
|
+
BookSearch: { phrase: 'graph' },
|
|
977
|
+
BlurbSearch: { phrase: 'neo4j' },
|
|
978
|
+
}
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
Before v2.0.0 the second form compiled to a search of `BookSearch` **only**; `BlurbSearch` and its phrase were dropped with no error, so the query returned a strictly *wider* result set than intended. `AND` compiles to correlated subqueries, `OR` to a `UNION` with `max(score)` deduplication.
|
|
982
|
+
|
|
983
|
+
**One operator per object (v2.0.0+).** The same rule applies to `AND` / `OR` / `NOT` — they nest, they don't stack:
|
|
984
|
+
|
|
985
|
+
```typescript
|
|
986
|
+
// ✅ correct — nest to combine
|
|
987
|
+
fulltext: {
|
|
988
|
+
AND: [
|
|
989
|
+
{ OR: [{ BookSearch: { phrase: 'graph' } }, { BookSearch: { phrase: 'db' } }] },
|
|
990
|
+
{ NOT: { BlurbSearch: { phrase: 'draft' } } },
|
|
991
|
+
],
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// ❌ rejected — two operators in one object
|
|
995
|
+
fulltext: {
|
|
996
|
+
OR: [{ BookSearch: { phrase: 'graph' } }],
|
|
997
|
+
AND: [{ BlurbSearch: { phrase: 'neo4j' } }],
|
|
998
|
+
}
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
The compiler reads `OR`, then `AND`, then `NOT`, and stops at the first one it finds. Before v2.0.0 the rest were dropped silently — and because `AND` is checked before `NOT`, writing `{ NOT: …, AND: … }` compiled the `AND` alone and **discarded the exclusion entirely**. Mixing an operator with a bare index key (`{ OR: [...], BookSearch: {...} }`) is rejected at runtime; TypeScript can't express that one.
|
|
1002
|
+
|
|
933
1003
|
### Subgraph Operations
|
|
934
1004
|
|
|
935
1005
|
Clone or delete entire subgraphs using APOC procedures. Requires the APOC plugin installed in Neo4j.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fulltext.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/fulltext.compiler.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,aAAa,EAEd,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"fulltext.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/fulltext.compiler.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,aAAa,EAEd,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAkChF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IACf,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAE1C;;;OAGG;IACH,OAAO,CACL,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE,cAAc,EACvB,OAAO,SAAM,GACZ,cAAc;IAqBjB;;;OAGG;IACH,mBAAmB,CACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAC5D,QAAQ,EAAE,aAAa,EACvB,MAAM,SAAM,GACX,cAAc;IAgDjB,OAAO,CAAC,WAAW;IAmDnB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAqCnB,+EAA+E;IAC/E,OAAO,CAAC,gBAAgB;IAoCxB;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;IAgFhC;;OAEG;IACH,OAAO,CAAC,SAAS;IAiDjB;;;OAGG;IACH,OAAO,CAAC,UAAU;IA8DlB;;;OAGG;IACH,OAAO,CAAC,UAAU;CA2BnB"}
|
|
@@ -67,12 +67,16 @@ class FulltextCompiler {
|
|
|
67
67
|
*/
|
|
68
68
|
compileRelationship(fulltext, relIndex, relVar = 'r') {
|
|
69
69
|
(0, validation_1.assertSafeIdentifier)(relVar, 'relationship variable');
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
(0, validation_1.assertPlainObject)(fulltext, 'Relationship fulltext input');
|
|
71
|
+
const entries = Object.entries(fulltext);
|
|
72
|
+
if (entries.length === 0)
|
|
72
73
|
throw new errors_1.OGMError('Fulltext input must contain at least one index entry');
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
74
|
+
// Validate every key, not just the surviving one, so the arity error only
|
|
75
|
+
// ever interpolates identifier-shaped strings.
|
|
76
|
+
for (const [key] of entries)
|
|
77
|
+
(0, validation_1.assertSafeIdentifier)(key, 'fulltext index name');
|
|
78
|
+
(0, validation_1.assertSingleFulltextKey)(entries, 'Relationship fulltext input');
|
|
79
|
+
const [indexName, input] = entries[0];
|
|
76
80
|
assertValidFulltextPhrase(input.phrase);
|
|
77
81
|
if (relIndex.name !== indexName)
|
|
78
82
|
throw new errors_1.OGMError(`Unknown relationship fulltext index "${indexName}". Expected "${relIndex.name}".`);
|
|
@@ -97,6 +101,12 @@ class FulltextCompiler {
|
|
|
97
101
|
compileNode(input, nodeDef, nodeVar, paramCounter, depth = 0) {
|
|
98
102
|
if (depth > MAX_DEPTH)
|
|
99
103
|
throw new errors_1.OGMError(`Fulltext logical nesting exceeds maximum depth of ${MAX_DEPTH}`);
|
|
104
|
+
// Guarded here rather than in `compile()` so it also covers every branch
|
|
105
|
+
// reached through OR / AND / NOT recursion, e.g. `{ OR: [null] }`.
|
|
106
|
+
(0, validation_1.assertPlainObject)(input, 'Fulltext input');
|
|
107
|
+
// The operator dispatch below returns on its first hit, so anything
|
|
108
|
+
// alongside OR / AND / NOT would be dropped without a word.
|
|
109
|
+
(0, validation_1.assertSingleFulltextOperator)(Object.keys(input));
|
|
100
110
|
if ((0, model_1.isFulltextLeaf)(input))
|
|
101
111
|
return this.compileLeaf(input, nodeDef, nodeVar, paramCounter);
|
|
102
112
|
if ('OR' in input)
|
|
@@ -113,11 +123,16 @@ class FulltextCompiler {
|
|
|
113
123
|
* - Relationship index: `{ relField: { IndexName: { phrase, score? } } }` — value is nested
|
|
114
124
|
*/
|
|
115
125
|
compileLeaf(leaf, nodeDef, nodeVar, paramCounter) {
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
126
|
+
const entries = Object.entries(leaf);
|
|
127
|
+
if (entries.length === 0)
|
|
118
128
|
throw new errors_1.OGMError('Fulltext input must contain at least one index entry');
|
|
119
|
-
const
|
|
120
|
-
|
|
129
|
+
for (const [k] of entries)
|
|
130
|
+
(0, validation_1.assertSafeIdentifier)(k, 'fulltext index or relationship name');
|
|
131
|
+
(0, validation_1.assertSingleFulltextKey)(entries, 'Fulltext leaf');
|
|
132
|
+
const key = entries[0][0];
|
|
133
|
+
// `isFulltextIndexEntry` uses the `in` operator, which throws a bare
|
|
134
|
+
// TypeError on a non-object (e.g. `{ BookSearch: null }`).
|
|
135
|
+
(0, validation_1.assertPlainObject)(leaf[key], `Fulltext entry "${key}"`);
|
|
121
136
|
const value = leaf[key];
|
|
122
137
|
// If value has `phrase`, it's a direct node index entry
|
|
123
138
|
if ((0, model_1.isFulltextIndexEntry)(value))
|
|
@@ -163,12 +178,13 @@ class FulltextCompiler {
|
|
|
163
178
|
const relProps = this.schema.relationshipProperties.get(relDef.properties);
|
|
164
179
|
if (!relProps)
|
|
165
180
|
throw new errors_1.OGMError(`@relationshipProperties type "${relDef.properties}" not found`);
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
181
|
+
const innerEntries = Object.entries(inner);
|
|
182
|
+
if (innerEntries.length === 0)
|
|
168
183
|
throw new errors_1.OGMError('Relationship fulltext entry must contain an index name');
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
184
|
+
for (const [key] of innerEntries)
|
|
185
|
+
(0, validation_1.assertSafeIdentifier)(key, 'fulltext index name');
|
|
186
|
+
(0, validation_1.assertSingleFulltextKey)(innerEntries, `Relationship fulltext entry for "${relFieldName}"`);
|
|
187
|
+
const [indexName, input] = innerEntries[0];
|
|
172
188
|
const ftIndex = (relProps.fulltextIndexes ?? []).find((idx) => idx.name === indexName);
|
|
173
189
|
if (!ftIndex)
|
|
174
190
|
throw new errors_1.OGMError(`Unknown fulltext index "${indexName}" on @relationshipProperties "${relDef.properties}"`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fulltext.compiler.js","sourceRoot":"","sources":["../../../src/compilers/fulltext.compiler.ts"],"names":[],"mappings":";;;AAAA,sCAAqC;AACrC,oCAKkB;AAElB,
|
|
1
|
+
{"version":3,"file":"fulltext.compiler.js","sourceRoot":"","sources":["../../../src/compilers/fulltext.compiler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { OGMLogger } from '../execution/executor';
|
|
2
|
+
import type { PolicyContextBundle } from '../policy/types';
|
|
2
3
|
import { NodeDefinition, SchemaMetadata } from '../schema/types';
|
|
4
|
+
import { WhereCompiler } from './where.compiler';
|
|
3
5
|
export interface MutationResult {
|
|
4
6
|
cypher: string;
|
|
5
7
|
params: Record<string, unknown>;
|
|
@@ -15,9 +17,44 @@ export declare class MutationCompiler {
|
|
|
15
17
|
*/
|
|
16
18
|
private labelCache;
|
|
17
19
|
private logger?;
|
|
20
|
+
/**
|
|
21
|
+
* WhereCompiler used ONLY to compile a relationship TARGET type's
|
|
22
|
+
* `read` policy predicate for connect/disconnect target MATCHes,
|
|
23
|
+
* mirroring the read paths (`WhereCompiler`/`SelectionCompiler`).
|
|
24
|
+
* Injected by the OGM so it shares the user's compiler options;
|
|
25
|
+
* lazily constructed with default options otherwise.
|
|
26
|
+
*/
|
|
27
|
+
private policyWhereCompiler?;
|
|
18
28
|
constructor(schema: SchemaMetadata, options?: {
|
|
19
29
|
logger?: OGMLogger;
|
|
30
|
+
whereCompiler?: WhereCompiler;
|
|
20
31
|
});
|
|
32
|
+
/**
|
|
33
|
+
* Resolve and compile the TARGET type's `read` policy predicate for a
|
|
34
|
+
* connect/disconnect target MATCH, mirroring the read path
|
|
35
|
+
* (`WhereCompiler.buildTargetBundle` + `WhereCompiler.compile`). You
|
|
36
|
+
* must be allowed to SEE a node to link/unlink it, so linking to an
|
|
37
|
+
* unreadable node is the IDOR this closes; the codebase models the
|
|
38
|
+
* row-level write gate for `update`/`delete` targets as `read`
|
|
39
|
+
* ReadRestrictive policies (see policy/types.ts), so `read` is both
|
|
40
|
+
* necessary and the semantically correct operation here.
|
|
41
|
+
*
|
|
42
|
+
* Returns `null` — leaving the emitted Cypher BYTE-IDENTICAL — when:
|
|
43
|
+
* - no policy context is threaded (no policy bound / bypass active), OR
|
|
44
|
+
* - no `paramCounter` is available to allocate policy params, OR
|
|
45
|
+
* - the target type has no policy applicable to `read`, OR
|
|
46
|
+
* - the resolved policy is `overridden` (compiles to nothing).
|
|
47
|
+
*
|
|
48
|
+
* When a target policy resolves, the compiled predicate is returned and
|
|
49
|
+
* its bound params are merged into `params`. Policy params use the
|
|
50
|
+
* shared `paramCounter` (`param<N>`), continuing the sequence started by
|
|
51
|
+
* the top-level WHERE, so they can never collide with the
|
|
52
|
+
* connect/disconnect `where` params (prefix-named, e.g.
|
|
53
|
+
* `connect_<field>_<prop>`) or the selection's params (allocated later
|
|
54
|
+
* from the same counter).
|
|
55
|
+
*/
|
|
56
|
+
private buildTargetPolicyPredicate;
|
|
57
|
+
private getPolicyWhereCompiler;
|
|
21
58
|
/**
|
|
22
59
|
* Surface a logical operator that contributed zero effective conditions.
|
|
23
60
|
* Mirrors WhereCompiler.warnEmptyLogical — fires only on the anomalous
|
|
@@ -39,7 +76,23 @@ export declare class MutationCompiler {
|
|
|
39
76
|
cypher: string;
|
|
40
77
|
params: Record<string, unknown>;
|
|
41
78
|
preludes?: string[];
|
|
42
|
-
}, labels?: string[], returnMode?: 'node' | 'count'
|
|
79
|
+
}, labels?: string[], returnMode?: 'node' | 'count',
|
|
80
|
+
/**
|
|
81
|
+
* The caller's resolved policy bundle for this mutation (the SOURCE
|
|
82
|
+
* type's `update` bundle). Its `resolveForType` is used to resolve
|
|
83
|
+
* each connect/disconnect TARGET type's `read` policy. `undefined`
|
|
84
|
+
* when no policy is bound or a bypass is active — in which case the
|
|
85
|
+
* emitted Cypher is byte-identical to before this enforcement.
|
|
86
|
+
*/
|
|
87
|
+
policyContext?: PolicyContextBundle,
|
|
88
|
+
/**
|
|
89
|
+
* Shared `param<N>` counter, continued from the top-level WHERE
|
|
90
|
+
* compile so target-policy params never collide with connect/
|
|
91
|
+
* disconnect `where` params or the selection's params.
|
|
92
|
+
*/
|
|
93
|
+
paramCounter?: {
|
|
94
|
+
count: number;
|
|
95
|
+
}): MutationResult;
|
|
43
96
|
/**
|
|
44
97
|
* Generate DELETE Cypher with optional cascade.
|
|
45
98
|
*/
|
|
@@ -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,EACL,cAAc,EAEd,cAAc,EACf,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"mutation.compiler.d.ts","sourceRoot":"","sources":["../../../src/compilers/mutation.compiler.ts"],"names":[],"mappings":"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"}
|