@vantreeseba/drizzle-graphql 7.9.1 → 8.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 +270 -6
- package/dist/README.md +270 -6
- package/dist/index.cjs +6389 -7159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +555 -263
- package/dist/index.d.ts +555 -263
- package/dist/index.js +6466 -7233
- package/dist/index.js.map +1 -1
- package/dist/package.json +2 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -141,14 +141,16 @@ const { schema } = buildSchema(db, {
|
|
|
141
141
|
fieldUpdateOperations: true, // increment/push operations on the update input (off by default)
|
|
142
142
|
countMutations: true, // update<Table>Count / delete<Table>Count mutations (off by default)
|
|
143
143
|
requireWhere: true, // make `where` non-null on plural update/delete (off by default)
|
|
144
|
+
uniqueKeyFilters: true, // a `where` field per compound unique constraint (off by default)
|
|
144
145
|
},
|
|
145
146
|
})
|
|
146
147
|
```
|
|
147
148
|
|
|
148
149
|
- Any flag left out keeps its default of `true`, so `{ features: { delete: false } }`
|
|
149
150
|
changes nothing else
|
|
150
|
-
- `upsert`, `nestedWrites`, `fieldUpdateOperations
|
|
151
|
-
exceptions:
|
|
151
|
+
- `upsert`, `nestedWrites`, `fieldUpdateOperations`, `countMutations`, `requireWhere` and
|
|
152
|
+
`uniqueKeyFilters` are the exceptions: they default to `false`, so they only exist if you
|
|
153
|
+
ask for them
|
|
152
154
|
- `groupBy` needs `aggregates`: it reuses those output types, so turning `aggregates` off
|
|
153
155
|
turns the group-by queries off with it
|
|
154
156
|
- Turning off `insert` or `update` also drops the input type that only that mutation
|
|
@@ -362,6 +364,56 @@ const { schema } = buildSchema(db, {
|
|
|
362
364
|
|
|
363
365
|
Return `undefined` for any table that should keep its default naming.
|
|
364
366
|
|
|
367
|
+
### Renaming the derived types
|
|
368
|
+
|
|
369
|
+
`typeNameMapper` names the table. Everything derived from it — `UsersFilters`, `UsersOrderBy`,
|
|
370
|
+
`CreateUsersInput`, `UsersAggregate`, and the shared inputs like `InnerOrder` and `StringFilter` —
|
|
371
|
+
follows the default template. Two schemas built from different databases therefore collide the
|
|
372
|
+
moment they are stitched into one gateway: both publish a `StringFilter`, and the two are not the
|
|
373
|
+
same type.
|
|
374
|
+
|
|
375
|
+
`typeNamePrefix` and `typeNameSuffix` move a whole build into its own namespace:
|
|
376
|
+
|
|
377
|
+
```Typescript
|
|
378
|
+
const { schema } = buildSchema(db, { typeNamePrefix: 'Shop' })
|
|
379
|
+
// type ShopUsers; input ShopUsersFilters, ShopCreateUsersInput, ShopInnerOrder, ShopStringFilter…
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
The prefix must be a valid GraphQL name start (`/^[_A-Za-z][_0-9A-Za-z]*$/`) and the suffix a valid
|
|
383
|
+
continuation (`/^[_0-9A-Za-z]+$/`); anything else throws at build time rather than producing a
|
|
384
|
+
schema graphql-js will reject.
|
|
385
|
+
|
|
386
|
+
For control over one kind of type rather than all of them, `derivedTypeNameMapper` is asked for
|
|
387
|
+
every type the build constructs:
|
|
388
|
+
|
|
389
|
+
```Typescript
|
|
390
|
+
const { schema } = buildSchema(db, {
|
|
391
|
+
typeNamePrefix: 'Shop',
|
|
392
|
+
derivedTypeNameMapper: ({ kind, defaultName, table }) =>
|
|
393
|
+
kind === 'filter' ? `${table}Where` : undefined,
|
|
394
|
+
})
|
|
395
|
+
// input UsersWhere; everything it declined keeps the prefix — ShopUsers, ShopUsersOrderBy…
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
`kind` is one of `object`, `filter`, `listRelationFilter`, `orderBy`, `createInput`, `updateInput`,
|
|
399
|
+
`updateManyInput`, `nestedWriteInput`, `fieldUpdateInput`, `aggregate`, `having`, `groupBy`,
|
|
400
|
+
`groupKeys`, `onConflict`, `uniqueKey`, `columnEnum`, `columnFilter` and `shared`. `table` is the
|
|
401
|
+
Drizzle schema key the type belongs to, where it belongs to one, and `operation` further identifies
|
|
402
|
+
it (the aggregate's `avg`, the column filter's `String`, the unique key's field name). An answer is
|
|
403
|
+
used **verbatim** — the prefix and suffix are the fallback, not a wrapper around it. Return
|
|
404
|
+
`undefined` to fall back.
|
|
405
|
+
|
|
406
|
+
Custom scalars (`BigInt`, `Decimal`, …), PostgreSQL enum types and `PgGeometryObject` keep their own
|
|
407
|
+
names under all three options: their definition does not vary between builds, so a gateway merges
|
|
408
|
+
them rather than colliding on them.
|
|
409
|
+
|
|
410
|
+
The [`resolveSelection` and `selectionToWith` helpers](#writing-your-own-resolver-over-a-generated-type) match selections
|
|
411
|
+
against GraphQL type names, so an override in a renamed build passes the same three options back:
|
|
412
|
+
|
|
413
|
+
```Typescript
|
|
414
|
+
resolveSelection(info, { db, table: 'Users', typeNamePrefix: 'Shop' })
|
|
415
|
+
```
|
|
416
|
+
|
|
365
417
|
## Soft delete
|
|
366
418
|
|
|
367
419
|
`softDelete` turns a table's `delete` mutation into an UPDATE that marks the row, and hides
|
|
@@ -388,7 +440,7 @@ What a declared table gets:
|
|
|
388
440
|
|
|
389
441
|
| Path | Effect |
|
|
390
442
|
| --- | --- |
|
|
391
|
-
| `deletePosts`, `deletePostsSingle` | UPDATE that writes the marker; returns the rows as they now stand |
|
|
443
|
+
| `deletePosts`, `deletePostsSingle` | UPDATE that writes the marker; returns the rows as they now stand — or a real DELETE under [`hard: true`](#purging-a-row), where the table opted in |
|
|
392
444
|
| `restorePosts`, `restorePostsSingle` | the inverse — matches **only** marked rows and clears the marker |
|
|
393
445
|
| `posts`, `postsSingle`, cursor pages | marked rows are not returned |
|
|
394
446
|
| `postsAggregate`, `postsGroupBy` | counted over unmarked rows only |
|
|
@@ -415,14 +467,94 @@ Defaults come from the column's type: `new Date()` for a timestamp, an ISO strin
|
|
|
415
467
|
the predicate has to compare against a constant: any NOT NULL column other than a boolean must
|
|
416
468
|
give `deletedValue` (a constant, not a function) and `restoredValue`, or the build throws.
|
|
417
469
|
|
|
470
|
+
**What counts as marked** follows the config, not the column's nullability. With no
|
|
471
|
+
`deletedValue`, a nullable column reads as "holding a value at all means deleted" — the
|
|
472
|
+
timestamp form, `deletedAt IS NOT NULL`. Name a constant and reads compare against it instead,
|
|
473
|
+
on a nullable column as much as a NOT NULL one:
|
|
474
|
+
|
|
475
|
+
```Typescript
|
|
476
|
+
// `is_deleted boolean DEFAULT false` — nullable, because it was added to an existing table
|
|
477
|
+
softDelete: { docs: { column: 'isDeleted', deletedValue: true, restoredValue: false } }
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Here a row is deleted when the column holds `true`; `false` **and NULL** are both alive, so the
|
|
481
|
+
rows written before the column existed are not swept into the trash view.
|
|
482
|
+
|
|
483
|
+
### What a relation field sees
|
|
484
|
+
|
|
485
|
+
By default the scope covers the table's own query fields **and** every relation field that
|
|
486
|
+
points at it, with one exception: a *required* to-one relation (`optional: false`, so the
|
|
487
|
+
field is `Kind!`) reads marked rows. Hiding one there can only ever produce `Cannot return
|
|
488
|
+
null for non-nullable field Item.kind` and take the whole parent down with it — there is no
|
|
489
|
+
usable result to protect.
|
|
490
|
+
|
|
491
|
+
Marking a lookup row usually means *retired*: keep it off the pickers and the lists, keep
|
|
492
|
+
rendering it on the historical rows that reference it. `scope: 'root'` says exactly that:
|
|
493
|
+
|
|
494
|
+
```Typescript
|
|
495
|
+
softDelete: {
|
|
496
|
+
kinds: { column: 'isRetired', deletedValue: true, restoredValue: false, scope: 'root' },
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
| | `scope: 'all'` (default) | `scope: 'root'` |
|
|
501
|
+
| --- | --- | --- |
|
|
502
|
+
| `kinds`, `kindsSingle`, `kindsAggregate` | marked rows hidden | marked rows hidden |
|
|
503
|
+
| `item.kind` (required to-one) | **shown** | shown |
|
|
504
|
+
| `item.altKind` (nullable to-one) | hidden (resolves `null`) | shown |
|
|
505
|
+
| `kind.items`, `kind.itemsAggregate` | hidden | shown |
|
|
506
|
+
|
|
507
|
+
The `deleted` argument is generated on relation fields either way, so a request can override
|
|
508
|
+
whichever default applies — `altKind(deleted: EXCLUDE)` under `'root'`, `altKind(deleted:
|
|
509
|
+
INCLUDE)` under `'all'`.
|
|
510
|
+
|
|
511
|
+
`scope` changes reads only. A nested `connect` / `disconnect` / `set` still refuses to attach a
|
|
512
|
+
marked row under either setting, and the table's own writes are unaffected.
|
|
513
|
+
|
|
514
|
+
### Purging a row
|
|
515
|
+
|
|
516
|
+
Soft delete and purge are not alternatives — emptying the trash, reclaiming a unique key and
|
|
517
|
+
clearing a table in development all need the row actually gone. `hardDelete: true` puts a
|
|
518
|
+
`hard` argument on the table's delete mutations:
|
|
519
|
+
|
|
520
|
+
```Typescript
|
|
521
|
+
softDelete: { articles: { column: 'deletedAt', hardDelete: true } }
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
```graphql
|
|
525
|
+
mutation {
|
|
526
|
+
trash: deleteArticles(where: { id: { eq: 1 } }) { id } # UPDATE — marks the row
|
|
527
|
+
purge: deleteArticles(where: { id: { eq: 3 } }, hard: true) { id } # DELETE — removes it
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
`hard: true` reads at `INCLUDE` rather than `EXCLUDE`, so it reaches rows that are **already
|
|
532
|
+
marked** — which is the case it mostly exists for; a `scope` still confines what it can reach,
|
|
533
|
+
exactly as it confines every other write. `deleteArticles(hard: true)` with no `where` empties
|
|
534
|
+
the table, marked rows included.
|
|
535
|
+
|
|
536
|
+
Opt-in per table, since it is destructive: no `hard` argument is generated where it was not
|
|
537
|
+
asked for, so the schema itself says which tables can be purged. `restore` never takes one.
|
|
538
|
+
|
|
539
|
+
One thing to weigh: an argument is harder to gate than a field — schema-level permission
|
|
540
|
+
middleware registers rules per field name, so `deleteArticles` with and without `hard: true`
|
|
541
|
+
is one rule that has to inspect arguments. The opted-in fields say so on their marker, so a
|
|
542
|
+
wrapper can find them without parsing the schema's arguments:
|
|
543
|
+
|
|
544
|
+
```Typescript
|
|
545
|
+
const meta = drizzleExtension(field) // { operation: 'delete', hardDelete: true, … }
|
|
546
|
+
```
|
|
547
|
+
|
|
418
548
|
Three things it deliberately does not do:
|
|
419
549
|
|
|
420
550
|
- **It does not cascade.** Marking a post does not mark its comments. A comment whose post is
|
|
421
|
-
marked is still returned by `comments`; its `post` field resolves to `null
|
|
422
|
-
|
|
551
|
+
marked is still returned by `comments`; its `post` field resolves to `null` (or to the
|
|
552
|
+
marked row, when the field is non-null or the table is `scope: 'root'` — see above).
|
|
553
|
+
Declare the child table too if that is not what you want.
|
|
423
554
|
- **It does not free the row's unique keys.** A marked row still occupies its primary key and
|
|
424
555
|
every unique index, so inserting a new row with the same natural key fails the constraint.
|
|
425
|
-
A partial unique index (`WHERE deleted_at IS NULL`) is the usual answer
|
|
556
|
+
A partial unique index (`WHERE deleted_at IS NULL`) is the usual answer; `hardDelete`
|
|
557
|
+
(above) is the other one.
|
|
426
558
|
- **It does not refuse.** Like `scope`, an unreachable row answers the same as a row that does
|
|
427
559
|
not exist.
|
|
428
560
|
|
|
@@ -629,6 +761,75 @@ A **to-many** relation takes a `some` / `none` / `every` wrapper
|
|
|
629
761
|
`users(where: { roles: { some: { name: { eq: "admin" } } } })` works the same as a direct
|
|
630
762
|
to-many relation
|
|
631
763
|
|
|
764
|
+
## Lookup by a unique key
|
|
765
|
+
|
|
766
|
+
A row with a two-column natural key — a stock line identified by item and location, a
|
|
767
|
+
membership identified by user and team — has to be spelled as two ordinary filters that
|
|
768
|
+
happen to match one row. Nothing says they belong together, and the query stays valid when
|
|
769
|
+
half of it goes missing. `features: { uniqueKeyFilters: true }` adds one `where` field per
|
|
770
|
+
compound unique constraint, named after its columns:
|
|
771
|
+
|
|
772
|
+
```graphql
|
|
773
|
+
{
|
|
774
|
+
stockSingle(where: { itemId_locationId: { itemId: "widget", locationId: "eu" } }) {
|
|
775
|
+
quantity
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
```graphql
|
|
781
|
+
input StockItemIdLocationIdKey {
|
|
782
|
+
itemId: String!
|
|
783
|
+
locationId: String!
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
- Every member is non-null, so a half-supplied key is a query-validation error rather than
|
|
788
|
+
a filter that quietly matches more rows than you meant
|
|
789
|
+
- The field lives on the table's `Filters` input, so it is accepted anywhere a filter is —
|
|
790
|
+
list and single queries, aggregates, `update<Table>Single` / `delete<Table>Single` — and
|
|
791
|
+
composes with column filters, relation filters, `OR` / `AND` / `NOT`
|
|
792
|
+
- Composite primary keys get a field like any other unique constraint
|
|
793
|
+
- Single-column constraints do not: `eq` on the column already names the row
|
|
794
|
+
- A field name a column or relation already holds is skipped, the same way a relation
|
|
795
|
+
yields to a same-named column
|
|
796
|
+
- It compiles to exactly the equalities you would have written by hand; the guarantee is in
|
|
797
|
+
the input type, not in the SQL
|
|
798
|
+
|
|
799
|
+
## Case-insensitive matching
|
|
800
|
+
|
|
801
|
+
Every filter on a string column carries an `insensitive` flag alongside its operators. Set it
|
|
802
|
+
and each comparison in that object compares `lower(column)` against `lower(operand)`:
|
|
803
|
+
|
|
804
|
+
```graphql
|
|
805
|
+
{
|
|
806
|
+
users(where: { email: { eq: "Dan@Example.com", insensitive: true } }) {
|
|
807
|
+
id
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
users(where: { username: { inArray: ["dan", "SAM", "Alex"], insensitive: true } }) {
|
|
811
|
+
id
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
This is what a natural key — an email, a username, a slug, an account code, a SKU — actually
|
|
817
|
+
wants, and `ilike` is the wrong tool for it: `%`, `_` and `\` in an `ilike` operand are
|
|
818
|
+
wildcards, so `100%_off` would silently match rows it should not unless the caller escapes them
|
|
819
|
+
by hand. Under `insensitive` the operand stays a literal and stays a bound parameter.
|
|
820
|
+
|
|
821
|
+
- It covers every operator beside it: `eq` / `ne`, the ordering comparisons, `inArray` /
|
|
822
|
+
`notInArray`, `like` / `notLike`, and the safe `startsWith` / `endsWith` / `contains`
|
|
823
|
+
(the `i`-prefixed forms are already case-insensitive and are unaffected)
|
|
824
|
+
- It applies to the operators in its own object only. A nested `AND` / `OR` / `NOT` branch is
|
|
825
|
+
a separate object and sets its own flag
|
|
826
|
+
- `lower()` is the same function the case-insensitive LIKE operators already fall back to on
|
|
827
|
+
the dialects without a native `ILIKE`, so the behaviour is identical on all three. Because
|
|
828
|
+
the left side is a plain `lower(column)`, a `lower(column)` expression index can serve the
|
|
829
|
+
lookup — which `ilike '%…%'` never can
|
|
830
|
+
- The flag appears only on filters whose column can take string operators, so it is absent
|
|
831
|
+
from the numeric, uuid and boolean filters
|
|
832
|
+
|
|
632
833
|
## Matching lists of values
|
|
633
834
|
|
|
634
835
|
`inArray` / `notInArray` take a list of candidate values and compile to SQL `IN` / `NOT IN`.
|
|
@@ -1118,6 +1319,30 @@ mutation {
|
|
|
1118
1319
|
- `values` is the same `Create<Type>Input` the insert mutations take, so turning `insert`
|
|
1119
1320
|
off does not remove it
|
|
1120
1321
|
|
|
1322
|
+
### A null key is an absent key
|
|
1323
|
+
|
|
1324
|
+
Which half of an upsert runs is decided by the key the request supplies, but a GraphQL client
|
|
1325
|
+
cannot leave an input field out based on a variable's value. So one document carries both
|
|
1326
|
+
halves with one nullable variable:
|
|
1327
|
+
|
|
1328
|
+
```graphql
|
|
1329
|
+
mutation Save($id: Int, $name: String!) {
|
|
1330
|
+
upsertUsersSingle(values: { id: $id, name: $name }) {
|
|
1331
|
+
id
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
`$id: null` inserts a new row; `$id: 1` overwrites row 1. An explicit `null` for a NOT NULL
|
|
1337
|
+
column is treated as if the field had been left out, so the column's default fills it in —
|
|
1338
|
+
the column cannot store null, so null cannot have been meant as the value to write. It works
|
|
1339
|
+
the same for a database default (`serial`, `defaultRandom()`), a drizzle-side one
|
|
1340
|
+
(`$defaultFn`), the plain `create*` mutations, and a batch where only some rows carry a key.
|
|
1341
|
+
|
|
1342
|
+
The key has to have *something* to fill it in: a primary key declared with no default at all
|
|
1343
|
+
stays non-null on the create input, so a nullable variable in that position is a validation
|
|
1344
|
+
error at query time rather than a NOT NULL violation from the database.
|
|
1345
|
+
|
|
1121
1346
|
Dialect differences:
|
|
1122
1347
|
|
|
1123
1348
|
- **PostgreSQL** and **SQLite** — the full surface above. A table with no primary key and no
|
|
@@ -1180,6 +1405,11 @@ mutation {
|
|
|
1180
1405
|
`author: { create: … }` available, `authorId` is one of two ways to supply it
|
|
1181
1406
|
- Nesting is one level deep: the row a nested `create` inserts takes columns only, and the
|
|
1182
1407
|
join column is left off, since the write it is part of sets it
|
|
1408
|
+
- The generated input types are `<Type><Relation>NestedCreateInput` and
|
|
1409
|
+
`<Type><Relation>NestedUpdateInput` for the operations, and
|
|
1410
|
+
`<Type><Relation>NestedCreatePayloadInput` for the row a `create` inserts — all named
|
|
1411
|
+
outside the root input namespace, so a relation whose name spells a sibling table's
|
|
1412
|
+
(`item.type` beside a table named `itemType`) is not a collision
|
|
1183
1413
|
- Many-to-many (`.through()`) relations are written through their junction table — see
|
|
1184
1414
|
below
|
|
1185
1415
|
- The whole tree runs in one transaction — a savepoint when the request already carries one
|
|
@@ -1570,6 +1800,39 @@ The hook covers root queries and mutations, relation and aggregate fields, and t
|
|
|
1570
1800
|
standalone `entities.fieldResolvers`. The default is exported as `defaultErrorMapper` if you
|
|
1571
1801
|
want to fall back to it explicitly.
|
|
1572
1802
|
|
|
1803
|
+
### What an error carries
|
|
1804
|
+
|
|
1805
|
+
Every error the library raises itself carries its context as data, so nothing downstream has
|
|
1806
|
+
to match on prose:
|
|
1807
|
+
|
|
1808
|
+
```json
|
|
1809
|
+
{
|
|
1810
|
+
"message": "'where' matched more than one row — nothing was written!",
|
|
1811
|
+
"path": ["updateUser"],
|
|
1812
|
+
"extensions": {
|
|
1813
|
+
"code": "DRIZZLE_MULTI_ROW_MATCH",
|
|
1814
|
+
"drizzle": { "table": "Users", "operation": "update", "field": "updateUsersSingle" }
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
```
|
|
1818
|
+
|
|
1819
|
+
- `code` classifies the failure — `DRIZZLE_MULTI_ROW_MATCH`, `DRIZZLE_WHERE_REQUIRED`,
|
|
1820
|
+
`DRIZZLE_LIMIT_EXCEEDED`, `DRIZZLE_NO_VALUES`, `DRIZZLE_INVALID_FILTER`,
|
|
1821
|
+
`DRIZZLE_INVALID_CURSOR`, and so on. The full union is exported as `DrizzleErrorCode`.
|
|
1822
|
+
- `drizzle` says what it was about: the Drizzle schema key of the `table`, the `operation`,
|
|
1823
|
+
the generated `field`, and the `relation` for a relation field. The type is exported as
|
|
1824
|
+
`DrizzleErrorContext`.
|
|
1825
|
+
|
|
1826
|
+
The **generated** field name is in `extensions.drizzle.field` rather than in the message on
|
|
1827
|
+
purpose. A schema that republishes these fields under other names — `RenameRootFields`, a
|
|
1828
|
+
stitched gateway, a hand-written façade — renames the schema, not the inside of a message, so
|
|
1829
|
+
a message naming `updateUsersSingle` would be telling a client about a field it has never
|
|
1830
|
+
heard of. The name the client asked for is in `path`, already correct under any rename.
|
|
1831
|
+
|
|
1832
|
+
Driver errors are not classified: they are sanitized as above and keep
|
|
1833
|
+
`code: "INTERNAL_SERVER_ERROR"` with no `drizzle` block, which is also how `onError` can tell
|
|
1834
|
+
the two apart without inspecting messages.
|
|
1835
|
+
|
|
1573
1836
|
## Transactions
|
|
1574
1837
|
|
|
1575
1838
|
Each resolver runs its statements on the database the schema was built from, so by default a
|
|
@@ -1923,6 +2186,7 @@ that explains it, where there is one.
|
|
|
1923
2186
|
| `exclude` | none | [tables and columns left out](#excluding-tables-and-columns) of the generated schema entirely |
|
|
1924
2187
|
| `relationsDepthLimit` | unlimited | how deep relation fields are generated; `0` gives a flat, columns-only schema |
|
|
1925
2188
|
| `prefixes` / `suffixes` / `typeNameMapper` | see [Naming](#naming) | operation and type names |
|
|
2189
|
+
| `typeNamePrefix` / `typeNameSuffix` / `derivedTypeNameMapper` | none | [names of the derived types](#renaming-the-derived-types) — filters, inputs, aggregates, shared inputs |
|
|
1926
2190
|
| `scalars` / `mapColumnType` | built-in detection | [override a column's GraphQL type](#overriding-a-columns-scalar), by name or by rule |
|
|
1927
2191
|
| `enumNameMapper` | shared `<EnumName>Enum` per database enum | [names the enum a column maps to](#enum-types) |
|
|
1928
2192
|
| `describeColumn` / `describeTable` / `describeRelation` | none | [GraphQL descriptions](#documenting-the-schema) |
|