@pothos/plugin-drizzle 0.18.1 → 0.19.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.
Files changed (165) hide show
  1. package/CHANGELOG.md +345 -0
  2. package/README.md +784 -221
  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 +80 -20
  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 +80 -20
  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 +55 -90
  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 +47 -88
  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 +8 -6
  114. package/src/connection-helpers.ts +167 -53
  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 +175 -44
  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 +53 -81
  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;
@@ -60,8 +68,24 @@ export type DrizzleClient<TCountSource = Table | SQL | SQLWrapper> = {
60
68
  readonly _: {
61
69
  readonly relations: AnyRelations;
62
70
  };
63
- query: {};
71
+ query: Record<
72
+ string,
73
+ {
74
+ // Each table accepts its own schema-specific config. The loader supplies that config.
75
+ findMany: (config: never) => PromiseLike<Record<string, unknown>[]>;
76
+ }
77
+ >;
64
78
  $count: (source: TCountSource, filter?: SQL) => SQL<number>;
79
+ /** Core SQL construction used for relation predicates and counts; it does not execute a query. */
80
+ select: (fields: Record<string, SQL>) => {
81
+ from(table: Extract<TCountSource, Table>): {
82
+ innerJoin(
83
+ table: Extract<TCountSource, Table>,
84
+ on: SQL,
85
+ ): { where: (filter?: SQL) => SQLWrapper };
86
+ where: (filter?: SQL) => SQLWrapper;
87
+ };
88
+ };
65
89
  };
66
90
 
67
91
  type GetTableConfigFn<TTable = Table> = (table: TTable) => {
@@ -85,6 +109,12 @@ type DrizzlePluginBaseOptions = {
85
109
  maxConnectionSize?: number;
86
110
  defaultConnectionSize?: number;
87
111
  skipDeferredFragments?: boolean;
112
+ /**
113
+ * When `true` (the default), the `totalCount` of a `relatedConnection` applies the `where`
114
+ * returned by the field's `query`, so it counts the same rows the connection paginates. Set to
115
+ * `false` to count every related row regardless of the filter.
116
+ */
117
+ filterConnectionTotalCount?: boolean;
88
118
  };
89
119
 
90
120
  export type DrizzlePluginOptions<Types extends SchemaTypes> = DrizzlePluginBaseOptions &
@@ -305,7 +335,7 @@ export type DrizzleObjectFieldOptions<
305
335
  Record<string, unknown> &
306
336
  // biome-ignore lint/suspicious/noExplicitAny: this is fine
307
337
  Select extends (...args: any[]) => infer R
308
- ? R & { columns: {} }
338
+ ? Awaited<R> & { columns: {} }
309
339
  : Select & { columns: {} }
310
340
  > &
311
341
  ParentShape,
@@ -329,21 +359,98 @@ export type DrizzleObjectFieldOptions<
329
359
  Args,
330
360
  ResolveReturnShape
331
361
  > & {
362
+ /**
363
+ * What the field needs from its parent row. With `AsyncSelections: true` the function may be
364
+ * async, and `nestedSelection` is then a promise when a selection beneath it is async, and
365
+ * must be awaited.
366
+ */
332
367
  select?: Select &
368
+ CheckAsyncSelection<Types, Select> &
333
369
  (
334
370
  | DBQueryConfig<'one', Types['DrizzleRelations'], ExtractTable<Types, ParentShape>>
335
371
  | ((
336
372
  args: InputShapeFromFields<Args>,
337
373
  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>>)
374
+ nestedSelection: NestedSelectionFn<Types, Type, Args>,
375
+ ) => MaybeAsyncSelection<
376
+ Types,
377
+ DBQueryConfig<'one', Types['DrizzleRelations'], ExtractTable<Types, ParentShape>>
378
+ >)
344
379
  );
345
380
  };
346
381
 
