@coderich/autograph 0.13.109 → 0.14.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coderich/autograph",
3
3
  "main": "index.js",
4
- "version": "0.13.109",
4
+ "version": "0.14.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -16,7 +16,7 @@
16
16
  "clinic": "clinic flame -- node ./test/server"
17
17
  },
18
18
  "dependencies": {
19
- "@coderich/util": "1.2.1",
19
+ "@coderich/util": "2.2.0",
20
20
  "@graphql-tools/merge": "9.0.0",
21
21
  "@graphql-tools/resolvers-composition": "7.0.0",
22
22
  "@hapi/boom": "10.0.1",
@@ -38,7 +38,6 @@
38
38
  "@graphql-tools/schema": "10.0.0",
39
39
  "clinic": "13.0.0",
40
40
  "graphql": "16.8.1",
41
- "mongodb": "5.9.2",
42
41
  "mongodb-memory-server": "8.13.0"
43
42
  },
44
43
  "peerDependencies": {
@@ -1,7 +1,6 @@
1
1
  const get = require('lodash.get');
2
2
  const Util = require('@coderich/util');
3
3
  const DataLoader = require('dataloader');
4
- const { hashObject } = require('../service/AppService');
5
4
 
6
5
  module.exports = class Loader {
7
6
  #model;
@@ -11,7 +10,7 @@ module.exports = class Loader {
11
10
  constructor(model, resolver) {
12
11
  this.#model = model;
13
12
  this.#resolver = resolver;
14
- this.#model.loader.cacheKeyFn ??= query => hashObject(query.toCacheKey());
13
+ this.#model.loader.cacheKeyFn ??= query => query.toCacheKey();
15
14
  this.#loader = new DataLoader(keys => this.#resolve(keys), this.#model.loader);
16
15
  }
17
16
 
@@ -33,10 +32,9 @@ module.exports = class Loader {
33
32
  const $query = query.toDriver().toObject();
34
33
  const key = $query.batch ?? '__default__';
35
34
  let [values] = key === '__default__' ? [] : Object.values(Util.flatten($query.where, { safe: true }));
36
- values = Array.from(new Set(Util.ensureArray(values)));
37
- const $values = values.map(value => (value instanceof RegExp ? value : new RegExp(`${value}`, 'i')));
35
+ values = Loader.#dedup(Util.ensureArray(values));
38
36
  prev[key] = prev[key] || [];
39
- prev[key].push({ query, $query, values, $values, i });
37
+ prev[key].push({ query, $query, values, i });
40
38
  return prev;
41
39
  }, {});
42
40
 
@@ -47,36 +45,37 @@ module.exports = class Loader {
47
45
  }
48
46
  default: {
49
47
  // Collect all the values for the where clause
50
- const values = Array.from(new Set(batches.map(batch => batch.values).flat()));
48
+ const values = Loader.#dedup(batches.map(batch => batch.values).flat());
51
49
  const $query = { ...batches[0].$query, op: 'findMany', where: { [key]: values } };
52
50
 
53
- //
54
51
  if (values.length < 3) {
55
52
  return batches.map(batch => this.#model.source.client.resolve(batch.$query).then(data => ({ data, ...batch })));
56
53
  }
57
54
 
58
- // Collect all the $values (Regular Expressions) to match doc (result) data by
59
- const $values = Array.from(new Set(batches.map(batch => batch.$values).flat()));
60
- const docsByRegExpKey = $values.reduce((map, re) => map.set(re, []), new Map());
61
-
62
55
  // Now we perform 1 query, instead of many smaller ones
63
56
  return this.#model.source.client.resolve($query).then((docs) => {
64
- // This one-time transformation keys all the docs by $value (regex) match
57
+ const docsByKey = new Map();
58
+
65
59
  docs.forEach((doc) => {
66
60
  Util.pathmap(key, doc, (value) => {
67
- docsByRegExpKey.forEach((set, re) => {
68
- Util.map(value, (v) => {
69
- if (`${v}`.match(re)) {
70
- set.push(doc);
71
- }
72
- });
61
+ Util.ensureArray(value).forEach((v) => {
62
+ const k = `${v}`;
63
+ if (!docsByKey.has(k)) docsByKey.set(k, []);
64
+ docsByKey.get(k).push(doc);
73
65
  });
74
66
  return value;
75
67
  });
76
68
  });
77
69
 
78
70
  return batches.map((batch) => {
79
- const matches = Array.from(new Set(batch.$values.map(re => docsByRegExpKey.get(re)).flat().filter(v => v !== undefined)));
71
+ const matches = Array.from(new Set(batch.values.flatMap((v) => {
72
+ if (v instanceof RegExp) {
73
+ const result = [];
74
+ docsByKey.forEach((d, k) => { if (v.test(k)) result.push(...d); });
75
+ return result;
76
+ }
77
+ return docsByKey.get(`${v}`) || [];
78
+ })));
80
79
  const data = batch.$query.op === 'findOne' ? matches[0] : matches;
81
80
  return { data, ...batch };
82
81
  });
@@ -103,6 +102,16 @@ module.exports = class Loader {
103
102
  // }));
104
103
  }
105
104
 
105
+ // Deduplicate an array of values that may contain RegExp objects.
106
+ // new Set() compares by reference, so two RegExp literals with identical patterns
107
+ // are treated as distinct. Using toString() as the Map key handles this correctly
108
+ // while preserving the actual RegExp instance as the value.
109
+ static #dedup(arr) {
110
+ const seen = new Map();
111
+ arr.forEach(v => seen.set(v instanceof RegExp ? v.toString() : v, v));
112
+ return Array.from(seen.values());
113
+ }
114
+
106
115
  static #paginateResults(rs, query) {
107
116
  let hasNextPage = false;
108
117
  let hasPreviousPage = false;
@@ -1,4 +1,4 @@
1
- const EventEmitter = require('events');
1
+ const EventEmitter = require('node:events');
2
2
  const Util = require('@coderich/util');
3
3
  const { AbortEarlyError } = require('../service/ErrorService');
4
4
 
@@ -9,28 +9,36 @@ const { AbortEarlyError } = require('../service/ErrorService');
9
9
  * If it expects more than 1 we block and wait for it to finish.
10
10
  */
11
11
  class Emitter extends EventEmitter {
12
+ #cache = new Map();
13
+
14
+ #invalidate(event) {
15
+ this.#cache.delete(event);
16
+ }
17
+
18
+ #getListeners(event) {
19
+ if (!this.#cache.has(event)) {
20
+ const [basicFuncs, nextFuncs] = this.rawListeners(event).reduce((prev, wrapper) => {
21
+ const { listener = wrapper } = wrapper;
22
+ wrapper.priority = listener.priority ?? 0;
23
+ return prev[listener.length < 2 ? 0 : 1].push(wrapper) && prev;
24
+ }, [[], []]);
25
+ this.#cache.set(event, { basicFuncs: basicFuncs.sort(Emitter.sort), nextFuncs: nextFuncs.sort(Emitter.sort) });
26
+ }
27
+ return this.#cache.get(event);
28
+ }
29
+
12
30
  emit(event, data) {
13
- // Here we pull out functions with "next" vs those without
14
- const [basicFuncs, nextFuncs] = this.rawListeners(event).reduce((prev, wrapper) => {
15
- const { listener = wrapper } = wrapper;
16
- const isBasic = listener.length < 2;
17
- wrapper.priority = listener.priority ?? 0;
18
- return prev[isBasic ? 0 : 1].push(wrapper) && prev;
19
- }, [[], []]);
20
-
21
- // // Basic functions are not designed to be bound to the query execution so we need an isolated resolver from any transactions
22
- // const resolver = data?.resolver?.clone();
23
- // const basicData = { ...data, resolver };
31
+ const { basicFuncs, nextFuncs } = this.#getListeners(event);
24
32
 
25
33
  return new Promise((resolve, reject) => {
26
34
  // Basic functions run first; if they return a value they abort the flow of execution
27
- basicFuncs.sort(Emitter.sort).forEach((fn) => {
35
+ basicFuncs.forEach((fn) => {
28
36
  const value = fn(data);
29
37
  if (value !== undefined && !(value instanceof Promise)) throw new AbortEarlyError(value);
30
38
  });
31
39
 
32
40
  // Next functions are async and control the timing of the next phase
33
- Promise.all(nextFuncs.sort(Emitter.sort).map((fn) => {
41
+ Promise.all(nextFuncs.map((fn) => {
34
42
  return new Promise((next, err) => {
35
43
  Promise.resolve().then(() => fn(data, next)).catch(err);
36
44
  }).then((result) => {
@@ -45,14 +53,47 @@ class Emitter extends EventEmitter {
45
53
 
46
54
  on(event, listener, priority = 0) {
47
55
  listener.priority = priority;
56
+ this.#invalidate(event);
48
57
  return super.on(event, listener);
49
58
  }
50
59
 
60
+ addListener(event, listener, priority = 0) {
61
+ return this.on(event, listener, priority);
62
+ }
63
+
64
+ once(event, listener, priority = 0) {
65
+ listener.priority = priority;
66
+ this.#invalidate(event);
67
+ return super.once(event, listener);
68
+ }
69
+
51
70
  prependListener(event, listener, priority = 0) {
52
71
  listener.priority = priority;
72
+ this.#invalidate(event);
53
73
  return super.prependListener(event, listener);
54
74
  }
55
75
 
76
+ prependOnceListener(event, listener, priority = 0) {
77
+ listener.priority = priority;
78
+ this.#invalidate(event);
79
+ return super.prependOnceListener(event, listener);
80
+ }
81
+
82
+ removeListener(event, listener) {
83
+ this.#invalidate(event);
84
+ return super.removeListener(event, listener);
85
+ }
86
+
87
+ off(event, listener) {
88
+ return this.removeListener(event, listener);
89
+ }
90
+
91
+ removeAllListeners(event) {
92
+ if (event) this.#invalidate(event);
93
+ else this.#cache.clear();
94
+ return super.removeAllListeners(event);
95
+ }
96
+
56
97
  /**
57
98
  * Syntactic sugar to listen on query keys
58
99
  */
@@ -63,7 +63,6 @@ module.exports = class Pipeline {
63
63
  Pipeline.define('$construct', params => Pipeline.resolve(params, 'construct'), { ignoreNull: false });
64
64
  Pipeline.define('$restruct', params => Pipeline.resolve(params, 'restruct'), { ignoreNull: false });
65
65
  Pipeline.define('$serialize', params => Pipeline.resolve(params, 'serialize'), { ignoreNull: false });
66
- Pipeline.define('$deserialize', params => Pipeline.resolve(params, 'deserialize'), { ignoreNull: false });
67
66
  Pipeline.define('$validate', params => Pipeline.resolve(params, 'validate'), { ignoreNull: false });
68
67
 
69
68
  //
@@ -245,7 +245,7 @@ module.exports = class Resolver {
245
245
  model = this.#schema.models[model];
246
246
 
247
247
  return Object.defineProperties(Util.map(result, (doc) => {
248
- const $doc = model.transformers.doc.transform(doc, { resolver: this, context: this.#context });
248
+ const $doc = model.docTransform(doc);
249
249
 
250
250
  // Assign useful/needed meta data
251
251
  return Object.defineProperties($doc, {
@@ -307,44 +307,26 @@ module.exports = class Resolver {
307
307
  const tquery = $query.transform(false);
308
308
  const query = tquery.toObject();
309
309
  const type = query.isMutation ? 'Mutation' : 'Query';
310
- const event = this.#createEvent(query);
310
+ const event = { schema: this.#schema, context: this.#context, resolver: this, query };
311
311
 
312
312
  return Emitter.emit(`pre${type}`, event).then(async (resultEarly) => {
313
- if (resultEarly !== undefined) return resultEarly; // Nothing to validate/transform
314
- // if (query.crud === 'update' && Util.isEqual({ added: {}, updated: {}, deleted: {} }, Util.changeset(query.doc, query.input))) return query.doc;
313
+ if (resultEarly !== undefined) return resultEarly;
315
314
 
316
315
  if (['create', 'update'].includes(query.crud)) {
317
- tquery.validate(); // Transformation sets $thunks
316
+ tquery.validate(); // sets async $thunks (e.g. ensureFK)
318
317
  await Promise.all([...query.input.$thunks]);
319
318
  await Emitter.emit('validate', event);
320
319
  }
321
320
 
322
321
  return thunk(tquery);
323
322
  }).then((result) => {
324
- event.result = result; // backwards compat
325
323
  query.result = result;
326
324
  return Emitter.emit(`post${type}`, event);
327
325
  }).then((result = query.result) => result).catch((e) => {
328
326
  throw Boom.boomify(e);
329
- // const { data = {} } = e;
330
- // throw Boom.boomify(e, { data: { ...event, ...data } });
331
327
  });
332
328
  }
333
329
 
334
- #createEvent(query) {
335
- const event = { schema: this.#schema, context: this.#context, resolver: this, query };
336
-
337
- // Backwards compat
338
- Object.assign(event, query);
339
- query.match = event.args.where;
340
- query.toObject = () => query;
341
- event.merged = event.input;
342
- event.input = Util.unflatten(event.args?.input, { safe: true });
343
- event.doc ??= {};
344
-
345
- return event;
346
- }
347
-
348
330
  static $loader(name, resolver, config) {
349
331
  if (!name) return loaders;
350
332
  if (!resolver) return loaders[name];
@@ -9,6 +9,8 @@ module.exports = class Transformer {
9
9
  keepUndefined: false, // If true, will preserve undefined values
10
10
  };
11
11
 
12
+ #callArgs = {}; // Ephemeral per-call merge of #config.args + transform() args; never persisted
13
+
12
14
  #operation = {
13
15
  set: (target, prop, startValue, proxy) => {
14
16
  if (this.#config.shape[prop]) {
@@ -16,7 +18,7 @@ module.exports = class Transformer {
16
18
 
17
19
  const result = this.#config.shape[prop].reduce((value, t) => {
18
20
  previousValue = value;
19
- if (typeof t === 'function') return Util.uvl(t({ startValue, value, ...this.#config.args }), value);
21
+ if (typeof t === 'function') return Util.uvl(t({ startValue, value, ...this.#callArgs }), value);
20
22
  prop = t; // rename key
21
23
  return value;
22
24
  }, startValue);
@@ -64,7 +66,7 @@ module.exports = class Transformer {
64
66
 
65
67
  transform(mixed, args = {}) {
66
68
  args.thunks ??= [];
67
- this.args(args);
69
+ this.#callArgs = { ...this.#config.args, ...args };
68
70
 
69
71
  const transformed = Util.map(mixed, (data) => {
70
72
  const thunks = Object.defineProperty({}, '$thunks', { value: args.thunks });
@@ -39,19 +39,18 @@ module.exports = class Query {
39
39
  }
40
40
 
41
41
  toCacheKey() {
42
- return {
42
+ return JSON.stringify({
43
43
  op: this.#query.op,
44
44
  select: this.#query.select,
45
45
  where: this.#query.where,
46
46
  sort: this.#query.sort,
47
- joins: this.#query.joins,
48
47
  skip: this.#query.skip,
49
48
  limit: this.#query.limit,
50
49
  before: this.#query.before,
51
50
  after: this.#query.after,
52
51
  first: this.#query.first,
53
52
  last: this.#query.last,
54
- };
53
+ });
55
54
  }
56
55
 
57
56
  /**
@@ -134,14 +133,25 @@ module.exports = class Query {
134
133
  query.batch = (op === 'findOne' || op === 'findMany') && Object.keys(query.where).length === 1 ? Object.keys(query.where)[0] : '__default__';
135
134
 
136
135
  // Construct joins
136
+ const joinsByPath = {};
137
137
  query.joins = [];
138
138
 
139
+ // Recursively search a join tree for the first join matching a target model key
140
+ const findJoin = (joins, modelKey) => {
141
+ for (const j of joins) {
142
+ if (j.to === modelKey) return j;
143
+ const found = findJoin(j.children, modelKey);
144
+ if (found) return found;
145
+ }
146
+ return null;
147
+ };
148
+
139
149
  this.#model.walk(joinData, (node) => {
140
150
  const { model, field, key, value, isLeaf, path, run } = node;
141
151
 
142
152
  if (field.join) {
143
153
  let isArray;
144
- const join = { ...field.join, where: {} };
154
+ const join = { ...field.join, where: {}, children: [] };
145
155
 
146
156
  if (run.length > 1) {
147
157
  join.from = path.reduce((prev, curr, i) => {
@@ -153,12 +163,21 @@ module.exports = class Query {
153
163
 
154
164
  join.isArray = isArray || model.resolvePath(join.from).isArray;
155
165
 
156
- query.joins.push(join);
166
+ // Find the nearest ancestor FK join by scanning ancestor paths from closest to farthest.
167
+ // Joins reached through embedded fields have no FK ancestor and stay at the root level.
168
+ const parentJoin = path.slice(0, -1).reduceRight((found, _, i) => found || joinsByPath[path.slice(0, i + 1).join('.')], null);
169
+
170
+ if (parentJoin) {
171
+ parentJoin.children.push(join);
172
+ } else {
173
+ query.joins.push(join);
174
+ }
175
+ joinsByPath[path.join('.')] = join;
157
176
  }
158
177
 
159
178
  if (isLeaf) {
160
179
  const $model = field.model || model;
161
- const join = query.joins.find(j => j.to === $model.key);
180
+ const join = findJoin(query.joins, $model.key);
162
181
  const $value = Util.map(value, el => (isGlob(el) ? globToRegex(el) : el));
163
182
  const $$value = Array.isArray($value) ? { $in: $value } : $value;
164
183
  const from = field.model ? join.from : key;
@@ -16,7 +16,7 @@ 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 = ['validate', 'construct', 'restruct', 'instruct', 'normalize', 'serialize', 'deserialize'];
19
+ const pipelines = ['validate', 'construct', 'restruct', 'instruct', 'normalize', 'serialize'];
20
20
  const createPipelines = ['validate', 'construct', 'instruct', 'normalize', 'serialize'];
21
21
  const updatePipelines = ['validate', 'restruct', 'instruct', 'normalize', 'serialize'];
22
22
  // const validatePipelines = ['validate', 'instruct', 'normalize', 'serialize'];
@@ -97,7 +97,7 @@ module.exports = class Schema {
97
97
  try {
98
98
  const $td = typeof td === 'string' ? parse(td) : td;
99
99
  return $td;
100
- } catch (e) {
100
+ } catch {
101
101
  console.log(`Unable to parse typeDef (being ignored):\n${td}`); // eslint-disable-line
102
102
  return null;
103
103
  }
@@ -153,7 +153,6 @@ module.exports = class Schema {
153
153
  create: new Transformer({ args: { schema: this.#schema, path: [] } }),
154
154
  update: new Transformer({ args: { schema: this.#schema, path: [] } }),
155
155
  where: new Transformer({ args: { schema: this.#schema, path: [] } }),
156
- doc: new Transformer({ args: { schema: this.#schema, path: [] } }),
157
156
  },
158
157
  directives: {},
159
158
  ignorePaths: [],
@@ -452,33 +451,6 @@ module.exports = class Schema {
452
451
 
453
452
  $model.transformers.sort = $model.transformers.where.clone({ defaults: {} });
454
453
 
455
- $model.transformers.doc.config({
456
- shape: Object.values($model.fields).reduce((prev, curr) => {
457
- const args = { model: $model, field: curr };
458
-
459
- const rules = [
460
- curr.name, // Rename key
461
- a => Pipeline.$deserialize({ ...a, ...args, path: a.path.concat(curr.name) }),
462
- ];
463
-
464
- if (curr.isArray) rules.unshift(({ value }) => (value == null ? value : Util.ensureArray(value)));
465
-
466
- if (curr.isEmbedded) {
467
- rules.unshift(a => Util.map(a.value, (value, i) => {
468
- const path = a.path.concat(curr.name);
469
- if (curr.isArray) path.push(i);
470
- return curr.model.transformers.doc.transform(value, { ...args, query: a.query, context: a.context, path });
471
- }));
472
- }
473
-
474
- return Object.assign(prev, { [curr.key]: rules });
475
- }, {}),
476
- defaults: Object.values($model.fields).reduce((prev, curr) => {
477
- if (curr.defaultValue === undefined) return prev;
478
- return Object.assign(prev, { [curr.key]: curr.defaultValue });
479
- }, {}),
480
- });
481
-
482
454
  $model.transformers.validate.config({
483
455
  strictSchema: true,
484
456
  shape: Object.values($model.fields).reduce((prev, curr) => {
@@ -499,6 +471,21 @@ module.exports = class Schema {
499
471
  }, {}),
500
472
  });
501
473
 
474
+ // Deserialize/docs special case handling for performance
475
+ const docFields = Object.values($model.fields);
476
+ $model.docTransform = (doc) => {
477
+ if (doc == null) return doc;
478
+ const out = {};
479
+ for (const docField of docFields) {
480
+ let value = docField.key in doc ? doc[docField.key] : docField.defaultValue;
481
+ if (value === undefined) continue; // eslint-disable-line
482
+ if (docField.isArray) value = value == null ? value : Util.ensureArray(value);
483
+ if (docField.isEmbedded) value = Util.map(value, v => docField.model.docTransform(v));
484
+ out[docField.name] = value;
485
+ }
486
+ return out;
487
+ };
488
+
502
489
  Util.traverse(Object.values($model.fields), (f, info) => {
503
490
  const path = info.path.concat(f.name);
504
491
  if (f.isEmbedded) return { value: Object.values(f.model.fields), info: { path } };
@@ -720,7 +707,6 @@ module.exports = class Schema {
720
707
  construct: [AutoGraphPipelineEnum!]
721
708
  restruct: [AutoGraphPipelineEnum!]
722
709
  serialize: [AutoGraphPipelineEnum!]
723
- deserialize: [AutoGraphPipelineEnum!]
724
710
  validate: [AutoGraphPipelineEnum!]
725
711
 
726
712
  # TEMP TO APPEASE TRANSITION
@@ -903,7 +889,7 @@ module.exports = class Schema {
903
889
  `,
904
890
  resolvers: {
905
891
  Node: {
906
- __resolveType: (doc, args, context, info) => doc.__typename, // eslint-disable-line no-underscore-dangle
892
+ __resolveType: (doc, args, context, info) => doc.__typename,
907
893
  },
908
894
  ...queryModels.reduce((prev, model) => {
909
895
  return Object.assign(prev, {
@@ -926,7 +912,7 @@ module.exports = class Schema {
926
912
  const model = schema.models[modelName];
927
913
  return context[schema.namespace].resolver.match(model).id(id).info(info).one().then((result) => {
928
914
  if (result == null) return result;
929
- result.__typename = modelName; // eslint-disable-line no-underscore-dangle
915
+ result.__typename = modelName;
930
916
  return result;
931
917
  });
932
918
  },
@@ -40,7 +40,7 @@ exports.JSONParse = (mixed) => {
40
40
  try {
41
41
  const json = JSON.parse(mixed);
42
42
  return json;
43
- } catch (e) {
43
+ } catch {
44
44
  return undefined;
45
45
  }
46
46
  };
@@ -1,11 +0,0 @@
1
- const API = jest.requireActual('../Emitter');
2
- const { mergeDeep } = require('../../service/AppService');
3
-
4
- const { emit } = API;
5
-
6
- API.emit = (eventName, data) => {
7
- if (API.cloneData) data = { ...data, query: mergeDeep({}, data.query) };
8
- return emit.call(API, eventName, data);
9
- };
10
-
11
- module.exports = API;
@@ -1,32 +0,0 @@
1
- /**
2
- * We need to export Pipeline as a POJO so jest can spy on it
3
- * This is because all the methods are defined using Object.defineProperty
4
- */
5
- const Pipeline = jest.requireActual('../Pipeline');
6
- const Util = require('@coderich/util');
7
-
8
- const API = {};
9
-
10
- Pipeline.resolve = (params, pipeline) => {
11
- const transformers = params.field.pipelines[pipeline] || [];
12
-
13
- return transformers.reduce((value, t) => {
14
- return Util.uvl(API[t]({ ...params, value }), value);
15
- }, params.value);
16
-
17
- // return Util.pipeline(transformers.map(t => (value) => {
18
- // return API[t]({ ...params, value });
19
- // }), params.value);
20
- };
21
-
22
- Object.getOwnPropertyNames(Pipeline).reduce((prev, key) => {
23
- return Object.assign(prev, { [key]: Pipeline[key] });
24
- }, API);
25
-
26
- // For those defined outside of Pipeline.js itself
27
- API.define = (key, ...args) => {
28
- Pipeline.define(key, ...args);
29
- API[key] = Pipeline[key];
30
- };
31
-
32
- module.exports = API;