@vantreeseba/drizzle-graphql 13.0.0 → 13.1.1
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 +111 -5
- package/dist/index.cjs +334 -153
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -4
- package/dist/index.d.ts +98 -4
- package/dist/index.js +334 -153
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -711,6 +711,51 @@ lists is the build-time error above.
|
|
|
711
711
|
> `${tableName}${columnName[0]!.toUpperCase()}${columnName.slice(1)}Enum`
|
|
712
712
|
> ```
|
|
713
713
|
|
|
714
|
+
### Enum value names
|
|
715
|
+
|
|
716
|
+
A value's GraphQL name is the stored value itself, or `Option<index>` when the stored value
|
|
717
|
+
is not a usable GraphQL name. `enumValueNameMapper` renames the values — most often to follow
|
|
718
|
+
GraphQL's SCREAMING_CASE convention while the database keeps its lowercase values:
|
|
719
|
+
|
|
720
|
+
```Typescript
|
|
721
|
+
export const Runs = pgTable('runs', {
|
|
722
|
+
status: text('status', { enum: ['running', 'ok', 'error', 'stopped'] }),
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
const { schema } = buildSchema(db, {
|
|
726
|
+
// The built-in preset: `inProgress` / `in-progress` → `IN_PROGRESS`
|
|
727
|
+
enumValueNameMapper: 'constant',
|
|
728
|
+
})
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
```graphql
|
|
732
|
+
enum RunsStatusEnum {
|
|
733
|
+
RUNNING # stored as 'running'
|
|
734
|
+
OK
|
|
735
|
+
ERROR
|
|
736
|
+
STOPPED
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
- Only the GraphQL name changes. Each value's internal `value` is still the stored string,
|
|
741
|
+
so filters (`eq`, `inArray`, …), create/update inputs, outputs and your own resolvers keep
|
|
742
|
+
sending and receiving exactly what the database stores. The generated TypeScript types
|
|
743
|
+
keep the stored values too
|
|
744
|
+
- The function form receives the stored `value`, its `index`, the enum's GraphQL `typeName`
|
|
745
|
+
(after `enumNameMapper`) and everything `enumNameMapper` receives. Returning `undefined`
|
|
746
|
+
keeps the default name for that value:
|
|
747
|
+
|
|
748
|
+
```Typescript
|
|
749
|
+
enumValueNameMapper: ({ value, typeName }) =>
|
|
750
|
+
typeName === 'RunsStatusEnum' ? value.toUpperCase() : undefined,
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
- The build fails, rather than producing an invalid or lossy schema, when a returned name is
|
|
754
|
+
not a valid GraphQL enum value name, when two values of one enum map to the same name, or
|
|
755
|
+
when columns that share one enum type (the columns of one `pgEnum`) would name its values
|
|
756
|
+
differently — give those columns separate types with `enumNameMapper` if they really
|
|
757
|
+
should differ
|
|
758
|
+
|
|
714
759
|
## Documenting the schema
|
|
715
760
|
|
|
716
761
|
Four hooks put descriptions and deprecations on the generated types. Each is called per
|
|
@@ -1578,9 +1623,27 @@ mutation {
|
|
|
1578
1623
|
- Both return `Int!` — the number of rows the write affected — and add no new output type
|
|
1579
1624
|
to the schema
|
|
1580
1625
|
- Arguments are exactly the plural mutation's, so a client swaps one field name for the
|
|
1581
|
-
other; `features.requireWhere` applies to them the same way
|
|
1626
|
+
other; `features.requireWhere` applies to them the same way, and on a soft-deleting
|
|
1627
|
+
table that opted into `hardDelete` the delete takes `hard` too
|
|
1582
1628
|
- They follow the feature switch of the write they mirror: `updateCount` needs
|
|
1583
1629
|
`features.update`, `deleteCount` needs `features.delete`
|
|
1630
|
+
- They obey the same row policies as the plural write — only the answer differs:
|
|
1631
|
+
- [`scope`](#row-scoping-and-multi-tenancy) is ANDed into the `where`, so they reach,
|
|
1632
|
+
and count, only in-scope rows
|
|
1633
|
+
- on a [soft-deleting](#soft-delete) table `delete<Table>Count` marks the rows rather
|
|
1634
|
+
than removing them, and neither mutation reaches a row that is already marked
|
|
1635
|
+
- a [`contextValues`](#row-scoping-and-multi-tenancy) column is never reassigned by the
|
|
1636
|
+
update
|
|
1637
|
+
- [`onWrite`](#write-hooks) hooks run `before` and `after`, in a transaction, with the
|
|
1638
|
+
plural write's payload (`operation: 'update'` / `'delete'`, `single: false`). An
|
|
1639
|
+
`after` hook is handed rows, so a table with one gives up the returnless write: the
|
|
1640
|
+
statement `RETURNING`s the hook's `columns`, or just the primary key when it names
|
|
1641
|
+
none (every column, on a table without a key), and the count is the number of rows
|
|
1642
|
+
returned. Without an `after` hook there is no `RETURNING` at all. On MySQL the hook
|
|
1643
|
+
gets `rows: []`, as it does on every MySQL mutation
|
|
1644
|
+
- Their [`extensions.drizzle`](#identifying-generated-fields) is the plural write's
|
|
1645
|
+
(`operation: 'update'` / `'delete'`, `targetArg: 'where'`) plus `count: true`, so a
|
|
1646
|
+
wrapper that gates writes on `operation` covers them too
|
|
1584
1647
|
- **MySQL** gets them too, and they are arguably most useful there: MySQL's other mutations
|
|
1585
1648
|
return only `MutationReturn { isSuccess }`, so a row count is strictly more information
|
|
1586
1649
|
- `update<Table>Count` **rejects** nested writes with a `GraphQLError` — a nested write
|
|
@@ -2019,8 +2082,12 @@ outside the resolver, so its after-position is after the commit.
|
|
|
2019
2082
|
```Typescript
|
|
2020
2083
|
const { schema } = buildSchema(db, {
|
|
2021
2084
|
onWrite: {
|
|
2022
|
-
posts:
|
|
2023
|
-
|
|
2085
|
+
posts: {
|
|
2086
|
+
after: async ({ table, operation, rows, tx }) => {
|
|
2087
|
+
await tx.insert(auditLog).values(rows.map((row) => ({ table, operation, rowId: row.id })))
|
|
2088
|
+
},
|
|
2089
|
+
// Every row carries `id`, even when the client's mutation did not select it.
|
|
2090
|
+
columns: ['id'],
|
|
2024
2091
|
},
|
|
2025
2092
|
},
|
|
2026
2093
|
})
|
|
@@ -2046,6 +2113,42 @@ Registering a bare function (or a positions object) at the top level applies it
|
|
|
2046
2113
|
anything else is read as a table map keyed by the **Drizzle schema key**, and a name that
|
|
2047
2114
|
matches nothing throws at build time.
|
|
2048
2115
|
|
|
2116
|
+
### Which columns `rows` holds
|
|
2117
|
+
|
|
2118
|
+
The statement's `RETURNING` list is built from the client's selection, so by default a row
|
|
2119
|
+
holds only the columns the mutation's document asked for (plus any key a selected relation or a
|
|
2120
|
+
nested write needed). A hook that reads a column the client may not select — `row.id` above, or
|
|
2121
|
+
the `personId` an `addresses` hook needs to bump its person's revision — names it in `columns`:
|
|
2122
|
+
|
|
2123
|
+
```Typescript
|
|
2124
|
+
onWrite: {
|
|
2125
|
+
addresses: {
|
|
2126
|
+
after: async ({ rows, tx }) => { /* every row has `id` and `personId` */ },
|
|
2127
|
+
columns: ['id', 'personId'],
|
|
2128
|
+
},
|
|
2129
|
+
contactInfos: {
|
|
2130
|
+
after: async ({ rows, tx }) => { /* every row has every column */ },
|
|
2131
|
+
columns: 'all',
|
|
2132
|
+
},
|
|
2133
|
+
}
|
|
2134
|
+
```
|
|
2135
|
+
|
|
2136
|
+
- The names are **column keys** — the property names on the Drizzle table object, which is
|
|
2137
|
+
what `rows` is keyed by. An unknown name throws at build time, and so does `columns` on a
|
|
2138
|
+
registration with no `after` hook (only `after` is handed rows).
|
|
2139
|
+
- On a top-level registration, a named column must exist on **every** table; `'all'` always
|
|
2140
|
+
works.
|
|
2141
|
+
- The columns are added to `RETURNING` for every write the hook is attached to: insert, upsert,
|
|
2142
|
+
update, `updateMany`, delete and restore. A delete's rows are the rows it removed.
|
|
2143
|
+
- The [count mutations](#row-counts-instead-of-rows) fire the hooks as `update` / `delete`.
|
|
2144
|
+
The client selects nothing there, so an `after` hook's rows hold exactly its `columns`, or
|
|
2145
|
+
just the primary key when it names none — the one `RETURNING` those mutations pay for.
|
|
2146
|
+
- **The client's response is unchanged.** Columns the hook asked for but the client did not
|
|
2147
|
+
select are taken back off before the rows are mapped, so they reach neither the response nor
|
|
2148
|
+
a custom resolver calling the generated one. The hook gets its own copy of the rows.
|
|
2149
|
+
- **MySQL** has no `RETURNING`, so its mutations return no rows and `columns` has nothing to
|
|
2150
|
+
add to: `rows` stays empty there. The names are still checked at build time.
|
|
2151
|
+
|
|
2049
2152
|
The payload:
|
|
2050
2153
|
|
|
2051
2154
|
| Field | |
|
|
@@ -2055,7 +2158,7 @@ The payload:
|
|
|
2055
2158
|
| `position` | `'before'` or `'after'` |
|
|
2056
2159
|
| `single` | whether the field writes one row or a list |
|
|
2057
2160
|
| `args` | the field's GraphQL arguments, as the resolver received them |
|
|
2058
|
-
| `rows` | the post-write rows **as the database returned them**, before the output mapper |
|
|
2161
|
+
| `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) |
|
|
2059
2162
|
| `context`, `info` | the GraphQL context value and resolve info |
|
|
2060
2163
|
| `tx` | the executor the mutation ran on |
|
|
2061
2164
|
|
|
@@ -2247,7 +2350,9 @@ the check to make before treating a field as one of ours. Relation fields (and
|
|
|
2247
2350
|
naming the relation's **target** — so `Users.posts` reads as
|
|
2248
2351
|
`{ table: 'Posts', parentTable: 'Users', relation: 'posts' }`. Object types carry
|
|
2249
2352
|
`{ table, kind: 'type', primaryKey }`, which is what a stitching layer needs to map a type
|
|
2250
|
-
back to its source.
|
|
2353
|
+
back to its source. The [count mutations](#row-counts-instead-of-rows) carry their plural
|
|
2354
|
+
write's `operation` plus `count: true` — they write the same rows but resolve to an `Int!`, so
|
|
2355
|
+
a wrapper that reads the result as rows should check `count` first.
|
|
2251
2356
|
|
|
2252
2357
|
Because the row identity of a mutation lives in a different argument for every operation,
|
|
2253
2358
|
`identifyRows` reads whichever one applies and hands back primary-key values:
|
|
@@ -2285,6 +2390,7 @@ that explains it, where there is one.
|
|
|
2285
2390
|
| `typeNamePrefix` / `typeNameSuffix` / `derivedTypeNameMapper` | none | [names of the derived types](#renaming-the-derived-types) — filters, inputs, aggregates, shared inputs |
|
|
2286
2391
|
| `scalars` / `mapColumnType` | built-in detection | [override a column's GraphQL type](#overriding-a-columns-scalar), by name or by rule |
|
|
2287
2392
|
| `enumNameMapper` | shared `<EnumName>Enum` per database enum | [names the enum a column maps to](#enum-types) |
|
|
2393
|
+
| `enumValueNameMapper` | the stored value | [names the enum's values](#enum-value-names); `'constant'` for CONSTANT_CASE |
|
|
2288
2394
|
| `describeColumn` / `describeTable` / `describeRelation` | none | [GraphQL descriptions](#documenting-the-schema) |
|
|
2289
2395
|
| `deprecateColumn` | none | `@deprecated` on a column's output and optional input fields |
|
|
2290
2396
|
| `conflictDoNothing` | `false` | PostgreSQL only — `ON CONFLICT DO NOTHING` on inserts, which makes `create<Table>Single` nullable |
|