@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 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: async ({ table, operation, rows, tx }) => {
2023
- await tx.insert(auditLog).values(rows.map((row) => ({ table, operation, rowId: row.id })))
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 |