@pothos/plugin-prisma 3.29.0 → 3.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +9 -1
  3. package/dts/global-types.d.ts +2 -0
  4. package/dts/global-types.d.ts.map +1 -1
  5. package/dts/model-loader.d.ts +3 -3
  6. package/dts/model-loader.d.ts.map +1 -1
  7. package/dts/prisma-field-builder.d.ts +1 -1
  8. package/dts/prisma-field-builder.d.ts.map +1 -1
  9. package/dts/types.d.ts +5 -3
  10. package/dts/types.d.ts.map +1 -1
  11. package/dts/util/map-query.d.ts.map +1 -1
  12. package/dts/util/selections.d.ts +1 -1
  13. package/dts/util/selections.d.ts.map +1 -1
  14. package/esm/field-builder.js.map +1 -1
  15. package/esm/generator.js +4 -3
  16. package/esm/generator.js.map +1 -1
  17. package/esm/global-types.js.map +1 -1
  18. package/esm/index.js.map +1 -1
  19. package/esm/model-loader.js.map +1 -1
  20. package/esm/object-ref.js.map +1 -1
  21. package/esm/prisma-field-builder.js +28 -14
  22. package/esm/prisma-field-builder.js.map +1 -1
  23. package/esm/schema-builder.js.map +1 -1
  24. package/esm/util/cursors.js.map +1 -1
  25. package/esm/util/datamodel.js.map +1 -1
  26. package/esm/util/deep-equal.js.map +1 -1
  27. package/esm/util/description.js.map +1 -1
  28. package/esm/util/get-client.js.map +1 -1
  29. package/esm/util/loader-map.js.map +1 -1
  30. package/esm/util/map-query.js +13 -20
  31. package/esm/util/map-query.js.map +1 -1
  32. package/esm/util/relation-map.js.map +1 -1
  33. package/esm/util/selections.js +12 -4
  34. package/esm/util/selections.js.map +1 -1
  35. package/lib/field-builder.js.map +1 -1
  36. package/lib/generator.js +3 -2
  37. package/lib/generator.js.map +1 -1
  38. package/lib/index.js.map +1 -1
  39. package/lib/model-loader.js.map +1 -1
  40. package/lib/object-ref.js.map +1 -1
  41. package/lib/prisma-field-builder.js +28 -14
  42. package/lib/prisma-field-builder.js.map +1 -1
  43. package/lib/schema-builder.js.map +1 -1
  44. package/lib/util/cursors.js.map +1 -1
  45. package/lib/util/datamodel.js.map +1 -1
  46. package/lib/util/deep-equal.js.map +1 -1
  47. package/lib/util/description.js.map +1 -1
  48. package/lib/util/get-client.js.map +1 -1
  49. package/lib/util/loader-map.js.map +1 -1
  50. package/lib/util/map-query.js +13 -19
  51. package/lib/util/map-query.js.map +1 -1
  52. package/lib/util/relation-map.js.map +1 -1
  53. package/lib/util/selections.js +12 -4
  54. package/lib/util/selections.js.map +1 -1
  55. package/package.json +6 -6
  56. package/src/generator.ts +4 -2
  57. package/src/global-types.ts +2 -0
  58. package/src/model-loader.ts +5 -5
  59. package/src/prisma-field-builder.ts +37 -12
  60. package/src/types.ts +5 -1
  61. package/src/util/map-query.ts +64 -49
  62. package/src/util/selections.ts +21 -8
  63. package/tsconfig.type.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pothos/plugin-prisma",
3
- "version": "3.29.0",
3
+ "version": "3.31.1",
4
4
  "description": "A Pothos plugin for more efficient integration with prisma",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./dts/index.d.ts",
@@ -36,7 +36,7 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@prisma/generator-helper": "^4.2.1"
39
+ "@prisma/generator-helper": "^4.3.1"
40
40
  },
41
41
  "prisma": {
42
42
  "seed": "node prisma/seed.mjs"
@@ -48,16 +48,16 @@
48
48
  "typescript": ">=4.7.2"
49
49
  },
