@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/dist/index.d.cts CHANGED
@@ -1,11 +1,214 @@
1
- import { Column, Table, Relation, SQL, One, Many } from 'drizzle-orm';
1
+ import { Column, Table, Relation, SQL, TablesRelationalConfig, One, Many } from 'drizzle-orm';
2
2
  import { MySqlDatabase } from 'drizzle-orm/mysql-core';
3
3
  import { PgAsyncDatabase } from 'drizzle-orm/pg-core';
4
4
  import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
5
- import { GraphQLScalarType, GraphQLOutputType, GraphQLInputType, GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema, GraphQLInputObjectType, GraphQLObjectType, GraphQLNonNull, GraphQLList, GraphQLEnumType } from 'graphql';
5
+ import { GraphQLError, GraphQLScalarType, GraphQLOutputType, GraphQLInputType, GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema, GraphQLInputObjectType, GraphQLObjectType, GraphQLNonNull, GraphQLList, GraphQLEnumType } from 'graphql';
6
6
  import { ResolveTree } from 'graphql-parse-resolve-info';
7
7
  export { GraphQLDate, GraphQLDateTime, GraphQLJSON, GraphQLUUID } from 'graphql-scalars';
8
8
 
9
+ /**
10
+ * The identity a generated field publishes about itself, under `extensions.drizzle`.
11
+ *
12
+ * Every field this library generates carries one, so a wrapper — `graphql-middleware`,
13
+ * envelop, `graphql-shield`, an audit log, a cache invalidator — can ask the schema what a
14
+ * field is instead of reverse-engineering its name. Names are configurable (`prefixes`,
15
+ * `suffixes`, `typeNameMapper`), so name parsing is ambiguous by construction; this is not.
16
+ *
17
+ * This shape is stable API: fields are only ever added to it, never removed or repurposed.
18
+ */
19
+ /** What family of field this is. */
20
+ type DrizzleFieldKind = 'query' | 'mutation' | 'relation' | 'aggregate' | 'type';
21
+ /**
22
+ * What the field does. Arity is carried separately by `single`, so `select` covers both
23
+ * the list query and its `…Single` sibling rather than being spelled twice.
24
+ */
25
+ type DrizzleFieldOperation = 'select' | 'insert' | 'update' | 'updateMany' | 'upsert' | 'delete' | 'restore' | 'aggregate' | 'groupBy' | 'relation' | 'relationAggregate';
26
+ /** The argument holding the rows a field is about, when it has one. */
27
+ type DrizzleTargetArg = 'where' | 'values' | 'updates';
28
+ /** `extensions.drizzle` on a generated field. */
29
+ type DrizzleFieldExtension = {
30
+ /** The Drizzle schema key of the table the field reads or writes. */
31
+ table: string;
32
+ kind: Exclude<DrizzleFieldKind, 'type'>;
33
+ operation: DrizzleFieldOperation;
34
+ /** Whether the field resolves to one row (or one aggregate) rather than a list. */
35
+ single: boolean;
36
+ /**
37
+ * Which argument names the rows: `where` for select/update/delete/aggregate, `values`
38
+ * for insert and upsert, `updates` for the batch update's per-entry `{ where, set }`.
39
+ * Absent when the field takes no such argument.
40
+ */
41
+ targetArg?: DrizzleTargetArg;
42
+ /**
43
+ * Primary-key property names of `table`, in schema order. Empty when the table declares
44
+ * no primary key. Carried here so {@link identifyRows} works from the field alone.
45
+ */
46
+ primaryKey: readonly string[];
47
+ /**
48
+ * For `operation: 'delete'` on a soft-deleting table that opted into `hardDelete`: the
49
+ * field takes a `hard` argument that issues a real `DELETE` rather than writing the marker.
50
+ * Absent everywhere else, so a wrapper can gate purging without parsing arguments.
51
+ */
52
+ hardDelete?: boolean;
53
+ /** For `kind: 'relation'` and `operation: 'relationAggregate'`: the relation's name. */
54
+ relation?: string;
55
+ /** For `kind: 'relation'` and `operation: 'relationAggregate'`: the table it hangs off. */
56
+ parentTable?: string;
57
+ };
58
+ /** `extensions.drizzle` on a generated object type. */
59
+ type DrizzleTypeExtension = {
60
+ /** The Drizzle schema key this type was generated from. */
61
+ table: string;
62
+ kind: 'type';
63
+ /** Primary-key property names of `table`, in schema order. Empty when there is none. */
64
+ primaryKey: readonly string[];
65
+ };
66
+ type DrizzleExtension = DrizzleFieldExtension | DrizzleTypeExtension;
67
+ /** Anything carrying GraphQL `extensions` — a field config, a field, or a named type. */
68
+ type WithExtensions = {
69
+ extensions?: {
70
+ drizzle?: unknown;
71
+ } | null;
72
+ } | null | undefined;
73
+ /**
74
+ * Reads the `drizzle` extension off a field or type, typed. Returns `undefined` for
75
+ * anything this library did not generate, which is the check a wrapper wants before it
76
+ * treats a field as one of ours.
77
+ */
78
+ declare const drizzleExtension: (target: WithExtensions) => DrizzleExtension | undefined;
79
+ /** Narrows to the field form — object types carry `kind: 'type'` and no operation. */
80
+ declare const isDrizzleFieldExtension: (value: DrizzleExtension | undefined) => value is DrizzleFieldExtension;
81
+ /** The rows a field's arguments name. */
82
+ type IdentifiedRows = {
83
+ /** The Drizzle schema key of the table being read or written. */
84
+ table: string;
85
+ /** Primary-key property names, in schema order. */
86
+ primaryKey: readonly string[];
87
+ /** One entry per identified row: the primary-key values, keyed by property name. */
88
+ rows: Record<string, unknown>[];
89
+ /**
90
+ * Whether `rows` is exactly the set the operation affects.
91
+ *
92
+ * `false` means the arguments do not pin the rows down — an insert whose key the
93
+ * database generates, a `where` that filters on something other than the primary key,
94
+ * a plural update with no filter at all, or a table with no primary key. `rows` is then
95
+ * a best-effort subset-free superset: every entry in it is a row the key values name,
96
+ * but other filters may narrow the real set further, and it may be empty.
97
+ */
98
+ complete: boolean;
99
+ };
100
+ /**
101
+ * The rows a generated field's arguments are about, as primary-key values.
102
+ *
103
+ * The row identity of a mutation lives in a different argument for every operation —
104
+ * `where` for update and delete, `values` for insert and upsert, a per-entry `where` inside
105
+ * `updates` for the batch update — and each of those has both a one-row and a many-row
106
+ * spelling. This reads whichever one applies:
107
+ *
108
+ * ```ts
109
+ * const field = info.parentType.getFields()[info.fieldName];
110
+ * const target = identifyRows(field, args);
111
+ * if (target?.complete) await invalidate(target.table, target.rows);
112
+ * ```
113
+ *
114
+ * Returns `undefined` for a field this library did not generate, or an object type. Check
115
+ * `complete` before treating `rows` as the affected set — see {@link IdentifiedRows}.
116
+ */
117
+ declare const identifyRows: (field: WithExtensions, args: Record<string, unknown> | undefined) => IdentifiedRows | undefined;
118
+
119
+ /**
120
+ * The classification of an error drizzle-graphql raised itself, published as
121
+ * `extensions.code`. A consumer maps these onto its own vocabulary; the prose is written for
122
+ * a client to read and may be reworded in any release, so it is not something to match on.
123
+ */
124
+ type DrizzleErrorCode =
125
+ /** A `Single` write's `where` matched more than one row, so nothing was written. */
126
+ 'DRIZZLE_MULTI_ROW_MATCH'
127
+ /** A write that refuses to run unbounded was given no `where`, or one matching every row. */
128
+ | 'DRIZZLE_WHERE_REQUIRED'
129
+ /** The requested `limit` is above the maximum the field's limit policy allows. */
130
+ | 'DRIZZLE_LIMIT_EXCEEDED'
131
+ /** A write's `values` / `set` carried nothing to write. */
132
+ | 'DRIZZLE_NO_VALUES'
133
+ /** An insert reported no row back, so there is nothing to return or attach. */
134
+ | 'DRIZZLE_NO_ROW_RETURNED'
135
+ /** The driver reported no row count for a returnless write. */
136
+ | 'DRIZZLE_ROW_COUNT_UNAVAILABLE'
137
+ /** A nested write was passed to a field that cannot run one. */
138
+ | 'DRIZZLE_NESTED_WRITES_UNSUPPORTED'
139
+ /** A nested `create` / `connect` / `disconnect` / `set` operand was not usable. */
140
+ | 'DRIZZLE_NESTED_WRITE_INVALID'
141
+ /** A `groupBy` named no column, or one that cannot be grouped by. */
142
+ | 'DRIZZLE_INVALID_GROUP_BY'
143
+ /** An `after` cursor did not decode, or was issued for a different ordering. */
144
+ | 'DRIZZLE_INVALID_CURSOR'
145
+ /** A `where` key was not a filter of the table, or a relation was used as one. */
146
+ | 'DRIZZLE_INVALID_FILTER'
147
+ /** A column's update operations input held none, or more than one. */
148
+ | 'DRIZZLE_INVALID_UPDATE_OPERATION'
149
+ /** A configured `scope` hook returned something that is not a predicate. */
150
+ | 'DRIZZLE_INVALID_SCOPE'
151
+ /** An `orderBy` named something that cannot be ordered by in this query. */
152
+ | 'DRIZZLE_INVALID_ORDER_BY'
153
+ /** `distinct` was asked for where it cannot be applied. */
154
+ | 'DRIZZLE_INVALID_DISTINCT'
155
+ /** An `onConflict` named a target or update column the upsert cannot use. */
156
+ | 'DRIZZLE_INVALID_ON_CONFLICT'
157
+ /** The request's shared mutation transaction was rolled back, so this field never ran. */
158
+ | 'DRIZZLE_TRANSACTION_ABORTED';
159
+ /**
160
+ * What an error was about, published as `extensions.drizzle` beside the code — the runtime
161
+ * half of the `extensions.drizzle` markers a generated field carries.
162
+ *
163
+ * `field` is the **generated** field name, which is why it is data rather than prose: a build
164
+ * that republishes these fields under other names (`RenameRootFields`, a stitched gateway, a
165
+ * hand-written façade) would otherwise be telling a client about a field it has never heard
166
+ * of. The name the client asked for is already in the error's `path`.
167
+ */
168
+ type DrizzleErrorContext = {
169
+ /** The Drizzle schema key of the table the field reads or writes. */
170
+ table?: string;
171
+ operation?: DrizzleFieldOperation;
172
+ /** The generated field's own name, pre-rename. */
173
+ field?: string;
174
+ /** For a relation field: the relation's name. */
175
+ relation?: string;
176
+ };
177
+ /**
178
+ * A `GraphQLError` carrying its context as data. The message says what went wrong, in prose
179
+ * meant for a client; `extensions.code` classifies it and `extensions.drizzle` says which
180
+ * table, operation and generated field it came from.
181
+ */
182
+ declare const drizzleError: (message: string, { code, ...context }: DrizzleErrorContext & {
183
+ code: DrizzleErrorCode;
184
+ }) => GraphQLError;
185
+ /**
186
+ * Default for `config.onError`: keeps drizzle-graphql's own errors, which are written for
187
+ * the client, and replaces driver/database errors with a generic message. Their text names
188
+ * tables, columns, constraints and offending values, none of which belongs in a response.
189
+ * The original is preserved on `originalError` for server-side logging.
190
+ */
191
+ declare const defaultErrorMapper: (error: unknown) => unknown;
192
+
193
+ /** {@link BuildSchemaConfig.limits} resolved for one table. `undefined` means no policy. */
194
+ type ResolvedLimitPolicy = {
195
+ defaultLimit: number | undefined;
196
+ maxLimit: number | undefined;
197
+ clampToMax: boolean;
198
+ };
199
+ /**
200
+ * Looks up the limit policy for a table by its Drizzle schema key. Built once per schema by
201
+ * `buildSchema`; `undefined` for a table the caller left unbounded.
202
+ */
203
+ type LimitPolicyFor = (tableName: string) => ResolvedLimitPolicy | undefined;
204
+ /**
205
+ * {@link BuildSchemaConfig.defaults} resolved for one table: the `orderBy` a read falls back
206
+ * to when the request passes none, already in the shape the generated argument has.
207
+ * `undefined` means the table declares no default and keeps whatever order the database
208
+ * returns.
209
+ */
210
+ type DefaultOrderByFor = (tableName: string) => Record<string, any> | undefined;
211
+
9
212
  /**
10
213
  * A scalar override for one column. A bare scalar applies to both directions; the object form
11
214
  * lets output (object type, aggregate min/max) and input (create/update inputs, filter operands)
@@ -171,6 +374,12 @@ type FilterColumnOperatorsCore<TColumn extends Column, TColType = GetColumnGqlDa
171
374
  isEmpty: boolean;
172
375
  isNull: boolean;
173
376
  isNotNull: boolean;
377
+ /**
378
+ * String columns: compares every operator in this object case-insensitively
379
+ * (`lower(column)` against `lower(operand)`). Applies to the operators beside it only —
380
+ * a nested `AND`/`OR`/`NOT` branch sets its own.
381
+ */
382
+ insensitive: boolean;
174
383
  }>;
