@pothos/plugin-prisma 3.7.0 → 3.10.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 (86) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +191 -100
  3. package/dts/global-types.d.ts +2 -2
  4. package/dts/global-types.d.ts.map +1 -1
  5. package/dts/index.d.ts.map +1 -1
  6. package/dts/model-loader.d.ts +13 -2
  7. package/dts/model-loader.d.ts.map +1 -1
  8. package/dts/node-ref.d.ts.map +1 -1
  9. package/dts/object-ref.d.ts.map +1 -1
  10. package/dts/prisma-field-builder.d.ts +7 -7
  11. package/dts/prisma-field-builder.d.ts.map +1 -1
  12. package/dts/types.d.ts +19 -9
  13. package/dts/types.d.ts.map +1 -1
  14. package/dts/util/cursors.d.ts +5 -1
  15. package/dts/util/cursors.d.ts.map +1 -1
  16. package/dts/util/datamodel.d.ts +5 -1
  17. package/dts/util/datamodel.d.ts.map +1 -1
  18. package/dts/util/get-client.d.ts +5 -1
  19. package/dts/util/get-client.d.ts.map +1 -1
  20. package/dts/util/loader-map.d.ts +2 -2
  21. package/dts/util/loader-map.d.ts.map +1 -1
  22. package/dts/util/map-query.d.ts.map +1 -1
  23. package/esm/field-builder.js.map +1 -1
  24. package/esm/generator.js.map +1 -1
  25. package/esm/global-types.js.map +1 -1
  26. package/esm/index.js +5 -7
  27. package/esm/index.js.map +1 -1
  28. package/esm/model-loader.js +88 -3
  29. package/esm/model-loader.js.map +1 -1
  30. package/esm/node-ref.js.map +1 -1
  31. package/esm/prisma-field-builder.js +29 -24
  32. package/esm/prisma-field-builder.js.map +1 -1
  33. package/esm/schema-builder.js +24 -16
  34. package/esm/schema-builder.js.map +1 -1
  35. package/esm/util/cursors.js +95 -2
  36. package/esm/util/cursors.js.map +1 -1
  37. package/esm/util/datamodel.js.map +1 -1
  38. package/esm/util/get-client.js.map +1 -1
  39. package/esm/util/loader-map.js +2 -3
  40. package/esm/util/loader-map.js.map +1 -1
  41. package/esm/util/map-query.js +47 -9
  42. package/esm/util/map-query.js.map +1 -1
  43. package/esm/util/relation-map.js.map +1 -1
  44. package/esm/util/selections.js.map +1 -1
  45. package/lib/field-builder.js +3 -6
  46. package/lib/field-builder.js.map +1 -1
  47. package/lib/generator.js +8 -16
  48. package/lib/generator.js.map +1 -1
  49. package/lib/index.js +6 -21
  50. package/lib/index.js.map +1 -1
  51. package/lib/model-loader.js +82 -3
  52. package/lib/model-loader.js.map +1 -1
  53. package/lib/node-ref.js +1 -2
  54. package/lib/node-ref.js.map +1 -1
  55. package/lib/prisma-field-builder.js +36 -40
  56. package/lib/prisma-field-builder.js.map +1 -1
  57. package/lib/schema-builder.js +25 -19
  58. package/lib/schema-builder.js.map +1 -1
  59. package/lib/util/cursors.js +100 -6
  60. package/lib/util/cursors.js.map +1 -1
  61. package/lib/util/datamodel.js +2 -4
  62. package/lib/util/datamodel.js.map +1 -1
  63. package/lib/util/get-client.js +1 -3
  64. package/lib/util/get-client.js.map +1 -1
  65. package/lib/util/loader-map.js +3 -5
  66. package/lib/util/loader-map.js.map +1 -1
  67. package/lib/util/map-query.js +47 -9
  68. package/lib/util/map-query.js.map +1 -1
  69. package/lib/util/relation-map.js +1 -2
  70. package/lib/util/relation-map.js.map +1 -1
  71. package/lib/util/selections.js +2 -4
  72. package/lib/util/selections.js.map +1 -1
  73. package/package.json +8 -8
  74. package/src/global-types.ts +3 -1
  75. package/src/index.ts +14 -4
  76. package/src/model-loader.ts +144 -3
  77. package/src/node-ref.ts +1 -1
  78. package/src/object-ref.ts +1 -1
  79. package/src/prisma-field-builder.ts +45 -26
  80. package/src/schema-builder.ts +25 -20
  81. package/src/types.ts +52 -10
  82. package/src/util/cursors.ts +117 -1
  83. package/src/util/get-client.ts +2 -1
  84. package/src/util/loader-map.ts +3 -9
  85. package/src/util/map-query.ts +66 -3
  86. package/tsconfig.type.tsbuildinfo +1 -1
