@coderich/autograph 0.13.21 → 0.13.22

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coderich/autograph",
3
3
  "main": "index.js",
4
- "version": "0.13.21",
4
+ "version": "0.13.22",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -15,7 +15,7 @@
15
15
  "dev": "coderich-dev"
16
16
  },
17
17
  "dependencies": {
18
- "@coderich/util": "1.0.2",
18
+ "@coderich/util": "1.0.3",
19
19
  "@graphql-tools/merge": "9.0.0",
20
20
  "@graphql-tools/resolvers-composition": "7.0.0",
21
21
  "@hapi/boom": "10.0.1",
@@ -12,20 +12,21 @@ class Emitter extends EventEmitter {
12
12
  emit(event, data) {
13
13
  // Here we pull out functions with "next" vs those without
14
14
  const [basicFuncs, nextFuncs] = this.rawListeners(event).reduce((prev, wrapper) => {
15
- const listener = wrapper.listener || wrapper;
15
+ const { listener = wrapper } = wrapper;
16
16
  const isBasic = listener.length < 2;
17
+ wrapper.priority = listener.priority ?? 0;
17
18
  return prev[isBasic ? 0 : 1].push(wrapper) && prev;
18
19
  }, [[], []]);
19
20
 
20
21
  return new Promise((resolve, reject) => {
21
22
  // Basic functions run first; if they return a value they abort the flow of execution
22
- basicFuncs.forEach((fn) => {
23
+ basicFuncs.sort(Emitter.sort).forEach((fn) => {
23
24
  const value = fn(data);
24
25
  if (value !== undefined && !(value instanceof Promise)) throw new AbortEarlyError(value);
25
26
  });
26
27
 
27
28
  // Next functions are async and control the timing of the next phase
28
- Promise.all(nextFuncs.map((fn) => {
29
+ Promise.all(nextFuncs.sort(Emitter.sort).map((fn) => {
29
30
  return new Promise((next) => {
30
31
  Promise.resolve(fn(data, next));
31
32
  }).then((result) => {
@@ -38,35 +39,45 @@ class Emitter extends EventEmitter {
38
39
  });
39
40
  }
40
41
 
42
+ on(event, listener, priority = 0) {
43
+ listener.priority = priority;
44
+ return super.on(event, listener);
45
+ }
46
+
47
+ prependListener(event, listener, priority = 0) {
48
+ listener.priority = priority;
49
+ return super.prependListener(event, listener);
50
+ }
51
+
41
52
  /**
42
53
  * Syntactic sugar to listen on query keys
43
54
  */
44
55
  onKeys(...args) {
45
- return this.#createWrapper(...args, 'key');
56
+ return this.#createWrapper('key', false, ...args,);
46
57
  }
47
58
 
48
59
  /**
49
60
  * Syntactic sugar to listen once on query keys
50
61
  */
51
62
  onceKeys(...args) {
52
- return this.#createWrapper(...args, 'key', true);
63
+ return this.#createWrapper('key', true, ...args);
53
64
  }
54
65
 
55
66
  /**
56
67
  * Syntactic sugar to listen on query models
57
68
  */
58
69
  onModels(...args) {
59
- return this.#createWrapper(...args, 'model');
70
+ return this.#createWrapper('model', false, ...args);
60
71
  }
61
72
 
62
73
  /**
63
74
  * Syntactic sugar to listen once on query models
64
75
  */
65
76
  onceModels(...args) {
66
- return this.#createWrapper(...args, 'model', true);
77
+ return this.#createWrapper('model', true, ...args);
67
78
  }
68
79
 
69
- #createWrapper(eventName, arr, listener, prop, once) {
80
+ #createWrapper(prop, once, eventName, arr, listener, priority) {
70
81
  arr = Util.ensureArray(arr);
71
82
 
72
83
  const wrapper = listener.length < 2 ? (event) => {
@@ -83,7 +94,13 @@ class Emitter extends EventEmitter {
83
94
  return next();
84
95
  };
85
96
 
86
- return this.on(eventName, wrapper);
97
+ return this.on(eventName, wrapper, priority);
98
+ }
99
+
100
+ static sort(a, b) {
101
+ if (a.priority > b.priority) return -1;
102
+ if (a.priority < b.priority) return 1;
103
+ return 0;
87
104
  }
88
105
  }
89
106
 
@@ -189,19 +189,11 @@ module.exports = class Resolver {
189
189
  */
190
190
  async resolve(query) {
191
191
  let thunk;
192
- const tquery = await query.transform();
193
- const oquery = Object.defineProperties(tquery.toObject(), {
194
- changeset: {
195
- get: function get() {
196
- return oquery.crud === 'update' ? Util.changeset(this.doc, this.input) : undefined;
197
- },
198
- },
199
- });
200
- const model = this.#schema.models[oquery.model];
192
+ const { model, doc, crud, isMutation, flags } = query.toObject();
201
193
  const currSession = this.#sessions.slice(-1).pop();
202
194
 
203
- if (oquery.isMutation) {
204
- thunk = () => model.source.client.resolve(tquery.toDriver().toObject()).then((results) => {
195
+ if (isMutation) {
196
+ thunk = tquery => this.#schema.models[model].source.client.resolve(tquery.toDriver().toObject()).then((results) => {
205
197
  // We clear the cache immediately (regardless if we're in transaction or not)
206
198
  this.clear(model);
207
199
 
@@ -209,16 +201,16 @@ module.exports = class Resolver {
209
201
  currSession?.thunks.push(...this.#sessions.map(s => () => s.parent.clear(model)));
210
202
 
211
203
  // Return results
212
- return oquery.crud === 'delete' ? oquery.doc : results;
204
+ return crud === 'delete' ? doc : results;
213
205
  });
214
206
  } else {
215
- thunk = () => this.#dataLoaders[model].resolve(tquery);
207
+ thunk = tquery => this.#dataLoaders[model].resolve(tquery);
216
208
  }
217
209
 
218
- return this.#createSystemEvent(tquery, () => {
219
- return thunk().then((result) => {
220
- if (oquery.flags?.required && (result == null || result?.length === 0)) throw Boom.notFound();
221
- return oquery.crud === 'delete' ? result : this.toResultSet(model, result);
210
+ return this.#createSystemEvent(query, (tquery) => {
211
+ return thunk(tquery).then((result) => {
212
+ if (flags?.required && (result == null || result?.length === 0)) throw Boom.notFound();
213
+ return crud === 'delete' ? result : this.toResultSet(model, result);
222
214
  });
223
215
  });
224
216
  }
@@ -274,8 +266,8 @@ module.exports = class Resolver {
274
266
  }, {});
275
267
  }
276
268
 
277
- #createSystemEvent(tquery, thunk = () => {}) {
278
- const query = tquery.toObject();
269
+ #createSystemEvent($query, thunk = () => {}) {
270
+ const query = $query.toObject();
279
271
  const type = query.isMutation ? 'Mutation' : 'Query';
280
272
  const event = { schema: this.#schema, context: this.#context, resolver: this, query };
281
273
 
@@ -287,23 +279,15 @@ module.exports = class Resolver {
287
279
  event.input = event.args?.input;
288
280
 
289
281
  return Emitter.emit(`pre${type}`, event).then(async (resultEarly) => {
290
- if (resultEarly !== undefined) return resultEarly;
291
- if (Util.isEqual(query.changeset, { added: {}, updated: {}, deleted: {} })) return query.doc;
292
- if (query.isMutation) query.input = await tquery.pipeline('input', query.input, ['$finalize']);
293
- if (query.isMutation) await Emitter.emit('finalize', event);
294
- return thunk().then((result) => {
282
+ if (resultEarly !== undefined) return resultEarly; // Nothing to validate/transform
283
+ // if (query.crud === 'update' && Util.isEqual({ added: {}, updated: {}, deleted: {} }, Util.changeset(query.doc, query.input))) return query.doc;
284
+ const tquery = await $query.transform();
285
+ // await Emitter.emit('validate', event); // We need to re-connect tquery to event
286
+ return thunk(tquery).then((result) => {
295
287
  event.result = result; // backwards compat
296
288
  query.result = result;
297
289
  return Emitter.emit(`post${type}`, event);
298
290
  });
299
- }).then((result = query.result) => {
300
- event.result = result; // backwards compat
301
- query.result = result;
302
- return Emitter.emit('preResponse', event);
303
- }).then((result = query.result) => {
304
- event.result = result; // backwards compat
305
- query.result = result;
306
- return Emitter.emit('postResponse', event);
307
291
  }).then((result = query.result) => result).catch((e) => {
308
292
  const { data = {} } = e;
309
293
  throw Boom.boomify(e, { data: { ...event, ...data } });
@@ -49,11 +49,12 @@ module.exports = class Query {
49
49
  * Run a portion of the pipeline against a data set
50
50
  */
51
51
  pipeline(target, data, transformers) {
52
- data = Util.unflatten(data);
52
+ data = Util.unflatten(data, { safe: true });
53
53
  const crudMap = { create: ['$construct', '$serialize'], update: ['$restruct', '$serialize'] };
54
54
  const crudLines = crudMap[this.#query.crud] || [];
55
55
  const transformerMap = { where: ['$cast', '$instruct', '$serialize'], sort: [], input: [] };
56
- if (this.#query.isMutation) transformerMap.input = ['$default', '$cast', '$normalize', '$instruct', ...crudLines];
56
+ if (this.#query.isMutation) transformerMap.input = ['$default', '$cast', '$normalize', '$instruct', ...crudLines, '$finalize'];
57
+ // if (this.#query.crud === 'create') transformerMap.input.unshift('$default'); // Cant because embedded documents on update are really "creates"
57
58
  transformers = transformers || transformerMap[target];
58
59
  return this.#pipeline(this.#query, target, this.#model, data, transformers.map(el => Pipeline[el]));
59
60
  }
@@ -128,7 +129,7 @@ module.exports = class Query {
128
129
  const { where = {}, sort = {} } = query;
129
130
  const flatSort = Util.flatten(sort, { safe: true });
130
131
  const flatWhere = Util.flatten(where, { safe: true });
131
- const $sort = Util.unflatten(Object.keys(flatSort).reduce((prev, key) => Object.assign(prev, { [key]: {} }), {}));
132
+ const $sort = Util.unflatten(Object.keys(flatSort).reduce((prev, key) => Object.assign(prev, { [key]: {} }), {}), { safe: true });
132
133
 
133
134
  //
134
135
  query.sort = this.#model.walk(sort, (node) => {
@@ -34,7 +34,7 @@ module.exports = class QueryResolver extends QueryBuilder {
34
34
  }
35
35
  case 'updateOne': {
36
36
  return this.#get(query).then((doc) => {
37
- const merged = mergeDeep({}, Util.unflatten(doc), Util.unflatten(input));
37
+ const merged = mergeDeep({}, Util.unflatten(doc, { safe: true }), Util.unflatten(input, { safe: true }));
38
38
  return this.#resolver.resolve(query.clone({ doc, input: merged }));
39
39
  });
40
40
  }
@@ -11,8 +11,7 @@ exports.isGlob = str => PicoMatch.scan(str).isGlob;
11
11
  exports.globToRegex = (glob, options = {}) => PicoMatch.makeRe(glob, { nocase: true, ...options, expandRange: (a, b) => `(${FillRange(a, b, { toRegex: true })})` });
12
12
 
13
13
  const smartMerge = (target, source, options) => source;
14
- exports.isScalarValue = value => typeof value !== 'object' && typeof value !== 'function';
15
- exports.isLeafValue = value => Array.isArray(value) || value instanceof Date || ObjectId.isValid(value) || exports.isScalarValue(value);
14
+ exports.isLeafValue = value => Array.isArray(value) || value instanceof Date || ObjectId.isValid(value) || Util.isScalarValue(value);
16
15
  exports.mergeDeep = (...args) => DeepMerge.all(args, { isMergeableObject: obj => (Util.isPlainObjectOrArray(obj)), arrayMerge: smartMerge });
17
16
  exports.hashObject = obj => ObjectHash(obj, { respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedArrays: true, ignoreUnknown: true, replacer: r => (ObjectId.isValid(r) ? `${r}` : r) });
18
17
  exports.fromGUID = guid => Buffer.from(`${guid}`, 'base64').toString('ascii').split(',');
@@ -39,8 +38,8 @@ exports.getGQLSelectFields = (model, info) => {
39
38
  return Object.keys(node || fields);
40
39
  };
41
40
 
42
- exports.removeUndefinedDeep = (obj) => {
43
- return Util.unflatten(Object.entries(Util.flatten(obj)).reduce((prev, [key, value]) => {
44
- return value === undefined ? prev : Object.assign(prev, { [key]: value });
45
- }, {}));
46
- };
41
+ // exports.removeUndefinedDeep = (obj) => {
42
+ // return Util.unflatten(Object.entries(Util.flatten(obj)).reduce((prev, [key, value]) => {
43
+ // return value === undefined ? prev : Object.assign(prev, { [key]: value });
44
+ // }, {}));
45
+ // };