382
+ /** The table a field's type param names: a drizzle ref, or a list of one. */
383
+ export type TableForTypeParam<Types extends SchemaTypes, Type> = Type extends [infer Item]
384
+ ? TableForTypeParam<Types, Item>
385
+ : // biome-ignore lint/suspicious/noExplicitAny: matching against any ref
386
+ Type extends DrizzleRef<any, infer Table>
387
+ ? Table & keyof Types['DrizzleRelations']
388
+ : never;
389
+
390
+ /**
391
+ * The query config for the table a field's type param names (a `many` config for a list field),
392
+ * or never when the type has no table.
393
+ */
394
+ export type QueryForTypeParam<Types extends SchemaTypes, Type> =
395
+ TableForTypeParam<Types, Type> extends infer Table
396
+ ? [Table] extends [never]
397
+ ? never
398
+ : DBQueryConfig<
399
+ Type extends [unknown] ? 'many' : 'one',
400
+ Types['DrizzleRelations'],
401
+ Types['DrizzleRelations'][Table & keyof Types['DrizzleRelations']]
402
+ >
403
+ : never;
404
+
405
+ /**
406
+ * What `nestedSelection` returns: the query config for the field's table, keeping the keys of
407
+ * the given selection as they were given (so `columns` in it still narrow the parent shape).
408
+ * With no selection, or `true`, it is the config itself. A field whose type has no table keeps
409
+ * the selection it was given.
410
+ */
411
+ export type NestedSelectionResult<Types extends SchemaTypes, Type, Selection> = [
412
+ QueryForTypeParam<Types, Type>,
413
+ ] extends [never]
414
+ ? Selection
415
+ : Selection extends boolean
416
+ ? QueryForTypeParam<Types, Type>
417
+ : Normalize<Omit<QueryForTypeParam<Types, Type>, keyof Selection> & Selection>;
418
+
419
+ /**
420
+ * The callback a field's `select` function plans the selection beneath the field with: `path`
421
+ * walks a field nested under the field's type, `type` names the type the selection is read as.
422
+ * It also carries the `PathInfo` of the field being planned. The selection is typed by the
423
+ * field's table when it has one, so `columns: { title: true }` keeps its literal `true`.
424
+ */
425
+ export type NestedSelectionFn<Types extends SchemaTypes, Type, Args extends InputFieldMap = {}> = (<
426
+ Selection extends
427
+ | boolean
428
+ | ([QueryForTypeParam<Types, Type>] extends [never]
429
+ ? {}
430
+ : QueryForTypeParam<Types, Type>) = true,
431
+ >(
432
+ selection?: NestedSelectionArg<Types, Selection, Args>,
433
+ path?: PathSegment[],
434
+ type?: string,
435
+ ) => NestedSelectionResult<Types, Type, Selection>) &
436
+ PathInfo;
437
+
438
+ /**
439
+ * The selection given to `nestedSelection`: the query config itself, or a callback building it
440
+ * from the field's arguments, the context, and the field's `PathInfo`. A schema with
441
+ * `AsyncSelections: true` may also give a promise of the config, or an async callback; the result
442
+ * is typed by the query either way, and is a promise at runtime only when a promise or an async
443
+ * callback was given, or a selection beneath it is async, and must then be awaited.
444
+ */
445
+ export type NestedSelectionArg<Types extends SchemaTypes, Selection, Args extends InputFieldMap> =
446
+ | Selection
447
+ | (true extends Types['AsyncSelections'] ? PromiseLike<Selection> : never)
448
+ | ((
449
+ args: InputShapeFromFields<Args>,
450
+ ctx: Types['Context'],
451
+ pathInfo: PathInfo,
452
+ ) => MaybeAsyncSelection<Types, Selection>);
453
+
347
454
  export type DrizzleFieldSelection =
348
455
  | DBQueryConfig<'one'>
349
456
  | ((
@@ -353,13 +460,17 @@ export type DrizzleFieldSelection =
353
460
  selection:
354
461
  | SelectionMap
355
462
  | boolean
356
- | ((args: object, context: object) => DBQueryConfig<'one'>),
357
- path?: IndirectInclude | string[],
463
+ | ((
464
+ args: object,
465
+ context: object,
466
+ pathInfo: PathInfo,
467
+ ) => MaybePromise<DBQueryConfig<'one'>>),
468
+ path?: IndirectInclude | PathSegment[],
358
469
  type?: string,
359
470
  ) => DBQueryConfig<'one'> | boolean,
360
471
  resolveSelection: (path: string[]) => FieldNode | null,
361
472
  pathInfo: PathInfo,
362
- ) => SelectionMap);
473
+ ) => MaybePromise<SelectionMap | false | null | undefined>);
363
474
 
