@simtlix/simfinity-core 3.2.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/LICENSE +201 -0
- package/README.md +28 -0
- package/package.json +40 -0
- package/src/auth/errors.js +43 -0
- package/src/auth/expressions.js +159 -0
- package/src/auth/index.js +421 -0
- package/src/auth/object-id.js +25 -0
- package/src/auth/rules.js +300 -0
- package/src/const/QLOperator.js +19 -0
- package/src/const/QLSort.js +24 -0
- package/src/const/QLValue.js +32 -0
- package/src/errors/internal-server.error.js +11 -0
- package/src/errors/simfinity.error.js +15 -0
- package/src/index.js +17 -0
- package/src/introspection.js +37 -0
- package/src/metadata.js +140 -0
- package/src/plugins.js +53 -0
- package/src/query-limits.js +26 -0
- package/src/query-plan.js +110 -0
- package/src/runtime.js +1387 -0
- package/src/scalars/factory.js +52 -0
- package/src/scalars.js +187 -0
- package/src/validators.js +249 -0
- package/types/index.d.ts +306 -0
package/src/runtime.js
ADDED
|
@@ -0,0 +1,1387 @@
|
|
|
1
|
+
import { configureQueryLimits } from './query-limits.js';
|
|
2
|
+
import {
|
|
3
|
+
GraphQLObjectType, GraphQLString, GraphQLID, GraphQLSchema, GraphQLList,
|
|
4
|
+
GraphQLNonNull, GraphQLInputObjectType, GraphQLScalarType,
|
|
5
|
+
GraphQLInt, GraphQLEnumType, GraphQLBoolean, Kind,
|
|
6
|
+
} from 'graphql';
|
|
7
|
+
|
|
8
|
+
import SimfinityError from './errors/simfinity.error.js';
|
|
9
|
+
import InternalServerError from './errors/internal-server.error.js';
|
|
10
|
+
import QLOperator from './const/QLOperator.js';
|
|
11
|
+
import QLValue from './const/QLValue.js';
|
|
12
|
+
import QLSort from './const/QLSort.js';
|
|
13
|
+
import './introspection.js';
|
|
14
|
+
|
|
15
|
+
const GraphQLJSON = new GraphQLScalarType({
|
|
16
|
+
name: 'JSON',
|
|
17
|
+
description: 'The `JSON` scalar type represents JSON values as specified by ECMA-404',
|
|
18
|
+
serialize(value) {
|
|
19
|
+
return value;
|
|
20
|
+
},
|
|
21
|
+
parseValue(value) {
|
|
22
|
+
return value;
|
|
23
|
+
},
|
|
24
|
+
parseLiteral(ast) {
|
|
25
|
+
switch (ast.kind) {
|
|
26
|
+
case Kind.STRING:
|
|
27
|
+
case Kind.BOOLEAN:
|
|
28
|
+
return ast.value;
|
|
29
|
+
case Kind.INT:
|
|
30
|
+
return parseInt(ast.value, 10);
|
|
31
|
+
case Kind.FLOAT:
|
|
32
|
+
return parseFloat(ast.value);
|
|
33
|
+
case Kind.OBJECT: {
|
|
34
|
+
const value = Object.create(null);
|
|
35
|
+
ast.fields.forEach((field) => {
|
|
36
|
+
value[field.name.value] = GraphQLJSON.parseLiteral(field.value);
|
|
37
|
+
});
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
case Kind.LIST:
|
|
41
|
+
return ast.values.map((n) => GraphQLJSON.parseLiteral(n));
|
|
42
|
+
case Kind.NULL:
|
|
43
|
+
return null;
|
|
44
|
+
default:
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const buildErrorFormatter = (callback) => {
|
|
51
|
+
const formatError = (err) => {
|
|
52
|
+
let result = null;
|
|
53
|
+
if (err instanceof SimfinityError) {
|
|
54
|
+
result = err;
|
|
55
|
+
} else {
|
|
56
|
+
result = new InternalServerError(err.message, err);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (callback) {
|
|
60
|
+
const formattedError = callback(result);
|
|
61
|
+
return formattedError || result;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
65
|
+
return formatError;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export { SimfinityError, InternalServerError };
|
|
69
|
+
|
|
70
|
+
const cloneInput = (value, type) => {
|
|
71
|
+
if (value === null || value === undefined || !type) return value;
|
|
72
|
+
if (type instanceof GraphQLNonNull) return cloneInput(value, type.ofType);
|
|
73
|
+
if (type instanceof GraphQLList) {
|
|
74
|
+
return Array.isArray(value)
|
|
75
|
+
? value.map((item) => cloneInput(item, type.ofType))
|
|
76
|
+
: value;
|
|
77
|
+
}
|
|
78
|
+
if (type instanceof GraphQLInputObjectType) {
|
|
79
|
+
const fields = type.getFields();
|
|
80
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
81
|
+
key,
|
|
82
|
+
fields[key] ? cloneInput(item, fields[key].type) : item,
|
|
83
|
+
]));
|
|
84
|
+
}
|
|
85
|
+
if (type instanceof GraphQLScalarType && value instanceof Date) {
|
|
86
|
+
return new Date(value.getTime());
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const createRuntime = (adapter) => {
|
|
92
|
+
if (!adapter) {
|
|
93
|
+
throw new SimfinityError('A database adapter is required', 'ADAPTER_REQUIRED', 500);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const stateValue = adapter.stateValue || ((state) => state.name);
|
|
97
|
+
const typesDict = { types: {} };
|
|
98
|
+
const waitingInputType = {};
|
|
99
|
+
const typesDictForUpdate = { types: {} };
|
|
100
|
+
const registeredMutations = {};
|
|
101
|
+
const middlewares = [];
|
|
102
|
+
|
|
103
|
+
const operations = {
|
|
104
|
+
SAVE: 'save',
|
|
105
|
+
UPDATE: 'update',
|
|
106
|
+
DELETE: 'delete',
|
|
107
|
+
STATE_CHANGED: 'state_changed',
|
|
108
|
+
CUSTOM_MUTATION: 'custom_mutation',
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const use = (middleware) => {
|
|
112
|
+
middlewares.push(middleware);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
let preventCollectionCreation = false;
|
|
116
|
+
|
|
117
|
+
const preventCreatingCollection = (prevent) => {
|
|
118
|
+
preventCollectionCreation = !!prevent;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const QLFilter = new GraphQLInputObjectType({
|
|
122
|
+
name: 'QLFilter',
|
|
123
|
+
fields: () => ({
|
|
124
|
+
operator: { type: QLOperator },
|
|
125
|
+
value: { type: QLValue },
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const QLTypeFilter = new GraphQLInputObjectType({
|
|
130
|
+
name: 'QLTypeFilter',
|
|
131
|
+
fields: () => ({
|
|
132
|
+
operator: { type: QLOperator },
|
|
133
|
+
value: { type: QLValue },
|
|
134
|
+
path: { type: GraphQLString },
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const IdInputType = new GraphQLInputObjectType({
|
|
139
|
+
name: 'IdInputType',
|
|
140
|
+
fields: () => ({
|
|
141
|
+
id: { type: new GraphQLNonNull(GraphQLString) },
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const QLTypeFilterExpression = new GraphQLInputObjectType({
|
|
146
|
+
name: 'QLTypeFilterExpression',
|
|
147
|
+
fields: () => ({
|
|
148
|
+
terms: { type: new GraphQLList(QLTypeFilter) },
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const QLFilterCondition = new GraphQLInputObjectType({
|
|
153
|
+
name: 'QLFilterCondition',
|
|
154
|
+
fields: () => ({
|
|
155
|
+
field: { type: new GraphQLNonNull(GraphQLString) },
|
|
156
|
+
operator: { type: QLOperator },
|
|
157
|
+
value: { type: QLValue },
|
|
158
|
+
path: { type: GraphQLString },
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const QLFilterGroup = new GraphQLInputObjectType({
|
|
163
|
+
name: 'QLFilterGroup',
|
|
164
|
+
fields: () => ({
|
|
165
|
+
AND: { type: new GraphQLList(QLFilterGroup) },
|
|
166
|
+
OR: { type: new GraphQLList(QLFilterGroup) },
|
|
167
|
+
conditions: { type: new GraphQLList(QLFilterCondition) },
|
|
168
|
+
}),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const QLPagination = new GraphQLInputObjectType({
|
|
172
|
+
name: 'QLPagination',
|
|
173
|
+
fields: () => ({
|
|
174
|
+
page: { type: new GraphQLNonNull(GraphQLInt) },
|
|
175
|
+
size: { type: new GraphQLNonNull(GraphQLInt) },
|
|
176
|
+
count: { type: GraphQLBoolean },
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const QLSortExpression = new GraphQLInputObjectType({
|
|
181
|
+
name: 'QLSortExpression',
|
|
182
|
+
fields: () => ({
|
|
183
|
+
terms: { type: new GraphQLList(QLSort) },
|
|
184
|
+
}),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const QLAggregationOperation = new GraphQLEnumType({
|
|
188
|
+
name: 'QLAggregationOperation',
|
|
189
|
+
values: {
|
|
190
|
+
SUM: { value: 'SUM' },
|
|
191
|
+
COUNT: { value: 'COUNT' },
|
|
192
|
+
AVG: { value: 'AVG' },
|
|
193
|
+
MIN: { value: 'MIN' },
|
|
194
|
+
MAX: { value: 'MAX' },
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const QLTypeAggregationFact = new GraphQLInputObjectType({
|
|
199
|
+
name: 'QLTypeAggregationFact',
|
|
200
|
+
fields: () => ({
|
|
201
|
+
operation: { type: new GraphQLNonNull(QLAggregationOperation) },
|
|
202
|
+
factName: { type: new GraphQLNonNull(GraphQLString) },
|
|
203
|
+
path: { type: new GraphQLNonNull(GraphQLString) },
|
|
204
|
+
}),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const QLTypeAggregationExpression = new GraphQLInputObjectType({
|
|
208
|
+
name: 'QLTypeAggregationExpression',
|
|
209
|
+
fields: () => ({
|
|
210
|
+
groupId: { type: new GraphQLNonNull(GraphQLString) },
|
|
211
|
+
facts: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(QLTypeAggregationFact))) },
|
|
212
|
+
}),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const QLTypeAggregationResult = new GraphQLObjectType({
|
|
216
|
+
name: 'QLTypeAggregationResult',
|
|
217
|
+
fields: () => ({
|
|
218
|
+
groupId: { type: GraphQLJSON },
|
|
219
|
+
facts: { type: GraphQLJSON },
|
|
220
|
+
}),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const isNonNullOfType = (fieldEntryType, graphQLType) => {
|
|
224
|
+
let isOfType = false;
|
|
225
|
+
if (fieldEntryType instanceof GraphQLNonNull) {
|
|
226
|
+
isOfType = fieldEntryType.ofType instanceof graphQLType;
|
|
227
|
+
}
|
|
228
|
+
return isOfType;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const unwrapNonNull = (type) => (type instanceof GraphQLNonNull ? type.ofType : type);
|
|
232
|
+
|
|
233
|
+
const unwrapListAndNonNull = (type) => {
|
|
234
|
+
let unwrapped = type;
|
|
235
|
+
while (unwrapped instanceof GraphQLList || unwrapped instanceof GraphQLNonNull) {
|
|
236
|
+
unwrapped = unwrapped.ofType;
|
|
237
|
+
}
|
|
238
|
+
return unwrapped;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const getListShape = (type) => {
|
|
242
|
+
const outerNonNull = type instanceof GraphQLNonNull;
|
|
243
|
+
const listType = unwrapNonNull(type);
|
|
244
|
+
if (!(listType instanceof GraphQLList)) return null;
|
|
245
|
+
const itemNonNull = listType.ofType instanceof GraphQLNonNull;
|
|
246
|
+
return {
|
|
247
|
+
outerNonNull,
|
|
248
|
+
itemNonNull,
|
|
249
|
+
itemType: unwrapNonNull(listType.ofType),
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const getFieldStorageName = (fieldName, field) => {
|
|
254
|
+
const relation = field.extensions?.relation;
|
|
255
|
+
return relation && !relation.embedded && !getListShape(field.type)
|
|
256
|
+
? relation.connectionField || fieldName
|
|
257
|
+
: fieldName;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const normalizeConnectionField = (childType, declaredFieldName) => {
|
|
261
|
+
if (!declaredFieldName) {
|
|
262
|
+
return { declaredFieldName, graphqlFieldName: null, storageFieldName: null };
|
|
263
|
+
}
|
|
264
|
+
const childFields = childType.getFields();
|
|
265
|
+
let graphqlFieldName = childFields[declaredFieldName] ? declaredFieldName : null;
|
|
266
|
+
if (!graphqlFieldName) {
|
|
267
|
+
const aliasedField = Object.entries(childFields).find(([fieldName, field]) => (
|
|
268
|
+
getFieldStorageName(fieldName, field) === declaredFieldName
|
|
269
|
+
));
|
|
270
|
+
graphqlFieldName = aliasedField?.[0] || null;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
declaredFieldName,
|
|
274
|
+
graphqlFieldName,
|
|
275
|
+
storageFieldName: graphqlFieldName
|
|
276
|
+
? getFieldStorageName(graphqlFieldName, childFields[graphqlFieldName])
|
|
277
|
+
: declaredFieldName,
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const wrapListInputType = (itemType, listShape, preserveOuterNonNull) => {
|
|
282
|
+
const wrappedItem = listShape.itemNonNull ? new GraphQLNonNull(itemType) : itemType;
|
|
283
|
+
const listType = new GraphQLList(wrappedItem);
|
|
284
|
+
return preserveOuterNonNull && listShape.outerNonNull
|
|
285
|
+
? new GraphQLNonNull(listType)
|
|
286
|
+
: listType;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Creates a new GraphQLInputObjectType with a field excluded.
|
|
291
|
+
* @param {string} inputNamePrefix - The prefix for the input type name.
|
|
292
|
+
* @param {GraphQLInputObjectType} originalType - The original input type.
|
|
293
|
+
* @param {string} fieldToExclude - The name of the field to exclude.
|
|
294
|
+
* @returns {GraphQLInputObjectType} A new input type without the specified field.
|
|
295
|
+
*/
|
|
296
|
+
const createTypeWithExcludedField = (inputNamePrefix, originalType, fieldToExclude,
|
|
297
|
+
connectionFieldName = fieldToExclude) => {
|
|
298
|
+
const originalFields = originalType.getFields();
|
|
299
|
+
const newFields = Object.fromEntries(
|
|
300
|
+
Object.entries(originalFields)
|
|
301
|
+
.filter(([fieldName]) => fieldName !== fieldToExclude)
|
|
302
|
+
.map(([fieldName, field]) => [fieldName, {
|
|
303
|
+
type: field.type,
|
|
304
|
+
description: field.description,
|
|
305
|
+
defaultValue: field.defaultValue,
|
|
306
|
+
}]),
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
return new GraphQLInputObjectType({
|
|
310
|
+
name: `${inputNamePrefix}${originalType.name}For${connectionFieldName.charAt(0).toUpperCase() + connectionFieldName.slice(1)}`,
|
|
311
|
+
fields: newFields,
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const createOneToManyInputType = (inputNamePrefix, fieldEntryName,
|
|
316
|
+
inputType, updateInputType, connection, itemNonNull = false) => {
|
|
317
|
+
let inputTypeForAdd = inputType;
|
|
318
|
+
|
|
319
|
+
if (connection.declaredFieldName) {
|
|
320
|
+
inputTypeForAdd = createTypeWithExcludedField(
|
|
321
|
+
inputNamePrefix,
|
|
322
|
+
inputType,
|
|
323
|
+
connection.graphqlFieldName || connection.declaredFieldName,
|
|
324
|
+
connection.declaredFieldName,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return new GraphQLInputObjectType({
|
|
329
|
+
name: `OneToMany${inputNamePrefix}${fieldEntryName}`,
|
|
330
|
+
fields: () => ({
|
|
331
|
+
added: {
|
|
332
|
+
type: new GraphQLList(itemNonNull
|
|
333
|
+
? new GraphQLNonNull(inputTypeForAdd) : inputTypeForAdd),
|
|
334
|
+
},
|
|
335
|
+
updated: {
|
|
336
|
+
type: new GraphQLList(itemNonNull
|
|
337
|
+
? new GraphQLNonNull(updateInputType) : updateInputType),
|
|
338
|
+
},
|
|
339
|
+
deleted: {
|
|
340
|
+
type: new GraphQLList(itemNonNull ? new GraphQLNonNull(GraphQLID) : GraphQLID),
|
|
341
|
+
},
|
|
342
|
+
}),
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const graphQLListInputType = (dict, fieldEntry, fieldEntryName,
|
|
347
|
+
inputNamePrefix, connectionField, preserveOuterNonNull) => {
|
|
348
|
+
const listShape = getListShape(fieldEntry.type);
|
|
349
|
+
if (!listShape) return null;
|
|
350
|
+
const { itemType } = listShape;
|
|
351
|
+
|
|
352
|
+
if (itemType instanceof GraphQLObjectType && dict.types[itemType.name].inputType) {
|
|
353
|
+
if (!fieldEntry.extensions || !fieldEntry.extensions.relation
|
|
354
|
+
|| !fieldEntry.extensions.relation.embedded) {
|
|
355
|
+
const oneToMany = createOneToManyInputType(inputNamePrefix, fieldEntryName,
|
|
356
|
+
typesDict.types[itemType.name].inputType,
|
|
357
|
+
typesDictForUpdate.types[itemType.name].inputType,
|
|
358
|
+
normalizeConnectionField(itemType, connectionField),
|
|
359
|
+
listShape.itemNonNull);
|
|
360
|
+
return preserveOuterNonNull && listShape.outerNonNull
|
|
361
|
+
? new GraphQLNonNull(oneToMany)
|
|
362
|
+
: oneToMany;
|
|
363
|
+
}
|
|
364
|
+
if (fieldEntry.extensions && fieldEntry.extensions.relation
|
|
365
|
+
&& fieldEntry.extensions.relation.embedded) {
|
|
366
|
+
return wrapListInputType(
|
|
367
|
+
dict.types[itemType.name].inputType,
|
|
368
|
+
listShape,
|
|
369
|
+
preserveOuterNonNull,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
} else if (itemType instanceof GraphQLScalarType || itemType instanceof GraphQLEnumType) {
|
|
373
|
+
return wrapListInputType(itemType, listShape, preserveOuterNonNull);
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const buildInputType = (gqltype) => {
|
|
379
|
+
const argTypes = gqltype.getFields();
|
|
380
|
+
|
|
381
|
+
const fieldsArgs = {};
|
|
382
|
+
const fieldsArgForUpdate = {};
|
|
383
|
+
|
|
384
|
+
const selfReferenceCollections = {};
|
|
385
|
+
|
|
386
|
+
for (const [fieldEntryName, fieldEntry] of Object.entries(argTypes)) {
|
|
387
|
+
const fieldArg = {};
|
|
388
|
+
const fieldArgForUpdate = {};
|
|
389
|
+
|
|
390
|
+
if (!fieldEntry.extensions || !fieldEntry.extensions.readOnly) {
|
|
391
|
+
const hasStateMachine = !!typesDict.types[gqltype.name].stateMachine;
|
|
392
|
+
const stateFieldManagedByStateMachine = !!(fieldEntryName === 'state' && hasStateMachine);
|
|
393
|
+
|
|
394
|
+
if (!stateFieldManagedByStateMachine) {
|
|
395
|
+
if (fieldEntry.type instanceof GraphQLScalarType
|
|
396
|
+
|| fieldEntry.type instanceof GraphQLEnumType
|
|
397
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLScalarType)
|
|
398
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLEnumType)) {
|
|
399
|
+
if (fieldEntryName !== 'id') {
|
|
400
|
+
fieldArg.type = fieldEntry.type;
|
|
401
|
+
}
|
|
402
|
+
fieldArgForUpdate.type = fieldEntry.type instanceof GraphQLNonNull
|
|
403
|
+
? fieldEntry.type.ofType : fieldEntry.type;
|
|
404
|
+
if (fieldEntryName === 'id' && unwrapNonNull(fieldEntry.type) === GraphQLID) {
|
|
405
|
+
fieldArgForUpdate.type = new GraphQLNonNull(GraphQLID);
|
|
406
|
+
}
|
|
407
|
+
} else if (fieldEntry.type instanceof GraphQLObjectType
|
|
408
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLObjectType)) {
|
|
409
|
+
if (fieldEntry.extensions && fieldEntry.extensions.relation) {
|
|
410
|
+
const fieldEntryNameValue = fieldEntry.type instanceof GraphQLNonNull
|
|
411
|
+
? fieldEntry.type.ofType.name : fieldEntry.type.name;
|
|
412
|
+
if (!fieldEntry.extensions.relation.embedded) {
|
|
413
|
+
fieldArg.type = fieldEntry.type instanceof GraphQLNonNull
|
|
414
|
+
? new GraphQLNonNull(IdInputType) : IdInputType;
|
|
415
|
+
fieldArgForUpdate.type = IdInputType;
|
|
416
|
+
} else if (typesDict.types[fieldEntryNameValue].inputType
|
|
417
|
+
&& typesDictForUpdate.types[fieldEntryNameValue].inputType) {
|
|
418
|
+
fieldArg.type = typesDict.types[fieldEntryNameValue].inputType;
|
|
419
|
+
fieldArgForUpdate.type = typesDictForUpdate.types[fieldEntryNameValue].inputType;
|
|
420
|
+
} else {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
console.warn(`Configuration issue: Field ${fieldEntryName} does not define extensions.relation`);
|
|
425
|
+
}
|
|
426
|
+
} else if (getListShape(fieldEntry.type)) {
|
|
427
|
+
const listShape = getListShape(fieldEntry.type);
|
|
428
|
+
if (listShape.itemType === gqltype) {
|
|
429
|
+
selfReferenceCollections[fieldEntryName] = fieldEntry;
|
|
430
|
+
} else {
|
|
431
|
+
const listInputTypeForAdd = graphQLListInputType(typesDict, fieldEntry,
|
|
432
|
+
fieldEntryName, `${gqltype.name}A`,
|
|
433
|
+
fieldEntry.extensions?.relation?.connectionField, true);
|
|
434
|
+
const listInputTypeForUpdate = graphQLListInputType(typesDictForUpdate, fieldEntry,
|
|
435
|
+
fieldEntryName, `${gqltype.name}U`,
|
|
436
|
+
fieldEntry.extensions?.relation?.connectionField, false);
|
|
437
|
+
if (listInputTypeForAdd && listInputTypeForUpdate) {
|
|
438
|
+
fieldArg.type = listInputTypeForAdd;
|
|
439
|
+
fieldArgForUpdate.type = listInputTypeForUpdate;
|
|
440
|
+
} else {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (fieldArg.type && fieldEntry.type instanceof GraphQLNonNull
|
|
446
|
+
&& !(fieldArg.type instanceof GraphQLNonNull)) fieldArg.type = new GraphQLNonNull(fieldArg.type);
|
|
447
|
+
fieldArg.description = fieldEntry.description;
|
|
448
|
+
fieldArgForUpdate.description = fieldEntry.description;
|
|
449
|
+
|
|
450
|
+
if (fieldArg.type) {
|
|
451
|
+
fieldsArgs[fieldEntryName] = fieldArg;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (fieldArgForUpdate.type) {
|
|
455
|
+
fieldsArgForUpdate[fieldEntryName] = fieldArgForUpdate;
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
fieldEntry.extensions = { ...fieldEntry.extensions, stateMachine: true };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const inputTypeBody = {
|
|
464
|
+
name: `${gqltype.name}Input`,
|
|
465
|
+
description: gqltype.description,
|
|
466
|
+
fields: fieldsArgs,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const inputTypeBodyForUpdate = {
|
|
470
|
+
name: `${gqltype.name}InputForUpdate`,
|
|
471
|
+
description: gqltype.description,
|
|
472
|
+
fields: fieldsArgForUpdate,
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const inputTypeForAdd = new GraphQLInputObjectType(inputTypeBody);
|
|
476
|
+
const inputTypeForUpdate = new GraphQLInputObjectType(inputTypeBodyForUpdate);
|
|
477
|
+
|
|
478
|
+
const inputTypeForAddFields = inputTypeForAdd._fields();
|
|
479
|
+
|
|
480
|
+
Object.keys(selfReferenceCollections).forEach((fieldEntryName) => {
|
|
481
|
+
if (Object.prototype.hasOwnProperty.call(selfReferenceCollections, fieldEntryName)) {
|
|
482
|
+
const fieldEntry = selfReferenceCollections[fieldEntryName];
|
|
483
|
+
const listShape = getListShape(fieldEntry.type);
|
|
484
|
+
const oneToMany = createOneToManyInputType('A', fieldEntryName,
|
|
485
|
+
inputTypeForAdd, inputTypeForUpdate,
|
|
486
|
+
normalizeConnectionField(
|
|
487
|
+
gqltype,
|
|
488
|
+
fieldEntry.extensions?.relation?.connectionField,
|
|
489
|
+
), listShape.itemNonNull);
|
|
490
|
+
inputTypeForAddFields[fieldEntryName] = {
|
|
491
|
+
type: listShape.outerNonNull ? new GraphQLNonNull(oneToMany) : oneToMany,
|
|
492
|
+
name: fieldEntryName,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
inputTypeForAdd._fields = () => inputTypeForAddFields;
|
|
498
|
+
|
|
499
|
+
const inputTypeForUpdateFields = inputTypeForUpdate._fields();
|
|
500
|
+
|
|
501
|
+
Object.keys(selfReferenceCollections).forEach((fieldEntryName) => {
|
|
502
|
+
if (Object.prototype.hasOwnProperty.call(selfReferenceCollections, fieldEntryName)) {
|
|
503
|
+
const fieldEntry = selfReferenceCollections[fieldEntryName];
|
|
504
|
+
const listShape = getListShape(fieldEntry.type);
|
|
505
|
+
inputTypeForUpdateFields[fieldEntryName] = {
|
|
506
|
+
type: createOneToManyInputType('U', fieldEntryName,
|
|
507
|
+
inputTypeForAdd, inputTypeForUpdate,
|
|
508
|
+
normalizeConnectionField(
|
|
509
|
+
gqltype,
|
|
510
|
+
fieldEntry.extensions?.relation?.connectionField,
|
|
511
|
+
), listShape.itemNonNull),
|
|
512
|
+
name: fieldEntryName,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
inputTypeForUpdate._fields = () => inputTypeForUpdateFields;
|
|
518
|
+
|
|
519
|
+
return { inputTypeBody: inputTypeForAdd, inputTypeBodyForUpdate: inputTypeForUpdate };
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const getInputType = (type) => typesDict.types[type.name].inputType;
|
|
523
|
+
|
|
524
|
+
const buildPendingInputTypes = (waitingForInputType) => {
|
|
525
|
+
let pending = waitingForInputType;
|
|
526
|
+
let previousPendingCount = Object.keys(pending).length + 1;
|
|
527
|
+
|
|
528
|
+
while (Object.keys(pending).length > 0) {
|
|
529
|
+
const currentCount = Object.keys(pending).length;
|
|
530
|
+
if (currentCount >= previousPendingCount) {
|
|
531
|
+
const unresolved = Object.keys(pending).join(', ');
|
|
532
|
+
throw new SimfinityError(
|
|
533
|
+
`Could not build input types for: ${unresolved}. Check for circular or misconfigured relations.`,
|
|
534
|
+
'INPUT_TYPE_UNRESOLVED',
|
|
535
|
+
500,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
previousPendingCount = currentCount;
|
|
539
|
+
|
|
540
|
+
const stillWaiting = {};
|
|
541
|
+
for (const [key, value] of Object.entries(pending)) {
|
|
542
|
+
const { gqltype } = value;
|
|
543
|
+
if (typesDict.types[gqltype.name].inputType) continue;
|
|
544
|
+
|
|
545
|
+
const result = buildInputType(gqltype);
|
|
546
|
+
if (result && result.inputTypeBody && result.inputTypeBodyForUpdate) {
|
|
547
|
+
typesDict.types[gqltype.name].inputType = result.inputTypeBody;
|
|
548
|
+
typesDictForUpdate.types[gqltype.name].inputType = result.inputTypeBodyForUpdate;
|
|
549
|
+
} else {
|
|
550
|
+
stillWaiting[key] = value;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
pending = stillWaiting;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const materializeModel = async (args, gqltype, linkToParent, operation, session) => {
|
|
558
|
+
if (!args) {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const argTypes = gqltype.getFields();
|
|
563
|
+
|
|
564
|
+
const modelArgs = {};
|
|
565
|
+
const collectionFields = {};
|
|
566
|
+
|
|
567
|
+
for (const [fieldEntryName, fieldEntry] of Object.entries(argTypes)) {
|
|
568
|
+
if (fieldEntry.extensions && fieldEntry.extensions.validations
|
|
569
|
+
&& fieldEntry.extensions.validations[operation]) {
|
|
570
|
+
for (const validator of fieldEntry.extensions.validations[operation]) {
|
|
571
|
+
await validator.validate(gqltype.name, fieldEntryName, args[fieldEntryName], session);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (args[fieldEntryName] !== undefined && args[fieldEntryName] !== null) {
|
|
576
|
+
if (fieldEntry.type instanceof GraphQLScalarType
|
|
577
|
+
|| fieldEntry.type instanceof GraphQLEnumType
|
|
578
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLScalarType)
|
|
579
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLEnumType)) {
|
|
580
|
+
modelArgs[fieldEntryName] = args[fieldEntryName];
|
|
581
|
+
} else if (fieldEntry.type instanceof GraphQLObjectType
|
|
582
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLObjectType)) {
|
|
583
|
+
if (fieldEntry.extensions && fieldEntry.extensions.relation) {
|
|
584
|
+
if (!fieldEntry.extensions.relation.embedded) {
|
|
585
|
+
const connectionField = fieldEntry.extensions.relation.connectionField || fieldEntryName;
|
|
586
|
+
modelArgs[connectionField] = adapter.castId(args[fieldEntryName].id);
|
|
587
|
+
} else {
|
|
588
|
+
const fieldType = fieldEntry.type instanceof GraphQLNonNull
|
|
589
|
+
? fieldEntry.type.ofType : fieldEntry.type;
|
|
590
|
+
modelArgs[fieldEntryName] = (await materializeModel(args[fieldEntryName], fieldType,
|
|
591
|
+
null, operation, session)).modelArgs;
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
throw new SimfinityError(
|
|
595
|
+
`Field ${gqltype.name}.${fieldEntryName} is an object type but does not define extensions.relation`,
|
|
596
|
+
'MISSING_RELATION_EXTENSION',
|
|
597
|
+
500,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
} else if (getListShape(fieldEntry.type)) {
|
|
601
|
+
const { itemType } = getListShape(fieldEntry.type);
|
|
602
|
+
if (itemType instanceof GraphQLObjectType && fieldEntry.extensions
|
|
603
|
+
&& fieldEntry.extensions.relation) {
|
|
604
|
+
if (!fieldEntry.extensions.relation.embedded) {
|
|
605
|
+
collectionFields[fieldEntryName] = args[fieldEntryName];
|
|
606
|
+
} else if (fieldEntry.extensions.relation.embedded) {
|
|
607
|
+
const collectionEntries = [];
|
|
608
|
+
|
|
609
|
+
for (const element of args[fieldEntryName]) {
|
|
610
|
+
if (element === null) {
|
|
611
|
+
collectionEntries.push(null);
|
|
612
|
+
} else {
|
|
613
|
+
const collectionEntry = (await materializeModel(element, itemType,
|
|
614
|
+
null, operation, session)).modelArgs;
|
|
615
|
+
if (collectionEntry) collectionEntries.push(collectionEntry);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
modelArgs[fieldEntryName] = collectionEntries;
|
|
619
|
+
}
|
|
620
|
+
} else if (itemType instanceof GraphQLScalarType || itemType instanceof GraphQLEnumType) {
|
|
621
|
+
modelArgs[fieldEntryName] = args[fieldEntryName];
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (linkToParent) {
|
|
628
|
+
linkToParent(modelArgs);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (gqltype.extensions && gqltype.extensions.validations
|
|
632
|
+
&& gqltype.extensions.validations[operation]) {
|
|
633
|
+
for (const validator of gqltype.extensions.validations[operation]) {
|
|
634
|
+
await validator.validate(gqltype.name, args, modelArgs, session);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return { modelArgs, collectionFields };
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
const executeRegisteredMutation = (args, callback, context, session, inputModel) => {
|
|
642
|
+
const inputSnapshot = cloneInput(args, inputModel);
|
|
643
|
+
return adapter.withTransaction(
|
|
644
|
+
session,
|
|
645
|
+
(mySession) => callback(cloneInput(inputSnapshot, inputModel), mySession, context),
|
|
646
|
+
);
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const iterateOnCollectionFields = async (materializedModel, gqltype, objectId, session, context) => {
|
|
650
|
+
for (const [collectionFieldKey, collectionField] of Object.entries(materializedModel.collectionFields)) {
|
|
651
|
+
if (collectionField.added) {
|
|
652
|
+
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
653
|
+
collectionField.added, operations.SAVE, context);
|
|
654
|
+
}
|
|
655
|
+
if (collectionField.updated) {
|
|
656
|
+
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
657
|
+
collectionField.updated, operations.UPDATE, context);
|
|
658
|
+
}
|
|
659
|
+
if (collectionField.deleted) {
|
|
660
|
+
await executeItemFunction(gqltype, collectionFieldKey, objectId, session,
|
|
661
|
+
collectionField.deleted, operations.DELETE, context);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const onDelete = async (Model, controller, id, session, context) => {
|
|
667
|
+
const currentObject = await adapter.getById(Model, id, session, { plain: true });
|
|
668
|
+
|
|
669
|
+
if (controller && controller.onDelete) {
|
|
670
|
+
await controller.onDelete(currentObject, session, context);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return adapter.delete(Model, id, session);
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const getEmbeddedFieldNames = (gqltype) => {
|
|
677
|
+
const cached = typesDict.types[gqltype.name];
|
|
678
|
+
if (cached && cached.embeddedFieldNames) return cached.embeddedFieldNames;
|
|
679
|
+
const names = [];
|
|
680
|
+
for (const [fieldName, fieldEntry] of Object.entries(gqltype.getFields())) {
|
|
681
|
+
if (fieldEntry.extensions?.relation?.embedded) names.push(fieldName);
|
|
682
|
+
}
|
|
683
|
+
if (cached) cached.embeddedFieldNames = names;
|
|
684
|
+
return names;
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
const onUpdateSubject = async (Model, gqltype, controller, args, session, linkToParent, context) => {
|
|
688
|
+
const materializedModel = await materializeModel(args, gqltype, linkToParent, 'UPDATE', session);
|
|
689
|
+
const objectId = args.id;
|
|
690
|
+
const argTypes = gqltype.getFields();
|
|
691
|
+
const embeddedFieldNames = getEmbeddedFieldNames(gqltype);
|
|
692
|
+
|
|
693
|
+
if (embeddedFieldNames.length > 0) {
|
|
694
|
+
const projection = Object.fromEntries(embeddedFieldNames.map((name) => [name, 1]));
|
|
695
|
+
const currentObject = await adapter.getById(Model, objectId, session, {
|
|
696
|
+
projection,
|
|
697
|
+
plain: true,
|
|
698
|
+
lock: true,
|
|
699
|
+
});
|
|
700
|
+
if (currentObject) {
|
|
701
|
+
for (const fieldEntryName of embeddedFieldNames) {
|
|
702
|
+
const oldObjectData = currentObject[fieldEntryName];
|
|
703
|
+
const newObjectData = materializedModel.modelArgs[fieldEntryName];
|
|
704
|
+
if (newObjectData) {
|
|
705
|
+
if (Array.isArray(newObjectData)) {
|
|
706
|
+
materializedModel.modelArgs[fieldEntryName] = newObjectData;
|
|
707
|
+
} else {
|
|
708
|
+
materializedModel.modelArgs[fieldEntryName] = { ...oldObjectData, ...newObjectData };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
for (const [fieldEntryName, fieldEntry] of Object.entries(argTypes)) {
|
|
716
|
+
if (args[fieldEntryName] === null && !(fieldEntry.type instanceof GraphQLNonNull)) {
|
|
717
|
+
const relation = fieldEntry.extensions?.relation;
|
|
718
|
+
const storageFieldName = relation && !relation.embedded && !(unwrapNonNull(fieldEntry.type) instanceof GraphQLList)
|
|
719
|
+
? relation.connectionField || fieldEntryName
|
|
720
|
+
: fieldEntryName;
|
|
721
|
+
materializedModel.modelArgs.$unset = {
|
|
722
|
+
...materializedModel.modelArgs.$unset,
|
|
723
|
+
[storageFieldName]: '',
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const unset = materializedModel.modelArgs.$unset || {};
|
|
729
|
+
const set = { ...materializedModel.modelArgs };
|
|
730
|
+
delete set.$unset;
|
|
731
|
+
const update = adapter.prepareUpdate(set, unset);
|
|
732
|
+
|
|
733
|
+
if (controller && controller.onUpdating) {
|
|
734
|
+
await controller.onUpdating(objectId, update, session, context);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (linkToParent) linkToParent(update);
|
|
738
|
+
const result = await adapter.update(Model, objectId, update, session);
|
|
739
|
+
if (!result) throw new SimfinityError(`${gqltype.name} ${objectId} is not valid`, 'NOT_VALID_ID', 404);
|
|
740
|
+
|
|
741
|
+
if (materializedModel.collectionFields) {
|
|
742
|
+
await iterateOnCollectionFields(materializedModel, gqltype, objectId, session, context);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (controller && controller.onUpdated) {
|
|
746
|
+
await controller.onUpdated(result, session, context);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return result;
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
const onStateChanged = async (Model, gqltype, controller, args, session, actionField, context) => {
|
|
753
|
+
const storedModel = await adapter.getById(Model, args.id, session, { lock: true });
|
|
754
|
+
if (!storedModel) {
|
|
755
|
+
throw new SimfinityError(`${gqltype.name} ${args.id} is not valid`, 'NOT_VALID_ID', 404);
|
|
756
|
+
}
|
|
757
|
+
if (storedModel.state === stateValue(actionField.from)) {
|
|
758
|
+
if (actionField.action) {
|
|
759
|
+
await actionField.action(args, session);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
args.state = stateValue(actionField.to);
|
|
763
|
+
let result = await onUpdateSubject(Model, gqltype, controller, args, session, null, context);
|
|
764
|
+
result = adapter.toObject(result);
|
|
765
|
+
result.state = actionField.to.value;
|
|
766
|
+
return result;
|
|
767
|
+
}
|
|
768
|
+
throw new SimfinityError(`Action is not allowed from state ${storedModel.state}`, 'BAD_REQUEST', 400);
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
const onSaveObject = async (Model, gqltype, controller, args, session, linkToParent, context) => {
|
|
772
|
+
const materializedModel = await materializeModel(args, gqltype, linkToParent, 'CREATE', session);
|
|
773
|
+
if (typesDict.types[gqltype.name].stateMachine) {
|
|
774
|
+
materializedModel.modelArgs.state = stateValue(
|
|
775
|
+
typesDict.types[gqltype.name].stateMachine.initialState,
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const newObject = adapter.newRecord(Model, materializedModel.modelArgs, session);
|
|
780
|
+
|
|
781
|
+
if (controller && controller.onSaving) {
|
|
782
|
+
await controller.onSaving(newObject, args, session, context);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (linkToParent) linkToParent(newObject);
|
|
786
|
+
let result = await adapter.saveRecord(Model, newObject, session);
|
|
787
|
+
result = adapter.toObject(result);
|
|
788
|
+
|
|
789
|
+
if (materializedModel.collectionFields) {
|
|
790
|
+
await iterateOnCollectionFields(materializedModel, gqltype, newObject._id, session, context);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (controller && controller.onSaved) {
|
|
794
|
+
await controller.onSaved(result, args, session, context);
|
|
795
|
+
}
|
|
796
|
+
if (typesDict.types[gqltype.name].stateMachine) {
|
|
797
|
+
result.state = typesDict.types[gqltype.name].stateMachine.initialState.value;
|
|
798
|
+
}
|
|
799
|
+
return result;
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
const saveObject = async (typeName, args, session, context) => {
|
|
803
|
+
const type = typesDict.types[typeName];
|
|
804
|
+
const snapshot = cloneInput(args, type.inputType);
|
|
805
|
+
return adapter.withTransaction(session, (transaction) => onSaveObject(
|
|
806
|
+
type.model, type.gqltype, type.controller, cloneInput(snapshot, type.inputType), transaction, null, context,
|
|
807
|
+
), type.model);
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
const executeOperation = (Model, gqltype, controller, args, operation, actionField,
|
|
811
|
+
session, context, inputType) => {
|
|
812
|
+
const inputSnapshot = operation === operations.DELETE ? args : cloneInput(args, inputType);
|
|
813
|
+
return adapter.withTransaction(
|
|
814
|
+
session,
|
|
815
|
+
async (mySession) => {
|
|
816
|
+
const attemptArgs = operation === operations.DELETE
|
|
817
|
+
? inputSnapshot
|
|
818
|
+
: cloneInput(inputSnapshot, inputType);
|
|
819
|
+
switch (operation) {
|
|
820
|
+
case operations.SAVE:
|
|
821
|
+
return onSaveObject(Model, gqltype, controller, attemptArgs, mySession, null, context);
|
|
822
|
+
case operations.UPDATE:
|
|
823
|
+
return onUpdateSubject(Model, gqltype, controller, attemptArgs, mySession, null, context);
|
|
824
|
+
case operations.DELETE:
|
|
825
|
+
return onDelete(Model, controller, attemptArgs, mySession, context);
|
|
826
|
+
case operations.STATE_CHANGED:
|
|
827
|
+
return onStateChanged(Model, gqltype, controller,
|
|
828
|
+
attemptArgs, mySession, actionField, context);
|
|
829
|
+
default:
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
Model,
|
|
834
|
+
);
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
const executeItemFunction = async (gqltype, collectionField, objectId, session,
|
|
838
|
+
collectionFieldsList, operationType, context) => {
|
|
839
|
+
const argTypes = gqltype.getFields();
|
|
840
|
+
const collectionGQLType = unwrapListAndNonNull(argTypes[collectionField].type);
|
|
841
|
+
const connection = normalizeConnectionField(
|
|
842
|
+
collectionGQLType,
|
|
843
|
+
argTypes[collectionField].extensions.relation.connectionField,
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
const type = operationType === operations.UPDATE
|
|
847
|
+
? typesDictForUpdate.types[collectionGQLType.name] : typesDict.types[collectionGQLType.name];
|
|
848
|
+
const linkToParent = (item) => {
|
|
849
|
+
item[connection.storageFieldName] = objectId;
|
|
850
|
+
if (item.$unset) delete item.$unset[connection.storageFieldName];
|
|
851
|
+
if (item.$set) delete item.$set[connection.storageFieldName];
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
for (const element of collectionFieldsList) {
|
|
855
|
+
if (element === null) continue;
|
|
856
|
+
const args = operationType === operations.DELETE ? { id: element } : { input: element };
|
|
857
|
+
await executeMiddleware({ type, args, operation: operationType, context });
|
|
858
|
+
|
|
859
|
+
if (operationType === operations.UPDATE || operationType === operations.DELETE) {
|
|
860
|
+
const id = operationType === operations.DELETE ? args.id : args.input.id;
|
|
861
|
+
const child = await adapter.getById(type.model, id, session, { plain: true, lock: true });
|
|
862
|
+
if (!child) {
|
|
863
|
+
throw new SimfinityError(`${collectionGQLType.name} ${id} is not valid`, 'NOT_VALID_ID', 404);
|
|
864
|
+
}
|
|
865
|
+
if (String(child[connection.storageFieldName]) !== String(objectId)) {
|
|
866
|
+
throw new SimfinityError('Child does not belong to this parent', 'FORBIDDEN', 403);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
switch (operationType) {
|
|
871
|
+
case operations.SAVE:
|
|
872
|
+
await onSaveObject(type.model, collectionGQLType, type.controller,
|
|
873
|
+
args.input, session, linkToParent, context);
|
|
874
|
+
break;
|
|
875
|
+
case operations.UPDATE:
|
|
876
|
+
await onUpdateSubject(type.model, collectionGQLType, type.controller,
|
|
877
|
+
args.input, session, linkToParent, context);
|
|
878
|
+
break;
|
|
879
|
+
case operations.DELETE:
|
|
880
|
+
await onDelete(type.model, type.controller, args.id, session, context);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
const shouldNotBeIncludedInSchema = (includedTypes,
|
|
886
|
+
type) => includedTypes && !includedTypes.includes(type);
|
|
887
|
+
|
|
888
|
+
const executeMiddleware = async (context) => {
|
|
889
|
+
const buildNext = (middlewaresParam) => {
|
|
890
|
+
if (!middlewaresParam) {
|
|
891
|
+
return async () => {};
|
|
892
|
+
}
|
|
893
|
+
return async () => {
|
|
894
|
+
const middleware = middlewaresParam[0];
|
|
895
|
+
if (middleware) {
|
|
896
|
+
await middleware(context, buildNext(middlewaresParam.slice(1)));
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
await buildNext(middlewares)();
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
const executeScope = async (params) => {
|
|
905
|
+
const { type, args, operation, context } = params;
|
|
906
|
+
|
|
907
|
+
if (!type || !type.gqltype || !type.gqltype.extensions) {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const extensions = type.gqltype.extensions;
|
|
912
|
+
if (!extensions.scope || !extensions.scope[operation]) {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const scopeFunction = extensions.scope[operation];
|
|
917
|
+
if (typeof scopeFunction !== 'function') {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
return scopeFunction({ type, args, operation, context });
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
const resolveById = async (type, args, context, requiredId) => {
|
|
925
|
+
await executeMiddleware({ type, args, operation: 'get_by_id', context });
|
|
926
|
+
if (!type.gqltype.extensions?.scope?.get_by_id) {
|
|
927
|
+
return adapter.getById(type.model, args.id, null, { requiredId, context });
|
|
928
|
+
}
|
|
929
|
+
const queryArgs = { id: { operator: 'EQ', value: args.id } };
|
|
930
|
+
await executeScope({ type, args: queryArgs, operation: 'get_by_id', context });
|
|
931
|
+
const results = await adapter.find(type.model, type.gqltype, queryArgs, null, { requiredId, context });
|
|
932
|
+
return results[0] || null;
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
const buildMutation = (name, includedMutationTypes, includedCustomMutations) => {
|
|
936
|
+
const rootQueryArgs = {};
|
|
937
|
+
rootQueryArgs.name = name;
|
|
938
|
+
rootQueryArgs.fields = {};
|
|
939
|
+
|
|
940
|
+
buildPendingInputTypes(waitingInputType);
|
|
941
|
+
|
|
942
|
+
for (const type of Object.values(typesDict.types)) {
|
|
943
|
+
if (!shouldNotBeIncludedInSchema(includedMutationTypes, type.gqltype)) {
|
|
944
|
+
if (type.endpoint) {
|
|
945
|
+
const argsObject = { input: { type: new GraphQLNonNull(type.inputType) } };
|
|
946
|
+
|
|
947
|
+
rootQueryArgs.fields[`add${type.simpleEntityEndpointName}`] = {
|
|
948
|
+
type: type.gqltype,
|
|
949
|
+
description: 'add',
|
|
950
|
+
args: argsObject,
|
|
951
|
+
async resolve(parent, args, context) {
|
|
952
|
+
const params = {
|
|
953
|
+
type,
|
|
954
|
+
args,
|
|
955
|
+
operation: operations.SAVE,
|
|
956
|
+
context,
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
await executeMiddleware(params);
|
|
960
|
+
return executeOperation(type.model, type.gqltype, type.controller,
|
|
961
|
+
args.input, operations.SAVE, null, null, context, type.inputType);
|
|
962
|
+
},
|
|
963
|
+
};
|
|
964
|
+
rootQueryArgs.fields[`delete${type.simpleEntityEndpointName}`] = {
|
|
965
|
+
type: type.gqltype,
|
|
966
|
+
description: 'delete',
|
|
967
|
+
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
|
|
968
|
+
async resolve(parent, args, context) {
|
|
969
|
+
const params = {
|
|
970
|
+
type,
|
|
971
|
+
args,
|
|
972
|
+
operation: operations.DELETE,
|
|
973
|
+
context,
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
await executeMiddleware(params);
|
|
977
|
+
return executeOperation(type.model, type.gqltype, type.controller,
|
|
978
|
+
args.id, operations.DELETE, null, null, context, null);
|
|
979
|
+
},
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
for (const type of Object.values(typesDictForUpdate.types)) {
|
|
986
|
+
if (!shouldNotBeIncludedInSchema(includedMutationTypes, type.gqltype)) {
|
|
987
|
+
if (type.endpoint) {
|
|
988
|
+
const argsObject = { input: { type: new GraphQLNonNull(type.inputType) } };
|
|
989
|
+
rootQueryArgs.fields[`update${type.simpleEntityEndpointName}`] = {
|
|
990
|
+
type: type.gqltype,
|
|
991
|
+
description: 'update',
|
|
992
|
+
args: argsObject,
|
|
993
|
+
async resolve(parent, args, context) {
|
|
994
|
+
const params = {
|
|
995
|
+
type,
|
|
996
|
+
args,
|
|
997
|
+
operation: operations.UPDATE,
|
|
998
|
+
context,
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
await executeMiddleware(params);
|
|
1002
|
+
return executeOperation(type.model, type.gqltype, type.controller,
|
|
1003
|
+
args.input, operations.UPDATE, null, null, context, type.inputType);
|
|
1004
|
+
},
|
|
1005
|
+
};
|
|
1006
|
+
if (type.stateMachine) {
|
|
1007
|
+
for (const [actionName, actionField] of Object.entries(type.stateMachine.actions)) {
|
|
1008
|
+
if ({}.hasOwnProperty.call(type.stateMachine.actions, actionName)) {
|
|
1009
|
+
rootQueryArgs.fields[`${actionName}_${type.simpleEntityEndpointName}`] = {
|
|
1010
|
+
type: type.gqltype,
|
|
1011
|
+
description: actionField.description,
|
|
1012
|
+
args: argsObject,
|
|
1013
|
+
async resolve(parent, args, context) {
|
|
1014
|
+
const params = {
|
|
1015
|
+
type,
|
|
1016
|
+
args,
|
|
1017
|
+
operation: operations.STATE_CHANGED,
|
|
1018
|
+
actionName,
|
|
1019
|
+
actionField,
|
|
1020
|
+
context,
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
await executeMiddleware(params);
|
|
1024
|
+
return executeOperation(type.model, type.gqltype, type.controller,
|
|
1025
|
+
args.input, operations.STATE_CHANGED, actionField,
|
|
1026
|
+
null, context, type.inputType);
|
|
1027
|
+
},
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
for (const [entry, registeredMutation] of Object.entries(registeredMutations)) {
|
|
1037
|
+
if (!shouldNotBeIncludedInSchema(includedCustomMutations, entry)) {
|
|
1038
|
+
const argsObject = registeredMutation.inputModel
|
|
1039
|
+
? { input: { type: new GraphQLNonNull(registeredMutation.inputModel) } } : null;
|
|
1040
|
+
rootQueryArgs.fields[entry] = {
|
|
1041
|
+
type: registeredMutation.outputModel,
|
|
1042
|
+
description: registeredMutation.description,
|
|
1043
|
+
args: argsObject,
|
|
1044
|
+
async resolve(parent, args, context) {
|
|
1045
|
+
const params = {
|
|
1046
|
+
args,
|
|
1047
|
+
operation: operations.CUSTOM_MUTATION,
|
|
1048
|
+
entry,
|
|
1049
|
+
context,
|
|
1050
|
+
};
|
|
1051
|
+
await executeMiddleware(params);
|
|
1052
|
+
return executeRegisteredMutation(args.input, registeredMutation.callback,
|
|
1053
|
+
context, null, registeredMutation.inputModel);
|
|
1054
|
+
},
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
return new GraphQLObjectType(rootQueryArgs);
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
const buildRootQuery = (name, includedTypes) => {
|
|
1063
|
+
const rootQueryArgs = {};
|
|
1064
|
+
rootQueryArgs.name = name;
|
|
1065
|
+
rootQueryArgs.fields = {};
|
|
1066
|
+
|
|
1067
|
+
for (const type of Object.values(typesDict.types)) {
|
|
1068
|
+
if (!shouldNotBeIncludedInSchema(includedTypes, type.gqltype)) {
|
|
1069
|
+
const wasAddedAsNoEnpointType = !type.simpleEntityEndpointName;
|
|
1070
|
+
if (!wasAddedAsNoEnpointType) {
|
|
1071
|
+
if (type.gqltype.getFields().id && !type.gqltype.getFields().id.resolve) {
|
|
1072
|
+
type.gqltype.getFields().id.resolve = (parent) => parent._id;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
rootQueryArgs.fields[type.simpleEntityEndpointName] = {
|
|
1076
|
+
type: type.gqltype,
|
|
1077
|
+
args: { id: { type: GraphQLID } },
|
|
1078
|
+
async resolve(parent, args, context) {
|
|
1079
|
+
return resolveById(type, args, context);
|
|
1080
|
+
},
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
const argTypes = type.gqltype.getFields();
|
|
1084
|
+
|
|
1085
|
+
const argsObject = createArgsForQuery(argTypes);
|
|
1086
|
+
|
|
1087
|
+
rootQueryArgs.fields[type.listEntitiesEndpointName] = {
|
|
1088
|
+
type: new GraphQLList(type.gqltype),
|
|
1089
|
+
args: argsObject,
|
|
1090
|
+
async resolve(parent, args, context) {
|
|
1091
|
+
const params = {
|
|
1092
|
+
type,
|
|
1093
|
+
args,
|
|
1094
|
+
operation: 'find',
|
|
1095
|
+
context,
|
|
1096
|
+
};
|
|
1097
|
+
await executeMiddleware(params);
|
|
1098
|
+
await executeScope(params);
|
|
1099
|
+
const wantsCount = !!(args.pagination && args.pagination.count);
|
|
1100
|
+
|
|
1101
|
+
const dataPromise = adapter.find(type.model, type.gqltype, args, null);
|
|
1102
|
+
const countPromise = wantsCount
|
|
1103
|
+
? adapter.count(type.model, type.gqltype, args, null)
|
|
1104
|
+
: null;
|
|
1105
|
+
|
|
1106
|
+
const [result, resultCount] = await Promise.all([dataPromise, countPromise]);
|
|
1107
|
+
if (wantsCount) {
|
|
1108
|
+
context.count = resultCount;
|
|
1109
|
+
}
|
|
1110
|
+
return result;
|
|
1111
|
+
},
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
const aggregateArgsObject = { ...argsObject };
|
|
1115
|
+
aggregateArgsObject.aggregation = {
|
|
1116
|
+
type: new GraphQLNonNull(QLTypeAggregationExpression),
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
rootQueryArgs.fields[`${type.listEntitiesEndpointName}_aggregate`] = {
|
|
1120
|
+
type: new GraphQLList(QLTypeAggregationResult),
|
|
1121
|
+
args: aggregateArgsObject,
|
|
1122
|
+
async resolve(parent, args, context) {
|
|
1123
|
+
const params = {
|
|
1124
|
+
type,
|
|
1125
|
+
args,
|
|
1126
|
+
operation: 'aggregate',
|
|
1127
|
+
context,
|
|
1128
|
+
};
|
|
1129
|
+
await executeMiddleware(params);
|
|
1130
|
+
await executeScope(params);
|
|
1131
|
+
return adapter.aggregate(type.model, type.gqltype, args, null);
|
|
1132
|
+
},
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return new GraphQLObjectType(rootQueryArgs);
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
const markReferencedTypesForModelGeneration = () => {
|
|
1142
|
+
Object.values(typesDict.types).forEach((typeInfo) => {
|
|
1143
|
+
Object.values(typeInfo.gqltype.getFields()).forEach((fieldEntry) => {
|
|
1144
|
+
const relation = fieldEntry.extensions?.relation;
|
|
1145
|
+
if (!relation || relation.embedded) return;
|
|
1146
|
+
|
|
1147
|
+
const relatedType = unwrapListAndNonNull(fieldEntry.type);
|
|
1148
|
+
const relatedTypeInfo = typesDict.types[relatedType.name];
|
|
1149
|
+
if (relatedTypeInfo && !relatedTypeInfo.endpoint) {
|
|
1150
|
+
relatedTypeInfo.needsModel = true;
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
const getRegistrations = () => Object.values(typesDict.types);
|
|
1157
|
+
|
|
1158
|
+
const createSchema = (includedQueryTypes, includedMutationTypes, includedCustomMutations) => {
|
|
1159
|
+
markReferencedTypesForModelGeneration();
|
|
1160
|
+
if (adapter.prepare) {
|
|
1161
|
+
adapter.prepare(getRegistrations(), { createCollection: !preventCollectionCreation });
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
Object.values(typesDict.types).forEach((typeInfo) => {
|
|
1165
|
+
if (typeInfo.gqltype && !typeInfo.model) {
|
|
1166
|
+
if (typeInfo.endpoint) {
|
|
1167
|
+
typeInfo.model = adapter.createModel(typeInfo.gqltype, typeInfo.onModelCreated, {
|
|
1168
|
+
createCollection: !preventCollectionCreation,
|
|
1169
|
+
});
|
|
1170
|
+
} else if (typeInfo.needsModel) {
|
|
1171
|
+
typeInfo.model = adapter.createModel(typeInfo.gqltype, null, { createCollection: false });
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
Object.keys(typesDict.types).forEach((typeName) => {
|
|
1177
|
+
if (typesDictForUpdate.types[typeName]) {
|
|
1178
|
+
typesDictForUpdate.types[typeName].model = typesDict.types[typeName].model;
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
Object.values(typesDict.types).forEach((typeInfo) => {
|
|
1183
|
+
if (typeInfo.gqltype) {
|
|
1184
|
+
autoGenerateResolvers(typeInfo.gqltype);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
return new GraphQLSchema({
|
|
1189
|
+
query: buildRootQuery('RootQueryType', includedQueryTypes),
|
|
1190
|
+
mutation: buildMutation('Mutation', includedMutationTypes, includedCustomMutations),
|
|
1191
|
+
});
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
const getModel = (gqltype) => typesDict.types[gqltype.name]?.model;
|
|
1195
|
+
|
|
1196
|
+
const getType = (typeName) => {
|
|
1197
|
+
if (typeof typeName === 'string') {
|
|
1198
|
+
return typesDict.types[typeName]?.gqltype;
|
|
1199
|
+
}
|
|
1200
|
+
if (typeName && typeName.name) {
|
|
1201
|
+
return typesDict.types[typeName.name]?.gqltype;
|
|
1202
|
+
}
|
|
1203
|
+
return null;
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
const registerMutation = (name, description, inputModel, outputModel, callback) => {
|
|
1207
|
+
registeredMutations[name] = {
|
|
1208
|
+
description,
|
|
1209
|
+
inputModel,
|
|
1210
|
+
outputModel,
|
|
1211
|
+
callback,
|
|
1212
|
+
};
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
const autoGenerateResolvers = (gqltype) => {
|
|
1216
|
+
const fields = gqltype.getFields();
|
|
1217
|
+
|
|
1218
|
+
for (const [fieldName, fieldEntry] of Object.entries(fields)) {
|
|
1219
|
+
if (fieldEntry.resolve) continue;
|
|
1220
|
+
const relation = fieldEntry.extensions?.relation;
|
|
1221
|
+
if (!relation || relation.embedded) continue;
|
|
1222
|
+
|
|
1223
|
+
const listShape = getListShape(fieldEntry.type);
|
|
1224
|
+
if (listShape) {
|
|
1225
|
+
const relatedType = listShape.itemType;
|
|
1226
|
+
const connection = normalizeConnectionField(
|
|
1227
|
+
relatedType,
|
|
1228
|
+
relation.connectionField || fieldName,
|
|
1229
|
+
);
|
|
1230
|
+
const relatedTypeInfo = typesDict.types[relatedType.name];
|
|
1231
|
+
const argsObject = createArgsForQuery(relatedTypeInfo.gqltype.getFields());
|
|
1232
|
+
if (connection.graphqlFieldName) delete argsObject[connection.graphqlFieldName];
|
|
1233
|
+
|
|
1234
|
+
fieldEntry.args = formatArgs(Object.entries(argsObject));
|
|
1235
|
+
fieldEntry.resolve = async (parent, args, context) => {
|
|
1236
|
+
if (!relatedTypeInfo || !relatedTypeInfo.model) {
|
|
1237
|
+
throw new Error(`Related type ${relatedType.name} not found or not connected. Make sure it's connected with simfinity.connect() or simfinity.addNoEndpointType().`);
|
|
1238
|
+
}
|
|
1239
|
+
const params = { type: relatedTypeInfo, args, operation: 'find', context };
|
|
1240
|
+
await executeMiddleware(params);
|
|
1241
|
+
await executeScope(params);
|
|
1242
|
+
return adapter.findChildren(
|
|
1243
|
+
relatedTypeInfo.model,
|
|
1244
|
+
relatedTypeInfo.gqltype,
|
|
1245
|
+
connection.storageFieldName,
|
|
1246
|
+
parent.id || parent._id,
|
|
1247
|
+
args,
|
|
1248
|
+
null,
|
|
1249
|
+
);
|
|
1250
|
+
};
|
|
1251
|
+
} else if (fieldEntry.type instanceof GraphQLObjectType
|
|
1252
|
+
|| (fieldEntry.type instanceof GraphQLNonNull && fieldEntry.type.ofType instanceof GraphQLObjectType)) {
|
|
1253
|
+
const relatedType = unwrapNonNull(fieldEntry.type);
|
|
1254
|
+
const connectionField = relation.connectionField || fieldName;
|
|
1255
|
+
|
|
1256
|
+
fieldEntry.resolve = async (parent, args, context) => {
|
|
1257
|
+
const relatedTypeInfo = typesDict.types[relatedType.name];
|
|
1258
|
+
if (!relatedTypeInfo || !relatedTypeInfo.model) {
|
|
1259
|
+
throw new Error(`Related type ${relatedType.name} not found or not connected. Make sure it's connected with simfinity.connect() or simfinity.addNoEndpointType().`);
|
|
1260
|
+
}
|
|
1261
|
+
const relatedId = parent[connectionField] || parent[fieldName];
|
|
1262
|
+
const id = relatedId?._id || relatedId;
|
|
1263
|
+
return id ? resolveById(relatedTypeInfo, { id: String(id) }, context, id) : null;
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
const connect = (model, gqltype, simpleEntityEndpointName,
|
|
1270
|
+
listEntitiesEndpointName, controller, onModelCreated, stateMachine) => {
|
|
1271
|
+
const registration = {
|
|
1272
|
+
model,
|
|
1273
|
+
gqltype,
|
|
1274
|
+
simpleEntityEndpointName,
|
|
1275
|
+
listEntitiesEndpointName,
|
|
1276
|
+
endpoint: true,
|
|
1277
|
+
controller,
|
|
1278
|
+
stateMachine,
|
|
1279
|
+
onModelCreated,
|
|
1280
|
+
};
|
|
1281
|
+
if (adapter.validateRegistration) adapter.validateRegistration(registration);
|
|
1282
|
+
|
|
1283
|
+
waitingInputType[gqltype.name] = {
|
|
1284
|
+
model,
|
|
1285
|
+
gqltype,
|
|
1286
|
+
};
|
|
1287
|
+
typesDict.types[gqltype.name] = registration;
|
|
1288
|
+
|
|
1289
|
+
typesDictForUpdate.types[gqltype.name] = { ...typesDict.types[gqltype.name] };
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
const addNoEndpointType = (gqltype) => {
|
|
1293
|
+
const fields = gqltype.getFields();
|
|
1294
|
+
let needsModel = false;
|
|
1295
|
+
for (const fieldEntry of Object.values(fields)) {
|
|
1296
|
+
if (fieldEntry.extensions?.relation
|
|
1297
|
+
&& (fieldEntry.type instanceof GraphQLObjectType
|
|
1298
|
+
|| getListShape(fieldEntry.type)
|
|
1299
|
+
|| (fieldEntry.type instanceof GraphQLNonNull && fieldEntry.type.ofType instanceof GraphQLObjectType))) {
|
|
1300
|
+
needsModel = true;
|
|
1301
|
+
break;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
const registration = {
|
|
1306
|
+
gqltype, endpoint: false, model: null, needsModel,
|
|
1307
|
+
};
|
|
1308
|
+
if (adapter.validateRegistration) adapter.validateRegistration(registration);
|
|
1309
|
+
|
|
1310
|
+
waitingInputType[gqltype.name] = { gqltype };
|
|
1311
|
+
typesDict.types[gqltype.name] = registration;
|
|
1312
|
+
typesDictForUpdate.types[gqltype.name] = { ...typesDict.types[gqltype.name] };
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
const createArgsForQuery = (argTypes) => {
|
|
1316
|
+
const argsObject = {};
|
|
1317
|
+
|
|
1318
|
+
for (const [fieldEntryName, fieldEntry] of Object.entries(argTypes)) {
|
|
1319
|
+
argsObject[fieldEntryName] = {};
|
|
1320
|
+
|
|
1321
|
+
if (fieldEntry.type instanceof GraphQLScalarType
|
|
1322
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLScalarType)
|
|
1323
|
+
|| fieldEntry.type instanceof GraphQLEnumType
|
|
1324
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLEnumType)) {
|
|
1325
|
+
argsObject[fieldEntryName].type = QLFilter;
|
|
1326
|
+
} else if (fieldEntry.type instanceof GraphQLObjectType
|
|
1327
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLObjectType)) {
|
|
1328
|
+
argsObject[fieldEntryName].type = QLTypeFilterExpression;
|
|
1329
|
+
} else if (getListShape(fieldEntry.type)) {
|
|
1330
|
+
const listOfType = getListShape(fieldEntry.type).itemType;
|
|
1331
|
+
if (listOfType instanceof GraphQLScalarType
|
|
1332
|
+
|| isNonNullOfType(listOfType, GraphQLScalarType)
|
|
1333
|
+
|| listOfType instanceof GraphQLEnumType
|
|
1334
|
+
|| isNonNullOfType(listOfType, GraphQLEnumType)) {
|
|
1335
|
+
argsObject[fieldEntryName].type = QLFilter;
|
|
1336
|
+
} else {
|
|
1337
|
+
argsObject[fieldEntryName].type = QLTypeFilterExpression;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
argsObject.pagination = {};
|
|
1343
|
+
argsObject.pagination.type = QLPagination;
|
|
1344
|
+
|
|
1345
|
+
argsObject.sort = {};
|
|
1346
|
+
argsObject.sort.type = QLSortExpression;
|
|
1347
|
+
|
|
1348
|
+
argsObject.AND = {};
|
|
1349
|
+
argsObject.AND.type = new GraphQLList(QLFilterGroup);
|
|
1350
|
+
|
|
1351
|
+
argsObject.OR = {};
|
|
1352
|
+
argsObject.OR.type = new GraphQLList(QLFilterGroup);
|
|
1353
|
+
|
|
1354
|
+
return argsObject;
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
function formatArgs(argsArray) {
|
|
1358
|
+
const graphqlArgs = [];
|
|
1359
|
+
for (const [key, value] of argsArray) {
|
|
1360
|
+
const item = {
|
|
1361
|
+
name: key,
|
|
1362
|
+
type: value.type,
|
|
1363
|
+
};
|
|
1364
|
+
graphqlArgs.push(item);
|
|
1365
|
+
}
|
|
1366
|
+
return graphqlArgs;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
if (adapter.bind) {
|
|
1370
|
+
adapter.bind({ getModel, getType, getRegistrations });
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
return {
|
|
1374
|
+
configureQueryLimits,
|
|
1375
|
+
connect,
|
|
1376
|
+
addNoEndpointType,
|
|
1377
|
+
createSchema,
|
|
1378
|
+
getModel,
|
|
1379
|
+
getType,
|
|
1380
|
+
getInputType,
|
|
1381
|
+
getRegistrations,
|
|
1382
|
+
use,
|
|
1383
|
+
registerMutation,
|
|
1384
|
+
saveObject,
|
|
1385
|
+
preventCreatingCollection,
|
|
1386
|
+
};
|
|
1387
|
+
};
|