@coderich/autograph 0.11.1 → 0.13.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.
Files changed (56) hide show
  1. package/index.js +4 -6
  2. package/package.json +30 -44
  3. package/src/data/DataLoader.js +77 -70
  4. package/src/data/Emitter.js +89 -0
  5. package/src/data/Loader.js +33 -0
  6. package/src/data/Pipeline.js +88 -96
  7. package/src/data/Resolver.js +304 -0
  8. package/src/data/Transaction.js +49 -0
  9. package/src/query/Query.js +159 -334
  10. package/src/query/QueryBuilder.js +228 -114
  11. package/src/query/QueryResolver.js +110 -216
  12. package/src/query/QueryResolverTransaction.js +16 -0
  13. package/src/schema/Schema.js +593 -0
  14. package/src/service/AppService.js +38 -0
  15. package/src/service/ErrorService.js +7 -0
  16. package/CHANGELOG.md +0 -41
  17. package/LICENSE +0 -21
  18. package/README.md +0 -76
  19. package/src/.DS_Store +0 -0
  20. package/src/core/.DS_Store +0 -0
  21. package/src/core/Boom.js +0 -9
  22. package/src/core/EventEmitter.js +0 -95
  23. package/src/core/Resolver.js +0 -124
  24. package/src/core/Schema.js +0 -55
  25. package/src/core/ServerResolver.js +0 -15
  26. package/src/data/.DS_Store +0 -0
  27. package/src/data/DataService.js +0 -120
  28. package/src/data/DataTransaction.js +0 -96
  29. package/src/data/Field.js +0 -83
  30. package/src/data/Model.js +0 -223
  31. package/src/data/TreeMap.js +0 -78
  32. package/src/data/Type.js +0 -50
  33. package/src/driver/.DS_Store +0 -0
  34. package/src/driver/MongoDriver.js +0 -227
  35. package/src/driver/index.js +0 -11
  36. package/src/graphql/.DS_Store +0 -0
  37. package/src/graphql/ast/.DS_Store +0 -0
  38. package/src/graphql/ast/Field.js +0 -206
  39. package/src/graphql/ast/Model.js +0 -145
  40. package/src/graphql/ast/Node.js +0 -291
  41. package/src/graphql/ast/Schema.js +0 -133
  42. package/src/graphql/ast/Type.js +0 -26
  43. package/src/graphql/ast/TypeDefApi.js +0 -93
  44. package/src/graphql/extension/.DS_Store +0 -0
  45. package/src/graphql/extension/api.js +0 -193
  46. package/src/graphql/extension/framework.js +0 -71
  47. package/src/graphql/extension/type.js +0 -34
  48. package/src/query/.DS_Store +0 -0
  49. package/src/query/QueryBuilderTransaction.js +0 -26
  50. package/src/query/QueryService.js +0 -111
  51. package/src/service/.DS_Store +0 -0
  52. package/src/service/app.service.js +0 -319
  53. package/src/service/decorator.service.js +0 -114
  54. package/src/service/event.service.js +0 -66
  55. package/src/service/graphql.service.js +0 -92
  56. package/src/service/schema.service.js +0 -95
