@pothos/plugin-drizzle 0.18.1 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +357 -0
  2. package/README.md +1199 -563
  3. package/dts/connection-helpers.d.ts +18 -8
  4. package/dts/connection-helpers.d.ts.map +1 -1
  5. package/dts/drizzle-field-builder.d.ts +4 -11
  6. package/dts/drizzle-field-builder.d.ts.map +1 -1
  7. package/dts/global-types.d.ts.map +1 -1
  8. package/dts/index.d.ts.map +1 -1
  9. package/dts/model-loader.d.ts +18 -17
  10. package/dts/model-loader.d.ts.map +1 -1
  11. package/dts/types.d.ts +82 -22
  12. package/dts/types.d.ts.map +1 -1
  13. package/dts/utils/adapter.d.ts +39 -0
  14. package/dts/utils/adapter.d.ts.map +1 -0
  15. package/dts/utils/await-selections.d.ts +12 -0
  16. package/dts/utils/await-selections.d.ts.map +1 -0
  17. package/dts/utils/config.d.ts +23 -3
  18. package/dts/utils/config.d.ts.map +1 -1
  19. package/dts/utils/cursors.d.ts +6 -6
  20. package/dts/utils/cursors.d.ts.map +1 -1
  21. package/dts/utils/map-query.d.ts +22 -21
  22. package/dts/utils/map-query.d.ts.map +1 -1
  23. package/dts/utils/path-info.d.ts +13 -0
  24. package/dts/utils/path-info.d.ts.map +1 -0
  25. package/dts/utils/relation-filter.d.ts +48 -0
  26. package/dts/utils/relation-filter.d.ts.map +1 -0
  27. package/dts/utils/selections.d.ts +2 -21
  28. package/dts/utils/selections.d.ts.map +1 -1
  29. package/esm/connection-helpers.d.ts +18 -8
  30. package/esm/connection-helpers.d.ts.map +1 -1
  31. package/esm/connection-helpers.js +52 -19
  32. package/esm/connection-helpers.js.map +1 -1
  33. package/esm/drizzle-field-builder.d.ts +4 -11
  34. package/esm/drizzle-field-builder.d.ts.map +1 -1
  35. package/esm/drizzle-field-builder.js +138 -96
  36. package/esm/drizzle-field-builder.js.map +1 -1
  37. package/esm/field-builder.js +61 -47
  38. package/esm/field-builder.js.map +1 -1
  39. package/esm/global-types.d.ts.map +1 -1
  40. package/esm/index.d.ts.map +1 -1
  41. package/esm/index.js +37 -61
  42. package/esm/index.js.map +1 -1
  43. package/esm/model-loader.d.ts +18 -17
  44. package/esm/model-loader.d.ts.map +1 -1
  45. package/esm/model-loader.js +76 -45
  46. package/esm/model-loader.js.map +1 -1
  47. package/esm/schema-builder.js +40 -149
  48. package/esm/schema-builder.js.map +1 -1
  49. package/esm/types.d.ts +82 -22
  50. package/esm/types.d.ts.map +1 -1
  51. package/esm/types.js.map +1 -1
  52. package/esm/utils/adapter.d.ts +39 -0
  53. package/esm/utils/adapter.d.ts.map +1 -0
  54. package/esm/utils/adapter.js +137 -0
  55. package/esm/utils/adapter.js.map +1 -0
  56. package/esm/utils/await-selections.d.ts +12 -0
  57. package/esm/utils/await-selections.d.ts.map +1 -0
  58. package/esm/utils/await-selections.js +19 -0
  59. package/esm/utils/await-selections.js.map +1 -0
  60. package/esm/utils/config.d.ts +23 -3
  61. package/esm/utils/config.d.ts.map +1 -1
  62. package/esm/utils/config.js +1 -4
  63. package/esm/utils/config.js.map +1 -1
  64. package/esm/utils/cursors.d.ts +6 -6
  65. package/esm/utils/cursors.d.ts.map +1 -1
  66. package/esm/utils/cursors.js +75 -92
  67. package/esm/utils/cursors.js.map +1 -1
  68. package/esm/utils/map-query.d.ts +22 -21
  69. package/esm/utils/map-query.d.ts.map +1 -1
  70. package/esm/utils/map-query.js +40 -354
  71. package/esm/utils/map-query.js.map +1 -1
  72. package/esm/utils/path-info.d.ts +13 -0
  73. package/esm/utils/path-info.d.ts.map +1 -0
  74. package/esm/utils/path-info.js +37 -0
  75. package/esm/utils/path-info.js.map +1 -0
  76. package/esm/utils/relation-filter.d.ts +48 -0
  77. package/esm/utils/relation-filter.d.ts.map +1 -0
  78. package/esm/utils/relation-filter.js +92 -0
  79. package/esm/utils/relation-filter.js.map +1 -0
  80. package/esm/utils/selections.d.ts +2 -21
  81. package/esm/utils/selections.d.ts.map +1 -1
  82. package/esm/utils/selections.js +1 -133
  83. package/esm/utils/selections.js.map +1 -1
  84. package/lib/connection-helpers.js +51 -18
  85. package/lib/connection-helpers.js.map +1 -1
  86. package/lib/drizzle-field-builder.js +136 -94
  87. package/lib/drizzle-field-builder.js.map +1 -1
  88. package/lib/field-builder.js +58 -44
  89. package/lib/field-builder.js.map +1 -1
  90. package/lib/index.js +36 -60
  91. package/lib/index.js.map +1 -1
  92. package/lib/model-loader.js +76 -45
  93. package/lib/model-loader.js.map +1 -1
  94. package/lib/schema-builder.js +40 -149
  95. package/lib/schema-builder.js.map +1 -1
  96. package/lib/types.js.map +1 -1
  97. package/lib/utils/adapter.js +147 -0
  98. package/lib/utils/adapter.js.map +1 -0
  99. package/lib/utils/await-selections.js +20 -0
  100. package/lib/utils/await-selections.js.map +1 -0
  101. package/lib/utils/config.js +1 -4
  102. package/lib/utils/config.js.map +1 -1
  103. package/lib/utils/cursors.js +67 -90
  104. package/lib/utils/cursors.js.map +1 -1
  105. package/lib/utils/map-query.js +28 -359
  106. package/lib/utils/map-query.js.map +1 -1
  107. package/lib/utils/path-info.js +39 -0
  108. package/lib/utils/path-info.js.map +1 -0
  109. package/lib/utils/relation-filter.js +91 -0
  110. package/lib/utils/relation-filter.js.map +1 -0
  111. package/lib/utils/selections.js +3 -158
  112. package/lib/utils/selections.js.map +1 -1
  113. package/package.json +10 -8
  114. package/src/connection-helpers.ts +168 -54
  115. package/src/drizzle-field-builder.ts +296 -176
  116. package/src/field-builder.ts +110 -77
  117. package/src/global-types.ts +0 -32
  118. package/src/index.ts +42 -68
  119. package/src/model-loader.ts +110 -71
  120. package/src/schema-builder.ts +76 -169
  121. package/src/types.ts +178 -46
  122. package/src/utils/adapter.ts +197 -0
  123. package/src/utils/await-selections.ts +31 -0
  124. package/src/utils/config.ts +1 -5
  125. package/src/utils/cursors.ts +84 -83
  126. package/src/utils/map-query.ts +51 -734
  127. package/src/utils/path-info.ts +40 -0
  128. package/src/utils/relation-filter.ts +185 -0
  129. package/src/utils/selections.ts +2 -207
  130. package/dts/utils/deep-equal.d.ts +0 -2
  131. package/dts/utils/deep-equal.d.ts.map +0 -1
  132. package/dts/utils/drizzle-graphql.d.ts +0 -2
  133. package/dts/utils/drizzle-graphql.d.ts.map +0 -1
  134. package/dts/utils/loader-map.d.ts +0 -11
  135. package/dts/utils/loader-map.d.ts.map +0 -1
  136. package/dts/utils/usage.d.ts +0 -5
  137. package/dts/utils/usage.d.ts.map +0 -1
  138. package/esm/utils/deep-equal.d.ts +0 -2
  139. package/esm/utils/deep-equal.d.ts.map +0 -1
  140. package/esm/utils/deep-equal.js +0 -45
  141. package/esm/utils/deep-equal.js.map +0 -1
  142. package/esm/utils/drizzle-graphql.d.ts +0 -2
  143. package/esm/utils/drizzle-graphql.d.ts.map +0 -1
  144. package/esm/utils/drizzle-graphql.js +0 -232
  145. package/esm/utils/drizzle-graphql.js.map +0 -1
  146. package/esm/utils/loader-map.d.ts +0 -11
  147. package/esm/utils/loader-map.d.ts.map +0 -1
  148. package/esm/utils/loader-map.js +0 -35
  149. package/esm/utils/loader-map.js.map +0 -1
  150. package/esm/utils/usage.d.ts +0 -5
  151. package/esm/utils/usage.d.ts.map +0 -1
  152. package/esm/utils/usage.js +0 -59
  153. package/esm/utils/usage.js.map +0 -1
  154. package/lib/utils/deep-equal.js +0 -55
  155. package/lib/utils/deep-equal.js.map +0 -1
  156. package/lib/utils/drizzle-graphql.js +0 -236
  157. package/lib/utils/drizzle-graphql.js.map +0 -1
  158. package/lib/utils/loader-map.js +0 -56
  159. package/lib/utils/loader-map.js.map +0 -1
  160. package/lib/utils/usage.js +0 -83
  161. package/lib/utils/usage.js.map +0 -1
  162. package/src/utils/deep-equal.ts +0 -57
  163. package/src/utils/drizzle-graphql.ts +0 -274
  164. package/src/utils/loader-map.ts +0 -49
  165. package/src/utils/usage.ts +0 -62
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type ArgumentRef,
3
+ type CheckAsyncSelection,
3
4
  type DistributeOmit,
