@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
@@ -1,504 +1,9 @@
1
- import { getMappedArgumentValues, PothosValidationError } from '@pothos/core';
2
- import type { DBQueryConfig, TableRelationalConfig } from 'drizzle-orm';
3
- import {
4
- type FieldNode,
5
- type FragmentDefinitionNode,
6
- type FragmentSpreadNode,
7
- type GraphQLField,
8
- GraphQLIncludeDirective,
9
- type GraphQLInterfaceType,
10
- type GraphQLNamedType,
11
- type GraphQLObjectType,
12
- type GraphQLResolveInfo,
13
- GraphQLSkipDirective,
14
- getDirectiveValues,
15
- getNamedType,
16
- type InlineFragmentNode,
17
- isInterfaceType,
18
- isListType,
19
- isObjectType,
20
- Kind,
21
- type SelectionSetNode,
22
- } from 'graphql';
23
- import type { DrizzleFieldSelection, FieldPathInfo, PathInfo } from '../types.js';
1
+ import { isThenable } from '@pothos/core';
2
+ import { type PathSegment, Plan } from '@pothos/selection-mapper';
3
+ import type { GraphQLResolveInfo } from 'graphql';
4
+ import { type DrizzlePlan, drizzleAdapter } from './adapter.js';
24
5
  import type { PothosDrizzleSchemaConfig } from './config.js';
