@vantreeseba/drizzle-graphql 13.1.0 → 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 +106 -7
- package/dist/index.cjs +547 -268
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -8
- package/dist/index.d.ts +86 -8
- package/dist/index.js +547 -268
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
@@ -1623,9 +1651,27 @@ mutation {
|
|
|
1623
1651
|
- Both return `Int!` — the number of rows the write affected — and add no new output type
|
|
1624
1652
|
to the schema
|
|
1625
1653
|
- Arguments are exactly the plural mutation's, so a client swaps one field name for the
|
|
1626
|
-
other; `features.requireWhere` applies to them the same way
|
|
1654
|
+
other; `features.requireWhere` applies to them the same way, and on a soft-deleting
|
|
1655
|
+
table that opted into `hardDelete` the delete takes `hard` too
|
|
1627
1656
|
- They follow the feature switch of the write they mirror: `updateCount` needs
|
|
1628
1657
|
`features.update`, `deleteCount` needs `features.delete`
|
|
1658
|
+
- They obey the same row policies as the plural write — only the answer differs:
|
|
1659
|
+
- [`scope`](#row-scoping-and-multi-tenancy) is ANDed into the `where`, so they reach,
|
|
1660
|
+
and count, only in-scope rows
|
|
1661
|
+
- on a [soft-deleting](#soft-delete) table `delete<Table>Count` marks the rows rather
|
|
1662
|
+
than removing them, and neither mutation reaches a row that is already marked
|
|
1663
|
+
- a [`contextValues`](#row-scoping-and-multi-tenancy) column is never reassigned by the
|
|
1664
|
+
update
|
|
1665
|
+
- [`onWrite`](#write-hooks) hooks run `before` and `after`, in a transaction, with the
|
|
1666
|
+
plural write's payload (`operation: 'update'` / `'delete'`, `single: false`). An
|
|
1667
|
+
`after` hook is handed rows, so a table with one gives up the returnless write: the
|
|
1668
|
+
statement `RETURNING`s the hook's `columns`, or just the primary key when it names
|
|
1669
|
+
none (every column, on a table without a key), and the count is the number of rows
|
|
1670
|
+
returned. Without an `after` hook there is no `RETURNING` at all. On MySQL the hook
|
|
1671
|
+
gets `rows: []`, as it does on every MySQL mutation
|
|
1672
|
+
- Their [`extensions.drizzle`](#identifying-generated-fields) is the plural write's
|
|
1673
|
+
(`operation: 'update'` / `'delete'`, `targetArg: 'where'`) plus `count: true`, so a
|
|
1674
|
+
wrapper that gates writes on `operation` covers them too
|
|
1629
1675
|
- **MySQL** gets them too, and they are arguably most useful there: MySQL's other mutations
|
|
1630
1676
|
return only `MutationReturn { isSuccess }`, so a row count is strictly more information
|
|
1631
1677
|
- `update<Table>Count` **rejects** nested writes with a `GraphQLError` — a nested write
|
|
@@ -2122,6 +2168,9 @@ onWrite: {
|
|
|
2122
2168
|
works.
|
|
2123
2169
|
- The columns are added to `RETURNING` for every write the hook is attached to: insert, upsert,
|
|
2124
2170
|
update, `updateMany`, delete and restore. A delete's rows are the rows it removed.
|
|
2171
|
+
- The [count mutations](#row-counts-instead-of-rows) fire the hooks as `update` / `delete`.
|
|
2172
|
+
The client selects nothing there, so an `after` hook's rows hold exactly its `columns`, or
|
|
2173
|
+
just the primary key when it names none — the one `RETURNING` those mutations pay for.
|
|
2125
2174
|
- **The client's response is unchanged.** Columns the hook asked for but the client did not
|
|
2126
2175
|
select are taken back off before the rows are mapped, so they reach neither the response nor
|
|
2127
2176
|
a custom resolver calling the generated one. The hook gets its own copy of the rows.
|
|
@@ -2140,6 +2189,7 @@ The payload:
|
|
|
2140
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) |
|
|
2141
2190
|
| `context`, `info` | the GraphQL context value and resolve info |
|
|
2142
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 |
|
|
2143
2193
|
|
|
2144
2194
|
- **`tx` is the executor the mutation itself used** — one you supplied under
|
|
2145
2195
|
`drizzleExecutorKey`, the request's auto-transaction, or, for a field that would otherwise
|
|
@@ -2149,11 +2199,58 @@ The payload:
|
|
|
2149
2199
|
already does.
|
|
2150
2200
|
- **`rows` is empty** at the `before` position, and on MySQL, whose mutations return no rows.
|
|
2151
2201
|
The `before` position and `args` still work there.
|
|
2152
|
-
-
|
|
2153
|
-
|
|
2202
|
+
- With `nestedWrites`, the hooks of **every table a nested write reaches** fire too —
|
|
2203
|
+
below.
|
|
2154
2204
|
|
|
2155
2205
|
A build with no `onWrite` opens exactly the transactions it did before.
|
|
2156
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
|
+
|
|
2157
2254
|
|
|
2158
2255
|
## Relations & N+1 handling
|
|
2159
2256
|
|
|
@@ -2329,7 +2426,9 @@ the check to make before treating a field as one of ours. Relation fields (and
|
|
|
2329
2426
|
naming the relation's **target** — so `Users.posts` reads as
|
|
2330
2427
|
`{ table: 'Posts', parentTable: 'Users', relation: 'posts' }`. Object types carry
|
|
2331
2428
|
`{ table, kind: 'type', primaryKey }`, which is what a stitching layer needs to map a type
|
|
2332
|
-
back to its source.
|
|
2429
|
+
back to its source. The [count mutations](#row-counts-instead-of-rows) carry their plural
|
|
2430
|
+
write's `operation` plus `count: true` — they write the same rows but resolve to an `Int!`, so
|
|
2431
|
+
a wrapper that reads the result as rows should check `count` first.
|
|
2333
2432
|
|
|
2334
2433
|
Because the row identity of a mutation lives in a different argument for every operation,
|
|
2335
2434
|
`identifyRows` reads whichever one applies and hands back primary-key values:
|