175
384
  type FiltersCore<TTable extends Table> = Partial<{
176
385
  [Column in keyof TTable['_']['columns']]: FilterColumnOperatorsCore<TTable['_']['columns'][Column]>;
@@ -198,133 +407,55 @@ type OrderByArgs<TTable extends Table> = {
198
407
  };
199
408
  };
200
409
 
201
- /** Optional mapper from table key to singular/plural name pair. Return undefined to use default naming for a table. */
202
- type TypeNameMapper = (tableName: string) => {
203
- singular: string;
204
- plural: string;
205
- } | undefined;
206
410
  /**
207
- * Extracts the join column info from a drizzle-orm v1 Relation object.
208
- * Returns the JS property name of the local column on the parent table and the
209
- * Column object for the foreign column on the target table, or undefined if the
210
- * relation internals are not accessible.
211
- */
212
- declare const extractRelationJoinColumns: (relEntry: TableNamedRelations, parentTable: Table, targetTable: Table) => {
213
- localColPropName: string;
214
- foreignCol: Column;
215
- foreignColPropName: string;
216
- } | undefined;
217
- type RelationResolverFactory = (params: {
218
- tableName: string;
219
- relationName: string;
220
- relEntry: TableNamedRelations;
221
- isOne: boolean;
222
- }) => GraphQLFieldResolver<any, any> | undefined;
223
- /**
224
- * Key on the GraphQL context object under which a caller can place a Drizzle transaction
225
- * (or any other executor: a pooled connection, a logging proxy). Every generated resolver
226
- * reads it at resolve time and runs its statements there instead of on the database the
227
- * schema was built from, which is what lets several mutations in one request share a
228
- * transaction and lets a query see that transaction's uncommitted rows:
229
- *
230
- * ```ts
231
- * await db.transaction(async (tx) => {
232
- * await graphql({ schema, source, contextValue: { [drizzleExecutorKey]: tx } });
233
- * });
234
- * ```
411
+ * What a generated type is, so a name mapper can answer for one kind without matching on the
412
+ * spelling of the default name.
235
413
  *
236
- * Registered with `Symbol.for` so the ESM and CJS builds of this package agree on it when
237
- * both end up loaded in one process.
414
+ * - `object` — a table's row type, the one `typeNameMapper` already names
415
+ * - `filter` / `listRelationFilter` — `<Type>Filters` and the `some`/`none`/`every` wrapper
416
+ * - `orderBy` — `<Type>OrderBy`
417
+ * - `createInput` / `updateInput` / `updateManyInput` / `nestedWriteInput` / `fieldUpdateInput`
418
+ * — the write inputs
419
+ * - `aggregate` — `<Type>Aggregate` and its per-operation children (`operation` names which)
420
+ * - `having` / `groupBy` / `groupKeys` — the group-by family
421
+ * - `onConflict` — the upsert's conflict input
422
+ * - `uniqueKey` — a compound unique constraint's key input
423
+ * - `columnEnum` — an enum naming a table's columns (`distinct`, conflict target, …)
424
+ * - `columnFilter` — a shared per-scalar filter (`StringFilter`, `IntFilter`, …), not per table
425
+ * - `shared` — everything else the build creates once (`OrderNulls`, `DeletedFilter`, …)
238
426
  */