364
475
  export type ExtractTable<Types extends SchemaTypes, Shape> = Shape extends {
365
476
  [drizzleTableName]?: keyof Types['DrizzleRelations'];
@@ -460,7 +571,50 @@ export type RelatedCountOptions<
460
571
  PothosSchemaTypes.ObjectFieldOptions<Types, Shape, 'Int', false, Args, number>,
461
572
  'type' | InferredFieldOptionKeys
462
573
  > & {
463
- where?: Where | ((args: InputShapeFromFields<Args>, context: Types['Context']) => Where);
574
+ where?:
575
+ | Where
576
+ | ((
577
+ args: InputShapeFromFields<Args>,
578
+ context: Types['Context'],
579
+ ) => MaybeAsyncSelection<Types, Where>);
580
+ };
581
+
582
+ /**
583
+ * The options of `t.relatedField`: the ordinary object field options (description, deprecation,
584
+ * extensions, and what other plugins add), with a `select` that plans a query on the relation.
585
+ */
586
+ export type RelatedSelectionFieldOptions<
587
+ Types extends SchemaTypes,
588
+ TableConfig extends TableRelationalConfig,
589
+ Type extends TypeParam<Types>,
590
+ Nullable extends boolean,
591
+ Args extends InputFieldMap,
592
+ Select,
593
+ ShapeWithSelection,
594
+ > = Omit<
595
+ PothosSchemaTypes.ObjectFieldOptions<Types, ShapeWithSelection, Type, Nullable, Args, unknown>,
596
+ InferredFieldOptionKeys
597
+ > & {
598
+ /**
599
+ * The query planned on the parent row for this field: `buildFilter` filters the related table
600
+ * to the parent's rows, `nestedQuery` plans the field's own selection beneath a query. With
601
+ * `AsyncSelections: true` the function may be async.
602
+ */
603
+ select: ((
604
+ buildFilter: (parentTable: TableConfig['table']) => SQL,
605
+ args: InputShapeFromFields<Args>,
606
+ ctx: Types['Context'],
607
+ nestedQuery: (
608
+ query: DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>,
609
+ ) => DBQueryConfig<'many', Types['DrizzleRelations'], TableConfig>,
610
+ ) => MaybeAsyncSelection<Types, Select>) &
611
+ CheckAsyncSelection<Types, Select>;
612
+ resolve: (
613
+ parent: ShapeWithSelection,
614
+ args: InputShapeFromFields<Args>,
615
+ ctx: Types['Context'],
616
+ info: GraphQLResolveInfo,
617
+ ) => MaybePromise<ShapeFromTypeParam<Types, Type, Nullable>>;
464
618
  };
465
619
 
466
620
  export type TypesForRelation<Types extends SchemaTypes, Rel extends Relation> = BuildQueryResult<
@@ -500,7 +654,7 @@ export type QueryForField<
500
654
  args: InputShapeFromFields<Args>,
501
655
  context: Types['Context'],
502
656
  pathInfo: PathInfo,
503
- ) => QueryConfig)
657
+ ) => MaybeAsyncSelection<Types, QueryConfig>)
504
658
  : never;
505
659
 
506
660
  export type QueryForDrizzleField<
@@ -523,7 +677,13 @@ export type QueryForRelatedConnection<
523
677
  > & {
524
678
  orderBy?: ConnectionOrderBy<Table> | ((table: Table) => ConnectionOrderBy<Table>);
525
679
  } extends infer QueryConfig
526
- ? QueryConfig | ((args: Args, context: Types['Context'], pathInfo: PathInfo) => QueryConfig)
680
+ ?
681
+ | QueryConfig
682
+ | ((
683
+ args: Args,
684
+ context: Types['Context'],
685
+ pathInfo: PathInfo,
686
+ ) => MaybeAsyncSelection<Types, QueryConfig>)
527
687
  : never;
528
688
 
529
689
  export type QueryForDrizzleConnection<
@@ -655,18 +815,6 @@ export type RelatedConnectionOptions<
655
815
  (InputShapeFromFields<Args> &
656
816
  PothosSchemaTypes.DefaultConnectionArguments extends infer ConnectionArgs
657
817
  ? {
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
818
  query?: QueryForRelatedConnection<Types, NodeTable, ConnectionArgs>;
671
819
  // biome-ignore lint/suspicious/noExplicitAny: this is fine
672
820
  type?: Type & DrizzleRef<any, Table['relations'][Field]['targetTableName']>;
@@ -723,22 +871,5 @@ export type DrizzleConnectionShape<
723
871
 
724
872
  export type WithBrand<T> = T & { [typeBrandKey]: string };
725
873
 
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
874
  export { DrizzleInterfaceRef, type DrizzleRef } from './interface-ref.js';
744
875
  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