@coderich/autograph 0.13.21 → 0.13.23

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.23",
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
  }
@@ -237,7 +229,7 @@ module.exports = class Resolver {
237
229
  return (...args) => {
238
230
  switch (cmd) {
239
231
  case 'save': {
240
- return queryResolver.save({ ...$doc, ...args[0] });
232
+ return queryResolver.save({ ...$doc, ...args[0] }); // $doc incase it's mutated
241
233
  }
242
234
  case 'lookup': {
243
235
  const field = self.toModel(model).fields[args[0]];
@@ -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 } });
@@ -1,6 +1,6 @@
1
1
  const Util = require('@coderich/util');
2
2
  const Pipeline = require('../data/Pipeline');
3
- const { isGlob, globToRegex, mergeDeep, finalizeWhereClause } = require('../service/AppService');
3
+ const { isGlob, globToRegex, mergeDeep, finalizeWhereClause, JSONParse } = require('../service/AppService');
4
4
 
5
5
  module.exports = class Query {
6
6
  #config;
@@ -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
  }
@@ -81,8 +82,8 @@ module.exports = class Query {
81
82
  input: this.#model.walk(input, node => node.value !== undefined && Object.assign(node, { key: node.field.key })),
82
83
  where: isNative ? where : this.#model.walk(where, node => Object.assign(node, { key: node.field.key })),
83
84
  sort: this.#model.walk(sort, node => Object.assign(node, { key: node.field.key })),
84
- before: (!isCursorPaging || !before) ? undefined : JSON.parse(Buffer.from(before, 'base64').toString('ascii')),
85
- after: (!isCursorPaging || !after) ? undefined : JSON.parse(Buffer.from(after, 'base64').toString('ascii')),
85
+ before: (!isCursorPaging || !before) ? undefined : JSONParse(Buffer.from(before, 'base64').toString('ascii')),
86
+ after: (!isCursorPaging || !after) ? undefined : JSONParse(Buffer.from(after, 'base64').toString('ascii')),
86
87
  $schema: this.#schema.resolvePath,
87
88
  });
88
89
 
@@ -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) => {
@@ -68,7 +68,7 @@ module.exports = class QueryBuilder {
68
68
  }
69
69
 
70
70
  info(info) {
71
- this.select(getGQLSelectFields(this.#query.model, info));
71
+ // this.select(getGQLSelectFields(this.#query.model, info));
72
72
  return this;
73
73
  }
74
74
 
@@ -82,7 +82,7 @@ module.exports = class QueryBuilder {
82
82
  }
83
83
 
84
84
  where(clause) {
85
- this.#propCheck('where', 'native', false);
85
+ this.#propCheck('where', 'native', false); // Allow redefine of "where" because we merge it
86
86
  const $clause = mergeDeep(this.#query.where || {}, clause);
87
87
  this.#query.where = $clause;
88
88
  this.#query.args.where = $clause;
@@ -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
  }
@@ -589,7 +589,7 @@ module.exports = class Schema {
589
589
  }
590
590
  ${connectionFields.length ? `
591
591
  extend type ${model} {
592
- ${connectionFields.map(field => `${field}: ${field.model}Connection`)}
592
+ ${connectionFields.map(field => `${field}(${Schema.#getConnectionArguments(field.model)}): ${field.model}Connection`)}
593
593
  }
594
594
  ` : ''}
595
595
  `;
@@ -619,16 +619,7 @@ module.exports = class Schema {
619
619
  node(id: ID!): Node
620
620
  ${queryModels.map(model => `
621
621
  get${model}(id: ID!): ${model}
622
- find${model}(
623
- where: ${model}InputWhere
624
- sortBy: ${model}InputSort
625
- limit: Int
626
- skip: Int
627
- first: Int
628
- after: String
629
- last: Int
630
- before: String
631
- ): ${model}Connection!
622
+ find${model}(${Schema.#getConnectionArguments(model)}): ${model}Connection!
632
623
  `)}
633
624
  }
634
625
 
@@ -713,13 +704,7 @@ module.exports = class Schema {
713
704
  Query: queryModels.reduce((prev, model) => {
714
705
  return Object.assign(prev, {
715
706
  [`get${model}`]: (doc, args, context, info) => context[schema.namespace].resolver.match(model).args(args).info(info).one({ required: true }),
716
- [`find${model}`]: (doc, args, context, info) => {
717
- return {
718
- edges: () => context[schema.namespace].resolver.match(model).args(args).info(info).many(),
719
- count: () => context[schema.namespace].resolver.match(model).args(args).info(info).count(),
720
- pageInfo: () => context[schema.namespace].resolver.match(model).args(args).info(info).many(),
721
- };
722
- },
707
+ [`find${model}`]: (doc, args, context, info) => context[schema.namespace].resolver.match(model).args(args).info(info).resolve(info),
723
708
  });
724
709
  }, {
725
710
  node: (doc, args, context, info) => {
@@ -746,7 +731,7 @@ module.exports = class Schema {
746
731
  [model]: Object.values(model.fields).filter(field => field.model?.isEntity).reduce((prev2, field) => {
747
732
  return Object.assign(prev2, {
748
733
  [field]: (doc, args, context, info) => {
749
- return context[schema.namespace].resolver.match(field.model).where({ [field.linkBy]: doc[field.linkField.name] }).args(args).info(info).resolve(info);
734
+ return doc.$.lookup(field).args(args).info(info).resolve(info);
750
735
  },
751
736
  });
752
737
  }, {}),
@@ -767,6 +752,19 @@ module.exports = class Schema {
767
752
  return type;
768
753
  }
769
754
 
755
+ static #getConnectionArguments(model) {
756
+ return `
757
+ where: ${model}InputWhere
758
+ sortBy: ${model}InputSort
759
+ limit: Int
760
+ skip: Int
761
+ first: Int
762
+ after: String
763
+ last: Int
764
+ before: String
765
+ `;
766
+ }
767
+
770
768
  static #identifyOnDeletes(models, parentName) {
771
769
  return models.reduce((prev, model) => {
772
770
  Object.values(model.fields).filter(f => f.onDelete).forEach((field) => {
@@ -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,17 @@ 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
- }, {}));
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
+ // };
46
+
47
+ exports.JSONParse = (mixed) => {
48
+ try {
49
+ const json = JSON.parse(mixed);
50
+ return json;
51
+ } catch (e) {
52
+ return undefined;
53
+ }
46
54
  };