@coderich/autograph 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +21 -3
  2. package/index.js +2 -6
  3. package/package.json +4 -4
  4. package/src/.DS_Store +0 -0
  5. package/src/core/EventEmitter.js +2 -4
  6. package/src/core/Resolver.js +43 -60
  7. package/src/core/Schema.js +3 -36
  8. package/src/data/.DS_Store +0 -0
  9. package/src/data/DataLoader.js +71 -32
  10. package/src/data/DataService.js +59 -58
  11. package/src/data/Field.js +71 -121
  12. package/src/data/Model.js +98 -108
  13. package/src/data/Pipeline.js +174 -0
  14. package/src/data/Type.js +19 -74
  15. package/src/driver/MongoDriver.js +21 -19
  16. package/src/graphql/.DS_Store +0 -0
  17. package/src/graphql/ast/Field.js +43 -24
  18. package/src/graphql/ast/Model.js +5 -16
  19. package/src/graphql/ast/Node.js +0 -25
  20. package/src/graphql/ast/Schema.js +107 -111
  21. package/src/graphql/extension/api.js +20 -18
  22. package/src/graphql/extension/framework.js +25 -33
  23. package/src/graphql/extension/type.js +2 -2
  24. package/src/query/Query.js +72 -14
  25. package/src/query/QueryBuilder.js +38 -30
  26. package/src/query/QueryBuilderTransaction.js +3 -3
  27. package/src/query/QueryResolver.js +92 -42
  28. package/src/query/QueryService.js +31 -34
  29. package/src/service/app.service.js +67 -9
  30. package/src/service/event.service.js +5 -79
  31. package/src/service/schema.service.js +5 -3
  32. package/src/core/Rule.js +0 -107
  33. package/src/core/SchemaDecorator.js +0 -46
  34. package/src/core/Transformer.js +0 -68
  35. package/src/data/Memoizer.js +0 -39
  36. package/src/data/ResultSet.js +0 -205
  37. package/src/data/stream/DataHydrator.js +0 -58
  38. package/src/data/stream/ResultSet.js +0 -34
  39. package/src/data/stream/ResultSetItem.js +0 -158
  40. package/src/data/stream/ResultSetItemProxy.js +0 -161
  41. package/src/graphql/ast/SchemaDecorator.js +0 -141
  42. package/src/graphql/directive/authz.directive.js +0 -84
@@ -1,77 +1,110 @@
1
- const { get, isEmpty } = require('lodash');
1
+ const { get, set, isEmpty } = require('lodash');
2
2
  const Boom = require('../core/Boom');
3
3
  const QueryService = require('./QueryService');
4
4
  const DataService = require('../data/DataService');
5
5
  const { createSystemEvent } = require('../service/event.service');
6
- const { mergeDeep } = require('../service/app.service');
6
+ const { mergeDeep, getGQLReturnType } = require('../service/app.service');
7
7
 
