grafeo-ogm 2.4.7 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +95 -35
  3. package/dist/cjs/cli/commands/db-push.d.ts.map +1 -1
  4. package/dist/cjs/cli/commands/db-push.js +74 -8
  5. package/dist/cjs/cli/commands/db-push.js.map +1 -1
  6. package/dist/cjs/cli/commands/db-seed.d.ts.map +1 -1
  7. package/dist/cjs/cli/commands/db-seed.js +10 -1
  8. package/dist/cjs/cli/commands/db-seed.js.map +1 -1
  9. package/dist/cjs/cli/introspect.d.ts +2 -2
  10. package/dist/cjs/cli/introspect.d.ts.map +1 -1
  11. package/dist/cjs/cli/introspect.js +44 -2
  12. package/dist/cjs/cli/introspect.js.map +1 -1
  13. package/dist/cjs/cli/push-planner.d.ts +57 -2
  14. package/dist/cjs/cli/push-planner.d.ts.map +1 -1
  15. package/dist/cjs/cli/push-planner.js +161 -3
  16. package/dist/cjs/cli/push-planner.js.map +1 -1
  17. package/dist/cjs/compilers/fulltext.compiler.d.ts.map +1 -1
  18. package/dist/cjs/compilers/fulltext.compiler.js +5 -1
  19. package/dist/cjs/compilers/fulltext.compiler.js.map +1 -1
  20. package/dist/cjs/compilers/mutation.compiler.d.ts +107 -1
  21. package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
  22. package/dist/cjs/compilers/mutation.compiler.js +695 -422
  23. package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
  24. package/dist/cjs/compilers/select-normalizer.d.ts.map +1 -1
  25. package/dist/cjs/compilers/select-normalizer.js +12 -1
  26. package/dist/cjs/compilers/select-normalizer.js.map +1 -1
  27. package/dist/cjs/compilers/selection.compiler.d.ts +56 -0
  28. package/dist/cjs/compilers/selection.compiler.d.ts.map +1 -1
  29. package/dist/cjs/compilers/selection.compiler.js +271 -29
  30. package/dist/cjs/compilers/selection.compiler.js.map +1 -1
  31. package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
  32. package/dist/cjs/compilers/where.compiler.js +36 -15
  33. package/dist/cjs/compilers/where.compiler.js.map +1 -1
  34. package/dist/cjs/execution/executor.d.ts +34 -9
  35. package/dist/cjs/execution/executor.d.ts.map +1 -1
  36. package/dist/cjs/execution/executor.js +42 -5
  37. package/dist/cjs/execution/executor.js.map +1 -1
  38. package/dist/cjs/execution/result-mapper.d.ts.map +1 -1
  39. package/dist/cjs/execution/result-mapper.js +4 -2
  40. package/dist/cjs/execution/result-mapper.js.map +1 -1
  41. package/dist/cjs/generator/generate-types.d.ts.map +1 -1
  42. package/dist/cjs/generator/generate-types.js +76 -0
  43. package/dist/cjs/generator/generate-types.js.map +1 -1
  44. package/dist/cjs/generator/type-emitters/aggregation-emitter.d.ts.map +1 -1
  45. package/dist/cjs/generator/type-emitters/aggregation-emitter.js +19 -7
  46. package/dist/cjs/generator/type-emitters/aggregation-emitter.js.map +1 -1
  47. package/dist/cjs/generator/type-emitters/input-emitter.js +13 -4
  48. package/dist/cjs/generator/type-emitters/input-emitter.js.map +1 -1
  49. package/dist/cjs/generator/type-emitters/model-map-emitter.d.ts +1 -0
  50. package/dist/cjs/generator/type-emitters/model-map-emitter.d.ts.map +1 -1
  51. package/dist/cjs/generator/type-emitters/model-map-emitter.js +5 -20
  52. package/dist/cjs/generator/type-emitters/model-map-emitter.js.map +1 -1
  53. package/dist/cjs/generator/type-emitters/node-type-emitter.d.ts +3 -0
  54. package/dist/cjs/generator/type-emitters/node-type-emitter.d.ts.map +1 -1
  55. package/dist/cjs/generator/type-emitters/node-type-emitter.js +6 -11
  56. package/dist/cjs/generator/type-emitters/node-type-emitter.js.map +1 -1
  57. package/dist/cjs/generator/type-emitters/select-fields-emitter.js +21 -2
  58. package/dist/cjs/generator/type-emitters/select-fields-emitter.js.map +1 -1
  59. package/dist/cjs/generator/type-emitters/where-emitter.js +19 -3
  60. package/dist/cjs/generator/type-emitters/where-emitter.js.map +1 -1
  61. package/dist/cjs/index.d.ts +1 -1
  62. package/dist/cjs/index.d.ts.map +1 -1
  63. package/dist/cjs/interface-model.d.ts +12 -0
  64. package/dist/cjs/interface-model.d.ts.map +1 -1
  65. package/dist/cjs/interface-model.js +27 -13
  66. package/dist/cjs/interface-model.js.map +1 -1
  67. package/dist/cjs/model.d.ts.map +1 -1
  68. package/dist/cjs/model.js +12 -0
  69. package/dist/cjs/model.js.map +1 -1
  70. package/dist/cjs/ogm.d.ts +22 -2
  71. package/dist/cjs/ogm.d.ts.map +1 -1
  72. package/dist/cjs/ogm.js +42 -5
  73. package/dist/cjs/ogm.js.map +1 -1
  74. package/dist/cjs/policy/nested-writes.d.ts.map +1 -1
  75. package/dist/cjs/policy/nested-writes.js +40 -20
  76. package/dist/cjs/policy/nested-writes.js.map +1 -1
  77. package/dist/cjs/schema/parser.d.ts.map +1 -1
  78. package/dist/cjs/schema/parser.js +94 -16
  79. package/dist/cjs/schema/parser.js.map +1 -1
  80. package/dist/cjs/schema/types.d.ts +1 -1
  81. package/dist/cjs/schema/types.d.ts.map +1 -1
  82. package/dist/cjs/subgraph/subgraph-operations.d.ts.map +1 -1
  83. package/dist/cjs/subgraph/subgraph-operations.js +71 -6
  84. package/dist/cjs/subgraph/subgraph-operations.js.map +1 -1
  85. package/dist/cjs/subgraph/types.d.ts +14 -0
  86. package/dist/cjs/subgraph/types.d.ts.map +1 -1
  87. package/dist/cjs/testing/cypher-assert.d.ts +19 -1
  88. package/dist/cjs/testing/cypher-assert.d.ts.map +1 -1
  89. package/dist/cjs/testing/cypher-assert.js +217 -13
  90. package/dist/cjs/testing/cypher-assert.js.map +1 -1
  91. package/dist/cjs/testing/neo4j-record-factory.d.ts +29 -2
  92. package/dist/cjs/testing/neo4j-record-factory.d.ts.map +1 -1
  93. package/dist/cjs/testing/neo4j-record-factory.js +93 -3
  94. package/dist/cjs/testing/neo4j-record-factory.js.map +1 -1
  95. package/dist/cjs/utils/cypher-field-projection.d.ts +31 -3
  96. package/dist/cjs/utils/cypher-field-projection.d.ts.map +1 -1
  97. package/dist/cjs/utils/cypher-field-projection.js +59 -7
  98. package/dist/cjs/utils/cypher-field-projection.js.map +1 -1
  99. package/dist/cjs/utils/write-coercion.d.ts +18 -0
  100. package/dist/cjs/utils/write-coercion.d.ts.map +1 -1
  101. package/dist/cjs/utils/write-coercion.js +74 -1
  102. package/dist/cjs/utils/write-coercion.js.map +1 -1
  103. package/dist/esm/cli/commands/db-push.d.ts.map +1 -1
  104. package/dist/esm/cli/commands/db-push.js +74 -8
  105. package/dist/esm/cli/commands/db-push.js.map +1 -1
  106. package/dist/esm/cli/commands/db-seed.d.ts.map +1 -1
  107. package/dist/esm/cli/commands/db-seed.js +10 -1
  108. package/dist/esm/cli/commands/db-seed.js.map +1 -1
  109. package/dist/esm/cli/introspect.d.ts +2 -2
  110. package/dist/esm/cli/introspect.d.ts.map +1 -1
  111. package/dist/esm/cli/introspect.js +44 -2
  112. package/dist/esm/cli/introspect.js.map +1 -1
  113. package/dist/esm/cli/push-planner.d.ts +57 -2
  114. package/dist/esm/cli/push-planner.d.ts.map +1 -1
  115. package/dist/esm/cli/push-planner.js +162 -4
  116. package/dist/esm/cli/push-planner.js.map +1 -1
  117. package/dist/esm/compilers/fulltext.compiler.d.ts.map +1 -1
  118. package/dist/esm/compilers/fulltext.compiler.js +5 -1
  119. package/dist/esm/compilers/fulltext.compiler.js.map +1 -1
  120. package/dist/esm/compilers/mutation.compiler.d.ts +107 -1
  121. package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
  122. package/dist/esm/compilers/mutation.compiler.js +692 -422
  123. package/dist/esm/compilers/mutation.compiler.js.map +1 -1
  124. package/dist/esm/compilers/select-normalizer.d.ts.map +1 -1
  125. package/dist/esm/compilers/select-normalizer.js +12 -1
  126. package/dist/esm/compilers/select-normalizer.js.map +1 -1
  127. package/dist/esm/compilers/selection.compiler.d.ts +56 -0
  128. package/dist/esm/compilers/selection.compiler.d.ts.map +1 -1
  129. package/dist/esm/compilers/selection.compiler.js +273 -31
  130. package/dist/esm/compilers/selection.compiler.js.map +1 -1
  131. package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
  132. package/dist/esm/compilers/where.compiler.js +37 -16
  133. package/dist/esm/compilers/where.compiler.js.map +1 -1
  134. package/dist/esm/execution/executor.d.ts +34 -9
  135. package/dist/esm/execution/executor.d.ts.map +1 -1
  136. package/dist/esm/execution/executor.js +41 -5
  137. package/dist/esm/execution/executor.js.map +1 -1
  138. package/dist/esm/execution/result-mapper.d.ts.map +1 -1
  139. package/dist/esm/execution/result-mapper.js +4 -2
  140. package/dist/esm/execution/result-mapper.js.map +1 -1
  141. package/dist/esm/generator/generate-types.d.ts.map +1 -1
  142. package/dist/esm/generator/generate-types.js +76 -0
  143. package/dist/esm/generator/generate-types.js.map +1 -1
  144. package/dist/esm/generator/type-emitters/aggregation-emitter.d.ts.map +1 -1
  145. package/dist/esm/generator/type-emitters/aggregation-emitter.js +19 -7
  146. package/dist/esm/generator/type-emitters/aggregation-emitter.js.map +1 -1
  147. package/dist/esm/generator/type-emitters/input-emitter.js +13 -4
  148. package/dist/esm/generator/type-emitters/input-emitter.js.map +1 -1
  149. package/dist/esm/generator/type-emitters/model-map-emitter.d.ts +1 -0
  150. package/dist/esm/generator/type-emitters/model-map-emitter.d.ts.map +1 -1
  151. package/dist/esm/generator/type-emitters/model-map-emitter.js +5 -20
  152. package/dist/esm/generator/type-emitters/model-map-emitter.js.map +1 -1
  153. package/dist/esm/generator/type-emitters/node-type-emitter.d.ts +3 -0
  154. package/dist/esm/generator/type-emitters/node-type-emitter.d.ts.map +1 -1
  155. package/dist/esm/generator/type-emitters/node-type-emitter.js +6 -11
  156. package/dist/esm/generator/type-emitters/node-type-emitter.js.map +1 -1
  157. package/dist/esm/generator/type-emitters/select-fields-emitter.js +21 -2
  158. package/dist/esm/generator/type-emitters/select-fields-emitter.js.map +1 -1
  159. package/dist/esm/generator/type-emitters/where-emitter.js +19 -3
  160. package/dist/esm/generator/type-emitters/where-emitter.js.map +1 -1
  161. package/dist/esm/index.d.ts +1 -1
  162. package/dist/esm/index.d.ts.map +1 -1
  163. package/dist/esm/interface-model.d.ts +12 -0
  164. package/dist/esm/interface-model.d.ts.map +1 -1
  165. package/dist/esm/interface-model.js +27 -13
  166. package/dist/esm/interface-model.js.map +1 -1
  167. package/dist/esm/model.d.ts.map +1 -1
  168. package/dist/esm/model.js +12 -0
  169. package/dist/esm/model.js.map +1 -1
  170. package/dist/esm/ogm.d.ts +22 -2
  171. package/dist/esm/ogm.d.ts.map +1 -1
  172. package/dist/esm/ogm.js +43 -6
  173. package/dist/esm/ogm.js.map +1 -1
  174. package/dist/esm/policy/nested-writes.d.ts.map +1 -1
  175. package/dist/esm/policy/nested-writes.js +40 -20
  176. package/dist/esm/policy/nested-writes.js.map +1 -1
  177. package/dist/esm/schema/parser.d.ts.map +1 -1
  178. package/dist/esm/schema/parser.js +94 -16
  179. package/dist/esm/schema/parser.js.map +1 -1
  180. package/dist/esm/schema/types.d.ts +1 -1
  181. package/dist/esm/schema/types.d.ts.map +1 -1
  182. package/dist/esm/subgraph/subgraph-operations.d.ts.map +1 -1
  183. package/dist/esm/subgraph/subgraph-operations.js +71 -6
  184. package/dist/esm/subgraph/subgraph-operations.js.map +1 -1
  185. package/dist/esm/subgraph/types.d.ts +14 -0
  186. package/dist/esm/subgraph/types.d.ts.map +1 -1
  187. package/dist/esm/testing/cypher-assert.d.ts +19 -1
  188. package/dist/esm/testing/cypher-assert.d.ts.map +1 -1
  189. package/dist/esm/testing/cypher-assert.js +217 -13
  190. package/dist/esm/testing/cypher-assert.js.map +1 -1
  191. package/dist/esm/testing/neo4j-record-factory.d.ts +29 -2
  192. package/dist/esm/testing/neo4j-record-factory.d.ts.map +1 -1
  193. package/dist/esm/testing/neo4j-record-factory.js +93 -3
  194. package/dist/esm/testing/neo4j-record-factory.js.map +1 -1
  195. package/dist/esm/utils/cypher-field-projection.d.ts +31 -3
  196. package/dist/esm/utils/cypher-field-projection.d.ts.map +1 -1
  197. package/dist/esm/utils/cypher-field-projection.js +58 -7
  198. package/dist/esm/utils/cypher-field-projection.js.map +1 -1
  199. package/dist/esm/utils/write-coercion.d.ts +18 -0
  200. package/dist/esm/utils/write-coercion.d.ts.map +1 -1
  201. package/dist/esm/utils/write-coercion.js +73 -1
  202. package/dist/esm/utils/write-coercion.js.map +1 -1
  203. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,131 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.5.0 (2026-10-18) — breaking fixes from the bug scan