50
50
  "devDependencies": {
51
- "@pothos/core": "3.19.0",
52
- "@pothos/plugin-complexity": "3.8.0",
51
+ "@pothos/core": "3.19.1",
52
+ "@pothos/plugin-complexity": "3.9.1",
53
53
  "@pothos/plugin-errors": "3.6.0",
54
54
  "@pothos/plugin-relay": "3.24.0",
55
55
  "@pothos/plugin-simple-objects": "3.4.0",
56
56
  "@pothos/test-utils": "1.3.0",
57
- "@prisma/client": "^4.2.1",
57
+ "@prisma/client": "^4.3.1",
58
58
  "graphql": "16.6.0",
59
59
  "graphql-tag": "^2.12.6",
60
- "prisma": "^4.2.1"
60
+ "prisma": "^4.3.1"
61
61
  },
62
62
  "scripts": {
63
63
  "generate": "prisma generate",
package/src/generator.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable no-magic-numbers */
2
2
  /* eslint-disable no-nested-ternary */
3
3
  import { mkdir, writeFile } from 'fs';
4
- import { dirname } from 'path';
4
+ import { dirname, resolve as resolvePath } from 'path';
5
5
  import ts, { ListFormat, ScriptKind, ScriptTarget, SyntaxKind, version } from 'typescript';
6
6
  import { generatorHandler } from '@prisma/generator-helper';
7
7
 
@@ -24,11 +24,13 @@ function checkTSVersion() {
24
24
  }
25
25
  }
26
26
 