4
5
  type FieldKind,
5
6
  type FieldMap,
@@ -14,6 +15,7 @@ import {
14
15
  type InterfaceRef,
15
16
  type InterfaceTypeOptions,
16
17
  type ListResolveValue,
18
+ type MaybeAsyncSelection,
17
19
  type MaybePromise,
18
20
  type Merge,
19
21
  type Normalize,
@@ -26,6 +28,7 @@ import {
26
28
  type TypeParam,
27
29
  typeBrandKey,
28
30
  } from '@pothos/core';
31
+ import type { IndirectInclude, IndirectPathSegment, PathSegment } from '@pothos/selection-mapper';
29
32
  import type {
30
33
  AnyRelations,
31
34
  BuildQueryResult,
@@ -41,9 +44,14 @@ import type {
41
44
  import type { FieldNode, GraphQLResolveInfo } from 'graphql';
42
45
  import type { DrizzleObjectFieldBuilder } from './drizzle-field-builder.js';
43
46
  import type { DrizzleRef } from './interface-ref.js';
44
- import type { IndirectInclude } from './utils/map-query.js';
45
47
  import type { SelectionMap } from './utils/selections.js';
46
48
 
49
+ /**
50
+ * A segment of a `path` given to `queryFromInfo` or `nestedSelection`: a field name, or
51
+ * `{ name, type }` to pin the type the field must be selected under (a fragment on it).
52
+ */
53
+ export type { IndirectInclude, IndirectPathSegment, PathSegment };
54
+
47
55
  export interface FieldPathInfo {
48
56
  field: string;
49
57
  alias: string;
@@ -56,12 +64,28 @@ export interface PathInfo {
56
64
  segments: FieldPathInfo[];
57
65
  }
58
66
 
59
- export type DrizzleClient<TCountSource = Table | SQL | SQLWrapper> = {
67
+ export type DrizzleClient<
68
+ TCountSource = Table | SQL | SQLWrapper,
69
+ TTable = Extract<TCountSource, Table>,
70
+ > = {
60
71
  readonly _: {
61
72
  readonly relations: AnyRelations;
62
73
  };
63
- query: {};
74
+ query: Record<
75
+ string,
76
+ {
77
+ // Each table accepts its own schema-specific config. The loader supplies that config.
78
+ findMany: (config: never) => PromiseLike<Record<string, unknown>[]>;
79
+ }
80
+ >;
64
81
  $count: (source: TCountSource, filter?: SQL) => SQL<number>;
82
+ /** Core SQL construction used for relation predicates and counts; it does not execute a query. */
83
+ select: (fields: Record<string, SQL>) => {
84
+ from(table: TTable): {
85
+ innerJoin(table: TTable, on: SQL): { where: (filter?: SQL) => SQLWrapper };
86
+ where: (filter?: SQL) => SQLWrapper;
87
+ };
88
+ };
65
89
  };
66
90
 
67
91
  type GetTableConfigFn<TTable = Table> = (table: TTable) => {
@@ -78,13 +102,20 @@ type AnyRelationTable<Types extends SchemaTypes> =
78
102
  Types['DrizzleRelations'][keyof Types['DrizzleRelations']]['table'];
79
103
 
80
104
  type DrizzleClientForTypes<Types extends SchemaTypes> = DrizzleClient<
81
- AnyRelationTable<Types> | SQL | SQLWrapper
105
+ AnyRelationTable<Types> | SQL | SQLWrapper,
106
+ Table extends AnyRelationTable<Types> ? Table : never
82
107
  >;
83
108
 
84
109
  type DrizzlePluginBaseOptions = {
85
110
  maxConnectionSize?: number;
86
111
  defaultConnectionSize?: number;
87
112
  skipDeferredFragments?: boolean;
113
+ /**
114
+ * When `true` (the default), the `totalCount` of a `relatedConnection` applies the `where`
115
+ * returned by the field's `query`, so it counts the same rows the connection paginates. Set to
116
+ * `false` to count every related row regardless of the filter.
117
+ */
118
+ filterConnectionTotalCount?: boolean;
88
119
  };
89
120
 
90
121
  export type DrizzlePluginOptions<Types extends SchemaTypes> = DrizzlePluginBaseOptions &
@@ -305,7 +336,7 @@ export type DrizzleObjectFieldOptions<
305
336
  Record<string, unknown> &
306
337
  // biome-ignore lint/suspicious/noExplicitAny: this is fine
307
338
  Select extends (...args: any[]) => infer R
308
- ? R & { columns: {} }
339
+ ? Awaited<R> & { columns: {} }
309
340
  : Select & { columns: {} }
310
341
  > &
311
342
  ParentShape,
@@ -329,21 +360,98 @@ export type DrizzleObjectFieldOptions<
329
360
  Args,
330
361
  ResolveReturnShape
331
362
  > & {
363
+ /**
364
+ * What the field needs from its parent row. With `AsyncSelections: true` the function may be
365
+ * async, and `nestedSelection` is then a promise when a selection beneath it is async, and
366
+ * must be awaited.
367
+ */
332
368
  select?: Select &
369
+ CheckAsyncSelection<Types, Select> &
333
370
  (
334
371
  | DBQueryConfig<'one', Types['DrizzleRelations'], ExtractTable<Types, ParentShape>>
335
372
  | ((
336
373
  args: InputShapeFromFields<Args>,
337
374
  ctx: Types['Context'],
338
- nestedSelection: (<Selection extends boolean | {}>(
339
- selection?: Selection,
340
- path?: string[],
341
- ) => Selection) &
342
- PathInfo,
343
- ) => DBQueryConfig<'one', Types['DrizzleRelations'], ExtractTable<Types, ParentShape>>)
375
+ nestedSelection: NestedSelectionFn<Types, Type, Args>,
376
+ ) => MaybeAsyncSelection<
377
+ Types,
378
+ DBQueryConfig<'one', Types['DrizzleRelations'], ExtractTable<Types, ParentShape>>
379
+ >)
344
380
  );
345
381
  };
346
382
 
383
+ /** The table a field's type param names: a drizzle ref, or a list of one. */
384
+ export type TableForTypeParam<Types extends SchemaTypes, Type> = Type extends [infer Item]
385
+ ? TableForTypeParam<Types, Item>
386
+ : // biome-ignore lint/suspicious/noExplicitAny: matching against any ref
387
+ Type extends DrizzleRef<any, infer Table>
388
+ ? Table & keyof Types['DrizzleRelations']
389
+ : never;
390
+
391
+ /**
392
+ * The query config for the table a field's type param names (a `many` config for a list field),
393
+ * or never when the type has no table.
394
+ */
395
+ export type QueryForTypeParam<Types extends SchemaTypes, Type> =
396
+ TableForTypeParam<Types, Type> extends infer Table
397
+ ? [Table] extends [never]
398
+ ? never
399
+ : DBQueryConfig<
400
+ Type extends [unknown] ? 'many' : 'one',
401
+ Types['DrizzleRelations'],
402
+ Types['DrizzleRelations'][Table & keyof Types['DrizzleRelations']]
403
+ >
404
+ : never;
405
+
406
+ /**
407
+ * What `nestedSelection` returns: the query config for the field's table, keeping the keys of
408
+ * the given selection as they were given (so `columns` in it still narrow the parent shape).
409
+ * With no selection, or `true`, it is the config itself. A field whose type has no table keeps
410
+ * the selection it was given.
411
+ */
412
+ export type NestedSelectionResult<Types extends SchemaTypes, Type, Selection> = [
413
+ QueryForTypeParam<Types, Type>,
414
+ ] extends [never]
415
+ ? Selection
416
+ : Selection extends boolean
417
+ ? QueryForTypeParam<Types, Type>
418
+ : Normalize<Omit<QueryForTypeParam<Types, Type>, keyof Selection> & Selection>;
419
+
420
+ /**
421
+ * The callback a field's `select` function plans the selection beneath the field with: `path`
422
+ * walks a field nested under the field's type, `type` names the type the selection is read as.
423
+ * It also carries the `PathInfo` of the field being planned. The selection is typed by the
424
+ * field's table when it has one, so `columns: { title: true }` keeps its literal `true`.
425
+ */
426
+ export type NestedSelectionFn<Types extends SchemaTypes, Type, Args extends InputFieldMap = {}> = (<
427
+ Selection extends
428
+ | boolean
429
+ | ([QueryForTypeParam<Types, Type>] extends [never]
430
+ ? {}
431
+ : QueryForTypeParam<Types, Type>) = true,
432
+ >(
433
+ selection?: NestedSelectionArg<Types, Selection, Args>,
434
+ path?: PathSegment[],
435
+ type?: string,
436
+ ) => NestedSelectionResult<Types, Type, Selection>) &
437
+ PathInfo;
438
+
439
+ /**
440
+ * The selection given to `nestedSelection`: the query config itself, or a callback building it
441
+ * from the field's arguments, the context, and the field's `PathInfo`. A schema with
442
+ * `AsyncSelections: true` may also give a promise of the config, or an async callback; the result
443
+ * is typed by the query either way, and is a promise at runtime only when a promise or an async
444
+ * callback was given, or a selection beneath it is async, and must then be awaited.
445
+ */
446
+ export type NestedSelectionArg<Types extends SchemaTypes, Selection, Args extends InputFieldMap> =
447
+ | Selection
448
+ | (true extends Types['AsyncSelections'] ? PromiseLike<Selection> : never)
449
+ | ((
450
+ args: InputShapeFromFields<Args>,
451
+ ctx: Types['Context'],
452
+ pathInfo: PathInfo,
453
+ ) => MaybeAsyncSelection<Types, Selection>);
454
+
347
455
  export type DrizzleFieldSelection =
348
456
  | DBQueryConfig<'one'>
349
457
  | ((
@@ -353,13 +461,17 @@ export type DrizzleFieldSelection =
353
461
  selection:
354
462
  | SelectionMap
355
463
  | boolean
356
- | ((args: object, context: object) => DBQueryConfig<'one'>),
357
- path?: IndirectInclude | string[],
464
+ | ((
465
+ args: object,
466
+ context: object,
467
+ pathInfo: PathInfo,
468
+ ) => MaybePromise<DBQueryConfig<'one'>>),
469
+ path?: IndirectInclude | PathSegment[],
358
470
  type?: string,
359
471
  ) => DBQueryConfig<'one'> | boolean,
360
472
  resolveSelection: (path: string[]) => FieldNode | null,
361
473
  pathInfo: PathInfo,
362
- ) => SelectionMap);
474
+ ) => MaybePromise<SelectionMap | false | null | undefined>);
363
475
 
364
476
  export type ExtractTable<Types extends SchemaTypes, Shape> = Shape extends {
365
477
  [drizzleTableName]?: keyof Types['DrizzleRelations'];
@@ -460,7 +572,50 @@ export type RelatedCountOptions<
460
572
  PothosSchemaTypes.ObjectFieldOptions<Types, Shape, 'Int', false, Args, number>,
461
573
  'type' | InferredFieldOptionKeys
462
574
  > & {
463
- where?: Where | ((args: InputShapeFromFields<Args>, context: Types['Context']) => Where);
575
+ where?:
576
+ | Where
577
+ | ((
578
+ args: InputShapeFromFields<Args>,
579
+ context: Types['Context'],
580
+ ) => MaybeAsyncSelection<Types, Where>);
581
+ };
582
+
583
+ /**
584
+ * The options of `t.relatedField`: the ordinary object field options (description, deprecation,
585
+ * extensions, and what other plugins add), with a `select` that plans a query on the relation.
586
+ */
587
+ export type RelatedSelectionFieldOptions<
588
+ Types extends SchemaTypes,
589
+ TableConfig extends TableRelationalConfig,
590
+ Type extends TypeParam<Types>,
591
+ Nullable extends boolean,
592
+ Args extends InputFieldMap,
593
+ Select,
594
+ ShapeWithSelection,
595
+ > = Omit<
596
+ PothosSchemaTypes.ObjectFieldOptions<Types, ShapeWithSelection, Type, Nullable, Args, unknown>,
597
+ InferredFieldOptionKeys
598
+ > & {
599
+ /**
600
+ * The query planned on the parent row for this field: `buildFilter` filters the related table
601
+ * to the parent's rows, `nestedQuery` plans the field's own selection beneath a query. With
602
+ * `AsyncSelections: true` the function may be async.
603
+ */
604
+ select: ((
605
+ buildFilter: (parentTable: TableConfig['table']) => SQL,
606
+ args: InputShapeFromFields<Args>,
607
+ ctx: Types['Context'],
608
+ nestedQuery: (
609
+ query: DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>,
610
+ ) => DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>,
611
+ ) => MaybeAsyncSelection<Types, Select>) &
612
+ CheckAsyncSelection<Types, Select>;
613
+ resolve: (
614
+ parent: ShapeWithSelection,
615
+ args: InputShapeFromFields<Args>,
616
+ ctx: Types['Context'],
617
+ info: GraphQLResolveInfo,
618
+ ) => MaybePromise<ShapeFromTypeParam<Types, Type, Nullable>>;
464
619
  };
465
620
 
466
621
  export type TypesForRelation<Types extends SchemaTypes, Rel extends Relation> = BuildQueryResult<
@@ -500,7 +655,7 @@ export type QueryForField<
500
655
  args: InputShapeFromFields<Args>,
501
656
  context: Types['Context'],
502
657
  pathInfo: PathInfo,
503
- ) => QueryConfig)
658
+ ) => MaybeAsyncSelection<Types, QueryConfig>)
504
659
  : never;