4
+
5
+ > **Minor release. Every item below changes behaviour that callers may depend on.** Each change fixes a confirmed bug, and the old behaviour was wrong (see each entry). The non-breaking fixes from the same scan shipped in **2.4.8**.
6
+
7
+ ### Upgrade checklist
8
+
9
+ Use this to check whether you're affected. If none of these apply, 2.5.0 is a drop-in upgrade.
10
+
11
+ | If you… | Then… |
12
+ |---|---|
13
+ | use `InterfaceModel` | it now finds implementor nodes **without** the interface label, so result counts can grow (see below) |
14
+ | put arguments or aliases in `selectionSet` strings (`posts(where: …)`, `r: rank`) | they now take effect, and unsupported ones throw |
15
+ | pass non-`@id`/`@unique` keys in `upsert` `where` | they now throw |
16
+ | rely on `createMany({ skipDuplicates: true })` counting the skipped duplicates | it now returns the created count |
17
+ | write keys that aren't in the schema | they now throw `Unknown field …` |
18
+ | pass relationship fields in a later `createMany` item | they now throw |
19
+ | filter list fields with element-typed operators (`tags: 'x'`, `tags_CONTAINS`) | those no longer compile (they never matched) |
20
+ | use `CypherAssert` or `Neo4jRecordFactory.dateTime` in tests | stricter assertions may expose tests that passed by accident |
21
+ | use `cloneSubgraph` / `deleteSubgraph` | traversal is now outgoing-only by default, and the root is matched by label |
22
+
23
+ ### Reads
24
+
25
+ - **`InterfaceModel` matches implementors, not the interface label.** `find`, `count` and `aggregate` used to `MATCH (n:<Interface>)`. Nodes created with `Model.create` never carry that label, so these methods found nothing for nodes the OGM itself wrote. They now `MATCH (n:(A|B|…))` over the implementors, as `@neo4j/graphql` does. Per-member policies, `__typename` and the `labels` filter work as before.
26
+ - **What to do:** nodes carrying *only* the interface label, with no implementor label, are no longer returned; give them their concrete label. Recheck counts and pagination that depended on the old, smaller result. Listing the interface in `@node(labels: [...])` still works but is no longer needed.
27
+ - **`selectionSet` arguments and aliases take effect.** They were silently ignored, so `posts(where: …)` and `posts(options: { sort, limit, offset })` returned every related node, `r: rank` came back as `rank`, and two aliases of one field collapsed into one key. Now relationship fields honour `where` (with the target `read` policy applied) and `options` (`sort` needs APOC; `limit` and `offset` are bound parameters). Connection fields honour `where`, and aliases name the output keys.
28
+ - **Now throw `OGMError`:** any other argument, an unknown option or sort field, `options` on a singular relationship, GraphQL `$variables`, and arguments on a field reached through a union-typed parent.
29
+ - **What to do:** search for `selectionSet` strings with arguments or aliases. Their results now match what they ask for: filtered, paged and re-keyed.
30
+
31
+ ### Writes
32
+
33
+ - **`upsert` `where` accepts only `@id` / `@unique` fields.** A plain key used to join the `MERGE` pattern, so `where: { email, name: 'Bob' }` against an existing `{ email, name: 'Alice' }` created a **duplicate** with the same email. An operator key (`name_CONTAINS`) was silently dropped, which widened the match so the **wrong row** could be updated. These now throw `OGMError`: a plain, operator, logical or unknown key, and a `null` value. At least one unique key is required.
34
+ - **What to do:** keep only unique keys in `where` and move the other fields into `create` / `update`. For a non-unique match, use `findFirst()` followed by `create()` / `update()`.
35
+ - **Unknown keys in write input throw.** A key that is neither a property nor a relationship of the type (a typo like `nmae`) was written as a stray property, and the intended field was left unchanged. It now throws `Unknown field …`, in `create`, `update`, `updateMany`, `createMany`, `upsert`, and nested creates and updates at any depth.
36
+ - **What to do:** declare every property you write in the schema.
37
+ - **`createMany({ skipDuplicates: true })` returns the number of nodes created.** It used to include the duplicates it skipped. It now matches Prisma. Calls without `skipDuplicates` are unchanged.
38
+ - **`createMany` rejects relationship fields in every item.** Only `data[0]` used to be checked; later items were created without their relationship input. The error names the item, e.g. `"tags" in data[1]`. Use `create()` for nested relationship writes.
39
+
40
+ ### Generated types
41
+
42
+ - **List-scalar where operators are typed as lists (compile-time only).** For `tags: [String!]`, equality and `_NOT` take `Array<T>`, and `_IN` / `_NOT_IN` take `Array<Array<T>>`. String and comparison operators are no longer generated for list fields. The old element-typed forms (`{ tags: 'x' }`, `tags_CONTAINS`) compiled but **never matched**, while the working form `{ tags: ['x', 'y'] }` didn't compile. Runtime behaviour is unchanged.
43
+
44
+ ### Testing helpers
45
+
46
+ - **`CypherAssert.assertContainsClause` looks for the pattern inside the clause.** The clause and the pattern were checked independently, so `assertContainsClause(q, 'WHERE', 'n { .name }')` passed when the pattern was in the `RETURN`. `'SET'` also matched `$offset`. Keywords now match whole words, outside strings, names and comments, and the pattern must sit within that clause.
47
+ - **`CypherAssert.normalize` and `assertStructurallyEqual` keep string literals and backticked names verbatim.** `'a b'` used to equal `'a b'`, and a `$name` inside a string was renamed as a parameter.
48
+ - **`Neo4jRecordFactory.dateTime` reads an offset-less string as UTC**, as Neo4j does. It used to use the machine's local zone, so results differed between a laptop and CI. A string that isn't a date now throws a `TypeError`.
49
+
50
+ Assertions that only passed by accident will now fail.
51
+
52
+ ### Subgraph operations (APOC)
53
+
54
+ - **Owned relationships are followed outgoing by default.** A bare type was expanded in **both** directions. With two books sharing a chapter, `deleteSubgraph(bookA)` climbed back up the shared chapter and **deleted book B**, `deleteSubgraph(chapter)` deleted its parent book, and `cloneSubgraph` pulled in the other parents. Now a bare `'TYPE'` is outgoing, `'<TYPE'` is incoming, and `'<TYPE>'` is both directions (the old behaviour).
55
+ - **What to do:** mark relationships you traverse backwards as `'<TYPE'` or `'<TYPE>'`.
56
+ - **The root is matched by label, and owned nodes without an `id` are refused.** The root was matched by `id` alone, so any node with that id became a root, found by scanning every node. It's now `(:rootLabel {id})`. The new optional `SubgraphConfig.rootLabel` defaults to `ownedLabels[0]`. `cloneSubgraph` now aborts before writing anything if an owned node has no `id` or shares one with another owned node; previously those nodes collapsed onto one mapping entry.
57
+ - **What to do:** set `rootLabel` if your root isn't of the first owned label.
58
+
59
+ Every entry above has a regression test, live wherever behaviour depends on the server. The subgraph changes are verified against Neo4j 5.26 with APOC 5.26.
60
+
61
+ ## 2.4.8 (2026-10-18) — bug-scan fixes across reads, writes, codegen, CLI and testing helpers
62
+
63
+ > **A large patch release. Upgrading is safe, with no breaking changes.** Most of these bugs were found by a third, multi-area bug scan. Each fix was reproduced on Neo4j 5.26 before it was fixed, and is pinned by a regression test, live wherever behaviour depends on the server. The breaking fixes from the same scan ship separately in **2.5.0**. Cypher for queries the fixes don't touch is unchanged; the policy byte-identical suite pins this.
64
+
65
+ ### Reads
66
+
67
+ - **`_SINGLE` and `Connection_SINGLE` filters work.** Every `_SINGLE` form failed on Neo4j with `Invalid input 'WHERE'`, because of an extra parenthesis around the pattern comprehension. That covered relationship, interface, union and connection filters. They now compile to `size([pattern WHERE … | x]) = 1`. The target type's `read` policy stays inside the comprehension, so `_SINGLE` counts only nodes the caller can see.
68
+ - **Relationship-index fulltext returns each node once.** A search on a relationship index returned a node once per matching relationship, which inflated `count` and made `limit` pages come up short. Results are now grouped per node with the best score. This also works inside `AND`, `OR` and `NOT`.
69
+ - **Connection `totalCount` is returned.** It was declared on connection types but always came back `undefined`. It is now the size of the same filtered comprehension `edges` uses, so it honours the connection `where` and the target `read` policy. Selecting only `totalCount` returns `{ totalCount }`, and the generated connection `SelectFields` now offer it.
70
+ - **Implementer `@cypher` fields resolve in inline fragments.** `InterfaceModel.find({ selectionSet: '{ id ... on Doc { dcount } }' })` used to return `dcount: null`. Fragment fields now project per type: `CASE WHEN n:Doc THEN … END`.
71
+ - **Typed `select` keeps scalars whose names end in `Connection`.** A scalar such as `internetConnection: String` was silently dropped. A `*Connection` key is now treated as a connection only when its base name is a relationship.
72
+ - **BigInt, DateTime and list where filters match.** These used to silently match nothing:
73
+ - a BigInt filter given a string (its generated input type) or a `bigint`
74
+ - a DateTime filter given a JS `Date`
75
+ - equality, `_NOT` or `_IN` on a temporal **list** field
76
+
77
+ Values are now bound in the stored representation. Case-insensitive mode on a list field used to emit `toLower(list)`, a Cypher type error. It now lowercases element-wise for equality and `_NOT`, and leaves `_IN` case-sensitive.
78
+ - **Nested relationship `orderBy` sorts.** `select: { friends: { select: { id: true }, orderBy: { rank: 'DESC' } } }` sorted maps that didn't contain the sort key. It now sorts on injected keys, as connection `orderBy` does. This needs APOC and is verified by the emitted Cypher's shape.
79
+ - **Point `srid` is a number** on the driver's default lossless integers. It used to come back as `{ low, high }`.
80
+ - **Clear errors instead of wrong results:**
81
+ - Selecting an **aggregating** `@cypher` field (e.g. `RETURN count(p)`) **through a relationship** now throws an `OGMError` naming the field. Neo4j 5 rejects aggregation inside a projection, and no rewrite parses. Top-level selection still works.
82
+ - `aggregate` on a `@cypher` field now throws instead of returning null min/max/average and a sum of 0.
83
+
84
+ ### Writes
85
+
86
+ - **Nested writes on interface relationships run.** A nested `connect`, `create`, `update`, `disconnect` or `delete` on an interface-typed relationship was skipped without an error. It now runs, keyed by implementer (`pets: { Dog: { create: [...] } }`) or flat. A flat nested create names its implementer: `node: { Dog: {...} }`. Each implementer's policies apply. Inputs that can't be resolved now throw `OGMError` instead of being ignored.
87
+ - **Top-level `connect` and `disconnect` work on interface and union relationships.** They matched the abstract type's label, which no node carries, so nothing changed. Per-member union input was also never unwrapped. Both now match the members' own labels and apply each member's `read` policy. A blanket disconnect keyed by one union member now unlinks only that member, not every member.
88
+ - **Array `connect` with a bare-property `where` links.** `connect: { posts: [{ where: { title: 'a' } }] }`, with no `node:` wrapper, used to link nothing.
89
+ - **A connect nested in a connect item, and a disconnect nested in a disconnect item, run.** They used to be dropped, at the top level, inside `create` and inside nested `update`. Every level applies its target type's `read` policy. Nesting is capped at 5 levels.
90
+ - **`upsert` rejects relationship fields** in `create` / `update` with an `OGMError`. They used to be ignored, so their nested ops never ran.
91
+
92
+ ### Schema and generated types
93
+
94
+ - **`@relationship(direction:)` is validated.** A direction other than exactly `IN` or `OUT`, such as `"out"`, was accepted. Reads treated it as OUT and writes as IN, so edges were written reversed and couldn't be read back. It's now a schema error that names the field.
95
+ - **List `@default` values work.** `@default(value: [RED, GREEN])` used to be ignored. They're now stored with native element types. A list default on a non-list field is a schema error.
96
+ - **`@declareRelationship` interface relationships work.** They parsed with an empty relationship type, so every operator on them threw `Invalid identifier ""`. The type, direction and properties now come from the implementors, and implementors that disagree are a schema error.
97
+ - **Generated types match the runtime:**
98
+ - scalar `@cypher` fields are selectable in `<T>SelectFields`
99
+ - required fields with `@default` are optional in `CreateInput`
100
+ - `IdAggregateSelection` and `StringAggregateSelection` are `{ min, max }`, not `{ shortest, longest }`
101
+ - interfaces are no longer in `ModelMap`, where `ogm.model('<Interface>')` type-checked but always threw
102
+ - unsupported `<rel>Aggregate` fields and where keys are no longer generated; the old aggregation types remain, deprecated
103
+ - **Colliding generated type names fail at generation time.** For example, `User.postComments` and `UserPost.comments` both produce `UserPostCommentsConnectionWhere`, which used to make the generated file fail to compile. Generation now fails with an error that lists each duplicate and its sources.
104
+
105
+ ### CLI and execution
106
+
107
+ - **`grafeo db push` reports changed index definitions.** Changing a fulltext index's fields, or a vector index's dimensions or similarity, used to print "No changes". Push now reports a **changed** bucket saying the schema is NOT in sync. It recreates those indexes only under `--force-drop`, with the usual consent rules.
108
+ - **`grafeo db push` output is accurate:**
109
+ - It no longer claims "created X" when an equivalent constraint already exists under another name; that one is reported as `X covered by <name>`.
110
+ - A conflicting RANGE index is reported instead of crashing the push.
111
+ - In-sync constraints are no longer listed as "Unmanaged".
112
+ - **`grafeo db seed` honours `--database`, `database.database` and `NEO4J_DATABASE`.** It used to seed the default database regardless. The new optional **`OGMConfig.database`** sends every session the OGM opens to that database. Without it, nothing changes.
113
+ - **Transaction audit metadata is no longer dropped silently.** Driver 5.x transactions have no `setMetaData`, so policy audit metadata for calls inside a transaction, including `unsafe.bypassPolicies` audits, never reached the server. Per-call metadata still can't be attached to an open transaction, but each such call now logs a `warn`. The new `ogm.$transaction(fn, { metadata })` attaches metadata when the transaction begins, and its `TransactionOptions` type is exported.
114
+ - **`Executor.debug = true` no longer breaks queries with `bigint` params.** This includes `$queryRaw` and `$executeRaw`.
115
+
116
+ ### Testing helpers
117
+
118
+ - **`Neo4jRecordFactory.dateTime` keeps the offset, nanoseconds and zone id.** It converted values to UTC and cut the fraction to milliseconds. It now matches what the real driver returns.
119
+ - **`CypherAssert.assertParams` ignores key order and compares bigints.** A bigint param used to throw.
120
+ - **`Neo4jRecordFactory.node` takes `{ identity?, elementId? }`**, so mock nodes can have distinct ids.
121
+
122
+ ### Docs
123
+
124
+ - `@id` does **not** create a unique constraint; only `@unique` does. Use `@id @unique` to have the database enforce uniqueness.
125
+ - The README Interfaces example is now valid GraphQL (directives go after `implements`).
126
+ - The Testing Utilities section documents the real API.
127
+ - The directive table covers `@declareRelationship`, the `IN`/`OUT` rule and list defaults.
128
+
3
129
  ## 2.4.7 (2026-10-18) — `db push --force-drop` only drops constraints grafeo created