27
+ const defaultOutput = resolvePath(__dirname, '../generated.ts');
28
+
27
29
  generatorHandler({
28
30
  onManifest: () => ({
29
31
  prettyName: 'Pothos integration',
30
- defaultOutput: 'node_modules/@pothos/plugin-prisma/generated.ts',
31
32
  requiresGenerators: ['prisma-client-js'],
33
+ defaultOutput,
32
34
  }),
33
35
  onGenerate: async (options) => {
34
36
  checkTSVersion();
@@ -39,6 +39,7 @@ declare global {
39
39
  export interface SchemaBuilderOptions<Types extends SchemaTypes> {
40
40
  prisma:
41
41
  | {
42
+ filterConnectionTotalCount?: boolean;
42
43
  client: PrismaClient;
43
44
  exposeDescriptions?:
44
45
  | boolean
@@ -48,6 +49,7 @@ declare global {
48
49
  };
49
50
  }
50
51
  | {
52
+ filterConnectionTotalCount?: boolean;
51
53
  client: (ctx: Types['Context']) => PrismaClient;
52
54
  dmmf: { datamodel: unknown };
53
55
  exposeDescriptions?:
@@ -11,7 +11,7 @@ import {
11
11
  export class ModelLoader {
12
12
  model: object;
13
13
  builder: PothosSchemaTypes.SchemaBuilder<never>;
14
- findUnique: (args: unknown, ctx: {}) => unknown;
14
+ findUnique: (model: Record<string, unknown>, ctx: {}) => unknown;
15
15
  modelName: string;
16
16
 
17
17
  staged = new Set<{
@@ -23,7 +23,7 @@ export class ModelLoader {
23
23
  model: object,
24
24
  builder: PothosSchemaTypes.SchemaBuilder<never>,
25
25
  modelName: string,
26
- findUnique: (args: unknown, ctx: {}) => unknown,
26
+ findUnique: (model: Record<string, unknown>, ctx: {}) => unknown,
27
27
  ) {
28
28
  this.model = model;
29
29
  this.builder = builder;
@@ -34,7 +34,7 @@ export class ModelLoader {
34
34
  static forRef<Types extends SchemaTypes>(
35
35
  ref: ObjectRef<unknown>,
36
36
  modelName: string,
37
- findUnique: (args: unknown, ctx: {}) => unknown,
37
+ findUnique: ((model: Record<string, unknown>, ctx: {}) => unknown) | undefined,
38
38
  builder: PothosSchemaTypes.SchemaBuilder<Types>,
39
39
  ) {
40
40
  return createContextCache(
@@ -238,14 +238,14 @@ export class ModelLoader {
238
238
  if (delegate.findUniqueOrThrow) {
239
239
  return delegate.findUniqueOrThrow({
240
240
  ...selectionToQuery(state),
241
- where: { ...(this.findUnique(this.model, context) as {}) },
241
+ where: { ...(this.findUnique(this.model as Record<string, unknown>, context) as {}) },
242
242
  } as never) as Promise<Record<string, unknown>>;
243
243
  }
244
244
 
245
245
  return delegate.findUnique({
246
246
  rejectOnNotFound: true,
247
247
  ...selectionToQuery(state),
248
- where: { ...(this.findUnique(this.model, context) as {}) },
248
+ where: { ...(this.findUnique(this.model as Record<string, unknown>, context) as {}) },
249
249
  } as never) as Promise<Record<string, unknown>>;
250
250
  }),
251
251
  state,
@@ -1,5 +1,6 @@
1
+ /* eslint-disable no-nested-ternary */
1
2
  /* eslint-disable no-underscore-dangle */
2
- import { GraphQLResolveInfo } from 'graphql';
3
+ import { FieldNode, GraphQLResolveInfo } from 'graphql';
3
4
  import {
4
5
  CompatibleTypes,
5
6
  FieldKind,
@@ -172,6 +173,7 @@ export class PrismaObjectFieldBuilder<
172
173
  args,
173
174
  }),
174
175
  });
176
+
175
177
  const cursorSelection = ModelLoader.getCursorSelection(
176
178
  ref,
177
179
  relationField.type,
@@ -182,7 +184,8 @@ export class PrismaObjectFieldBuilder<
182
184
  const relationSelect = (
183
185
  args: object,
184
186
  context: object,
185
- nestedQuery: (query: unknown, path: unknown) => unknown,
187
+ nestedQuery: (query: unknown, path?: unknown) => { select?: {} },
188
+ getSelection: (path: string[]) => FieldNode | null,
186
189
  ) => {
187
190
  const nested = nestedQuery(getQuery(args, context), {
188
191
  getType: () => {
@@ -194,8 +197,16 @@ export class PrismaObjectFieldBuilder<
194
197
  path: [{ name: 'edges' }, { name: 'node' }],
195
198
  }) as SelectionMap;
196
199
 
200
+ const hasTotalCount = totalCount && !!getSelection(['totalCount']);
201
+ const countSelect = this.builder.options.prisma.filterConnectionTotalCount
202
+ ? nested.where
203
+ ? { where: nested.where }
204
+ : true
205
+ : true;
206
+
197
207
  return {
198
208
  select: {
209
+ ...(hasTotalCount ? { _count: { select: { [name]: countSelect } } } : {}),
199
210
  [name]: nested?.select
200
211
  ? {
201
212
  ...nested,
@@ -266,9 +277,6 @@ export class PrismaObjectFieldBuilder<
266
277
  ? (t: PothosSchemaTypes.ObjectFieldBuilder<SchemaTypes, { totalCount?: number }>) => ({
267
278
  totalCount: t.int({
268
279
  nullable: false,
269
- extensions: {
270
- pothosPrismaParentSelect: { _count: { select: { [name]: true } } },
271
- },
272
280
  resolve: (parent, args, context) => parent.totalCount,
273
281
  }),
274
282
  ...(connectionOptions as { fields?: (t: unknown) => {} }).fields?.(t),
@@ -352,18 +360,35 @@ export class PrismaObjectFieldBuilder<
352
360
 
353
361
  relationCount<Field extends Model['RelationName']>(
354
362
  ...allArgs: NormalizeArgs<
355
- [name: Field, options?: RelationCountOptions<Types, Shape, NeedsResolve>]
363
+ [
364
+ name: Field,
365
+ options?: RelationCountOptions<
366
+ Types,
367
+ Shape,
368
+ NeedsResolve,
369
+ Model['Relations'][Field]['Types']['Where']
370
+ >,
371
+ ]
356
372
  >
357
373
  ): FieldRef<number, 'Object'> {
358
- const [name, options = {} as never] = allArgs;
374
+ const [name, { where, ...options } = {} as never] = allArgs;
359
375
 
360
376
  const { resolve, ...rest } = options;
361
377
 
362
- const countSelect = {
363
- _count: {
364
- select: { [name]: true },
365
- },
366
- };
378
+ const countSelect =
379
+ typeof where === 'function'
380
+ ? (args: {}, context: {}) => ({
381
+ _count: {
382
+ select: {
383
+ [name]: { where: (where as (args: unknown, ctx: unknown) => {})(args, context) },
384
+ },
385
+ },
386
+ })
387
+ : {
388
+ _count: {
389
+ select: { [name]: where ? { where } : true },
390
+ },
391
+ };
367
392
 
368
393
  return this.field({
369
394
  ...(rest as {}),
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GraphQLResolveInfo } from 'graphql';
1
+ import { FieldNode, GraphQLResolveInfo } from 'graphql';
2
2
  import {
3
3
  FieldKind,
4
4
  FieldMap,
@@ -385,12 +385,14 @@ export type RelationCountOptions<
385
385
  Types extends SchemaTypes,
386
386
  Shape,
387
387
  NeedsResolve extends boolean,
388
+ Where,
388
389
  > = Omit<
389
390
  PothosSchemaTypes.ObjectFieldOptions<Types, Shape, 'Int', false, {}, number>,
390
391
  'resolve' | 'type'
391
392
  > &
392
393
  (NeedsResolve extends false
393
394
  ? {
395
+ where?: Where | ((args: {}, context: Types['Context']) => Where);
394
396
  resolve?: (
395
397
  parent: Shape,
396
398
  args: {},
@@ -399,6 +401,7 @@ export type RelationCountOptions<
399
401
  ) => MaybePromise<number>;
400
402
  }
401
403
  : {
404
+ where?: Where | ((args: {}, context: Types['Context']) => Where);
402
405
  resolve: (
403
406
  parent: Shape,
404
407
  args: {},
@@ -620,6 +623,7 @@ export type FieldSelection =
620
623
  selection: SelectionMap | boolean | ((args: object, context: object) => SelectionMap),
621
624
  path?: string[] | IndirectInclude,
622
625
  ) => SelectionMap | boolean,
626
+ resolveSelection: (path: string[]) => FieldNode | null,
623
627
  ) => SelectionMap);
624
628
 
625
629
  export type LoaderMappings = Record<
@@ -222,9 +222,6 @@ function addFieldSelection(
222
222
 
223
223
  let fieldSelectionMap: SelectionMap;
224
224
 
225
- const fieldParentSelect = field.extensions?.pothosPrismaParentSelect as
226
- | Record<string, SelectionMap | boolean>
227
- | undefined;
228
225
  let mappings: LoaderMappings = {};
229
226
 
230
227
  if (typeof fieldSelect === 'function') {
@@ -233,52 +230,82 @@ function addFieldSelection(
233
230
  unknown
234
231
  >;
235
232
 
236
- fieldSelectionMap = fieldSelect(args, context, (rawQuery, indirectInclude) => {
237
- const returnType = getNamedType(field.type);
238
- const query = typeof rawQuery === 'function' ? rawQuery(args, context) : rawQuery;
233
+ fieldSelectionMap = fieldSelect(
234
+ args,
235
+ context,
236
+ (rawQuery, indirectInclude) => {
237
+ const returnType = getNamedType(field.type);
238
+ const query = typeof rawQuery === 'function' ? rawQuery(args, context) : rawQuery;
239
+
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
+ );
239
254
 
240
- const normalizedIndirectInclude = Array.isArray(indirectInclude)
241
- ? normalizeInclude(indirectInclude, getIndirectType(returnType, info))
242
- : indirectInclude;
255
+ if (typeof query === 'object' && Object.keys(query).length > 0) {
256
+ mergeSelection(fieldState, { select: {}, ...query });
257
+ }
243
258
 
244
- const fieldState = createStateForType(
245
- getIndirectType(
246
- normalizedIndirectInclude
247
- ? info.schema.getType(normalizedIndirectInclude.getType())!
248
- : returnType,
249
- info,
250
- ),
251
- info,
252
- state,
253
- );
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(
271
+ resolvedType,
272
+ context,
273
+ info,
274
+ fieldState,
275
+ resolvedField,
276
+ path,
277
+ );
278
+ },
279
+ );
280
+ }
281
+
282
+ addTypeSelectionsForField(returnType, context, info, fieldState, selection, []);
283
+
284
+ // eslint-disable-next-line prefer-destructuring
285
+ mappings = fieldState.mappings;
254
286
 
255
- if (typeof query === 'object' && Object.keys(query).length > 0) {
256
- mergeSelection(fieldState, { select: {}, ...query });
257
- }
287
+ return selectionToQuery(fieldState);
288
+ },
289
+ (path) => {
290
+ const returnType = getNamedType(field.type);
291
+ let node: FieldNode | null = null;
258
292
 
259
- if (normalizedIndirectInclude && normalizedIndirectInclude.path.length > 0) {
260
293
  resolveIndirectInclude(
261
294
  returnType,
262
295
  info,
263
296
  selection,
264
- [
265
- ...((returnType.extensions?.pothosPrismaIndirectInclude as { path: [] })?.path ?? []),
266
- ...normalizedIndirectInclude.path,
267
- ],
297
+ path.map((name) => ({
298
+ name,
299
+ })),
268
300
  [],
269
- (resolvedType, resolvedField, path) => {
270
- addTypeSelectionsForField(resolvedType, context, info, fieldState, resolvedField, path);
301
+ (_, resolvedField) => {
302
+ node = resolvedField;
271
303
  },
272
304
  );
273
- }
274
-
275
- addTypeSelectionsForField(returnType, context, info, fieldState, selection, []);
276
-
277
- // eslint-disable-next-line prefer-destructuring
278
- mappings = fieldState.mappings;
279
305
 
280
- return selectionToQuery(fieldState);
281
- });
306
+ return node;
307
+ },
308
+ );
282
309
  } else {
283
310
  fieldSelectionMap = { select: fieldSelect };
284
311
  }
@@ -291,18 +318,6 @@ function addFieldSelection(
291
318
  mappings,
292
319
  indirectPath,
293
320
  };
294
- } else if (
295
- fieldParentSelect &&
296
- state.parent &&
297
- selectionCompatible(state.parent, { select: fieldParentSelect }, true)
298
- ) {
299
- mergeSelection(state.parent, { select: fieldParentSelect });
300
- state.mappings[selection.alias?.value ?? selection.name.value] = {
301
- field: selection.name.value,
302
- type: type.name,
303
- mappings,
304
- indirectPath,
305
- };
306
321
  }
307
322
  }
308
323
 
@@ -11,7 +11,7 @@ export interface SelectionState {
11
11
  query: object;
12
12
  mode: SelectionMode;
13
13
  fields: Set<string>;
14
- counts: Set<string>;
14
+ counts: Map<string, boolean | Record<string, unknown>>;
15
15
  relations: Map<string, SelectionState>;
16
16
  mappings: LoaderMappings;
17
17
  parent?: SelectionState;
@@ -39,6 +39,19 @@ export function selectionCompatible(
39
39
  return ignoreQuery || deepEqual(state.query, query);
40
40
 
41
41
  function compare(key: string, value: SelectionMap | boolean) {
42
+ if (key === '_count') {
43
+ const selections = value && (value as { select?: Record<string, unknown> }).select;
44
+ const keys = selections && Object.keys(selections);
45
+
46
+ if (!keys || keys.length === 0) {
47
+ return false;
48
+ }
49
+
50
+ return keys.some(
51
+ (k) => state.counts.has(k) && !deepEqual(state.counts.get(k), selections[k]),
52
+ );
53
+ }
54
+
42
55
  return (
43
56
  value &&
44
57
  state.fieldMap.relations.has(key) &&
@@ -89,7 +102,7 @@ export function createState(
89
102
  fieldMap,
90
103
  query: {},
91
104
  fields: new Set(),
92
- counts: new Set(),
105
+ counts: new Map(),
93
106
  relations: new Map(),
94
107
  mappings: {},
95
108
  };
@@ -122,9 +135,9 @@ export function mergeSelection(state: SelectionState, { select, include, ...quer
122
135
  }
123
136
 
124
137
  if (key === '_count') {
125
- const counts = (value as { select?: {} }).select ?? {};
138
+ const counts = (value as { select?: Record<string, boolean> }).select ?? {};
126
139
  Object.keys(counts).forEach((count) => {
127
- state.counts.add(count);
140
+ state.counts.set(count, counts[count]);
128
141
  });
129
142
 
130
143
  return;
@@ -149,7 +162,7 @@ export function mergeSelection(state: SelectionState, { select, include, ...quer
149
162
 
150
163
  export function selectionToQuery(state: SelectionState): SelectionMap {
151
164
  const nestedIncludes: Record<string, SelectionMap | boolean> = {};
152
- const counts: Record<string, boolean> = {};
165
+ const counts: Record<string, unknown> = {};
153
166
 
154
167
  let hasSelection = false;
155
168
 
@@ -161,12 +174,12 @@ export function selectionToQuery(state: SelectionState): SelectionMap {
161
174
 
162
175
  if (state.counts.size > 0) {
163
176
  hasSelection = true;
164
- for (const count of state.counts) {
165
- counts[count] = true;
177
+ for (const [count, selection] of state.counts) {
178
+ counts[count] = selection;
166
179
  }
167
180
 
168
181
  nestedIncludes._count = {
169
- select: counts,
182
+ select: counts as {},
170
183
  };
171
184
  }
172
185