@@ -5,6 +5,7 @@ import {
5
5
  FieldKind,
6
6
  FieldRef,
7
7
  InputFieldMap,
8
+ isThenable,
8
9
  MaybePromise,
9
10
  NormalizeArgs,
10
11
  ObjectRef,
@@ -136,18 +137,29 @@ export class PrismaObjectFieldBuilder<
136
137
  const relationSelect = (
137
138
  args: object,
138
139
  context: object,
139
- nestedQuery: (query: unknown) => unknown,
140
+ nestedQuery: (query: unknown, path: unknown) => unknown,
140
141
  ) => ({
141
142
  select: {
142
- [name]: nestedQuery({
143
- ...((typeof query === 'function' ? query(args, context) : query) as {}),
144
- ...prismaCursorConnectionQuery({
145
- parseCursor,
146
- maxSize,
147
- defaultSize,
148
- args,
149
- }),
150
- }),
143
+ [name]: nestedQuery(
144
+ {
145
+ ...((typeof query === 'function' ? query(args, context) : query) as {}),
146
+ ...prismaCursorConnectionQuery({
147
+ parseCursor,
148
+ maxSize,
149
+ defaultSize,
150
+ args,
151
+ }),
152
+ },
153
+ {
154
+ getType: () => {
155
+ if (!typeName) {
156
+ typeName = this.builder.configStore.getTypeConfig(ref).name;
157
+ }
158
+ return typeName;
159
+ },
160
+ path: [{ name: 'edges' }, { name: 'node' }],
161
+ },
162
+ ),
151
163
  },
152
164
  });
153
165
 
@@ -215,18 +227,6 @@ export class PrismaObjectFieldBuilder<
215
227
  ...(connectionOptions as { fields?: (t: unknown) => {} }).fields?.(t),
216
228
  })
217
229
  : (connectionOptions as { fields: undefined }).fields,
218
- extensions: {
219
- ...(connectionOptions as Record<string, {}> | undefined)?.extensions,
220
- pothosPrismaIndirectInclude: {
221
- getType: () => {
222
- if (!typeName) {
223
- typeName = this.builder.configStore.getTypeConfig(ref).name;
224
- }
225
- return typeName;
226
- },
227
- path: [{ name: 'edges' }, { name: 'node' }],
228
- },
229
- },
230
230
  },
231
231
  edgeOptions,
232
232
  );
@@ -327,19 +327,26 @@ export class PrismaObjectFieldBuilder<
327
327
  }) as FieldRef<number, 'Object'>;
328
328
  }
329
329
 