4
130
 
5
131
  ### 🐛 `--force-drop` could drop constraints on labels grafeo doesn't manage
package/README.md CHANGED
@@ -309,7 +309,7 @@ npx grafeo generate --verify # CI gate: exit 1 if the file on disk is stale
309
309
 
310
310
  ### `grafeo db push`
311
311
 
312
- Synchronizes the **constraints and indexes** your SDL declares (`@id`/`@unique` → unique constraints, `@fulltext` → fulltext indexes, `@vector` → vector indexes) with the live database. Constraints and indexes only — **not data**. (`db push` is deliberately scoped; use a dedicated migration tool for data.)
312
+ Synchronizes the **constraints and indexes** your SDL declares (`@unique` → unique constraints, `@fulltext` → fulltext indexes, `@vector` → vector indexes) with the live database. Constraints and indexes only — **not data**. (`db push` is deliberately scoped; use a dedicated migration tool for data.) `@id` alone creates **no** constraint: it only generates a UUID on create. Add `@unique` (`id: ID! @id @unique`) to have the database enforce uniqueness.
313
313
 
314
314
  ```bash
315
315
  npx grafeo db push --dry-run # preview the plan — DO THIS FIRST
@@ -317,19 +317,22 @@ npx grafeo db push # apply (additive: creates only what's mis
317
317
  npx grafeo db push --force-drop --yes # also drop orphaned grafeo-managed items
318
318
  ```
