@coderich/autograph 0.9.16 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/index.js +2 -6
- package/package.json +10 -10
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +35 -58
- package/src/core/Schema.js +5 -38
- package/src/core/ServerResolver.js +7 -93
- package/src/data/DataLoader.js +68 -27
- package/src/data/DataService.js +59 -58
- package/src/data/Field.js +71 -96
- package/src/data/Model.js +95 -113
- package/src/data/Pipeline.js +174 -0
- package/src/data/Type.js +19 -60
- package/src/driver/MongoDriver.js +53 -28
- package/src/graphql/ast/Field.js +44 -26
- package/src/graphql/ast/Model.js +5 -16
- package/src/graphql/ast/Node.js +0 -32
- package/src/graphql/ast/Schema.js +109 -112
- package/src/graphql/extension/api.js +22 -35
- package/src/graphql/extension/framework.js +25 -33
- package/src/graphql/extension/type.js +2 -2
- package/src/query/Query.js +73 -15
- package/src/query/QueryBuilder.js +37 -28
- package/src/query/QueryBuilderTransaction.js +3 -3
- package/src/query/QueryResolver.js +93 -44
- package/src/query/QueryService.js +31 -34
- package/src/service/app.service.js +56 -35
- package/src/service/decorator.service.js +21 -288
- package/src/service/event.service.js +5 -79
- package/src/service/graphql.service.js +0 -9
- package/src/service/schema.service.js +5 -3
- package/src/core/Rule.js +0 -107
- package/src/core/SchemaDecorator.js +0 -46
- package/src/core/Transformer.js +0 -68
- package/src/data/Memoizer.js +0 -39
- package/src/data/ResultSet.js +0 -246
- package/src/graphql/ast/SchemaDecorator.js +0 -138
- package/src/graphql/directive/authz.directive.js +0 -84
|
@@ -54,6 +54,11 @@ exports.stripObjectUndefineds = obj => Object.entries(obj).reduce((prev, [key, v
|
|
|
54
54
|
exports.pushIt = (arr, it) => arr[arr.push(it) - 1];
|
|
55
55
|
exports.toKeyObj = obj => exports.keyPaths(obj).reduce((prev, path) => Object.assign(prev, { [path]: _.get(obj, path) }), {});
|
|
56
56
|
|
|
57
|
+
exports.getGQLReturnType = (returnType) => {
|
|
58
|
+
const typeMap = { array: /^\[.+\].?$/, connection: /.+Connection!?$/, number: /^(Int|Float)!?$/, scalar: /.*/ };
|
|
59
|
+
return Object.entries(typeMap).find(([type, pattern]) => returnType.match(pattern))[0];
|
|
60
|
+
};
|
|
61
|
+
|
|
57
62
|
exports.removeUndefinedDeep = (obj) => {
|
|
58
63
|
return exports.unravelObject(exports.keyPaths(obj).reduce((prev, path) => {
|
|
59
64
|
const value = _.get(obj, path);
|
|
@@ -90,7 +95,8 @@ exports.map = (mixed, fn) => {
|
|
|
90
95
|
if (mixed == null) return mixed;
|
|
91
96
|
const isArray = Array.isArray(mixed);
|
|
92
97
|
const arr = isArray ? mixed : [mixed];
|
|
93
|
-
const results = arr.map((...args) => fn(...args));
|
|
98
|
+
// const results = arr.map((...args) => fn(...args));
|
|
99
|
+
const results = arr.map((el, i, a) => fn(el, isArray ? i : undefined, isArray ? a : undefined));
|
|
94
100
|
return isArray ? results : results[0];
|
|
95
101
|
};
|
|
96
102
|
|
|
@@ -99,32 +105,6 @@ exports.mapPromise = (mixed, fn) => {
|
|
|
99
105
|
return Array.isArray(map) ? Promise.all(map) : Promise.resolve(map);
|
|
100
106
|
};
|
|
101
107
|
|
|
102
|
-
exports.castCmp = (type, value) => {
|
|
103
|
-
switch (type) {
|
|
104
|
-
case 'String': {
|
|
105
|
-
return `${value}`;
|
|
106
|
-
}
|
|
107
|
-
case 'Float': case 'Number': {
|
|
108
|
-
const num = Number(value);
|
|
109
|
-
if (!Number.isNaN(num)) return num;
|
|
110
|
-
return value;
|
|
111
|
-
}
|
|
112
|
-
case 'Int': {
|
|
113
|
-
const num = Number(value);
|
|
114
|
-
if (!Number.isNaN(num)) return parseInt(value, 10);
|
|
115
|
-
return value;
|
|
116
|
-
}
|
|
117
|
-
case 'Boolean': {
|
|
118
|
-
if (value === 'true') return true;
|
|
119
|
-
if (value === 'false') return false;
|
|
120
|
-
return value;
|
|
121
|
-
}
|
|
122
|
-
default: {
|
|
123
|
-
return value;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
108
|
exports.objectContaining = (a, b) => {
|
|
129
109
|
if (a === b) return true;
|
|
130
110
|
|
|
@@ -141,14 +121,6 @@ exports.objectContaining = (a, b) => {
|
|
|
141
121
|
return exports.hashObject(a) === exports.hashObject(b);
|
|
142
122
|
};
|
|
143
123
|
|
|
144
|
-
exports.serialize = (field, value) => {
|
|
145
|
-
if (!exports.isPlainObject(value)) return value;
|
|
146
|
-
const model = field.getModelRef();
|
|
147
|
-
if (!model) return value;
|
|
148
|
-
const key = model.idKey();
|
|
149
|
-
return value[key];
|
|
150
|
-
};
|
|
151
|
-
|
|
152
124
|
/**
|
|
153
125
|
* Transform an object with dot.notation keys into an expanded object.
|
|
154
126
|
* eg. { 'user.name': 'richard' } => { user: { name: 'richard' } }
|
|
@@ -277,3 +249,52 @@ exports.resolveDataObject = (obj) => {
|
|
|
277
249
|
}, {});
|
|
278
250
|
});
|
|
279
251
|
};
|
|
252
|
+
|
|
253
|
+
exports.seek = (obj, paths, hint) => {
|
|
254
|
+
// We first do a normal get
|
|
255
|
+
const value = _.get(obj, paths);
|
|
256
|
+
if (!hint || value !== undefined) return value;
|
|
257
|
+
|
|
258
|
+
// Normalize paths & hint for traversal
|
|
259
|
+
const $paths = Array.isArray(paths) ? paths : paths.split('.');
|
|
260
|
+
const $hint = exports.unravelObject(hint);
|
|
261
|
+
|
|
262
|
+
// Traverse paths and get as close to the value as possible
|
|
263
|
+
const { currentValue, pathsToGo } = $paths.reduce((prev, path, i, arr) => {
|
|
264
|
+
if (prev.currentValue === undefined) return prev;
|
|
265
|
+
if (!Object.prototype.hasOwnProperty.call(prev.currentValue, path)) return prev;
|
|
266
|
+
prev.currentValue = prev.currentValue[path];
|
|
267
|
+
prev.pathsToGo = arr.slice(i + 1);
|
|
268
|
+
return prev;
|
|
269
|
+
}, { currentValue: obj, pathsToGo: $paths });
|
|
270
|
+
|
|
271
|
+
// Only if we hit an array can we continue
|
|
272
|
+
if (!Array.isArray(currentValue)) return undefined;
|
|
273
|
+
|
|
274
|
+
// If we got to the last segment we need the hint in order to verify
|
|
275
|
+
const lastPath = Boolean(pathsToGo.length === 1);
|
|
276
|
+
const arr = lastPath ? currentValue.filter(v => exports.objectContaining(v, $hint)) : currentValue;
|
|
277
|
+
|
|
278
|
+
// We keep going, recursive, till we find the first value
|
|
279
|
+
return arr.reduce((prev, v) => prev || exports.seek(v, pathsToGo, $hint), undefined);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
exports.deseek = (shape, obj, paths, hint) => {
|
|
283
|
+
// Normalize paths
|
|
284
|
+
const $paths = (Array.isArray(paths) ? paths : paths.split('.')).map((path) => {
|
|
285
|
+
const item = shape.find(s => s.to === path); // Deserializing from unknown to expected
|
|
286
|
+
return item ? item.from : path;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Normalize hint
|
|
290
|
+
const $hint = Object.entries(exports.toKeyObj(hint)).reduce((prev, [key, value]) => {
|
|
291
|
+
const segments = key.split('.').map((path) => {
|
|
292
|
+
const item = shape.find(s => s.to === path); // Deserializing from unknown to expected
|
|
293
|
+
return item ? item.from : path;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return Object.assign(prev, { [segments.join('.')]: value });
|
|
297
|
+
}, {});
|
|
298
|
+
|
|
299
|
+
return exports.seek(obj, $paths, $hint);
|
|
300
|
+
};
|
|
@@ -1,275 +1,27 @@
|
|
|
1
|
-
const { get, set, remove, merge } = require('lodash');
|
|
2
1
|
const GraphqlFields = require('graphql-fields');
|
|
3
|
-
const Boom = require('../core/Boom');
|
|
4
|
-
const Query = require('../query/Query');
|
|
5
|
-
const { createSystemEvent } = require('./event.service');
|
|
6
|
-
const { guidToId, unrollGuid, ucFirst, getDeep, ensureArray, objectContaining } = require('./app.service');
|
|
7
|
-
|
|
8
|
-
const findParentField = (name, embed, model) => {
|
|
9
|
-
const schema = model.getSchema();
|
|
10
|
-
const fieldName = ucFirst(embed.isArray() ? embed.getName().replace(/s$/, '') : embed.getName());
|
|
11
|
-
const parentModelName = name.substr(0, name.lastIndexOf(fieldName));
|
|
12
|
-
const parentModel = schema.getModel(parentModelName);
|
|
13
|
-
const field = model.getFields().find(f => f.getType() === parentModelName) || parentModel.getFields().find(f => f.getType() === model.getName());
|
|
14
|
-
if (!field) throw Boom.badData(`Unable to locate parent field: '${model.getName()} -> ${parentModelName}'`);
|
|
15
|
-
return field;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const findParentAndContainerCreate = (name, doc, input, model, embeds) => {
|
|
19
|
-
let parent = doc;
|
|
20
|
-
|
|
21
|
-
const container = embeds.reduce((prev, embed, i) => {
|
|
22
|
-
// If further nested; must find the correct container
|
|
23
|
-
if (i > 0) {
|
|
24
|
-
const subField = findParentField(name, embed, model);
|
|
25
|
-
prev = prev.find(el => `${el.id}` === `${input[subField]}`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
parent = prev;
|
|
29
|
-
return getDeep(prev, embed.getName());
|
|
30
|
-
}, doc);
|
|
31
|
-
|
|
32
|
-
return { parent, container };
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const findParentAndContainerUpdate = (fieldPath, doc, id, embeds) => {
|
|
36
|
-
const data = embeds.map((embed, i, arr) => getDeep(doc, arr.slice(0, i + 1).join('.')));
|
|
37
|
-
const [tail, prev = tail, head = prev] = [data[0], data[data.length - 2], data[data.length - 1]];
|
|
38
|
-
const container = head.find(el => `${id}` === `${el.id}`);
|
|
39
|
-
const parent = head === prev ? doc : prev.find(p => p[fieldPath.split('.').pop()].find(el => `${id}` === `${el.id}`));
|
|
40
|
-
return { tail, parent, container };
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const resolveQuery = (method, name, resolver, model, embeds = []) => {
|
|
44
|
-
const [base] = embeds;
|
|
45
|
-
const curr = embeds[embeds.length - 1];
|
|
46
|
-
const fieldPath = embeds.map(field => field.getName()).join('.');
|
|
47
2
|
|
|
3
|
+
const resolveQuery = (method, name, resolver, model) => {
|
|
48
4
|
return async (root, args, context, info) => {
|
|
49
|
-
const {
|
|
50
|
-
|
|
51
|
-
// Embedded document handler
|
|
52
|
-
if (fieldPath.length) {
|
|
53
|
-
switch (method) {
|
|
54
|
-
case 'get': {
|
|
55
|
-
// Readjust the where clause
|
|
56
|
-
const where = get(args, 'where', {});
|
|
57
|
-
set(where, `${fieldPath}.id`, args.id);
|
|
58
|
-
set(args, 'where', where);
|
|
59
|
-
|
|
60
|
-
return resolver.query(context, base.getModel(), args, info).then(([result]) => {
|
|
61
|
-
const arr = ensureArray(getDeep(result, fieldPath, []));
|
|
62
|
-
return arr.find(el => `${el.id}` === `${args.id}`);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
case 'find': {
|
|
66
|
-
// Readjust the where clause
|
|
67
|
-
const where = get(args, 'where', {});
|
|
68
|
-
const $where = set({}, `${fieldPath}`, where);
|
|
69
|
-
set(args, 'where', $where);
|
|
70
|
-
|
|
71
|
-
return resolver.query(context, base.getModel(), args, info).then((results) => {
|
|
72
|
-
const arr = results.map(result => ensureArray(getDeep(result, fieldPath, []))).flat();
|
|
73
|
-
return arr.filter(el => objectContaining(el, where));
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
case 'count': {
|
|
77
|
-
// Readjust the where clause
|
|
78
|
-
const where = get(args, 'where', {});
|
|
79
|
-
const $where = set({}, `${fieldPath}`, where);
|
|
80
|
-
set(args, 'where', $where);
|
|
81
|
-
|
|
82
|
-
return resolver.query(context, base.getModel(), args, info).then((results) => {
|
|
83
|
-
const arr = results.map(result => ensureArray(getDeep(result, fieldPath, []))).flat();
|
|
84
|
-
return arr.filter(el => objectContaining(el, where)).length;
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
case 'create': {
|
|
88
|
-
const field = findParentField(name, curr, model);
|
|
89
|
-
const path = fieldPath.split('.').slice(0, -1).concat('id').join('.');
|
|
90
|
-
const input = unrollGuid(autograph, model, args.input);
|
|
91
|
-
const meta = args.meta || {};
|
|
92
|
-
const id = guidToId(autograph, get(input, field.getName()));
|
|
93
|
-
|
|
94
|
-
// Get overall document
|
|
95
|
-
const where = { [path]: id };
|
|
96
|
-
const doc = await autograph.resolver.match(base.getModel()).where(where).one();
|
|
97
|
-
if (!doc) throw Boom.notFound(`${base.getModel().getName()} Not Found`);
|
|
98
|
-
|
|
99
|
-
// Get parent and container within document
|
|
100
|
-
const query = new Query({ resolver: autograph.resolver, model, input, where, meta, root: doc });
|
|
101
|
-
const { container } = findParentAndContainerCreate(name, doc, input, model, embeds);
|
|
102
|
-
model.appendDefaultFields(query, input);
|
|
103
|
-
|
|
104
|
-
return createSystemEvent('Mutation', { method: 'create', query }, async () => {
|
|
105
|
-
let $$input = ensureArray(input.$input || input);
|
|
106
|
-
$$input = await Promise.all($$input.map(el => model.appendCreateFields(el, true)));
|
|
107
|
-
container.push(...$$input);
|
|
108
|
-
const $update = { [base.getName()]: get(doc, base.getName()) };
|
|
109
|
-
return autograph.resolver.match(base.getModel()).id(doc.id).flags({ novalidate: true }).save($update).then(($doc) => {
|
|
110
|
-
return getDeep(doc, fieldPath).pop();
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
case 'update': {
|
|
115
|
-
// Get overall document
|
|
116
|
-
const id = guidToId(autograph, args.id);
|
|
117
|
-
const where = { [`${fieldPath}.id`]: id };
|
|
118
|
-
const meta = args.meta || {};
|
|
119
|
-
const input = unrollGuid(autograph, model, args.input || {});
|
|
120
|
-
const doc = await autograph.resolver.match(base.getModel()).where(where).one();
|
|
121
|
-
if (!doc) throw Boom.notFound(`${base.getModel().getName()} Not Found`);
|
|
122
|
-
|
|
123
|
-
// Get parent and container within document
|
|
124
|
-
const query = new Query({ resolver: autograph.resolver, model, input, where, meta, root: doc });
|
|
125
|
-
const { tail, container } = findParentAndContainerUpdate(fieldPath, doc, id, embeds);
|
|
126
|
-
|
|
127
|
-
return createSystemEvent('Mutation', { method: 'update', query }, async () => {
|
|
128
|
-
const $input = await model.appendUpdateFields(input);
|
|
129
|
-
merge(container, $input); // Must mutate object here
|
|
130
|
-
const $update = { [base.getName()]: tail };
|
|
131
|
-
doc[base.getName()] = tail; // Deficiency in how update works; must pass entire doc
|
|
132
|
-
return autograph.resolver.match(base.getModel()).id(doc.id).flags({ novalidate: true }).save($update).then(() => container);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
case 'delete': {
|
|
136
|
-
const id = guidToId(autograph, args.id);
|
|
137
|
-
const where = { [`${fieldPath}.id`]: id };
|
|
138
|
-
const meta = args.meta || {};
|
|
139
|
-
const doc = await autograph.resolver.match(base.getModel()).where(where).one();
|
|
140
|
-
if (!doc) throw Boom.notFound(`${base.getModel()} Not Found`);
|
|
141
|
-
|
|
142
|
-
// Get parent and container within document
|
|
143
|
-
const query = new Query({ resolver: autograph.resolver, model, where, meta, root: doc });
|
|
144
|
-
const { tail, parent, container } = findParentAndContainerUpdate(fieldPath, doc, id, embeds);
|
|
145
|
-
|
|
146
|
-
return createSystemEvent('Mutation', { method: 'delete', query }, () => {
|
|
147
|
-
const key = fieldPath.split('.').pop();
|
|
148
|
-
remove(parent[key], el => `${el.id}` === `${id}`);
|
|
149
|
-
const $update = { [base.getName()]: tail };
|
|
150
|
-
doc[base.getName()] = tail; // Deficiency in how update works; must pass entire doc
|
|
151
|
-
return autograph.resolver.match(base.getModel()).id(doc.id).flags({ novalidate: true }).save($update).then(() => container);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
default: {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
5
|
+
const queryInfo = { fields: GraphqlFields(info, {}, { processArguments: true }) };
|
|
159
6
|
|
|
160
7
|
switch (method) {
|
|
161
|
-
case 'get': return resolver.get(context, model, args, true,
|
|
8
|
+
case 'get': return resolver.get(context, model, args, true, queryInfo);
|
|
162
9
|
case 'find': {
|
|
163
10
|
return {
|
|
164
|
-
edges: () => resolver.query(context, model, args,
|
|
165
|
-
pageInfo: () => resolver.query(context, model, args,
|
|
166
|
-
count: () => resolver.count(context, model, args,
|
|
11
|
+
edges: () => resolver.query(context, model, args, queryInfo),
|
|
12
|
+
pageInfo: () => resolver.query(context, model, args, queryInfo),
|
|
13
|
+
count: () => resolver.count(context, model, args, queryInfo),
|
|
167
14
|
};
|
|
168
15
|
}
|
|
169
|
-
case 'count': return resolver.count(context, model, args,
|
|
170
|
-
case 'create': return resolver.create(context, model, args,
|
|
171
|
-
case 'update': return resolver.update(context, model, args,
|
|
172
|
-
case 'delete': return resolver.delete(context, model, args,
|
|
16
|
+
case 'count': return resolver.count(context, model, args, queryInfo);
|
|
17
|
+
case 'create': return resolver.create(context, model, args, queryInfo);
|
|
18
|
+
case 'update': return resolver.update(context, model, args, queryInfo);
|
|
19
|
+
case 'delete': return resolver.delete(context, model, args, queryInfo);
|
|
173
20
|
default: return null;
|
|
174
21
|
}
|
|
175
22
|
};
|
|
176
23
|
};
|
|
177
24
|
|
|
178
|
-
const makeEmbeddedAPI = (model, method, parent) => {
|
|
179
|
-
let gql = '';
|
|
180
|
-
const modelName = model.getName();
|
|
181
|
-
const fields = model.getEmbeddedFields().filter(field => field !== parent && field.isEmbeddedApi());
|
|
182
|
-
|
|
183
|
-
if (fields.length) {
|
|
184
|
-
fields.forEach((field) => {
|
|
185
|
-
const modelRef = field.getModelRef();
|
|
186
|
-
const fieldName = ucFirst(field.isArray() ? field.getName().replace(/s$/, '') : field.getName());
|
|
187
|
-
const name = `${modelName}${fieldName}`;
|
|
188
|
-
|
|
189
|
-
switch (method) {
|
|
190
|
-
case 'create': {
|
|
191
|
-
if (field.hasFieldScope('c')) gql += exports.makeCreateAPI(name, modelRef, field);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
case 'read': {
|
|
195
|
-
if (field.hasFieldScope('r')) gql += exports.makeReadAPI(name, modelRef, field);
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
case 'update': {
|
|
199
|
-
if (field.hasFieldScope('u')) gql += exports.makeUpdateAPI(name, modelRef, field);
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
case 'delete': {
|
|
203
|
-
if (field.hasFieldScope('d')) gql += exports.makeDeleteAPI(name, modelRef, field);
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
default: {
|
|
207
|
-
throw new Error(`Unknown method '${method}'`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return gql;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const makeEmbeddedResolver = (model, resolver, type, embeds = []) => {
|
|
217
|
-
const obj = {};
|
|
218
|
-
|
|
219
|
-
const modelName = model.getName();
|
|
220
|
-
const fields = model.getEmbeddedFields().filter(field => embeds.indexOf(field) === -1 && field.isEmbeddedApi());
|
|
221
|
-
|
|
222
|
-
fields.forEach((field) => {
|
|
223
|
-
const modelRef = field.getModelRef();
|
|
224
|
-
const fieldName = ucFirst(field.isArray() ? field.getName().replace(/s$/, '') : field.getName());
|
|
225
|
-
const name = `${modelName}${fieldName}`;
|
|
226
|
-
|
|
227
|
-
switch (type) {
|
|
228
|
-
case 'query': {
|
|
229
|
-
Object.assign(obj, exports.makeQueryResolver(name, modelRef, resolver, embeds.concat(field)));
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
case 'mutation': {
|
|
233
|
-
Object.assign(obj, exports.makeMutationResolver(name, modelRef, resolver, embeds.concat(field)));
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
default: {
|
|
237
|
-
throw new Error(`Unknown type '${type}'`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
return obj;
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
exports.makeInputSplice = (model, embed = false) => {
|
|
246
|
-
let gql = '';
|
|
247
|
-
const fields = model.getArrayFields().filter(field => field.hasGQLScope('c', 'u', 'd') && field.isSpliceable());
|
|
248
|
-
|
|
249
|
-
if (fields.length) {
|
|
250
|
-
gql += fields.map((field) => {
|
|
251
|
-
const embedded = field.isEmbedded() ? exports.makeInputSplice(field.getModelRef(), true) : '';
|
|
252
|
-
|
|
253
|
-
return `
|
|
254
|
-
${embedded}
|
|
255
|
-
input ${model.getName()}${ucFirst(field.getName())}InputSplice {
|
|
256
|
-
with: ${field.getGQLType('InputWhere', { splice: true })}
|
|
257
|
-
put: ${field.getGQLType('InputUpdate', { splice: true })}
|
|
258
|
-
${embedded.length ? `splice: ${field.getModelRef().getName()}InputSplice` : ''}
|
|
259
|
-
}
|
|
260
|
-
`;
|
|
261
|
-
}).join('\n\n');
|
|
262
|
-
|
|
263
|
-
gql += `
|
|
264
|
-
input ${model.getName()}InputSplice {
|
|
265
|
-
${fields.map(field => `${field.getName()}: ${model.getName()}${ucFirst(field.getName())}InputSplice`)}
|
|
266
|
-
}
|
|
267
|
-
`;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return gql;
|
|
271
|
-
};
|
|
272
|
-
|
|
273
25
|
// APIs
|
|
274
26
|
exports.makeCreateAPI = (name, model, parent) => {
|
|
275
27
|
let gql = '';
|
|
@@ -279,8 +31,6 @@ exports.makeCreateAPI = (name, model, parent) => {
|
|
|
279
31
|
gql += `create${name}(input: ${model.getName()}InputCreate! ${meta}): ${model.getName()}!`;
|
|
280
32
|
}
|
|
281
33
|
|
|
282
|
-
gql += makeEmbeddedAPI(model, 'create', parent);
|
|
283
|
-
|
|
284
34
|
return gql;
|
|
285
35
|
};
|
|
286
36
|
|
|
@@ -303,8 +53,6 @@ exports.makeReadAPI = (name, model, parent) => {
|
|
|
303
53
|
`;
|
|
304
54
|
}
|
|
305
55
|
|
|
306
|
-
gql += makeEmbeddedAPI(model, 'read', parent);
|
|
307
|
-
|
|
308
56
|
return gql;
|
|
309
57
|
};
|
|
310
58
|
|
|
@@ -312,21 +60,10 @@ exports.makeUpdateAPI = (name, model, parent) => {
|
|
|
312
60
|
let gql = '';
|
|
313
61
|
|
|
314
62
|
if (model.hasGQLScope('u')) {
|
|
315
|
-
const spliceFields = model.getArrayFields().filter(field => field.hasGQLScope('c', 'u', 'd') && field.isSpliceable());
|
|
316
63
|
const meta = model.getMeta() ? `meta: ${model.getMeta()}` : '';
|
|
317
|
-
|
|
318
|
-
gql += `
|
|
319
|
-
update${name}(
|
|
320
|
-
id: ID!
|
|
321
|
-
input: ${model.getName()}InputUpdate
|
|
322
|
-
# ${!spliceFields.length ? '' : `splice: ${model.getName()}InputSplice`}
|
|
323
|
-
${meta}
|
|
324
|
-
): ${model.getName()}!
|
|
325
|
-
`;
|
|
64
|
+
gql += `update${name}(id: ID! input: ${model.getName()}InputUpdate ${meta}): ${model.getName()}!`;
|
|
326
65
|
}
|
|
327
66
|
|
|
328
|
-
gql += makeEmbeddedAPI(model, 'update', parent);
|
|
329
|
-
|
|
330
67
|
return gql;
|
|
331
68
|
};
|
|
332
69
|
|
|
@@ -338,8 +75,6 @@ exports.makeDeleteAPI = (name, model, parent) => {
|
|
|
338
75
|
gql += `delete${name}(id: ID! ${meta}): ${model.getName()}!`;
|
|
339
76
|
}
|
|
340
77
|
|
|
341
|
-
gql += makeEmbeddedAPI(model, 'delete', parent);
|
|
342
|
-
|
|
343
78
|
return gql;
|
|
344
79
|
};
|
|
345
80
|
|
|
@@ -357,25 +92,23 @@ exports.makeSubscriptionAPI = (name, model, parent) => {
|
|
|
357
92
|
};
|
|
358
93
|
|
|
359
94
|
// Resolvers
|
|
360
|
-
exports.makeQueryResolver = (name, model, resolver
|
|
95
|
+
exports.makeQueryResolver = (name, model, resolver) => {
|
|
361
96
|
const obj = {};
|
|
362
|
-
const [field] = embeds.slice(-1);
|
|
363
97
|
|
|
364
|
-
if (
|
|
365
|
-
obj[`get${name}`] = resolveQuery('get', name, resolver, model
|
|
366
|
-
obj[`find${name}`] = resolveQuery('find', name, resolver, model
|
|
98
|
+
if (model.hasGQLScope('r')) {
|
|
99
|
+
obj[`get${name}`] = resolveQuery('get', name, resolver, model);
|
|
100
|
+
obj[`find${name}`] = resolveQuery('find', name, resolver, model);
|
|
367
101
|
}
|
|
368
102
|
|
|
369
|
-
return
|
|
103
|
+
return obj;
|
|
370
104
|
};
|
|
371
105
|
|
|
372
|
-
exports.makeMutationResolver = (name, model, resolver
|
|
106
|
+
exports.makeMutationResolver = (name, model, resolver) => {
|
|
373
107
|
const obj = {};
|
|
374
|
-
const [field] = embeds.slice(-1);
|
|
375
108
|
|
|
376
|
-
if (
|
|
377
|
-
if (
|
|
378
|
-
if (
|
|
109
|
+
if (model.hasGQLScope('c')) obj[`create${name}`] = resolveQuery('create', name, resolver, model);
|
|
110
|
+
if (model.hasGQLScope('u')) obj[`update${name}`] = resolveQuery('update', name, resolver, model);
|
|
111
|
+
if (model.hasGQLScope('d')) obj[`delete${name}`] = resolveQuery('delete', name, resolver, model);
|
|
379
112
|
|
|
380
|
-
return
|
|
113
|
+
return obj;
|
|
381
114
|
};
|
|
@@ -1,59 +1,26 @@
|
|
|
1
|
-
const QueryService = require('../query/QueryService');
|
|
2
1
|
const EventEmitter = require('../core/EventEmitter');
|
|
3
|
-
const {
|
|
2
|
+
const { ucFirst } = require('./app.service');
|
|
4
3
|
|
|
5
4
|
// Event emitters
|
|
6
5
|
const eventEmitter = new EventEmitter().setMaxListeners(100);
|
|
7
|
-
const internalEmitter = new EventEmitter().setMaxListeners(100);
|
|
8
6
|
const systemEvent = new EventEmitter().setMaxListeners(100).on('system', async (event, next) => {
|
|
9
7
|
const { type, data } = event;
|
|
10
|
-
await internalEmitter.emit(type, data);
|
|
11
8
|
next(await eventEmitter.emit(type, data)); // Return result from user-defined middleware
|
|
12
9
|
});
|
|
13
10
|
|
|
14
11
|
//
|
|
15
12
|
exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
16
13
|
let event = mixed;
|
|
17
|
-
let middleware = () => Promise.resolve();
|
|
18
14
|
const type = ucFirst(name);
|
|
19
15
|
|
|
20
16
|
if (name !== 'Setup' && name !== 'Response') {
|
|
21
|
-
const {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
event = {
|
|
25
|
-
context: resolver.getContext(),
|
|
26
|
-
key: `${method}${model}`,
|
|
27
|
-
resolver,
|
|
28
|
-
method,
|
|
29
|
-
crud,
|
|
30
|
-
model,
|
|
31
|
-
meta,
|
|
32
|
-
id,
|
|
33
|
-
input,
|
|
34
|
-
query,
|
|
35
|
-
doc,
|
|
36
|
-
merged,
|
|
37
|
-
root,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
middleware = () => new Promise(async (resolve) => {
|
|
41
|
-
if (!native) {
|
|
42
|
-
const $where = await QueryService.resolveWhereClause(query);
|
|
43
|
-
query.match(model.serialize(query, $where, true));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (sort) {
|
|
47
|
-
query.$sort(QueryService.resolveSortBy(query));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
resolve();
|
|
51
|
-
});
|
|
17
|
+
const { query } = mixed;
|
|
18
|
+
event = query.toObject();
|
|
19
|
+
event.query = query;
|
|
52
20
|
}
|
|
53
21
|
|
|
54
22
|
return systemEvent.emit('system', { type: `pre${type}`, data: event }).then((result) => {
|
|
55
|
-
|
|
56
|
-
return middleware().then(thunk);
|
|
23
|
+
return (result !== undefined) ? result : thunk(); // Allowing middleware to dictate result
|
|
57
24
|
}).then((result) => {
|
|
58
25
|
event.result = result;
|
|
59
26
|
if (event.crud === 'create') event.doc = event.query.toObject().doc;
|
|
@@ -66,44 +33,3 @@ exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
|
66
33
|
};
|
|
67
34
|
|
|
68
35
|
exports.eventEmitter = eventEmitter;
|
|
69
|
-
exports.internalEmitter = internalEmitter;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Hook into the pre event only!
|
|
74
|
-
*
|
|
75
|
-
* Kick off system events for embedded fields
|
|
76
|
-
*/
|
|
77
|
-
const eventHandler = (event) => {
|
|
78
|
-
const { model, input, method, doc = input, query } = event;
|
|
79
|
-
|
|
80
|
-
return Promise.all(model.getEmbeddedFields().map((field) => {
|
|
81
|
-
return new Promise((resolve, reject) => {
|
|
82
|
-
if (Object.prototype.hasOwnProperty.call(input || {}, field.getName())) {
|
|
83
|
-
let i = 0;
|
|
84
|
-
const value = input[field.getName()];
|
|
85
|
-
const values = ensureArray(value).filter(el => el != null);
|
|
86
|
-
const newModel = field.getModelRef();
|
|
87
|
-
|
|
88
|
-
if (values.length) {
|
|
89
|
-
values.forEach((val) => {
|
|
90
|
-
const clone = query.clone().model(newModel).input(val).doc(doc);
|
|
91
|
-
exports.createSystemEvent('Mutation', { method, query: clone }, () => {
|
|
92
|
-
if (++i >= values.length) resolve();
|
|
93
|
-
}).catch(e => reject(e));
|
|
94
|
-
// const newEvent = { parent: doc, key: `${method}${field}`, method, model: newModel, resolver, query: new Query(resolver, newModel, { meta }), input: val };
|
|
95
|
-
// exports.createSystemEvent('Mutation', newEvent, () => {
|
|
96
|
-
// if (++i >= values.length) resolve();
|
|
97
|
-
// }).catch(e => reject(e));
|
|
98
|
-
});
|
|
99
|
-
} else {
|
|
100
|
-
resolve();
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
resolve();
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
}));
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
internalEmitter.on('preMutation', async (event, next) => eventHandler(event).then(next)); // Only preMutation!
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const { get } = require('lodash');
|
|
2
2
|
const { Kind, parse, print } = require('graphql');
|
|
3
|
-
const { validate } = require('graphql/validation');
|
|
4
|
-
const { makeExecutableSchema } = require('graphql-tools');
|
|
5
3
|
|
|
6
4
|
//
|
|
7
5
|
const mergePairs = [
|
|
@@ -81,13 +79,6 @@ exports.mergeASTArray = (arr) => {
|
|
|
81
79
|
}, []).filter(el => !el.deleteFlag);
|
|
82
80
|
};
|
|
83
81
|
|
|
84
|
-
exports.validateSchema = (ast) => {
|
|
85
|
-
const errs = validate(makeExecutableSchema(ast), ast.typeDefs).filter(({ message }) => message.indexOf('not executable') === -1).map(({ message }) => message);
|
|
86
|
-
if (errs.length) throw new Error(errs.join('\n'));
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
exports.makeExecutableSchema = makeExecutableSchema;
|
|
90
|
-
|
|
91
82
|
exports.toAST = (a) => {
|
|
92
83
|
if (typeof a === 'string') return parse(a);
|
|
93
84
|
if (Array.isArray(a)) return parse(a.map(e => exports.toGQL(e)).join('\n\n'));
|
|
@@ -54,11 +54,13 @@ exports.getSchemaData = (schema) => {
|
|
|
54
54
|
exports.identifyOnDeletes = (models, parentModel) => {
|
|
55
55
|
return models.reduce((prev, model) => {
|
|
56
56
|
model.getOnDeleteFields().forEach((field) => {
|
|
57
|
-
|
|
57
|
+
const { modelRef, isArray } = field.toObject();
|
|
58
|
+
|
|
59
|
+
if (`${modelRef}` === `${parentModel}`) {
|
|
58
60
|
if (model.isEntity()) {
|
|
59
|
-
prev.push({ model, field, isArray
|
|
61
|
+
prev.push({ model, field, isArray, op: field.getOnDelete() });
|
|
60
62
|
} else {
|
|
61
|
-
prev.push(...exports.identifyOnDeletes(models, model).map(od => Object.assign(od, { fieldRef: field, isArray
|
|
63
|
+
prev.push(...exports.identifyOnDeletes(models, model).map(od => Object.assign(od, { fieldRef: field, isArray, op: field.getOnDelete() })));
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
});
|