25
- import { type LoaderMappings, setLoaderMappings } from './loader-map.js';
26
- import {
27
- createState,
28
- mergeSelection,
29
- type SelectionMap,
30
- type SelectionState,
31
- selectionCompatible,
32
- selectionToQuery,
33
- } from './selections.js';
34
- import { wrapWithUsageCheck } from './usage.js';
35
-
36
- export interface IndirectInclude {
37
- getType: () => string;
38
- path?: { type?: string; name: string }[];
39
- paths?: { type?: string; name: string }[][];
40
- }
41
-
42
- function addTypeSelectionsForField(
43
- config: PothosDrizzleSchemaConfig,
44
- type: GraphQLNamedType,
45
- context: object,
46
- info: GraphQLResolveInfo,
47
- state: SelectionState,
48
- selection: FieldNode,
49
- indirectPath: string[],
50
- deferred?: boolean,
51
- segments: FieldPathInfo[] = [],
52
- ) {
53
- if (selection.name.value.startsWith('__')) {
54
- return;
55
- }
56
-
57
- const { pothosDrizzleSelect, pothosIndirectInclude } = (type.extensions ?? {}) as {
58
- pothosIndirectInclude?: IndirectInclude;
59
- pothosDrizzleSelect?: boolean | DBQueryConfig<'one'>;
60
- };
61
-
62
- if (
63
- (!!pothosIndirectInclude?.path && pothosIndirectInclude.path.length > 0) ||
64
- (!!pothosIndirectInclude?.paths && pothosIndirectInclude.paths.length === 0)
65
- ) {
66
- resolveIndirectIncludePaths(
67
- type,
68
- info,
69
- selection,
70
- [],
71
- pothosIndirectInclude.paths ?? [pothosIndirectInclude.path!],
72
- indirectPath,
73
- (resolvedType, field, path) => {
74
- addTypeSelectionsForField(
75
- config,
76
- resolvedType,
77
- context,
78
- info,
79
- state,
80
- field,
81
- path,
82
- deferred,
83
- segments,
84
- );
85
- },
86
- deferred,
87
- );
88
- } else if (pothosIndirectInclude) {
89
- addTypeSelectionsForField(
90
- config,
91
- info.schema.getType(pothosIndirectInclude.getType())!,
92
- context,
93
- info,
94
- state,
95
- selection,
96
- indirectPath,
97
- deferred,
98
- segments,
99
- );
100
- return;
101
- }
102
-
103
- if (!(isObjectType(type) || isInterfaceType(type))) {
104
- return;
105
- }
106
-
107
- if (pothosDrizzleSelect) {
108
- mergeSelection(config, state, pothosDrizzleSelect === true ? true : { ...pothosDrizzleSelect });
109
- }
110
-
111
- if (selection.selectionSet && (!deferred || !state.skipDeferredFragments)) {
112
- addNestedSelections(
113
- config,
114
- type,
115
- context,
116
- info,
117
- state,
118
- selection.selectionSet,
119
- indirectPath,
120
- segments,
121
- );
122
- }
123
- }
124
-
125
- function resolveIndirectIncludePaths(
126
- type: GraphQLNamedType,
127
- info: GraphQLResolveInfo,
128
- selection: FieldNode | FragmentDefinitionNode | InlineFragmentNode,
129
- pathPrefix: { type?: string; name: string }[],
130
- includePaths: { type?: string; name: string }[][],
131
- path: string[],
132
- resolve: (type: GraphQLNamedType, field: FieldNode, path: string[], deferred: boolean) => void,
133
- deferred?: boolean,
134
- ) {
135
- for (const includePath of includePaths) {
136
- if (pathPrefix.length > 0) {
137
- resolveIndirectInclude(
138
- type,
139
- info,
140
- selection,
141
- [...pathPrefix, ...includePath],
142
- path,
143
- resolve,
144
- deferred,
145
- );
146
- } else {
147
- resolveIndirectInclude(type, info, selection, includePath, path, resolve, deferred);
148
- }
149
- }
150
- }
151
-
152
- function resolveIndirectInclude(
153
- type: GraphQLNamedType,
154
- info: GraphQLResolveInfo,
155
- selection: FieldNode | FragmentDefinitionNode | InlineFragmentNode,
156
- includePath: { type?: string; name: string }[],
157
- path: string[],
158
- resolve: (type: GraphQLNamedType, field: FieldNode, path: string[], deferred: boolean) => void,
159
- deferred = false,
160
- ) {
161
- if (includePath.length === 0) {
162
- resolve(type, selection as FieldNode, path, deferred);
163
- return;
164
- }
165
-
166
- const [include, ...rest] = includePath;
167
- if (!selection.selectionSet || !include) {
168
- return;
169
- }
170
-
171
- for (const sel of selection.selectionSet.selections) {
172
- switch (sel.kind) {
173
- case Kind.FIELD:
174
- if (
175
- !fieldSkipped(info, sel) &&
176
- sel.name.value === include.name &&
177
- (isObjectType(type) || isInterfaceType(type))
178
- ) {
179
- const returnType = getNamedType(type.getFields()[sel.name.value].type);
180
-
181
- resolveIndirectInclude(
182
- returnType,
183
- info,
184
- sel,
185
- rest,
186
- [...path, sel.alias?.value ?? sel.name.value],
187
- resolve,
188
- deferred,
189
- );
190
- }
191
- continue;
192
- case Kind.FRAGMENT_SPREAD:
193
- if (
194
- !include.type ||
195
- info.fragments[sel.name.value].typeCondition.name.value === include.type
196
- ) {
197
- resolveIndirectInclude(
198
- include.type ? info.schema.getType(include.type)! : type,
199
- info,
200
- info.fragments[sel.name.value],
201
- includePath,
202
- path,
203
- resolve,
204
- deferred || isDeferredFragment(sel, info),
205
- );
206
- }
207
-
208
- continue;
209
-
210
- case Kind.INLINE_FRAGMENT:
211
- if (!sel.typeCondition || !include.type || sel.typeCondition.name.value === include.type) {
212
- resolveIndirectInclude(
213
- sel.typeCondition ? info.schema.getType(sel.typeCondition.name.value)! : type,
214
- info,
215
- sel,
216
- includePath,
217
- path,
218
- resolve,
219
- deferred || isDeferredFragment(sel, info),
220
- );
221
- }
222
-
223
- continue;
224
-
225
- default:
226
- throw new PothosValidationError(
227
- `Unsupported selection kind ${(selection as { kind: string }).kind}`,
228
- );
229
- }
230
- }
231
- }
232
-
233
- // The type to plan a fragment's selections against, or null when the fragment
234
- // cannot apply to `type`. A fragment on an interface the object implements is
235
- // planned against the object, whose field map already carries the interface
236
- // fields and their `select` extensions.
237
- function typeForFragment(
238
- type: GraphQLInterfaceType | GraphQLObjectType,
239
- condition: GraphQLNamedType,
240
- ): GraphQLInterfaceType | GraphQLObjectType | null {
241
- if (isObjectType(type)) {
242
- if (condition.name === type.name) {
243
- return type;
244
- }
245
-
246
- if (
247
- isInterfaceType(condition) &&
248
- type.getInterfaces().some((iface) => iface.name === condition.name)
249
- ) {
250
- return type;
251
- }
252
-
253
- return null;
254
- }
255
-
256
- if (
257
- (isObjectType(condition) || isInterfaceType(condition)) &&
258
- condition.extensions?.pothosDrizzleModel === type.extensions.pothosDrizzleModel
259
- ) {
260
- return condition;
261
- }
262
-
263
- return null;
264
- }
265
-
266
- function addNestedSelections(
267
- config: PothosDrizzleSchemaConfig,
268
- type: GraphQLInterfaceType | GraphQLObjectType,
269
- context: object,
270
- info: GraphQLResolveInfo,
271
- state: SelectionState,
272
- selections: SelectionSetNode,
273
- indirectPath: string[],
274
- segments: FieldPathInfo[] = [],
275
- ) {
276
- let parentType: GraphQLInterfaceType | GraphQLObjectType | null = type;
277
- for (const selection of selections.selections) {
278
- switch (selection.kind) {
279
- case Kind.FIELD:
280
- addFieldSelection(config, type, context, info, state, selection, indirectPath, segments);
281
-
282
- continue;
283
- case Kind.FRAGMENT_SPREAD:
284
- if (state.skipDeferredFragments && isDeferredFragment(selection, info)) {
285
- continue;
286
- }
287
-
288
- parentType = typeForFragment(
289
- type,
290
- info.schema.getType(info.fragments[selection.name.value].typeCondition.name.value)!,
291
- );
292
- if (!parentType) {
293
- continue;
294
- }
295
-
296
- addNestedSelections(
297
- config,
298
- parentType,
299
- context,
300
- info,
301
- state,
302
- info.fragments[selection.name.value].selectionSet,
303
- indirectPath,
304
- segments,
305
- );
306
-
307
- continue;
308
-
309
- case Kind.INLINE_FRAGMENT:
310
- if (state.skipDeferredFragments && isDeferredFragment(selection, info)) {
311
- continue;
312
- }
313
-
314
- parentType = selection.typeCondition
315
- ? typeForFragment(type, info.schema.getType(selection.typeCondition.name.value)!)
316
- : type;
317
- if (!parentType) {
318
- continue;
319
- }
320
-
321
- addNestedSelections(
322
- config,
323
- parentType,
324
- context,
325
- info,
326
- state,
327
- selection.selectionSet,
328
- indirectPath,
329
- segments,
330
- );
331
-
332
- continue;
333
-
334
- default:
335
- throw new PothosValidationError(
336
- `Unsupported selection kind ${(selection as { kind: string }).kind}`,
337
- );
338
- }
339
- }
340
- }
341
-
342
- function addFieldSelection(
343
- config: PothosDrizzleSchemaConfig,
344
- type: GraphQLInterfaceType | GraphQLObjectType,
345
- context: object,
346
- info: GraphQLResolveInfo,
347
- state: SelectionState,
348
- selection: FieldNode,
349
- indirectPath: string[],
350
- segments: FieldPathInfo[] = [],
351
- ) {
352
- if (selection.name.value.startsWith('__') || fieldSkipped(info, selection)) {
353
- return;
354
- }
355
- const field = type.getFields()[selection.name.value];
356
- if (!field) {
357
- throw new PothosValidationError(`Unknown field ${selection.name.value} on ${type.name}`);
358
- }
359
-
360
- const allSegments = [
361
- ...segments,
362
- {
363
- field: selection.name.value,
364
- alias: selection.alias?.value ?? selection.name.value,
365
- parentType: type.name,
366
- isList: isListType(field.type) || isListType(getNamedType(field.type)),
367
- },
368
- ];
369
-
370
- const fieldSelect = field.extensions?.pothosDrizzleSelect as DrizzleFieldSelection | undefined;
371
-
372
- let fieldSelectionMap: DBQueryConfig<'one'> | undefined;
373
- let mappings: LoaderMappings = {};
374
- if (typeof fieldSelect === 'function') {
375
- const pathInfo: PathInfo = {
376
- path: allSegments.map((s) => `${s.parentType}.${s.field}`),
377
- segments: allSegments,
378
- };
379
-
380
- const args = getMappedArgumentValues(field, selection, context, info) as Record<
381
- string,
382
- unknown
383
- >;
384
- fieldSelectionMap = fieldSelect(
385
- args,
386
- context,
387
- (rawQuery, indirectInclude, expectedType) => {
388
- const returnType = getNamedType(field.type);
389
- const query = typeof rawQuery === 'function' ? rawQuery(args, context) : rawQuery;
390
- const normalizedIndirectInclude = Array.isArray(indirectInclude)
391
- ? normalizeInclude(
392
- indirectInclude,
393
- getIndirectType(returnType, info),
394
- expectedType ? getNamedType(info.schema.getType(expectedType)) : undefined,
395
- )
396
- : indirectInclude;
397
- const fieldState = createStateForSelection(
398
- config,
399
- info,
400
- normalizedIndirectInclude
401
- ? info.schema.getType(normalizedIndirectInclude.getType())!
402
- : returnType,
403
- state,
404
- );
405
-
406
- if (typeof query === 'object' && Object.keys(query).length > 0) {
407
- mergeSelection(config, fieldState, { columns: {}, ...query });
408
- }
409
- if (
410
- (!!normalizedIndirectInclude?.path && normalizedIndirectInclude.path.length > 0) ||
411
- (!!normalizedIndirectInclude?.paths && normalizedIndirectInclude.paths.length > 0)
412
- ) {
413
- resolveIndirectIncludePaths(
414
- returnType,
415
- info,
416
- selection,
417
- (returnType.extensions?.pothosIndirectInclude as { path: [] })?.path ?? [],
418
- normalizedIndirectInclude.paths ?? [normalizedIndirectInclude.path!],
419
- [],
420
- (resolvedType, resolvedField, path, deferred) => {
421
- addTypeSelectionsForField(
422
- config,
423
- resolvedType,
424
- context,
425
- info,
426
- fieldState,
427
- resolvedField,
428
- path,
429
- deferred,
430
- allSegments,
431
- );
432
- },
433
- );
434
- }
435
- addTypeSelectionsForField(
436
- config,
437
- returnType,
438
- context,
439
- info,
440
- fieldState,
441
- selection,
442
- [],
443
- undefined,
444
- allSegments,
445
- );
446
- mappings = fieldState.mappings;
447
- return selectionToQuery(config, fieldState);
448
- },
449
- (path) => {
450
- const returnType = getNamedType(field.type);
451
- let node: FieldNode | null = null;
452
- resolveIndirectInclude(
453
- returnType,
454
- info,
455
- selection,
456
- path.map((name) => ({
457
- name,
458
- })),
459
- [],
460
- (_, resolvedField) => {
461
- node = resolvedField;
462
- },
463
- );
464
- return node;
465
- },
466
- pathInfo,
467
- );
468
- } else {
469
- fieldSelectionMap = fieldSelect!;
470
- }
471
-
472
- if (fieldSelect && selectionCompatible(state, fieldSelectionMap, true)) {
473
- mergeSelection(config, state, fieldSelectionMap);
474
-
475
- state.mappings = mergeMappings(state.mappings, {
476
- [selection.alias?.value ?? selection.name.value]: {
477
- field: selection.name.value,
478
- type: type.name,
479
- mappings,
480
- indirectPath,
481
- },
482
- });
483
- }
484
- }
485
-
486
- function mergeMappings(existing: LoaderMappings, incoming: LoaderMappings): LoaderMappings {
487
- const result: LoaderMappings = { ...existing };
488
-
489
- for (const [key, value] of Object.entries(incoming)) {
490
- if (result[key]) {
491
- result[key] = {
492
- ...result[key],
493
- mappings: mergeMappings(result[key].mappings, value.mappings),
494
- };
495
- } else {
496
- result[key] = value;
497
- }
498
- }
499
-
500
- return result;
501
- }
6
+ import type { SelectionMap } from './selections.js';
502
7
 
