@vantreeseba/drizzle-graphql 2.0.0 → 3.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
@@ -2,7 +2,8 @@ import { Table, Column, Relation, SQL, 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 { GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema, GraphQLInputObjectType, GraphQLObjectType, GraphQLNonNull, GraphQLList, GraphQLScalarType } from 'graphql';
5
+ import { GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema, GraphQLInputObjectType, GraphQLObjectType, GraphQLNonNull, GraphQLList, GraphQLScalarType, GraphQLEnumType } from 'graphql';
6
+ export { GraphQLDate, GraphQLDateTime, GraphQLJSON, GraphQLUUID } from 'graphql-scalars';
6
7
 
7
8
  type TableNamedRelations = {
8
9
  relation: Relation;
@@ -89,6 +90,23 @@ type RelationResolverFactory = (params: {
89
90
  relEntry: TableNamedRelations;
90
91
  isOne: boolean;
91
92
  }) => GraphQLFieldResolver<any, any> | undefined;
93
+ /**
94
+ * Key on the GraphQL context object under which a caller can place a Drizzle transaction
95
+ * (or any other executor: a pooled connection, a logging proxy). Every generated resolver
96
+ * reads it at resolve time and runs its statements there instead of on the database the
97
+ * schema was built from, which is what lets several mutations in one request share a
98
+ * transaction and lets a query see that transaction's uncommitted rows:
99
+ *
100
+ * ```ts
101
+ * await db.transaction(async (tx) => {
102
+ * await graphql({ schema, source, contextValue: { [drizzleExecutorKey]: tx } });
103
+ * });
104
+ * ```
105
+ *
106
+ * Registered with `Symbol.for` so the ESM and CJS builds of this package agree on it when
107
+ * both end up loaded in one process.
108
+ */
109
+ declare const drizzleExecutorKey: unique symbol;
92
110
  /**
93
111
  * Creates a RelationResolverFactory that generates field-level resolvers for each relation.
94
112
  * Each resolver:
@@ -97,9 +115,41 @@ type RelationResolverFactory = (params: {
97
115
  * 3. Otherwise batches all sibling resolver calls within the same GraphQL execution tick
98
116
  * into a single IN-clause query, eliminating N+1 database round-trips.
99
117
  */
100
- declare const createRelationResolverFactory: (db: any, tables: Record<string, Table>) => RelationResolverFactory;
118
+ declare const createRelationResolverFactory: (db: any, tables: Record<string, Table>, filterCtx?: RelationFilterBase) => RelationResolverFactory;
101
119
  declare const extractOrderBy: <TTable extends Table, TArgs extends OrderByArgs<any> = OrderByArgs<TTable>>(table: TTable, orderArgs: TArgs) => SQL[];
102
- declare const extractFilters: <TTable extends Table>(table: TTable, tableName: string, filters: Filters<TTable>) => SQL | undefined;
120
+ /**
121
+ * Everything `extractFilters` needs to turn a relation key in a `where` argument into a
122
+ * correlated subquery. Omitted by callers that don't generate relation filters, in which case
123
+ * relation keys can't appear in the input to begin with.
124
+ */
125
+ interface RelationFilterContext {
126
+ /** Every table in the schema, keyed by its schema key. */
127
+ tables: Record<string, Table>;
128
+ /** Relations keyed by table schema key, then relation name. */
129
+ relationMap: Record<string, Record<string, TableNamedRelations>>;
130
+ /**
131
+ * Schema key of the table being filtered. Not always the same as the `tableName` label
132
+ * used in error messages (relation `where` callbacks pass the relation name there).
133
+ */
134
+ tableKey: string;
135
+ /** Shared counter making every subquery alias unique within one extraction. */
136
+ aliases?: {
137
+ n: number;
138
+ };
139
+ }
140
+ /**
141
+ * The build-scoped half of {@link RelationFilterContext}. Created once per generated schema and
142
+ * handed to every resolver, which adds the table it is filtering.
143
+ */
144
+ type RelationFilterBase = Pick<RelationFilterContext, 'tables' | 'relationMap'>;
145
+ declare const extractFilters: <TTable extends Table>(table: TTable, tableName: string, filters: Filters<TTable>, relationCtx?: RelationFilterContext) => SQL | undefined;
146
+ /**
147
+ * Default for `config.onError`: keeps drizzle-graphql's own errors, which are written for
148
+ * the client, and replaces driver/database errors with a generic message. Their text names
149
+ * tables, columns, constraints and offending values, none of which belongs in a response.
150
+ * The original is preserved on `originalError` for server-side logging.
151
+ */
152
+ declare const defaultErrorMapper: (error: unknown) => unknown;
103
153
 
104
154
  type Relations<TTable extends string = string, TConfig extends Record<string, Relation> = Record<string, Relation>> = {
105
155
  table: {
@@ -144,6 +194,22 @@ type UpdateArgs<TTable extends Table> = Partial<{
144
194
  set: GetRemappedTableUpdateDataType<TTable>;
145
195
  where?: Filters<TTable>;
146
196
  }>;
197
+ /**
198
+ * The `onConflict` argument of the generated upsert mutations.
199
+ *
200
+ * `target` and `where` exist on PostgreSQL and SQLite only: MySQL's
201
+ * `ON DUPLICATE KEY UPDATE` fires on whichever unique key was violated and takes no
202
+ * predicate, so neither field is generated there.
203
+ */
204
+ type UpsertConflictArgs<TTable extends Table> = {
205
+ action?: 'UPDATE' | 'NOTHING';
206
+ target?: string[];
207
+ update?: string[];
208
+ where?: Filters<TTable>;
209
+ };
210
+ type UpsertArgs<TTable extends Table, isSingle extends boolean> = InsertArgs<TTable, isSingle> & {
211
+ onConflict?: UpsertConflictArgs<TTable>;
212
+ };
147
213
  type DeleteArgs<TTable extends Table> = {
148
214
  where?: Filters<TTable>;
149
215
  };
@@ -156,6 +222,18 @@ type SelectSingleResolver<TTable extends Table, TTables extends Record<string, T
156
222
  type InsertResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: Partial<InsertArgs<TTable, false>>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? Array<GetRemappedTableDataType<TTable>> : MutationReturnlessResult>;
157
223
  type InsertArrResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: Partial<InsertArgs<TTable, true>>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
158
224
  type UpdateResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: UpdateArgs<TTable>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
225
+ /** Resolver for `upsert<Table>Single`. */
226
+ type UpsertResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: Partial<UpsertArgs<TTable, true>>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
227
+ /** Resolver for `upsert<Table>`. */
228
+ type UpsertArrResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: Partial<UpsertArgs<TTable, false>>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? Array<GetRemappedTableDataType<TTable>> : MutationReturnlessResult>;
229
+ /**
230
+ * Resolver for a table's generated aggregate query (`<plural>Aggregate`).
231
+ * Returns only the requested aggregations: `count` plus per-column `avg` / `sum`
232
+ * (numeric columns, as Float) and `min` / `max` (orderable columns, as the column's type).
233
+ */
234
+ type AggregateResolver<TTable extends Table> = (source: any, args: {
235
+ where?: Filters<TTable>;
236
+ }, context: any, info: GraphQLResolveInfo) => Promise<Record<string, any>>;
159
237
  type DeleteResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: DeleteArgs<TTable>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
160
238
  type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations extends Record<string, Relations>, TInputs extends Record<string, GraphQLInputObjectType>, TOutputs extends Record<string, GraphQLObjectType>> = {
161
239
  [TName in keyof TSchemaTables as TName extends string ? `${Uncapitalize<TName>}` : never]: TName extends string ? {
@@ -173,6 +251,9 @@ type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations e
173
251
  where: {
174
252
  type: TInputs[`${Capitalize<TName>}Filters`] extends GraphQLInputObjectType ? TInputs[`${Capitalize<TName>}Filters`] : never;
175
253
  };
254
+ distinct: {
255
+ type: GraphQLList<GraphQLNonNull<GraphQLEnumType>>;
256
+ };
176
257
  };
177
258
  resolve: SelectResolver<TSchemaTables[TName], TSchemaTables, ExtractTableRelations<TSchemaTables[TName], TSchemaRelations> extends infer R ? R[keyof R] : never>;
178
259
  } : never;
@@ -192,6 +273,16 @@ type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations e
192
273
  };
193
274
  resolve: SelectSingleResolver<TSchemaTables[TName], TSchemaTables, ExtractTableRelations<TSchemaTables[TName], TSchemaRelations> extends infer R ? R[keyof R] : never>;
194
275
  } : never;
276
+ } & {
277
+ [TName in keyof TSchemaTables as TName extends string ? `${Uncapitalize<TName>}Aggregate` : never]: TName extends string ? {
278
+ type: GraphQLNonNull<TOutputs[`${Capitalize<TName>}Aggregate`] extends GraphQLObjectType ? TOutputs[`${Capitalize<TName>}Aggregate`] : GraphQLObjectType>;
279
+ args: {
280
+ where: {
281
+ type: TInputs[`${Capitalize<TName>}Filters`] extends GraphQLInputObjectType ? TInputs[`${Capitalize<TName>}Filters`] : never;
282
+ };
283
+ };
284
+ resolve: AggregateResolver<TSchemaTables[TName]>;
285
+ } : never;
195
286
  };
196
287
  type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends Record<string, GraphQLInputObjectType>, TOutputs extends Record<string, GraphQLObjectType>, IsReturnless extends boolean> = {
197
288
  [TName in keyof TSchemaTables as TName extends string ? `create${Capitalize<TName>}` : never]: TName extends string ? {
@@ -213,6 +304,32 @@ type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends
213
304
  };
214
305
  resolve: InsertResolver<TSchemaTables[TName], IsReturnless>;
215
306
  } : never;
307
+ } & {
308
+ [TName in keyof TSchemaTables as TName extends string ? `upsert${Capitalize<TName>}` : never]?: TName extends string ? {
309
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}Item`]>>>;
310
+ args: {
311
+ values: {
312
+ type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TInputs[`${Capitalize<TName>}InsertInput`]>>>;
313
+ };
314
+ onConflict: {
315
+ type: GraphQLInputObjectType;
316
+ };
317
+ };
318
+ resolve: UpsertArrResolver<TSchemaTables[TName], IsReturnless>;
319
+ } : never;
320
+ } & {
321
+ [TName in keyof TSchemaTables as TName extends string ? `upsert${Capitalize<TName>}Single` : never]?: TName extends string ? {
322
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[`${Capitalize<TName>}Item`];
323
+ args: {
324
+ values: {
325
+ type: GraphQLNonNull<TInputs[`${Capitalize<TName>}InsertInput`]>;
326
+ };
327
+ onConflict: {
328
+ type: GraphQLInputObjectType;
329
+ };
330
+ };
331
+ resolve: UpsertResolver<TSchemaTables[TName], IsReturnless>;
332
+ } : never;
216
333
  } & {
217
334
  [TName in keyof TSchemaTables as TName extends string ? `update${Capitalize<TName>}` : never]: TName extends string ? {
218
335
  type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}Item`]>>>;