239
- declare const drizzleExecutorKey: unique symbol;
240
- /** The write a hook is attached to. `restore` only exists for a soft-deleting table. */
241
- type WriteOperation = 'insert' | 'update' | 'updateMany' | 'upsert' | 'delete' | 'restore';
242
- /** What a write hook is handed. See {@link BuildSchemaConfig.onWrite}. */
243
- type WriteHookPayload = {
244
- /** Drizzle schema key of the table being written. */
245
- table: string;
246
- operation: WriteOperation;
247
- /** `'before'` runs ahead of the statement, `'after'` once it has returned. */
248
- position: 'before' | 'after';
249
- /** Whether the field writes a single row (`createUsersSingle`) or a list. */
250
- single: boolean;
251
- /** The field's GraphQL arguments, exactly as the resolver received them. */
252
- args: any;
427
+ type GeneratedTypeKind = 'object' | 'filter' | 'listRelationFilter' | 'orderBy' | 'createInput' | 'updateInput' | 'updateManyInput' | 'nestedWriteInput' | 'fieldUpdateInput' | 'aggregate' | 'having' | 'groupBy' | 'groupKeys' | 'onConflict' | 'uniqueKey' | 'columnEnum' | 'columnFilter' | 'shared';
428
+ /** What a name mapper is told about the type it is being asked to name. */
429
+ interface GeneratedTypeInfo {
430
+ /** Which kind of generated type this is. */
431
+ kind: GeneratedTypeKind;
432
+ /** The name the library would use, before any prefix or suffix. */
433
+ defaultName: string;
434
+ /** The table key the type belongs to, where it belongs to one. */
435
+ table?: string;
253
436
  /**
254
- * The rows the write produced, as the database returned them — before the output mapper,
255
- * so values are database-shaped. Empty at the `before` position, and on MySQL, whose
256
- * mutations return no rows.
437
+ * The narrower thing the type is for, where the kind alone does not say: the aggregate
438
+ * operation (`sum`, `avg`, …), the relation a nested-write input is for, the scalar a
439
+ * column filter covers, the columns of a unique key.
257
440
  */
258
- rows: any[];
259
- context: any;
260
- info: any;
261
- /** The executor the mutation itself ran on. Throwing from the hook rolls it back. */
262
- tx: any;
263
- };
264
- type WriteHook = (payload: WriteHookPayload) => void | Promise<void>;
265
- type WriteHookPositions = {
266
- before?: WriteHook;
267
- after?: WriteHook;
268
- };
269
- type WriteHooks = WriteHook | WriteHookPositions;
270
- /** Resolved, per-field hooks: `undefined` when the table registered none. */
271
- type ResolvedWriteHooks = {
272
- before?: WriteHook;
273
- after?: WriteHook;
274
- };
275
- /** Build-time lookup of a table's hooks, installed on {@link TablePolicies}. */
276
- type WriteHookFor = (tableName: string, operation: WriteOperation) => ResolvedWriteHooks | undefined;
277
- /**
278
- * Creates a RelationResolverFactory that generates field-level resolvers for each relation.
279
- * Each resolver:
280
- * 1. Returns pre-fetched data if the parent resolver already included it (eager path, zero cost).
281
- * 2. When limit/offset args are present, falls back to a direct per-item query.
282
- * 3. Otherwise batches all sibling resolver calls within the same GraphQL execution tick
283
- * into a single IN-clause query, eliminating N+1 database round-trips.
284
- *
285
- * `nullOrdering` is the dialect's native `ORDER BY` placement for `NULL` — the keyset
286
- * predicate behind a relation's `after` argument has to match it, or rows with a `NULL` in an
287
- * ordered column would be skipped instead of paged through. It has no sensible default, so
288
- * each dialect builder states it: `'nulls-largest'` for PostgreSQL, `'nulls-smallest'` for
289
- * MySQL and SQLite.
290
- */
291
- declare const createRelationResolverFactory: (db: any, tables: Record<string, Table>, nullOrdering: NullOrdering, filterCtx?: RelationFilterBase, limits?: LimitPolicyFor, policies?: TablePolicies) => RelationResolverFactory;
292
- /** {@link BuildSchemaConfig.limits} resolved for one table. `undefined` means no policy. */
293
- type ResolvedLimitPolicy = {
294
- defaultLimit: number | undefined;
295
- maxLimit: number | undefined;
296
- clampToMax: boolean;
297
- };
298
- /**
299
- * Looks up the limit policy for a table by its Drizzle schema key. Built once per schema by
300
- * `buildSchema`; `undefined` for a table the caller left unbounded.
301
- */
302
- type LimitPolicyFor = (tableName: string) => ResolvedLimitPolicy | undefined;
441
+ operation?: string;
442
+ }
303
443
  /**
304
- * {@link BuildSchemaConfig.defaults} resolved for one table: the `orderBy` a read falls back
305
- * to when the request passes none, already in the shape the generated argument has.
306
- * `undefined` means the table declares no default and keeps whatever order the database
307
- * returns.
444
+ * Renames a generated type. Return `undefined` — or the same name — to keep the default,
445
+ * which is what an unlisted kind should do; anything returned is used verbatim, without the
446
+ * build's prefix or suffix.
308
447
  */
309
- type DefaultOrderByFor = (tableName: string) => Record<string, any> | undefined;
310
- /**
311
- * Compiles an `orderBy` argument into ORDER BY expressions, highest priority first.
312
- *
313
- * A key naming one of the table's columns orders by that column. A key naming a to-one
314
- * relation takes the target table's own OrderBy input and orders by the related row's
315
- * column(s), compiled as a correlated scalar subquery — reusing the aliased-join machinery
316
- * the relation filters are built on — so it works identically on all three dialects and
317
- * inside the relational query builder's aliased CTEs. Priorities share one global space
318
- * across relation boundaries. Each entry may also carry `nulls: first | last`
319
- * (see {@link orderExpressions} for how MySQL emulates it).
320
- *
321
- * An entry may instead set `matchFilterOrder`, which sorts by the column's position in the
322
- * `inArray` list the same request's `where` gives it — `whereArgs` is that `where`.
323
- *
324
- * `ctx` supplies the tables and relation map that relation ordering needs; callers whose
325
- * inputs cannot contain relation keys may omit it.
326
- */
327
- declare const extractOrderBy: <TTable extends Table, TArgs extends OrderByArgs<any> = OrderByArgs<TTable>>(table: TTable, orderArgs: TArgs, ctx?: RelationFilterContext, whereArgs?: Record<string, any>) => SQL[];
448
+ type DerivedTypeNameMapper = (info: GeneratedTypeInfo) => string | undefined;
449
+
450
+ /** Optional mapper from table key to singular/plural name pair. Return undefined to use default naming for a table. */
451
+ type TypeNameMapper = (tableName: string) => {
452
+ singular: string;
453
+ plural: string;
454
+ } | undefined;
455
+
456
+ /** A table's unique-key `where` fields: field name → the constraint's member property names. */
457
+ type UniqueKeyMap = Record<string, readonly string[]>;
458
+
328
459
  /**
329
460
  * Everything `extractFilters` needs to turn a relation key in a `where` argument into a
330
461
  * correlated subquery. Omitted by callers that don't generate relation filters, in which case
@@ -344,13 +475,58 @@ interface RelationFilterContext {
344
475
  aliases?: {
345
476
  n: number;
346
477
  };
478
+ /**
479
+ * The compound-unique-key `where` fields of each table, keyed by table schema key. Only
480
+ * populated when `features.uniqueKeyFilters` generated them — a key field the build did not
481
+ * generate is an unknown filter key like any other.
482
+ */
483
+ uniqueKeys?: Record<string, UniqueKeyMap>;
347
484
  }
348
485
  /**
349
486
  * The build-scoped half of {@link RelationFilterContext}. Created once per generated schema and
350
487
  * handed to every resolver, which adds the table it is filtering.
351
488
  */
352
- type RelationFilterBase = Pick<RelationFilterContext, 'tables' | 'relationMap'>;
489
+ type RelationFilterBase = Pick<RelationFilterContext, 'tables' | 'relationMap' | 'uniqueKeys'>;
353
490
  declare const extractFilters: <TTable extends Table>(table: TTable, tableName: string, filters: Filters<TTable>, relationCtx?: RelationFilterContext) => SQL | undefined;