319
319
 
320
- `db push` introspects `SHOW CONSTRAINTS` / `SHOW INDEXES`, diffs against your SDL, and prints a plan in four buckets:
320
+ `db push` introspects `SHOW CONSTRAINTS` / `SHOW INDEXES`, diffs against your SDL, and prints a plan in five buckets:
321
321
 
322
322
  - **create** — declared in SDL, missing in the database
323
- - **in sync** — already present
323
+ - **in sync** — already present with the declared definition. A unique constraint is also in sync when an equivalent constraint (same label and property) exists under another name, such as one a DBA created; push prints `X covered by <name>` instead of creating it. Before v2.4.8 push printed "created X" on every run while Neo4j created nothing.
324
+ - **changed** — an index present under the SDL name whose definition differs (fulltext fields or label, vector dimensions or similarity, index type). The output says the schema is **NOT in sync**; the index is **kept as-is** unless you pass `--force-drop`, which drops and recreates it under the same consent rules as orphans. Before v2.4.8 indexes were matched by name only, so a changed definition was reported as in sync and never applied.
324
325
  - **orphans** — grafeo-named (`{Label}_{prop}_unique`) constraints no longer in your SDL; **reported and kept** unless you pass `--force-drop`
325
326
  - **unmanaged** — constraints/indexes that don't follow grafeo's naming convention (hand-created DBA indexes, etc.); **never touched, never counted as drift**
