@coderich/autograph 0.12.0 → 0.13.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 (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 +84 -101
  7. package/src/data/Resolver.js +304 -0
  8. package/src/data/Transaction.js +49 -0
  9. package/src/query/Query.js +159 -335
  10. package/src/query/QueryBuilder.js +228 -114
  11. package/src/query/QueryResolver.js +110 -205
  12. package/src/query/QueryResolverTransaction.js +16 -0
  13. package/src/schema/Schema.js +602 -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 -161
  29. package/src/data/Field.js +0 -83
  30. package/src/data/Model.js +0 -214
  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,120 +1,234 @@
1
1
  const Query = require('./Query');
2
- const QueryResolver = require('./QueryResolver');
3
- const { unravelObject } = require('../service/app.service');
4
-
5
- /*
6
- * QueryBuilder
7
- *
8
- * Facilitates the creation and execution of a Query. It provides a chainable API to build a query
9
- * plus a list of terminal commands to execute the query.
10
- */
2
+ const { getGQLReturnType, mergeDeep } = require('../service/AppService');
3
+
11
4
  module.exports = class QueryBuilder {
12
- constructor(resolver, model) {
13
- // this.terminated = false; // Prevent accidental re-use of the QueryBuilder
14
- this.query = new Query({ model, resolver });
15
-
16
- // Chainable commands
17
- this.id = (id) => { this.query.id(id); return this; };
18
- this.select = (select) => { this.query.select(unravelObject(select)); return this; };
19
- // this.select = (select) => { this.query.select(Object.entries(toKeyObj(select)).reduce((prev, [key, value]) => Object.assign(prev, { [key.replace(/edges.node./g, '')]: !!value }), {})); return this; };
20
- this.where = (where) => { this.query.where(unravelObject(where)); return this; };
21
- this.match = (match) => { this.query.match(unravelObject(match)); return this; };
22
- this.native = (native) => { this.query.native(native); return this; };
23
- this.sort = (sort) => { this.query.sort(unravelObject(sort)); return this; };
24
- this.sortBy = (sortBy) => { this.query.sort(unravelObject(sortBy)); return this; };
25
- this.limit = (limit) => { this.query.limit(limit); return this; };
26
- this.skip = (skip) => { this.query.skip(skip); return this; };
27
- this.before = (cursor) => { this.query.before(cursor); return this; };
28
- this.after = (cursor) => { this.query.after(cursor); return this; };
29
- this.meta = (meta) => { this.query.meta(meta); return this; };
30
- this.flags = (flags) => { this.query.flags(flags); return this; };
31
- this.merge = (merge) => { this.query.merge(unravelObject(merge)); return this; };
32
- this.batch = (batch) => { this.query.batch(batch); return this; };
33
- this.transaction = (txn) => { this.query.transaction(txn); return this; };
34
-
35
- // Terminal commands
36
- this.one = (...args) => this.execute('one', args);
37
- this.many = (...args) => this.execute('many', args);
38
- this.save = (...args) => this.execute('save', args.map(arg => unravelObject(arg)));
39
- this.delete = (...args) => this.execute('delete', args);
40
- this.remove = (...args) => this.execute('remove', args);
41
- this.resolve = (...args) => this.execute('resolve', args);
42
- //
43
- this.count = (...args) => this.execute('count', args);
44
- this.push = (...args) => this.execute('push', args.map(arg => unravelObject(arg)));
45
- this.pull = (...args) => this.execute('pull', args.map(arg => unravelObject(arg)));
46
- this.splice = (...args) => this.execute('splice', args.map(arg => unravelObject(arg)));
47
- this.first = (...args) => { this.query.first(...args); return this.execute('first', args); };
48
- this.last = (...args) => { this.query.last(...args); return this.execute('last', args); };
49
- //
50
- // this.min = (...args) => this.makeTheCall(query, 'min', args);
51
- // this.max = (...args) => this.makeTheCall(query, 'max', args);
52
- // this.avg = (...args) => this.makeTheCall(query, 'avg', args);
53
- // this.sum = (...args) => this.makeTheCall(query, 'sum', args); // Would sum be different than count?
54
- // // Food for thought...
55
- // this.archive = (...args) => this.makeTheCall(query, 'archive', args); // Soft Delete
56
- // this.stream = (...args) => this.makeTheCall(query, 'stream', args); // Stream records 1 by 1
57
- // this.rollup = (...args) => this.makeTheCall(query, 'rollup', args); // Like sum, but for nested attributes (eg. Person.rollupAuthoredChaptersPages)
58
- }
59
-
60
- execute(cmd, args) {
61
- // // Do not allow re-use
62
- // if (this.terminated) return Promise.reject(new Error('This query has already been executed'));
63
- // this.terminated = true;
64
-
65
- let method, crud, input, flags = {};
66
- const { id, where } = this.query.toObject();
67
-
68
- switch (cmd) {
69
- case 'resolve': {
70
- crud = 'read';
71
- method = 'autoResolve';
72
- break;
73
- }
74
- case 'one': case 'many': {
75
- crud = 'read';
76
- [flags] = args;
77
- method = cmd === 'one' ? 'findOne' : 'findMany';
78
- break;
79
- }
80
- case 'first': case 'last': {
81
- crud = 'read';
82
- [, flags] = args;
83
- method = cmd;
84
- break;
85
- }
86
- case 'save': {
87
- [input, flags] = args;
88
- crud = id || where ? 'update' : 'create';
89
- if (crud === 'update') { method = id ? 'updateOne' : 'updateMany'; }
90
- if (crud === 'create') { method = Array.isArray(input) ? 'createMany' : 'createOne'; }
91
- break;
92
- }
93
- case 'push': case 'pull': case 'splice': {
94
- // [target, input, flags] = args;
95
- crud = 'update'; // Your logic wants this to be a simple "update". Sub documents systemEvents will emit either "create" or "udpate"
96
- method = id ? `${cmd}One` : `${cmd}Many`;
97
- break;
98
- }
99
- case 'remove': case 'delete': {
100
- crud = 'delete';
101
- [flags] = args;
102
- if (id) method = 'deleteOne';
103
- else if (where) method = 'deleteMany';
104
- else return Promise.reject(new Error('Remove requires an id() or where()'));
105
- break;
106
- }
107
- case 'count': {
108
- crud = 'read';
109
- [flags] = args;
110
- method = 'count';
111
- break;
112
- }
113
- default: {
114
- return Promise.reject(new Error(`Unknown query command: ${cmd}`));
115
- }
5
+ #config;
6
+ #query;
7
+ #terminalCommands = ['one', 'many', 'count', 'save', 'delete', 'first', 'last', 'push', 'pull', 'splice'];
8
+
9
+ constructor(config) {
10
+ const { query } = config;
11
+
12
+ this.#config = config;
13
+
14
+ this.#query = Object.defineProperties(query, {
15
+ id: { writable: true, enumerable: true, value: query.id },
16
+ args: { writable: true, enumerable: true, value: query.args || {} },
17
+ flags: { writable: true, enumerable: true, value: query.flags || {} },
18
+ options: { writable: true, enumerable: true, value: query.options || {} },
19
+ });
20
+
21
+ // Aliases
22
+ this.opts = this.options;
23
+ this.sortBy = this.sort;
24
+ this.remove = this.delete;
25
+ }
26
+
27
+ isTerminal(cmd) {
28
+ return this.#terminalCommands.includes(cmd);
29
+ }
30
+
31
+ /**
32
+ * When a termnial command is called, terminate() returns the Query object
33
+ * We have to clone() the Query because we mutate this.#config all while the query is being built
34
+ * However there is a "bug" when using .resolve() (below) and the QueryBuilder is re-used to resolve each thunk
35
+ */
36
+ terminate() {
37
+ return new Query(this.#config).clone();
38
+ }
39
+
40
+ /**
41
+ * For use in GraphQL resolver methods to return the "correct" response
42
+ */
43
+ resolve(info) {
44
+ switch (getGQLReturnType(`${info.returnType}`)) {
45
+ case 'array': return this.many();
46
+ case 'number': return this.count();
47
+ case 'connection': return { count: () => this.count(), edges: () => this.many(), pageInfo: () => this.many() };
48
+ case 'scalar': default: return this.one();
116
49
  }
50
+ }
51
+
52
+ /**
53
+ * Chainable methods
54
+ */
55
+ id(id) {
56
+ this.#propCheck('id', 'native', 'sort', 'skip', 'limit', 'before', 'after');
57
+ this.#query.id = id;
58
+ this.#query.where = mergeDeep(this.#query.where || {}, { id });
59
+ this.#query.args.id = id;
60
+ return this;
61
+ }
62
+
63
+ args(args) {
64
+ Object.entries(args).forEach(([key, value]) => { if (this[key]) this[key](value); }); // Call method only if exists
65
+ return this;
66
+ }
67
+
68
+ native(clause) {
69
+ this.#propCheck('native', 'id', 'where');
70
+ this.#query.isNative = true;
71
+ this.#query.native = clause;
72
+ this.#query.where = clause;
73
+ this.#query.args.native = clause;
74
+ return this;
75
+ }
76
+
77
+ where(clause) {
78
+ this.#propCheck('where', 'native', false);
79
+ const $clause = mergeDeep(this.#query.where || {}, clause);
80
+ this.#query.where = $clause;
81
+ this.#query.args.where = $clause;
82
+ return this;
83
+ }
84
+
85
+ select(...select) {
86
+ this.#propCheck('select');
87
+ select = select.flat();
88
+ this.#query.select = select;
89
+ this.#query.args.select = select;
90
+ return this;
91
+ }
92
+
93
+ skip(skip) {
94
+ this.#propCheck('skip', 'id');
95
+ this.isClassicPaging = true;
96
+ this.#query.skip = skip;
97
+ this.#query.args.skip = skip;
98
+ return this;
99
+ }
100
+
101
+ limit(limit) {
102
+ this.#propCheck('limit', 'id');
103
+ this.isClassicPaging = true;
104
+ this.#query.limit = limit;
105
+ this.#query.args.limit = limit;
106
+ return this;
107
+ }
108
+
109
+ before(before) {
110
+ this.#propCheck('before', 'id');
111
+ this.#query.isCursorPaging = true;
112
+ this.#query.before = before;
113
+ this.#query.args.before = before;
114
+ return this;
115
+ }
116
+
117
+ after(after) {
118
+ this.#propCheck('after', 'id');
119
+ this.#query.isCursorPaging = true;
120
+ this.#query.after = after;
121
+ this.#query.args.after = after;
122
+ return this;
123
+ }
124
+
125
+ sort(sort) {
126
+ this.#propCheck('sort', 'id');
127
+ this.#query.sort = sort;
128
+ this.#query.args.sort = sort;
129
+ return this;
130
+ }
131
+
132
+ meta(meta) {
133
+ this.#query.meta = meta;
134
+ this.#query.args.meta = meta;
135
+ return this;
136
+ }
137
+
138
+ options(options) {
139
+ Object.assign(this.#query.options, options);
140
+ return this;
141
+ }
142
+
143
+ flags(flags) {
144
+ Object.assign(this.#query.flags, flags);
145
+ return this;
146
+ }
147
+
148
+ /**
149
+ * Core terminal commands
150
+ */
151
+ one(flags) {
152
+ return this.flags(flags).terminate(Object.assign(this.#query, { op: 'findOne', crud: 'read', key: `get${this.#query.model}` }));
153
+ }
154
+
155
+ many(flags) {
156
+ return this.flags(flags).terminate(Object.assign(this.#query, { op: 'findMany', crud: 'read', key: `find${this.#query.model}` }));
157
+ }
158
+
159
+ count() {
160
+ return this.terminate(Object.assign(this.#query, { op: 'count', crud: 'read', key: `count${this.#query.model}` }));
161
+ }
162
+
163
+ save(...args) {
164
+ const { id, where } = this.#query;
165
+ const crud = (id || where ? (args[1] ? 'upsert' : 'update') : 'create'); // eslint-disable-line
166
+ return this.#mutation(crud, ...args);
167
+ }
168
+
169
+ delete(...args) {
170
+ const { id, where } = this.#query;
171
+ if (!id && !where) throw new Error('Delete requires id() or where()');
172
+ return this.#mutation('delete', ...args);
173
+ }
174
+
175
+ /**
176
+ * Proxy terminial commands
177
+ */
178
+ first(first) {
179
+ this.#query.isCursorPaging = true;
180
+ this.#query.first = first + 2; // Adding 2 for pagination meta info (hasNext hasPrev)
181
+ this.#query.args.first = first;
182
+ return this.many();
183
+ }
184
+
185
+ last(last) {
186
+ this.#query.isCursorPaging = true;
187
+ this.#query.last = last + 2; // Adding 2 for pagination meta info (hasNext hasPrev)
188
+ this.#query.args.last = last;
189
+ return this.many();
190
+ }
191
+
192
+ /**
193
+ * Array terminal commands
194
+ */
195
+ push(path, ...values) {
196
+ values = values.flat();
197
+ return this.#mutation('push', { [path]: values });
198
+ }
199
+
200
+ pull(path, ...values) {
201
+ values = values.flat();
202
+ return this.#mutation('pull', { [path]: values });
203
+ }
204
+
205
+ splice(path, ...values) {
206
+ values = values.flat();
207
+ return this.#mutation('splice', { [path]: values });
208
+ }
209
+
210
+ /**
211
+ */
212
+ #mutation(crud, ...args) {
213
+ args = args.flat();
214
+ const { id, limit } = this.#query;
215
+ const suffix = id || limit === 1 || (crud === 'create' && args.length < 2) ? 'One' : 'Many';
216
+ let input = suffix === 'One' ? args[0] : args;
217
+ if (input === undefined) input = {};
218
+ this.#query.args.input = input;
219
+ return this.terminate(Object.assign(this.#query, {
220
+ op: `${crud}${suffix}`,
221
+ key: `${crud}${this.#query.model}`,
222
+ crud: ['push', 'pull', 'splice'].includes(crud) ? 'update' : crud,
223
+ input,
224
+ isMutation: true,
225
+ }));
226
+ }
117
227
 
118
- return new QueryResolver(this.query.method(method).cmd(cmd).crud(crud).input(input).flags(flags).args(args)).resolve();
228
+ #propCheck(prop, ...checks) {
229
+ if (checks[checks.length - 1] !== false && this.#query[prop]) throw new Error(`Cannot redefine "${prop}"`);
230
+ if (['skip', 'limit'].includes(prop) && this.#query.isCursorPaging) throw new Error(`Cannot use "${prop}" while using Cursor-Style Pagination`);
231
+ if (['first', 'last', 'before', 'after'].includes(prop) && this.isClassicPaging) throw new Error(`Cannot use "${prop}" while using Classic-Style Pagination`);
232
+ checks.forEach((check) => { if (this.#query[check]) throw new Error(`Cannot use "${prop}" while using "${check}"`); });
119
233
  }
120
234
  };