8
8
  module.exports = class QueryResolver {
9
9
  constructor(query) {
10
10
  this.query = query;
11
11
  this.resolver = query.toObject().resolver;
12
+ this.context = this.resolver.getContext();
12
13
  }
13
14
 
14
- findOne(query) {
15
- return createSystemEvent('Query', { method: 'get', query }, () => {
15
+ autoResolve(query) {
16
+ const { args } = query.toObject();
17
+ const [,,, info] = args;
18
+
19
+ switch (getGQLReturnType(info.returnType)) {
20
+ case 'array': {
21
+ return new QueryResolver(this.query.clone().method('findMany')).resolve();
22
+ }
23
+ case 'number': {
24
+ return new QueryResolver(this.query.clone().method('count')).resolve();
25
+ }
26
+ case 'connection': {
27
+ return Promise.resolve({
28
+ count: () => new QueryResolver(this.query.clone().method('count')).resolve(),
29
+ edges: () => new QueryResolver(this.query.clone().method('findMany')).resolve(),
30
+ pageInfo: () => new QueryResolver(this.query.clone().method('findMany')).resolve(),
31
+ });
32
+ }
33
+ case 'scalar': default: {
34
+ return new QueryResolver(this.query.clone().method('findOne')).resolve();
35
+ }
36
+ }
37
+ }
38
+
39
+ async findOne(query) {
40
+ await QueryService.resolveQuery(query);
41
+
42
+ return createSystemEvent('Query', { query }, () => {
16
43
  return this.resolver.resolve(query);
17
44
  });
18
45
  }
19
46
 
20
- findMany(query) {
21
- return createSystemEvent('Query', { method: 'find', query }, () => {
47
+ async findMany(query) {
48
+ await QueryService.resolveQuery(query);
49
+
50
+ return createSystemEvent('Query', { query }, () => {
22
51
  return this.resolver.resolve(query);
23
52
  });
24
53
  }
25
54
 
26
- count(query) {
27
- return createSystemEvent('Query', { method: 'count', query }, () => {
55
+ async count(query) {
56
+ await QueryService.resolveQuery(query);
57
+
58
+ return createSystemEvent('Query', { query }, () => {
28
59
  return this.resolver.resolve(query);
29
60
  });
30
61
  }
31
62
 
32
- createOne(query) {
33
- const { model, input, flags } = query.toObject();
34
- model.appendDefaultFields(query, input);
63
+ async createOne(query) {
64
+ const { model, input } = query.toObject();
65
+ const shape = model.getShape('create');
35
66
 
36
- return createSystemEvent('Mutation', { method: 'create', query }, async () => {
37
- const $input = model.serialize(query, model.appendCreateFields(input));
38
- query.$input($input);
39
- if (!get(flags, 'novalidate')) await model.validate(query, $input);
40
- const doc = await this.resolver.resolve(query);
67
+ await QueryService.resolveQuery(query);
68
+
69
+ return createSystemEvent('Mutation', { query }, async () => {
70
+ const $input = model.shapeObject(shape, input, query);
71
+ await model.validate(query, $input);
72
+ const doc = await this.resolver.resolve(query.$input($input));
41
73
  query.doc(doc);
42
74
  return doc;
43
75
  });
44
76
  }
45
77
 
46
78
  createMany(query) {
47
- const { model, args, transaction } = query.toObject();
79
+ const { model, input, transaction } = query.toObject();
48
80
  const txn = this.resolver.transaction(transaction);
49
- args.forEach(arg => txn.match(model).save(arg));
81
+ input.forEach(arg => txn.match(model).save(arg));
50
82
  return txn.run();
51
83
  }
52
84
 
53
- updateOne(query) {
54
- const { model, match, flags } = query.toObject();
85
+ async updateOne(query) {
86
+ const { model, match, input } = query.toObject();
87
+
88
+ return this.resolver.match(model).match(match).one({ required: true }).then(async (doc) => {
89
+ const shape = model.getShape('update');
90
+ const merged = model.shapeObject(shape, mergeDeep(doc, input), query);
55
91
 
56
- return this.resolver.match(model).match(match).one({ required: true }).then((doc) => {
57
- const { input } = query.toObject();
58
- const merged = mergeDeep(doc, input);
92
+ await QueryService.resolveQuery(query);
59
93
 
60
- return createSystemEvent('Mutation', { method: 'update', query: query.doc(doc).merged(merged) }, async () => {
61
- const $input = model.serialize(query, model.appendUpdateFields(input), true);
62
- if (!get(flags, 'novalidate')) await model.validate(query, $input);
63
- const $doc = mergeDeep(model.serialize(query, doc, true), $input);
64
- return this.resolver.resolve(query.$doc($doc).$input($input));
94
+ return createSystemEvent('Mutation', { query: query.doc(doc).merged(merged) }, async () => {
95
+ const $doc = model.shapeObject(shape, mergeDeep(doc, input), query);
96
+ await model.validate(query, $doc);
97
+ return this.resolver.resolve(query.$doc($doc));
65
98
  });
66
99
  });
67
100
  }
68
101
 
69
102
  updateMany(query) {
70
- const { model, args, match, transaction, flags } = query.toObject();
103
+ const { model, input, match, transaction, flags } = query.toObject();
71
104
 
72
- return this.resolver.match(model).match(match).flags(flags).many().then((docs) => {
105
+ return this.resolver.match(model).match(match).many(flags).then((docs) => {
73
106
  const txn = this.resolver.transaction(transaction);
74
- docs.forEach(doc => txn.match(model).id(doc.id).save(...args));
107
+ docs.forEach(doc => txn.match(model).id(doc.id).save(input, flags));
75
108
  return txn.run();
76
109
  });
77
110
  }
@@ -79,8 +112,10 @@ module.exports = class QueryResolver {
79
112
  deleteOne(query) {
80
113
  const { model, id, flags } = query.toObject();
81
114
 
82
- return this.resolver.match(model).id(id).flags(flags).one({ required: true }).then((doc) => {
83
- return createSystemEvent('Mutation', { method: 'delete', query: query.doc(doc) }, () => {
115
+ return this.resolver.match(model).id(id).flags(flags).one({ required: true }).then(async (doc) => {
116
+ await QueryService.resolveQuery(query);
117
+
118
+ return createSystemEvent('Mutation', { query: query.doc(doc) }, () => {
84
119
  return QueryService.resolveReferentialIntegrity(query).then(() => {
85
120
  return this.resolver.resolve(query).then(() => doc);
86
121
  });
@@ -151,16 +186,28 @@ module.exports = class QueryResolver {
151
186
  }
152
187
 
153
188
  splice(query) {
154
- const { model, match, args, flags } = query.toObject();
189
+ const { model, match, args, flags = {} } = query.toObject();
155
190
  const [key, from, to] = args;
156
191
 
157
- return this.resolver.match(model).match(match).flags(flags).one({ required: true }).then(async (doc) => {
158
- await DataService.spliceEmbeddedArray(query, doc, key, from, to);
192
+ // Can only splice arrays
193
+ const field = model.getField(key);
194
+ const isArray = field.isArray();
195
+ if (!isArray) throw Boom.badRequest(`Cannot splice field '${model}.${field}'`);
159
196
 
160
- return createSystemEvent('Mutation', { method: 'update', query: query.doc(doc).merged(doc) }, async () => {
161
- await model.validate(query, doc);
162
- const $doc = model.serialize(query, doc, true);
163
- return this.resolver.resolve(query.method('updateOne').doc(doc).$doc($doc));
197
+ return this.resolver.match(model).match(match).flags(flags).one({ required: true }).then(async (doc) => {
198
+ const array = get(doc, key) || [];
199
+ const paramShape = model.getShape('create', 'spliceTo');
200
+ const $to = model.shapeObject(paramShape, { [key]: to }, query)[key] || to;
201
+ const $from = model.shapeObject(paramShape, { [key]: from }, query)[key] || from;
202
+ set(doc, key, DataService.spliceEmbeddedArray(array, $from, $to));
203
+
204
+ await QueryService.resolveQuery(query);
205
+
206
+ return createSystemEvent('Mutation', { query: query.method('updateOne').doc(doc).merged(doc) }, async () => {
207
+ const shape = model.getShape('update');
208
+ const $doc = model.shapeObject(shape, doc, query);
209
+ await model.validate(query, $doc);
210
+ return this.resolver.resolve(query.$doc($doc));
164
211
  });
165
212
  });
166
213
  }
@@ -173,11 +220,14 @@ module.exports = class QueryResolver {
173
220
  return this.findMany(query.method('findMany'));
174
221
  }
175
222
 
176
- resolve() {
223
+ async resolve() {
177
224
  const { model, method, flags } = this.query.toObject();
178
225
 
226
+ // const resolveQueryMethods = ['findOne', 'findMany', 'count', 'createOne', 'updateOne', 'deleteOne', 'splice'];
227
+ // if (resolveQueryMethods.indexOf(method) > -1) await QueryService.resolveQuery(this.query);
228
+
179
229
  return this[method](this.query).then((data) => {
180
- if (flags.required && (data == null || isEmpty(data))) throw Boom.notFound(`${model} Not Found`);
230
+ if (flags.required && isEmpty(data)) throw Boom.notFound(`${model} Not Found`);
181
231
  if (data == null) return null; // Explicitly return null here
182
232
  return data;
183
233
  });
@@ -1,50 +1,31 @@
1
1
  const { get, set, uniq, flattenDeep } = require('lodash');
2
- const { map, keyPaths, ensureArray, isPlainObject } = require('../service/app.service');
3
-
4
- const resolveEmbeddedWhere = (ref, key, value) => {
5
- const resolved = ensureArray(map(value, (obj) => {
6
- return Object.entries(obj).reduce((p, [k, v]) => {
7
- const f = ref.getFieldByName(k);
8
-
9
- if (k === 'id') return Object.assign(p, { [k]: ref.idValue(v) });
10
- if (f.isScalar()) return Object.assign(p, { [k]: v });
11
- if (f.isEmbedded()) return Object.assign(p, { [k]: resolveEmbeddedWhere(f.getModelRef(), k, v) });
12
- return Object.assign(p, { [k]: v });
13
- }, {});
14
- }));
15
-
16
- return resolved.length > 1 ? resolved : resolved[0];
17
- };
2
+ const { keyPaths, ensureArray, isPlainObject } = require('../service/app.service');
18
3
 
4
+ /**
5
+ * The where clause may contain attributes that are NOT in the model
6
+ * This can happen because the where clause reaches into the schema via refs/virtual refs
7
+ */
19
8
  exports.resolveWhereClause = (query) => {
20
9
  const { resolver, model, match: where = {}, flags = {} } = query.toObject();
10
+ const shape = model.getShape('create', 'where');
21
11
 
22
- // This is needed for where clause (but why!?!)
23
- if (where.id) where.id = map(where.id, v => model.idValue(v));
12
+ const $where = Object.entries(where).reduce((prev, [from, value]) => {
13
+ const el = shape.find(s => s.from === from);
14
+ if (!el) return prev; // There's no knowing what this could be
24
15
 
25
- // Construct
26
- const $where = Object.entries(where).reduce((prev, [key, value]) => {
27
- const field = model.getField(key);
28
- if (!field) return prev;
29
- const modelRef = field.getModelRef();
16
+ const { isVirtual, isEmbedded, modelRef, virtualRef } = el.field.toObject();
30
17
 
31
- if (field.isVirtual()) {
32
- const virtualRef = field.getVirtualRef();
18
+ if (isVirtual) {
33
19
  const ids = Promise.all(ensureArray(value).map(v => resolver.match(modelRef).where(isPlainObject(v) ? v : { id: v }).many(flags).then(docs => docs.map(doc => doc[virtualRef])))).then(results => uniq(flattenDeep(results)));
34
20
  return Object.assign(prev, { id: ids });
35
21
  }
36
22
 
37
- if (modelRef && !field.isEmbedded()) {
23
+ if (modelRef && !isEmbedded) {
38
24
  const ids = Promise.all(ensureArray(value).map(v => (isPlainObject(v) ? resolver.match(modelRef).where(v).many(flags).then(docs => docs.map(doc => doc.id)) : Promise.resolve(v)))).then(results => uniq(flattenDeep(results)));
39
- return Object.assign(prev, { [key]: ids });
25
+ return Object.assign(prev, { [from]: ids });
40
26
  }
41
27
 
42
- // You do not have a unit-test that tests this (BUT ITS NEEDED)
43
- if (field.isEmbedded()) {
44
- return Object.assign(prev, { [key]: resolveEmbeddedWhere(modelRef, key, value) });
45
- }
46
-
47
- return Object.assign(prev, { [key]: value });
28
+ return Object.assign(prev, { [from]: value });
48
29
  }, {});
49
30
 
50
31
  // Resolve
@@ -61,7 +42,8 @@ exports.resolveWhereClause = (query) => {
61
42
 
62
43
  exports.resolveSortBy = (query) => {
63
44
  const { model, sort = {} } = query.toObject();
64
- const $sort = model.normalize(query, sort, 'serialize', true);
45
+ const shape = model.getShape('create', 'sortBy');
46
+ const $sort = model.shapeObject(shape, sort, query);
65
47
 
66
48
  // Because normalize casts the value (sometimes to an array) need special handling
67
49
  keyPaths($sort).forEach((path) => {
@@ -127,3 +109,18 @@ exports.resolveReferentialIntegrity = (query) => {
127
109
  }
128
110
  });
129
111
  };
112
+
113
+ exports.resolveQuery = async (query) => {
114
+ const { model, sort, native, batch, match } = query.toObject();
115
+
116
+ if (!native) {
117
+ const shape = model.getShape('create', 'where');
118
+ const $where = batch ? match : await exports.resolveWhereClause(query);
119
+ const $$where = model.shapeObject(shape, $where, query);
120
+ query.match($$where);
121
+ }
122
+
123
+ if (sort) {
124
+ query.$sort(exports.resolveSortBy(query));
125
+ }
126
+ };
@@ -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(el => fn(el));
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
 
@@ -141,14 +147,6 @@ exports.objectContaining = (a, b) => {
141
147
  return exports.hashObject(a) === exports.hashObject(b);
142
148
  };
143
149
 
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
150
  /**
153
151
  * Transform an object with dot.notation keys into an expanded object.
154
152
  * eg. { 'user.name': 'richard' } => { user: { name: 'richard' } }
@@ -266,3 +264,63 @@ exports.proxyDeep = (obj, handler, proxyMap = new WeakMap(), path = '') => {
266
264
 
267
265
  return finalProxy;
268
266
  };
267
+
268
+ exports.resolveDataObject = (obj) => {
269
+ return Promise.all(Object.keys(obj).map(async (key) => {
270
+ const value = await obj[key];
271
+ return { key, value };
272
+ })).then((results) => {
273
+ return results.reduce((prev, { key, value }) => {
274
+ return Object.assign(prev, { [key]: value });
275
+ }, {});
276
+ });
277
+ };
278
+
279
+ exports.seek = (obj, paths, hint) => {
280
+ // We first do a normal get
281
+ const value = _.get(obj, paths);
282
+ if (!hint || value !== undefined) return value;
283
+
284
+ // Normalize paths & hint for traversal
285
+ const $paths = Array.isArray(paths) ? paths : paths.split('.');
286
+ const $hint = exports.unravelObject(hint);
287
+
288
+ // Traverse paths and get as close to the value as possible
289
+ const { currentValue, pathsToGo } = $paths.reduce((prev, path, i, arr) => {
290
+ if (prev.currentValue === undefined) return prev;
291
+ if (!Object.prototype.hasOwnProperty.call(prev.currentValue, path)) return prev;
292
+ prev.currentValue = prev.currentValue[path];
293
+ prev.pathsToGo = arr.slice(i + 1);
294
+ return prev;
295
+ }, { currentValue: obj, pathsToGo: $paths });
296
+
297
+ // Only if we hit an array can we continue
298
+ if (!Array.isArray(currentValue)) return undefined;
299
+
300
+ // If we got to the last segment we need the hint in order to verify
301
+ const lastPath = Boolean(pathsToGo.length === 1);
302
+ const arr = lastPath ? currentValue.filter(v => exports.objectContaining(v, $hint)) : currentValue;
303
+
304
+ // We keep going, recursive, till we find the first value
305
+ return arr.reduce((prev, v) => prev || exports.seek(v, pathsToGo, $hint), undefined);
306
+ };
307
+
308
+ exports.deseek = (shape, obj, paths, hint) => {
309
+ // Normalize paths
310
+ const $paths = (Array.isArray(paths) ? paths : paths.split('.')).map((path) => {
311
+ const item = shape.find(s => s.to === path); // Deserializing from unknown to expected
312
+ return item ? item.from : path;
313
+ });
314
+
315
+ // Normalize hint
316
+ const $hint = Object.entries(exports.toKeyObj(hint)).reduce((prev, [key, value]) => {
317
+ const segments = key.split('.').map((path) => {
318
+ const item = shape.find(s => s.to === path); // Deserializing from unknown to expected
319
+ return item ? item.from : path;
320
+ });
321
+
322
+ return Object.assign(prev, { [segments.join('.')]: value });
323
+ }, {});
324
+
325
+ return exports.seek(obj, $paths, $hint);
326
+ };
@@ -1,59 +1,26 @@
1
- const QueryService = require('../query/QueryService');
2
1
  const EventEmitter = require('../core/EventEmitter');
3
- const { ensureArray, ucFirst } = require('./app.service');
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 { method, query } = mixed;
22
- const { resolver, model, meta, doc, id, input, sort, merged, native, root, crud } = query.toObject();
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
- if (result !== undefined) return result; // Allowing middleware to dictate result
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!
@@ -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
- if (`${field.getModelRef()}` === `${parentModel}`) {
57
+ const { modelRef, isArray } = field.toObject();
58
+
59
+ if (`${modelRef}` === `${parentModel}`) {
58
60
  if (model.isEntity()) {
59
- prev.push({ model, field, isArray: field.isArray(), op: field.getOnDelete() });
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: field.isArray(), op: field.getOnDelete() })));
63
+ prev.push(...exports.identifyOnDeletes(models, model).map(od => Object.assign(od, { fieldRef: field, isArray, op: field.getOnDelete() })));
62
64
  }
63
65
  }
64
66
  });
package/src/core/Rule.js DELETED
@@ -1,107 +0,0 @@
1
- const { get } = require('lodash');
2
- const isEmail = require('validator/lib/isEmail');
3
- const Boom = require('./Boom');
4
- const { map, ensureArray, hashObject } = require('../service/app.service');
5
-
6
- const instances = {};
7
- const jsStringMethods = ['endsWith', 'includes', 'match', 'search', 'startsWith'];
8
-
9
- class Rule {
10
- constructor(thunk, options = {}, name = 'Unknown') {
11
- const {
12
- ignoreNull = true,
13
- itemize = true,
14
- toError = (field, value, msg) => Boom.notAcceptable(`Rule (${name}) failed for { ${field.getModel()}.${field}: ${value} }`),
15
- } = (options || {});
16
-
17
- return Object.defineProperty((field, val, query) => {
18
- return new Promise((resolve, reject) => {
19
- if (ignoreNull && val == null) return resolve();
20
-
21
- if (ignoreNull && itemize) {
22
- return Promise.all(ensureArray(map(val, async (v) => {
23
- const err = await thunk(field, v, query);
24
- if (err) return Promise.reject(toError(field, v));
25
- return Promise.resolve();
26
- }))).then(v => resolve()).catch(e => reject(e));
27
- }
28
-
29
- return Promise.all([(async () => {
30
- const err = await thunk(field, val, query);
31
- if (err) return Promise.reject(toError(field, val));
32
- return Promise.resolve();
33
- })()]).then(v => resolve()).catch(e => reject(e));
34
- });
35
- }, 'type', { value: 'rule' });
36
- }
37
-
38
- static factory(name, thunk, options = {}) {
39
- return Object.defineProperty(Rule, name, {
40
- value: (...args) => Object.defineProperty(new Rule(thunk(...args), options, name), 'method', { value: name }),
41
- writable: options.writable,
42
- enumerable: options.enumerable,
43
- })[name];
44
- }
45
-
46
- static extend(name, instance) {
47
- const invalidArg = () => { throw new Error('Invalid argument; expected Rule factory instance'); };
48
- const { method = invalidArg(), type = invalidArg() } = instance;
49
- if (type !== 'rule' || !Rule[method]) invalidArg();
50
- return (instances[name] = instance);
51
- }
52
-
53
- static getInstances() {
54
- const defaultRules = Object.entries(Rule).map(([name, method]) => ({ name, instance: method() }));
55
- const customRules = Object.entries(instances).map(([name, instance]) => ({ name, instance }));
56
- const rules = defaultRules.concat(customRules);
57
- return rules.reduce((prev, { name, instance }) => Object.assign(prev, { [name]: instance }), {});
58
- }
59
- }
60
-
61
- // Factory methods
62
- jsStringMethods.forEach(name => Rule.factory(name, (...args) => (f, v) => !String(v)[name](...args)));
63
-
64
- // Ensures Foreign Key relationships
65
- Rule.factory('ensureId', () => (f, v, q) => {
66
- const { resolver } = q.toObject();
67
- return resolver.match(f.getType()).id(v).one().then(doc => Boolean(doc == null));
68
- });
69
-
70
- // Enforces required fields (only during create)
71
- Rule.factory('required', () => (f, v, q) => {
72
- const { crud, input } = q.toObject();
73
- return (crud === 'create' ? v == null : Object.prototype.hasOwnProperty.call(input, f.getName()) && v == null);
74
- }, { ignoreNull: false, enumerable: true });
75
-
76
- // A field cannot hold a reference to itself (model)
77
- Rule.factory('selfless', () => (f, v, q) => {
78
- const { doc } = q.toObject();
79
- if (`${v}` === `${get(doc, 'id')}`) throw Boom.badRequest(`${f.getModel()}.${f.getName()} cannot hold a reference to itself`);
80
- return false;
81
- }, { enumerable: true });
82
-
83
- // Once set it cannot be changed
84
- Rule.factory('immutable', () => (f, v, q) => {
85
- const { doc, crud } = q.toObject();
86
- const path = `${f.getModel()}.${f.getName()}`;
87
- const p = path.substr(path.indexOf('.') + 1);
88
- const oldVal = get(doc, p);
89
- if (crud === 'update' && oldVal !== undefined && v !== undefined && `${hashObject(v)}` !== `${hashObject(oldVal)}`) throw Boom.badRequest(`${path} is immutable; cannot be changed once set`);
90
- return false;
91
- }, { enumerable: true });
92
-
93
- Rule.factory('allow', (...args) => (f, v) => args.indexOf(v) === -1);
94
- Rule.factory('deny', (...args) => (f, v) => args.indexOf(v) > -1);
95
- Rule.factory('range', (min, max) => {
96
- if (min == null) min = undefined;
97
- if (max == null) max = undefined;
98
- return (f, v) => {
99
- const num = +v; // Coerce to number if possible
100
- const test = Number.isNaN(num) ? v.length : num;
101
- return test < min || test > max;
102
- };
103
- }, { itemize: false });
104
- Rule.factory('email', () => (f, v) => !isEmail(v), { enumerable: true });
105
- Rule.factory('distinct', () => (f, v) => false, { enumerable: true });
106
-
107
- module.exports = Rule;