@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.
- package/CHANGELOG.md +22 -0
- package/README.md +187 -94
- package/dts/global-types.d.ts +2 -2
- package/dts/global-types.d.ts.map +1 -1
- package/dts/index.d.ts.map +1 -1
- package/dts/model-loader.d.ts +13 -2
- package/dts/model-loader.d.ts.map +1 -1
- package/dts/node-ref.d.ts.map +1 -1
- package/dts/object-ref.d.ts.map +1 -1
- package/dts/prisma-field-builder.d.ts +5 -5
- package/dts/prisma-field-builder.d.ts.map +1 -1
- package/dts/types.d.ts +16 -8
- package/dts/types.d.ts.map +1 -1
- package/dts/util/cursors.d.ts +5 -1
- package/dts/util/cursors.d.ts.map +1 -1
- package/dts/util/datamodel.d.ts +5 -1
- package/dts/util/datamodel.d.ts.map +1 -1
- package/dts/util/get-client.d.ts +5 -1
- package/dts/util/get-client.d.ts.map +1 -1
- package/dts/util/loader-map.d.ts +2 -2
- package/dts/util/loader-map.d.ts.map +1 -1
- package/dts/util/map-query.d.ts.map +1 -1
- package/esm/field-builder.js.map +1 -1
- package/esm/generator.js.map +1 -1
- package/esm/global-types.js.map +1 -1
- package/esm/index.js +5 -7
- package/esm/index.js.map +1 -1
- package/esm/model-loader.js +88 -3
- package/esm/model-loader.js.map +1 -1
- package/esm/node-ref.js.map +1 -1
- package/esm/prisma-field-builder.js +16 -21
- package/esm/prisma-field-builder.js.map +1 -1
- package/esm/schema-builder.js +24 -16
- package/esm/schema-builder.js.map +1 -1
- package/esm/util/cursors.js +95 -2
- package/esm/util/cursors.js.map +1 -1
- package/esm/util/datamodel.js.map +1 -1
- package/esm/util/get-client.js.map +1 -1
- package/esm/util/loader-map.js +2 -3
- package/esm/util/loader-map.js.map +1 -1
- package/esm/util/map-query.js +47 -9
- package/esm/util/map-query.js.map +1 -1
- package/esm/util/relation-map.js.map +1 -1
- package/esm/util/selections.js.map +1 -1
- package/lib/field-builder.js +3 -6
- package/lib/field-builder.js.map +1 -1
- package/lib/generator.js +8 -16
- package/lib/generator.js.map +1 -1
- package/lib/index.js +6 -21
- package/lib/index.js.map +1 -1
- package/lib/model-loader.js +82 -3
- package/lib/model-loader.js.map +1 -1
- package/lib/node-ref.js +1 -2
- package/lib/node-ref.js.map +1 -1
- package/lib/prisma-field-builder.js +25 -40
- package/lib/prisma-field-builder.js.map +1 -1
- package/lib/schema-builder.js +25 -19
- package/lib/schema-builder.js.map +1 -1
- package/lib/util/cursors.js +100 -6
- package/lib/util/cursors.js.map +1 -1
- package/lib/util/datamodel.js +2 -4
- package/lib/util/datamodel.js.map +1 -1
- package/lib/util/get-client.js +1 -3
- package/lib/util/get-client.js.map +1 -1
- package/lib/util/loader-map.js +3 -5
- package/lib/util/loader-map.js.map +1 -1
- package/lib/util/map-query.js +47 -9
- package/lib/util/map-query.js.map +1 -1
- package/lib/util/relation-map.js +1 -2
- package/lib/util/relation-map.js.map +1 -1
- package/lib/util/selections.js +2 -4
- package/lib/util/selections.js.map +1 -1
- package/package.json +8 -8
- package/src/global-types.ts +3 -1
- package/src/index.ts +14 -4
- package/src/model-loader.ts +144 -3
- package/src/node-ref.ts +1 -1
- package/src/object-ref.ts +1 -1
- package/src/prisma-field-builder.ts +21 -22
- package/src/schema-builder.ts +25 -20
- package/src/types.ts +32 -8
- package/src/util/cursors.ts +117 -1
- package/src/util/get-client.ts +2 -1
- package/src/util/loader-map.ts +3 -9
- package/src/util/map-query.ts +66 -3
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/util/cursors.ts
CHANGED
|
@@ -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[];
|
package/src/util/get-client.ts
CHANGED
|
@@ -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
|
}
|
package/src/util/loader-map.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
31
|
+
const key = cacheKey(mapping.type, info.path, subPath);
|
|
38
32
|
|
|
39
33
|
map.set(key, mapping.mappings);
|
|
40
34
|
});
|
package/src/util/map-query.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
+
}
|