@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
@@ -0,0 +1,602 @@
1
+ /* eslint-disable indent */
2
+
3
+ const Util = require('@coderich/util');
4
+ const { Kind, parse, visit } = require('graphql');
5
+ const { mergeTypeDefs, mergeFields } = require('@graphql-tools/merge');
6
+ const { isLeafValue, isPlainObject, isBasicObject, mergeDeep, fromGUID } = require('../service/AppService');
7
+ const Pipeline = require('../data/Pipeline');
8
+ const Emitter = require('../data/Emitter');
9
+
10
+ const operations = ['Query', 'Mutation', 'Subscription'];
11
+ // const interfaceKinds = [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
12
+ const modelKinds = [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION, Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
13
+ const allowedKinds = modelKinds.concat(Kind.DOCUMENT, Kind.FIELD_DEFINITION, Kind.NON_NULL_TYPE, Kind.NAMED_TYPE, Kind.LIST_TYPE, Kind.DIRECTIVE);
14
+ const pipelines = ['finalize', 'construct', 'restruct', 'instruct', 'normalize', 'serialize'];
15
+ const inputPipelines = ['finalize', 'construct', 'instruct', 'normalize', 'serialize'];
16
+ const scalars = ['ID', 'String', 'Float', 'Int', 'Boolean'];
17
+
18
+ module.exports = class Schema {
19
+ #config;
20
+ #schema;
21
+ #typeDefs;
22
+ #resolvers = {};
23
+
24
+ constructor(config) {
25
+ this.#config = config;
26
+ this.#typeDefs = Schema.#framework();
27
+ }
28
+
29
+ /**
30
+ * Decorate each marked @model with config-driven field decorators
31
+ */
32
+ decorate() {
33
+ this.#typeDefs = visit(this.#typeDefs, {
34
+ enter: (node) => {
35
+ if (modelKinds.includes(node.kind) && !operations.includes(node.name.value)) {
36
+ const directive = node.directives.find(({ name }) => name.value === 'model');
37
+
38
+ if (directive) {
39
+ const arg = directive.arguments.find(({ name }) => name.value === 'decorate');
40
+ const value = arg?.value.value || 'default';
41
+ const decorator = this.#config.decorators?.[value];
42
+
43
+ if (decorator) {
44
+ const { fields } = parse(`type decorator { ${decorator} }`).definitions[0];
45
+ node.fields = mergeFields(node, node.fields, fields, { noLocation: true, onFieldTypeConflict: a => a });
46
+ return node;
47
+ }
48
+ }
49
+
50
+ return false;
51
+ }
52
+
53
+ return undefined;
54
+ },
55
+ });
56
+
57
+ return this;
58
+ }
59
+
60
+ /**
61
+ * Merge typeDefs and resolvers
62
+ */
63
+ merge(schema = {}) {
64
+ // Normalize schema input
65
+ if (typeof schema === 'string') schema = { typeDefs: schema };
66
+ else if (schema instanceof Schema) schema = schema.toObject();
67
+
68
+ if (schema.typeDefs) {
69
+ const typeDefs = Util.ensureArray(schema.typeDefs).map(td => (typeof td === 'string' ? parse(td) : td));
70
+ this.#typeDefs = mergeTypeDefs([typeDefs, this.#typeDefs], { noLocation: true, reverseDirectives: true, onFieldTypeConflict: a => a });
71
+ }
72
+
73
+ if (schema.resolvers) {
74
+ this.#resolvers = mergeDeep(this.#resolvers, schema.resolvers);
75
+ }
76
+
77
+ return this;
78
+ }
79
+
80
+ /**
81
+ * Parse typeDefs; returning a schema POJO
82
+ */
83
+ parse() {
84
+ if (this.#schema) return this.#schema;
85
+
86
+ this.#schema = { models: {}, indexes: [] };
87
+ let model, field, isField, isList;
88
+ const thunks = [];
89
+
90
+ // Parse AST
91
+ visit(this.#typeDefs, {
92
+ enter: (node) => {
93
+ const name = node.name?.value;
94
+ if (!allowedKinds.includes(node.kind)) return false;
95
+
96
+ if (modelKinds.includes(node.kind) && !operations.includes(name)) {
97
+ model = this.#schema.models[name] = {
98
+ name,
99
+ key: name,
100
+ fields: {},
101
+ idField: 'id',
102
+ crud: 'crud',
103
+ scope: 'crud',
104
+ isPersistable: true,
105
+ source: this.#config.dataSources?.default,
106
+ loader: this.#config.dataLoaders?.default,
107
+ directives: {},
108
+ toString: () => name,
109
+ };
110
+ } else if (node.kind === Kind.FIELD_DEFINITION) {
111
+ isField = true;
112
+ field = model.fields[name] = {
113
+ name,
114
+ key: name,
115
+ crud: 'crud',
116
+ pipelines: pipelines.reduce((prev, key) => Object.assign(prev, { [key]: [] }), {}),
117
+ directives: {},
118
+ toString: () => name,
119
+ };
120
+ } else if (node.kind === Kind.NON_NULL_TYPE) {
121
+ field[isList ? 'isArrayRequired' : 'isRequired'] = true;
122
+ } else if (node.kind === Kind.NAMED_TYPE) {
123
+ field.type = node.name.value;
124
+ } else if (node.kind === Kind.LIST_TYPE) {
125
+ field.isArray = true;
126
+ isList = true;
127
+ } else if (node.kind === Kind.DIRECTIVE) {
128
+ const target = isField ? field : model;
129
+ target.directives[name] = target.directives[name] || {};
130
+
131
+ if (name === 'model') model.isMarkedModel = true;
132
+ else if (name === 'index') this.#schema.indexes.push({ model });
133
+
134
+ node.arguments.forEach((arg) => {
135
+ const key = arg.name.value;
136
+ const { value: val, values } = arg.value;
137
+ const value = values ? values.map(n => n.value) : val;
138
+ target.directives[name][key] = value;
139
+
140
+ if (name === 'index') this.#schema.indexes[this.#schema.indexes.length - 1][key] = value;
141
+
142
+ switch (`${name}-${key}`) {
143
+ // Model specific directives
144
+ case 'model-id': {
145
+ model.idField = value;
146
+ break;
147
+ }
148
+ case 'model-source': {
149
+ model.source = this.#config.dataSources?.[value];
150
+ break;
151
+ }
152
+ case 'model-loader': {
153
+ model.loader = this.#config.dataLoaders?.[value];
154
+ break;
155
+ }
156
+ case 'model-embed': {
157
+ model.isEmbedded = value;
158
+ break;
159
+ }
160
+ // Field specific directives
161
+ case 'field-default': {
162
+ field.defaultValue = value;
163
+ break;
164
+ }
165
+ case 'field-connection': {
166
+ field.isConnection = value;
167
+ break;
168
+ }
169
+ case 'link-by': {
170
+ field.linkBy = value;
171
+ field.isVirtual = true;
172
+ break;
173
+ }
174
+ // Generic by target directives
175
+ case 'model-persist': case 'field-persist': {
176
+ target.isPersistable = value;
177
+ break;
178
+ }
179
+ case 'model-crud': case 'model-scope': case 'field-crud': {
180
+ target[key] = Util.nvl(value, '');
181
+ break;
182
+ }
183
+ case 'model-key': case 'model-meta': case 'field-key': case 'field-onDelete': {
184
+ target[key] = value;
185
+ break;
186
+ }
187
+ default: {
188
+ if (pipelines.includes(key)) {
189
+ target.pipelines[key] = target.pipelines[key].concat(value).filter(Boolean);
190
+ }
191
+ break;
192
+ }
193
+ }
194
+ });
195
+ }
196
+
197
+ return undefined; // Continue
198
+ },
199
+ leave: (node) => {
200
+ if (modelKinds.includes(node.kind) && !operations.includes(node.name.value)) {
201
+ const $model = model;
202
+ // const idField = $model.fields[$model.idField];
203
+ // $model.primaryKey = Util.nvl(idField?.key, idField?.name, 'id');
204
+
205
+ // Model resolution after field resolution (push)
206
+ thunks.push(($schema) => {
207
+ $model.isEntity = Boolean($model.isMarkedModel && !$model.isEmbedded);
208
+
209
+ $model.resolvePath = (path, prop = 'name') => this.#schema.resolvePath(`${$model[prop]}.${path}`, prop);
210
+
211
+ $model.isJoinPath = (path, prop = 'name') => {
212
+ let foundJoin = false;
213
+ return !path.split('.').every((el, i, arr) => {
214
+ if (foundJoin) return false;
215
+ const $field = $model.resolvePath(arr.slice(0, i + 1).join('.'), prop);
216
+ foundJoin = $field.isVirtual || $field.isFKReference;
217
+ return !$field.isVirtual;
218
+ });
219
+ };
220
+
221
+ $model.walk = (data, fn, opts = {}) => {
222
+ if (data == null || !isPlainObject(data)) return data;
223
+
224
+ // Options
225
+ opts.key = opts.key ?? 'name';
226
+ opts.run = opts.run ?? [];
227
+ opts.path = opts.path ?? [];
228
+ opts.itemize = opts.itemize ?? true;
229
+
230
+ return Object.entries(data).reduce((prev, [key, value]) => {
231
+ // Find the field; remove it if not found
232
+ const $field = Object.values($model.fields).find(el => el[opts.key] === key);
233
+ if (!$field) return prev;
234
+
235
+ // Invoke callback function; allowing result to be modified in order to change key/value
236
+ let run = opts.run.concat($field[opts.key]);
237
+ const path = opts.path.concat($field[opts.key]);
238
+ const isLeaf = isLeafValue(value);
239
+ const $node = fn({ model: $model, field: $field, key, value, path, run, isLeaf });
240
+ if (!$node) return prev;
241
+
242
+ // Recursive walk
243
+ if (!$field.model?.isEmbedded) run = [];
244
+ const $value = opts.itemize && $field.model && isBasicObject($node.value) ? Util.map($node.value, el => $field.model.walk(el, fn, { ...opts, path, run })) : $node.value;
245
+ return Object.assign(prev, { [$node.key]: $value });
246
+ }, {});
247
+ };
248
+
249
+ // Pre-processing
250
+ $model.pipelineFields = {
251
+ input: Object.values($model.fields).filter(f => f.defaultValue !== undefined || inputPipelines.some(k => f.pipelines[k].length)).reduce((prev, f) => Object.assign(prev, { [f.name]: undefined }), {}),
252
+ where: Object.values($model.fields).filter(f => f.pipelines.instruct.length).reduce((prev, f) => Object.assign(prev, { [f.name]: undefined }), {}),
253
+ };
254
+ });
255
+ } else if (node.kind === Kind.FIELD_DEFINITION) {
256
+ const $field = field;
257
+ const $model = model;
258
+
259
+ $field.isPrimaryKey = Boolean($field.name === model.idField);
260
+ $field.isPersistable = Util.uvl($field.isPersistable, model.isPersistable, true);
261
+
262
+ // Field resolution comes first (unshift)
263
+ thunks.unshift(($schema) => {
264
+ $field.model = $schema.models[$field.type];
265
+ $field.linkBy = $field.linkBy || $field.model?.idField;
266
+ $field.linkField = $field.isVirtual ? $model.fields[$model.idField] : $field;
267
+ $field.isFKReference = !$field.isPrimaryKey && $field.model?.isMarkedModel && !$field.model?.isEmbedded;
268
+ $field.isEmbedded = Boolean($field.model && !$field.isFKReference && !$field.isPrimaryKey);
269
+ $field.isScalar = Boolean(!$field.model || scalars.includes($field.type));
270
+
271
+ if ($field.isArray) $field.pipelines.normalize.unshift('toArray');
272
+ if ($field.isPrimaryKey) $field.pipelines.serialize.unshift('$pk'); // Will create/convert to FK type always
273
+ if ($field.isFKReference) $field.pipelines.serialize.unshift('$fk'); // Will convert to FK type IFF defined in payload
274
+
275
+ if ($field.isRequired && $field.isPersistable && !$field.isVirtual) $field.pipelines.finalize.push('required');
276
+ if ($field.isFKReference) {
277
+ const to = $field.model.key;
278
+ const on = $field.model.fields[$field.linkBy].key;
279
+ const from = $field.linkField.key;
280
+ const as = `join_${to}`;
281
+ $field.join = { to, on, from, as };
282
+ $field.pipelines.finalize.push('ensureId'); // Absolute Last
283
+ }
284
+ });
285
+
286
+ isField = false;
287
+ } else if (node.kind === Kind.LIST_TYPE) {
288
+ isList = false;
289
+ }
290
+ },
291
+ });
292
+
293
+ // Resolve data thunks
294
+ thunks.forEach(thunk => thunk(this.#schema));
295
+
296
+ // Resolve indexes
297
+ this.#schema.indexes = this.#schema.indexes.map((index) => {
298
+ const { key } = index.model;
299
+ const { name, type } = index;
300
+ const on = index.on.map(f => index.model.fields[f].key);
301
+ return { key, name, type, on };
302
+ });
303
+
304
+ // Resolve referential integrity
305
+ Object.values(this.#schema.models).forEach(($model) => {
306
+ $model.referentialIntegrity = Schema.#identifyOnDeletes(Object.values(this.#schema.models), $model.name);
307
+ });
308
+
309
+ // Helper methods
310
+ this.#schema.resolvePath = (path, prop = 'key') => {
311
+ const [modelKey, ...fieldKeys] = path.split('.');
312
+ const $model = Object.values(this.#schema.models).find(el => el[prop] === modelKey);
313
+ if (!$model || !fieldKeys.length) return $model;
314
+ return fieldKeys.reduce((parent, key) => Object.values(parent.fields || parent.model.fields).find(el => el[prop] === key) || parent, $model);
315
+ };
316
+
317
+ // Return schema
318
+ return this.#schema;
319
+ }
320
+
321
+ api() {
322
+ return this.merge(Schema.#api(this.parse()));
323
+ }
324
+
325
+ setup() {
326
+ return Emitter.emit('setup', this.#schema);
327
+ }
328
+
329
+ toObject() {
330
+ return {
331
+ typeDefs: this.#typeDefs,
332
+ resolvers: this.#resolvers,
333
+ };
334
+ }
335
+
336
+ makeExecutableSchema() {
337
+ return this.#config.makeExecutableSchema(this.toObject());
338
+ }
339
+
340
+ static #framework() {
341
+ return parse(`
342
+ scalar AutoGraphMixed
343
+
344
+ enum AutoGraphIndexEnum { unique }
345
+ enum AutoGraphOnDeleteEnum { cascade nullify restrict defer }
346
+ enum AutoGraphPipelineEnum { ${Object.keys(Pipeline).filter(k => !k.startsWith('$')).join(' ')} }
347
+
348
+ directive @model(
349
+ id: String # Specify the ID/PK field (default "id")
350
+ key: String # Specify db table/collection name
351
+ crud: AutoGraphMixed # CRUD API
352
+ scope: AutoGraphMixed #
353
+ meta: AutoGraphMixed # Custom input "meta" field for mutations
354
+ source: AutoGraphMixed # Data source (default: "default")
355
+ decorate: AutoGraphMixed # Decorator (default: "default")
356
+ embed: Boolean # Mark this an embedded model (default false)
357
+ persist: Boolean # Persist this model (default true)
358
+ ) on OBJECT | INTERFACE
359
+
360
+ directive @field(
361
+ key: String # Specify db key
362
+ persist: Boolean # Persist this field (default true)
363
+ connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
364
+ default: AutoGraphMixed # Define a default value
365
+ crud: AutoGraphMixed # CRUD API
366
+ onDelete: AutoGraphOnDeleteEnum # onDelete behavior
367
+
368
+ # Pipeline Structure
369
+ normalize: [AutoGraphPipelineEnum!]
370
+ instruct: [AutoGraphPipelineEnum!]
371
+ construct: [AutoGraphPipelineEnum!]
372
+ restruct: [AutoGraphPipelineEnum!]
373
+ serialize: [AutoGraphPipelineEnum!]
374
+ finalize: [AutoGraphPipelineEnum!]
375
+ ) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
376
+
377
+ directive @link(
378
+ to: AutoGraphMixed # The MODEL to link to (default's to modelRef)
379
+ by: AutoGraphMixed! # The FIELD to match yourself by
380
+ use: AutoGraphMixed # The VALUE to use (default's to @link'd value); useful for many-to-many relationships
381
+ ) on FIELD_DEFINITION
382
+
383
+ directive @index(
384
+ name: String
385
+ on: [AutoGraphMixed!]!
386
+ type: AutoGraphIndexEnum!
387
+ ) repeatable on OBJECT
388
+ `);
389
+ }
390
+
391
+ static #api(schema) {
392
+ // These models are for creating types
393
+ const readModels = Object.values(schema.models).filter(model => model.crud?.includes('r'));
394
+ const createModels = Object.values(schema.models).filter(model => model.crud?.includes('c'));
395
+ const updateModels = Object.values(schema.models).filter(model => model.crud?.includes('u'));
396
+
397
+ // These are for defining schema queries/mutations
398
+ const entityModels = Object.values(schema.models).filter(model => model.isEntity);
399
+ const queryModels = entityModels.filter(model => model.crud?.includes('r'));
400
+ const mutationModels = entityModels.filter(model => ['c', 'u', 'd'].some(el => model.crud?.includes(el)));
401
+ const subscriptionModels = entityModels.filter(model => model.crud?.includes('s'));
402
+
403
+ return {
404
+ typeDefs: `
405
+ scalar AutoGraphMixed
406
+
407
+ interface Node { id: ID! }
408
+
409
+ enum SortOrderEnum { asc desc }
410
+ enum SubscriptionCrudEnum { create update delete }
411
+ enum SubscriptionWhenEnum { preEvent postEvent }
412
+
413
+ type PageInfo {
414
+ startCursor: String!
415
+ endCursor: String!
416
+ hasPreviousPage: Boolean!
417
+ hasNextPage: Boolean!
418
+ }
419
+
420
+ ${entityModels.map(model => `
421
+ extend type ${model} implements Node {
422
+ id: ID!
423
+ }
424
+ `)}
425
+
426
+ ${readModels.map((model) => {
427
+ const fields = Object.values(model.fields).filter(field => field.crud?.includes('r'));
428
+ const connectionFields = fields.filter(field => field.isConnection);
429
+
430
+ return `
431
+ input ${model}InputWhere {
432
+ ${fields.map(field => `${field}: ${field.model?.isEntity ? `${field.model}InputWhere` : 'AutoGraphMixed'}`)}
433
+ }
434
+ input ${model}InputSort {
435
+ ${fields.map(field => `${field}: ${field.model?.isEntity ? `${field.model}InputSort` : 'SortOrderEnum'}`)}
436
+ }
437
+ type ${model}Connection {
438
+ count: Int!
439
+ pageInfo: PageInfo
440
+ edges: [${model}Edge]
441
+ }
442
+ type ${model}Edge {
443
+ node: ${model}
444
+ cursor: String
445
+ }
446
+ ${connectionFields.length ? `
447
+ extend type ${model} {
448
+ ${connectionFields.map(field => `${field}: ${field.model}Connection`)}
449
+ }
450
+ ` : ''}
451
+ `;
452
+ })}
453
+
454
+ ${createModels.map((model) => {
455
+ const fields = Object.values(model.fields).filter(field => field.crud?.includes('c') && !field.isVirtual);
456
+
457
+ return `
458
+ input ${model}InputCreate {
459
+ ${fields.map(field => `${field}: ${Schema.#getGQLType(field, 'InputCreate')}`)}
460
+ }
461
+ `;
462
+ })}
463
+
464
+ ${updateModels.map((model) => {
465
+ const fields = Object.values(model.fields).filter(field => field.crud?.includes('u') && !field.isVirtual);
466
+
467
+ return `
468
+ input ${model}InputUpdate {
469
+ ${fields.map(field => `${field}: ${Schema.#getGQLType(field, 'InputUpdate')}`)}
470
+ }
471
+ `;
472
+ })}
473
+
474
+ type Query {
475
+ node(id: ID!): Node
476
+ ${queryModels.map(model => `
477
+ get${model}(id: ID!): ${model}
478
+ find${model}(
479
+ where: ${model}InputWhere
480
+ sortBy: ${model}InputSort
481
+ limit: Int
482
+ skip: Int
483
+ first: Int
484
+ after: String
485
+ last: Int
486
+ before: String
487
+ ): ${model}Connection!
488
+ `)}
489
+ }
490
+
491
+ ${mutationModels.length ? `
492
+ type Mutation {
493
+ ${mutationModels.map((model) => {
494
+ const api = [];
495
+ const meta = model.meta ? `meta: ${model.meta}` : '';
496
+ if (model.crud?.includes('c')) api.push(`create${model}(input: ${model}InputCreate! ${meta}): ${model}!`);
497
+ if (model.crud?.includes('u')) api.push(`update${model}(id: ID! input: ${model}InputUpdate ${meta}): ${model}!`);
498
+ if (model.crud?.includes('d')) api.push(`delete${model}(id: ID! ${meta}): ${model}!`);
499
+ return api.join('\n');
500
+ })}
501
+ }
502
+ ` : ''}
503
+
504
+ ${subscriptionModels.length ? `
505
+ type Subscription {
506
+ ${subscriptionModels.map(model => `
507
+ ${model}(
508
+ on: [SubscriptionCrudEnum!]! = [create, update, delete]
509
+ filter: ${model}SubscriptionInputFilter
510
+ ): ${model}SubscriptionPayload!
511
+ `)}
512
+ }
513
+ ` : ''}
514
+ `,
515
+ resolvers: {
516
+ Node: {
517
+ __resolveType: (doc, args, context, info) => doc.__typename, // eslint-disable-line no-underscore-dangle
518
+ },
519
+ ...queryModels.reduce((prev, model) => {
520
+ return Object.assign(prev, {
521
+ [`${model}Connection`]: {
522
+ count: ({ count }) => count(),
523
+ edges: ({ edges }) => edges().then(rs => rs.map(node => ({ cursor: node.$cursor, node }))),
524
+ pageInfo: ({ pageInfo }) => pageInfo().then(rs => rs?.$pageInfo),
525
+ },
526
+ });
527
+ }, {}),
528
+ Query: queryModels.reduce((prev, model) => {
529
+ return Object.assign(prev, {
530
+ [`get${model}`]: (doc, args, context, info) => context.autograph.resolver.match(model).args(args).one({ required: true }),
531
+ [`find${model}`]: (doc, args, context, info) => {
532
+ return {
533
+ edges: () => context.autograph.resolver.match(model).args(args).many(),
534
+ count: () => context.autograph.resolver.match(model).args(args).count(),
535
+ pageInfo: () => context.autograph.resolver.match(model).args(args).many(),
536
+ };
537
+ },
538
+ });
539
+ }, {
540
+ node: (doc, args, context, info) => {
541
+ const { id } = args;
542
+ const [modelName] = fromGUID(id);
543
+ const model = schema.models[modelName];
544
+ return context.autograph.resolver.match(model).id(id).one().then((result) => {
545
+ if (result == null) return result;
546
+ result.__typename = modelName; // eslint-disable-line no-underscore-dangle
547
+ return result;
548
+ });
549
+ },
550
+ }),
551
+ ...(mutationModels.length ? {
552
+ Mutation: mutationModels.reduce((prev, model) => {
553
+ if (model.crud?.includes('c')) prev[`create${model}`] = (doc, args, context, info) => context.autograph.resolver.match(model).args(args).save(args.input);
554
+ if (model.crud?.includes('u')) prev[`update${model}`] = (doc, args, context, info) => context.autograph.resolver.match(model).args(args).save(args.input);
555
+ if (model.crud?.includes('d')) prev[`delete${model}`] = (doc, args, context, info) => context.autograph.resolver.match(model).args(args).delete();
556
+ return prev;
557
+ }, {}),
558
+ } : {}),
559
+ ...readModels.reduce((prev, model) => {
560
+ return Object.assign(prev, {
561
+ [model]: Object.values(model.fields).filter(field => field.model?.isEntity).reduce((prev2, field) => {
562
+ return Object.assign(prev2, {
563
+ [field]: (doc, args, context, info) => {
564
+ return context.autograph.resolver.match(field.model).where({ [field.linkBy]: doc[field.linkField.name] }).args(args).resolve(info);
565
+ },
566
+ });
567
+ }, {}),
568
+ });
569
+ }, {}),
570
+ },
571
+ };
572
+ }
573
+
574
+ static #getGQLType(field, suffix) {
575
+ let { type } = field;
576
+ const { isEmbedded, isRequired, isScalar, isArray, isArrayRequired, defaultValue } = field;
577
+ const modelType = `${type}${suffix}`;
578
+ if (suffix && !isScalar) type = isEmbedded ? modelType : 'ID';
579
+ type = isArray ? `[${type}${isArrayRequired ? '!' : ''}]` : type;
580
+ if (!suffix && isRequired) type += '!';
581
+ if (suffix === 'InputCreate' && isRequired && defaultValue != null) type += '!';
582
+ return type;
583
+ }
584
+
585
+ static #identifyOnDeletes(models, parentName) {
586
+ return models.reduce((prev, model) => {
587
+ Object.values(model.fields).filter(f => f.onDelete).forEach((field) => {
588
+ if (`${field.model.name}` === `${parentName}`) {
589
+ if (model.isEntity) {
590
+ prev.push({ model, field, isArray: field.isArray, op: field.onDelete });
591
+ }
592
+ // else {
593
+ // prev.push(...Schema.#identifyOnDeletes(models, model.name).map(od => Object.assign(od, { fieldRef: field.name, isArray: field.isArray, op: field.onDelete })));
594
+ // }
595
+ }
596
+ });
597
+
598
+ // Assign model referential integrity
599
+ return Util.filterBy(prev, (a, b) => `${a.model.name}:${a.field.name}:${a.fieldRef}:${a.op}` === `${b.model.name}:${b.field.name}:${b.fieldRef}:${b.op}`);
600
+ }, []);
601
+ }
602
+ };
@@ -0,0 +1,38 @@
1
+ const Util = require('@coderich/util');
2
+ const PicoMatch = require('picomatch');
3
+ const FillRange = require('fill-range');
4
+ const ObjectHash = require('object-hash');
5
+ const ObjectId = require('bson-objectid');
6
+ const DeepMerge = require('deepmerge');
7
+
8
+ exports.isGlob = str => PicoMatch.scan(str).isGlob;
9
+ exports.globToRegex = (glob, options = {}) => PicoMatch.makeRe(glob, { nocase: true, ...options, expandRange: (a, b) => `(${FillRange(a, b, { toRegex: true })})` });
10
+
11
+ const smartMerge = (target, source, options) => source;
12
+ exports.isScalarValue = value => typeof value !== 'object' && typeof value !== 'function';
13
+ exports.isLeafValue = value => Array.isArray(value) || value instanceof Date || ObjectId.isValid(value) || exports.isScalarValue(value);
14
+ exports.isBasicObject = obj => obj != null && typeof obj === 'object' && !(ObjectId.isValid(obj)) && !(obj instanceof Date) && typeof (obj.then) !== 'function';
15
+ exports.isPlainObject = obj => exports.isBasicObject(obj) && !Array.isArray(obj);
16
+ exports.mergeDeep = (...args) => DeepMerge.all(args, { isMergeableObject: obj => (exports.isPlainObject(obj) || Array.isArray(obj)), arrayMerge: smartMerge });
17
+ exports.hashObject = obj => ObjectHash(obj, { respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedArrays: true, ignoreUnknown: true, replacer: r => (ObjectId.isValid(r) ? `${r}` : r) });
18
+ exports.fromGUID = guid => Buffer.from(`${guid}`, 'base64').toString('ascii').split(',');
19
+ exports.guidToId = (autograph, guid) => (autograph.legacyMode ? guid : exports.uvl(exports.fromGUID(guid)[1], guid));
20
+
21
+ exports.finalizeWhereClause = (obj, arrayOp = '$in') => {
22
+ return Object.entries(Util.flatten(obj, { safe: true })).reduce((prev, [key, value]) => {
23
+ const isArray = Array.isArray(value);
24
+ if (isArray) return Object.assign(prev, { [key]: { [arrayOp]: value } });
25
+ return Object.assign(prev, { [key]: value });
26
+ }, {});
27
+ };
28
+
29
+ exports.getGQLReturnType = (returnType) => {
30
+ const typeMap = { array: /^\[.+\].?$/, connection: /.+Connection!?$/, number: /^(Int|Float)!?$/, scalar: /.*/ };
31
+ return Object.entries(typeMap).find(([type, pattern]) => returnType.match(pattern))[0];
32
+ };
33
+
34
+ exports.removeUndefinedDeep = (obj) => {
35
+ return Util.unflatten(Object.entries(Util.flatten(obj)).reduce((prev, [key, value]) => {
36
+ return value === undefined ? prev : Object.assign(prev, { [key]: value });
37
+ }, {}));
38
+ };
@@ -0,0 +1,7 @@
1
+ class MyError extends Error {
2
+ constructor(e) {
3
+ super(e);
4
+ this.data = e;
5
+ }
6
+ }
7
+ exports.AbortEarlyError = class extends MyError {};
package/CHANGELOG.md DELETED
@@ -1,41 +0,0 @@
1
- # CHANGELOG
2
-
3
- ## v0.10.x
4
- - Replaced ResultSet -> POJOs
5
- - Removed all $field methods (auto populated)
6
- - Removed .toObject()
7
- - $model $save remove $delete $lookup $cursor $pageInfo
8
- - Removed embedded API completely
9
- - Removed Directives
10
- - embedApi -> no replacement
11
- - enforce -> use pipeline methods
12
- - resolve -> use graphql resolvers
13
- - @value -> use @field.instruct directive
14
- - Removed Model.tform() -> use Model.shapeObject(shape, data)
15
- - Removed Transformer + Rule -> use Pipeline
16
- - Removed many pre-defined rules + transformers
17
- - Moved "validator" to dev dependency -> isEmail
18
- - Added QueryBuilder.resolve() terminal command
19
- - Exported SchemaDecorator -> Schema
20
- - Removed embedded schema SystemEvents (internal emitter also removed)
21
- - Removed spread of arguments in QueryBuilder terminal commands (must pass in array)
22
- - Mutate "merged" instead of "input"
23
- - Validate "payload"
24
-
25
- ## v0.9.x
26
- - Subscriptions API
27
- - postMutation no longer mutates "doc" and adds "result"
28
- - Added onDelete defer option
29
-
30
- ## v0.8.x
31
- - Engine 14+
32
-
33
- ## v0.7.x
34
- - Complete overhaul of Query to Mongo Driver (pagination, sorting, counts, etc)
35
- - Removed countModel Queries from the API (now available as `count` property on `Connetion` types)
36
- - Dropped Neo4J (temporarily)
37
-
38
- ## v0.6.x
39
- - Mongo driver no longer checks for `version` directive
40
- - Models no longer share a Connection type; removing the need to use `... on Model` for GraphQL queries
41
- - Added `@field(connection: Boolean)` parameter to specifically indicate fields that should return a Connection type