@pothos/plugin-prisma 3.8.0 → 3.11.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 +187 -94
  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 +5 -5
  11. package/dts/prisma-field-builder.d.ts.map +1 -1
  12. package/dts/types.d.ts +16 -8
  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 +16 -21
  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 +25 -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 +21 -22
  80. package/src/schema-builder.ts +25 -20
  81. package/src/types.ts +32 -8
  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
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;
@@ -564,8 +586,9 @@ export type FieldSelection =
564
586
  | ((
565
587
  args: object,
566
588
  context: object,
567
- query: (
589
+ mergeNestedSelection: (
568
590
  selection: SelectionMap | boolean | ((args: object, context: object) => SelectionMap),
591
+ path?: string[] | IndirectInclude,
569
592
  ) => SelectionMap | boolean,
570
593
  ) => SelectionMap);
571
594
 
@@ -573,6 +596,7 @@ export type LoaderMappings = Record<
573
596
  string,
574
597
  {
575
598
  field: string;
599
+ type: string;
576
600
  mappings: LoaderMappings;
577
601
  indirectPath: string[];
578
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
+ }