@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
@@ -0,0 +1,593 @@
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
+ if (typeof schema === 'string') schema = { typeDefs: schema };
65
+ else if (schema instanceof Schema) schema = schema.toObject();
66
+ const { typeDefs, resolvers } = schema;
67
+ if (typeDefs) this.#typeDefs = mergeTypeDefs([parse(typeDefs), this.#typeDefs], { noLocation: true, reverseDirectives: true, onFieldTypeConflict: a => a });
68
+ if (resolvers) this.#resolvers = mergeDeep(this.#resolvers, resolvers);
69
+ return this;
70
+ }
71
+
72
+ /**
73
+ * Parse typeDefs; returning a schema POJO
74
+ */
75
+ parse() {
76
+ if (this.#schema) return this.#schema;
77
+
78
+ this.#schema = { models: {}, indexes: [] };
79
+ let model, field, isField, isList;
80
+ const thunks = [];
81
+
82
+ // Parse AST
83
+ visit(this.#typeDefs, {
84
+ enter: (node) => {
85
+ const name = node.name?.value;
86
+ if (!allowedKinds.includes(node.kind)) return false;
87
+
88
+ if (modelKinds.includes(node.kind) && !operations.includes(name)) {
89
+ model = this.#schema.models[name] = {
90
+ name,
91
+ key: name,
92
+ fields: {},
93
+ idField: 'id',
94
+ crud: 'crud',
95
+ scope: 'crud',
96
+ isPersistable: true,
97
+ source: this.#config.dataSources?.default,
98
+ loader: this.#config.dataLoaders?.default,
99
+ directives: {},
100
+ toString: () => name,
101
+ };
102
+ } else if (node.kind === Kind.FIELD_DEFINITION) {
103
+ isField = true;
104
+ field = model.fields[name] = {
105
+ name,
106
+ key: name,
107
+ crud: 'crud',
108
+ pipelines: pipelines.reduce((prev, key) => Object.assign(prev, { [key]: [] }), {}),
109
+ directives: {},
110
+ toString: () => name,
111
+ };
112
+ } else if (node.kind === Kind.NON_NULL_TYPE) {
113
+ field[isList ? 'isArrayRequired' : 'isRequired'] = true;
114
+ } else if (node.kind === Kind.NAMED_TYPE) {
115
+ field.type = node.name.value;
116
+ } else if (node.kind === Kind.LIST_TYPE) {
117
+ field.isArray = true;
118
+ isList = true;
119
+ } else if (node.kind === Kind.DIRECTIVE) {
120
+ const target = isField ? field : model;
121
+ target.directives[name] = target.directives[name] || {};
122
+
123
+ if (name === 'model') model.isMarkedModel = true;
124
+ else if (name === 'index') this.#schema.indexes.push({ model });
125
+
126
+ node.arguments.forEach((arg) => {
127
+ const key = arg.name.value;
128
+ const { value: val, values } = arg.value;
129
+ const value = values ? values.map(n => n.value) : val;
130
+ target.directives[name][key] = value;
131
+
132
+ if (name === 'index') this.#schema.indexes[this.#schema.indexes.length - 1][key] = value;
133
+
134
+ switch (`${name}-${key}`) {
135
+ // Model specific directives
136
+ case 'model-id': {
137
+ model.idField = value;
138
+ break;
139
+ }
140
+ case 'model-source': {
141
+ model.source = this.#config.dataSources?.[value];
142
+ break;
143
+ }
144
+ case 'model-loader': {
145
+ model.loader = this.#config.dataLoaders?.[value];
146
+ break;
147
+ }
148
+ case 'model-embed': {
149
+ model.isEmbedded = value;
150
+ break;
151
+ }
152
+ // Field specific directives
153
+ case 'field-default': {
154
+ field.defaultValue = value;
155
+ break;
156
+ }
157
+ case 'field-connection': {
158
+ field.isConnection = value;
159
+ break;
160
+ }
161
+ case 'link-by': {
162
+ field.linkBy = value;
163
+ field.isVirtual = true;
164
+ break;
165
+ }
166
+ // Generic by target directives
167
+ case 'model-persist': case 'field-persist': {
168
+ target.isPersistable = value;
169
+ break;
170
+ }
171
+ case 'model-crud': case 'model-scope': case 'field-crud': {
172
+ target[key] = Util.nvl(value, '');
173
+ break;
174
+ }
175
+ case 'model-key': case 'model-meta': case 'field-key': case 'field-onDelete': {
176
+ target[key] = value;
177
+ break;
178
+ }
179
+ default: {
180
+ if (pipelines.includes(key)) {
181
+ target.pipelines[key] = target.pipelines[key].concat(value).filter(Boolean);
182
+ }
183
+ break;
184
+ }
185
+ }
186
+ });
187
+ }
188
+
189
+ return undefined; // Continue
190
+ },
191
+ leave: (node) => {
192
+ if (modelKinds.includes(node.kind) && !operations.includes(node.name.value)) {
193
+ const $model = model;
194
+ // const idField = $model.fields[$model.idField];
195
+ // $model.primaryKey = Util.nvl(idField?.key, idField?.name, 'id');
196
+
197
+ // Model resolution after field resolution (push)
198
+ thunks.push(($schema) => {
199
+ $model.isEntity = Boolean($model.isMarkedModel && !$model.isEmbedded);
200
+
201
+ $model.resolvePath = (path, prop = 'name') => this.#schema.resolvePath(`${$model[prop]}.${path}`, prop);
202
+
203
+ $model.isJoinPath = (path, prop = 'name') => {
204
+ let foundJoin = false;
205
+ return !path.split('.').every((el, i, arr) => {
206
+ if (foundJoin) return false;
207
+ const $field = $model.resolvePath(arr.slice(0, i + 1).join('.'), prop);
208
+ foundJoin = $field.isVirtual || $field.isFKReference;
209
+ return !$field.isVirtual;
210
+ });
211
+ };
212
+
213
+ $model.walk = (data, fn, opts = {}) => {
214
+ if (data == null || !isPlainObject(data)) return data;
215
+
216
+ // Options
217
+ opts.key = opts.key ?? 'name';
218
+ opts.run = opts.run ?? [];
219
+ opts.path = opts.path ?? [];
220
+ opts.itemize = opts.itemize ?? true;
221
+
222
+ return Object.entries(data).reduce((prev, [key, value]) => {
223
+ // Find the field; remove it if not found
224
+ const $field = Object.values($model.fields).find(el => el[opts.key] === key);
225
+ if (!$field) return prev;
226
+
227
+ // Invoke callback function; allowing result to be modified in order to change key/value
228
+ let run = opts.run.concat($field[opts.key]);
229
+ const path = opts.path.concat($field[opts.key]);
230
+ const isLeaf = isLeafValue(value);
231
+ const $node = fn({ model: $model, field: $field, key, value, path, run, isLeaf });
232
+ if (!$node) return prev;
233
+
234
+ // Recursive walk
235
+ if (!$field.model?.isEmbedded) run = [];
236
+ const $value = opts.itemize && $field.model && isBasicObject($node.value) ? Util.map($node.value, el => $field.model.walk(el, fn, { ...opts, path, run })) : $node.value;
237
+ return Object.assign(prev, { [$node.key]: $value });
238
+ }, {});
239
+ };
240
+
241
+ // Pre-processing
242
+ $model.pipelineFields = {
243
+ 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 }), {}),
244
+ where: Object.values($model.fields).filter(f => f.pipelines.instruct.length).reduce((prev, f) => Object.assign(prev, { [f.name]: undefined }), {}),
245
+ };
246
+ });
247
+ } else if (node.kind === Kind.FIELD_DEFINITION) {
248
+ const $field = field;
249
+ const $model = model;
250
+
251
+ $field.isPrimaryKey = Boolean($field.name === model.idField);
252
+ $field.isPersistable = Util.uvl($field.isPersistable, model.isPersistable, true);
253
+
254
+ // Field resolution comes first (unshift)
255
+ thunks.unshift(($schema) => {
256
+ $field.model = $schema.models[$field.type];
257
+ $field.linkBy = $field.linkBy || $field.model?.idField;
258
+ $field.linkField = $field.isVirtual ? $model.fields[$model.idField] : $field;
259
+ $field.isFKReference = !$field.isPrimaryKey && $field.model?.isMarkedModel && !$field.model?.isEmbedded;
260
+ $field.isEmbedded = Boolean($field.model && !$field.isFKReference && !$field.isPrimaryKey);
261
+ $field.isScalar = Boolean(!$field.model || scalars.includes($field.type));
262
+
263
+ if ($field.isArray) $field.pipelines.normalize.unshift('toArray');
264
+ if ($field.isPrimaryKey) $field.pipelines.serialize.unshift('$pk'); // Will create/convert to FK type always
265
+ if ($field.isFKReference) $field.pipelines.serialize.unshift('$fk'); // Will convert to FK type IFF defined in payload
266
+
267
+ if ($field.isRequired && $field.isPersistable && !$field.isVirtual) $field.pipelines.finalize.push('required');
268
+ if ($field.isFKReference) {
269
+ const to = $field.model.key;
270
+ const on = $field.model.fields[$field.linkBy].key;
271
+ const from = $field.linkField.key;
272
+ const as = `join_${to}`;
273
+ $field.join = { to, on, from, as };
274
+ $field.pipelines.finalize.push('ensureId'); // Absolute Last
275
+ }
276
+ });
277
+
278
+ isField = false;
279
+ } else if (node.kind === Kind.LIST_TYPE) {
280
+ isList = false;
281
+ }
282
+ },
283
+ });
284
+
285
+ // Resolve data thunks
286
+ thunks.forEach(thunk => thunk(this.#schema));
287
+
288
+ // Resolve indexes
289
+ this.#schema.indexes = this.#schema.indexes.map((index) => {
290
+ const { key } = index.model;
291
+ const { name, type } = index;
292
+ const on = index.on.map(f => index.model.fields[f].key);
293
+ return { key, name, type, on };
294
+ });
295
+
296
+ // Resolve referential integrity
297
+ Object.values(this.#schema.models).forEach(($model) => {
298
+ $model.referentialIntegrity = Schema.#identifyOnDeletes(Object.values(this.#schema.models), $model.name);
299
+ });
300
+
301
+ // Helper methods
302
+ this.#schema.resolvePath = (path, prop = 'key') => {
303
+ const [modelKey, ...fieldKeys] = path.split('.');
304
+ const $model = Object.values(this.#schema.models).find(el => el[prop] === modelKey);
305
+ if (!$model || !fieldKeys.length) return $model;
306
+ return fieldKeys.reduce((parent, key) => Object.values(parent.fields || parent.model.fields).find(el => el[prop] === key) || parent, $model);
307
+ };
308
+
309
+ // Return schema
310
+ return this.#schema;
311
+ }
312
+
313
+ api() {
314
+ return this.merge(Schema.#api(this.parse()));
315
+ }
316
+
317
+ setup() {
318
+ return Emitter.emit('setup', this.#schema);
319
+ }
320
+
321
+ toObject() {
322
+ return {
323
+ typeDefs: this.#typeDefs,
324
+ resolvers: this.#resolvers,
325
+ };
326
+ }
327
+
328
+ makeExecutableSchema() {
329
+ return this.#config.makeExecutableSchema(this.toObject());
330
+ }
331
+
332
+ static #framework() {
333
+ return parse(`
334
+ scalar AutoGraphMixed
335
+
336
+ enum AutoGraphIndexEnum { unique }
337
+ enum AutoGraphOnDeleteEnum { cascade nullify restrict defer }
338
+ enum AutoGraphPipelineEnum { ${Object.keys(Pipeline).filter(k => !k.startsWith('$')).join(' ')} }
339
+
340
+ directive @model(
341
+ id: String # Specify the ID/PK field (default "id")
342
+ key: String # Specify db table/collection name
343
+ crud: AutoGraphMixed # CRUD API
344
+ scope: AutoGraphMixed #
345
+ meta: AutoGraphMixed # Custom input "meta" field for mutations
346
+ source: AutoGraphMixed # Data source (default: "default")
347
+ embed: Boolean # Mark this an embedded model (default false)
348
+ persist: Boolean # Persist this model (default true)
349
+ ) on OBJECT | INTERFACE
350
+
351
+ directive @field(
352
+ key: String # Specify db key
353
+ persist: Boolean # Persist this field (default true)
354
+ connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
355
+ default: AutoGraphMixed # Define a default value
356
+ crud: AutoGraphMixed # CRUD API
357
+ onDelete: AutoGraphOnDeleteEnum # onDelete behavior
358
+
359
+ # Pipeline Structure
360
+ normalize: [AutoGraphPipelineEnum!]
361
+ instruct: [AutoGraphPipelineEnum!]
362
+ construct: [AutoGraphPipelineEnum!]
363
+ restruct: [AutoGraphPipelineEnum!]
364
+ serialize: [AutoGraphPipelineEnum!]
365
+ finalize: [AutoGraphPipelineEnum!]
366
+ ) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
367
+
368
+ directive @link(
369
+ to: AutoGraphMixed # The MODEL to link to (default's to modelRef)
370
+ by: AutoGraphMixed! # The FIELD to match yourself by
371
+ use: AutoGraphMixed # The VALUE to use (default's to @link'd value); useful for many-to-many relationships
372
+ ) on FIELD_DEFINITION
373
+
374
+ directive @index(
375
+ name: String
376
+ on: [AutoGraphMixed!]!
377
+ type: AutoGraphIndexEnum!
378
+ ) repeatable on OBJECT
379
+ `);
380
+ }
381
+
382
+ static #api(schema) {
383
+ // These models are for creating types
384
+ const readModels = Object.values(schema.models).filter(model => model.crud.includes('r'));
385
+ const createModels = Object.values(schema.models).filter(model => model.crud.includes('c'));
386
+ const updateModels = Object.values(schema.models).filter(model => model.crud.includes('u'));
387
+
388
+ // These are for defining schema queries/mutations
389
+ const entityModels = Object.values(schema.models).filter(model => model.isEntity);
390
+ const queryModels = entityModels.filter(model => model.crud.includes('r'));
391
+ const mutationModels = entityModels.filter(model => ['c', 'u', 'd'].some(el => model.crud.includes(el)));
392
+ const subscriptionModels = entityModels.filter(model => model.crud.includes('s'));
393
+
394
+ return {
395
+ typeDefs: `
396
+ scalar AutoGraphMixed
397
+
398
+ interface Node { id: ID! }
399
+
400
+ enum SortOrderEnum { asc desc }
401
+ enum SubscriptionCrudEnum { create update delete }
402
+ enum SubscriptionWhenEnum { preEvent postEvent }
403
+
404
+ type PageInfo {
405
+ startCursor: String!
406
+ endCursor: String!
407
+ hasPreviousPage: Boolean!
408
+ hasNextPage: Boolean!
409
+ }
410
+
411
+ ${entityModels.map(model => `
412
+ extend type ${model} implements Node {
413
+ id: ID!
414
+ }
415
+ `)}
416
+
417
+ ${readModels.map((model) => {
418
+ const fields = Object.values(model.fields).filter(field => field.crud.includes('r'));
419
+ const connectionFields = fields.filter(field => field.isConnection);
420
+
421
+ return `
422
+ input ${model}InputWhere {
423
+ ${fields.map(field => `${field}: ${field.model?.isEntity ? `${field.model}InputWhere` : 'AutoGraphMixed'}`)}
424
+ }
425
+ input ${model}InputSort {
426
+ ${fields.map(field => `${field}: ${field.model?.isEntity ? `${field.model}InputSort` : 'SortOrderEnum'}`)}
427
+ }
428
+ type ${model}Connection {
429
+ count: Int!
430
+ pageInfo: PageInfo
431
+ edges: [${model}Edge]
432
+ }
433
+ type ${model}Edge {
434
+ node: ${model}
435
+ cursor: String
436
+ }
437
+ ${connectionFields.length ? `
438
+ extend type ${model} {
439
+ ${connectionFields.map(field => `${field}: ${field.model}Connection`)}
440
+ }
441
+ ` : ''}
442
+ `;
443
+ })}
444
+
445
+ ${createModels.map((model) => {
446
+ const fields = Object.values(model.fields).filter(field => field.crud.includes('c') && !field.isVirtual);
447
+
448
+ return `
449
+ input ${model}InputCreate {
450
+ ${fields.map(field => `${field}: ${Schema.#getGQLType(field, 'InputCreate')}`)}
451
+ }
452
+ `;
453
+ })}
454
+
455
+ ${updateModels.map((model) => {
456
+ const fields = Object.values(model.fields).filter(field => field.crud.includes('u') && !field.isVirtual);
457
+
458
+ return `
459
+ input ${model}InputUpdate {
460
+ ${fields.map(field => `${field}: ${Schema.#getGQLType(field, 'InputUpdate')}`)}
461
+ }
462
+ `;
463
+ })}
464
+
465
+ type Query {
466
+ node(id: ID!): Node
467
+ ${queryModels.map(model => `
468
+ get${model}(id: ID!): ${model}
469
+ find${model}(
470
+ where: ${model}InputWhere
471
+ sortBy: ${model}InputSort
472
+ limit: Int
473
+ skip: Int
474
+ first: Int
475
+ after: String
476
+ last: Int
477
+ before: String
478
+ ): ${model}Connection!
479
+ `)}
480
+ }
481
+
482
+ ${mutationModels.length ? `
483
+ type Mutation {
484
+ ${mutationModels.map((model) => {
485
+ const api = [];
486
+ const meta = model.meta ? `meta: ${model.meta}` : '';
487
+ if (model.crud.includes('c')) api.push(`create${model}(input: ${model}InputCreate! ${meta}): ${model}!`);
488
+ if (model.crud.includes('u')) api.push(`update${model}(id: ID! input: ${model}InputUpdate ${meta}): ${model}!`);
489
+ if (model.crud.includes('d')) api.push(`delete${model}(id: ID! ${meta}): ${model}!`);
490
+ return api.join('\n');
491
+ })}
492
+ }
493
+ ` : ''}
494
+
495
+ ${subscriptionModels.length ? `
496
+ type Subscription {
497
+ ${subscriptionModels.map(model => `
498
+ ${model}(
499
+ on: [SubscriptionCrudEnum!]! = [create, update, delete]
500
+ filter: ${model}SubscriptionInputFilter
501
+ ): ${model}SubscriptionPayload!
502
+ `)}
503
+ }
504
+ ` : ''}
505
+ `,
506
+ resolvers: {
507
+ Node: {
508
+ __resolveType: (doc, args, context, info) => doc.__typename, // eslint-disable-line no-underscore-dangle
509
+ },
510
+ ...queryModels.reduce((prev, model) => {
511
+ return Object.assign(prev, {
512
+ [`${model}Connection`]: {
513
+ count: ({ count }) => count(),
514
+ edges: ({ edges }) => edges().then(rs => rs.map(node => ({ cursor: node.$cursor, node }))),
515
+ pageInfo: ({ pageInfo }) => pageInfo().then(rs => rs?.$pageInfo),
516
+ },
517
+ });
518
+ }, {}),
519
+ Query: queryModels.reduce((prev, model) => {
520
+ return Object.assign(prev, {
521
+ [`get${model}`]: (doc, args, context, info) => context.autograph.resolver.match(model).args(args).one({ required: true }),
522
+ [`find${model}`]: (doc, args, context, info) => {
523
+ return {
524
+ edges: () => context.autograph.resolver.match(model).args(args).many(),
525
+ count: () => context.autograph.resolver.match(model).args(args).count(),
526
+ pageInfo: () => context.autograph.resolver.match(model).args(args).many(),
527
+ };
528
+ },
529
+ });
530
+ }, {
531
+ node: (doc, args, context, info) => {
532
+ const { id } = args;
533
+ const [modelName] = fromGUID(id);
534
+ const model = schema.models[modelName];
535
+ return context.autograph.resolver.match(model).id(id).one().then((result) => {
536
+ if (result == null) return result;
537
+ result.__typename = modelName; // eslint-disable-line no-underscore-dangle
538
+ return result;
539
+ });
540
+ },
541
+ }),
542
+ ...(mutationModels.length ? {
543
+ Mutation: mutationModels.reduce((prev, model) => {
544
+ if (model.crud.includes('c')) prev[`create${model}`] = (doc, args, context, info) => context.autograph.resolver.match(model).args(args).save(args.input);
545
+ if (model.crud.includes('u')) prev[`update${model}`] = (doc, args, context, info) => context.autograph.resolver.match(model).args(args).save(args.input);
546
+ if (model.crud.includes('d')) prev[`delete${model}`] = (doc, args, context, info) => context.autograph.resolver.match(model).args(args).delete();
547
+ return prev;
548
+ }, {}),
549
+ } : {}),
550
+ ...readModels.reduce((prev, model) => {
551
+ return Object.assign(prev, {
552
+ [model]: Object.values(model.fields).filter(field => field.model?.isEntity).reduce((prev2, field) => {
553
+ return Object.assign(prev2, {
554
+ [field]: (doc, args, context, info) => {
555
+ return context.autograph.resolver.match(field.model).where({ [field.linkBy]: doc[field.linkField.name] }).args(args).resolve(info);
556
+ },
557
+ });
558
+ }, {}),
559
+ });
560
+ }, {}),
561
+ },
562
+ };
563
+ }
564
+
565
+ static #getGQLType(field, suffix) {
566
+ let { type } = field;
567
+ const { isEmbedded, isRequired, isScalar, isArray, isArrayRequired, defaultValue } = field;
568
+ const modelType = `${type}${suffix}`;
569
+ if (suffix && !isScalar) type = isEmbedded ? modelType : 'ID';
570
+ type = isArray ? `[${type}${isArrayRequired ? '!' : ''}]` : type;
571
+ if (!suffix && isRequired) type += '!';
572
+ if (suffix === 'InputCreate' && isRequired && defaultValue != null) type += '!';
573
+ return type;
574
+ }
575
+
576
+ static #identifyOnDeletes(models, parentName) {
577
+ return models.reduce((prev, model) => {
578
+ Object.values(model.fields).filter(f => f.onDelete).forEach((field) => {
579
+ if (`${field.model.name}` === `${parentName}`) {
580
+ if (model.isEntity) {
581
+ prev.push({ model, field, isArray: field.isArray, op: field.onDelete });
582
+ }
583
+ // else {
584
+ // prev.push(...Schema.#identifyOnDeletes(models, model.name).map(od => Object.assign(od, { fieldRef: field.name, isArray: field.isArray, op: field.onDelete })));
585
+ // }
586
+ }
587
+ });
588
+
589
+ // Assign model referential integrity
590
+ 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}`);
591
+ }, []);
592
+ }
593
+ };
@@ -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