505
660
 
506
661
  export type QueryForDrizzleField<
@@ -523,7 +678,13 @@ export type QueryForRelatedConnection<
523
678
  > & {
524
679
  orderBy?: ConnectionOrderBy<Table> | ((table: Table) => ConnectionOrderBy<Table>);
525
680
  } extends infer QueryConfig
526
- ? QueryConfig | ((args: Args, context: Types['Context'], pathInfo: PathInfo) => QueryConfig)
681
+ ?
682
+ | QueryConfig
683
+ | ((
684
+ args: Args,
685
+ context: Types['Context'],
686
+ pathInfo: PathInfo,
687
+ ) => MaybeAsyncSelection<Types, QueryConfig>)
527
688
  : never;
528
689
 
529
690
  export type QueryForDrizzleConnection<
@@ -655,18 +816,6 @@ export type RelatedConnectionOptions<
655
816
  (InputShapeFromFields<Args> &
656
817
  PothosSchemaTypes.DefaultConnectionArguments extends infer ConnectionArgs
657
818
  ? {
658
- resolve?: (
659
- parent: Shape,
660
- args: ConnectionArgs,
661
- context: Types['Context'],
662
- info: GraphQLResolveInfo,
663
- ) => MaybePromise<
664
- ShapeFromTypeParam<
665
- Types,
666
- [ObjectRef<Types, TypesForRelation<Types, Table['relations'][Field]>>],
667
- Nullable
668
- >
669
- >;
670
819
  query?: QueryForRelatedConnection<Types, NodeTable, ConnectionArgs>;
671
820
  // biome-ignore lint/suspicious/noExplicitAny: this is fine
672
821
  type?: Type & DrizzleRef<any, Table['relations'][Field]['targetTableName']>;
@@ -723,22 +872,5 @@ export type DrizzleConnectionShape<
723
872
 
724
873
  export type WithBrand<T> = T & { [typeBrandKey]: string };
725
874
 
726
- export interface AddGraphQLInputTypeOptions<Types extends SchemaTypes, Shape extends {}>
727
- extends Omit<
728
- PothosSchemaTypes.InputObjectTypeOptions<
729
- Types,
730
- InputFieldsFromShape<Types, Shape, 'InputObject'>
731
- >,
732
- 'fields'
733
- > {
734
- name?: string;
735
- }
736
-
737
- export interface DrizzleGraphQLInputExtensions {
738
- table: string;
739
- tableConfig: TableRelationalConfig;
740
- inputType: 'insert' | 'filters' | 'orderBy' | 'update';
741
- }
742
-
743
875
  export { DrizzleInterfaceRef, type DrizzleRef } from './interface-ref.js';
744
876
  export { DrizzleObjectRef } from './object-ref.js';
@@ -0,0 +1,197 @@
1
+ import { createContextCache, PothosValidationError } from '@pothos/core';
2
+ import {
3
+ type Node,
4
+ NodeAdapter,
5
+ type Plan,
6
+ type PlayedPlan,
7
+ type QueryVisitor,
8
+ type SelectFn,
9
+ } from '@pothos/selection-mapper';
10
+ import type { TableRelationalConfig } from 'drizzle-orm';
11
+ import type { GraphQLField, GraphQLNamedType } from 'graphql';
12
+ import type { DrizzleFieldSelection, PathInfo } from '../types.js';
13
+ import type { PothosDrizzleSchemaConfig } from './config.js';
14
+ import { pathInfoFor } from './path-info.js';
15
+ import { omitUndefinedKeys, type SelectionMap } from './selections.js';
16
+
17
+ export type DrizzleNode = Node<TableRelationalConfig>;
18
+ export type DrizzlePlan = Plan<TableRelationalConfig, SelectionMap>;
19
+ export type DrizzlePlayedPlan = PlayedPlan<TableRelationalConfig, SelectionMap>;
20
+ type DrizzleVisitor = QueryVisitor<TableRelationalConfig, SelectionMap>;
21
+
22
+ /** A map without `columns`: every column. Shared and never mutated. */
23
+ const ALL: SelectionMap = Object.freeze({});
24
+
25
+ /**
26
+ * How drizzle selections (`DBQueryConfig`: `columns`, `with`, `extras` and query arguments) read
27
+ * onto the shared query tree, and how a node is written back. A node whose columns are `null`
28
+ * selects every column; a named-column node always adds the table's primary key so rows can be
29
+ * matched back to their parent.
30
+ *
31
+ * The merge, compare and conflict rules are `NodeAdapter`'s: this is the schema side, the key
32
+ * loop and `toQuery`. The schema config every one of them needs is the adapter's own state.
33
+ */
34
+ export class DrizzleAdapter extends NodeAdapter<TableRelationalConfig, SelectionMap> {
35
+ override skipDeferredFragments: boolean;
36
+
37
+ private readonly fieldSelections = new WeakMap<
38
+ GraphQLField<unknown, unknown>,
39
+ SelectFn<SelectionMap>
40
+ >();
41
+
42
+ constructor(private readonly config: PothosDrizzleSchemaConfig) {
43
+ super();
44
+ this.skipDeferredFragments = config.skipDeferredFragments;
45
+ }
46
+
47
+ // Set by drizzleObject/drizzleInterface and propagated to implementing types by onTypeConfig.
48
+ modelFor(type: GraphQLNamedType) {
49
+ return type.extensions?.pothosDrizzleTable as TableRelationalConfig | undefined;
50
+ }
51
+
52
+ typeSelection(type: GraphQLNamedType) {
53
+ // schema-builder stores `{ columns: {}, ...select }`, or `true` for every column.
54
+ const selection = type.extensions?.pothosDrizzleSelect as SelectionMap | true | undefined;
55
+
56
+ return selection === true ? ALL : selection || undefined;
57
+ }
58
+
59
+ fieldSelection(field: GraphQLField<unknown, unknown>) {
60
+ const selection = field.extensions?.pothosDrizzleSelect as DrizzleFieldSelection | undefined;
61
+ if (typeof selection !== 'function') {
62
+ return selection || undefined;
63
+ }
64
+
65
+ let compiled = this.fieldSelections.get(field);
66
+ if (!compiled) {
67
+ compiled = (args, context, nested, selected, position) => {
68
+ // Keep the public metadata at the adapter boundary. Static query objects do not need
69
+ // either path array; callbacks that inspect metadata build them once per invocation.
70
+ let resolvedPath: PathInfo | undefined;
71
+ const pathInfo: PathInfo = {
72
+ get path() {
73
+ resolvedPath ??= pathInfoFor(position)!;
74
+ return resolvedPath.path;
75
+ },
76
+ get segments() {
77
+ resolvedPath ??= pathInfoFor(position)!;
78
+ return resolvedPath.segments;
79
+ },
80
+ };
81
+ return selection(
82
+ args,
83
+ context,
84
+ (query, path, type) =>
85
+ nested(
86
+ // The public API also accepts false, which the walker treats as an empty seed.
87
+ (typeof query === 'function' ? query(args, context, pathInfo) : query) as Parameters<
88
+ typeof nested
89
+ >[0],
90
+ path,
91
+ type,
92
+ ),
93
+ selected,
94
+ pathInfo,
95
+ );
96
+ };
97
+ this.fieldSelections.set(field, compiled);
98
+ }
99
+ return compiled;
100
+ }
101
+
102
+ visitQuery(
103
+ { columns, with: withSelection, extras, ...args }: SelectionMap,
104
+ model: TableRelationalConfig,
105
+ visit: DrizzleVisitor,
106
+ ) {
107
+ this.readRelations(withSelection, model, visit);
108
+ visit.args(omitUndefinedKeys(args));
109
+
110
+ for (const key of Object.keys(extras ?? {})) {
111
+ visit.computed(key, extras![key]);
112
+ }
113
+
114
+ if (!columns) {
115
+ // No `columns` means every column, which a node never goes back from.
116
+ visit.allColumns();
117
+
118
+ return;
119
+ }
120
+
121
+ for (const key of Object.keys(columns)) {
122
+ if (columns[key]) {
123
+ visit.column(key);
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Drizzle's `extras` are functions, compared by identity: the inherited deep-equal would let
130
+ * two different functions computing the same shape merge.
131
+ */
132
+ override computedConflicts(computed: ReadonlyMap<string, unknown>, name: string, value: unknown) {
133
+ return computed.has(name) && computed.get(name) !== value;
134
+ }
135
+
136
+ toQuery(node: DrizzleNode): SelectionMap {
137
+ // `columns` stays present (as undefined) for an every-column node: it is what callers
138
+ // spreading or comparing the query have always seen.
139
+ const query: SelectionMap & {
140
+ with: Record<string, unknown>;
141
+ extras: Record<string, unknown>;
142
+ } = { ...node.args, columns: undefined, with: {}, extras: {} };
143
+
144
+ if (node.columns) {
145
+ query.columns = {};
146
+
147
+ for (const column of node.columns) {
148
+ query.columns[column] = true;
149
+ }
150
+
151
+ for (const column of this.config.getPrimaryKey(node.model.name)) {
152
+ query.columns[this.config.columnToTsName(column)] = true;
153
+ }
154
+ }
155
+
156
+ for (const [key, value] of node.computed) {
157
+ query.extras[key] = value as never;
158
+ }
159
+
160
+ for (const [key, child] of node.relations) {
161
+ query.with[key] = this.toQuery(child);
162
+ }
163
+
164
+ return query;
165
+ }
166
+
167
+ /** The keys of a `with` map, resolved to the tables they target. */
168
+ private readRelations(
169
+ withSelection: Record<string, unknown> | undefined,
170
+ model: TableRelationalConfig,
171
+ visit: DrizzleVisitor,
172
+ ) {
173
+ for (const key of Object.keys(withSelection ?? {})) {
174
+ const value = withSelection![key];
175
+
176
+ if (!value) {
177
+ continue;
178
+ }
179
+
180
+ const relationConfig = model.relations[key];
181
+
182
+ if (!relationConfig) {
183
+ throw new PothosValidationError(`Relation ${key} does not exist on ${model.name}`);
184
+ }
185
+
186
+ visit.relation(
187
+ key,
188
+ this.config.relations[relationConfig.targetTableName],
189
+ value === true ? ALL : (value as SelectionMap),
190
+ );
191
+ }
192
+ }
193
+ }
194
+
195
+ export const drizzleAdapter = createContextCache(
196
+ (config: PothosDrizzleSchemaConfig) => new DrizzleAdapter(config),
197
+ );
@@ -0,0 +1,31 @@
1
+ import { isThenable, PothosValidationError } from '@pothos/core';
2
+
3
+ /**
4
+ * The guard behind `awaitSelections`. A query is only built asynchronously when a selection
5
+ * beneath the field is async, which the call site cannot see: what it plans is the rest of the
6
+ * document. Rather than hand back a promise where the declared type says there is none — which
7
+ * spreads into a drizzle call as one unusable key, with no type error — the promise is refused
8
+ * unless the caller asked for it.
9
+ *
10
+ * The refused promise is left handled, so a plan that also rejects does not report an unhandled
11
+ * rejection on the way out.
12
+ */
13
+ export function checkAwaitSelections<T>(
14
+ value: T,
15
+ awaitSelections: boolean | undefined,
16
+ caller: string,
17
+ subject: string,
18
+ ): T {
19
+ if (awaitSelections || !isThenable(value)) {
20
+ return value;
21
+ }
22
+
23
+ value.then(
24
+ () => {},
25
+ () => {},
26
+ );
27
+
28
+ throw new PothosValidationError(
29
+ `${caller} could not build the query for ${subject} synchronously, because a selection beneath it is async. Pass awaitSelections: true and await the result.`,
30
+ );
31
+ }
@@ -173,11 +173,7 @@ export const drizzleClientCache = createContextCache(
173
173
  ? createContextCache((ctx) => clientConfig(ctx))
174
174
  : (_ctx: object) => clientConfig;
175
175
 
176
- return createContextCache((context: object) => {
177
- const client = getClient(context);
178
-
179
- return client;
180
- });
176
+ return createContextCache(getClient);
181
177
  },
182
178
  );
183
179