326
327
 
327
- It is **additive by default** and **idempotent** — a second run with no schema change is a no-op. Dropping orphans is destructive and gated: `--force-drop` lists the exact drops (shown in `--dry-run` too) and requires interactive confirmation, or `--yes` in non-interactive sessions like CI.
328
+ If a plain RANGE index already covers a property you mark `@unique`, Neo4j refuses the constraint until that index is dropped. Push reports this as a warning (the index isn't grafeo's, so it is never dropped) and applies the rest of the plan; before v2.4.8 the push crashed with exit 1. "created X" is printed only when Neo4j reports that something was added.
329
+
330
+ It is **additive by default** and **idempotent** — a second run with no schema change is a no-op. Dropping orphans and recreating changed indexes is destructive and gated: `--force-drop` lists the exact drops (shown in `--dry-run` too) and requires interactive confirmation, or `--yes` in non-interactive sessions like CI.
328
331
 
329
332
  | Flag | Description |
330
333
  |---|---|
331
334
  | `--dry-run` | Print the full plan (including the exact Cypher, drops included) and exit without writing |
332
- | `--force-drop` | Permit dropping orphaned grafeo-managed constraints/indexes |
335
+ | `--force-drop` | Permit dropping orphaned grafeo-managed constraints/indexes, and dropping + recreating indexes whose definition changed |
333
336
  | `--yes` | Confirm destructive drops non-interactively (required with `--force-drop` in CI) |
334
337
  | `--schema <path>`, `--uri`, `--username`, `--database` | Schema path and connection overrides |
335
338
 
