@vantreeseba/drizzle-graphql 15.0.0 → 15.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
@@ -6,7 +6,7 @@ Site: [cubicecho.github.io/drizzle-graphql](https://cubicecho.github.io/drizzle-
6
6
 
7
7
  ## Usage
8
8
 
9
- - Pass your drizzle database instance and schema into builder to generate `{ schema, entities }` object
9
+ - Pass your drizzle database instance into the builder to generate a `{ schema, entities }` object. The tables are read from the `relations` you gave the drizzle constructor (`drizzle({ client, relations })`), so a table missing from your relations config is not part of the generated schema
10
10
  - Use `schema` if pre-built schema already satisfies all your needs. It's compatible with any server that consumes `GraphQLSchema` class instance
11
11
 
12
12
  Example: hosting schema using [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
@@ -39,6 +39,7 @@ Site: [cubicecho.github.io/drizzle-graphql](https://cubicecho.github.io/drizzle-
39
39
 
40
40
  // Schema contains 'Users' and 'Customers' tables
41
41
  import { db } from './database'
42
+ import { users } from './schema'
42
43
 
43
44
  const { entities } = buildSchema(db)
44
45
 
@@ -63,7 +64,7 @@ Site: [cubicecho.github.io/drizzle-graphql](https://cubicecho.github.io/drizzle-
63
64
  },
64
65
  resolve: async (source, args, context, info) => {
65
66
  // Your custom logic goes here...
66
- const result = await db.select(schema.Users).where()...
67
+ const result = await db.select().from(users).where(/* … */)
67
68
 
68
69
  return result
69
70
  }
@@ -168,8 +169,9 @@ const { schema } = buildSchema(db, {
168
169
  turns the group-by queries off with it
169
170
  - Turning off `insert` or `update` also drops the input type that only that mutation
170
171
  used (`Create<Type>Input` / `Update<Type>Input`)
171
- - Turning off all three mutation features omits the `Mutation` type entirely, the same as
172
- `mutations: false`
172
+ - Turning off every mutation feature omits the `Mutation` type entirely, the same as
173
+ `mutations: false` — unless a table has `softDelete`, whose `restore` mutations are
174
+ generated regardless
173
175
  - List and single queries are always generated, so `Query` is never empty
174
176
 
175
177
  ### Per-table operations
@@ -344,9 +346,7 @@ Together the two close the loop: `contextValues` decides which tenant a new row
344
346
 
345
347
  Names that match nothing throw at build time, the same as `exclude` — a renamed column fails the
346
348
  build instead of quietly ceasing to be scoped.
347
- > request who may see what is a job for [`graphql-shield`](https://the-guild.dev/graphql/shield)
348
- > or an equivalent layer above the schema, which has the request context this library
349
- > does not.
349
+
350
350
  ## Naming
351
351
 
352
352
  Type and field names are derived from the Drizzle schema keys. Three options change them.
@@ -463,7 +463,7 @@ Keyed by the **Drizzle schema key**, like `scope` and `exclude`. A function form
463
463
  accepted, for a convention applied across the schema:
464
464
 
465
465
  ```Typescript
466
- softDelete: (table, tableName) => ('deletedAt' in getTableColumns(table) ? 'deletedAt' : undefined)
466
+ softDelete: (table, tableName) => ('deletedAt' in getColumns(table) ? 'deletedAt' : undefined)
467
467
  ```
468
468
 
469
469
  What a declared table gets:
@@ -612,6 +612,7 @@ generated SDL says what a field actually holds instead of falling back to `Strin
612
612
  | `timestamp` / `datetime` (either `mode`) | `DateTime` | an ISO-8601 string |
613
613
  | SQLite `integer({ mode: 'timestamp' })` / `'timestamp_ms'` | `DateTime` | an ISO-8601 string |
614
614
  | `date` (either `mode`) | `Date` | a `YYYY-MM-DD` string |
615
+ | `numeric` / `decimal` | `Decimal` | a decimal string |
615
616
 
616
617
  ```graphql
617
618
  mutation {
@@ -629,6 +630,8 @@ mutation {
629
630
  `Number.MAX_SAFE_INTEGER` survive the round-trip. Integer literals are accepted on input;
630
631
  floats and non-numeric strings are rejected
631
632
  - **`UUID`** validates the format on the way in, including inside `where` filters
633
+ - **`Decimal`** is a decimal string in both directions, so the precision the column declares
634
+ is not lost to a JS float. Non-numeric and non-finite input is rejected
632
635
  - **`Date`** is a calendar day — no time, no offset — and is the same type in every position
633
636
  a `date` column appears in: the object field, the create and update inputs, the
634
637
  `DateFilter` operands, aggregate `min` / `max` and group keys. It takes and returns an
@@ -656,7 +659,7 @@ mutation {
656
659
  The scalars are exported if you need them in a hand-written schema:
657
660
 
658
661
  ```Typescript
659
- import { GraphQLBigIntString, GraphQLDate, GraphQLDateTime, GraphQLJSON, GraphQLUUID } from '@vantreeseba/drizzle-graphql'
662
+ import { GraphQLBigIntString, GraphQLDate, GraphQLDateTime, GraphQLDecimalString, GraphQLJSON, GraphQLUUID } from '@vantreeseba/drizzle-graphql'
660
663
  ```
661
664
 
662
665
  `GraphQLBigIntString` is the `BigInt` scalar — named for what it does rather than what it is
@@ -781,7 +784,7 @@ only has to handle the cases it cares about:
781
784
  ```Typescript
782
785
  const { schema } = buildSchema(db, {
783
786
  // `schema` is the Postgres schema the enum was declared in, if any; `values` is its
784
- // value list. Everything but `enumName` is always present.
787
+ // value list. `enumName` and `schema` are undefined for an inline value list.
785
788
  enumNameMapper: ({ enumName, schema }) =>
786
789
  schema === 'reporting' ? `Reporting${enumName}Enum` : undefined,
787
790
  })
@@ -861,9 +864,40 @@ const { schema } = buildSchema(db, {
861
864
 
862
865
  - `describeColumn` reaches every place the column appears: the object type, the create and
863
866
  update inputs, the filter input's field, `Min`/`Max` aggregates and `GroupKeys`
864
- - The hook receives the drizzle column itself, so a project that keeps its documentation in
865
- the schema (a `.$type()` brand, a custom column property, a comment table) can read it
866
- straight off
867
+ - `describeColumn` receives the drizzle column itself and `describeTable` the drizzle table
868
+ itself, so a project that keeps its documentation in the schema (a `WeakMap` keyed by
869
+ column/table, a custom property, a comment table) can read it straight off:
870
+
871
+ ```Typescript
872
+ import type { Table } from 'drizzle-orm';
873
+
874
+ const docs = new WeakMap<object, string>();
875
+
876
+ // Register docs on the *built* table and its columns. `text('email')` is only a builder:
877
+ // pgTable creates a new column object from it, so keying on the builder never matches.
878
+ const described = <T extends Table>(
879
+ table: T,
880
+ description: string,
881
+ columns: Partial<Record<keyof T['_']['columns'], string>> = {},
882
+ ): T => {
883
+ docs.set(table, description);
884
+ for (const [key, columnText] of Object.entries(columns)) {
885
+ docs.set((table as any)[key], columnText as string);
886
+ }
887
+ return table;
888
+ };
889
+
890
+ export const users = described(
891
+ pgTable('users', { id: integer('id').primaryKey(), email: text('email').notNull() }),
892
+ 'A person who can sign in',
893
+ { email: 'Primary login address' },
894
+ );
895
+
896
+ buildSchema(db, {
897
+ describeColumn: (column) => docs.get(column),
898
+ describeTable: (_tableName, table) => docs.get(table),
899
+ });
900
+ ```
867
901
  - `deprecateColumn` marks output fields and *optional* input fields. A required input field
868
902
  is skipped: GraphQL forbids deprecating one, and emitting it would make the schema invalid
869
903
  - Filter and `orderBy` fields are never deprecated — filtering on a deprecated column is how
@@ -1008,6 +1042,11 @@ The pattern operators are `like` / `notLike` / `ilike` / `notIlike`, the safe
1008
1042
  `startsWith` / `endsWith` / `contains` and their `i`-prefixed forms, plus the `insensitive`
1009
1043
  flag that modifies them.
1010
1044
 
1045
+ An operand of `null` leaves its operator out, as though it had not been sent. `false` is an
1046
+ ordinary value: `active: { eq: false }` matches the rows that are false, `ne: false` the rows
1047
+ that are true, and `has: false` the arrays holding a `false`. Only the flag operators —
1048
+ `isNull`, `isNotNull` and `isEmpty` — read `false` as "add no condition".
1049
+
1011
1050
  The omissions are the cases where the operator cannot ask anything a caller means:
1012
1051
 
1013
1052
  - **Pattern matching a timestamp, a boolean or an enum member** compares whatever the value
@@ -1090,7 +1129,8 @@ that column's `inArray` list instead of by the column's value:
1090
1129
  ## Filtering inside JSON documents
1091
1130
 
1092
1131
  `json` / `jsonb` columns get a `path` filter that compares the value at one path inside the
1093
- document, alongside the whole-document `eq` / `ne` / `contains` operators:
1132
+ document, alongside the whole-document `eq` / `ne` operators and, on PostgreSQL and MySQL,
1133
+ structural `contains` (SQLite stores JSON as text and has no containment operator):
1094
1134
 
1095
1135
  ```graphql
1096
1136
  {
@@ -1256,9 +1296,10 @@ columns you group by.
1256
1296
  }
1257
1297
  ```
1258
1298
 
1259
- - `having: { count: … }` filters on the row count; `avg` / `sum` / `min` / `max` /
1260
- `countNonNull` / `countDistinct` take one filter per column, over the same columns the
1261
- matching aggregate type exposes
1299
+ - `having: { count: … }` filters on the row count; `avg` / `sum` / `min` / `max` take one
1300
+ filter per numeric column (the comparison is numeric, so a `max` over a text or date
1301
+ column has no `having` entry even though the aggregate type returns it),
1302
+ `countNonNull` one per column, and `countDistinct` one per column `countDistinct` exposes
1262
1303
  - Every entry in a `having` is ANDed together
1263
1304
  - A group filter does not need to be in the selection — `having: { count: { gt: 3 } }` works
1264
1305
  whether or not you asked for `count`
@@ -1297,6 +1338,8 @@ can count or summarise related rows without fetching them:
1297
1338
  - A parent with no related rows gets `count: 0` and `null` for every other aggregate
1298
1339
  - To-one relations get no aggregate field — there is nothing to aggregate over
1299
1340
  - The field is skipped if its name would collide with a column or another relation
1341
+ - Set `features: { relationAggregates: false }` (globally or per table) to leave these
1342
+ fields out
1300
1343
 
1301
1344
  All parents in a selection are aggregated with a single
1302
1345
  `SELECT <fk>, … WHERE <fk> IN (…) GROUP BY <fk>` per request, so `postsAggregate` on a list of
@@ -1320,7 +1363,8 @@ enum. Rows sharing the same combination of those columns collapse to one:
1320
1363
  ```
1321
1364
 
1322
1365
  - Which row survives each group is decided by `orderBy` — the first one wins. With no
1323
- `orderBy`, that's the lowest primary key
1366
+ `orderBy`, the table's [default ordering](#default-ordering) decides, and failing that the
1367
+ lowest primary key
1324
1368
  - `where` is applied **before** rows are collapsed; `limit` and `offset` are applied
1325
1369
  **after**, so `limit: 10` returns ten distinct rows
1326
1370
  - Several columns are treated as one combined key, not as independent ones
@@ -1339,7 +1383,7 @@ or **SQLite 3.25+**), and a table with no primary key cannot use it.
1339
1383
  SQL gives no ordering guarantee for a query that has no `ORDER BY`, so paging through an
1340
1384
  unordered result can return the same row twice or skip one entirely. To keep pages stable,
1341
1385
  a query that returns only part of a table is ordered by its primary key when the request
1342
- supplies no `orderBy`:
1386
+ supplies no `orderBy` and the table has no [default ordering](#default-ordering):
1343
1387
 
1344
1388
  - list queries with `limit` and/or `offset`
1345
1389
  - `<tableName>Single` queries, which are an implicit `limit 1`
@@ -1347,7 +1391,8 @@ supplies no `orderBy`:
1347
1391
  `orderBy` you do supply, so per-parent slices are deterministic)
1348
1392
 
1349
1393
  An explicit `orderBy` always takes precedence, composite primary keys are ordered by every
1350
- key column, and an unpaginated list query is left unordered so no sort is paid for.
1394
+ key column, and an unpaginated list query of a table with no `defaults` entry is left
1395
+ unordered so no sort is paid for.
1351
1396
 
1352
1397
  ## Default ordering
1353
1398
 
@@ -1434,14 +1479,21 @@ field, and passing it back as `after` resumes strictly after that row.
1434
1479
  window
1435
1480
  - `after` compiles to a keyset predicate built from drizzle's `and`/`or`/`gt`/`lt`
1436
1481
  comparisons (not SQL row-value syntax), so mixed-direction `orderBy` works
1437
- - `NULL`s in ordered columns follow each dialect's native `ORDER BY` placement —
1438
- PostgreSQL sorts them largest (last in `asc`), MySQL and SQLite smallest (first in
1439
- `asc`) — and the predicate matches, so rows with `NULL` in an ordered column are paged
1440
- through, not skipped
1441
- - Dates and bigints round-trip losslessly through the cursor
1482
+ - `NULL`s in ordered columns sort where the entry's `nulls: first | last` says or, without
1483
+ it, at each dialect's native `ORDER BY` placement — PostgreSQL sorts them largest (last
1484
+ in `asc`), MySQL and SQLite smallest (first in `asc`). The predicate matches either way,
1485
+ so rows with `NULL` in an ordered column are paged through, not skipped, and `nulls` is
1486
+ part of the ordering a cursor is tied to
1487
+ - Dates and bigints round-trip losslessly through the cursor, at the column's own precision:
1488
+ a PostgreSQL `timestamp` / `timestamptz` (microseconds unless declared `precision: 3` or
1489
+ less) or a MySQL `datetime` / `timestamp` with `fsp` above 3 holds more than the
1490
+ millisecond JS `Date` that `mode: 'date'` reads it into, so the cursor carries the
1491
+ database's own text of the value instead, and rows a microsecond apart are still distinct
1492
+ pages
1442
1493
  - A cursor is only valid under the ordering it was issued for — reusing it with a
1443
1494
  different `orderBy` (or a malformed/corrupted cursor) returns a `GraphQLError`
1444
- - `after` cannot be combined with `distinct` or with `matchFilterOrder`, and a table with
1495
+ - `after` cannot be combined with `distinct`, with `matchFilterOrder`, or with an `orderBy`
1496
+ that orders through a relation (`cursor` resolves to `null` there), and a table with
1445
1497
  no primary key cannot use cursor pagination (its `cursor` field resolves to `null`)
1446
1498
  - If a table has a real column named `cursor`, the column wins and the meta field is
1447
1499
  skipped
@@ -1595,8 +1647,10 @@ mutation {
1595
1647
  }
1596
1648
  ```
1597
1649
 
1598
- - `connect`, `disconnect` and `set` take the target's own `Filters` input, so they select
1599
- rows the same way a query does. A filter that matches everything is rejected rather than
1650
+ - `connect`, `disconnect` and `set` take the target's own `Filters` input — a list of them
1651
+ on a to-many relation, a single one that must match exactly one row on a to-one — so they
1652
+ select rows the same way a query does. A to-one `disconnect` is just `disconnect: true`,
1653
+ since there is only one row to detach. A filter that matches everything is rejected rather than
1600
1654
  attaching the whole table
1601
1655
  - `set` replaces the relation wholesale — everything attached is detached first, then the
1602
1656
  filters attach. It runs before `create`, so a row created in the same write survives it
@@ -1847,6 +1901,10 @@ Two details worth stating plainly:
1847
1901
  - **A rejection is the default because truncation lies.** Silently cutting a page down to
1848
1902
  `maxLimit` tells a paginating client it has reached the end when it has not. `clampToMax:
1849
1903
  true` opts into that when you would rather never fail a query.
1904
+ - **A negative `limit` or `offset` is always rejected** with `DRIZZLE_INVALID_INPUT_VALUE`,
1905
+ whether or not `limits` is configured — on root lists, single queries, relation fields and
1906
+ `resolveSelection`. SQLite reads `LIMIT -1` as "no limit", so a negative value would
1907
+ otherwise walk straight past `maxLimit`; `0` stays valid and returns no rows.
1850
1908
 
1851
1909
  The policy applies to root list queries and to to-many relation fields, on both the eager and
1852
1910
  the lazy relation paths. A relation takes the policy of the table it *reads*, not its parent's:
@@ -1944,8 +2002,8 @@ const yoga = createYoga({ schema, plugins: armor.protect().plugins });
1944
2002
 
1945
2003
  Armor's `costLimit` is its own document-shape estimate and does not read the `complexity`
1946
2004
  extensions above; the two compose, if you want a cheap static ceiling alongside row-count
1947
- aware pricing. Neither one bounds an individual `limit` argument, so a single unbounded list
1948
- query is still a full table scan — cap `limit` yourself if that matters.
2005
+ aware pricing. Neither one bounds an individual `limit` argument — a list query with no
2006
+ `limit` is still a full table scan unless you set a [`limits`](#bounding-page-size) policy.
1949
2007
 
1950
2008
  ## Authorization
1951
2009
 
@@ -1987,21 +2045,17 @@ request context actually lives.
1987
2045
  or through your own resolver wrapper, and let the ability answer the questions.
1988
2046
 
1989
2047
  What neither can do is **narrow** a query. They permit or deny a field; they do not append
1990
- `WHERE user_id = …` to the SQL a generated resolver runs, and a permitted `users` query still
1991
- returns every tenant's rows. Until row scoping is a first-class option here, the ways to get
1992
- it are, in increasing order of effort:
1993
-
1994
- - supply the tenant predicate as an ordinary `where` argument from a trusted gateway, and
1995
- deny the raw generated fields to everyone else;
1996
- - build the schema by hand from `entities`, exposing only the generated resolvers you have
1997
- wrapped;
1998
- - put the scoped tables behind database row-level security and give the driver the
1999
- request's identity, so the narrowing happens below Drizzle entirely.
2048
+ `WHERE org_id = …` to the SQL a generated resolver runs, so a permitted `posts` query still
2049
+ returns every tenant's rows. That is what [`scope`](#row-scoping-and-multi-tenancy) is for: a
2050
+ per-table predicate built from the request context and ANDed onto every read and write of
2051
+ that table, nested relation fields included. Pair it with
2052
+ [`contextValues`](#columns-the-server-owns) so new rows are stamped with the caller's tenant.
2053
+ The usual split is shield (or CASL) to decide who may reach a field, and `scope` to decide
2054
+ which rows they see through it.
2000
2055
 
2001
2056
  None of this removes anything from introspection either — `deny` hides the data, not the
2002
- shape. If the schema must not so much as mention a table, keep that table out of the Drizzle
2003
- instance you pass to `buildSchema` (a second instance over a narrowed schema object is
2004
- cheap).
2057
+ shape. If the schema must not so much as mention a table or column, leave it out with
2058
+ [`exclude`](#excluding-tables-and-columns).
2005
2059
 
2006
2060
  Nor is it a row bound: a complexity ceiling prices the `limit` a client asks for, it does not
2007
2061
  cap it. Use [`limits`](#bounding-page-size) for that.
@@ -2074,7 +2128,8 @@ to match on prose:
2074
2128
  `DrizzleErrorContext`.
2075
2129
 
2076
2130
  A value the request could not supply is `DRIZZLE_INVALID_INPUT_VALUE` — a date that does not
2077
- parse, a `BigInt` argument that is not an integer, a malformed JSON column value — and a write
2131
+ parse, a `BigInt` argument that is not an integer, a malformed JSON column value, a negative
2132
+ `limit` or `offset` — and a write
2078
2133
  naming something that is not a column of its table is `DRIZZLE_UNKNOWN_COLUMN`. The mirror
2079
2134
  image, a stored value the scalar transporting it cannot represent, is
2080
2135
  `DRIZZLE_UNREPRESENTABLE_VALUE`; it says the data is out of range, not that the request was
@@ -2142,6 +2197,9 @@ are untouched.
2142
2197
  - A document that mixes in mutation fields this build did not generate is left alone: their
2143
2198
  completion cannot be tracked, so no transaction is opened
2144
2199
  - It never nests — an executor supplied on the context wins, as below
2200
+ - A single mutation field can still run in a transaction of its own when it carries a
2201
+ nested write or its table has an `onWrite` hook — those open one regardless of this
2202
+ setting
2145
2203
 
2146
2204
  ### Supplying your own executor
2147
2205
 
@@ -2176,17 +2234,6 @@ example a pooled connection bound to a tenant, or a logging proxy around `db`. B
2176
2234
  key is created with `Symbol.for`, the ESM and CJS builds of this package agree on it when
2177
2235
  both end up loaded in one process.
2178
2236
 
2179
- Or let the library open one per request: `transactions: 'auto'` wraps a document that fires
2180
- more than one generated mutation in a single transaction, committed when the last one
2181
- completes and rolled back if any of them fails.
2182
-
2183
- ```Typescript
2184
- buildSchema(db, { transactions: 'auto' })
2185
- ```
2186
-
2187
- A document with one mutation, or one that selects a mutation field this build did not
2188
- generate, still runs unwrapped — there is nothing to keep atomic.
2189
-
2190
2237
  ## Write hooks
2191
2238
 
2192
2239
  `onWrite` runs a hook **inside the mutation's transaction**, so its writes commit or roll back
@@ -2347,12 +2394,12 @@ Generated schemas resolve nested relations without N+1 query explosions:
2347
2394
  relation in a single round-trip using Drizzle's relational query builder
2348
2395
  (`with:`), including nested relations and per-relation `where` / `orderBy` /
2349
2396
  `limit` / `offset` arguments.
2350
- - **Mutations (PostgreSQL & SQLite)** — after an insert or update, if the selection set
2397
+ - **Mutations (PostgreSQL & SQLite)** — after an insert, upsert, update or `updateMany`, if the selection set
2351
2398
  includes relation fields, the affected rows are re-fetched by primary key through one
2352
2399
  relational query so their relations are eagerly loaded (single- and composite-column
2353
- primary keys are supported). `delete` mutations keep using the request-scoped batch
2354
- loader, since the rows no longer exist to re-fetch. MySQL mutations return only a
2355
- success flag (no row payload), so this step does not apply there.
2400
+ primary keys are supported). `delete` and `restore` mutations keep using the
2401
+ request-scoped batch loader. MySQL mutations return only a success flag (no row
2402
+ payload), so this step does not apply there.
2356
2403
  - **Custom schemas** — each relation field also has a standalone resolver, exported as
2357
2404
  `entities.fieldResolvers[TableName][relationName]`. When you wire generated types
2358
2405
  into your own schema and your root resolver does **not** pre-fetch relations, these
@@ -2369,9 +2416,8 @@ Generated schemas resolve nested relations without N+1 query explosions:
2369
2416
 
2370
2417
  > [!IMPORTANT]
2371
2418
  > Per-parent paginated relations (a to-many relation field with `limit` or `offset`)
2372
- > rely on SQL window functions. These require **PostgreSQL**, **MySQL 8.0+**, or
2373
- > **SQLite 3.25+**. Relations without pagination, and all other query/mutation paths,
2374
- > have no such requirement.
2419
+ > and the `distinct` argument rely on SQL window functions. These require **PostgreSQL**,
2420
+ > **MySQL 8.0+**, or **SQLite 3.25+**. Everything else has no such requirement.
2375
2421
 
2376
2422
  ### Relation nullability
2377
2423
 
@@ -2432,7 +2478,9 @@ const finalSchema = addResolversToSchema({
2432
2478
  excludes that relation from `with:` (and from the mutation eager re-fetch).
2433
2479
 
2434
2480
  Opting a relation out does **not** remove its field — it keeps resolving lazily via the
2435
- request-scoped batch loader, so the field still works even before you override it. Table
2481
+ request-scoped batch loader, so the field still works even before you override it. A
2482
+ many-to-many (`.through()`) relation whose junction has a composite key cannot be loaded
2483
+ lazily (`DRIZZLE_UNSUPPORTED_RELATION`); leave it eager-loaded. Table
2436
2484
  and relation names are the Drizzle schema keys (e.g. `Users`, `posts`), matching the keys
2437
2485
  of `entities.fieldResolvers`.
2438
2486
 
@@ -2475,9 +2523,10 @@ into its nested entry, and leaves `<relation>Aggregate` selections out — they
2475
2523
  `resolveSelection` additionally accepts `single`, `orderBy`, `limit`, `offset` and `after`,
2476
2524
  and returns the rows already remapped to their GraphQL transport forms.
2477
2525
 
2478
- Both take a `typeNameMapper` option. If the schema was built with one, pass the same
2479
- function here — the translation matches selections against GraphQL type names, so a build
2480
- that renames its types has to say so.
2526
+ Both take the naming options the build used — `typeNameMapper`, `derivedTypeNameMapper`,
2527
+ `typeNamePrefix` and `typeNameSuffix`. The translation matches selections against the
2528
+ published GraphQL type names, so a build that renames its types has to say so here too. If
2529
+ the build used `typeNameMapper: 'singularize'`, pass the exported `singularizeMapper`.
2481
2530
 
2482
2531
  ## Identifying generated fields
2483
2532
 
@@ -2548,7 +2597,7 @@ that explains it, where there is one.
2548
2597
  | `mutations` | `true` | `false` omits the `Mutation` type entirely |
2549
2598
  | `features` | see [Choosing what gets generated](#choosing-what-gets-generated) | which operations are generated, per operation |
2550
2599
  | `exclude` | none | [tables and columns left out](#excluding-tables-and-columns) of the generated schema entirely |
2551
- | `relationsDepthLimit` | unlimited | how deep relation fields are generated; `0` gives a flat, columns-only schema |
2600
+ | `relationsDepthLimit` | unlimited | `0` generates no relation fields (a flat, columns-only schema); any other value currently behaves like the default |
2552
2601
  | `prefixes` / `suffixes` / `typeNameMapper` | see [Naming](#naming) | operation and type names |
2553
2602
  | `typeNamePrefix` / `typeNameSuffix` / `derivedTypeNameMapper` | none | [names of the derived types](#renaming-the-derived-types) — filters, inputs, aggregates, shared inputs |
2554
2603
  | `scalars` / `mapColumnType` | built-in detection | [override a column's GraphQL type](#overriding-a-columns-scalar), by name or by rule |
@@ -2556,7 +2605,7 @@ that explains it, where there is one.
2556
2605
  | `enumValueNameMapper` | the stored value | [names the enum's values](#enum-value-names); `'constant'` for CONSTANT_CASE |
2557
2606
  | `describeColumn` / `describeTable` / `describeRelation` | none | [GraphQL descriptions](#documenting-the-schema) |
2558
2607
  | `deprecateColumn` | none | `@deprecated` on a column's output and optional input fields |
2559
- | `conflictDoNothing` | `false` | PostgreSQL only — `ON CONFLICT DO NOTHING` on inserts, which makes `create<Table>Single` nullable |
2608
+ | `conflictDoNothing` | `false` | **Deprecated** — use [`onConflict: { action: NOTHING }`](#upsert) per request. PostgreSQL and SQLite: `ON CONFLICT DO NOTHING` on every insert, which makes `create<Table>Single` nullable; ignored on MySQL |
2560
2609
  | `complexity` | on | [cost hints](#query-cost) in field extensions; `false` turns them off |
2561
2610
  | `transactions` | `'none'` | `'auto'` wraps a multi-mutation request in one [transaction](#transactions) |
2562
2611
  | `limits` | unbounded | [default and maximum page size](#bounding-page-size), globally or per table |