@@ -1,361 +1,186 @@
1
- module.exports = class Query {
2
- constructor(props = {}) {
3
- this.props = {};
4
- this.timers = {};
5
- this.isCursorPaging = false;
6
- this.isClassicPaging = false;
7
- this.props.joins = this.props.joins || [];
8
- this.props.match = this.props.match || {};
9
- this.props.options = this.props.options || {};
10
- this.props.flags = this.props.flags || {
11
- debug: false,
12
- silent: false,
13
- validate: true, // { externals }
14
- pipeline: true, // { instruct, construct, destruct, restruct, serialize, deserialize, transform, rekey }
15
- required: false,
16
- };
17
- this.merge(props);
18
- }
19
-
20
- propCheck(prop, ...checks) {
21
- checks.forEach((check) => {
22
- if (this.props[check]) throw new Error(`Cannot use "${prop}" while using "${check}"`);
23
- });
24
- }
25
-
26
- id(id) {
27
- this.propCheck('id', 'where', 'native', 'sort', 'skip', 'limit', 'before', 'after', 'first', 'last');
28
- this.props.id = id;
29
- this.props.batch = 'id';
30
- this.props.where = { id };
31
- return this.match({ id });
32
- }
33
-
34
- batch(batch) {
35
- this.props.batch = batch;
36
- return this;
37
- }
38
-
39
- where(where) {
40
- this.propCheck('where', 'id', 'native');
41
- this.props.where = where;
42
- return this.match(where);
43
- }
44
-
45
- search(search) {
46
- this.propCheck('search', 'id');
47
- this.props.search = search;
48
- return this;
49
- }
50
-
51
- native(native) {
52
- this.propCheck('native', 'id', 'where');
53
- this.props.native = native;
54
- return this.match(native);
55
- }
56
-
57
- match(match) {
58
- this.props.match = match;
59
- return this;
60
- }
61
-
62
- select(select) {
63
- this.props.select = select;
64
- return this;
65
- }
66
-
67
- $select(select) {
68
- this.props.$select = select;
69
- return this;
70
- }
71
-
72
- fields(fields) {
73
- this.props.select = fields;
74
- return this;
75
- }
76
-
77
- joins(...joins) {
78
- this.props.joins.push(...joins);
79
- return this;
80
- }
1
+ const Util = require('@coderich/util');
2
+ const Pipeline = require('../data/Pipeline');
3
+ const { isGlob, globToRegex, mergeDeep, finalizeWhereClause } = require('../service/AppService');
81
4
 
82
- sort(sort) {
83
- this.propCheck('sort', 'id');
84
- this.props.sort = sort;
85
- return this;
86
- }
87
-
88
- $sort(sort) {
89
- this.props.$sort = sort;
90
- return this;
91
- }
92
-
93
- sortBy(sort) {
94
- return this.sort(sort);
95
- }
96
-
97
- skip(skip) {
98
- this.propCheck('skip', 'id');
99
- if (this.isCursorPaging) throw new Error('Cannot use "skip" while using Cursor-Style Pagination');
100
- this.isClassicPaging = true;
101
- this.props.skip = skip;
102
- return this;
103
- }
104
-
105
- limit(limit) {
106
- this.propCheck('limit', 'id');
107
- if (this.isCursorPaging) throw new Error('Cannot use "limit" while using Cursor-Style Pagination');
108
- this.isClassicPaging = true;
109
- this.props.limit = limit;
110
- return this;
111
- }
112
-
113
- first(first) {
114
- this.propCheck('first', 'id', 'last');
115
- if (this.isClassicPaging) throw new Error('Cannot use "first" while using Classic-Style Pagination');
116
- this.isCursorPaging = true;
117
- this.props.first = first + 2; // Adding 2 for pagination meta info (hasNext hasPrev)
118
- return this;
119
- }
120
-
121
- last(last) {
122
- this.propCheck('last', 'id', 'first');
123
- if (this.isClassicPaging) throw new Error('Cannot use "last" while using Classic-Style Pagination');
124
- this.isCursorPaging = true;
125
- this.props.last = last + 2; // Adding 2 for pagination meta info (hasNext hasPrev)
126
- return this;
127
- }
128
-
129
- before(before) {
130
- this.propCheck('before', 'id');
131
- if (this.isClassicPaging) throw new Error('Cannot use "before" while using Classic-Style Pagination');
132
- this.isCursorPaging = true;
133
- this.props.before = before;
134
- return this;
135
- }
136
-
137
- after(after) {
138
- this.propCheck('after', 'id');
139
- if (this.isClassicPaging) throw new Error('Cannot use "after" while using Classic-Style Pagination');
140
- this.isCursorPaging = true;
141
- this.props.after = after;
142
- return this;
143
- }
5
+ module.exports = class Query {
6
+ #config;
7
+ #resolver;
8
+ #context;
9
+ #schema;
10
+ #model;
11
+ #query;
144
12
 
145
- options(options) {
146
- this.props.options = options;
147
- return this;
13
+ constructor(config) {
14
+ const { schema, context, resolver, query } = config;
15
+ this.#config = config;
16
+ this.#resolver = resolver;
17
+ this.#context = context;
18
+ this.#schema = schema;
19
+ this.#model = schema.models[query.model];
20
+ this.#query = query;
148
21
  }
149
22
 
150
- meta(meta) {
151
- this.props.meta = meta;
152
- return this;
23
+ clone(query) {
24
+ query = { ...this.#query, ...query }; // NO deepMerge here; must replace fields entirely
25
+ return new Query({ ...this.#config, query });
153
26
  }
154
27
 
155
- flags(flags) {
156
- Object.assign(this.props.flags, flags);
157
- return this;
28
+ toObject() {
29
+ return this.#query;
158
30
  }
159
31
 
160
- root(root) {
161
- this.props.root = root;
162
- return this;
32
+ toCacheKey() {
33
+ return {
34
+ op: this.#query.op,
35
+ where: this.#query.where,
36
+ sort: this.#query.sort,
37
+ joins: this.#query.joins,
38
+ skip: this.#query.skip,
39
+ limit: this.#query.limit,
40
+ before: this.#query.before,
41
+ after: this.#query.after,
42
+ first: this.#query.first,
43
+ last: this.#query.last,
44
+ };
163
45
  }
164
46
 
165
47
  /**
166
- * Merge unknown attributes into props; hence the check to do a noop
48
+ * Run a portion of the pipeline against a data set
167
49
  */
168
- merge(query) {
169
- Object.entries(query).forEach(([key, value]) => { if (this[key]) this[key](value); }); // Call method only if exists
170
- return this;
50
+ pipeline(target, data, transformers) {
51
+ data = Util.unflatten(data);
52
+ const crudMap = { create: ['$construct', '$serialize'], update: ['$restruct', '$serialize'] };
53
+ const crudLines = crudMap[this.#query.crud] || [];
54
+ const transformerMap = { where: ['$cast', '$instruct', '$serialize'], sort: [], input: [] };
55
+ if (this.#query.isMutation) transformerMap.input = ['$default', '$cast', '$normalize', '$instruct', ...crudLines];
56
+ transformers = transformers || transformerMap[target];
57
+ return this.#pipeline(this.#query, target, this.#model, data, transformers.map(el => Pipeline[el]));
171
58
  }
172
59
 
173
- resolver(resolver) {
174
- this.props.resolver = resolver;
175
- this.props.context = resolver.getContext();
176
- return this;
60
+ /**
61
+ * Transform entire query via pipeline. At minimum, pipeline is needed to unflatten the data...
62
+ */
63
+ transform() {
64
+ return Promise.all([
65
+ this.pipeline('input', this.#query.input),
66
+ this.#query.isNative ? this.#query.where : this.pipeline('where', this.#query.where),
67
+ this.pipeline('sort', this.#query.sort),
68
+ ]).then(([input, where, sort]) => this.clone({ input, where, sort }));
177
69
  }
178
70
 
179
- context(context) {
180
- this.props.context = context;
181
- return this;
182
- }
71
+ /**
72
+ * Transform entire query for driver
73
+ */
74
+ toDriver() {
75
+ const { input, where, sort, before, after, isNative, isCursorPaging } = this.#query;
76
+
77
+ const query = this.clone({
78
+ select: Object.values(this.#model.fields).map(field => field.key),
79
+ input: this.#model.walk(input, node => node.value !== undefined && Object.assign(node, { key: node.field.key })),
80
+ where: isNative ? where : this.#model.walk(where, node => Object.assign(node, { key: node.field.key })),
81
+ sort: this.#model.walk(sort, node => Object.assign(node, { key: node.field.key })),
82
+ before: (!isCursorPaging || !before) ? undefined : JSON.parse(Buffer.from(before, 'base64').toString('ascii')),
83
+ after: (!isCursorPaging || !after) ? undefined : JSON.parse(Buffer.from(after, 'base64').toString('ascii')),
84
+ $schema: this.#schema.resolvePath,
85
+ });
183
86
 
184
- model(model) {
185
- this.props.model = model;
186
- return this;
187
- }
87
+ if (!isNative) this.#finalize(query.toObject());
188
88
 
189
- transaction(transaction) {
190
- this.props.transaction = transaction;
191
- return this;
89
+ return query;
192
90
  }
193
91
 
194
- cmd(cmd) {
195
- this.props.cmd = cmd; // Terminal cmd from QueryBuilder
196
- return this;
92
+ /**
93
+ * Recursive pipeline function
94
+ */
95
+ #pipeline(query, target, model, data, transformers = [], paths = []) {
96
+ return Util.mapPromise(data, (doc, index) => {
97
+ const path = [...paths];
98
+ if (Array.isArray(data)) path.push(index);
99
+ if (target === 'input') doc = mergeDeep(model.pipelineFields.input, doc);
100
+ else if (target === 'where') doc = mergeDeep(model.pipelineFields.where, doc);
101
+
102
+ return Util.pipeline(Object.entries(doc).map(([key, startValue]) => async (prev) => {
103
+ const field = model.fields[key];
104
+ if (!field) return prev;
105
+
106
+ // Transform value
107
+ let $value = await Util.pipeline(transformers.map(t => async (value) => {
108
+ const v = await t({ query, model, field, value, path: path.concat(key), startValue, resolver: this.#resolver, context: this.#context, schema: this.#schema });
109
+ return v === undefined ? value : v;
110
+ }), startValue);
111
+
112
+ // If it's embedded - delegate
113
+ if (field.isEmbedded) $value = await this.#pipeline(query, target, field.model, $value, transformers, path.concat(key));
114
+
115
+ // Assign it back
116
+ if (target === 'input' && $value === undefined) return prev;
117
+ return Object.assign(prev, { [field.name]: $value });
118
+ }), {});
119
+ });
197
120
  }
198
121
 
199
- method(method) {
200
- this.props.method = method;
201
-
202
- switch (method) {
203
- case 'createOne': case 'createMany': {
204
- this.props.crud = 'create';
205
- this.props.key = `create${this.props.model}`;
206
- break;
207
- }
208
- case 'updateOne': case 'updateMany': {
209
- this.props.crud = 'update';
210
- this.props.key = `update${this.props.model}`;
211
- break;
212
- }
213
- case 'deleteOne': case 'deleteMany': case 'removeOne': case 'removeMany': {
214
- this.props.crud = 'delete';
215
- this.props.key = `delete${this.props.model}`;
216
- break;
217
- }
218
- case 'count': {
219
- this.props.crud = 'read';
220
- this.props.key = `count${this.props.model}`;
221
- break;
222
- }
223
- case 'findOne': {
224
- this.props.crud = 'read';
225
- this.props.key = `get${this.props.model}`;
226
- break;
227
- }
228
- case 'findMany': {
229
- this.props.crud = 'read';
230
- this.props.key = `find${this.props.model}`;
231
- break;
232
- }
233
- default: {
234
- this.props.crud = 'read';
235
- break;
122
+ /**
123
+ * Finalize the query for the driver
124
+ */
125
+ #finalize(query) {
126
+ const { where = {}, sort = {} } = query;
127
+ const flatSort = Util.flatten(sort, { safe: true });
128
+ const flatWhere = Util.flatten(where, { safe: true });
129
+ const $sort = Util.unflatten(Object.keys(flatSort).reduce((prev, key) => Object.assign(prev, { [key]: {} }), {}));
130
+
131
+ //
132
+ query.sort = this.#model.walk(sort, (node) => {
133
+ if (node.field.isVirtual || node.field.isFKReference) node.key = `join_${node.field.model.key}`;
134
+ return node;
135
+ }, { key: 'key' });
136
+
137
+ // Reconstruct the where clause by pulling out anything that requires a join
138
+ query.where = finalizeWhereClause(Util.unflatten(Object.entries(flatWhere).reduce((prev, [key, value]) => {
139
+ if (this.#model.isJoinPath(key, 'key')) return prev;
140
+ value = Util.map(value, el => (isGlob(el) ? globToRegex(el) : el));
141
+ return Object.assign(prev, { [key]: value });
142
+ }, {}), { safe: true }));
143
+
144
+ // Determine what join data is needed (derived from where + sort)
145
+ const joinData = mergeDeep($sort, Util.unflatten(Object.entries(flatWhere).reduce((prev, [key, value]) => {
146
+ if (this.#model.isJoinPath(key, 'key')) return Object.assign(prev, { [key]: value });
147
+ return prev;
148
+ }, {}), { safe: true }));
149
+
150
+ // Construct joins
151
+ query.joins = [];
152
+
153
+ this.#model.walk(joinData, (node) => {
154
+ const { model, field, key, value, isLeaf, path, run } = node;
155
+
156
+ if (field.join) {
157
+ let isArray;
158
+ const join = { ...field.join, where: {} };
159
+
160
+ if (run.length > 1) {
161
+ join.from = path.reduce((prev, curr, i) => {
162
+ const $field = this.#model.resolvePath(path.slice(0, i + 1).join('.'), 'key');
163
+ if ($field.isArray) isArray = true;
164
+ return prev.concat($field.linkField.key);
165
+ }, []).join('.');
166
+ }
167
+
168
+ join.isArray = isArray || model.resolvePath(join.from).isArray;
169
+
170
+ query.joins.push(join);
236
171
  }
237
- }
238
-
239
- return this;
240
- }
241
-
242
- crud(crud) {
243
- this.props.crud = crud;
244
- return this;
245
- }
246
-
247
- key(key) {
248
- this.props.key = key;
249
- return this;
250
- }
251
-
252
- input(input = {}) { // Allows .save(/* empty */);
253
- // delete input.id; // We do not want to allow changing id via input
254
- this.props.input = input;
255
- return this;
256
- }
257
-
258
- $input(input) {
259
- this.props.$input = input;
260
- return this;
261
- }
262
172
 
263
- doc(doc) {
264
- this.props.doc = doc;
265
- return this;
266
- }
267
-
268
- merged(merged) {
269
- this.props.merged = merged;
270
- return this;
271
- }
272
-
273
- payload(payload) {
274
- this.props.payload = payload;
275
- return this;
276
- }
277
-
278
- result(result) {
279
- this.props.result = result;
280
- return this;
281
- }
282
-
283
- $doc($doc) {
284
- this.props.$doc = $doc;
285
- return this;
286
- }
287
-
288
- args(args) {
289
- this.props.args = args;
290
- return this;
291
- }
292
-
293
- clone() {
294
- const clone = new Query();
295
- clone.props = { ...this.props };
296
- return clone;
297
- }
298
-
299
- toDriver() {
300
- const self = this;
301
- const { model } = this.props;
302
- const isSorted = Boolean(Object.keys(this.props.$sort || {}).length);
303
-
304
- return {
305
- isNative: Boolean(this.props.native),
306
- model: model.getKey(),
307
- shape: model.getShape(),
308
- method: this.props.method,
309
- select: this.props.$select,
310
- joins: this.props.joins,
311
- where: this.props.match,
312
- search: this.props.search,
313
- sort: this.props.$sort,
314
- skip: this.props.skip,
315
- limit: this.props.limit,
316
- first: isSorted ? this.props.first : undefined,
317
- last: isSorted ? this.props.last : undefined,
318
- // before: isSorted && this.props.before ? model.normalize(this, JSON.parse(Buffer.from(this.props.before, 'base64').toString('ascii')), 'serialize') : undefined,
319
- get before() {
320
- if (!isSorted || !self.props.before) return undefined;
321
- const shape = model.getShape('create', 'sort');
322
- const before = JSON.parse(Buffer.from(self.props.before, 'base64').toString('ascii'));
323
- const $before = model.shapeObject(shape, before, self);
324
- return $before;
325
- },
326
- get after() {
327
- if (!isSorted || !self.props.after) return undefined;
328
- const shape = model.getShape('create', 'sort');
329
- const after = JSON.parse(Buffer.from(self.props.after, 'base64').toString('ascii'));
330
- const $after = model.shapeObject(shape, after, self);
331
- return $after;
332
- },
333
- options: this.props.options,
334
- input: this.props.$input,
335
- flags: this.props.flags,
336
- $doc: this.props.$doc,
337
- doc: this.props.doc,
338
- };
339
- }
340
-
341
- toObject() {
342
- return this.props;
343
- }
173
+ if (isLeaf) {
174
+ const $model = field.model || model;
175
+ const join = query.joins.find(j => j.to === $model.key);
176
+ const $value = Util.map(value, el => (isGlob(el) ? globToRegex(el) : el));
177
+ const $$value = Array.isArray($value) ? { $in: $value } : $value;
178
+ const from = field.model ? join.from : key;
179
+ join.where[from] = $$value;
180
+ return false;
181
+ }
344
182
 
345
- getCacheKey() {
346
- return {
347
- cmd: this.props.cmd,
348
- method: this.props.method,
349
- where: this.props.match,
350
- search: this.props.search,
351
- sort: this.props.sort,
352
- skip: this.props.skip,
353
- limit: this.props.limit,
354
- before: this.props.before,
355
- after: this.props.after,
356
- first: this.props.first,
357
- last: this.props.last,
358
- options: this.props.options,
359
- };
183
+ return node;
184
+ }, { key: 'key' });
360
185
  }
361
186
  };