330
- variant<Variant extends Model['Name'] | PrismaObjectRef<Model>>(
330
+ variant<
331
+ Variant extends Model['Name'] | PrismaObjectRef<Model>,
332
+ Args extends InputFieldMap,
333
+ Nullable,
334
+ >(
331
335
  ...allArgs: NormalizeArgs<
332
336
  [
333
337
  variant: Variant,
334
338
  options?: VariantFieldOptions<
335
339
  Types,
336
340
  Model,
337
- Variant extends PrismaObjectRef<Model> ? Variant : PrismaObjectRef<Model>
341
+ Variant extends PrismaObjectRef<Model> ? Variant : PrismaObjectRef<Model>,
342
+ Args,
343
+ Nullable,
344
+ Shape
338
345
  >,
339
346
  ]
340
347
  >
341
348
  ): FieldRef<Model['Shape'], 'Object'> {
342
- const [variant, options = {} as never] = allArgs;
349
+ const [variant, { isNull, nullable, ...options } = {} as never] = allArgs;
343
350
  const ref: PrismaObjectRef<PrismaModelTypes> =
344
351
  typeof variant === 'string' ? getRefFromModel(variant, this.builder) : variant;
345
352
 
@@ -353,7 +360,19 @@ export class PrismaObjectFieldBuilder<
353
360
  ...options?.extensions,
354
361
  pothosPrismaSelect: selfSelect,
355
362
  },
356
- resolve: (parent, args, context, info) => parent as never,
363
+ nullable: nullable ?? !!isNull,
364
+ resolve: isNull
365
+ ? (parent, args, context, info) => {
366
+ const parentIsNull = isNull(parent, args as never, context, info);
367
+ if (parentIsNull) {
368
+ if (isThenable(parentIsNull)) {
369
+ return parentIsNull.then((resolved) => (resolved ? null : parent)) as never;
370
+ }
371
+ return null as never;
372
+ }
373
+ return parent as never;
374
+ }
375
+ : (parent) => parent as never,
357
376
  }) as FieldRef<Model['Shape'], 'Object'>;
358
377
  }
359
378
 
@@ -11,6 +11,7 @@ import { PrismaObjectFieldBuilder } from './field-builder';
11
11
  import { ModelLoader } from './model-loader';
12
12
  import PrismaNodeRef from './node-ref';
13
13
  import { PrismaModelTypes, PrismaNodeOptions } from './types';
14
+ import { getDefaultIDParser, getDefaultIDSerializer } from './util/cursors';
14
15
  import { getDelegateFromModel, getRefFromModel } from './util/datamodel';
15
16
  import { getClient, getDMMF } from './util/get-client';
16
17
  import { queryFromInfo } from './util/map-query';
@@ -18,10 +19,14 @@ import { getRelationMap } from './util/relation-map';
18
19
 
19
20
  const schemaBuilderProto = SchemaBuilder.prototype as PothosSchemaTypes.SchemaBuilder<SchemaTypes>;
20
21
 