491
+
492
+ /** The write a hook is attached to. `restore` only exists for a soft-deleting table. */
493
+ type WriteOperation = 'insert' | 'update' | 'updateMany' | 'upsert' | 'delete' | 'restore';
494
+ /** What a write hook is handed. See {@link BuildSchemaConfig.onWrite}. */
495
+ type WriteHookPayload = {
496
+ /** Drizzle schema key of the table being written. */
497
+ table: string;
498
+ operation: WriteOperation;
499
+ /** `'before'` runs ahead of the statement, `'after'` once it has returned. */
500
+ position: 'before' | 'after';
501
+ /** Whether the field writes a single row (`createUsersSingle`) or a list. */
502
+ single: boolean;
503
+ /** The field's GraphQL arguments, exactly as the resolver received them. */
504
+ args: any;
505
+ /**
506
+ * The rows the write produced, as the database returned them — before the output mapper,
507
+ * so values are database-shaped. Empty at the `before` position, and on MySQL, whose
508
+ * mutations return no rows.
509
+ */
510
+ rows: any[];
511
+ context: any;
512
+ info: any;
513
+ /** The executor the mutation itself ran on. Throwing from the hook rolls it back. */
514
+ tx: any;
515
+ };
516
+ type WriteHook = (payload: WriteHookPayload) => void | Promise<void>;
517
+ type WriteHookPositions = {
518
+ before?: WriteHook;
519
+ after?: WriteHook;
520
+ };
521
+ type WriteHooks = WriteHook | WriteHookPositions;
522
+ /** Resolved, per-field hooks: `undefined` when the table registered none. */
523
+ type ResolvedWriteHooks = {
524
+ before?: WriteHook;
525
+ after?: WriteHook;
526
+ };
527
+ /** Build-time lookup of a table's hooks, installed on {@link TablePolicies}. */
528
+ type WriteHookFor = (tableName: string, operation: WriteOperation) => ResolvedWriteHooks | undefined;
529
+
354
530
  /**
355
531
  * A table's row-level scope: the predicate every generated read, update and delete on that
356
532
  * table is narrowed by. See `BuildSchemaConfig.scope`.
@@ -368,10 +544,11 @@ type ScopeFor = (tableName: string) => ScopeHook | undefined;
368
544
  /**
369
545
  * One table's soft-delete convention, resolved against the real column at build time.
370
546
  *
371
- * `nullable` picks the shape of the predicate. A nullable column marks a row deleted by
372
- * holding a value at all (`deletedAt IS NOT NULL`), which is the common timestamp form; a
373
- * non-nullable one marks it by holding `marker` (`isDeleted = true`), so the column has to
374
- * have a constant that means "deleted" and another that means "not deleted".
547
+ * `marker` picks the shape of the predicate. When the config names a constant that means
548
+ * deleted, the predicate compares against it (`isDeleted = true`); otherwise the column marks
549
+ * a row deleted by holding a value at all (`deletedAt IS NOT NULL`), the common timestamp
550
+ * form. A NOT NULL column has no "absent" state, so it must have a marker; a nullable one
551
+ * takes the NULL-means-alive reading only when no marker was configured.
375
552
  */
