@coderich/autograph 0.13.34 → 0.13.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coderich/autograph",
3
3
  "main": "index.js",
4
- "version": "0.13.34",
4
+ "version": "0.13.35",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -62,7 +62,7 @@ module.exports = class Pipeline {
62
62
  Pipeline.define('$construct', params => Pipeline.resolve(params, 'construct'), { ignoreNull: false });
63
63
  Pipeline.define('$restruct', params => Pipeline.resolve(params, 'restruct'), { ignoreNull: false });
64
64
  Pipeline.define('$serialize', params => Pipeline.resolve(params, 'serialize'), { ignoreNull: false });
65
- Pipeline.define('$finalize', params => Pipeline.resolve(params, 'finalize'), { ignoreNull: false });
65
+ Pipeline.define('$validate', params => Pipeline.resolve(params, 'validate'), { ignoreNull: false });
66
66
 
67
67
  //
68
68
  Pipeline.define('$pk', (params) => {
@@ -114,14 +114,17 @@ module.exports = class Pipeline {
114
114
  Pipeline.define('ensureFK', ({ query, resolver, field, value }) => {
115
115
  const { type, fkField } = field;
116
116
  const ids = Util.filterBy(Util.ensureArray(value), (a, b) => `${a}` === `${b}`);
117
+ if (!ids.length) return undefined;
117
118
  return resolver.match(type).flags(query.flags).where({ [fkField]: ids }).count().then((count) => {
118
119
  if (count !== ids.length) throw Boom.notFound(`${type} Not Found`);
119
120
  });
120
121
  }, { itemize: false });
121
122
 
122
123
  // Required fields
123
- Pipeline.define('required', ({ query, model, field, value }) => {
124
- if ((query.crud === 'create' && value == null) || (query.crud === 'update' && value === null)) throw Boom.badRequest(`${model.name}.${field.name} is required`);
124
+ Pipeline.define('required', ({ query, model, field, value, path }) => {
125
+ if ((query.crud === 'create' && value == null) || (query.crud === 'update' && value === null)) {
126
+ throw Boom.badRequest(`${model.name}.${field.name} is required`);
127
+ }
125
128
  }, { ignoreNull: false });
126
129
 
127
130
  // A field cannot hold a reference to itself
@@ -1,11 +1,11 @@
1
1
  const { graphql } = require('graphql');
2
2
  const Boom = require('@hapi/boom');
3
3
  const Util = require('@coderich/util');
4
+ const QueryResolver = require('../query/QueryResolver');
4
5
  const Emitter = require('./Emitter');
5
6
  const Loader = require('./Loader');
6
7
  const DataLoader = require('./DataLoader');
7
8
  const Transaction = require('./Transaction');
8
- const QueryResolver = require('../query/QueryResolver');
9
9
 
10
10
  const loaders = {};
11
11
 
@@ -291,7 +291,7 @@ module.exports = class Resolver {
291
291
  const tquery = await $query.transform(false);
292
292
  query = tquery.toObject();
293
293
  event = this.#createEvent(query);
294
- if (query.isMutation) await Emitter.emit('validate', event);
294
+ if (query.isMutation) await Promise.all([...query.input.$thunks, Emitter.emit('validate', event)]);
295
295
  return thunk(tquery);
296
296
  }).then((result) => {
297
297
  event.doc ??= result; // Case of create
@@ -1,39 +1,34 @@
1
1
  const Util = require('@coderich/util');
2
2
 
3
- // { query, path: path.concat(key), context: this.#context }
4
-
5
3
  module.exports = class Transformer {
6
- #config = {
7
- args: {},
8
- shape: {},
9
- defaults: {},
10
- operation: 'set',
11
- };
4
+ #config = { shape: {}, defaults: {}, args: {}, strictSchema: false, keepUndefined: false };
12
5
 
13
- #operations = {
14
- get: {
15
- get: () => {
6
+ #operation = {
7
+ set: (target, prop, startValue, proxy) => {
8
+ if (this.#config.shape[prop]) {
9
+ let previousValue;
16
10
 
17
- },
18
- },
19
- set: {
20
- set: (target, prop, startValue) => {
21
- const transforms = this.#config.shape[prop] ?? [];
22
-
23
- const result = transforms.reduce((value, t) => {
11
+ const result = this.#config.shape[prop].reduce((value, t) => {
12
+ previousValue = value;
24
13
  if (typeof t === 'function') return Util.uvl(t({ startValue, value, ...this.#config.args }), value);
25
14
  prop = t;
26
15
  return value;
27
16
  }, startValue);
28
17
 
29
- target[prop] = result;
30
- return true;
31
- },
18
+ if (result instanceof Promise) {
19
+ target[prop] = previousValue;
20
+ proxy.$thunks.push(result);
21
+ } else if (result !== undefined || this.#config.keepUndefined) {
22
+ target[prop] = result;
23
+ }
24
+ } else if (!this.#config.strictSchema) {
25
+ target[prop] = startValue;
26
+ }
27
+
28
+ return true;
32
29
  },
33
30
  };
34
31
 
35
- #operation;
36
-
37
32
  /**
38
33
  * Allowing construction of object before knowing full configuration
39
34
  */
@@ -46,7 +41,6 @@ module.exports = class Transformer {
46
41
  */
47
42
  config(config = {}) {
48
43
  Object.assign(this.#config, config);
49
- this.#operation = this.#operations[this.#config.operation];
50
44
  return this;
51
45
  }
52
46
 
@@ -58,8 +52,18 @@ module.exports = class Transformer {
58
52
  return this;
59
53
  }
60
54
 
61
- transform(mixed, args) {
55
+ clone(config) {
56
+ return new Transformer({ ...this.#config }).config(config);
57
+ }
58
+
59
+ transform(mixed, args = {}) {
60
+ args.thunks ??= [];
62
61
  this.args(args);
63
- return Util.map(mixed, data => Object.assign(new Proxy({}, this.#operation), this.#config.defaults, data));
62
+
63
+ return Util.map(mixed, (data) => {
64
+ const thunks = Object.defineProperty({}, '$thunks', { value: args.thunks });
65
+ const $data = Object.assign({}, this.#config.defaults, data); // eslint-disable-line
66
+ return Object.assign(new Proxy(thunks, this.#operation), $data);
67
+ });
64
68
  }
65
69
  };
@@ -1,5 +1,4 @@
1
1
  const Util = require('@coderich/util');
2
- const Pipeline = require('../data/Pipeline');
3
2
  const { isGlob, globToRegex, mergeDeep, finalizeWhereClause, JSONParse } = require('../service/AppService');
4
3
 
5
4
  module.exports = class Query {
@@ -46,35 +45,22 @@ module.exports = class Query {
46
45
  }
47
46
 
48
47
  /**
49
- * Run a portion of the pipeline against a data set
50
- */
51
- pipeline(target, data, transformers) {
52
- data = Util.unflatten(data, { safe: true });
53
- const crudMap = { create: ['$construct', '$serialize'], update: ['$restruct', '$serialize'] };
54
- const crudLines = crudMap[this.#query.crud] || [];
55
- const transformerMap = { where: ['$cast', '$instruct', '$serialize'], sort: [], input: [] };
56
- if (this.#query.isMutation) transformerMap.input = ['$default', '$cast', '$normalize', '$instruct', ...crudLines, '$finalize'];
57
- // if (this.#query.crud === 'create') transformerMap.input.unshift('$default'); // Cant because embedded documents on update are really "creates"
58
- transformers = transformers || transformerMap[target];
59
- return this.#pipeline(this.#query, target, this.#model, data, transformers.map(el => Pipeline[el]));
60
- }
61
-
62
- /**
63
- * Transform entire query via pipeline. At minimum, pipeline is needed to unflatten the data...
48
+ * Transform entire query for user consumption
64
49
  */
65
50
  transform(asClone = true) {
66
- return Promise.all([
67
- // this.#model.transformers.input.transform(this.#query.input),
68
- this.pipeline('input', this.#query.input),
69
- this.#query.isNative ? this.#query.where : this.pipeline('where', this.#query.where ?? {}),
70
- this.pipeline('sort', this.#query.sort),
71
- ]).then(([input, where, sort]) => {
72
- if (asClone) return this.clone({ input, where, sort });
73
- this.#query.input = input;
74
- this.#query.where = where;
75
- this.#query.sort = sort;
76
- return this;
77
- });
51
+ const args = { query: this.#query, resolver: this.#resolver, context: this.#context };
52
+
53
+ const [input, where, sort] = [
54
+ this.#model.transformers.input.transform(Util.unflatten(this.#query.input, { safe: true }), args),
55
+ this.#query.isNative ? this.#query.where : this.#model.transformers.where.transform(Util.unflatten(this.#query.where ?? {}, { safe: true }), args),
56
+ this.#model.transformers.sort.transform(Util.unflatten(this.#query.sort, { safe: true }), args),
57
+ ];
58
+
59
+ if (asClone) return this.clone({ input, where, sort });
60
+ this.#query.input = input;
61
+ this.#query.where = where;
62
+ this.#query.sort = sort;
63
+ return this;
78
64
  }
79
65
 
80
66
  /**
@@ -86,7 +72,7 @@ module.exports = class Query {
86
72
  const query = this.clone({
87
73
  model: this.#model.key,
88
74
  select: this.#query.select.map(name => this.#model.fields[name].key),
89
- input: this.#model.walk(input, node => node.value !== undefined && Object.assign(node, { key: node.field.key })),
75
+ input: this.#model.transformers.toDriver.transform(input),
90
76
  where: isNative ? where : this.#model.walk(where, node => Object.assign(node, { key: node.field.key })),
91
77
  sort: this.#model.walk(sort, node => Object.assign(node, { key: node.field.key })),
92
78
  before: (!isCursorPaging || !before) ? undefined : JSONParse(Buffer.from(before, 'base64').toString('ascii')),
@@ -99,36 +85,6 @@ module.exports = class Query {
99
85
  return query;
100
86
  }
101
87
 
102
- /**
103
- * Recursive pipeline function
104
- */
105
- #pipeline(query, target, model, data, transformers = [], paths = []) {
106
- return Util.mapPromise(data, (doc, index) => {
107
- const path = [...paths];
108
- if (Array.isArray(data)) path.push(index);
109
- if (target === 'input') doc = mergeDeep(model.pipelineFields.input, doc);
110
- else if (target === 'where') doc = mergeDeep(model.pipelineFields.where, doc);
111
-
112
- return Util.pipeline(Object.entries(doc).map(([key, startValue]) => async (prev) => {
113
- const field = model.fields[key];
114
- if (!field) return prev;
115
-
116
- // Transform value
117
- let $value = await Util.pipeline(transformers.map(t => async (value) => {
118
- const v = await t({ query, model, field, value, path: path.concat(key), startValue, resolver: this.#resolver, context: this.#context, schema: this.#schema });
119
- return v === undefined ? value : v;
120
- }), startValue);
121
-
122
- // If it's embedded - delegate
123
- if (field.isEmbedded) $value = await this.#pipeline(query, target, field.model, $value, transformers, path.concat(key));
124
-
125
- // Assign it back
126
- if (target === 'input' && $value === undefined) return prev;
127
- return Object.assign(prev, { [field.name]: $value });
128
- }), {});
129
- });
130
- }
131
-
132
88
  /**
133
89
  * Finalize the query for the driver
134
90
  */
@@ -1,5 +1,5 @@
1
1
  const Query = require('./Query');
2
- const { getGQLReturnType, getGQLSelectFields, mergeDeep } = require('../service/AppService');
2
+ const { getGQLReturnType, mergeDeep } = require('../service/AppService');
3
3
 
4
4
  module.exports = class QueryBuilder {
5
5
  #query;
@@ -44,9 +44,9 @@ module.exports = class QueryResolver extends QueryBuilder {
44
44
  });
45
45
  }
46
46
  case 'pushOne': {
47
- return this.#get(query).then(async (doc) => {
47
+ return this.#get(query).then((doc) => {
48
48
  const [key] = Object.keys(input);
49
- const values = get(await query.pipeline('input', input), key);
49
+ const values = get(this.#model.transformers.input.transform(input), key);
50
50
  const $input = { [key]: (get(doc, key) || []).concat(...values) };
51
51
  return this.#resolver.match(this.#model.name).id(doc.id).save($input);
52
52
  });
@@ -58,9 +58,9 @@ module.exports = class QueryResolver extends QueryBuilder {
58
58
  });
59
59
  }
60
60
  case 'pullOne': {
61
- return this.#get(query).then(async (doc) => {
61
+ return this.#get(query).then((doc) => {
62
62
  const [key] = Object.keys(input);
63
- const values = get(await query.pipeline('input', input), key);
63
+ const values = get(this.#model.transformers.input.transform(input), key);
64
64
  const $input = { [key]: (get(doc, key) || []).filter(el => values.every(v => `${v}` !== `${el}`)) };
65
65
  return this.#resolver.match(this.#model.name).id(doc.id).save($input);
66
66
  });
@@ -72,9 +72,9 @@ module.exports = class QueryResolver extends QueryBuilder {
72
72
  });
73
73
  }
74
74
  case 'spliceOne': {
75
- return this.#get(query).then(async (doc) => {
75
+ return this.#get(query).then((doc) => {
76
76
  const [key] = Object.keys(input);
77
- const [find, replace] = get(await query.pipeline('input', input), key);
77
+ const [find, replace] = get(this.#model.transformers.input.transform(input), key);
78
78
  const $input = { [key]: (get(doc, key) || []).map(el => (`${el}` === `${find}` ? replace : el)) };
79
79
  return this.#resolver.match(this.#model.name).id(doc.id).save($input);
80
80
  });
@@ -16,8 +16,8 @@ const scalarKinds = [Kind.SCALAR_TYPE_DEFINITION, Kind.SCALAR_TYPE_EXTENSION];
16
16
  const fieldKinds = [Kind.FIELD_DEFINITION];
17
17
  const modelKinds = [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].concat(interfaceKinds);
18
18
  const allowedKinds = modelKinds.concat(fieldKinds).concat(Kind.DOCUMENT, Kind.NON_NULL_TYPE, Kind.NAMED_TYPE, Kind.LIST_TYPE, Kind.DIRECTIVE).concat(scalarKinds).concat(enumKinds);
19
- const pipelines = ['finalize', 'construct', 'restruct', 'instruct', 'normalize', 'serialize'];
20
- const inputPipelines = ['finalize', 'construct', 'instruct', 'normalize', 'serialize'];
19
+ const pipelines = ['validate', 'construct', 'restruct', 'instruct', 'normalize', 'serialize'];
20
+ const inputPipelines = ['validate', 'construct', 'instruct', 'normalize', 'serialize'];
21
21
  const scalars = ['ID', 'String', 'Float', 'Int', 'Boolean'];
22
22
 
23
23
  module.exports = class Schema {
@@ -145,8 +145,9 @@ module.exports = class Schema {
145
145
  generator: this.#config.generators?.default,
146
146
  pipelines: pipelines.reduce((prev, key) => Object.assign(prev, { [key]: [] }), {}),
147
147
  transformers: {
148
- input: new Transformer(),
149
- doc: new Transformer({ args: { model, schema: this.#schema } }),
148
+ input: new Transformer({ args: { schema: this.#schema, path: [] } }),
149
+ where: new Transformer({ args: { schema: this.#schema, path: [] } }),
150
+ doc: new Transformer({ args: { schema: this.#schema, path: [] } }),
150
151
  },
151
152
  directives: {},
152
153
  toString: () => name,
@@ -158,9 +159,6 @@ module.exports = class Schema {
158
159
  name,
159
160
  key: name,
160
161
  pipelines: pipelines.reduce((prev, key) => Object.assign(prev, { [key]: [] }), {}),
161
- transformers: {
162
- input: new Transformer({ args: { model, field, schema: this.#schema } }),
163
- },
164
162
  directives: {},
165
163
  toString: () => name,
166
164
  };
@@ -185,7 +183,7 @@ module.exports = class Schema {
185
183
 
186
184
  // Define (and assign) an Allow pipeline for the enumeration
187
185
  Pipeline.define(name, Pipeline.Allow(...values), { configurable: true });
188
- target.pipelines.finalize.push(name);
186
+ target.pipelines.validate.push(name);
189
187
  }
190
188
 
191
189
  if (node.kind === Kind.NON_NULL_TYPE) {
@@ -239,8 +237,8 @@ module.exports = class Schema {
239
237
  target.isConnection = value;
240
238
  break;
241
239
  }
242
- case `${directives.field}-validate`: { // Alias for finalize
243
- target.pipelines.finalize = target.pipelines.finalize.concat(value).filter(Boolean);
240
+ case `${directives.field}-validate`: {
241
+ target.pipelines.validate = target.pipelines.validate.concat(value).filter(Boolean);
244
242
  break;
245
243
  }
246
244
  case `${directives.field}-transform`: { // Deprecated
@@ -350,22 +348,74 @@ module.exports = class Schema {
350
348
  where: Object.values($model.fields).filter(f => f.pipelines.instruct.length).reduce((prev, f) => Object.assign(prev, { [f.name]: undefined }), {}),
351
349
  };
352
350
 
351
+ $model.transformers.toDriver = new Transformer({
352
+ shape: Object.values($model.fields).reduce((prev, curr) => {
353
+ const rules = [curr.key]; // Rename key
354
+ if (curr.isEmbedded) rules.unshift(({ value }) => Util.map(value, v => curr.model.transformers.toDriver.transform(v)));
355
+ return Object.assign(prev, { [curr.name]: rules });
356
+ }, {}),
357
+ });
358
+
353
359
  $model.transformers.input.config({
360
+ strictSchema: true,
361
+ shape: Object.values($model.fields).reduce((prev, curr) => {
362
+ const args = { model: $model, field: curr };
363
+
364
+ const rules = [
365
+ a => Pipeline.$default({ ...a, ...args, path: a.path.concat(curr.name) }),
366
+ a => Pipeline.$cast({ ...a, ...args, path: a.path.concat(curr.name) }),
367
+ a => Pipeline.$normalize({ ...a, ...args, path: a.path.concat(curr.name) }),
368
+ a => Pipeline.$instruct({ ...a, ...args, path: a.path.concat(curr.name) }),
369
+ (a) => {
370
+ if (a.query.crud === 'create') return Pipeline.$construct({ ...a, ...args, path: a.path.concat(curr.name) });
371
+ if (a.query.crud === 'update') return Pipeline.$restruct({ ...a, ...args, path: a.path.concat(curr.name) });
372
+ return undefined;
373
+ },
374
+ a => Pipeline.$serialize({ ...a, ...args, path: a.path.concat(curr.name) }),
375
+ ];
376
+
377
+ if (curr.isEmbedded) {
378
+ rules.push(a => Util.map(a.value, (value, i) => {
379
+ const path = a.path.concat(curr.name);
380
+ if (curr.isArray) path.push(i);
381
+ return curr.model.transformers.input.transform(value, { ...args, thunks: a.thunks, query: a.query, resolver: a.resolver, context: a.context, path });
382
+ }));
383
+ }
384
+
385
+ // Validate
386
+ rules.push(a => Pipeline.$validate({ ...a, ...args, path: a.path.concat(curr.name) }));
387
+
388
+ return Object.assign(prev, { [curr.name]: rules });
389
+ }, {}),
390
+ defaults: $model.pipelineFields.input,
391
+ });
392
+
393
+ $model.transformers.where.config({
394
+ keepUndefined: true,
354
395
  shape: Object.values($model.fields).reduce((prev, curr) => {
396
+ const args = { model: $model, field: curr };
397
+
355
398
  const rules = [
356
- a => Pipeline.$default({ ...a, field: curr }),
357
- a => Pipeline.$cast({ ...a, field: curr }),
358
- a => Pipeline.$normalize({ ...a, field: curr }),
359
- a => Pipeline.$instruct({ ...a, field: curr }),
360
- // a => Pipeline.$finalize({ ...a, field: curr }),
399
+ a => Pipeline.$cast({ ...a, ...args, path: a.path.concat(curr.name) }),
400
+ a => Pipeline.$instruct({ ...a, ...args, path: a.path.concat(curr.name) }),
401
+ a => Pipeline.$serialize({ ...a, ...args, path: a.path.concat(curr.name) }),
361
402
  ];
362
- // if (curr.isEmbedded) rules.push(a => Util.map(a.value, value => curr.model.transformers.input.transform(value)));
363
- if (curr.isEmbedded) rules.push(a => curr.model.transformers.input.transform(a.value));
364
- // rules.push(curr.key); // rename
403
+
404
+ if (curr.isEmbedded) {
405
+ rules.push(a => Util.map(a.value, (value, i) => {
406
+ const path = a.path.concat(curr.name);
407
+ if (curr.isArray) path.push(i);
408
+ return curr.model.transformers.where.transform(value, { ...args, query: a.query, context: a.context, path });
409
+ }));
410
+ }
411
+
365
412
  return Object.assign(prev, { [curr.name]: rules });
366
413
  }, {}),
414
+ defaults: $model.pipelineFields.where,
367
415
  });
368
416
 
417
+ $model.transformers.sort = $model.transformers.where.clone({ defaults: {} });
418
+
369
419
  $model.transformers.doc.config({
370
420
  shape: Object.values($model.fields).reduce((prev, curr) => {
371
421
  const rules = [curr.name]; // Rename key
@@ -406,7 +456,7 @@ module.exports = class Schema {
406
456
 
407
457
  if ($field.isArray) $field.pipelines.normalize.unshift('toArray');
408
458
  if ($field.isPrimaryKey) $field.pipelines.serialize.unshift('$pk'); // Will create/convert to FK type always
409
- if ($field.isRequired && $field.isPersistable && !$field.isVirtual) $field.pipelines.finalize.push('required');
459
+ if ($field.isRequired && $field.isPersistable && !$field.isVirtual) $field.pipelines.validate.push('required');
410
460
 
411
461
  if ($field.isFKReference) {
412
462
  const to = $field.model.key;
@@ -415,7 +465,7 @@ module.exports = class Schema {
415
465
  const as = `join_${to}`;
416
466
  $field.join = { to, on, from, as };
417
467
  $field.pipelines.serialize.unshift('$fk'); // Will convert to FK type IFF defined in payload
418
- $field.pipelines.finalize.push('ensureFK'); // Absolute Last
468
+ $field.pipelines.validate.push('ensureFK'); // Absolute Last
419
469
  }
420
470
  });
421
471
 
@@ -561,8 +611,7 @@ module.exports = class Schema {
561
611
  construct: [AutoGraphPipelineEnum!]
562
612
  restruct: [AutoGraphPipelineEnum!]
563
613
  serialize: [AutoGraphPipelineEnum!]
564
- finalize: [AutoGraphPipelineEnum!]
565
- validate: [AutoGraphPipelineEnum!] # Alias for finalize
614
+ validate: [AutoGraphPipelineEnum!]
566
615
 
567
616
  # TEMP TO APPEASE TRANSITION
568
617
  ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)