503
8
  export interface QueryFromInfoOptions<T extends SelectionMap> {
504
9
  config: PothosDrizzleSchemaConfig;
@@ -506,248 +11,60 @@ export interface QueryFromInfoOptions<T extends SelectionMap> {
506
11
  info: GraphQLResolveInfo;
507
12
  typeName?: string;
508
13
  select?: T;
509
- path?: string[];
510
- paths?: string[][];
511
- withUsageCheck?: boolean;
14
+ path?: PathSegment[];
15
+ paths?: PathSegment[][];
512
16
  }
513
17
 
514
18
  export function queryFromInfo<T extends SelectionMap>({
515
- withUsageCheck,
516
- ...options
517
- }: QueryFromInfoOptions<T>): T {
518
- const state = stateFromInfo(options);
519
-
520
- setLoaderMappings(options.context, options.info, state.mappings);
521
-
522
- const query = selectionToQuery(options.config, state) as T;
523
-
524
- return withUsageCheck ? wrapWithUsageCheck(query) : query;
525
- }
526
-
527
- export function stateFromInfo<T extends SelectionMap>({
528
19
  config,
529
- context,
530
- info,
531
- typeName,
532
20
  select,
533
- path = [],
534
- paths = [],
535
- }: QueryFromInfoOptions<T>) {
536
- const returnType = getNamedType(info.returnType);
537
- const type = typeName ? info.schema.getTypeMap()[typeName] : returnType;
538
- const initialSelection = select
539
- ? {
540
- columns: {},
541
- ...select,
542
- }
543
- : undefined;
544
-
545
- let state: SelectionState | undefined;
546
-
547
- if (path.length > 0 || paths.length > 0) {
548
- const { pothosIndirectInclude } = (returnType.extensions ?? {}) as {
549
- pothosIndirectInclude?: IndirectInclude;
550
- };
551
-
552
- resolveIndirectInclude(
553
- returnType,
554
- info,
555
- info.fieldNodes[0],
556
- pothosIndirectInclude?.path ?? [],
557
- [],
558
- (indirectType, indirectField, subPath, deferred) => {
559
- resolveIndirectIncludePaths(
560
- indirectType,
561
- info,
562
- indirectField,
563
- [],
564
- paths.length > 0
565
- ? paths.map((p) => p.map((n) => (typeof n === 'string' ? { name: n } : n)))
566
- : [path.map((n) => (typeof n === 'string' ? { name: n } : n))],
567
- subPath,
568
- (resolvedType, resolvedField, nested, deferred) => {
569
- state = createStateForSelection(
570
- config,
571
- info,
572
- resolvedType,
573
- undefined,
574
- initialSelection,
575
- );
576
-
577
- addTypeSelectionsForField(
578
- config,
579
- typeName ? type : resolvedType,
580
- context,
581
- info,
582
- state,
583
- resolvedField,
584
- nested,
585
- deferred,
586
- );
587
- },
588
- deferred,
589
- );
590
- },
591
- );
592
- } else {
593
- state = createStateForSelection(config, info, type, undefined, initialSelection);
594
-
595
- // Create initial segment for the root query field
596
- const rootFieldNode = info.fieldNodes[0];
597
- const rootField = info.parentType.getFields()[rootFieldNode.name.value];
598
- const initialSegments: FieldPathInfo[] = rootField
599
- ? [
600
- {
601
- field: rootFieldNode.name.value,
602
- alias: rootFieldNode.alias?.value ?? rootFieldNode.name.value,
603
- parentType: info.parentType.name,
604
- isList: isListType(rootField.type) || isListType(getNamedType(rootField.type)),
605
- },
606
- ]
607
- : [];
608
-
609
- addTypeSelectionsForField(
610
- config,
611
- type,
612
- context,
613
- info,
614
- state,
615
- info.fieldNodes[0],
616
- [],
617
- undefined,
618
- initialSegments,
619
- );
620
- }
621
-
622
- if (!state) {
623
- state = createStateForSelection(config, info, type, undefined, initialSelection);
624
- }
625
-
626
- return state;
627
- }
628
-
629
- export function selectionStateFromInfo(
630
- config: PothosDrizzleSchemaConfig,
631
- context: object,
632
- info: GraphQLResolveInfo,
633
- typeName?: string,
634
- ) {
635
- const type = typeName ? info.schema.getTypeMap()[typeName] : info.parentType;
636
-
637
- const state = createStateForSelection(config, info, type);
638
-
639
- if (!(isObjectType(type) || isInterfaceType(type))) {
640
- throw new PothosValidationError(
641
- 'Drizzle plugin can only resolve selections for object and interface types',
642
- );
643
- }
644
-
645
- addFieldSelection(config, type, context, info, state, info.fieldNodes[0], []);
646
-
647
- return state;
648
- }
649
-
650
- function createStateForSelection(
651
- config: PothosDrizzleSchemaConfig,
652
- info: GraphQLResolveInfo,
653
- type: GraphQLNamedType,
654
- parent?: SelectionState,
655
- initialSelections?: SelectionMap,
656
- ) {
657
- const targetType = getIndirectType(type, info);
658
- const { pothosDrizzleTable } = (targetType.extensions ?? {}) as {
659
- pothosDrizzleTable?: TableRelationalConfig;
660
- };
661
-
662
- if (!pothosDrizzleTable) {
663
- throw new PothosValidationError(`Expected ${targetType.name} to have a table config`);
664
- }
665
-
666
- const state = createState(
667
- pothosDrizzleTable,
668
- parent?.skipDeferredFragments ?? config.skipDeferredFragments,
669
- parent,
670
- );
671
-
672
- if (initialSelections) {
673
- mergeSelection(config, state, initialSelections);
674
- }
675
-
676
- return state;
677
- }
678
-
679
- export function getIndirectType(type: GraphQLNamedType, info: GraphQLResolveInfo) {
680
- let targetType = type;
681
-
682
- while (targetType.extensions?.pothosIndirectInclude) {
683
- targetType = info.schema.getType(
684
- (targetType.extensions.pothosIndirectInclude as IndirectInclude).getType(),
685
- )!;
686
- }
687
-
688
- return targetType;
689
- }
690
-
691
- export function normalizeInclude(
692
- path: string[],
693
- type: GraphQLNamedType,
694
- expectedType?: GraphQLNamedType,
695
- ): IndirectInclude {
696
- let currentType = path.length > 0 ? type : (expectedType ?? type);
697
-
698
- const normalized: { name: string; type: string }[] = [];
699
-
700
- if (!(isObjectType(currentType) || isInterfaceType(currentType))) {
701
- throw new PothosValidationError(`Expected ${currentType} to be an Object type`);
702
- }
703
-
704
- for (const fieldName of path) {
705
- const field: GraphQLField<unknown, unknown> = currentType.getFields()[fieldName];
706
-
707
- if (!field) {
708
- throw new PothosValidationError(`Expected ${currentType} to have a field ${fieldName}`);
709
- }
710
-
711
- currentType = getNamedType(field.type);
712
-
713
- if (!(isObjectType(currentType) || isInterfaceType(currentType))) {
714
- throw new PothosValidationError(`Expected ${currentType} to be an Object or Interface type`);
715
- }
716
-
717
- normalized.push({ name: fieldName, type: currentType.name });
718
- }
719
-
720
- return {
721
- getType: () =>
722
- expectedType?.name ??
723
- (normalized.length > 0 ? normalized[normalized.length - 1].type : type.name),
724
- path: normalized,
725
- };
726
- }
727
-
728
- function fieldSkipped(info: GraphQLResolveInfo, selection: FieldNode) {
729
- const skip = getDirectiveValues(GraphQLSkipDirective, selection, info.variableValues);
730
- if (skip?.if === true) {
731
- return true;
732
- }
21
+ ...options
22
+ }: QueryFromInfoOptions<T>): T {
23
+ const plan = Plan.fromInfo(drizzleAdapter(config), {
24
+ ...options,
25
+ // A `select` without `columns` merges as "no columns yet", not "every column".
26
+ initial: select ? { columns: {}, ...select } : undefined,
27
+ });
733
28
 
734
- const include = getDirectiveValues(GraphQLIncludeDirective, selection, info.variableValues);
735
- if (include?.if === false) {
736
- return true;
29
+ if (!plan) {
30
+ // Nothing is planned under the paths: the caller gets its own selection back untouched.
31
+ return (select ?? {}) as T;
737
32
  }
738
33
 
739
- return false;
34
+ return (
35
+ isThenable(plan) ? plan.then((settled) => (settled as DrizzlePlan).query()) : plan.query()
36
+ ) as T;
740
37
  }
741
38
 
742
- function isDeferredFragment(
743
- node: FragmentSpreadNode | InlineFragmentNode,
744
- info: GraphQLResolveInfo,
745
- ) {
746
- const deferDirective = info.schema.getDirective('defer');
747
- if (!deferDirective) {
748
- return false;
749
- }
750
-
751
- const defer = getDirectiveValues(deferDirective, node, info.variableValues);
752
- return !!defer && defer.if !== false;
39
+ /**
40
+ * The plan for the field `info` resolves (or `typeName`), nothing recorded: a promise while a
41
+ * select beneath the field is async, undefined when `paths` are given and nothing is selected
42
+ * under them. The model loader's, and the first step of a `drizzleField` resolve, whose
43
+ * merges are recorded so the resolver's `query()` selection can come first.
44
+ */
45
+ export function planFromInfo({
46
+ config,
47
+ ...options
48
+ }: Omit<QueryFromInfoOptions<SelectionMap>, 'select'>): DrizzlePlan | undefined {
49
+ return Plan.fromInfo(drizzleAdapter(config), options);
50
+ }
51
+
52
+ /**
53
+ * The query for a plan `planFromInfo` returned, settled: the caller's `select` first, the walked
54
+ * plan after it, the mappings recorded, and the query serialized. This is how the `query()`
55
+ * builder handed to a `drizzleField` or `drizzleConnection` resolver stays synchronous once a
56
+ * selection beneath the field is async, with the precedence of `queryFromInfo`: a relation the
57
+ * document also plans with other arguments loads on its own.
58
+ */
59
+ export function queryFromPlan<T extends SelectionMap>(
60
+ plan: DrizzlePlan | undefined,
61
+ select?: T,
62
+ ): T {
63
+ if (!plan) {
64
+ // Nothing is selected under the paths: the caller gets its own selection back.
65
+ return (select ?? {}) as T;
66
+ }
67
+
68
+ // A `select` without `columns` merges as "no columns yet", not "every column".
69
+ return plan.query(select ? { columns: {}, ...select } : undefined) as T;
753
70
  }