@@ -358,7 +361,7 @@ export default async function seed(ogm: OGM) {
358
361
  npx grafeo db seed
359
362
  ```
360
363
 
361
- grafeo does not enforce idempotency — use `upsert` so re-running the seed is safe. Connection and schema resolve exactly as for `db push`.
364
+ grafeo does not enforce idempotency — use `upsert` so re-running the seed is safe. Connection and schema resolve exactly as for `db push`, including the database (`--database` > `database.database` > `NEO4J_DATABASE`). Before v2.4.8 `db seed` ignored the database setting and always seeded the server's default database.
362
365
 
363
366
  ---
364
367
 
@@ -394,13 +397,14 @@ grafeo-ogm uses standard GraphQL SDL with Neo4j directives to define your graph
394
397
 
395
398
  | Directive | Target | Description |
396
399
  |---|---|---|
397
- | `@node` | Type | Marks a type as a Neo4j node. Optional `labels` argument for multi-label nodes |
398
- | `@relationship` | Field | Defines a relationship with `type`, `direction` (`IN`/`OUT`), and optional `properties` |
400
+ | `@node` | Type | Marks a type as a Neo4j node. Optional `labels` argument for multi-label nodes (implementors of an interface no longer need to include the interface name since v2.5.0, see [Interfaces](#interfaces)) |
401
+ | `@relationship` | Field | Defines a relationship with `type`, `direction` (exactly `IN` or `OUT`; anything else is a schema error), and optional `properties` |
402
+ | `@declareRelationship` | Interface field | Declares a relationship on an interface. Its `type`, `direction` and `properties` come from the implementors' `@relationship`, which must all agree |
399
403
  | `@relationshipProperties` | Type | Marks a type as relationship properties (edge data) |
400
- | `@id` | Field | Auto-generates a UUID on create |
404
+ | `@id` | Field | Auto-generates a UUID on create (creates no constraint — add `@unique` for one) |
401
405
  | `@unique` | Field | Creates a uniqueness constraint in Neo4j |
402
406
  | `@cypher` | Field | Computed field resolved via a custom Cypher statement. Scalar return types are resolved at runtime in `select` / `selectionSet`, `where`, and `options.sort` (see [Computed Fields with @cypher](#computed-fields-with-cypher)); node/interface return types remain projection-only |
403
- | `@default` | Field | Sets a default value on create |
407
+ | `@default` | Field | Sets a default `value` on create (scalar, enum, or a list of them for list fields). A required field with a default is optional in the generated `CreateInput` |
404
408
  | `@fulltext` | Type | Defines fulltext search indexes with `name` and `fields` |
405
409
  | `@vector` | Type | Registers one or more Neo4j vector indexes on a node (see [Vector Search](#vector-search)) |
406
410
 
@@ -450,26 +454,38 @@ type HasReview @relationshipProperties {
450
454
  ### Interfaces
451
455
 
452
456
  ```graphql
453
- interface Entity @node {
454
- id: ID! @id
457
+ interface Entity {
458
+ id: ID!
455
459
  name: String!
460
+ resources: [Resource!]! @declareRelationship
456
461
  }
457
462
 
458
- type User @node implements Entity {
463
+ # Directives go after `implements` — `type User @node implements Entity` is
464
+ # not valid GraphQL.
465
+ type User implements Entity @node(labels: ["User", "Entity"]) {
459
466
  id: ID! @id
460
467
  name: String!
461
468
  email: String!
469
+ resources: [Resource!]! @relationship(type: "OWNS", direction: OUT)
462
470
  }
463
471
 
464
- type Organization @node implements Entity {
472
+ type Organization implements Entity @node(labels: ["Organization", "Entity"]) {
465
473
  id: ID! @id
466
474
  name: String!
467
475
  website: String
476
+ resources: [Resource!]! @relationship(type: "OWNS", direction: OUT)
477
+ }
478
+
479
+ type Resource @node {
480
+ id: ID! @id
468
481
  }
469
482
  ```
470
483
 
471
484
  Interface types can be queried polymorphically via `ogm.interfaceModel('Entity')`. Results include `__typename` to discriminate concrete types.
472
485
 
486
+ - The interface model matches nodes by their **implementors' labels** (`MATCH (n:(User|Organization))`), so a node created with `ogm.model('User').create(...)` appears in interface queries with no extra labels. Since v2.5.0 an implementor does **not** need to carry the interface name; before v2.5.0 it had to list it in `@node(labels: [...])`, which still works.
487
+ - A `@declareRelationship` field takes its relationship `type`, `direction` and `properties` from the implementors. They must all declare the same ones; if they disagree, or an implementor has no `@relationship` on that field, `parseSchema` throws.
488
+
473
489
  ### Enums
474
490
 
475
491
  ```graphql
@@ -571,6 +587,15 @@ const book = await Book.findUniqueOrThrow({
571
587
  | `_NOT_ENDS_WITH` | `NOT ENDS WITH` | `{ name_NOT_ENDS_WITH: 'x' }` |
572
588
  | `_MATCHES` | `=~` (regex) | `{ name_MATCHES: '^A.*' }` |
573
589
 
590
+ **List fields** (`tags: [String!]`) are compared as a whole list, so only these operators exist for them:
591
+
592
+ | Operator | Matches when | Example |
593
+ |---|---|---|
594
+ | *(equality)* | the stored list equals the given list | `{ tags: ['a', 'b'] }` |
595
+ | `_NOT` | the stored list differs from the given list | `{ tags_NOT: ['a'] }` |
596
+ | `_IN` | the stored list equals one of the given lists | `{ tags_IN: [['a'], ['b', 'c']] }` |
597
+ | `_NOT_IN` | the stored list equals none of the given lists | `{ tags_NOT_IN: [['a']] }` |
598
+
574
599
  **Relationship quantifiers:**
575
600
 
576
601
  | Operator | Meaning | Example |
@@ -1028,6 +1053,10 @@ The compiler reads `OR`, then `AND`, then `NOT`, and stops at the first one it f
1028
1053
 
1029
1054
  Clone or delete entire subgraphs using APOC procedures. Requires the APOC plugin installed in Neo4j.
1030
1055
 
1056
+ The traversal starts at the root and follows `ownedRelationships` into nodes carrying one of `ownedLabels`. A bare type such as `'HAS_CHAPTER'` is followed **outgoing only**; write `'<CHILD_OF'` to follow a relationship incoming, or `'<LINKED>'` to follow it both ways. Before v2.5.0 a bare type was followed both ways, so deleting one book could climb from a chapter it shared with another book up to that book and delete it too; add `<…>` to keep the old behavior for a type.
1057
+
1058
+ The root is matched as `(:rootLabel {id: rootId})`, with `rootLabel` defaulting to the first entry of `ownedLabels` (v2.5.0; before, a node of any label with that id was a root). Set `rootLabel` when the root is not of that label — a delete whose root isn't found deletes nothing and returns `deletedCount: 0`. `cloneSubgraph` maps every owned node by its `id`, so it refuses, before cloning anything, a subgraph in which an owned node has no `id` or shares one with another owned node.
1059
+
1031
1060
  ```typescript
1032
1061
  import { cloneSubgraph, deleteSubgraph } from 'grafeo-ogm';
1033
1062
 
@@ -1053,12 +1082,13 @@ const cloneResult = await cloneSubgraph(
1053
1082
  // v1.13.0+ — post-process only the nodes of one label, no broad-IN filtering:
1054
1083
  const chapterIds = cloneResult.nodesByLabel.get('Chapter') ?? [];
1055
1084
 
1056
- // Delete a subgraph
1085
+ // Delete a subgraph rooted at a Chapter: its sections, not its Book
1057
1086
  const deleteResult = await deleteSubgraph(
1058
- 'root-node-id',
1087
+ 'chapter-node-id',
1059
1088
  {
1060
- ownedLabels: ['Book', 'Chapter'],
1061
- ownedRelationships: ['HAS_CHAPTER'],
1089
+ rootLabel: 'Chapter', // v2.5.0+ — defaults to ownedLabels[0]
1090
+ ownedLabels: ['Chapter', 'Section'],
1091
+ ownedRelationships: ['HAS_SECTION'], // outgoing; '<TYPE' incoming, '<TYPE>' both
1062
1092
  maxLevel: 5,
1063
1093
  referenceRelationships: [],
1064
1094
  },
@@ -1355,6 +1385,8 @@ When `policies` is configured, every OGM-emitted query attaches transaction meta
1355
1385
 
1356
1386
  Disable via `policyDefaults: { auditMetadata: false }`. The fingerprint is intentionally key-only — no ctx values are leaked.
1357
1387
 
1388
+ > **Inside a transaction the per-call audit metadata does not reach the server.** neo4j-driver accepts transaction metadata only when a transaction begins, so it is attached on the OGM's own auto-commit sessions and on a caller-supplied `session`, but not to a call that runs in an already-open transaction (`$transaction`, an explicit `beginTransaction()` tx, or an `executeRead`/`executeWrite` managed tx). Since v2.4.8 each such call logs a `warn` instead of dropping the metadata silently; before v2.4.8 the docs claimed it was forwarded. To tag the transaction itself, pass metadata when it opens: `ogm.$transaction(fn, { metadata })`, `session.beginTransaction({ metadata })`, or `session.executeWrite(fn, { metadata })`.
1389
+
1358
1390
  `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`.
1359
1391
 
1360
1392
  #### Explaining policy decisions
@@ -1446,8 +1478,19 @@ const [books, authors] = await ogm.$transaction([
1446
1478
  (ctx) => Book.find({ select: { id: true } }, { context: ctx }),
1447
1479
  (ctx) => Author.find({ select: { id: true } }, { context: ctx }),
1448
1480
  ]);
1481
+
1482
+ // Transaction metadata (v2.4.8) — sent to the server when the tx begins,
1483
+ // visible in SHOW TRANSACTIONS and the query log
1484
+ await ogm.$transaction(
1485
+ async (ctx) => {
1486
+ /* ... */
1487
+ },
1488
+ { metadata: { job: 'nightly-import' } },
1489
+ );
1449
1490
  ```
1450
1491
 
1492
+ Per-call policy audit metadata cannot be attached once a transaction is open; see [Audit metadata](#audit-metadata).
1493
+
1451
1494
  ### Interface Models
1452
1495
 
1453
1496
  Query across all types implementing a shared interface:
@@ -1731,25 +1774,42 @@ import {
1731
1774
 
1732
1775
  ### CypherAssert
1733
1776
 
1734
- Assert Cypher queries contain expected patterns:
1777
+ Assert on compiled Cypher and its parameters:
1735
1778
 
1736
1779
  ```typescript
1737
- // Check that a Cypher string contains a fragment
1738
- CypherAssert.assertContains(cypher, 'MATCH (n:`Book`)');
1780
+ // Equal after collapsing whitespace and renaming params to $p0, $p1, …
1781
+ CypherAssert.assertStructurallyEqual(cypher, 'MATCH (n:`Book`) RETURN n');
1739
1782
 
1740
- // Check parameter values
1783
+ // A clause is present and contains a fragment
1784
+ CypherAssert.assertContainsClause(cypher, 'WHERE', 'n.price');
1785
+
1786
+ // A fragment is absent
1787
+ CypherAssert.assertNotContainsClause(cypher, 'DELETE');
1788
+
1789
+ // Only the listed keys are checked. Object key order doesn't matter;
1790
+ // bigints compare by value, and a neo4j Integer never equals a plain number.
1741
1791
  CypherAssert.assertParams(params, { param0: 'Graph Databases' });
1742
1792
  ```
1743
1793
 
1744
1794
  ### Neo4jRecordFactory
1745
1795
 
1746
- Build mock Neo4j records for unit tests without a running database:
1796
+ Build mock Neo4j values for unit tests without a running database:
1747
1797
 
1748
1798
  ```typescript
1749
- const records = Neo4jRecordFactory.create([
1750
- { id: '1', title: 'Test Book', published: '2024-01-01' },
1751
- { id: '2', title: 'Another Book', published: '2024-06-15' },
1752
- ]);
1799
+ const record = Neo4jRecordFactory.create({ title: 'Test Book', count: 2 });
1800
+ const count = Neo4jRecordFactory.integer(42);
1801
+
1802
+ // Same fields and toString() as the driver's datetime($iso): the offset,
1803
+ // all nine fractional digits and an [Area/City] zone id are kept (v2.4.8).
1804
+ const dt = Neo4jRecordFactory.dateTime('2024-01-01T10:00:00.123456789+02:00');
1805
+
1806
+ // Properties are stored as given; use integer() where the driver would
1807
+ // return an Integer. The third argument (v2.4.8) sets identity / elementId.
1808
+ const node = Neo4jRecordFactory.node(
1809
+ ['Book'],
1810
+ { title: 'Mock', pages: Neo4jRecordFactory.integer(300) },
1811
+ { identity: 1, elementId: '4:test:1' },
1812
+ );
1753
1813
  ```
1754
1814
 
1755
1815
  ### SelectionSetFactory
@@ -1757,11 +1817,11 @@ const records = Neo4jRecordFactory.create([
1757
1817
  Build selection sets programmatically:
1758
1818
 
1759
1819
  ```typescript
1760
- const selectionSet = SelectionSetFactory.build(`{
1761
- id
1762
- title
1763
- author { name }
1764
- }`);
1820
+ SelectionSetFactory.gql(['id', 'title', { author: ['name'] }]);
1821
+ // => '{ id title author { name } }'
1822
+
1823
+ SelectionSetFactory.select(['id', 'title', { author: ['name'] }]);
1824
+ // => { id: true, title: true, author: { select: { name: true } } }
1765
1825
  ```
1766
1826
 
1767
1827
  ---
@@ -1932,7 +1992,7 @@ Yes — `ogm.$transaction(async (ctx) => { ... })` runs multiple operations atom
1932
1992
 
1933
1993
  ### Does it support multi-database / multi-tenant Neo4j setups?
1934
1994
 
1935
- Yes — pass a `database` option per query via the `ExecutionContext`, or instantiate one OGM per database. Multi-label nodes (`@node(labels: [...])`) handle a different multi-tenant pattern where tenants share a graph but have isolated label sets.
1995
+ Yes — instantiate one OGM per database with `new OGM({ typeDefs, driver, database: 'tenant-a' })` (v2.4.8); every session the OGM opens then targets that database. Alternatively, open the session or transaction yourself (`driver.session({ database })`) and pass it via `{ context: { session } }` / `{ context: { transaction } }`. (Before v2.4.8 this answer claimed a per-query `database` option on `ExecutionContext`; there is none.) Multi-label nodes (`@node(labels: [...])`) handle a different multi-tenant pattern where tenants share a graph but have isolated label sets.
1936
1996
 
1937
1997
  ### What's the performance overhead vs. raw Cypher?
1938
1998
 
@@ -1954,12 +2014,12 @@ Open an issue on [GitHub](https://github.com/neomodular/grafeo-ogm/issues). For
1954
2014
 
1955
2015
  | Method | Description |
1956
2016
  |---|---|
1957
- | `new OGM(config)` | Create an OGM instance with `{ typeDefs, driver, logger?, features? }` |
2017
+ | `new OGM(config)` | Create an OGM instance with `{ typeDefs, driver, database?, logger?, features? }` |
1958
2018
  | `model(name)` | Get a typed Model for a node type |
1959
2019
  | `interfaceModel(name)` | Get a typed InterfaceModel for an interface type |
1960
2020
  | `$queryRaw(cypher, params?)` | Execute a read query and return mapped results |
1961
2021
  | `$executeRaw(cypher, params?)` | Execute a write operation and return affected counts |
1962
- | `$transaction(fn)` | Execute operations in an atomic transaction |
2022
+ | `$transaction(fn, options?)` | Execute operations in an atomic transaction; `options.metadata` is attached when it begins |
1963
2023
  | `assertIndexesAndConstraints(options?)` | Create fulltext indexes and uniqueness constraints |
1964
2024
  | `close()` | Clear caches and release references |
1965
2025
 
@@ -1 +1 @@
1
- {"version":3,"file":"db-push.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/db-push.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAkJtC,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAgE1E"}
1
+ {"version":3,"file":"db-push.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/db-push.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAyNtC,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAkF1E"}
@@ -76,9 +76,11 @@ function parseDbPushFlags(argv) {
76
76
  */
77
77
  function renderPlan(plan, io, willDrop) {
78
78
  if (plan.create.length === 0)
79
- if (plan.orphans.length === 0)
79
+ if (plan.orphans.length === 0 &&
80
+ plan.changed.length === 0 &&
81
+ plan.blocked.length === 0)
80
82
  // Only claim a clean "in sync" when there is genuinely nothing to do —
81
- // outstanding orphans mean the schema is not in sync.
83
+ // outstanding orphans or changed definitions mean it is not in sync.
82
84
  io.out('✓ No changes — constraints and indexes are in sync');
83
85
  else
84
86
  io.out('No constraints or indexes to create.');
@@ -89,6 +91,28 @@ function renderPlan(plan, io, willDrop) {
89
91
  io.out(` ${statement.cypher}`);
90
92
  }
91
93
  }
94
+ // v2.4.8 — an equivalent constraint under another name satisfies the SDL;
95
+ // say which one, instead of "creating" it on every run.
96
+ for (const cover of plan.covered)
97
+ io.out(` = ${cover.statement.name} covered by ${(0, display_1.safeDisplay)(cover.by)} (equivalent constraint)`);
98
+ // v2.4.8 — a changed definition used to be reported "in sync". Under
99
+ // --force-drop it is dropped and recreated; otherwise it is left alone
100
+ // and the output says plainly that the schema is NOT in sync.
101
+ if (plan.changed.length > 0) {
102
+ if (willDrop)
103
+ io.out(`Changed — ${plan.changed.length} index definition(s) differ from the SDL; eligible to recreate (--force-drop):`);
104
+ else
105
+ io.out(`Changed — ${plan.changed.length} index definition(s) differ from the SDL — schema is NOT in sync (kept as-is; use --force-drop to recreate):`);
106
+ for (const change of plan.changed) {
107
+ io.out(` ~ ${change.statement.name}`);
108
+ for (const diff of change.differences)
109
+ io.out(` ${diff}`);
110
+ if (willDrop) {
111
+ io.out(` ${change.dropCypher}`);
112
+ io.out(` ${change.statement.cypher}`);
113
+ }
114
+ }
115
+ }
92
116
  if (plan.orphans.length > 0)
93
117
  if (willDrop) {
94
118
  // "eligible to drop" — this is the plan; consent is resolved afterward.
@@ -112,7 +136,7 @@ async function resolveDropConsent(plan, io, yes) {
112
136
  if (io.interactive && io.confirm)
113
137
  // Strict `=== true`: only an explicit boolean true consents, so a
114
138
  // misbehaving embedder returning a truthy non-boolean can't authorize a drop.
115
- return (await io.confirm(`Drop the ${plan.orphans.length} item(s) listed above?`)) === true
139
+ return (await io.confirm(`Drop the ${plan.orphans.length + plan.changed.length} item(s) listed above?`)) === true
116
140
  ? 'granted'
117
141
  : 'declined';
118
142
  return 'blocked';
@@ -123,19 +147,51 @@ async function applyCreates(driver, database, plan, io) {
123
147
  const session = driver.session(database ? { database } : undefined);
124
148
  try {
125
149
  for (const statement of plan.create) {
126
- await session.run(statement.cypher);
127
- io.out(`✓ created ${statement.name}`);
150
+ let result;
151
+ try {
152
+ result = await session.run(statement.cypher);
153
+ }
154
+ catch (error) {
155
+ // v2.4.8 — an existing schema rule the plan could not see (a race,
156
+ // or a server that hides definition columns) used to crash the
157
+ // whole push with exit 1. Report it and carry on with the rest.
158
+ if (!isSchemaAlreadyExists(error))
159
+ throw error;
160
+ io.err(`warning: ${statement.name} not created — ${(0, display_1.safeDisplay)(error.message)}`);
161
+ continue;
162
+ }
163
+ // v2.4.8 — `IF NOT EXISTS` is a silent no-op when an equivalent rule
164
+ // exists; only claim "created" when Neo4j says something was added.
165
+ const updates = result?.summary?.counters?.updates?.();
166
+ if (updates &&
167
+ (updates.constraintsAdded ?? 0) + (updates.indexesAdded ?? 0) === 0)
168
+ io.out(`= ${statement.name} not created — Neo4j reports an equivalent constraint or index already exists`);
169
+ else
170
+ io.out(`✓ created ${statement.name}`);
128
171
  }
129
172
  }
130
173
  finally {
131
174
  await session.close();
132
175
  }
133
176
  }
177
+ /** `Neo.ClientError.Schema.*AlreadyExists` (index / constraint / equivalent rule). */
178
+ function isSchemaAlreadyExists(error) {
179
+ const code = error?.code;
180
+ return (typeof code === 'string' &&
181
+ /^Neo\.ClientError\.Schema\.\w*AlreadyExists$/.test(code));
182
+ }
134
183
  async function applyDrops(driver, database, plan, io) {
135
- if (plan.orphans.length === 0)
184
+ if (plan.orphans.length === 0 && plan.changed.length === 0)
136
185
  return;
137
186
  const session = driver.session(database ? { database } : undefined);
138
187
  try {
188
+ // Drop + create, in that order: `CREATE … IF NOT EXISTS` would be a
189
+ // no-op while the old definition still holds the name.
190
+ for (const change of plan.changed) {
191
+ await session.run(change.dropCypher);
192
+ await session.run(change.statement.cypher);
193
+ io.out(`✓ recreated ${change.statement.name}`);
194
+ }
139
195
  for (const orphan of plan.orphans) {
140
196
  await session.run(orphan.dropCypher);
141
197
  io.out(`✓ dropped ${(0, display_1.safeDisplay)(orphan.name)}`);
@@ -160,14 +216,24 @@ async function runDbPush(argv, io) {
160
216
  try {
161
217
  const live = await (0, introspect_1.fetchLiveSchema)(driver, connection.database);
162
218
  const plan = (0, push_planner_1.planSchemaSync)(schema, live, vectorConfig);
163
- const willDrop = forceDrop && plan.orphans.length > 0;
219
+ const willDrop = forceDrop && (plan.orphans.length > 0 || plan.changed.length > 0);
164
220
  renderPlan(plan, io, willDrop);
165
221
  // `@vector` indexes without dimensions are reported, never silently
166
222
  // skipped, and never block the rest of the plan.
223
+ // v2.4.8 — Neo4j refuses a unique constraint over a property a plain
224
+ // RANGE index already covers. Report it (the index is not grafeo's to
225
+ // drop) instead of letting the CREATE crash the whole push.
226
+ for (const block of plan.blocked)
227
+ io.err(`warning: ${block.statement.name} not created — index "${(0, display_1.safeDisplay)(block.by)}" ` +
228
+ `already covers the same label and property, and Neo4j will not add a ` +
229
+ `unique constraint until that index is dropped. Schema is NOT in sync.`);
167
230
  for (const needs of plan.needsConfig)
168
231
  io.err(`warning: vector index "${needs.indexName}" ` +
169
232
  `(${needs.typeName}.${needs.embeddingProperty}) skipped — set ` +
170
233
  `vectorIndexes['${needs.indexName}'].dimensions in grafeo.config.ts to create it`);
234
+ if (plan.changed.length > 0 && !forceDrop)
235
+ io.err(`warning: ${plan.changed.length} index definition(s) differ from the SDL and were NOT applied ` +
236
+ `(${plan.changed.map((c) => c.statement.name).join(', ')}) — re-run with --force-drop to drop and recreate them`);
171
237
  // Nothing to create and nothing to drop → no writes (idempotent re-run).
172
238
  if (plan.create.length === 0 && !willDrop)
173
239
  return 0;
@@ -190,7 +256,7 @@ async function runDbPush(argv, io) {
190
256
  if (consent === 'granted')
191
257
  await applyDrops(driver, connection.database, plan, io);
192
258
  else if (consent === 'declined')
193
- io.out('Drop declined — orphans kept.');
259
+ io.out('Drop declined — orphans and changed definitions kept.');
194
260
  return 0;
195
261
  }
196
262
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"db-push.js","sourceRoot":"","sources":["../../../../src/cli/commands/db-push.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"db-push.js","sourceRoot":"","sources":["../../../../src/cli/commands/db-push.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"db-seed.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/db-seed.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,KAAK,EAAgB,MAAM,UAAU,CAAC;AAwGpD,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CA+B1E"}
1
+ {"version":3,"file":"db-seed.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/db-seed.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,KAAK,EAAgB,MAAM,UAAU,CAAC;AAwGpD,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAwC1E"}
@@ -134,7 +134,16 @@ async function runDbSeed(argv, io) {
134
134
  // The OGM constructor parses the SDL synchronously and throws on an
135
135
  // invalid schema — it lives inside this try so the driver created above
136
136
  // is still closed in that case (cli-db-seed spec: close on any failure).
137
- const ogm = new ogm_1.OGM({ typeDefs, driver });
137
+ //
138
+ // v2.4.8 — target the resolved database (`--database` >
139
+ // `database.database` > NEO4J_DATABASE), as `db push` does. Pre-2.4.8
140
+ // the OGM had no database option and the seed always ran against the
141
+ // server's default database.
142
+ const ogm = new ogm_1.OGM({
143
+ typeDefs,
144
+ driver,
145
+ ...(connection.database ? { database: connection.database } : {}),
146
+ });
138
147
  await ogm.init();
139
148
  io.out(`running seed ${seedPath}`);
140
149
  try {