21
- schemaBuilderProto.prismaObject = function prismaObject(type, { fields, findUnique, ...options }) {
22
+ schemaBuilderProto.prismaObject = function prismaObject(
23
+ type,
24
+ { fields, findUnique, select, include, ...options },
25
+ ) {
22
26
  const ref = options.variant ? this.objectRef(options.variant) : getRefFromModel(type, this);
23
27
  const name = options.variant ?? options.name ?? type;
24
28
  const fieldMap = getRelationMap(getDMMF(this)).get(type)!;
29
+ const idSelection = ModelLoader.getDefaultIDSelection(ref, type, this);
25
30
 
26
31
  ref.name = name;
27
32
 
@@ -29,18 +34,11 @@ schemaBuilderProto.prismaObject = function prismaObject(type, { fields, findUniq
29
34
  ...(options as {}),
30
35
  extensions: {
31
36
  ...options.extensions,
32
- pothosPrismaInclude: options.include,
37
+ pothosPrismaInclude: include,
33
38
  pothosPrismaModel: type,
34
39
  pothosPrismaFieldMap: fieldMap,
35
- pothosPrismaSelect: options.select,
36
- pothosPrismaLoader: ModelLoader.forRef(
37
- type,
38
- (findUnique as never) ||
39
- (() => {
40
- throw new Error(`Missing findUnique for ${ref.name}`);
41
- }),
42
- this,
43
- ),
40
+ pothosPrismaSelect: select && { ...idSelection, ...(select as {}) },
41
+ pothosPrismaLoader: ModelLoader.forRef(ref, type, findUnique as never, this),
44
42
  },
45
43
  name,
46
44
  fields: fields
@@ -65,26 +63,33 @@ schemaBuilderProto.prismaNode = function prismaNode(
65
63
  },
66
64
  type: keyof SchemaTypes['PrismaTypes'],
67
65
  {
68
- findUnique,
66
+ id: { field, resolve: rawResolve, ...idOptions },
67
+ findUnique: rawFindUnique,
69
68
  name,
70
69
  variant,
71
70
  ...options
72
- }: PrismaNodeOptions<SchemaTypes, PrismaModelTypes, [], never, {}, {}>,
71
+ }: PrismaNodeOptions<SchemaTypes, PrismaModelTypes, [], never, {}, {}, undefined>,
73
72
  ) {
73
+ const fieldName = field as unknown as string;
74
74
  const interfaceRef = this.nodeInterfaceRef?.();
75
+ const resolve = rawResolve ?? getDefaultIDSerializer(type, fieldName, this);
76
+ const idParser = fieldName ? getDefaultIDParser(type, fieldName, this) : undefined;
77
+ const typeName = variant ?? name ?? type;
78
+ const nodeRef = new PrismaNodeRef(typeName);
79
+ const findUnique = rawFindUnique
80
+ ? (parent: unknown, context: {}) =>
81
+ rawFindUnique(resolve(parent as never, context) as string, context)
82
+ : ModelLoader.getFindUniqueForField(nodeRef, type, fieldName, this);
75
83
 
76
84
  if (!interfaceRef) {
77
85
  throw new TypeError('builder.prismaNode requires @pothos/plugin-relay to be installed');
78
86
  }
79
87
 
80
- const typeName = variant ?? name ?? type;
81
- const nodeRef = new PrismaNodeRef(typeName);
82
88
  const extendedOptions = {
83
89
  ...options,
84
90
  variant,
85
91
  interfaces: [interfaceRef],
86
- findUnique: (parent: unknown, context: {}) =>
87
- findUnique(options.id.resolve(parent as never, context) as string, context),
92
+ findUnique,
88
93
  loadWithoutCache: async (
89
94
  id: string,
90
95
  context: SchemaTypes['Context'],
@@ -95,7 +100,7 @@ schemaBuilderProto.prismaNode = function prismaNode(
95
100
  const record = await delegate.findUnique({
96
101
  ...query,
97
102
  rejectOnNotFound: true,
98
- where: findUnique(id, context),
103
+ where: rawFindUnique ? rawFindUnique(id, context) : { [fieldName]: idParser!(id) },
99
104
  } as never);
100
105
 
101
106
  brandWithType(record, typeName as OutputType<SchemaTypes>);
@@ -117,7 +122,7 @@ schemaBuilderProto.prismaNode = function prismaNode(
117
122
  globalID: (options: Record<string, unknown>) => FieldRef<unknown>;
118
123
  }
119
124
  ).globalID({
120
- ...options.id,
125
+ ...idOptions,
121
126
  nullable: false,
122
127
  args: {},
123
128
  resolve: async (
@@ -127,7 +132,7 @@ schemaBuilderProto.prismaNode = function prismaNode(
127
132
  info: GraphQLResolveInfo,
128
133
  ) => ({
129
134
  type: nodeConfig.name,
130
- id: await options.id.resolve(parent, context),
135
+ id: await resolve(parent, context),
131
136
  }),
132
137
  }),
133
138
  );
package/src/types.ts CHANGED
@@ -89,6 +89,10 @@ export type PrismaObjectFieldOptions<
89
89
  | ((
90
90
  args: InputShapeFromFields<Args>,
91
91
  ctx: Types['Context'],
92
+ nestedSelection: <Selection extends boolean | {}>(
93
+ selection?: Selection,
94
+ path?: string[],
95
+ ) => Selection,
92
96
  ) => ExtractModel<Types, ParentShape>['Select'])
93
97
  );
94
98
  };
@@ -169,13 +173,13 @@ export type PrismaObjectTypeOptions<
169
173
  | {
170
174
  include?: Include & Model['Include'];
171
175
  select?: never;
172
- findUnique: FindUnique &
176
+ findUnique?: FindUnique &
173
177
  (((parent: Shape, context: Types['Context']) => Model['Where']) | null);
174
178
  }
175
179
  | {
176
180
  select: Model['Select'] & Select;
177
181
  include?: never;
178
- findUnique: (parent: Shape, context: Types['Context']) => Model['Where'];
182
+ findUnique?: (parent: Shape, context: Types['Context']) => Model['Where'];
179
183
  }
180
184
  );
181
185
 
@@ -196,6 +200,7 @@ export type PrismaNodeOptions<
196
200
  Include,
197
201
  Select,
198
202
  Shape extends object,
203
+ UniqueField,
199
204
  > = NameOrVariant &
200
205
  Omit<
201
206
  | PothosSchemaTypes.ObjectTypeOptions<Types, Shape>
@@ -214,9 +219,20 @@ export type PrismaNodeOptions<
214
219
  MaybePromise<OutputShape<Types, 'ID'>>
215
220
  >,
216
221
  'args' | 'nullable' | 'resolve' | 'type'
217
- > & {
218
- resolve: (parent: Shape, context: Types['Context']) => MaybePromise<OutputShape<Types, 'ID'>>;
219
- };
222
+ > &
223
+ (
224
+ | {
225
+ field?: never;
226
+ resolve: (
227
+ parent: Shape,
228
+ context: Types['Context'],
229
+ ) => MaybePromise<OutputShape<Types, 'ID'>>;
230
+ }
231
+ | {
232
+ resolve?: never;
233
+ field: UniqueField extends keyof Model['Where'] ? UniqueField : keyof Model['Where'];
234
+ }
235
+ );
220
236
  fields?: PrismaObjectFieldsShape<
221
237
  Types,
222
238
  Model,
@@ -224,8 +240,14 @@ export type PrismaNodeOptions<
224
240
  Shape & { [prismaModelName]?: Model['Name'] },
225
241
  Select
226
242
  >;
227
- findUnique: (id: string, context: Types['Context']) => Model['Where'];
228
- } & (
243
+ } & (UniqueField extends string
244
+ ? {
245
+ findUnique?: (id: string, context: Types['Context']) => Model['Where'];
246
+ }
247
+ : {
248
+ findUnique: (id: string, context: Types['Context']) => Model['Where'];
249
+ }) &
250
+ (
229
251
  | {
230
252
  include?: Include & Model['Include'];
231
253
  select?: never;
@@ -330,10 +352,28 @@ export type VariantFieldOptions<
330
352
  Types extends SchemaTypes,
331
353
  Model extends PrismaModelTypes,
332
354
  Variant extends PrismaObjectRef<Model>,
355
+ Args extends InputFieldMap,
356
+ isNull,
357
+ Shape,
333
358
  > = Omit<
334
- PothosSchemaTypes.ObjectFieldOptions<Types, Model['Shape'], Variant, false, {}, Model['Shape']>,
359
+ PothosSchemaTypes.ObjectFieldOptions<
360
+ Types,
361
+ Shape,
362
+ Variant,
363
+ unknown extends isNull ? false : true,
364
+ Args,
365
+ Model['Shape']
366
+ >,
335
367
  'resolve' | 'type'
336
- >;
368
+ > & {
369
+ isNull?: isNull &
370
+ ((
371
+ parent: Shape,
372
+ args: InputShapeFromFields<Args>,
373
+ context: Types['Context'],
374
+ info: GraphQLResolveInfo,
375
+ ) => MaybePromise<boolean>);
376
+ };
337
377
 
338
378
  export type RelationCountOptions<
339
379
  Types extends SchemaTypes,
@@ -546,8 +586,9 @@ export type FieldSelection =
546
586
  | ((
547
587
  args: object,
548
588
  context: object,
549
- query: (
589
+ mergeNestedSelection: (
550
590
  selection: SelectionMap | boolean | ((args: object, context: object) => SelectionMap),
591
+ path?: string[] | IndirectInclude,
551
592
  ) => SelectionMap | boolean,
552
593
  ) => SelectionMap);
553
594
 
@@ -555,6 +596,7 @@ export type LoaderMappings = Record<
555
596
  string,
556
597
  {
557
598
  field: string;
599
+ type: string;
558
600
  mappings: LoaderMappings;
559
601
  indirectPath: string[];
560
602
  }
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable no-nested-ternary */
2
- import { MaybePromise } from '@pothos/core';
2
+ import { MaybePromise, SchemaTypes } from '@pothos/core';
3
+ import { getModel } from './datamodel';
4
+ import { DMMFField } from './get-client';
3
5
 
4
6
  const DEFAULT_MAX_SIZE = 100;
5
7
  const DEFAULT_SIZE = 20;
@@ -57,6 +59,120 @@ export function parseRawCursor(cursor: unknown) {
57
59
  }
58
60
  }
59
61
 
62
+ export function parseID(id: string, dataType: string): unknown {
63
+ if (!id) {
64
+ return id;
65
+ }
66
+
67
+ switch (dataType) {
68
+ case 'String':
69
+ return id;
70
+ case 'Int':
71
+ return Number.parseInt(id, 10);
72
+ case 'BigInt':
73
+ // eslint-disable-next-line node/no-unsupported-features/es-builtins
74
+ return BigInt(id);
75
+ case 'Boolean':
76
+ return id !== 'false';
77
+ case 'Float':
78
+ case 'Decimal':
79
+ return Number.parseFloat(id);
80
+ case 'DateTime':
81
+ return new Date(id);
82
+ case 'Json':
83
+ return JSON.parse(id) as unknown;
84
+ case 'Byte':
85
+ return Buffer.from(id, 'base64');
86
+ default:
87
+ return id;
88
+ }
89
+ }
90
+
91
+ export function getDefaultIDSerializer<Types extends SchemaTypes>(
92
+ modelName: string,
93
+ fieldName: string,
94
+ builder: PothosSchemaTypes.SchemaBuilder<Types>,
95
+ ): (parent: Record<string, unknown>) => unknown {
96
+ const model = getModel(modelName, builder);
97
+
98
+ const field = model.fields.find((f) => f.name === fieldName);
99
+
100
+ if (field) {
101
+ return (parent) => serializeID(parent[fieldName], field.type);
102
+ }
103
+
104
+ if ((model.primaryKey?.name ?? model.primaryKey?.fields.join('_')) === fieldName) {
105
+ const fields = model.primaryKey!.fields.map((n) => model.fields.find((f) => f.name === n)!);
106
+ return (parent) => JSON.stringify(fields.map((f) => serializeID(parent[f.name], f.kind)));
107
+ }
108
+
109
+ const index = model.uniqueIndexes.find((idx) => (idx.name ?? idx.fields.join('_')) === fieldName);
110
+
111
+ if (index) {
112
+ const fields = index.fields.map((n) => model.fields.find((f) => f.name === n)!);
113
+ return (parent) => JSON.stringify(fields.map((f) => serializeID(parent[f.name], f.kind)));
114
+ }
115
+
116
+ throw new Error(`Unable to find ${fieldName} for model ${modelName}`);
117
+ }
118
+
119
+ export function getDefaultIDParser<Types extends SchemaTypes>(
120
+ modelName: string,
121
+ fieldName: string,
122
+ builder: PothosSchemaTypes.SchemaBuilder<Types>,
123
+ ): (id: string) => unknown {
124
+ if (!fieldName) {
125
+ throw new Error('Missing field name');
126
+ }
127
+ const model = getModel(modelName, builder);
128
+
129
+ const field = model.fields.find((f) => f.name === fieldName);
130
+
131
+ if (field) {
132
+ return (id) => parseID(id, field.type);
133
+ }
134
+
135
+ const index = model.uniqueIndexes.find((idx) => (idx.name ?? idx.fields.join('_')) === fieldName);
136
+
137
+ let fields: DMMFField[] | undefined;
138
+ if ((model.primaryKey?.name ?? model.primaryKey?.fields.join('_')) === fieldName) {
139
+ fields = model.primaryKey!.fields.map((n) => model.fields.find((f) => f.name === n)!);
140
+ } else if (index) {
141
+ fields = index.fields.map((n) => model.fields.find((f) => f.name === n)!);
142
+ }
143
+
144
+ if (!fields) {
145
+ throw new Error(`Unable to find ${fieldName} for model ${modelName}`);
146
+ }
147
+
148
+ return (id) => {
149
+ const parts = JSON.parse(id) as unknown;
150
+
151
+ if (!Array.isArray(parts)) {
152
+ throw new TypeError(`Invalid id received for ${fieldName} of ${modelName}`);
153
+ }
154
+
155
+ const result: Record<string, unknown> = {};
156
+
157
+ for (let i = 0; i < fields!.length; i += 1) {
158
+ result[fields![i].name] = parseID(parts[i] as string, fields![i].type);
159
+ }
160
+
161
+ return result;
162
+ };
163
+ }
164
+
165
+ export function serializeID(id: unknown, dataType: string) {
166
+ switch (dataType) {
167
+ case 'Json':
168
+ return JSON.stringify(id);
169
+ case 'Byte':
170
+ return (id as Buffer).toString('base64');
171
+ default:
172
+ return String(id);
173
+ }
174
+ }
175
+
60
176
  export function parseCompositeCursor(fields: string[]) {
61
177
  return (cursor: string) => {
62
178
  const parsed = parseRawCursor(cursor) as unknown[];
@@ -20,7 +20,8 @@ interface DMMF {
20
20
  name: string;
21
21
  fields: DMMFField[];
22
22
  fieldMap?: Record<string, DMMFField>;
23
- primaryKey: { name: string; fields: string[] } | null;
23
+ primaryKey: { name: string | null; fields: string[] } | null;
24
+ uniqueIndexes: { name: string | null; fields: string[] }[];
24
25
  }
25
26
  >;
26
27
  }
@@ -1,7 +1,6 @@
1
- import { GraphQLNamedType, GraphQLResolveInfo } from 'graphql';
1
+ import { GraphQLResolveInfo } from 'graphql';
2
2
  import { createContextCache } from '@pothos/core';
3
3
  import { LoaderMappings } from '../types';
4
- import { getIndirectType } from './map-query';
5
4
 
6
5
  const cache = createContextCache((ctx) => new Map<string, LoaderMappings>());
7
6
 
@@ -23,18 +22,13 @@ export function cacheKey(type: string, path: GraphQLResolveInfo['path'], subPath
23
22
  return `${type}@${key}`;
24
23
  }
25
24
 
26
- export function setLoaderMappings(
27
- ctx: object,
28
- info: GraphQLResolveInfo,
29
- value: LoaderMappings,
30
- type: GraphQLNamedType,
31
- ) {
25
+ export function setLoaderMappings(ctx: object, info: GraphQLResolveInfo, value: LoaderMappings) {
32
26
  Object.keys(value).forEach((field) => {
33
27
  const map = cache(ctx);
34
28
 
35
29
  const mapping = value[field];
36
30
  const subPath = [...mapping.indirectPath, field];
37
- const key = cacheKey(getIndirectType(type, info).name, info.path, subPath);
31
+ const key = cacheKey(mapping.type, info.path, subPath);
38
32
 
39
33
  map.set(key, mapping.mappings);
40
34
  });
@@ -4,6 +4,7 @@ import {
4
4
  FieldNode,
5
5
  FragmentDefinitionNode,
6
6
  getNamedType,
7
+ GraphQLField,
7
8
  GraphQLNamedType,
8
9
  GraphQLObjectType,
9
10
  GraphQLResolveInfo,
@@ -232,16 +233,45 @@ function addFieldSelection(
232
233
  unknown
233
234
  >;
234
235
 
235
- fieldSelectionMap = fieldSelect(args, context, (rawQuery) => {
236
+ fieldSelectionMap = fieldSelect(args, context, (rawQuery, indirectInclude) => {
236
237
  const returnType = getNamedType(field.type);
237
238
  const query = typeof rawQuery === 'function' ? rawQuery(args, context) : rawQuery;
238
239
 
239
- const fieldState = createStateForType(returnType, info, state);
240
+ const normalizedIndirectInclude = Array.isArray(indirectInclude)
241
+ ? normalizeInclude(indirectInclude, getIndirectType(returnType, info))
242
+ : indirectInclude;
243
+
244
+ const fieldState = createStateForType(
245
+ getIndirectType(
246
+ normalizedIndirectInclude
247
+ ? info.schema.getType(normalizedIndirectInclude.getType())!
248
+ : returnType,
249
+ info,
250
+ ),
251
+ info,
252
+ state,
253
+ );
240
254
 
241
255
  if (typeof query === 'object' && Object.keys(query).length > 0) {
242
256
  mergeSelection(fieldState, { select: {}, ...query });
243
257
  }
244
258
 
259
+ if (normalizedIndirectInclude && normalizedIndirectInclude.path.length > 0) {
260
+ resolveIndirectInclude(
261
+ returnType,
262
+ info,
263
+ selection,
264
+ [
265
+ ...((returnType.extensions?.pothosPrismaIndirectInclude as { path: [] })?.path ?? []),
266
+ ...normalizedIndirectInclude.path,
267
+ ],
268
+ [],
269
+ (resolvedType, resolvedField, path) => {
270
+ addTypeSelectionsForField(resolvedType, context, info, fieldState, resolvedField, path);
271
+ },
272
+ );
273
+ }
274
+
245
275
  addTypeSelectionsForField(returnType, context, info, fieldState, selection, []);
246
276
 
247
277
  // eslint-disable-next-line prefer-destructuring
@@ -257,6 +287,7 @@ function addFieldSelection(
257
287
  mergeSelection(state, fieldSelectionMap);
258
288
  state.mappings[selection.alias?.value ?? selection.name.value] = {
259
289
  field: selection.name.value,
290
+ type: type.name,
260
291
  mappings,
261
292
  indirectPath,
262
293
  };
@@ -268,6 +299,7 @@ function addFieldSelection(
268
299
  mergeSelection(state.parent, { select: fieldParentSelect });
269
300
  state.mappings[selection.alias?.value ?? selection.name.value] = {
270
301
  field: selection.name.value,
302
+ type: type.name,
271
303
  mappings,
272
304
  indirectPath,
273
305
  };
@@ -280,7 +312,7 @@ export function queryFromInfo(context: object, info: GraphQLResolveInfo, typeNam
280
312
 
281
313
  addTypeSelectionsForField(type, context, info, state, info.fieldNodes[0], []);
282
314
 
283
- setLoaderMappings(context, info, state.mappings, type);
315
+ setLoaderMappings(context, info, state.mappings);
284
316
 
285
317
  return selectionToQuery(state);
286
318
  }
@@ -330,3 +362,34 @@ export function getIndirectType(type: GraphQLNamedType, info: GraphQLResolveInfo
330
362
 
331
363
  return targetType;
332
364
  }
365
+
366
+ function normalizeInclude(path: string[], type: GraphQLNamedType) {
367
+ let currentType = type;
368
+
369
+ const normalized: { name: string; type: string }[] = [];
370
+
371
+ if (!isObjectType(currentType)) {
372
+ throw new Error(`Expected ${currentType} to be an Object type`);
373
+ }
374
+
375
+ for (const fieldName of path) {
376
+ const field: GraphQLField<unknown, unknown> = currentType.getFields()[fieldName];
377
+
378
+ if (!field) {
379
+ throw new Error(`Expected ${currentType} to have a field ${fieldName}`);
380
+ }
381
+
382
+ currentType = getNamedType(field.type);
383
+
384
+ if (!isObjectType(currentType)) {
385
+ throw new Error(`Expected ${currentType} to be an Object type`);
386
+ }
387
+
388
+ normalized.push({ name: fieldName, type: currentType.name });
389
+ }
390
+
391
+ return {
392
+ getType: () => (normalized.length > 0 ? normalized[normalized.length - 1].type : type.name),
393
+ path: normalized,
394
+ };
395
+ }