zenstack-graphql-builder 0.1.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/README.md +246 -0
- package/package.json +34 -0
- package/src/index.js +1536 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,1536 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GraphQLSchema,
|
|
3
|
+
GraphQLObjectType,
|
|
4
|
+
GraphQLInputObjectType,
|
|
5
|
+
GraphQLEnumType,
|
|
6
|
+
GraphQLScalarType,
|
|
7
|
+
GraphQLDirective,
|
|
8
|
+
DirectiveLocation,
|
|
9
|
+
specifiedDirectives,
|
|
10
|
+
GraphQLList,
|
|
11
|
+
GraphQLNonNull,
|
|
12
|
+
GraphQLBoolean,
|
|
13
|
+
GraphQLInt,
|
|
14
|
+
GraphQLFloat,
|
|
15
|
+
GraphQLString,
|
|
16
|
+
GraphQLID,
|
|
17
|
+
Kind,
|
|
18
|
+
} from 'graphql';
|
|
19
|
+
import { Decimal } from 'decimal.js';
|
|
20
|
+
import { Buffer } from 'buffer';
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 定义 @upperCase 指令
|
|
25
|
+
*/
|
|
26
|
+
export const UpperCaseDirective = new GraphQLDirective({
|
|
27
|
+
name: 'upperCase',
|
|
28
|
+
description: '将字符串字段转换为大写',
|
|
29
|
+
locations: [DirectiveLocation.FIELD],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const AllCrudOperations = ["findMany", "findUnique", "findFirst", "create", "createMany", "createManyAndReturn", "update", "updateMany", "updateManyAndReturn", "upsert", "delete", "deleteMany", "count", "aggregate", "groupBy", "exists", "findUniqueOrThrow", "findFirstOrThrow"];
|
|
33
|
+
|
|
34
|
+
// ==================== 自定义标量 ====================
|
|
35
|
+
const DateTimeScalar = new GraphQLScalarType({
|
|
36
|
+
name: 'DateTime',
|
|
37
|
+
serialize: (v) => (v instanceof Date ? v.toISOString() : v),
|
|
38
|
+
parseValue: (v) => (v == null ? null : new Date(v)),
|
|
39
|
+
parseLiteral: (ast) => (ast.kind === Kind.STRING || ast.kind === Kind.INT ? new Date(ast.value) : null),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const JsonScalar = new GraphQLScalarType({
|
|
43
|
+
name: 'Json',
|
|
44
|
+
serialize: (v) => v,
|
|
45
|
+
parseValue: (v) => v,
|
|
46
|
+
parseLiteral: (ast, variables) => {
|
|
47
|
+
// if (ast.kind === Kind.STRING) return JSON.parse(ast.value);
|
|
48
|
+
// if (ast.kind === Kind.OBJECT) return valueFromASTUntyped(ast, variables);
|
|
49
|
+
if (ast.kind === Kind.STRING || ast.kind === Kind.BOOLEAN || ast.kind === Kind.INT || ast.kind === Kind.FLOAT) return ast.value;
|
|
50
|
+
if (ast.kind === Kind.OBJECT) {
|
|
51
|
+
return JSON.parse(ast.loc?.source?.body.slice(ast.loc.start, ast.loc.end) || '{}');
|
|
52
|
+
}
|
|
53
|
+
if (ast.kind === Kind.LIST) return ast.values.map((v) => JsonScalar.parseLiteral(v));
|
|
54
|
+
return null;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const BigIntScalar = new GraphQLScalarType({
|
|
59
|
+
name: 'BigInt',
|
|
60
|
+
serialize: (v) => (typeof v === 'bigint' ? v.toString() : v?.toString?.()),
|
|
61
|
+
parseValue: (v) => (v == null ? null : BigInt(v)),
|
|
62
|
+
parseLiteral: (ast) => {
|
|
63
|
+
if (ast.kind === Kind.STRING || ast.kind === Kind.INT) {
|
|
64
|
+
try { return BigInt(ast.value); } catch { return null; }
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 尝试将 BigInt 转换为安全整数 Number,否则保留 BigInt
|
|
72
|
+
*/
|
|
73
|
+
function toSafeNumericValue(value) {
|
|
74
|
+
if (value == null) return null;
|
|
75
|
+
|
|
76
|
+
const bigIntValue = typeof value === 'bigint' ? value : BigInt(value);
|
|
77
|
+
|
|
78
|
+
// 检查是否在 JavaScript 安全整数范围内
|
|
79
|
+
if (bigIntValue <= BigInt(Number.MAX_SAFE_INTEGER) &&
|
|
80
|
+
bigIntValue >= BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
81
|
+
return Number(bigIntValue);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return bigIntValue; // 必须返回,否则不安全时会变成 undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const JSONIntScalar = new GraphQLScalarType({
|
|
88
|
+
name: 'JSONInt',
|
|
89
|
+
description: 'The `JSONInt` scalar type represents a signed 53-bit numeric non-fractional value. It corresponds to JavaScript `Number.MAX_SAFE_INTEGER`, representing values between -(2^53) and 2^53-1.',
|
|
90
|
+
serialize: (v) => {
|
|
91
|
+
const value = toSafeNumericValue(v);
|
|
92
|
+
// 如果是 BigInt 类型(说明超出了安全范围),转为字符串防止前端精度丢失
|
|
93
|
+
if (typeof value === 'bigint') {
|
|
94
|
+
return value.toString();
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
},
|
|
98
|
+
parseValue: toSafeNumericValue,
|
|
99
|
+
parseLiteral: (ast) => {
|
|
100
|
+
if (ast.kind === Kind.STRING || ast.kind === Kind.INT) {
|
|
101
|
+
return toSafeNumericValue(ast.value);
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
const BytesScalar = new GraphQLScalarType({
|
|
109
|
+
name: 'Bytes',
|
|
110
|
+
serialize: (v) => {
|
|
111
|
+
if (Buffer.isBuffer(v)) return v.toString('base64');
|
|
112
|
+
if (v instanceof Uint8Array) return Buffer.from(v).toString('base64');
|
|
113
|
+
if (typeof v === 'string') return Buffer.from(v, 'base64').toString('base64');
|
|
114
|
+
return null;
|
|
115
|
+
},
|
|
116
|
+
parseValue: (v) => (typeof v === 'string' ? Buffer.from(v, 'base64') : null),
|
|
117
|
+
parseLiteral: (ast) => (ast.kind === Kind.STRING ? Buffer.from(ast.value, 'base64') : null),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const DecimalScalar = new GraphQLScalarType({
|
|
121
|
+
name: 'Decimal',
|
|
122
|
+
serialize: (v) => (v instanceof Decimal ? v.toString() : v?.toString?.()),
|
|
123
|
+
parseValue: (v) => (v == null ? null : new Decimal(v)),
|
|
124
|
+
parseLiteral: (ast) => {
|
|
125
|
+
if (ast.kind === Kind.STRING || ast.kind === Kind.INT || ast.kind === Kind.FLOAT) {
|
|
126
|
+
try { return new Decimal(ast.value); } catch { return null; }
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
// ==================== 基础枚举 ====================
|
|
134
|
+
const SortOrderEnum = new GraphQLEnumType({
|
|
135
|
+
name: 'SortOrder',
|
|
136
|
+
values: { asc: { value: 'asc' }, desc: { value: 'desc' } },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const NullsOrderEnum = new GraphQLEnumType({
|
|
140
|
+
name: 'NullsOrder',
|
|
141
|
+
values: { first: { value: 'first' }, last: { value: 'last' } },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const QueryModeEnum = new GraphQLEnumType({
|
|
145
|
+
name: 'QueryMode',
|
|
146
|
+
values: { default: { value: 'default' }, insensitive: { value: 'insensitive' } },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ==================== 主生成器类 ====================
|
|
150
|
+
export class ZenStackGraphQLBuilder {
|
|
151
|
+
constructor({ schema, options, directives, directiveDefinitions, operations, scalars }) {
|
|
152
|
+
this.zenSchema = schema; // ZenStack SchemaDef
|
|
153
|
+
this.modelNames = Object.keys(schema.models);
|
|
154
|
+
this.outputSchema = null;
|
|
155
|
+
this.outpuRootValue = null
|
|
156
|
+
this.options = {
|
|
157
|
+
maxDepth: 9,
|
|
158
|
+
maxTake: 100,
|
|
159
|
+
throwOnError: false,
|
|
160
|
+
useJSONIntScalar: false,
|
|
161
|
+
...options
|
|
162
|
+
};
|
|
163
|
+
this.directives = directives || []
|
|
164
|
+
this.directiveDefinitions = directiveDefinitions || []
|
|
165
|
+
this.operations = operations || AllCrudOperations
|
|
166
|
+
|
|
167
|
+
// 初始化标量映射
|
|
168
|
+
this.scalarRegistry = this._initializeScalars(scalars);
|
|
169
|
+
|
|
170
|
+
// 统一类型缓存,键为类型完整名称
|
|
171
|
+
this.typeMap = new Map();
|
|
172
|
+
|
|
173
|
+
// 构建枚举并放入缓存
|
|
174
|
+
this._buildEnums();
|
|
175
|
+
this._buildSchema();
|
|
176
|
+
this._buildRootValue();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getSchema() {
|
|
180
|
+
return this.outputSchema;
|
|
181
|
+
}
|
|
182
|
+
getRootResolver() {
|
|
183
|
+
return this.outpuRootValue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_initializeScalars(scalars = {}) {
|
|
187
|
+
const newScalars = {
|
|
188
|
+
String: GraphQLString,
|
|
189
|
+
Int: this.options.useJSONIntScalar ? JSONIntScalar : GraphQLInt,
|
|
190
|
+
Float: GraphQLFloat,
|
|
191
|
+
Boolean: GraphQLBoolean,
|
|
192
|
+
ID: GraphQLID,
|
|
193
|
+
DateTime: DateTimeScalar,
|
|
194
|
+
Json: JsonScalar,
|
|
195
|
+
BigInt: BigIntScalar,
|
|
196
|
+
Bytes: BytesScalar,
|
|
197
|
+
Decimal: DecimalScalar,
|
|
198
|
+
...scalars
|
|
199
|
+
};
|
|
200
|
+
return newScalars;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 生成最终的 GraphQLSchema
|
|
204
|
+
_buildSchema() {
|
|
205
|
+
// ---------- 预先生成所有类型并存入缓存 ----------
|
|
206
|
+
for (const model of this.modelNames) {
|
|
207
|
+
// 这些方法内部会递归生成所有依赖的类型,并存入 typeMap
|
|
208
|
+
this._getOutputType(model);
|
|
209
|
+
this._getWhereInput(model);
|
|
210
|
+
this._getOrderByInput(model);
|
|
211
|
+
this._getCreateInput(model);
|
|
212
|
+
this._getUpdateInput(model);
|
|
213
|
+
this._getWhereUniqueInput(model);
|
|
214
|
+
this._getCreateManyInput(model);
|
|
215
|
+
this._getCountAggOutput(model);
|
|
216
|
+
this._getDistinctEnum(model);
|
|
217
|
+
this._getOmitInput(model);
|
|
218
|
+
this._getCountAggInput(model);
|
|
219
|
+
this._getAggInput(model);
|
|
220
|
+
this._getConnectOrCreateInput(model);
|
|
221
|
+
this._getUpdateNestedInput(model);
|
|
222
|
+
this._getUpdateManyNestedInput(model);
|
|
223
|
+
this._getUpsertNestedInput(model);
|
|
224
|
+
}
|
|
225
|
+
this._getAffectedRowsOutput(); // 确保 AffectedRowsOutput 也被缓存
|
|
226
|
+
|
|
227
|
+
const queryFields = {};
|
|
228
|
+
const mutationFields = {};
|
|
229
|
+
|
|
230
|
+
for (const model of this.modelNames) {
|
|
231
|
+
const lower = model[0].toLowerCase() + model.slice(1);
|
|
232
|
+
|
|
233
|
+
// ---------- 查询字段 ----------
|
|
234
|
+
queryFields[`${lower}_findUnique`] = {
|
|
235
|
+
type: this.typeMap.get(model),
|
|
236
|
+
args: {
|
|
237
|
+
where: { type: new GraphQLNonNull(this.typeMap.get(`${model}WhereUniqueInput`)) },
|
|
238
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
queryFields[`${lower}_findUniqueOrThrow`] = {
|
|
242
|
+
type: new GraphQLNonNull(this.typeMap.get(model)),
|
|
243
|
+
args: {
|
|
244
|
+
where: { type: new GraphQLNonNull(this.typeMap.get(`${model}WhereUniqueInput`)) },
|
|
245
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
queryFields[`${lower}_findFirst`] = {
|
|
249
|
+
type: this.typeMap.get(model),
|
|
250
|
+
args: {
|
|
251
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
252
|
+
orderBy: { type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}OrderByInput`))) },
|
|
253
|
+
cursor: { type: this.typeMap.get(`${model}WhereUniqueInput`) },
|
|
254
|
+
take: { type: GraphQLInt },
|
|
255
|
+
skip: { type: GraphQLInt },
|
|
256
|
+
distinct: { type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}DistinctFieldEnum`))) },
|
|
257
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
queryFields[`${lower}_findFirstOrThrow`] = {
|
|
261
|
+
type: new GraphQLNonNull(this.typeMap.get(model)),
|
|
262
|
+
args: {
|
|
263
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
264
|
+
orderBy: { type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}OrderByInput`))) },
|
|
265
|
+
cursor: { type: this.typeMap.get(`${model}WhereUniqueInput`) },
|
|
266
|
+
take: { type: GraphQLInt },
|
|
267
|
+
skip: { type: GraphQLInt },
|
|
268
|
+
distinct: { type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}DistinctFieldEnum`))) },
|
|
269
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
queryFields[`${lower}_findMany`] = {
|
|
273
|
+
type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(model))),
|
|
274
|
+
args: {
|
|
275
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
276
|
+
orderBy: { type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}OrderByInput`))) },
|
|
277
|
+
cursor: { type: this.typeMap.get(`${model}WhereUniqueInput`) },
|
|
278
|
+
take: { type: GraphQLInt },
|
|
279
|
+
skip: { type: GraphQLInt },
|
|
280
|
+
distinct: { type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}DistinctFieldEnum`))) },
|
|
281
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
queryFields[`${lower}_count`] = {
|
|
285
|
+
type: this.typeMap.get(`${model}CountAggregateOutput`),
|
|
286
|
+
args: {
|
|
287
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
288
|
+
orderBy: { type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}OrderByInput`))) },
|
|
289
|
+
cursor: { type: this.typeMap.get(`${model}WhereUniqueInput`) },
|
|
290
|
+
take: { type: GraphQLInt },
|
|
291
|
+
skip: { type: GraphQLInt },
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
queryFields[`${lower}_aggregate`] = {
|
|
295
|
+
type: new GraphQLNonNull(JsonScalar),
|
|
296
|
+
args: {
|
|
297
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
298
|
+
orderBy: { type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}OrderByInput`))) },
|
|
299
|
+
cursor: { type: this.typeMap.get(`${model}WhereUniqueInput`) },
|
|
300
|
+
take: { type: GraphQLInt },
|
|
301
|
+
skip: { type: GraphQLInt },
|
|
302
|
+
_count: { type: this.typeMap.get(`${model}CountAggregateInput`) },
|
|
303
|
+
_avg: { type: this.typeMap.get(`${model}AggregateInput`) },
|
|
304
|
+
_sum: { type: this.typeMap.get(`${model}AggregateInput`) },
|
|
305
|
+
_min: { type: this.typeMap.get(`${model}AggregateInput`) },
|
|
306
|
+
_max: { type: this.typeMap.get(`${model}AggregateInput`) },
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
queryFields[`${lower}_groupBy`] = {
|
|
310
|
+
type: new GraphQLList(new GraphQLNonNull(JsonScalar)),
|
|
311
|
+
args: {
|
|
312
|
+
by: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}DistinctFieldEnum`)))) },
|
|
313
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
314
|
+
having: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
315
|
+
take: { type: GraphQLInt },
|
|
316
|
+
skip: { type: GraphQLInt },
|
|
317
|
+
_count: { type: this.typeMap.get(`${model}CountAggregateInput`) },
|
|
318
|
+
_avg: { type: this.typeMap.get(`${model}AggregateInput`) },
|
|
319
|
+
_sum: { type: this.typeMap.get(`${model}AggregateInput`) },
|
|
320
|
+
_min: { type: this.typeMap.get(`${model}AggregateInput`) },
|
|
321
|
+
_max: { type: this.typeMap.get(`${model}AggregateInput`) },
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
queryFields[`${lower}_exists`] = {
|
|
325
|
+
type: new GraphQLNonNull(GraphQLBoolean),
|
|
326
|
+
args: { where: { type: this.typeMap.get(`${model}WhereInput`) } },
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// ---------- 变更字段 ----------
|
|
330
|
+
mutationFields[`${lower}_create`] = {
|
|
331
|
+
type: new GraphQLNonNull(this.typeMap.get(model)),
|
|
332
|
+
args: {
|
|
333
|
+
data: { type: new GraphQLNonNull(this.typeMap.get(`${model}CreateInput`)) },
|
|
334
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
mutationFields[`${lower}_createMany`] = {
|
|
338
|
+
type: this.typeMap.get('affectedRowsOutput'),
|
|
339
|
+
args: {
|
|
340
|
+
data: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}CreateManyInput`)))) },
|
|
341
|
+
skipDuplicates: { type: GraphQLBoolean },
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
mutationFields[`${lower}_createManyAndReturn`] = {
|
|
345
|
+
type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(model))),
|
|
346
|
+
args: {
|
|
347
|
+
data: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(this.typeMap.get(`${model}CreateManyInput`)))) },
|
|
348
|
+
skipDuplicates: { type: GraphQLBoolean },
|
|
349
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
mutationFields[`${lower}_update`] = {
|
|
353
|
+
type: this.typeMap.get(model),
|
|
354
|
+
args: {
|
|
355
|
+
where: { type: new GraphQLNonNull(this.typeMap.get(`${model}WhereUniqueInput`)) },
|
|
356
|
+
data: { type: new GraphQLNonNull(this.typeMap.get(`${model}UpdateInput`)) },
|
|
357
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
mutationFields[`${lower}_updateMany`] = {
|
|
361
|
+
type: this.typeMap.get('affectedRowsOutput'),
|
|
362
|
+
args: {
|
|
363
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
364
|
+
data: { type: new GraphQLNonNull(this.typeMap.get(`${model}UpdateInput`)) },
|
|
365
|
+
limit: { type: GraphQLInt },
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
mutationFields[`${lower}_updateManyAndReturn`] = {
|
|
369
|
+
type: new GraphQLList(new GraphQLNonNull(this.typeMap.get(model))),
|
|
370
|
+
args: {
|
|
371
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
372
|
+
data: { type: new GraphQLNonNull(this.typeMap.get(`${model}UpdateInput`)) },
|
|
373
|
+
limit: { type: GraphQLInt },
|
|
374
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
mutationFields[`${lower}_upsert`] = {
|
|
378
|
+
type: new GraphQLNonNull(this.typeMap.get(model)),
|
|
379
|
+
args: {
|
|
380
|
+
where: { type: new GraphQLNonNull(this.typeMap.get(`${model}WhereUniqueInput`)) },
|
|
381
|
+
create: { type: new GraphQLNonNull(this.typeMap.get(`${model}CreateInput`)) },
|
|
382
|
+
update: { type: new GraphQLNonNull(this.typeMap.get(`${model}UpdateInput`)) },
|
|
383
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
mutationFields[`${lower}_delete`] = {
|
|
387
|
+
type: this.typeMap.get(model),
|
|
388
|
+
args: {
|
|
389
|
+
where: { type: new GraphQLNonNull(this.typeMap.get(`${model}WhereUniqueInput`)) },
|
|
390
|
+
omit: { type: this.typeMap.get(`${model}OmitInput`) },
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
mutationFields[`${lower}_deleteMany`] = {
|
|
394
|
+
type: this.typeMap.get('affectedRowsOutput'),
|
|
395
|
+
args: {
|
|
396
|
+
where: { type: this.typeMap.get(`${model}WhereInput`) },
|
|
397
|
+
limit: { type: GraphQLInt },
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const queryType = new GraphQLObjectType({ name: 'Query', fields: queryFields });
|
|
403
|
+
const mutationType = new GraphQLObjectType({ name: 'Mutation', fields: mutationFields });
|
|
404
|
+
|
|
405
|
+
// 收集所有已缓存类型(排除基础标量和枚举,它们已手动加入)
|
|
406
|
+
const allTypes = Array.from(this.typeMap.values());
|
|
407
|
+
|
|
408
|
+
this.outputSchema = new GraphQLSchema({
|
|
409
|
+
query: queryType,
|
|
410
|
+
mutation: mutationType,
|
|
411
|
+
directives: [...specifiedDirectives, ...this.directiveDefinitions],
|
|
412
|
+
types: [
|
|
413
|
+
...allTypes,
|
|
414
|
+
SortOrderEnum,
|
|
415
|
+
NullsOrderEnum,
|
|
416
|
+
QueryModeEnum,
|
|
417
|
+
DateTimeScalar,
|
|
418
|
+
JsonScalar,
|
|
419
|
+
BigIntScalar,
|
|
420
|
+
BytesScalar,
|
|
421
|
+
DecimalScalar,
|
|
422
|
+
],
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// return new GraphQLSchema({
|
|
426
|
+
// query: queryType,
|
|
427
|
+
// mutation: mutationType,
|
|
428
|
+
// directives: [...specifiedDirectives, ...customDirectives],
|
|
429
|
+
// types: [
|
|
430
|
+
// ...allTypes,
|
|
431
|
+
// SortOrderEnum,
|
|
432
|
+
// NullsOrderEnum,
|
|
433
|
+
// QueryModeEnum,
|
|
434
|
+
// DateTimeScalar,
|
|
435
|
+
// JsonScalar,
|
|
436
|
+
// BigIntScalar,
|
|
437
|
+
// BytesScalar,
|
|
438
|
+
// DecimalScalar,
|
|
439
|
+
// ],
|
|
440
|
+
// });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ---------- 构建枚举 ----------
|
|
444
|
+
_buildEnums() {
|
|
445
|
+
if (!this.zenSchema.enums) return;
|
|
446
|
+
for (const [name, def] of Object.entries(this.zenSchema.enums)) {
|
|
447
|
+
const values = Object.keys(def).reduce((acc, key) => ({ ...acc, [key]: { value: key } }), {});
|
|
448
|
+
const enumType = new GraphQLEnumType({ name, values });
|
|
449
|
+
this.typeMap.set(name, enumType);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ---------- 辅助:获取模型定义并检查 ----------
|
|
454
|
+
_getModelDef(model) {
|
|
455
|
+
const def = this.zenSchema.models[model];
|
|
456
|
+
if (!def) throw new Error(`Model "${model}" not found`);
|
|
457
|
+
if (!def.fields || typeof def.fields !== 'object') {
|
|
458
|
+
throw new Error(`Model "${model}" has no valid fields`);
|
|
459
|
+
}
|
|
460
|
+
return def;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ---------- 辅助:字段是否为标量 ----------
|
|
464
|
+
_isScalar(field) {
|
|
465
|
+
return !field.relation && !field.foreignKeyFor;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ---------- 辅助:字段是否为关系 ----------
|
|
469
|
+
_isRelation(field) {
|
|
470
|
+
return !!field.relation;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ---------- 辅助:获取目标模型名 ----------
|
|
474
|
+
_getTargetModel(field) {
|
|
475
|
+
return field.type; // 关系字段的 type 就是目标模型名
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ---------- 辅助:是否为自增 ----------
|
|
479
|
+
_isAutoIncrement(field) {
|
|
480
|
+
return field.default?.name === 'autoincrement';
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ---------- 辅助:将字段类型转换为 GraphQL 类型 ----------
|
|
484
|
+
_fieldToGraphQLType(field) {
|
|
485
|
+
let base;
|
|
486
|
+
if (this.typeMap.has(field.type)) {
|
|
487
|
+
base = this.typeMap.get(field.type);
|
|
488
|
+
} else if (this.scalarRegistry[field.type]) {
|
|
489
|
+
base = this.scalarRegistry[field.type];
|
|
490
|
+
} else {
|
|
491
|
+
base = GraphQLString; // 回退
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
let type = base;
|
|
495
|
+
if (field.array) {
|
|
496
|
+
type = new GraphQLList(new GraphQLNonNull(base));
|
|
497
|
+
}
|
|
498
|
+
if (!field.optional) {
|
|
499
|
+
type = new GraphQLNonNull(type);
|
|
500
|
+
}
|
|
501
|
+
return type;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ---------- 标量过滤器工厂(使用 typeMap)----------
|
|
505
|
+
_getFilter(typeName) {
|
|
506
|
+
const name = `${typeName}Filter`;
|
|
507
|
+
// 如果已经存在,直接返回
|
|
508
|
+
const cached = this.typeMap.get(name);
|
|
509
|
+
if (cached) return cached;
|
|
510
|
+
|
|
511
|
+
// 如果是枚举,创建枚举过滤器
|
|
512
|
+
if (this.typeMap.has(typeName) && this.typeMap.get(typeName) instanceof GraphQLEnumType) {
|
|
513
|
+
const enumType = this.typeMap.get(typeName);
|
|
514
|
+
return this._createEnumFilter(enumType);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
let filter;
|
|
518
|
+
switch (typeName) {
|
|
519
|
+
case 'String': filter = this._createStringFilter(); break;
|
|
520
|
+
case 'Int':
|
|
521
|
+
case 'Float': filter = this._createNumberFilter(typeName); break;
|
|
522
|
+
case 'Boolean': filter = this._createBooleanFilter(); break;
|
|
523
|
+
case 'DateTime': filter = this._createDateTimeFilter(); break;
|
|
524
|
+
case 'BigInt': filter = this._createBigIntFilter(); break;
|
|
525
|
+
case 'Decimal': filter = this._createDecimalFilter(); break;
|
|
526
|
+
case 'Bytes': filter = this._createBytesFilter(); break;
|
|
527
|
+
case 'Json': filter = this._createJsonFilter(); break;
|
|
528
|
+
default: return null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
this.typeMap.set(name, filter);
|
|
532
|
+
return filter;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
_createStringFilter() {
|
|
536
|
+
const name = 'StringFilter';
|
|
537
|
+
const existing = this.typeMap.get(name);
|
|
538
|
+
if (existing) return existing;
|
|
539
|
+
const filter = new GraphQLInputObjectType({
|
|
540
|
+
name,
|
|
541
|
+
fields: () => ({
|
|
542
|
+
equals: { type: GraphQLString },
|
|
543
|
+
in: { type: new GraphQLList(new GraphQLNonNull(GraphQLString)) },
|
|
544
|
+
notIn: { type: new GraphQLList(new GraphQLNonNull(GraphQLString)) },
|
|
545
|
+
lt: { type: GraphQLString },
|
|
546
|
+
lte: { type: GraphQLString },
|
|
547
|
+
gt: { type: GraphQLString },
|
|
548
|
+
gte: { type: GraphQLString },
|
|
549
|
+
contains: { type: GraphQLString },
|
|
550
|
+
startsWith: { type: GraphQLString },
|
|
551
|
+
endsWith: { type: GraphQLString },
|
|
552
|
+
mode: { type: QueryModeEnum },
|
|
553
|
+
not: { type: this._getFilter('String') },
|
|
554
|
+
_count: { type: this._getFilter('Int') },
|
|
555
|
+
_min: { type: this._getFilter('String') },
|
|
556
|
+
_max: { type: this._getFilter('String') },
|
|
557
|
+
}),
|
|
558
|
+
});
|
|
559
|
+
this.typeMap.set(name, filter);
|
|
560
|
+
return filter;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
_createNumberFilter(typeName) {
|
|
564
|
+
const name = typeName === 'Int' ? 'IntFilter' : typeName === 'Float' ? 'FloatFilter' : 'NumberFilter';
|
|
565
|
+
const existing = this.typeMap.get(name);
|
|
566
|
+
if (existing) return existing;
|
|
567
|
+
const baseType = this.scalarRegistry[typeName] || GraphQLInt;
|
|
568
|
+
const filter = new GraphQLInputObjectType({
|
|
569
|
+
name,
|
|
570
|
+
fields: () => ({
|
|
571
|
+
equals: { type: baseType },
|
|
572
|
+
in: { type: new GraphQLList(new GraphQLNonNull(baseType)) },
|
|
573
|
+
notIn: { type: new GraphQLList(new GraphQLNonNull(baseType)) },
|
|
574
|
+
lt: { type: baseType },
|
|
575
|
+
lte: { type: baseType },
|
|
576
|
+
gt: { type: baseType },
|
|
577
|
+
gte: { type: baseType },
|
|
578
|
+
between: { type: new GraphQLList(new GraphQLNonNull(baseType)) },
|
|
579
|
+
not: { type: this._getFilter(typeName) },
|
|
580
|
+
_count: { type: this._getFilter('Int') },
|
|
581
|
+
_avg: { type: this._getFilter(typeName) },
|
|
582
|
+
_sum: { type: this._getFilter(typeName) },
|
|
583
|
+
_min: { type: this._getFilter(typeName) },
|
|
584
|
+
_max: { type: this._getFilter(typeName) },
|
|
585
|
+
}),
|
|
586
|
+
});
|
|
587
|
+
this.typeMap.set(name, filter);
|
|
588
|
+
return filter;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
_createBooleanFilter() {
|
|
592
|
+
const name = 'BooleanFilter';
|
|
593
|
+
const existing = this.typeMap.get(name);
|
|
594
|
+
if (existing) return existing;
|
|
595
|
+
const filter = new GraphQLInputObjectType({
|
|
596
|
+
name,
|
|
597
|
+
fields: () => ({
|
|
598
|
+
equals: { type: GraphQLBoolean },
|
|
599
|
+
not: { type: this._getFilter('Boolean') },
|
|
600
|
+
_count: { type: this._getFilter('Int') },
|
|
601
|
+
_min: { type: this._getFilter('Boolean') },
|
|
602
|
+
_max: { type: this._getFilter('Boolean') },
|
|
603
|
+
}),
|
|
604
|
+
});
|
|
605
|
+
this.typeMap.set(name, filter);
|
|
606
|
+
return filter;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
_createDateTimeFilter() {
|
|
610
|
+
const name = 'DateTimeFilter';
|
|
611
|
+
const existing = this.typeMap.get(name);
|
|
612
|
+
if (existing) return existing;
|
|
613
|
+
const filter = new GraphQLInputObjectType({
|
|
614
|
+
name,
|
|
615
|
+
fields: () => ({
|
|
616
|
+
equals: { type: DateTimeScalar },
|
|
617
|
+
in: { type: new GraphQLList(new GraphQLNonNull(DateTimeScalar)) },
|
|
618
|
+
notIn: { type: new GraphQLList(new GraphQLNonNull(DateTimeScalar)) },
|
|
619
|
+
lt: { type: DateTimeScalar },
|
|
620
|
+
lte: { type: DateTimeScalar },
|
|
621
|
+
gt: { type: DateTimeScalar },
|
|
622
|
+
gte: { type: DateTimeScalar },
|
|
623
|
+
between: { type: new GraphQLList(new GraphQLNonNull(DateTimeScalar)) },
|
|
624
|
+
not: { type: this._getFilter('DateTime') },
|
|
625
|
+
_count: { type: this._getFilter('Int') },
|
|
626
|
+
_min: { type: this._getFilter('DateTime') },
|
|
627
|
+
_max: { type: this._getFilter('DateTime') },
|
|
628
|
+
}),
|
|
629
|
+
});
|
|
630
|
+
this.typeMap.set(name, filter);
|
|
631
|
+
return filter;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
_createBigIntFilter() {
|
|
635
|
+
const name = 'BigIntFilter';
|
|
636
|
+
const existing = this.typeMap.get(name);
|
|
637
|
+
if (existing) return existing;
|
|
638
|
+
const filter = new GraphQLInputObjectType({
|
|
639
|
+
name,
|
|
640
|
+
fields: () => ({
|
|
641
|
+
equals: { type: BigIntScalar },
|
|
642
|
+
in: { type: new GraphQLList(new GraphQLNonNull(BigIntScalar)) },
|
|
643
|
+
notIn: { type: new GraphQLList(new GraphQLNonNull(BigIntScalar)) },
|
|
644
|
+
lt: { type: BigIntScalar },
|
|
645
|
+
lte: { type: BigIntScalar },
|
|
646
|
+
gt: { type: BigIntScalar },
|
|
647
|
+
gte: { type: BigIntScalar },
|
|
648
|
+
between: { type: new GraphQLList(new GraphQLNonNull(BigIntScalar)) },
|
|
649
|
+
not: { type: this._getFilter('BigInt') },
|
|
650
|
+
_count: { type: this._getFilter('Int') },
|
|
651
|
+
_avg: { type: this._getFilter('BigInt') },
|
|
652
|
+
_sum: { type: this._getFilter('BigInt') },
|
|
653
|
+
_min: { type: this._getFilter('BigInt') },
|
|
654
|
+
_max: { type: this._getFilter('BigInt') },
|
|
655
|
+
}),
|
|
656
|
+
});
|
|
657
|
+
this.typeMap.set(name, filter);
|
|
658
|
+
return filter;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
_createDecimalFilter() {
|
|
662
|
+
const name = 'DecimalFilter';
|
|
663
|
+
const existing = this.typeMap.get(name);
|
|
664
|
+
if (existing) return existing;
|
|
665
|
+
const filter = new GraphQLInputObjectType({
|
|
666
|
+
name,
|
|
667
|
+
fields: () => ({
|
|
668
|
+
equals: { type: DecimalScalar },
|
|
669
|
+
in: { type: new GraphQLList(new GraphQLNonNull(DecimalScalar)) },
|
|
670
|
+
notIn: { type: new GraphQLList(new GraphQLNonNull(DecimalScalar)) },
|
|
671
|
+
lt: { type: DecimalScalar },
|
|
672
|
+
lte: { type: DecimalScalar },
|
|
673
|
+
gt: { type: DecimalScalar },
|
|
674
|
+
gte: { type: DecimalScalar },
|
|
675
|
+
between: { type: new GraphQLList(new GraphQLNonNull(DecimalScalar)) },
|
|
676
|
+
not: { type: this._getFilter('Decimal') },
|
|
677
|
+
_count: { type: this._getFilter('Int') },
|
|
678
|
+
_avg: { type: this._getFilter('Decimal') },
|
|
679
|
+
_sum: { type: this._getFilter('Decimal') },
|
|
680
|
+
_min: { type: this._getFilter('Decimal') },
|
|
681
|
+
_max: { type: this._getFilter('Decimal') },
|
|
682
|
+
}),
|
|
683
|
+
});
|
|
684
|
+
this.typeMap.set(name, filter);
|
|
685
|
+
return filter;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
_createBytesFilter() {
|
|
689
|
+
const name = 'BytesFilter';
|
|
690
|
+
const existing = this.typeMap.get(name);
|
|
691
|
+
if (existing) return existing;
|
|
692
|
+
const filter = new GraphQLInputObjectType({
|
|
693
|
+
name,
|
|
694
|
+
fields: () => ({
|
|
695
|
+
equals: { type: BytesScalar },
|
|
696
|
+
in: { type: new GraphQLList(new GraphQLNonNull(BytesScalar)) },
|
|
697
|
+
notIn: { type: new GraphQLList(new GraphQLNonNull(BytesScalar)) },
|
|
698
|
+
not: { type: this._getFilter('Bytes') },
|
|
699
|
+
_count: { type: this._getFilter('Int') },
|
|
700
|
+
_min: { type: this._getFilter('Bytes') },
|
|
701
|
+
_max: { type: this._getFilter('Bytes') },
|
|
702
|
+
}),
|
|
703
|
+
});
|
|
704
|
+
this.typeMap.set(name, filter);
|
|
705
|
+
return filter;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
_createJsonFilter() {
|
|
709
|
+
const name = 'JsonFilter';
|
|
710
|
+
const existing = this.typeMap.get(name);
|
|
711
|
+
if (existing) return existing;
|
|
712
|
+
const filter = new GraphQLInputObjectType({
|
|
713
|
+
name,
|
|
714
|
+
fields: () => ({
|
|
715
|
+
equals: { type: JsonScalar },
|
|
716
|
+
not: { type: JsonScalar },
|
|
717
|
+
path: { type: GraphQLString },
|
|
718
|
+
string_contains: { type: GraphQLString },
|
|
719
|
+
string_starts_with: { type: GraphQLString },
|
|
720
|
+
string_ends_with: { type: GraphQLString },
|
|
721
|
+
mode: { type: QueryModeEnum },
|
|
722
|
+
array_contains: { type: JsonScalar },
|
|
723
|
+
array_starts_with: { type: JsonScalar },
|
|
724
|
+
array_ends_with: { type: JsonScalar },
|
|
725
|
+
}),
|
|
726
|
+
});
|
|
727
|
+
this.typeMap.set(name, filter);
|
|
728
|
+
return filter;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
_createEnumFilter(enumType) {
|
|
732
|
+
const name = `${enumType.name}Filter`;
|
|
733
|
+
const existing = this.typeMap.get(name);
|
|
734
|
+
if (existing) return existing;
|
|
735
|
+
const filter = new GraphQLInputObjectType({
|
|
736
|
+
name,
|
|
737
|
+
fields: () => ({
|
|
738
|
+
equals: { type: enumType },
|
|
739
|
+
in: { type: new GraphQLList(new GraphQLNonNull(enumType)) },
|
|
740
|
+
notIn: { type: new GraphQLList(new GraphQLNonNull(enumType)) },
|
|
741
|
+
not: { type: this._getFilter(enumType.name) },
|
|
742
|
+
_count: { type: this._getFilter('Int') },
|
|
743
|
+
_min: { type: this._getFilter(enumType.name) },
|
|
744
|
+
_max: { type: this._getFilter(enumType.name) },
|
|
745
|
+
}),
|
|
746
|
+
});
|
|
747
|
+
this.typeMap.set(name, filter);
|
|
748
|
+
return filter;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ---------- 类型获取方法(统一使用 typeMap)----------
|
|
752
|
+
_getOutputType(model) {
|
|
753
|
+
const name = model;
|
|
754
|
+
const existing = this.typeMap.get(name);
|
|
755
|
+
if (existing) return existing;
|
|
756
|
+
|
|
757
|
+
const modelDef = this._getModelDef(model);
|
|
758
|
+
const type = new GraphQLObjectType({
|
|
759
|
+
name,
|
|
760
|
+
fields: () => {
|
|
761
|
+
const fields = {};
|
|
762
|
+
const toManyRelations = []; // 收集所有一对多/多对多关系
|
|
763
|
+
|
|
764
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
765
|
+
// 标量字段
|
|
766
|
+
if (this._isScalar(field)) {
|
|
767
|
+
fields[fieldName] = { type: this._fieldToGraphQLType(field) };
|
|
768
|
+
}
|
|
769
|
+
// 关系字段
|
|
770
|
+
if (this._isRelation(field)) {
|
|
771
|
+
const target = this._getTargetModel(field);
|
|
772
|
+
const targetType = this._getOutputType(target);
|
|
773
|
+
|
|
774
|
+
const fieldConfig = {
|
|
775
|
+
type: field.array
|
|
776
|
+
? new GraphQLList(new GraphQLNonNull(targetType))
|
|
777
|
+
: field.optional ? targetType : new GraphQLNonNull(targetType),
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
// 如果是一对多/多对多关系(array=true),添加过滤参数
|
|
781
|
+
if (field.array) {
|
|
782
|
+
toManyRelations.push({ fieldName, field });
|
|
783
|
+
const args = {};
|
|
784
|
+
args.where = { type: this._getWhereInput(target) };
|
|
785
|
+
args.orderBy = { type: new GraphQLList(this._getOrderByInput(target)) };
|
|
786
|
+
args.take = { type: GraphQLInt };
|
|
787
|
+
args.skip = { type: GraphQLInt };
|
|
788
|
+
args.cursor = { type: this._getWhereUniqueInput(target) };
|
|
789
|
+
args.distinct = { type: new GraphQLList(new GraphQLNonNull(this._getDistinctEnum(target))) };
|
|
790
|
+
fieldConfig.args = args;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
fields[fieldName] = fieldConfig;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// _count 字段:基于 toManyRelations 构建,并为每个子字段添加参数
|
|
798
|
+
if (toManyRelations.length) {
|
|
799
|
+
const countTypeName = `${model}_count`;
|
|
800
|
+
let countType = this.typeMap.get(countTypeName);
|
|
801
|
+
if (!countType) {
|
|
802
|
+
const countFields = {};
|
|
803
|
+
toManyRelations.forEach(({ fieldName, field }) => {
|
|
804
|
+
const target = this._getTargetModel(field);
|
|
805
|
+
// 为每个计数子字段添加与数组关系相同的参数
|
|
806
|
+
const args = {
|
|
807
|
+
// where: { type: this._getWhereInput(target) },
|
|
808
|
+
// orderBy: { type: new GraphQLList(this._getOrderByInput(target)) },
|
|
809
|
+
// take: { type: GraphQLInt },
|
|
810
|
+
// skip: { type: GraphQLInt },
|
|
811
|
+
// cursor: { type: this._getWhereUniqueInput(target) },
|
|
812
|
+
// distinct: { type: new GraphQLList(new GraphQLNonNull(this._getDistinctEnum(target))) },
|
|
813
|
+
};
|
|
814
|
+
countFields[fieldName] = {
|
|
815
|
+
type: GraphQLInt,
|
|
816
|
+
args: args,
|
|
817
|
+
};
|
|
818
|
+
});
|
|
819
|
+
countType = new GraphQLObjectType({
|
|
820
|
+
name: countTypeName,
|
|
821
|
+
fields: countFields,
|
|
822
|
+
});
|
|
823
|
+
this.typeMap.set(countTypeName, countType);
|
|
824
|
+
}
|
|
825
|
+
fields._count = { type: countType };
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return fields;
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
this.typeMap.set(name, type);
|
|
833
|
+
return type;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
_getWhereInput(model) {
|
|
837
|
+
const name = `${model}WhereInput`;
|
|
838
|
+
const existing = this.typeMap.get(name);
|
|
839
|
+
if (existing) return existing;
|
|
840
|
+
|
|
841
|
+
const modelDef = this._getModelDef(model);
|
|
842
|
+
let whereInput; // 用于自引用
|
|
843
|
+
whereInput = new GraphQLInputObjectType({
|
|
844
|
+
name,
|
|
845
|
+
fields: () => {
|
|
846
|
+
const fields = {
|
|
847
|
+
AND: { type: new GraphQLList(whereInput) },
|
|
848
|
+
OR: { type: new GraphQLList(whereInput) },
|
|
849
|
+
NOT: { type: new GraphQLList(whereInput) },
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// 标量过滤
|
|
853
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
854
|
+
if (this._isScalar(field)) {
|
|
855
|
+
const filter = this._getFilter(field.type);
|
|
856
|
+
if (filter) fields[fieldName] = { type: filter };
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// 关系过滤
|
|
861
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
862
|
+
if (this._isRelation(field)) {
|
|
863
|
+
const target = this._getTargetModel(field);
|
|
864
|
+
const targetWhere = this._getWhereInput(target);
|
|
865
|
+
const filterName = `${model}${fieldName}RelationFilter`;
|
|
866
|
+
let filter = this.typeMap.get(filterName);
|
|
867
|
+
if (!filter) {
|
|
868
|
+
filter = new GraphQLInputObjectType({
|
|
869
|
+
name: filterName,
|
|
870
|
+
fields: field.array
|
|
871
|
+
? {
|
|
872
|
+
every: { type: targetWhere },
|
|
873
|
+
some: { type: targetWhere },
|
|
874
|
+
none: { type: targetWhere },
|
|
875
|
+
}
|
|
876
|
+
: {
|
|
877
|
+
is: { type: targetWhere },
|
|
878
|
+
isNot: { type: targetWhere },
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
this.typeMap.set(filterName, filter);
|
|
882
|
+
}
|
|
883
|
+
fields[fieldName] = { type: filter };
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return fields;
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
this.typeMap.set(name, whereInput);
|
|
891
|
+
return whereInput;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
_getWhereUniqueInput(model) {
|
|
895
|
+
const name = `${model}WhereUniqueInput`;
|
|
896
|
+
const existing = this.typeMap.get(name);
|
|
897
|
+
if (existing) return existing;
|
|
898
|
+
|
|
899
|
+
const modelDef = this._getModelDef(model);
|
|
900
|
+
const fields = {};
|
|
901
|
+
for (const idField of modelDef.idFields) {
|
|
902
|
+
const field = modelDef.fields[idField];
|
|
903
|
+
if (!field) throw new Error(`ID field ${idField} not found in ${model}`);
|
|
904
|
+
fields[idField] = { type: this._fieldToGraphQLType(field) };
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const input = new GraphQLInputObjectType({ name, fields });
|
|
908
|
+
this.typeMap.set(name, input);
|
|
909
|
+
return input;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
_getOrderByInput(model) {
|
|
913
|
+
const name = `${model}OrderByInput`;
|
|
914
|
+
const existing = this.typeMap.get(name);
|
|
915
|
+
if (existing) return existing;
|
|
916
|
+
|
|
917
|
+
const modelDef = this._getModelDef(model);
|
|
918
|
+
const orderBy = new GraphQLInputObjectType({
|
|
919
|
+
name,
|
|
920
|
+
fields: () => {
|
|
921
|
+
const fields = {};
|
|
922
|
+
|
|
923
|
+
// 标量排序
|
|
924
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
925
|
+
if (this._isScalar(field)) {
|
|
926
|
+
fields[fieldName] = { type: SortOrderEnum };
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// 关系排序
|
|
931
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
932
|
+
if (this._isRelation(field)) {
|
|
933
|
+
const target = this._getTargetModel(field);
|
|
934
|
+
if (field.array) {
|
|
935
|
+
const aggName = `${model}${fieldName}OrderByRelationAggregateInput`;
|
|
936
|
+
let aggType = this.typeMap.get(aggName);
|
|
937
|
+
if (!aggType) {
|
|
938
|
+
aggType = new GraphQLInputObjectType({
|
|
939
|
+
name: aggName,
|
|
940
|
+
fields: { _count: { type: SortOrderEnum } },
|
|
941
|
+
});
|
|
942
|
+
this.typeMap.set(aggName, aggType);
|
|
943
|
+
}
|
|
944
|
+
fields[fieldName] = { type: aggType };
|
|
945
|
+
} else {
|
|
946
|
+
fields[fieldName] = { type: this._getOrderByInput(target) };
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return fields;
|
|
951
|
+
},
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
this.typeMap.set(name, orderBy);
|
|
955
|
+
return orderBy;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
_getCreateInput(model) {
|
|
959
|
+
const name = `${model}CreateInput`;
|
|
960
|
+
const existing = this.typeMap.get(name);
|
|
961
|
+
if (existing) return existing;
|
|
962
|
+
|
|
963
|
+
const modelDef = this._getModelDef(model);
|
|
964
|
+
const create = new GraphQLInputObjectType({
|
|
965
|
+
name,
|
|
966
|
+
fields: () => {
|
|
967
|
+
const fields = {};
|
|
968
|
+
|
|
969
|
+
// 标量字段(排除自增)
|
|
970
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
971
|
+
if (this._isScalar(field) && !this._isAutoIncrement(field)) {
|
|
972
|
+
fields[fieldName] = { type: this._fieldToGraphQLType(field) };
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// 关系创建
|
|
977
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
978
|
+
if (this._isRelation(field)) {
|
|
979
|
+
const target = this._getTargetModel(field);
|
|
980
|
+
const targetCreate = this._getCreateInput(target);
|
|
981
|
+
const targetWhereUnique = this._getWhereUniqueInput(target);
|
|
982
|
+
const targetCreateMany = this._getCreateManyInput(target);
|
|
983
|
+
const targetConnectOrCreate = this._getConnectOrCreateInput(target);
|
|
984
|
+
|
|
985
|
+
if (field.array) {
|
|
986
|
+
const nestedName = `${model}${fieldName}CreateNestedManyInput`;
|
|
987
|
+
let nestedType = this.typeMap.get(nestedName);
|
|
988
|
+
if (!nestedType) {
|
|
989
|
+
nestedType = new GraphQLInputObjectType({
|
|
990
|
+
name: nestedName,
|
|
991
|
+
fields: {
|
|
992
|
+
create: { type: new GraphQLList(new GraphQLNonNull(targetCreate)) },
|
|
993
|
+
connect: { type: new GraphQLList(new GraphQLNonNull(targetWhereUnique)) },
|
|
994
|
+
connectOrCreate: { type: new GraphQLList(new GraphQLNonNull(targetConnectOrCreate)) },
|
|
995
|
+
createMany: { type: targetCreateMany },
|
|
996
|
+
},
|
|
997
|
+
});
|
|
998
|
+
this.typeMap.set(nestedName, nestedType);
|
|
999
|
+
}
|
|
1000
|
+
fields[fieldName] = { type: nestedType };
|
|
1001
|
+
} else {
|
|
1002
|
+
const nestedName = `${model}${fieldName}CreateNestedOneInput`;
|
|
1003
|
+
let nestedType = this.typeMap.get(nestedName);
|
|
1004
|
+
if (!nestedType) {
|
|
1005
|
+
nestedType = new GraphQLInputObjectType({
|
|
1006
|
+
name: nestedName,
|
|
1007
|
+
fields: {
|
|
1008
|
+
create: { type: targetCreate },
|
|
1009
|
+
connect: { type: targetWhereUnique },
|
|
1010
|
+
connectOrCreate: { type: targetConnectOrCreate },
|
|
1011
|
+
},
|
|
1012
|
+
});
|
|
1013
|
+
this.typeMap.set(nestedName, nestedType);
|
|
1014
|
+
}
|
|
1015
|
+
fields[fieldName] = { type: nestedType };
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return fields;
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
this.typeMap.set(name, create);
|
|
1024
|
+
return create;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
_getUpdateInput(model) {
|
|
1028
|
+
const name = `${model}UpdateInput`;
|
|
1029
|
+
const existing = this.typeMap.get(name);
|
|
1030
|
+
if (existing) return existing;
|
|
1031
|
+
|
|
1032
|
+
const modelDef = this._getModelDef(model);
|
|
1033
|
+
const update = new GraphQLInputObjectType({
|
|
1034
|
+
name,
|
|
1035
|
+
fields: () => {
|
|
1036
|
+
const fields = {};
|
|
1037
|
+
|
|
1038
|
+
// 标量更新
|
|
1039
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
1040
|
+
if (this._isScalar(field)) {
|
|
1041
|
+
const baseType = this._fieldToGraphQLType(field);
|
|
1042
|
+
const numeric = ['Int', 'Float', 'BigInt', 'Decimal'].includes(field.type);
|
|
1043
|
+
if (numeric && !field.array) {
|
|
1044
|
+
const opName = `${model}${fieldName}UpdateNumberInput`;
|
|
1045
|
+
let opType = this.typeMap.get(opName);
|
|
1046
|
+
if (!opType) {
|
|
1047
|
+
opType = new GraphQLInputObjectType({
|
|
1048
|
+
name: opName,
|
|
1049
|
+
fields: {
|
|
1050
|
+
set: { type: baseType },
|
|
1051
|
+
increment: { type: baseType },
|
|
1052
|
+
decrement: { type: baseType },
|
|
1053
|
+
multiply: { type: baseType },
|
|
1054
|
+
divide: { type: baseType },
|
|
1055
|
+
},
|
|
1056
|
+
});
|
|
1057
|
+
this.typeMap.set(opName, opType);
|
|
1058
|
+
}
|
|
1059
|
+
fields[fieldName] = { type: opType };
|
|
1060
|
+
} else if (field.array) {
|
|
1061
|
+
const arrName = `${model}${fieldName}UpdateArrayInput`;
|
|
1062
|
+
let arrType = this.typeMap.get(arrName);
|
|
1063
|
+
if (!arrType) {
|
|
1064
|
+
arrType = new GraphQLInputObjectType({
|
|
1065
|
+
name: arrName,
|
|
1066
|
+
fields: {
|
|
1067
|
+
set: { type: new GraphQLList(new GraphQLNonNull(baseType)) },
|
|
1068
|
+
push: { type: new GraphQLList(new GraphQLNonNull(baseType)) },
|
|
1069
|
+
},
|
|
1070
|
+
});
|
|
1071
|
+
this.typeMap.set(arrName, arrType);
|
|
1072
|
+
}
|
|
1073
|
+
fields[fieldName] = { type: arrType };
|
|
1074
|
+
} else {
|
|
1075
|
+
const scalarName = `${model}${fieldName}UpdateScalarInput`;
|
|
1076
|
+
let scalarType = this.typeMap.get(scalarName);
|
|
1077
|
+
if (!scalarType) {
|
|
1078
|
+
scalarType = new GraphQLInputObjectType({
|
|
1079
|
+
name: scalarName,
|
|
1080
|
+
fields: { set: { type: baseType } },
|
|
1081
|
+
});
|
|
1082
|
+
this.typeMap.set(scalarName, scalarType);
|
|
1083
|
+
}
|
|
1084
|
+
fields[fieldName] = { type: scalarType };
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// 关系更新
|
|
1090
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
1091
|
+
if (this._isRelation(field)) {
|
|
1092
|
+
const target = this._getTargetModel(field);
|
|
1093
|
+
const targetCreate = this._getCreateInput(target);
|
|
1094
|
+
const targetWhereUnique = this._getWhereUniqueInput(target);
|
|
1095
|
+
const targetConnectOrCreate = this._getConnectOrCreateInput(target);
|
|
1096
|
+
const targetUpdate = this._getUpdateInput(target);
|
|
1097
|
+
const targetWhere = this._getWhereInput(target);
|
|
1098
|
+
const targetUpdateNested = this._getUpdateNestedInput(target);
|
|
1099
|
+
const targetUpdateManyNested = this._getUpdateManyNestedInput(target);
|
|
1100
|
+
const targetUpsertNested = this._getUpsertNestedInput(target);
|
|
1101
|
+
|
|
1102
|
+
if (field.array) {
|
|
1103
|
+
const relName = `${model}${fieldName}UpdateManyRelationInput`;
|
|
1104
|
+
let relType = this.typeMap.get(relName);
|
|
1105
|
+
if (!relType) {
|
|
1106
|
+
relType = new GraphQLInputObjectType({
|
|
1107
|
+
name: relName,
|
|
1108
|
+
fields: {
|
|
1109
|
+
create: { type: new GraphQLList(new GraphQLNonNull(targetCreate)) },
|
|
1110
|
+
connect: { type: new GraphQLList(new GraphQLNonNull(targetWhereUnique)) },
|
|
1111
|
+
connectOrCreate: { type: new GraphQLList(new GraphQLNonNull(targetConnectOrCreate)) },
|
|
1112
|
+
disconnect: { type: new GraphQLList(new GraphQLNonNull(targetWhereUnique)) },
|
|
1113
|
+
delete: { type: new GraphQLList(new GraphQLNonNull(targetWhereUnique)) },
|
|
1114
|
+
update: { type: new GraphQLList(new GraphQLNonNull(targetUpdateNested)) },
|
|
1115
|
+
updateMany: { type: new GraphQLList(new GraphQLNonNull(targetUpdateManyNested)) },
|
|
1116
|
+
deleteMany: { type: new GraphQLList(new GraphQLNonNull(targetWhere)) },
|
|
1117
|
+
set: { type: new GraphQLList(new GraphQLNonNull(targetWhereUnique)) },
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
this.typeMap.set(relName, relType);
|
|
1121
|
+
}
|
|
1122
|
+
fields[fieldName] = { type: relType };
|
|
1123
|
+
} else {
|
|
1124
|
+
const relName = `${model}${fieldName}UpdateOneRelationInput`;
|
|
1125
|
+
let relType = this.typeMap.get(relName);
|
|
1126
|
+
if (!relType) {
|
|
1127
|
+
relType = new GraphQLInputObjectType({
|
|
1128
|
+
name: relName,
|
|
1129
|
+
fields: {
|
|
1130
|
+
create: { type: targetCreate },
|
|
1131
|
+
connect: { type: targetWhereUnique },
|
|
1132
|
+
connectOrCreate: { type: targetConnectOrCreate },
|
|
1133
|
+
disconnect: { type: GraphQLBoolean },
|
|
1134
|
+
delete: { type: GraphQLBoolean },
|
|
1135
|
+
update: { type: targetUpdate },
|
|
1136
|
+
upsert: { type: targetUpsertNested },
|
|
1137
|
+
},
|
|
1138
|
+
});
|
|
1139
|
+
this.typeMap.set(relName, relType);
|
|
1140
|
+
}
|
|
1141
|
+
fields[fieldName] = { type: relType };
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return fields;
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
this.typeMap.set(name, update);
|
|
1150
|
+
return update;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
_getCreateManyInput(model) {
|
|
1154
|
+
const name = `${model}CreateManyInput`;
|
|
1155
|
+
const existing = this.typeMap.get(name);
|
|
1156
|
+
if (existing) return existing;
|
|
1157
|
+
|
|
1158
|
+
const modelDef = this._getModelDef(model);
|
|
1159
|
+
const fields = {};
|
|
1160
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
1161
|
+
if (this._isScalar(field) && !this._isAutoIncrement(field)) {
|
|
1162
|
+
fields[fieldName] = { type: this._fieldToGraphQLType(field) };
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const input = new GraphQLInputObjectType({ name, fields });
|
|
1167
|
+
this.typeMap.set(name, input);
|
|
1168
|
+
return input;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
_getCountAggOutput(model) {
|
|
1172
|
+
const name = `${model}CountAggregateOutput`;
|
|
1173
|
+
const existing = this.typeMap.get(name);
|
|
1174
|
+
if (existing) return existing;
|
|
1175
|
+
|
|
1176
|
+
const modelDef = this._getModelDef(model);
|
|
1177
|
+
const fields = { _all: { type: GraphQLInt } };
|
|
1178
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
1179
|
+
if (this._isScalar(field)) {
|
|
1180
|
+
fields[fieldName] = { type: GraphQLInt };
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const output = new GraphQLObjectType({ name, fields });
|
|
1185
|
+
this.typeMap.set(name, output);
|
|
1186
|
+
return output;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
_getDistinctEnum(model) {
|
|
1190
|
+
const name = `${model}DistinctFieldEnum`;
|
|
1191
|
+
const existing = this.typeMap.get(name);
|
|
1192
|
+
if (existing) return existing;
|
|
1193
|
+
|
|
1194
|
+
const modelDef = this._getModelDef(model);
|
|
1195
|
+
const values = {};
|
|
1196
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
1197
|
+
if (this._isScalar(field)) {
|
|
1198
|
+
values[fieldName] = { value: fieldName };
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const enumType = new GraphQLEnumType({ name, values });
|
|
1203
|
+
this.typeMap.set(name, enumType);
|
|
1204
|
+
return enumType;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
_getOmitInput(model) {
|
|
1208
|
+
const name = `${model}OmitInput`;
|
|
1209
|
+
const existing = this.typeMap.get(name);
|
|
1210
|
+
if (existing) return existing;
|
|
1211
|
+
|
|
1212
|
+
const modelDef = this._getModelDef(model);
|
|
1213
|
+
const fields = {};
|
|
1214
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
1215
|
+
if (this._isScalar(field)) {
|
|
1216
|
+
fields[fieldName] = { type: GraphQLBoolean };
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const input = new GraphQLInputObjectType({ name, fields });
|
|
1221
|
+
this.typeMap.set(name, input);
|
|
1222
|
+
return input;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
_getCountAggInput(model) {
|
|
1226
|
+
const name = `${model}CountAggregateInput`;
|
|
1227
|
+
const existing = this.typeMap.get(name);
|
|
1228
|
+
if (existing) return existing;
|
|
1229
|
+
|
|
1230
|
+
const modelDef = this._getModelDef(model);
|
|
1231
|
+
const fields = { _all: { type: GraphQLBoolean } };
|
|
1232
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
1233
|
+
if (this._isScalar(field)) {
|
|
1234
|
+
fields[fieldName] = { type: GraphQLBoolean };
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const input = new GraphQLInputObjectType({ name, fields });
|
|
1239
|
+
this.typeMap.set(name, input);
|
|
1240
|
+
return input;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
_getAggInput(model) {
|
|
1244
|
+
const name = `${model}AggregateInput`;
|
|
1245
|
+
const existing = this.typeMap.get(name);
|
|
1246
|
+
if (existing) return existing;
|
|
1247
|
+
|
|
1248
|
+
const modelDef = this._getModelDef(model);
|
|
1249
|
+
const fields = {};
|
|
1250
|
+
for (const [fieldName, field] of Object.entries(modelDef.fields)) {
|
|
1251
|
+
if (this._isScalar(field)) {
|
|
1252
|
+
fields[fieldName] = { type: GraphQLBoolean };
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
const input = new GraphQLInputObjectType({ name, fields });
|
|
1257
|
+
this.typeMap.set(name, input);
|
|
1258
|
+
return input;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
_getConnectOrCreateInput(model) {
|
|
1262
|
+
const name = `${model}ConnectOrCreateInput`;
|
|
1263
|
+
const existing = this.typeMap.get(name);
|
|
1264
|
+
if (existing) return existing;
|
|
1265
|
+
|
|
1266
|
+
const input = new GraphQLInputObjectType({
|
|
1267
|
+
name,
|
|
1268
|
+
fields: {
|
|
1269
|
+
where: { type: new GraphQLNonNull(this._getWhereUniqueInput(model)) },
|
|
1270
|
+
create: { type: new GraphQLNonNull(this._getCreateInput(model)) },
|
|
1271
|
+
},
|
|
1272
|
+
});
|
|
1273
|
+
this.typeMap.set(name, input);
|
|
1274
|
+
return input;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
_getUpdateNestedInput(model) {
|
|
1278
|
+
const name = `${model}UpdateNestedInput`;
|
|
1279
|
+
const existing = this.typeMap.get(name);
|
|
1280
|
+
if (existing) return existing;
|
|
1281
|
+
|
|
1282
|
+
const input = new GraphQLInputObjectType({
|
|
1283
|
+
name,
|
|
1284
|
+
fields: {
|
|
1285
|
+
where: { type: new GraphQLNonNull(this._getWhereUniqueInput(model)) },
|
|
1286
|
+
data: { type: new GraphQLNonNull(this._getUpdateInput(model)) },
|
|
1287
|
+
},
|
|
1288
|
+
});
|
|
1289
|
+
this.typeMap.set(name, input);
|
|
1290
|
+
return input;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
_getUpdateManyNestedInput(model) {
|
|
1294
|
+
const name = `${model}UpdateManyNestedInput`;
|
|
1295
|
+
const existing = this.typeMap.get(name);
|
|
1296
|
+
if (existing) return existing;
|
|
1297
|
+
|
|
1298
|
+
const input = new GraphQLInputObjectType({
|
|
1299
|
+
name,
|
|
1300
|
+
fields: {
|
|
1301
|
+
where: { type: this._getWhereInput(model) },
|
|
1302
|
+
data: { type: new GraphQLNonNull(this._getUpdateInput(model)) },
|
|
1303
|
+
limit: { type: GraphQLInt },
|
|
1304
|
+
},
|
|
1305
|
+
});
|
|
1306
|
+
this.typeMap.set(name, input);
|
|
1307
|
+
return input;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
_getUpsertNestedInput(model) {
|
|
1311
|
+
const name = `${model}UpsertNestedInput`;
|
|
1312
|
+
const existing = this.typeMap.get(name);
|
|
1313
|
+
if (existing) return existing;
|
|
1314
|
+
|
|
1315
|
+
const input = new GraphQLInputObjectType({
|
|
1316
|
+
name,
|
|
1317
|
+
fields: {
|
|
1318
|
+
where: { type: new GraphQLNonNull(this._getWhereUniqueInput(model)) },
|
|
1319
|
+
create: { type: new GraphQLNonNull(this._getCreateInput(model)) },
|
|
1320
|
+
update: { type: new GraphQLNonNull(this._getUpdateInput(model)) },
|
|
1321
|
+
},
|
|
1322
|
+
});
|
|
1323
|
+
this.typeMap.set(name, input);
|
|
1324
|
+
return input;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
_getAffectedRowsOutput() {
|
|
1328
|
+
const name = 'affectedRowsOutput';
|
|
1329
|
+
const payload = new GraphQLObjectType({
|
|
1330
|
+
name,
|
|
1331
|
+
fields: { count: { type: new GraphQLNonNull(GraphQLInt) } },
|
|
1332
|
+
});
|
|
1333
|
+
this.typeMap.set(name, payload);
|
|
1334
|
+
return payload;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
applySecurityRules(key, value) {
|
|
1338
|
+
const quantityKeys = ['take', 'first', 'last', 'limit'];
|
|
1339
|
+
if (quantityKeys.includes(key) && typeof value === 'number') {
|
|
1340
|
+
if (value > this.options.maxTake) {
|
|
1341
|
+
if (this.options.throwOnError) {
|
|
1342
|
+
throw new Error(`[Security Violation] '${key}' exceeds max limit of ${this.options.maxTake}`);
|
|
1343
|
+
}
|
|
1344
|
+
return this.options.maxTake;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return value;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* 校验并清理参数对象
|
|
1352
|
+
*/
|
|
1353
|
+
validateArguments(argsObject) {
|
|
1354
|
+
// const policy = { ...DEFAULT_SECURITY_POLICY, ...options };
|
|
1355
|
+
const sanitized = {};
|
|
1356
|
+
for (const [key, val] of Object.entries(argsObject || {})) {
|
|
1357
|
+
sanitized[key] = this.applySecurityRules(key, val);
|
|
1358
|
+
}
|
|
1359
|
+
return sanitized;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* 解析 AST 节点参数(包括指令参数)
|
|
1364
|
+
*/
|
|
1365
|
+
getArgsFromAST(nodes, variables) {
|
|
1366
|
+
if (!nodes || nodes.length === 0) return null;
|
|
1367
|
+
const args = {};
|
|
1368
|
+
for (const node of nodes) {
|
|
1369
|
+
args[node.name.value] = valueFromASTUntyped(node.value, variables);
|
|
1370
|
+
}
|
|
1371
|
+
return args;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* 解析 GraphQL ResolveInfo 转换为 Prisma Select 和 指令转换计划
|
|
1377
|
+
*/
|
|
1378
|
+
parseGraphQLProjection(info, options = {}) {
|
|
1379
|
+
const policy = { ...this.options, ...options };
|
|
1380
|
+
const { fieldNodes, fragments, variableValues } = info;
|
|
1381
|
+
|
|
1382
|
+
return this.traverseASTNode(fieldNodes[0].selectionSet, fragments, variableValues, policy, 0);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
traverseASTNode(selectionSet, fragments, variables, policy, depth) {
|
|
1386
|
+
|
|
1387
|
+
if (depth >= policy.maxDepth) {
|
|
1388
|
+
if (policy.throwOnError) {
|
|
1389
|
+
throw new Error(`[Security Violation] Query depth limit reached (${policy.maxDepth})`);
|
|
1390
|
+
}
|
|
1391
|
+
return { prismaSelect: undefined, transformPlan: null };
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
const prismaSelect = {};
|
|
1395
|
+
const transformPlan = {}; // 仅存储包含指令的字段路径
|
|
1396
|
+
let hasDirectivesInTree = false;
|
|
1397
|
+
|
|
1398
|
+
if (!selectionSet) return { prismaSelect: undefined, transformPlan: null };
|
|
1399
|
+
|
|
1400
|
+
for (const selection of selectionSet.selections) {
|
|
1401
|
+
// 1. 处理片段 (Fragments)
|
|
1402
|
+
if (selection.kind === 'FragmentSpread' || selection.kind === 'InlineFragment') {
|
|
1403
|
+
const fragment = selection.kind === 'FragmentSpread' ? fragments[selection.name.value] : selection;
|
|
1404
|
+
if (fragment) {
|
|
1405
|
+
const result = this.traverseASTNode(fragment.selectionSet, fragments, variables, policy, depth);
|
|
1406
|
+
Object.assign(prismaSelect, result.prismaSelect);
|
|
1407
|
+
if (result.transformPlan) {
|
|
1408
|
+
Object.assign(transformPlan, result.transformPlan);
|
|
1409
|
+
hasDirectivesInTree = true;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// 2. 处理标准字段
|
|
1416
|
+
if (selection.kind === 'Field') {
|
|
1417
|
+
const fieldName = selection.name.value;
|
|
1418
|
+
|
|
1419
|
+
// 解析字段参数 (e.g. users(take: 10))
|
|
1420
|
+
const args = this.getArgsFromAST(selection.arguments, variables);
|
|
1421
|
+
const validatedArgs = this.validateArguments(args, policy);
|
|
1422
|
+
|
|
1423
|
+
// 解析指令及其参数 (e.g. @mask(start: 3))
|
|
1424
|
+
const directiveConfigs = selection.directives?.map(d => ({
|
|
1425
|
+
name: d.name.value,
|
|
1426
|
+
args: this.getArgsFromAST(d.arguments, variables)
|
|
1427
|
+
})) || [];
|
|
1428
|
+
|
|
1429
|
+
if (selection.selectionSet) {
|
|
1430
|
+
// 递归处理嵌套
|
|
1431
|
+
const subResult = this.traverseASTNode(selection.selectionSet, fragments, variables, policy, depth + 1);
|
|
1432
|
+
|
|
1433
|
+
prismaSelect[fieldName] = {
|
|
1434
|
+
select: subResult.prismaSelect,
|
|
1435
|
+
...validatedArgs
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
// 只有当子节点有指令或当前节点有指令时,才记录到 Plan
|
|
1439
|
+
if (directiveConfigs.length > 0 || subResult.transformPlan) {
|
|
1440
|
+
transformPlan[fieldName] = {
|
|
1441
|
+
directives: directiveConfigs.length > 0 ? directiveConfigs : null,
|
|
1442
|
+
nested: subResult.transformPlan
|
|
1443
|
+
};
|
|
1444
|
+
hasDirectivesInTree = true;
|
|
1445
|
+
}
|
|
1446
|
+
} else {
|
|
1447
|
+
// 叶子节点
|
|
1448
|
+
prismaSelect[fieldName] = true;
|
|
1449
|
+
if (directiveConfigs.length > 0) {
|
|
1450
|
+
transformPlan[fieldName] = { directives: directiveConfigs };
|
|
1451
|
+
hasDirectivesInTree = true;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
return {
|
|
1458
|
+
prismaSelect,
|
|
1459
|
+
transformPlan: hasDirectivesInTree ? transformPlan : null
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// ==========================================
|
|
1464
|
+
// 3. 结果映射模块 (支持异步指令)
|
|
1465
|
+
// ==========================================
|
|
1466
|
+
|
|
1467
|
+
/**
|
|
1468
|
+
* 递归应用指令转换
|
|
1469
|
+
* @param {Object|Array} data - Prisma 返回的数据
|
|
1470
|
+
* @param {Object} plan - transformPlan
|
|
1471
|
+
*/
|
|
1472
|
+
async applyDirectives(data, plan, vars) {
|
|
1473
|
+
// 如果数据为空或没有转换计划,直接返回
|
|
1474
|
+
if (!data || !plan) return data;
|
|
1475
|
+
|
|
1476
|
+
// 处理数组性能优化:使用 Promise.all 并行处理
|
|
1477
|
+
if (Array.isArray(data)) {
|
|
1478
|
+
return Promise.all(data.map(item => this.applyDirectives(item, plan)));
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// 浅拷贝对象以避免副作用,仅处理 plan 中存在的字段
|
|
1482
|
+
const result = { ...data };
|
|
1483
|
+
|
|
1484
|
+
for (const fieldName in plan) {
|
|
1485
|
+
const { directives, nested } = plan[fieldName];
|
|
1486
|
+
let value = result[fieldName];
|
|
1487
|
+
|
|
1488
|
+
if (value === undefined) continue;
|
|
1489
|
+
|
|
1490
|
+
// 1. 先处理嵌套数据
|
|
1491
|
+
if (nested && value !== null) {
|
|
1492
|
+
value = await this.applyDirectives(value, nested);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// 2. 顺序执行当前字段的所有指令
|
|
1496
|
+
if (directives) {
|
|
1497
|
+
for (const dir of directives) {
|
|
1498
|
+
const handler = this.directives[dir.name];
|
|
1499
|
+
if (handler) {
|
|
1500
|
+
value = await handler(value, dir.args || {}, vars, fieldName);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
result[fieldName] = value;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
return result;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
|
|
1512
|
+
/**
|
|
1513
|
+
* 静态生成 rootValue 结构
|
|
1514
|
+
* 此时它不依赖具体的 prisma 实例,只定义逻辑骨架
|
|
1515
|
+
*/
|
|
1516
|
+
_buildRootValue() {
|
|
1517
|
+
this.outpuRootValue = this.modelNames.reduce((acc, model) => {
|
|
1518
|
+
const lower = model[0].toLowerCase() + model.slice(1);
|
|
1519
|
+
for (const operation of this.operations) {
|
|
1520
|
+
acc[`${lower}_${operation}`] = async (args, contextValue, info) => {
|
|
1521
|
+
// console.log(info.variableValues)
|
|
1522
|
+
const { client, options: contextOptions } = contextValue;
|
|
1523
|
+
const safeArgs = this.validateArguments(args, contextOptions);
|
|
1524
|
+
const { prismaSelect, transformPlan: schemaMapping } = this.parseGraphQLProjection(info, contextOptions);
|
|
1525
|
+
const rawResult = await client[lower][operation]({
|
|
1526
|
+
...safeArgs,
|
|
1527
|
+
select: prismaSelect
|
|
1528
|
+
});
|
|
1529
|
+
return await this.applyDirectives(rawResult, schemaMapping, info.variableValues);
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
return acc;
|
|
1533
|
+
}, {});
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
}
|