@vantreeseba/drizzle-graphql 13.1.1 → 14.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/README.md CHANGED
@@ -189,14 +189,16 @@ const { schema } = buildSchema(db, {
189
189
  - The table is still fully readable — this decides which operations it offers, not whether
190
190
  it is in the schema at all. For that, use `exclude` below
191
191
  - `nestedWrites` is the one flag that stays build-wide: its plans are computed once over
192
- the whole relation graph, and a nested write reaches a second table by definition
192
+ the whole relation graph, and a nested write reaches a second table by definition. What
193
+ it offers on each relation still follows the *target* table's flags — see
194
+ [Nested writes](#nested-writes)
193
195
  - `Mutation` is omitted when *no* table generates a mutation, and kept when one still does
194
196
  - Some operations are built out of others, and the generator knows which. A table that
195
197
  asks for `upsert` while its `insert` or `update` is off — an upsert is a second write
196
198
  path past the operation you turned off — warns on the console at build time, naming the
197
199
  tables, rather than leaving that to be discovered later. So does asking for `updateMany`
198
- without `update`, `groupBy` without `aggregates`, `countMutations` with neither plural
199
- write, or `nestedWrites` alongside a table whose own writes are off
200
+ without `update`, `groupBy` without `aggregates`, or `countMutations` with neither plural
201
+ write. `nestedWrites` notes the nested inputs it left out because their table's write is off
200
202
 
201
203
  ## Excluding tables and columns
202
204
 
@@ -1531,6 +1533,32 @@ mutation {
1531
1533
  - A `set` that carries only relation fields is a valid update: it changes what the row is
1532
1534
  attached to without rewriting the row
1533
1535
 
1536
+ ### The target table's own rules
1537
+
1538
+ A nested write is a write to another table, so it follows that table's rules, not just the
1539
+ root field's:
1540
+
1541
+ - **`scope`** narrows the rows `connect` / `disconnect` / `set` can reach, and
1542
+ **`contextValues`** stamp the rows a nested `create` inserts
1543
+ - **`features`**: each nested input is generated only when the table it writes has that
1544
+ write turned on. Whatever is left out is noted on the console at build time, and a
1545
+ relation with nothing left loses its field altogether
1546
+
1547
+ | Input | Needs, on the table it writes |
1548
+ | --- | --- |
1549
+ | `create` | the target's `insert` (and its `update` too on a to-one whose nullable key lives on the target, since attaching detaches the previous row) |
1550
+ | `connect` / `disconnect` / `set`, key on the target | the target's `update` |
1551
+ | `connect` / `disconnect`, key on the parent | nothing — only the parent's own row changes |
1552
+ | many-to-many `connect` | the junction's `insert` |
1553
+ | many-to-many `disconnect` | the junction's `delete` |
1554
+ | many-to-many `set` | the junction's `insert` and `delete` |
1555
+
1556
+ An excluded junction table takes its relation's inputs with it. A table whose writes are
1557
+ reserved for a hand-written mutation (`insert: false`) can therefore no longer be written
1558
+ by another table's nested `create` either
1559
+ - **`onWrite`**: the hooks of the table a nested statement writes run too — see
1560
+ [Nested writes and hooks](#nested-writes-and-hooks)
1561
+
1534
1562
  ### Many-to-many relations
1535
1563
 
1536
1564
  A relation declared with `.through()` is written by inserting and deleting rows of its
@@ -2161,6 +2189,7 @@ The payload:
2161
2189
  | `rows` | the post-write rows **as the database returned them**, before the output mapper — the selected columns plus the hook's [`columns`](#which-columns-rows-holds) |
2162
2190
  | `context`, `info` | the GraphQL context value and resolve info |
2163
2191
  | `tx` | the executor the mutation ran on |
2192
+ | `nested` | `{ parentTable, relation, op }` when a [nested write](#nested-writes-and-hooks) wrote the rows; `undefined` at the mutation field's own table |
2164
2193
 
2165
2194
  - **`tx` is the executor the mutation itself used** — one you supplied under
2166
2195
  `drizzleExecutorKey`, the request's auto-transaction, or, for a field that would otherwise
@@ -2170,11 +2199,58 @@ The payload:
2170
2199
  already does.
2171
2200
  - **`rows` is empty** at the `before` position, and on MySQL, whose mutations return no rows.
2172
2201
  The `before` position and `args` still work there.
2173
- - The hook fires for the **mutation field's own table**. Rows a nested `create` writes into
2174
- another table do not fire that table's hook.
2202
+ - With `nestedWrites`, the hooks of **every table a nested write reaches** fire too —
2203
+ below.
2175
2204
 
2176
2205
  A build with no `onWrite` opens exactly the transactions it did before.
2177
2206
 
2207
+ ### Nested writes and hooks
2208
+
2209
+ A nested `create` / `connect` / `disconnect` / `set` runs the `before` and `after` hooks of the
2210
+ table its statement writes, on the same `tx`, with `payload.nested` saying where it came from:
2211
+
2212
+ ```Typescript
2213
+ onWrite: {
2214
+ todos: {
2215
+ before: async ({ nested, args, tx }) => { /* runs ahead of the nested statement too */ },
2216
+ after: async ({ operation, rows, nested, tx }) => {
2217
+ // createProjectsSingle(values: { …, todos: { create: [ … ] } }) arrives here as
2218
+ // operation: 'insert', nested: { parentTable: 'projects', relation: 'todos', op: 'create' }
2219
+ for (const row of rows) await assertOwnsProject(tx, row.projectId)
2220
+ },
2221
+ columns: ['projectId'],
2222
+ },
2223
+ }
2224
+ ```
2225
+
2226
+ | Nested input | `table` | `operation` |
2227
+ | --- | --- | --- |
2228
+ | `create` | the target | `insert` |
2229
+ | `connect` / `disconnect` / `set`, key on the target | the target | `update` (the foreign key moved; nothing is deleted) |
2230
+ | `connect` / `disconnect`, key on the parent | — | no target hook: only the parent's row changes, and its own hook sees that |
2231
+ | many-to-many `connect` | the junction | `insert` |
2232
+ | many-to-many `disconnect` | the junction | `delete` |
2233
+ | many-to-many `set` | the junction | `delete` (the old links), then `insert` (the new ones) |
2234
+
2235
+ - **`nested`** is `{ parentTable, relation, op }` — the Drizzle schema key of the table whose
2236
+ input carried the relation field, the relation's name, and which of its inputs this is.
2237
+ It is `undefined` at the mutation field's own table, so a hook that only cares about root
2238
+ writes starts with `if (payload.nested) return`
2239
+ - **One call per input and operation**, not per row: a nested `create` of three rows is one
2240
+ `insert` call with three rows, and an update whose `where` matched several parents hands
2241
+ the hook every child it wrote across all of them. A to-one `connect` that also detaches
2242
+ the row attached before is one `update` call holding both rows; a detached row's key is
2243
+ `null`
2244
+ - **`rows`** hold the table's primary key and the join column(s) the statement changed — a
2245
+ nested write has no client selection of its own — plus whatever the hook names in
2246
+ `columns`
2247
+ - **`args`, `context` and `info`** are the root mutation field's; **`single`** says whether the
2248
+ relation is a to-one
2249
+ - **`before`** runs ahead of that input's first statement, **`after`** once its last has
2250
+ returned, and both inside the transaction every nested write opens — so a hook on a child
2251
+ table can roll the whole tree back, the parent row included, even when the root field has
2252
+ no hook of its own
2253
+
2178
2254
 
2179
2255
  ## Relations & N+1 handling
2180
2256