@coderich/autograph 0.13.33 → 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.33",
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
@@ -161,9 +164,9 @@ module.exports = class Pipeline {
161
164
  static resolve(params, pipeline) {
162
165
  const transformers = params.field.pipelines[pipeline] || [];
163
166
 
164
- return Util.pipeline(transformers.map(t => (value) => {
165
- return Pipeline[t]({ ...params, value });
166
- }), params.value);
167
+ return transformers.reduce((value, t) => {
168
+ return Util.uvl(Pipeline[t]({ ...params, value }), value);
169
+ }, params.value);
167
170
  }
168
171
  };
169
172
 
@@ -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,30 +1,36 @@
1
1
  const Util = require('@coderich/util');
2
2
 
3
3
  module.exports = class Transformer {
4
- #config;
5
- #operation;
6
- #operations = {
7
- get: {
8
- get: () => {
4
+ #config = { shape: {}, defaults: {}, args: {}, strictSchema: false, keepUndefined: false };
9
5
 
10
- },
11
- },
12
- set: {
13
- set: (target, prop, value) => {
14
- const transforms = this.#config.shape[prop] ?? [];
15
- const $value = transforms.reduce((prev, t) => {
16
- if (typeof t === 'function') return t(prev);
6
+ #operation = {
7
+ set: (target, prop, startValue, proxy) => {
8
+ if (this.#config.shape[prop]) {
9
+ let previousValue;
10
+
11
+ const result = this.#config.shape[prop].reduce((value, t) => {
12
+ previousValue = value;
13
+ if (typeof t === 'function') return Util.uvl(t({ startValue, value, ...this.#config.args }), value);
17
14
  prop = t;
18
- return prev;
19
- }, value);
20
- target[prop] = $value;
21
- return true;
22
- },
15
+ return value;
16
+ }, startValue);
17
+
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;
23
29
  },
24
30
  };
25
31
 
26
32
  /**
27
- * Allowing construction of object before knowing configuration
33
+ * Allowing construction of object before knowing full configuration
28
34
  */
29
35
  constructor(config = {}) {
30
36
  this.config(config);
@@ -34,14 +40,30 @@ module.exports = class Transformer {
34
40
  * Re-assign configuration after instantiation
35
41
  */
36
42
  config(config = {}) {
37
- this.#config = config;
38
- this.#config.shape ??= {};
39
- this.#config.defaults ??= {};
40
- this.#config.operation ??= 'set';
41
- this.#operation = this.#operations[this.#config.operation];
43
+ Object.assign(this.#config, config);
44
+ return this;
42
45
  }
43
46
 
44
- transform(mixed) {
45
- return Util.map(mixed, data => Object.assign(new Proxy({}, this.#operation), this.#config.defaults, data));
47
+ /**
48
+ * Re-assign args after instantiation
49
+ */
50
+ args(args = {}) {
51
+ Object.assign(this.#config.args, args);
52
+ return this;
53
+ }
54
+
55
+ clone(config) {
56
+ return new Transformer({ ...this.#config }).config(config);
57
+ }
58
+
59
+ transform(mixed, args = {}) {
60
+ args.thunks ??= [];
61
+ this.args(args);
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
+ });
46
68
  }
47
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,34 +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.pipeline('input', this.#query.input),
68
- this.#query.isNative ? this.#query.where : this.pipeline('where', this.#query.where ?? {}),
69
- this.pipeline('sort', this.#query.sort),
70
- ]).then(([input, where, sort]) => {
71
- if (asClone) return this.clone({ input, where, sort });
72
- this.#query.input = input;
73
- this.#query.where = where;
74
- this.#query.sort = sort;
75
- return this;
76
- });
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;
77
64
  }
78
65
 
79
66
  /**
@@ -85,7 +72,7 @@ module.exports = class Query {
85
72
  const query = this.clone({
86
73
  model: this.#model.key,
87
74
  select: this.#query.select.map(name => this.#model.fields[name].key),
88
- input: this.#model.walk(input, node => node.value !== undefined && Object.assign(node, { key: node.field.key })),
75
+ input: this.#model.transformers.toDriver.transform(input),
89
76
  where: isNative ? where : this.#model.walk(where, node => Object.assign(node, { key: node.field.key })),
90
77
  sort: this.#model.walk(sort, node => Object.assign(node, { key: node.field.key })),
91
78
  before: (!isCursorPaging || !before) ? undefined : JSONParse(Buffer.from(before, 'base64').toString('ascii')),
@@ -98,36 +85,6 @@ module.exports = class Query {
98
85
  return query;
99
86
  }
100
87
 
101
- /**
102
- * Recursive pipeline function
103
- */
104
- #pipeline(query, target, model, data, transformers = [], paths = []) {
105
- return Util.mapPromise(data, (doc, index) => {
106
- const path = [...paths];
107
- if (Array.isArray(data)) path.push(index);
108
- if (target === 'input') doc = mergeDeep(model.pipelineFields.input, doc);
109
- else if (target === 'where') doc = mergeDeep(model.pipelineFields.where, doc);
110
-
111
- return Util.pipeline(Object.entries(doc).map(([key, startValue]) => async (prev) => {
112
- const field = model.fields[key];
113
- if (!field) return prev;
114
-
115
- // Transform value
116
- let $value = await Util.pipeline(transformers.map(t => async (value) => {
117
- const v = await t({ query, model, field, value, path: path.concat(key), startValue, resolver: this.#resolver, context: this.#context, schema: this.#schema });
118
- return v === undefined ? value : v;
119
- }), startValue);
120
-
121
- // If it's embedded - delegate
122
- if (field.isEmbedded) $value = await this.#pipeline(query, target, field.model, $value, transformers, path.concat(key));
123
-
124
- // Assign it back
125
- if (target === 'input' && $value === undefined) return prev;
126
- return Object.assign(prev, { [field.name]: $value });
127
- }), {});
128
- });
129
- }
130
-
131
88
  /**
132
89
  * Finalize the query for the driver
133
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 {
@@ -144,7 +144,11 @@ module.exports = class Schema {
144
144
  loader: this.#config.dataLoaders?.default,
145
145
  generator: this.#config.generators?.default,
146
146
  pipelines: pipelines.reduce((prev, key) => Object.assign(prev, { [key]: [] }), {}),
147
- transformers: { doc: new Transformer() },
147
+ transformers: {
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: [] } }),
151
+ },
148
152
  directives: {},
149
153
  toString: () => name,
150
154
  };
@@ -155,7 +159,6 @@ module.exports = class Schema {
155
159
  name,
156
160
  key: name,
157
161
  pipelines: pipelines.reduce((prev, key) => Object.assign(prev, { [key]: [] }), {}),
158
- transformers: { doc: new Transformer() },
159
162
  directives: {},
160
163
  toString: () => name,
161
164
  };
@@ -180,7 +183,7 @@ module.exports = class Schema {
180
183
 
181
184
  // Define (and assign) an Allow pipeline for the enumeration
182
185
  Pipeline.define(name, Pipeline.Allow(...values), { configurable: true });
183
- target.pipelines.finalize.push(name);
186
+ target.pipelines.validate.push(name);
184
187
  }
185
188
 
186
189
  if (node.kind === Kind.NON_NULL_TYPE) {
@@ -234,8 +237,8 @@ module.exports = class Schema {
234
237
  target.isConnection = value;
235
238
  break;
236
239
  }
237
- case `${directives.field}-validate`: { // Alias for finalize
238
- 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);
239
242
  break;
240
243
  }
241
244
  case `${directives.field}-transform`: { // Deprecated
@@ -345,11 +348,79 @@ module.exports = class Schema {
345
348
  where: Object.values($model.fields).filter(f => f.pipelines.instruct.length).reduce((prev, f) => Object.assign(prev, { [f.name]: undefined }), {}),
346
349
  };
347
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
+
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,
395
+ shape: Object.values($model.fields).reduce((prev, curr) => {
396
+ const args = { model: $model, field: curr };
397
+
398
+ const rules = [
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) }),
402
+ ];
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
+
412
+ return Object.assign(prev, { [curr.name]: rules });
413
+ }, {}),
414
+ defaults: $model.pipelineFields.where,
415
+ });
416
+
417
+ $model.transformers.sort = $model.transformers.where.clone({ defaults: {} });
418
+
348
419
  $model.transformers.doc.config({
349
420
  shape: Object.values($model.fields).reduce((prev, curr) => {
350
421
  const rules = [curr.name]; // Rename key
351
- if (curr.isArray) rules.unshift(v => (v == null ? v : Util.ensureArray(v)));
352
- if (curr.isEmbedded) rules.unshift(v => Util.map(v, el => curr.model.transformers.doc.transform(el)));
422
+ if (curr.isArray) rules.unshift(({ value }) => (value == null ? value : Util.ensureArray(value)));
423
+ if (curr.isEmbedded) rules.unshift(({ value }) => Util.map(value, v => curr.model.transformers.doc.transform(v)));
353
424
  return Object.assign(prev, { [curr.key]: rules });
354
425
  }, {}),
355
426
  });
@@ -385,7 +456,7 @@ module.exports = class Schema {
385
456
 
386
457
  if ($field.isArray) $field.pipelines.normalize.unshift('toArray');
387
458
  if ($field.isPrimaryKey) $field.pipelines.serialize.unshift('$pk'); // Will create/convert to FK type always
388
- 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');
389
460
 
390
461
  if ($field.isFKReference) {
391
462
  const to = $field.model.key;
@@ -394,7 +465,7 @@ module.exports = class Schema {
394
465
  const as = `join_${to}`;
395
466
  $field.join = { to, on, from, as };
396
467
  $field.pipelines.serialize.unshift('$fk'); // Will convert to FK type IFF defined in payload
397
- $field.pipelines.finalize.push('ensureFK'); // Absolute Last
468
+ $field.pipelines.validate.push('ensureFK'); // Absolute Last
398
469
  }
399
470
  });
400
471
 
@@ -540,8 +611,7 @@ module.exports = class Schema {
540
611
  construct: [AutoGraphPipelineEnum!]
541
612
  restruct: [AutoGraphPipelineEnum!]
542
613
  serialize: [AutoGraphPipelineEnum!]
543
- finalize: [AutoGraphPipelineEnum!]
544
- validate: [AutoGraphPipelineEnum!] # Alias for finalize
614
+ validate: [AutoGraphPipelineEnum!]
545
615
 
546
616
  # TEMP TO APPEASE TRANSITION
547
617
  ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)