@@ -248,6 +365,8 @@ type GeneratedInputs<TSchema extends Record<string, Table>> = {
248
365
  };
249
366
  type GeneratedOutputs<TSchema extends Record<string, Table>, IsReturnless extends boolean> = {
250
367
  [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}SelectItem` : never]: GraphQLObjectType;
368
+ } & {
369
+ [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}Aggregate` : never]: GraphQLObjectType;
251
370
  } & (IsReturnless extends true ? {
252
371
  MutationReturn: GraphQLObjectType;
253
372
  } : {
@@ -272,6 +391,32 @@ type GeneratedData<TDatabase extends AnyDrizzleDB<any>> = {
272
391
  schema: GraphQLSchema;
273
392
  entities: GeneratedEntities<TDatabase>;
274
393
  };
394
+ /**
395
+ * Per-feature switches for what `buildSchema` generates. Every flag defaults to `true`.
396
+ * See {@link BuildSchemaConfig.features}.
397
+ */
398
+ type SchemaFeatures = {
399
+ /** `<plural>Aggregate` root queries and the aggregate output types. @default true */
400
+ aggregates?: boolean;
401
+ /** `<relation>Aggregate` fields on object types, for to-many relations. @default true */
402
+ relationAggregates?: boolean;
403
+ /** The `distinct` argument on list queries. @default true */
404
+ distinct?: boolean;
405
+ /** `create<Table>` / `create<Table>Single` mutations. @default true */
406
+ insert?: boolean;
407
+ /** `update<Table>` mutations. @default true */
408
+ update?: boolean;
409
+ /** `delete<Table>` mutations. @default true */
410
+ delete?: boolean;
411
+ /**
412
+ * `upsert<Table>` / `upsert<Table>Single` mutations — insert, or update the row that
413
+ * already holds the same unique key. Off unless asked for, so an existing schema does
414
+ * not grow new mutations on upgrade.
415
+ *
416
+ * @default false
417
+ */
418
+ upsert?: boolean;
419
+ };
275
420
  type BuildSchemaConfig = {
276
421
  /**
277
422
  * Determines whether generated mutations will be passed to returned schema.
@@ -304,7 +449,7 @@ type BuildSchemaConfig = {
304
449
  /**
305
450
  * Customizes query name prefixes for generated GraphQL operations.
306
451
  *
307
- * @default { insert: 'create', delete: 'delete', update: 'update' }
452
+ * @default { insert: 'create', delete: 'delete', update: 'update', upsert: 'upsert' }
308
453
  */
309
454
  prefixes?: {
310
455
  /** Prefix for insert mutations (e.g., 'users' -> 'createUsers') */
@@ -313,6 +458,8 @@ type BuildSchemaConfig = {
313
458
  delete?: string;
314
459
  /** Prefix for update mutations (e.g., 'users' -> 'updateUsers') */
315
460
  update?: string;
461
+ /** Prefix for upsert mutations (e.g., 'users' -> 'upsertUsers') */
462
+ upsert?: string;
316
463
  };
317
464
  /**
318
465
  * Customizes query name suffixes for generated GraphQL operations.
@@ -328,8 +475,40 @@ type BuildSchemaConfig = {
328
475
  /**
329
476
  * When true, insert mutations will use onConflictDoNothing() to silently
330
477
  * ignore duplicate key violations. Defaults to false (conflicts throw errors).
478
+ *
479
+ * PostgreSQL and SQLite only — MySQL's insert builder has no equivalent, so the flag is
480
+ * ignored there and conflicts keep throwing.
481
+ *
482
+ * @deprecated Build-wide and unconditional: a request cannot opt out of it, and a
483
+ * swallowed insert returns `null` with no indication why. Turn on `features.upsert` and
484
+ * pass `onConflict: { action: NOTHING }` per request instead. This flag keeps working
485
+ * until the next major.
331
486
  */
332
487
  conflictDoNothing?: boolean;
488
+ /**
489
+ * Turns individual generated features off.
490
+ *
491
+ * Every flag defaults to `true` except `upsert`, which is opt-in: a build with no
492
+ * `features` block generates exactly what it generated before this option existed.
493
+ * Turning one off removes both the schema surface it adds and the work behind it.
494
+ *
495
+ * ```ts
496
+ * buildSchema(db, {
497
+ * features: {
498
+ * aggregates: false, // no `<plural>Aggregate` root queries
499
+ * relationAggregates: false, // no `<relation>Aggregate` fields on object types
500
+ * distinct: false, // no `distinct` argument on list queries
501
+ * delete: false, // no delete mutations
502
+ * upsert: true, // opt in to `upsert<Table>` mutations
503
+ * },
504
+ * });
505
+ * ```
506
+ *
507
+ * Turning off every mutation feature omits the `Mutation` type entirely, exactly as
508
+ * `mutations: false` does. Query-side features can all be off — the list and single
509
+ * queries are always generated, so `Query` is never empty.
510
+ */
511
+ features?: SchemaFeatures;
333
512
  /**
334
513
  * Optional mapper from table key to singular/plural name pair.
335
514
  * When provided for a table, overrides the default (table key) naming for GraphQL type names,
@@ -377,8 +556,40 @@ type BuildSchemaConfig = {
377
556
  * @default true
378
557
  */
379
558
  eagerLoadRelations?: boolean | ((tableName: string, relationName: string) => boolean);
559
+ /**
560
+ * Called for every error thrown by a generated resolver, before it reaches the client.
561
+ *
562
+ * Return an error to surface that one instead. Return nothing to fall through to the
563
+ * default handling, which makes this a pure logging hook:
564
+ *
565
+ * ```ts
566
+ * buildSchema(db, { onError: (error) => { logger.error(error) } })
567
+ * ```
568
+ *
569
+ * The default passes through errors drizzle-graphql raises itself (bad filter, missing
570
+ * values, invalid date, …) and replaces everything else — driver and database errors —
571
+ * with a generic `Internal server error`, keeping the original on `originalError` so it
572
+ * is still available to server-side logging. Database messages routinely name tables,
573
+ * columns, constraints and the values that violated them, which is not something a
574
+ * public API should hand back.
575
+ *
576
+ * To surface raw database errors instead (useful in development):
577
+ *
578
+ * ```ts
579
+ * buildSchema(db, { onError: (error) => error as Error })
580
+ * ```
581
+ */
582
+ onError?: (error: unknown) => unknown;
380
583
  };
381
584
 
585
+ /**
586
+ * A 64-bit integer. Always transported as a decimal string, in both directions, so no value
587
+ * is ever silently rounded by JSON's double-precision numbers. `graphql-scalars`' own
588
+ * `GraphQLBigInt` is deliberately not used: it emits numbers for safe integers and strings
589
+ * for everything else, so a client cannot know which it will get.
590
+ */
591
+ declare const GraphQLBigIntString: GraphQLScalarType<string, string>;
592
+
382
593
  declare const buildSchema: <TDbClient extends AnyDrizzleDB<any>>(db: TDbClient, config?: BuildSchemaConfig) => GeneratedData<TDbClient>;
383
594
 
384
- export { type AnyDrizzleDB, type BuildSchemaConfig, type DeleteResolver, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, type InsertArrResolver, type InsertResolver, type MutationReturnlessResult, type MutationsCore, type QueriesCore, type RelationResolverFactory, type SelectResolver, type SelectSingleResolver, type TableNamedRelations, type UpdateResolver, buildSchema, createRelationResolverFactory, extractFilters, extractOrderBy, extractRelationJoinColumns };
595
+ export { type AggregateResolver, type AnyDrizzleDB, type BuildSchemaConfig, type DeleteResolver, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, GraphQLBigIntString, type InsertArrResolver, type InsertResolver, type MutationReturnlessResult, type MutationsCore, type QueriesCore, type RelationResolverFactory, type SchemaFeatures, type SelectResolver, type SelectSingleResolver, type TableNamedRelations, type UpdateResolver, type UpsertArgs, type UpsertArrResolver, type UpsertConflictArgs, type UpsertResolver, buildSchema, createRelationResolverFactory, defaultErrorMapper, drizzleExecutorKey, extractFilters, extractOrderBy, extractRelationJoinColumns };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,8 @@ import { Table, Column, Relation, SQL, 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 { GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema, GraphQLInputObjectType, GraphQLObjectType, GraphQLNonNull, GraphQLList, GraphQLScalarType } from 'graphql';
5
+ import { GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema, GraphQLInputObjectType, GraphQLObjectType, GraphQLNonNull, GraphQLList, GraphQLScalarType, GraphQLEnumType } from 'graphql';
6
+ export { GraphQLDate, GraphQLDateTime, GraphQLJSON, GraphQLUUID } from 'graphql-scalars';
6
7
 
7
8
  type TableNamedRelations = {
8
9
  relation: Relation;
@@ -89,6 +90,23 @@ type RelationResolverFactory = (params: {
89
90
  relEntry: TableNamedRelations;
90
91
  isOne: boolean;
91
92
  }) => GraphQLFieldResolver<any, any> | undefined;
93
+ /**
94
+ * Key on the GraphQL context object under which a caller can place a Drizzle transaction
95
+ * (or any other executor: a pooled connection, a logging proxy). Every generated resolver
96
+ * reads it at resolve time and runs its statements there instead of on the database the
97
+ * schema was built from, which is what lets several mutations in one request share a
98
+ * transaction and lets a query see that transaction's uncommitted rows:
99
+ *
100
+ * ```ts
101
+ * await db.transaction(async (tx) => {
102
+ * await graphql({ schema, source, contextValue: { [drizzleExecutorKey]: tx } });
103
+ * });
104
+ * ```
105
+ *
106
+ * Registered with `Symbol.for` so the ESM and CJS builds of this package agree on it when
107
+ * both end up loaded in one process.
108
+ */
109
+ declare const drizzleExecutorKey: unique symbol;
92
110
  /**
93
111
  * Creates a RelationResolverFactory that generates field-level resolvers for each relation.
94
112
  * Each resolver:
@@ -97,9 +115,41 @@ type RelationResolverFactory = (params: {
97
115
  * 3. Otherwise batches all sibling resolver calls within the same GraphQL execution tick
98
116
  * into a single IN-clause query, eliminating N+1 database round-trips.
99
117
  */
100
- declare const createRelationResolverFactory: (db: any, tables: Record<string, Table>) => RelationResolverFactory;
118
+ declare const createRelationResolverFactory: (db: any, tables: Record<string, Table>, filterCtx?: RelationFilterBase) => RelationResolverFactory;
101
119
  declare const extractOrderBy: <TTable extends Table, TArgs extends OrderByArgs<any> = OrderByArgs<TTable>>(table: TTable, orderArgs: TArgs) => SQL[];
102
- declare const extractFilters: <TTable extends Table>(table: TTable, tableName: string, filters: Filters<TTable>) => SQL | undefined;
120
+ /**
121
+ * Everything `extractFilters` needs to turn a relation key in a `where` argument into a
122
+ * correlated subquery. Omitted by callers that don't generate relation filters, in which case
123
+ * relation keys can't appear in the input to begin with.
124
+ */
125
+ interface RelationFilterContext {
126
+ /** Every table in the schema, keyed by its schema key. */
127
+ tables: Record<string, Table>;
128
+ /** Relations keyed by table schema key, then relation name. */
129
+ relationMap: Record<string, Record<string, TableNamedRelations>>;
130
+ /**
131
+ * Schema key of the table being filtered. Not always the same as the `tableName` label
132
+ * used in error messages (relation `where` callbacks pass the relation name there).
133
+ */
134
+ tableKey: string;
135
+ /** Shared counter making every subquery alias unique within one extraction. */
136
+ aliases?: {
137
+ n: number;
138
+ };
139
+ }
140
+ /**
141
+ * The build-scoped half of {@link RelationFilterContext}. Created once per generated schema and
142
+ * handed to every resolver, which adds the table it is filtering.
143
+ */
144
+ type RelationFilterBase = Pick<RelationFilterContext, 'tables' | 'relationMap'>;
145
+ declare const extractFilters: <TTable extends Table>(table: TTable, tableName: string, filters: Filters<TTable>, relationCtx?: RelationFilterContext) => SQL | undefined;
146
+ /**
147
+ * Default for `config.onError`: keeps drizzle-graphql's own errors, which are written for
148
+ * the client, and replaces driver/database errors with a generic message. Their text names
149
+ * tables, columns, constraints and offending values, none of which belongs in a response.
150
+ * The original is preserved on `originalError` for server-side logging.
151
+ */
152
+ declare const defaultErrorMapper: (error: unknown) => unknown;
103
153
 
104
154
  type Relations<TTable extends string = string, TConfig extends Record<string, Relation> = Record<string, Relation>> = {
105
155
  table: {
@@ -144,6 +194,22 @@ type UpdateArgs<TTable extends Table> = Partial<{
144
194
  set: GetRemappedTableUpdateDataType<TTable>;
145
195
  where?: Filters<TTable>;
146
196
  }>;
197
+ /**
198
+ * The `onConflict` argument of the generated upsert mutations.
199
+ *
200
+ * `target` and `where` exist on PostgreSQL and SQLite only: MySQL's
201
+ * `ON DUPLICATE KEY UPDATE` fires on whichever unique key was violated and takes no
202
+ * predicate, so neither field is generated there.
203
+ */
204
+ type UpsertConflictArgs<TTable extends Table> = {
205
+ action?: 'UPDATE' | 'NOTHING';
206
+ target?: string[];
207
+ update?: string[];
208
+ where?: Filters<TTable>;
209
+ };
210
+ type UpsertArgs<TTable extends Table, isSingle extends boolean> = InsertArgs<TTable, isSingle> & {
211
+ onConflict?: UpsertConflictArgs<TTable>;
212
+ };
147
213
  type DeleteArgs<TTable extends Table> = {
148
214
  where?: Filters<TTable>;
149
215
  };
@@ -156,6 +222,18 @@ type SelectSingleResolver<TTable extends Table, TTables extends Record<string, T
156
222
  type InsertResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: Partial<InsertArgs<TTable, false>>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? Array<GetRemappedTableDataType<TTable>> : MutationReturnlessResult>;
157
223
  type InsertArrResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: Partial<InsertArgs<TTable, true>>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
158
224
  type UpdateResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: UpdateArgs<TTable>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
225
+ /** Resolver for `upsert<Table>Single`. */
226
+ type UpsertResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: Partial<UpsertArgs<TTable, true>>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
227
+ /** Resolver for `upsert<Table>`. */
228
+ type UpsertArrResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: Partial<UpsertArgs<TTable, false>>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? Array<GetRemappedTableDataType<TTable>> : MutationReturnlessResult>;
229
+ /**
230
+ * Resolver for a table's generated aggregate query (`<plural>Aggregate`).
231
+ * Returns only the requested aggregations: `count` plus per-column `avg` / `sum`
232
+ * (numeric columns, as Float) and `min` / `max` (orderable columns, as the column's type).
233
+ */
234
+ type AggregateResolver<TTable extends Table> = (source: any, args: {
235
+ where?: Filters<TTable>;
236
+ }, context: any, info: GraphQLResolveInfo) => Promise<Record<string, any>>;
159
237
  type DeleteResolver<TTable extends Table, IsReturnless extends boolean> = (source: any, args: DeleteArgs<TTable>, context: any, info: GraphQLResolveInfo) => Promise<IsReturnless extends false ? GetRemappedTableDataType<TTable> | undefined : MutationReturnlessResult>;
160
238
  type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations extends Record<string, Relations>, TInputs extends Record<string, GraphQLInputObjectType>, TOutputs extends Record<string, GraphQLObjectType>> = {
161
239
  [TName in keyof TSchemaTables as TName extends string ? `${Uncapitalize<TName>}` : never]: TName extends string ? {
@@ -173,6 +251,9 @@ type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations e
173
251
  where: {
174
252
  type: TInputs[`${Capitalize<TName>}Filters`] extends GraphQLInputObjectType ? TInputs[`${Capitalize<TName>}Filters`] : never;
175
253
  };
254
+ distinct: {
255
+ type: GraphQLList<GraphQLNonNull<GraphQLEnumType>>;
256
+ };
176
257
  };
177
258
  resolve: SelectResolver<TSchemaTables[TName], TSchemaTables, ExtractTableRelations<TSchemaTables[TName], TSchemaRelations> extends infer R ? R[keyof R] : never>;
178
259
  } : never;
@@ -192,6 +273,16 @@ type QueriesCore<TSchemaTables extends Record<string, Table>, TSchemaRelations e
192
273
  };
193
274
  resolve: SelectSingleResolver<TSchemaTables[TName], TSchemaTables, ExtractTableRelations<TSchemaTables[TName], TSchemaRelations> extends infer R ? R[keyof R] : never>;
194
275
  } : never;
276
+ } & {
277
+ [TName in keyof TSchemaTables as TName extends string ? `${Uncapitalize<TName>}Aggregate` : never]: TName extends string ? {
278
+ type: GraphQLNonNull<TOutputs[`${Capitalize<TName>}Aggregate`] extends GraphQLObjectType ? TOutputs[`${Capitalize<TName>}Aggregate`] : GraphQLObjectType>;
279
+ args: {
280
+ where: {
281
+ type: TInputs[`${Capitalize<TName>}Filters`] extends GraphQLInputObjectType ? TInputs[`${Capitalize<TName>}Filters`] : never;
282
+ };
283
+ };
284
+ resolve: AggregateResolver<TSchemaTables[TName]>;
285
+ } : never;
195
286
  };
196
287
  type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends Record<string, GraphQLInputObjectType>, TOutputs extends Record<string, GraphQLObjectType>, IsReturnless extends boolean> = {
197
288
  [TName in keyof TSchemaTables as TName extends string ? `create${Capitalize<TName>}` : never]: TName extends string ? {
@@ -213,6 +304,32 @@ type MutationsCore<TSchemaTables extends Record<string, Table>, TInputs extends
213
304
  };
214
305
  resolve: InsertResolver<TSchemaTables[TName], IsReturnless>;
215
306
  } : never;
307
+ } & {
308
+ [TName in keyof TSchemaTables as TName extends string ? `upsert${Capitalize<TName>}` : never]?: TName extends string ? {
309
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}Item`]>>>;
310
+ args: {
311
+ values: {
312
+ type: GraphQLNonNull<GraphQLList<GraphQLNonNull<TInputs[`${Capitalize<TName>}InsertInput`]>>>;
313
+ };
314
+ onConflict: {
315
+ type: GraphQLInputObjectType;
316
+ };
317
+ };
318
+ resolve: UpsertArrResolver<TSchemaTables[TName], IsReturnless>;
319
+ } : never;
320
+ } & {
321
+ [TName in keyof TSchemaTables as TName extends string ? `upsert${Capitalize<TName>}Single` : never]?: TName extends string ? {
322
+ type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : TOutputs[`${Capitalize<TName>}Item`];
323
+ args: {
324
+ values: {
325
+ type: GraphQLNonNull<TInputs[`${Capitalize<TName>}InsertInput`]>;
326
+ };
327
+ onConflict: {
328
+ type: GraphQLInputObjectType;
329
+ };
330
+ };
331
+ resolve: UpsertResolver<TSchemaTables[TName], IsReturnless>;
332
+ } : never;
216
333
  } & {
217
334
  [TName in keyof TSchemaTables as TName extends string ? `update${Capitalize<TName>}` : never]: TName extends string ? {
218
335
  type: IsReturnless extends true ? TOutputs['MutationReturn'] extends GraphQLObjectType ? TOutputs['MutationReturn'] : never : GraphQLNonNull<GraphQLList<GraphQLNonNull<TOutputs[`${Capitalize<TName>}Item`]>>>;
@@ -248,6 +365,8 @@ type GeneratedInputs<TSchema extends Record<string, Table>> = {
248
365
  };
249
366
  type GeneratedOutputs<TSchema extends Record<string, Table>, IsReturnless extends boolean> = {
250
367
  [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}SelectItem` : never]: GraphQLObjectType;
368
+ } & {
369
+ [TName in keyof TSchema as TName extends string ? `${Capitalize<TName>}Aggregate` : never]: GraphQLObjectType;
251
370
  } & (IsReturnless extends true ? {
252
371
  MutationReturn: GraphQLObjectType;
253
372
  } : {
@@ -272,6 +391,32 @@ type GeneratedData<TDatabase extends AnyDrizzleDB<any>> = {
272
391
  schema: GraphQLSchema;
273
392
  entities: GeneratedEntities<TDatabase>;
274
393
  };
394
+ /**
395
+ * Per-feature switches for what `buildSchema` generates. Every flag defaults to `true`.
396
+ * See {@link BuildSchemaConfig.features}.
397
+ */
398
+ type SchemaFeatures = {
399
+ /** `<plural>Aggregate` root queries and the aggregate output types. @default true */
400
+ aggregates?: boolean;
401
+ /** `<relation>Aggregate` fields on object types, for to-many relations. @default true */
402
+ relationAggregates?: boolean;
403
+ /** The `distinct` argument on list queries. @default true */
404
+ distinct?: boolean;
405
+ /** `create<Table>` / `create<Table>Single` mutations. @default true */
406
+ insert?: boolean;
407
+ /** `update<Table>` mutations. @default true */
408
+ update?: boolean;
409
+ /** `delete<Table>` mutations. @default true */
410
+ delete?: boolean;
411
+ /**
412
+ * `upsert<Table>` / `upsert<Table>Single` mutations — insert, or update the row that
413
+ * already holds the same unique key. Off unless asked for, so an existing schema does
414
+ * not grow new mutations on upgrade.
415
+ *
416
+ * @default false
417
+ */
418
+ upsert?: boolean;
419
+ };
275
420
  type BuildSchemaConfig = {
276
421
  /**
277
422
  * Determines whether generated mutations will be passed to returned schema.
@@ -304,7 +449,7 @@ type BuildSchemaConfig = {
304
449
  /**
305
450
  * Customizes query name prefixes for generated GraphQL operations.
306
451
  *
307
- * @default { insert: 'create', delete: 'delete', update: 'update' }
452
+ * @default { insert: 'create', delete: 'delete', update: 'update', upsert: 'upsert' }
308
453
  */
309
454
  prefixes?: {
310
455
  /** Prefix for insert mutations (e.g., 'users' -> 'createUsers') */
@@ -313,6 +458,8 @@ type BuildSchemaConfig = {
313
458
  delete?: string;
314
459
  /** Prefix for update mutations (e.g., 'users' -> 'updateUsers') */
315
460
  update?: string;
461
+ /** Prefix for upsert mutations (e.g., 'users' -> 'upsertUsers') */
462
+ upsert?: string;
316
463
  };
317
464
  /**
318
465
  * Customizes query name suffixes for generated GraphQL operations.
@@ -328,8 +475,40 @@ type BuildSchemaConfig = {
328
475
  /**
329
476
  * When true, insert mutations will use onConflictDoNothing() to silently
330
477
  * ignore duplicate key violations. Defaults to false (conflicts throw errors).
478
+ *
479
+ * PostgreSQL and SQLite only — MySQL's insert builder has no equivalent, so the flag is
480
+ * ignored there and conflicts keep throwing.
481
+ *
482
+ * @deprecated Build-wide and unconditional: a request cannot opt out of it, and a
483
+ * swallowed insert returns `null` with no indication why. Turn on `features.upsert` and
484
+ * pass `onConflict: { action: NOTHING }` per request instead. This flag keeps working
485
+ * until the next major.
331
486
  */
332
487
  conflictDoNothing?: boolean;
488
+ /**
489
+ * Turns individual generated features off.
490
+ *
491
+ * Every flag defaults to `true` except `upsert`, which is opt-in: a build with no
492
+ * `features` block generates exactly what it generated before this option existed.
493
+ * Turning one off removes both the schema surface it adds and the work behind it.
494
+ *
495
+ * ```ts
496
+ * buildSchema(db, {
497
+ * features: {
498
+ * aggregates: false, // no `<plural>Aggregate` root queries
499
+ * relationAggregates: false, // no `<relation>Aggregate` fields on object types
500
+ * distinct: false, // no `distinct` argument on list queries
501
+ * delete: false, // no delete mutations
502
+ * upsert: true, // opt in to `upsert<Table>` mutations
503
+ * },
504
+ * });
505
+ * ```
506
+ *
507
+ * Turning off every mutation feature omits the `Mutation` type entirely, exactly as
508
+ * `mutations: false` does. Query-side features can all be off — the list and single
509
+ * queries are always generated, so `Query` is never empty.
510
+ */
511
+ features?: SchemaFeatures;
333
512
  /**
334
513
  * Optional mapper from table key to singular/plural name pair.
335
514
  * When provided for a table, overrides the default (table key) naming for GraphQL type names,
@@ -377,8 +556,40 @@ type BuildSchemaConfig = {
377
556
  * @default true
378
557
  */
379
558
  eagerLoadRelations?: boolean | ((tableName: string, relationName: string) => boolean);
559
+ /**
560
+ * Called for every error thrown by a generated resolver, before it reaches the client.
561
+ *
562
+ * Return an error to surface that one instead. Return nothing to fall through to the
563
+ * default handling, which makes this a pure logging hook:
564
+ *
565
+ * ```ts
566
+ * buildSchema(db, { onError: (error) => { logger.error(error) } })
567
+ * ```
568
+ *
569
+ * The default passes through errors drizzle-graphql raises itself (bad filter, missing
570
+ * values, invalid date, …) and replaces everything else — driver and database errors —
571
+ * with a generic `Internal server error`, keeping the original on `originalError` so it
572
+ * is still available to server-side logging. Database messages routinely name tables,
573
+ * columns, constraints and the values that violated them, which is not something a
574
+ * public API should hand back.
575
+ *
576
+ * To surface raw database errors instead (useful in development):
577
+ *
578
+ * ```ts
579
+ * buildSchema(db, { onError: (error) => error as Error })
580
+ * ```
581
+ */
582
+ onError?: (error: unknown) => unknown;
380
583
  };
381
584
 
585
+ /**
586
+ * A 64-bit integer. Always transported as a decimal string, in both directions, so no value
587
+ * is ever silently rounded by JSON's double-precision numbers. `graphql-scalars`' own
588
+ * `GraphQLBigInt` is deliberately not used: it emits numbers for safe integers and strings
589
+ * for everything else, so a client cannot know which it will get.
590
+ */
591
+ declare const GraphQLBigIntString: GraphQLScalarType<string, string>;
592
+
382
593
  declare const buildSchema: <TDbClient extends AnyDrizzleDB<any>>(db: TDbClient, config?: BuildSchemaConfig) => GeneratedData<TDbClient>;
383
594
 
384
- export { type AnyDrizzleDB, type BuildSchemaConfig, type DeleteResolver, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, type InsertArrResolver, type InsertResolver, type MutationReturnlessResult, type MutationsCore, type QueriesCore, type RelationResolverFactory, type SelectResolver, type SelectSingleResolver, type TableNamedRelations, type UpdateResolver, buildSchema, createRelationResolverFactory, extractFilters, extractOrderBy, extractRelationJoinColumns };
595
+ export { type AggregateResolver, type AnyDrizzleDB, type BuildSchemaConfig, type DeleteResolver, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, GraphQLBigIntString, type InsertArrResolver, type InsertResolver, type MutationReturnlessResult, type MutationsCore, type QueriesCore, type RelationResolverFactory, type SchemaFeatures, type SelectResolver, type SelectSingleResolver, type TableNamedRelations, type UpdateResolver, type UpsertArgs, type UpsertArrResolver, type UpsertConflictArgs, type UpsertResolver, buildSchema, createRelationResolverFactory, defaultErrorMapper, drizzleExecutorKey, extractFilters, extractOrderBy, extractRelationJoinColumns };