376
553
  type SoftDeleteInfo = {
377
554
  /** Property name of the column on the drizzle table — also the key on an aliased proxy. */
@@ -382,8 +559,22 @@ type SoftDeleteInfo = {
382
559
  writeDeleted: () => any;
383
560
  /** Written by the restore mutation. */
384
561
  writeRestored: any;
385
- /** Non-nullable form only: the constant that means "this row is deleted". */
562
+ /**
563
+ * The constant that means "this row is deleted", when the config named one. Required on a
564
+ * NOT NULL column; optional on a nullable one, which otherwise reads NULL as alive.
565
+ */
386
566
  marker?: any;
567
+ /**
568
+ * Which reads hide marked rows by default — `'all'` (the table's own fields and every
569
+ * relation pointing at it) or `'root'` (its own fields only). See
570
+ * {@link relationDeletedDefault}.
571
+ */
572
+ scope: 'root' | 'all';
573
+ /**
574
+ * Whether the delete mutations take a `hard` argument that issues a real `DELETE`. Off
575
+ * unless the table opts in, so the generated schema says which tables can be purged.
576
+ */
577
+ hardDelete: boolean;
387
578
  };
388
579
  /** Build-time lookup: the soft-delete convention of a table, if it declares one. */
389
580
  type SoftDeleteFor = (tableName: string) => SoftDeleteInfo | undefined;
@@ -409,6 +600,26 @@ type TablePolicies = {
409
600
  /** See `BuildSchemaConfig.defaults`. */
410
601
  defaultOrderBy?: DefaultOrderByFor;
411
602
  };
603
+
604
+ /**
605
+ * Compiles an `orderBy` argument into ORDER BY expressions, highest priority first.
606
+ *
607
+ * A key naming one of the table's columns orders by that column. A key naming a to-one
608
+ * relation takes the target table's own OrderBy input and orders by the related row's
609
+ * column(s), compiled as a correlated scalar subquery — reusing the aliased-join machinery
610
+ * the relation filters are built on — so it works identically on all three dialects and
611
+ * inside the relational query builder's aliased CTEs. Priorities share one global space
612
+ * across relation boundaries. Each entry may also carry `nulls: first | last`
613
+ * (see {@link orderExpressions} for how MySQL emulates it).
614
+ *
615
+ * An entry may instead set `matchFilterOrder`, which sorts by the column's position in the
616
+ * `inArray` list the same request's `where` gives it — `whereArgs` is that `where`.
617
+ *
618
+ * `ctx` supplies the tables and relation map that relation ordering needs; callers whose
619
+ * inputs cannot contain relation keys may omit it.
620
+ */
621
+ declare const extractOrderBy: <TTable extends Table, TArgs extends OrderByArgs<any> = OrderByArgs<TTable>>(table: TTable, orderArgs: TArgs, ctx?: RelationFilterContext, whereArgs?: Record<string, any>) => SQL[];
622
+
412
623
  /**
413
624
  * Where a dialect's default sort places NULLs relative to non-NULL values:
414
625
  * - `nulls-largest` — PostgreSQL: NULLs sort as the largest values (last in ASC, first in DESC).
@@ -417,13 +628,58 @@ type TablePolicies = {
417
628
  * The keyset predicate has to agree with the dialect's ORDER BY, so each builder passes its own.
418
629
  */
419
630
  type NullOrdering = 'nulls-largest' | 'nulls-smallest';
631
+
420
632
  /**
421
- * Default for `config.onError`: keeps drizzle-graphql's own errors, which are written for
422
- * the client, and replaces driver/database errors with a generic message. Their text names
423
- * tables, columns, constraints and offending values, none of which belongs in a response.
424
- * The original is preserved on `originalError` for server-side logging.
633
+ * Key on the GraphQL context object under which a caller can place a Drizzle transaction
634
+ * (or any other executor: a pooled connection, a logging proxy). Every generated resolver
635
+ * reads it at resolve time and runs its statements there instead of on the database the
636
+ * schema was built from, which is what lets several mutations in one request share a
637
+ * transaction and lets a query see that transaction's uncommitted rows:
638
+ *
639
+ * ```ts
640
+ * await db.transaction(async (tx) => {
641
+ * await graphql({ schema, source, contextValue: { [drizzleExecutorKey]: tx } });
642
+ * });
643
+ * ```
644
+ *
645
+ * Registered with `Symbol.for` so the ESM and CJS builds of this package agree on it when
646
+ * both end up loaded in one process.
425
647
  */
426
- declare const defaultErrorMapper: (error: unknown) => unknown;
648
+ declare const drizzleExecutorKey: unique symbol;
649
+
650
+ /**
651
+ * Extracts the join column info from a drizzle-orm v1 Relation object.
652
+ * Returns the JS property name of the local column on the parent table and the
653
+ * Column object for the foreign column on the target table, or undefined if the
654
+ * relation internals are not accessible.
655
+ */
656
+ declare const extractRelationJoinColumns: (relEntry: TableNamedRelations, parentTable: Table, targetTable: Table) => {
657
+ localColPropName: string;
658
+ foreignCol: Column;
659
+ foreignColPropName: string;
660
+ } | undefined;
661
+ type RelationResolverFactory = (params: {
662
+ tableName: string;
663
+ relationName: string;
664
+ relEntry: TableNamedRelations;
665
+ isOne: boolean;
666
+ }) => GraphQLFieldResolver<any, any> | undefined;
667
+
668
+ /**
669
+ * Creates a RelationResolverFactory that generates field-level resolvers for each relation.
670
+ * Each resolver:
671
+ * 1. Returns pre-fetched data if the parent resolver already included it (eager path, zero cost).
672
+ * 2. When limit/offset args are present, falls back to a direct per-item query.
673
+ * 3. Otherwise batches all sibling resolver calls within the same GraphQL execution tick
674
+ * into a single IN-clause query, eliminating N+1 database round-trips.
675
+ *
676
+ * `nullOrdering` is the dialect's native `ORDER BY` placement for `NULL` — the keyset
677
+ * predicate behind a relation's `after` argument has to match it, or rows with a `NULL` in an
678
+ * ordered column would be skipped instead of paged through. It has no sensible default, so
679
+ * each dialect builder states it: `'nulls-largest'` for PostgreSQL, `'nulls-smallest'` for
680
+ * MySQL and SQLite.
681
+ */
682
+ declare const createRelationResolverFactory: (db: any, tables: Record<string, Table>, nullOrdering: NullOrdering, filterCtx?: RelationFilterBase, limits?: LimitPolicyFor, policies?: TablePolicies) => RelationResolverFactory;
427
683
 
428
684
  type Relations<TTable extends string = string, TConfig extends Record<string, Relation> = Record<string, Relation>> = {
429
685
  table: {
@@ -435,7 +691,19 @@ type Relations<TTable extends string = string, TConfig extends Record<string, Re
435
691
  };
436
692
 
437
693
  type AnyDrizzleDB<TSchema extends Record<string, any>> = PgAsyncDatabase<any, TSchema> | BaseSQLiteDatabase<any, any, TSchema> | MySqlDatabase<any, any, TSchema>;
438
- type ExtractTables<TSchema extends Record<string, Table | unknown>> = {
694
+ /**
695
+ * The tables a database handle knows about, keyed by their schema key.
696
+ *
697
+ * Two shapes reach this type, because drizzle-orm v1 changed what a database's generic slot
698
+ * holds. SQLite and MySQL handles still carry the schema module itself, so the tables are
699
+ * picked out of it directly. PostgreSQL handles carry only the relations config built by
700
+ * `buildRelations` — a `Record<string, { table; name; relations }>` — so the tables are read
701
+ * off its `table` members instead. Without the second branch every PG-derived type collapses
702
+ * to `never`.
703
+ */
704
+ type ExtractTables<TSchema extends Record<string, Table | unknown>> = TSchema extends TablesRelationalConfig ? {
705
+ [K in keyof TSchema]: TSchema[K]['table'] extends infer TTable extends Table ? TTable : never;
706
+ } : {
439
707
  [K in keyof TSchema as TSchema[K] extends Table ? K : never]: TSchema[K] extends Table ? TSchema[K] : never;
440
708
  };
441
709
  type ExtractRelations<TSchema extends Record<string, Table | unknown>> = {
@@ -550,7 +818,7 @@ type DeleteResolver<TTable extends Table, IsReturnless extends boolean> = (sourc
550
818
  type DeleteSingleResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: DeleteSingleArgs<TTable>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
551
819
  type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations extends Record<string, Relations>, TInputs extends Record<string, GraphQLInputObjectType>, TOutputs extends Record<string, GraphQLObjectType>> = {
552
820
  [TName in keyof TSchemaTables as TName extends string ? `${Uncapitalize<TName>}` : never]: TName extends string ? {
553
- type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}SelectItem`]>>>;
821
+ type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[Capitalize<TName>]>>>;
554
822
  args: {
555
823
  offset: {
556
824
  type: GraphQLScalarType<number, number>;
@@ -572,7 +840,7 @@ type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations e
572
840
  } : never;
573
841
  } & {
574
842
  [TName in keyof TSchemaTables as TName extends string ? `${Uncapitalize<TName>}Single` : never]: TName extends string ? {
575
- type: TOutputs[`${Capitalize<TName>}SelectItem`];
843
+ type: TOutputs[Capitalize<TName>];
576
844
  args: {
577
845
  offset: {
578
846
  type: GraphQLScalarType<number, number>;
@@ -599,30 +867,30 @@ type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations e
599
867
  };
600
868
  type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends Record<string, GraphQLInputObjectType>, TOutputs extends Record<string, GraphQLObjectType>, IsReturnless extends boolean> = {
601
869
  [TName in keyof TSchemaTables as TName extends string ? `create${Capitalize<TName>}` : never]: TName extends string ? {
602
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}Item`]>>>;
870
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[Capitalize<TName>]>>>;
603
871
  args: {
604
872
  values: {
605
- type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TInputs[`${Capitalize<TName>}InsertInput`]>>>;
873
+ type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TInputs[`Create${Capitalize<TName>}Input`]>>>;
606
874
  };
607
875
  };
608
876
  resolve: InsertArrResolver<TSchemaTables[TName], IsReturnless>;
609
877
  } : never;
610
878
  } & {
611
879
  [TName in keyof TSchemaTables as TName extends string ? `create${Capitalize<TName>}Single` : never]: TName extends string ? {
612
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[`${Capitalize<TName>}Item`];
880
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[Capitalize<TName>];
613
881
  args: {
614
882
  values: {
615
- type: GraphQLNonNull<TInputs[`${Capitalize<TName>}InsertInput`]>;
883
+ type: GraphQLNonNull<TInputs[`Create${Capitalize<TName>}Input`]>;
616
884
  };
617
885
  };
618
886
  resolve: InsertResolver<TSchemaTables[TName], IsReturnless>;
619
887
  } : never;
620
888
  } & {
621
889
  [TName in keyof TSchemaTables as TName extends string ? `upsert${Capitalize<TName>}` : never]?: TName extends string ? {
622
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}Item`]>>>;
890
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[Capitalize<TName>]>>>;
623
891
  args: {
624
892
  values: {
625
- type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TInputs[`${Capitalize<TName>}InsertInput`]>>>;
893
+ type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TInputs[`Create${Capitalize<TName>}Input`]>>>;
626
894
  };
627
895
  onConflict: {
628
896
  type: GraphQLInputObjectType;
@@ -632,10 +900,10 @@ type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends
632
900
  } : never;
633
901
  } & {
634
902
  [TName in keyof TSchemaTables as TName extends string ? `upsert${Capitalize<TName>}Single` : never]?: TName extends string ? {
635
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[`${Capitalize<TName>}Item`];
903
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[Capitalize<TName>];
636
904
  args: {
637
905
  values: {
638
- type: GraphQLNonNull<TInputs[`${Capitalize<TName>}InsertInput`]>;
906
+ type: GraphQLNonNull<TInputs[`Create${Capitalize<TName>}Input`]>;
639
907
  };
640
908
  onConflict: {
641
909
  type: GraphQLInputObjectType;
@@ -645,10 +913,10 @@ type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends
645
913
  } : never;
646
914
  } & {
647
915
  [TName in keyof TSchemaTables as TName extends string ? `update${Capitalize<TName>}` : never]: TName extends string ? {
648
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}Item`]>>>;
916
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[Capitalize<TName>]>>>;
649
917
  args: {
650
918
  set: {
651
- type: GraphQLNonNull<TInputs[`${Capitalize<TName>}UpdateInput`]>;
919
+ type: GraphQLNonNull<TInputs[`Update${Capitalize<TName>}Input`]>;
652
920
  };
653
921
  where: {
654
922
  type: TInputs[`${Capitalize<TName>}Filters`] extends GraphQLInputObjectType ? TInputs[`${Capitalize<TName>}Filters`] : never;
@@ -658,20 +926,20 @@ type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends
658
926
  } : never;
659
927
  } & {
660
928
  [TName in keyof TSchemaTables as TName extends string ? `update${Capitalize<TName>}Many` : never]: TName extends string ? {
661
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<TOutputs[`${Capitalize<TName>}Item`]>>;
929
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<TOutputs[Capitalize<TName>]>>;
662
930
  args: {
663
931
  updates: {
664
- type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TInputs[`${Capitalize<TName>}UpdateManyInput`]>>>;
932
+ type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TInputs[`Update${Capitalize<TName>}ManyInput`]>>>;
665
933
  };
666
934
  };
667
935
  resolve: UpdateManyResolver<TSchemaTables[TName], IsReturnless>;
668
936
  } : never;
669
937
  } & {
670
938
  [TName in keyof TSchemaTables as TName extends string ? `update${Capitalize<TName>}Single` : never]: TName extends string ? {
671
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[`${Capitalize<TName>}Item`];
939
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[Capitalize<TName>];
672
940
  args: {
673
941
  set: {
674
- type: GraphQLNonNull<TInputs[`${Capitalize<TName>}UpdateInput`]>;
942
+ type: GraphQLNonNull<TInputs[`Update${Capitalize<TName>}Input`]>;
675
943
  };
676
944
  where: {
677
945
  type: GraphQLNonNull<TInputs[`${Capitalize<TName>}Filters`] extends GraphQLInputObjectType ? TInputs[`${Capitalize<TName>}Filters`] : never>;
@@ -681,7 +949,7 @@ type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends
681
949
  } : never;
682
950
  } & {
683
951
  [TName in keyof TSchemaTables as TName extends string ? `delete${Capitalize<TName>}` : never]: TName extends string ? {
684
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}Item`]>>>;
952
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[Capitalize<TName>]>>>;
685
953
  args: {
686
954
  where: {
687
955
  type: TInputs[`${Capitalize<TName>}Filters`] extends GraphQLInputObjectType ? TInputs[`${Capitalize<TName>}Filters`] : never;
@@ -691,7 +959,7 @@ type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends
691
959
  } : never;
692
960
  } & {
693
961
  [TName in keyof TSchemaTables as TName extends string ? `delete${Capitalize<TName>}Single` : never]: TName extends string ? {
694
- type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[`${Capitalize<TName>}Item`];
962
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[Capitalize<TName>];
695
963
  args: {
696
964
  where: {
697
965
  type: GraphQLNonNull<TInputs[`${Capitalize<TName>}Filters`] extends GraphQLInputObjectType ? TInputs[`${Capitalize<TName>}Filters`] : never>;
@@ -700,25 +968,42 @@ type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends
700
968
  resolve: DeleteSingleResolver<TSchemaTables[TName], IsReturnless>;
701
969
  } : never;
702
970
  };
971
+ /**
972
+ * Keys of `entities.inputs`, which is keyed by generated GraphQL input type name.
973
+ *
974
+ * The write inputs are named from the configured `prefixes`, so these keys describe a build
975
+ * that uses the defaults (`insert: 'create'`, `update: 'update'`); a build that overrides a
976
+ * prefix produces correspondingly different keys. Inputs that only exist when a feature is
977
+ * enabled — `${Table}OnConflict` for upsert, `${Table}Having` for group-by — are not listed,
978
+ * since whether they were generated is a runtime decision.
979
+ */
703
980
  type GeneratedInputs<TSchema extends Record<string, Table>> = {
704
- [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}InsertInput` : never]: GraphQLInputObjectType;
981
+ [TName in keyof TSchema as TName extends string ? `Create${Capitalize<TName>}Input` : never]: GraphQLInputObjectType;
705
982
  } & {
706
- [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}UpdateInput` : never]: GraphQLInputObjectType;
983
+ [TName in keyof TSchema as TName extends string ? `Update${Capitalize<TName>}Input` : never]: GraphQLInputObjectType;
707
984
  } & {
708
- [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}UpdateManyInput` : never]: GraphQLInputObjectType;
985
+ [TName in keyof TSchema as TName extends string ? `Update${Capitalize<TName>}ManyInput` : never]: GraphQLInputObjectType;
709
986
  } & {
710
987
  [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}OrderBy` : never]: GraphQLInputObjectType;
711
988
  } & {
712
989
  [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}Filters` : never]: GraphQLInputObjectType;
713
990
  };
991
+ /**
992
+ * Keys of `entities.types`, which is keyed by generated GraphQL type name.
993
+ *
994
+ * This build emits ONE object type per table (`Users`) used for both selects and mutation
995
+ * returns — upstream drizzle-graphql emitted two (`UsersSelectItem` / `UsersItem`), and this
996
+ * type kept those names long after the generator stopped producing them. MySQL still has a
997
+ * shared `MutationReturn` instead of per-table return types, because its writes return no rows.
998
+ */
714
999
  type GeneratedOutputs<TSchema extends Record<string, Table>, IsReturnless extends boolean> = {
715
- [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}SelectItem` : never]: GraphQLObjectType;
1000
+ [TName in keyof TSchema as TName extends string ? Capitalize<TName> : never]: GraphQLObjectType;
716
1001
  } & {
717
1002
  [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}Aggregate` : never]: GraphQLObjectType;
718
1003
  } & (IsReturnless extends true ? {
719
1004
  MutationReturn: GraphQLObjectType;
720
1005
  } : {
721
- [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}Item` : never]: GraphQLObjectType;
1006
+ [TName in keyof TSchema as TName extends string ? Capitalize<TName> : never]: GraphQLObjectType;
722
1007
  });
723
1008
  type GeneratedEntities<TDatabase extends AnyDrizzleDB<TSchema>, TSchema extends Record<string, unknown> = TDatabase extends AnyDrizzleDB<infer ISchema> ? ISchema : never, TSchemaTables extends ExtractTables<TSchema> = ExtractTables<TSchema>, TSchemaRelations extends ExtractRelations<TSchema> = ExtractRelations<TSchema>, TInputs extends GeneratedInputs<TSchemaTables> = GeneratedInputs<TSchemaTables>, TOutputs extends GeneratedOutputs<TSchemaTables, TDatabase extends MySqlDatabase<any, any, any, any> ? true : false> = GeneratedOutputs<TSchemaTables, TDatabase extends MySqlDatabase<any, any, any, any> ? true : false>> = {
724
1009
  queries: QueriesCore<TSchemaTables, TSchemaRelations, TInputs, TOutputs>;
@@ -750,7 +1035,8 @@ type GeneratedData<TDatabase extends AnyDrizzleDB<any>> = {
750
1035
  type FeatureSwitch = boolean | ((tableName: string) => boolean);
751
1036
  /**
752
1037
  * Per-feature switches for what `buildSchema` generates. Every flag defaults to `true` except
753
- * `upsert`, `nestedWrites`, `fieldUpdateOperations`, `countMutations` and `requireWhere`.
1038
+ * `upsert`, `nestedWrites`, `fieldUpdateOperations`, `countMutations`, `requireWhere` and
1039
+ * `uniqueKeyFilters`.
754
1040
  * Each one takes a boolean or a per-table predicate, apart from `nestedWrites`, which is
755
1041
  * build-wide.
756
1042
  * See {@link BuildSchemaConfig.features}.
@@ -885,6 +1171,29 @@ type SchemaFeatures = {
885
1171
  * @default false
886
1172
  */
887
1173
  requireWhere?: FeatureSwitch;
1174
+ /**
1175
+ * One `where` field per compound unique constraint, so a lookup by a natural key names the
1176
+ * key instead of restating its columns:
1177
+ *
1178
+ * ```graphql
1179
+ * stockSingle(where: { itemId_locationId: { itemId: "i1", locationId: "l1" } }) { quantity }
1180
+ * ```
1181
+ *
1182
+ * The field is named after its member columns and every member is non-null, so a half-
1183
+ * supplied key fails query validation rather than becoming a broader filter that returns
1184
+ * whichever row happens to come first. It compiles to the same equalities the columns would
1185
+ * have — the guarantee is in the input type.
1186
+ *
1187
+ * Single-column constraints get no field (`eq` on the column already says it), and a
1188
+ * constraint whose field name is taken by a column or a relation is skipped. The fields live
1189
+ * on `<Type>Filters`, so they are accepted wherever a filter is: the `Single` reads and
1190
+ * writes they are meant for, but also list queries, relation filters and `onConflict.where`.
1191
+ *
1192
+ * Off unless asked for: it adds an input type per compound constraint.
1193
+ *
1194
+ * @default false
1195
+ */
1196
+ uniqueKeyFilters?: FeatureSwitch;
888
1197
  };
889
1198
  /**
890
1199
  * Per-field cost hints for `graphql-query-complexity` (or any estimator that reads
@@ -995,6 +1304,33 @@ type SoftDeleteColumn = string | {
995
1304
  * for a NOT NULL boolean; required for any other NOT NULL column.
996
1305
  */
997
1306
  restoredValue?: any;
1307
+ /**
1308
+ * Which generated reads hide marked rows by default.
1309
+ *
1310
+ * - `'all'` (the default) — the table's own query fields and every relation field that
1311
+ * points at it. A *required* to-one relation is the one exception: hiding the row
1312
+ * there can only produce "Cannot return null for non-nullable field", never a usable
1313
+ * result, so it reads `INCLUDE` unless the request says otherwise.
1314
+ * - `'root'` — only the table's own query fields. Relation fields read marked rows,
1315
+ * which is what "retired, not erased" usually means: keep the row off the pickers and
1316
+ * the lists, keep rendering it on the historical rows that reference it.
1317
+ *
1318
+ * The `deleted` argument is generated either way, so either default can be overridden
1319
+ * per request.
1320
+ */
1321
+ scope?: 'root' | 'all';
1322
+ /**
1323
+ * Whether the table's delete mutations accept `hard: Boolean = false`, which issues a
1324
+ * real `DELETE` instead of writing the marker — emptying the trash, reclaiming a unique
1325
+ * key, clearing a table. Opt-in per table, and the argument is only generated when it is
1326
+ * on, so the schema itself says which tables can be purged.
1327
+ *
1328
+ * A hard delete reads at `INCLUDE`: it reaches already-marked rows, which is the case it
1329
+ * mostly exists for. A `scope` still confines it, exactly as it confines every other write.
1330
+ *
1331
+ * @default false
1332
+ */
1333
+ hardDelete?: boolean;
998
1334
  };
999
1335
 
1000
1336
  /**
@@ -1466,6 +1802,58 @@ type BuildSchemaConfig = {
1466
1802
  singular: string;
1467
1803
  plural: string;
1468
1804
  } | undefined);
1805
+ /**
1806
+ * Renames the types generated *around* a table — the filter, the write inputs, the
1807
+ * order-by, the aggregate family, the group-by types, the conflict input, the shared scalar
1808
+ * filters. `typeNameMapper` names a table's object type and its root fields; those derived
1809
+ * names follow from it by a fixed rule, and this is the hook that overrides the rule.
1810
+ *
1811
+ * Asked once per generated type, with the kind, the name the library would use, and the
1812
+ * table it belongs to where it belongs to one. Return `undefined` — or the same name — to
1813
+ * keep the default, so a mapper that answers for one kind leaves everything else alone:
1814
+ *
1815
+ * ```ts
1816
+ * buildSchema(db, {
1817
+ * derivedTypeNameMapper: ({ kind, table, operation, defaultName }) =>
1818
+ * kind === 'aggregate' && operation ? `${table}Aggregate${operation}` : undefined,
1819
+ * });
1820
+ * ```
1821
+ *
1822
+ * A returned name is used verbatim — {@link BuildSchemaConfig.typeNamePrefix} and
1823
+ * {@link BuildSchemaConfig.typeNameSuffix} do not wrap it, since the more specific of the
1824
+ * two knobs wins.
1825
+ *
1826
+ * The point of it is stitching: a schema merged with a second generator over the same
1827
+ * tables collides on the derived names constantly, because both derive them from the model
1828
+ * name by the same obvious rule. Renaming them here means the collision never exists, rather
1829
+ * than being papered over by a `RenameTypes` transform on the way out.
1830
+ */
1831
+ derivedTypeNameMapper?: DerivedTypeNameMapper;
1832
+ /**
1833
+ * Prepended to every generated type name — object types, filters, write inputs, the
1834
+ * aggregate family, the shared scalar filters, everything — that
1835
+ * {@link BuildSchemaConfig.derivedTypeNameMapper} did not answer for. The one-line way to
1836
+ * make a whole subschema collision-free before stitching it:
1837
+ *
1838
+ * ```ts
1839
+ * buildSchema(db, { typeNamePrefix: 'Db' });
1840
+ * // type DbUsers, input DbUsersFilters, input DbStringFilter, enum DbOrderNulls, …
1841
+ * ```
1842
+ *
1843
+ * Custom scalars, database enum types and `PgGeometryObject` keep their own names: their
1844
+ * definition does not vary between builds, so a gateway merges them rather than colliding
1845
+ * on them. Field and argument names are untouched too — only type names move, so a client's
1846
+ * queries are unaffected apart from the names it spells in variable declarations.
1847
+ *
1848
+ * Must be a valid GraphQL name start (`/^[_A-Za-z][_0-9A-Za-z]*$/`); anything else throws.
1849
+ */
1850
+ typeNamePrefix?: string;
1851
+ /**
1852
+ * Appended to every generated type name that {@link BuildSchemaConfig.derivedTypeNameMapper}
1853
+ * did not answer for. The counterpart of {@link BuildSchemaConfig.typeNamePrefix}; both may
1854
+ * be given at once.
1855
+ */
1856
+ typeNameSuffix?: string;
1469
1857
  /**
1470
1858
  * Controls whether a relation is eagerly pre-fetched via Drizzle's `with:` clause
1471
1859
  * when its parent is loaded through a generated query or mutation.
@@ -1682,110 +2070,6 @@ declare const singularizeMapper: (tableName: string) => {
1682
2070
  plural: string;
1683
2071
  };
1684
2072
 
1685
- /**
1686
- * The identity a generated field publishes about itself, under `extensions.drizzle`.
1687
- *
1688
- * Every field this library generates carries one, so a wrapper — `graphql-middleware`,
1689
- * envelop, `graphql-shield`, an audit log, a cache invalidator — can ask the schema what a
1690
- * field is instead of reverse-engineering its name. Names are configurable (`prefixes`,
1691
- * `suffixes`, `typeNameMapper`), so name parsing is ambiguous by construction; this is not.
1692
- *
1693
- * This shape is stable API: fields are only ever added to it, never removed or repurposed.
1694
- */
1695
- /** What family of field this is. */
1696
- type DrizzleFieldKind = 'query' | 'mutation' | 'relation' | 'aggregate' | 'type';
1697
- /**
1698
- * What the field does. Arity is carried separately by `single`, so `select` covers both
1699
- * the list query and its `…Single` sibling rather than being spelled twice.
1700
- */
1701
- type DrizzleFieldOperation = 'select' | 'insert' | 'update' | 'updateMany' | 'upsert' | 'delete' | 'restore' | 'aggregate' | 'groupBy' | 'relation' | 'relationAggregate';
1702
- /** The argument holding the rows a field is about, when it has one. */
1703
- type DrizzleTargetArg = 'where' | 'values' | 'updates';
1704
- /** `extensions.drizzle` on a generated field. */
1705
- type DrizzleFieldExtension = {
1706
- /** The Drizzle schema key of the table the field reads or writes. */
1707
- table: string;
1708
- kind: Exclude<DrizzleFieldKind, 'type'>;
1709
- operation: DrizzleFieldOperation;
1710
- /** Whether the field resolves to one row (or one aggregate) rather than a list. */
1711
- single: boolean;
1712
- /**
1713
- * Which argument names the rows: `where` for select/update/delete/aggregate, `values`
1714
- * for insert and upsert, `updates` for the batch update's per-entry `{ where, set }`.
1715
- * Absent when the field takes no such argument.
1716
- */
1717
- targetArg?: DrizzleTargetArg;
1718
- /**
1719
- * Primary-key property names of `table`, in schema order. Empty when the table declares
1720
- * no primary key. Carried here so {@link identifyRows} works from the field alone.
1721
- */
1722
- primaryKey: readonly string[];
1723
- /** For `kind: 'relation'` and `operation: 'relationAggregate'`: the relation's name. */
1724
- relation?: string;
1725
- /** For `kind: 'relation'` and `operation: 'relationAggregate'`: the table it hangs off. */
1726
- parentTable?: string;
1727
- };
1728
- /** `extensions.drizzle` on a generated object type. */
1729
- type DrizzleTypeExtension = {
1730
- /** The Drizzle schema key this type was generated from. */
1731
- table: string;
1732
- kind: 'type';
1733
- /** Primary-key property names of `table`, in schema order. Empty when there is none. */
1734
- primaryKey: readonly string[];
1735
- };
1736
- type DrizzleExtension = DrizzleFieldExtension | DrizzleTypeExtension;
1737
- /** Anything carrying GraphQL `extensions` — a field config, a field, or a named type. */
1738
- type WithExtensions = {
1739
- extensions?: {
1740
- drizzle?: unknown;
1741
- } | null;
1742
- } | null | undefined;
1743
- /**
1744
- * Reads the `drizzle` extension off a field or type, typed. Returns `undefined` for
1745
- * anything this library did not generate, which is the check a wrapper wants before it
1746
- * treats a field as one of ours.
1747
- */
1748
- declare const drizzleExtension: (target: WithExtensions) => DrizzleExtension | undefined;
1749
- /** Narrows to the field form — object types carry `kind: 'type'` and no operation. */
1750
- declare const isDrizzleFieldExtension: (value: DrizzleExtension | undefined) => value is DrizzleFieldExtension;
1751
- /** The rows a field's arguments name. */
1752
- type IdentifiedRows = {
1753
- /** The Drizzle schema key of the table being read or written. */
1754
- table: string;
1755
- /** Primary-key property names, in schema order. */
1756
- primaryKey: readonly string[];
1757
- /** One entry per identified row: the primary-key values, keyed by property name. */
1758
- rows: Record<string, unknown>[];
1759
- /**
1760
- * Whether `rows` is exactly the set the operation affects.
1761
- *
1762
- * `false` means the arguments do not pin the rows down — an insert whose key the
1763
- * database generates, a `where` that filters on something other than the primary key,
1764
- * a plural update with no filter at all, or a table with no primary key. `rows` is then
1765
- * a best-effort subset-free superset: every entry in it is a row the key values name,
1766
- * but other filters may narrow the real set further, and it may be empty.
1767
- */
1768
- complete: boolean;
1769
- };
1770
- /**
1771
- * The rows a generated field's arguments are about, as primary-key values.
1772
- *
1773
- * The row identity of a mutation lives in a different argument for every operation —
1774
- * `where` for update and delete, `values` for insert and upsert, a per-entry `where` inside
1775
- * `updates` for the batch update — and each of those has both a one-row and a many-row
1776
- * spelling. This reads whichever one applies:
1777
- *
1778
- * ```ts
1779
- * const field = info.parentType.getFields()[info.fieldName];
1780
- * const target = identifyRows(field, args);
1781
- * if (target?.complete) await invalidate(target.table, target.rows);
1782
- * ```
1783
- *
1784
- * Returns `undefined` for a field this library did not generate, or an object type. Check
1785
- * `complete` before treating `rows` as the affected set — see {@link IdentifiedRows}.
1786
- */
1787
- declare const identifyRows: (field: WithExtensions, args: Record<string, unknown> | undefined) => IdentifiedRows | undefined;
1788
-
1789
2073
  /**
1790
2074
  * A 64-bit integer. Always transported as a decimal string, in both directions, so no value
1791
2075
  * is ever silently rounded by JSON's double-precision numbers. `graphql-scalars`' own
@@ -1813,6 +2097,14 @@ type SelectionOptions = {
1813
2097
  * pass the same mapper here or nothing will match.
1814
2098
  */
1815
2099
  typeNameMapper?: TypeNameMapper;
2100
+ /**
2101
+ * The `derivedTypeNameMapper` / `typeNamePrefix` / `typeNameSuffix` the schema was built
2102
+ * with, when any were configured. Same reason as `typeNameMapper`: the translation matches
2103
+ * selections against the names the build actually published.
2104
+ */
2105
+ derivedTypeNameMapper?: DerivedTypeNameMapper;
2106
+ typeNamePrefix?: string;
2107
+ typeNameSuffix?: string;
1816
2108
  };
1817
2109
  /**
1818
2110
  * Translates a GraphQL selection set into the relational query builder's `with` tree — the
@@ -1872,4 +2164,4 @@ declare const resolveSelection: (info: GraphQLResolveInfo | ResolveTree, options
1872
2164
 
1873
2165
  declare const buildSchema: <TDbClient extends AnyDrizzleDB<any>>(db: TDbClient, config?: BuildSchemaConfig) => GeneratedData<TDbClient>;
1874
2166
 
1875
- export { type AggregateResolver, type AnyDrizzleDB, type BuildSchemaConfig, type ColumnDeprecator, type ColumnDescriber, type ColumnDocInfo, type ColumnTypeMapper, type ColumnTypeMapperInfo, type ComplexityConfig, type ContextValuesConfig, type DefaultOrderByEntry, type DefaultsConfig, type DeleteResolver, type DeleteSingleResolver, type DrizzleExtension, type DrizzleFieldExtension, type DrizzleFieldKind, type DrizzleFieldOperation, type DrizzleTargetArg, type DrizzleTypeExtension, type EnumNameInfo, type EnumNameMapper, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type FeatureSwitch, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, GraphQLBigIntString, GraphQLDecimalString, type IdentifiedRows, type InsertArrResolver, type InsertResolver, type LimitsConfig, type MutationReturnlessResult, type MutationsCore, type OnWriteConfig, type QueriesCore, type RelationDescriber, type RelationResolverFactory, type ResolveSelectionOptions, type RowScope, type RowScopeFilter, type ScalarOverride, type ScalarOverridesConfig, type SchemaDocs, type SchemaExclusions, type SchemaFeatures, type ScopeConfig, type SelectResolver, type SelectSingleResolver, type SelectionOptions, type SoftDeleteColumn, type SoftDeleteConfig, type TableDefaults, type TableDescriber, type TableLimitPolicy, type TableNamedRelations, type UpdateManyArgs, type UpdateManyEntry, type UpdateManyResolver, type UpdateResolver, type UpdateSingleResolver, type UpsertArgs, type UpsertArrResolver, type UpsertConflictArgs, type UpsertResolver, type WriteHook, type WriteHookPayload, type WriteHookPositions, type WriteHooks, type WriteOperation, buildSchema, createRelationResolverFactory, defaultErrorMapper, drizzleExecutorKey, drizzleExtension, extractFilters, extractOrderBy, extractRelationJoinColumns, identifyRows, isDrizzleFieldExtension, resolveSelection, selectionToWith, singularizeMapper };
2167
+ export { type AggregateResolver, type AnyDrizzleDB, type BuildSchemaConfig, type ColumnDeprecator, type ColumnDescriber, type ColumnDocInfo, type ColumnTypeMapper, type ColumnTypeMapperInfo, type ComplexityConfig, type ContextValuesConfig, type DefaultOrderByEntry, type DefaultsConfig, type DeleteResolver, type DeleteSingleResolver, type DrizzleErrorCode, type DrizzleErrorContext, type DrizzleExtension, type DrizzleFieldExtension, type DrizzleFieldKind, type DrizzleFieldOperation, type DrizzleTargetArg, type DrizzleTypeExtension, type EnumNameInfo, type EnumNameMapper, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type FeatureSwitch, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, GraphQLBigIntString, GraphQLDecimalString, type IdentifiedRows, type InsertArrResolver, type InsertResolver, type LimitsConfig, type MutationReturnlessResult, type MutationsCore, type OnWriteConfig, type QueriesCore, type RelationDescriber, type RelationResolverFactory, type ResolveSelectionOptions, type RowScope, type RowScopeFilter, type ScalarOverride, type ScalarOverridesConfig, type SchemaDocs, type SchemaExclusions, type SchemaFeatures, type ScopeConfig, type SelectResolver, type SelectSingleResolver, type SelectionOptions, type SoftDeleteColumn, type SoftDeleteConfig, type TableDefaults, type TableDescriber, type TableLimitPolicy, type TableNamedRelations, type UpdateManyArgs, type UpdateManyEntry, type UpdateManyResolver, type UpdateResolver, type UpdateSingleResolver, type UpsertArgs, type UpsertArrResolver, type UpsertConflictArgs, type UpsertResolver, type WriteHook, type WriteHookPayload, type WriteHookPositions, type WriteHooks, type WriteOperation, buildSchema, createRelationResolverFactory, defaultErrorMapper, drizzleError, drizzleExecutorKey, drizzleExtension, extractFilters, extractOrderBy, extractRelationJoinColumns, identifyRows, isDrizzleFieldExtension, resolveSelection, selectionToWith, singularizeMapper };