@coderich/autograph 0.10.2 → 0.10.3

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/CHANGELOG.md CHANGED
@@ -2,27 +2,25 @@
2
2
 
3
3
  ## v0.10.x
4
4
  - Replaced ResultSet -> POJOs
5
- - Removed all $$ magic resolver methods
6
- - Removed all $ magic field methods
5
+ - Removed all $field methods (auto populated)
7
6
  - Removed .toObject()
7
+ - $model $save remove $delete $lookup $cursor $pageInfo
8
8
  - Removed embedded API completely
9
9
  - Removed Directives
10
10
  - embedApi -> no replacement
11
11
  - enforce -> use pipeline methods
12
12
  - resolve -> use graphql resolvers
13
13
  - @value -> use @field.instruct directive
14
- - Removed toId Transform -> use @field(id: '')
15
14
  - Removed Model.tform() -> use Model.shapeObject(shape, data)
16
- - Removed Resolver.toResultSet() -> ? TBD ?
17
15
  - Removed Transformer + Rule -> use Pipeline
18
16
  - Removed many pre-defined rules + transformers
19
- - Pre-defined names start with $ (eg. $toLowerCase)
20
17
  - Moved "validator" to dev dependency -> isEmail
21
18
  - Added QueryBuilder.resolve() terminal command
22
19
  - Exported SchemaDecorator -> Schema
23
20
  - Removed embedded schema SystemEvents (internal emitter also removed)
24
21
  - Removed spread of arguments in QueryBuilder terminal commands (must pass in array)
25
- - Must pass makeExecutableSchema to Schema constructor
22
+ - Mutate "merged" instead of "input"
23
+ - Validate "payload"
26
24
 
27
25
  ## v0.9.x
28
26
  - Subscriptions API
package/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const Schema = require('./src/core/Schema');
2
- const GraphQL = require('./src/core/GraphQL');
3
2
  const Resolver = require('./src/core/Resolver');
4
3
  const Pipeline = require('./src/data/Pipeline');
5
4
  const Driver = require('./src/driver');
@@ -7,7 +6,6 @@ const { eventEmitter: Emitter } = require('./src/service/event.service');
7
6
 
8
7
  module.exports = {
9
8
  Schema,
10
- GraphQL,
11
9
  Resolver,
12
10
  Driver,
13
11
  Emitter,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coderich/autograph",
3
3
  "author": "Richard Livolsi (coderich)",
4
- "version": "0.10.2",
4
+ "version": "0.10.3",
5
5
  "description": "AutoGraph",
6
6
  "keywords": [
7
7
  "graphql",
@@ -31,7 +31,6 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@hapi/boom": "^9.1.0",
34
- "axios": "^0.21.4",
35
34
  "dataloader": "^2.0.0",
36
35
  "deepmerge": "^4.2.2",
37
36
  "fill-range": "^7.0.1",
@@ -40,12 +39,11 @@
40
39
  "lodash": "^4.17.21",
41
40
  "mongodb": "^4.8.0",
42
41
  "object-hash": "^2.0.1",
43
- "picomatch": "^2.1.1",
44
- "uuid": "^3.3.3"
42
+ "picomatch": "^2.1.1"
45
43
  },
46
44
  "devDependencies": {
47
45
  "@coderich/ratchet": "^1.5.7",
48
- "@graphql-tools/schema": "^8.3.14",
46
+ "@graphql-tools/schema": "^9.0.1",
49
47
  "graphql": "^15.5.0",
50
48
  "mongodb-memory-server": "^8.7.2",
51
49
  "neo4j-driver": "^4.0.0",
@@ -54,6 +52,9 @@
54
52
  "redis-mock": "^0.47.0",
55
53
  "validator": "^13.7.0"
56
54
  },
55
+ "peerDependencies": {
56
+ "graphql": "*"
57
+ },
57
58
  "repository": {
58
59
  "type": "git",
59
60
  "url": "git@github.com:coderich/autograph.git"
@@ -1,7 +1,10 @@
1
1
  const Model = require('../data/Model');
2
2
  const DataLoader = require('../data/DataLoader');
3
3
  const DataTransaction = require('../data/DataTransaction');
4
+ const Query = require('../query/Query');
4
5
  const QueryBuilder = require('../query/QueryBuilder');
6
+ const { finalizeResults } = require('../data/DataService');
7
+ const { createSystemEvent } = require('../service/event.service');
5
8
 
6
9
  module.exports = class Resolver {
7
10
  constructor(schema, context = {}) {
@@ -58,7 +61,8 @@ module.exports = class Resolver {
58
61
  case 'create': case 'update': case 'delete': {
59
62
  return model.getDriver().resolve(query.toDriver()).then((data) => {
60
63
  this.clear(model);
61
- return model.shapeObject(model.getShape(), data, query);
64
+ const rs = model.shapeObject(model.getShape(), data, query);
65
+ return finalizeResults(rs, query);
62
66
  });
63
67
  }
64
68
  default: {
@@ -99,25 +103,13 @@ module.exports = class Resolver {
99
103
  return entity;
100
104
  }
101
105
 
102
- // toResultSet(model, data, method) {
103
- // const crud = ['get', 'find', 'count'].indexOf(method) > -1 ? 'read' : method;
104
- // const doc = model.shape(data);
105
- // const result = doc;
106
- // const merged = doc;
107
-
108
- // return createSystemEvent('Response', {
109
- // model,
110
- // crud,
111
- // method,
112
- // result,
113
- // doc,
114
- // merged,
115
- // resolver: this,
116
- // key: `${method}${model}`,
117
- // context: this.getContext(),
118
- // query: query.doc(result).merged(result),
119
- // }, () => result);
120
- // }
106
+ toResultSet(model, data, method) {
107
+ model = this.toModel(model);
108
+ const query = new Query({ model, resolver: this, context: this.context, method });
109
+ const result = model.deserialize(data, query);
110
+ const event = { result, query, ...query.doc(result).merged(result).toObject() };
111
+ return createSystemEvent('Response', event, () => result);
112
+ }
121
113
 
122
114
  // DataLoader Proxy Methods
123
115
  clear(model) {
@@ -1,12 +1,12 @@
1
1
  const Model = require('../data/Model');
2
2
  const Schema = require('../graphql/ast/Schema');
3
3
  const { identifyOnDeletes } = require('../service/schema.service');
4
- const { createSystemEvent } = require('../service/event.service');
4
+ const { eventEmitter } = require('../service/event.service');
5
5
 
6
6
  // Export class
7
7
  module.exports = class extends Schema {
8
- constructor(schema, stores, toExecutableSchema) {
9
- super(schema, toExecutableSchema);
8
+ constructor(schema, stores) {
9
+ super(schema);
10
10
 
11
11
  // Create drivers
12
12
  this.drivers = Object.entries(stores).reduce((prev, [key, value]) => {
@@ -23,7 +23,7 @@ module.exports = class extends Schema {
23
23
  }
24
24
 
25
25
  setup() {
26
- return createSystemEvent('Setup', this, () => {
26
+ return eventEmitter.emit('setup', this).then(() => {
27
27
  const entities = this.models.filter(m => m.isEntity());
28
28
 
29
29
  // Create model indexes
@@ -1,27 +1,50 @@
1
1
  const { get, remove } = require('lodash');
2
2
  const { map, isPlainObject, objectContaining, mergeDeep, ensureArray, keyPaths } = require('../service/app.service');
3
3
 
4
- exports.paginateResultSet = (rs, query) => {
4
+ exports.finalizeResults = (rs, query) => {
5
+ const { model, resolver } = query.toObject();
6
+
7
+ return map(exports.paginateResults(rs, query), (doc) => {
8
+ return Object.defineProperties(doc, {
9
+ $model: { value: model },
10
+ $save: { value: input => resolver.match(model).id(doc.id).save({ ...doc, ...input }) },
11
+ $remove: { value: (...args) => resolver.match(model).id(doc.id).remove(...args) },
12
+ $delete: { value: (...args) => resolver.match(model).id(doc.id).delete(...args) },
13
+ $lookup: { value: (fieldName, args) => model.getFieldByName(fieldName).resolve(resolver, doc, args) },
14
+ // $resolve: { value: (fieldName, args) => model.getFieldByName(fieldName).resolve(resolver, doc, args, true) },
15
+ });
16
+ });
17
+ };
18
+
19
+ /**
20
+ * This is cursor-style pagination only
21
+ * You add 2 extra records to the result in order to determine previous/next
22
+ */
23
+ exports.paginateResults = (rs, query) => {
5
24
  const { first, after, last, before, sort } = query.toObject();
6
- const sortPaths = keyPaths(sort);
7
- const limiter = first || last;
25
+ const isPaginating = Boolean(first || last || after || before);
26
+
27
+ // Return right away if not paginating
28
+ if (!isPaginating) return rs;
29
+
8
30
  let hasNextPage = false;
9
31
  let hasPreviousPage = false;
32
+ const limiter = first || last;
33
+ const sortPaths = keyPaths(sort);
10
34
 
11
- // Add $$cursor data
35
+ // Add $cursor data
12
36
  map(rs, (doc) => {
13
37
  const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(doc, path) }), {});
14
- const sortJSON = JSON.stringify(sortValues);
15
- doc.$$cursor = Buffer.from(sortJSON).toString('base64');
38
+ Object.defineProperty(doc, '$cursor', { get() { return Buffer.from(JSON.stringify(sortValues)).toString('base64'); } });
16
39
  });
17
40
 
18
41
  // First try to take off the "bookends" ($gte | $lte)
19
- if (rs.length && rs[0].$$cursor === after) {
42
+ if (rs.length && rs[0].$cursor === after) {
20
43
  rs.shift();
21
44
  hasPreviousPage = true;
22
45
  }
23
46
 
24
- if (rs.length && rs[rs.length - 1].$$cursor === before) {
47
+ if (rs.length && rs[rs.length - 1].$cursor === before) {
25
48
  rs.pop();
26
49
  hasNextPage = true;
27
50
  }
@@ -42,18 +65,17 @@ exports.paginateResultSet = (rs, query) => {
42
65
  }
43
66
  }
44
67
 
45
- // Add $$pageInfo data (hidden)
68
+ // Add $pageInfo
46
69
  return Object.defineProperties(rs, {
47
- $$pageInfo: {
70
+ $pageInfo: {
48
71
  get() {
49
72
  return {
50
- startCursor: get(rs, '0.$$cursor', ''),
51
- endCursor: get(rs, `${rs.length - 1}.$$cursor`, ''),
73
+ startCursor: get(rs, '0.$cursor', ''),
74
+ endCursor: get(rs, `${rs.length - 1}.$cursor`, ''),
52
75
  hasPreviousPage,
53
76
  hasNextPage,
54
77
  };
55
78
  },
56
- enumerable: false,
57
79
  },
58
80
  });
59
81
  };
package/src/data/Field.js CHANGED
@@ -1,9 +1,7 @@
1
1
  const { isEmpty } = require('lodash');
2
2
  const Type = require('./Type');
3
3
  const Field = require('../graphql/ast/Field');
4
- const Boom = require('../core/Boom');
5
4
  const Pipeline = require('./Pipeline');
6
- const { isPlainObject, ensureArray } = require('../service/app.service');
7
5
 
8
6
  module.exports = class extends Field {
9
7
  constructor(model, field) {
@@ -15,49 +13,34 @@ module.exports = class extends Field {
15
13
  getStructures() {
16
14
  // Grab structures from the underlying type
17
15
  const structures = this.type.getStructures();
18
- const { isRequired, isPersistable, isVirtual, isPrimaryKeyId, isIdField } = this.props;
16
+ const { type, isPrimaryKeyId, isIdField, isRequired, isPersistable, isVirtual, isEmbedded, modelRef } = this.props;
19
17
 
20
18
  // Structures defined on the field
21
19
  const $structures = Object.entries(this.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
22
20
  if (!Array.isArray(value)) value = [value];
23
- if (key === 'instruct') prev.instructs.unshift(...value.map(t => Pipeline[t]));
24
- if (key === 'restruct') prev.restructs.unshift(...value.map(t => Pipeline[t]));
25
- if (key === 'destruct') prev.destructs.unshift(...value.map(t => Pipeline[t]));
26
- if (key === 'construct') prev.constructs.unshift(...value.map(t => Pipeline[t]));
27
- if (key === 'serialize') prev.serializers.unshift(...value.map(t => Pipeline[t]));
28
- if (key === 'deserialize') prev.deserializers.unshift(...value.map(t => Pipeline[t]));
29
- if (key === 'transform') prev.transformers.unshift(...value.map(t => Pipeline[t]));
21
+ if (key === 'validate') prev.validators.push(...value.map(t => Pipeline[t]));
22
+ if (key === 'instruct') prev.instructs.push(...value.map(t => Pipeline[t]));
23
+ if (key === 'restruct') prev.restructs.push(...value.map(t => Pipeline[t]));
24
+ if (key === 'destruct') prev.destructs.push(...value.map(t => Pipeline[t]));
25
+ if (key === 'construct') prev.constructs.push(...value.map(t => Pipeline[t]));
26
+ if (key === 'transform') prev.transforms.push(...value.map(t => Pipeline[t]));
27
+ if (key === 'normalize') prev.normalizers.push(...value.map(t => Pipeline[t]));
28
+ if (key === 'serialize') prev.serializers.push(...value.map(t => Pipeline[t]));
29
+ if (key === 'deserialize') prev.deserializers.push(...value.map(t => Pipeline[t]));
30
30
  return prev;
31
31
  }, structures);
32
32
 
33
33
  // IDs (first - shift)
34
- if (isPrimaryKeyId) $structures.serializers.unshift(Pipeline.idKey);
34
+ if (isPrimaryKeyId && type === 'ID') $structures.serializers.unshift(Pipeline.idKey);
35
35
  if (isIdField) $structures.$serializers.unshift(Pipeline.idField);
36
36
 
37
37
  // Required (last - push)
38
- if (isRequired && isPersistable && !isVirtual) $structures.serializers.push(Pipeline.required);
38
+ if (isRequired && isPersistable && !isVirtual) $structures.validators.push(Pipeline.required);
39
+ if (modelRef && !isEmbedded) $structures.validators.push(Pipeline.ensureId);
39
40
 
40
41
  return $structures;
41
42
  }
42
43
 
43
- async validate(query, value) {
44
- if (value == null) return value;
45
- const { resolver } = query.toObject();
46
- const { type, modelRef, isEmbedded } = this.props;
47
-
48
- if (modelRef && !isEmbedded) {
49
- const ids = Array.from(new Set(ensureArray(value).map(v => `${v}`)));
50
- await resolver.match(type).where({ id: ids }).count().then((count) => {
51
- // if (type === 'Category') console.log(ids, count);
52
- if (count !== ids.length) throw Boom.notFound(`${type} Not Found`);
53
- });
54
- }
55
-
56
- if (modelRef && isPlainObject(ensureArray(value)[0])) return modelRef.validate(query, value); // Model delegation
57
-
58
- return value;
59
- }
60
-
61
44
  resolve(resolver, doc, args = {}) {
62
45
  const { name, isArray, isScalar, isVirtual, isRequired, isEmbedded, modelRef, virtualField } = this.props;
63
46
  const value = doc[name];
package/src/data/Model.js CHANGED
@@ -1,10 +1,9 @@
1
1
  const Stream = require('stream');
2
2
  const Field = require('./Field');
3
- const Pipeline = require('./Pipeline');
4
3
  const Model = require('../graphql/ast/Model');
5
- const { paginateResultSet } = require('./DataService');
6
4
  const { eventEmitter } = require('../service/event.service');
7
- const { map, seek, deseek, ensureArray } = require('../service/app.service');
5
+ const { finalizeResults } = require('./DataService');
6
+ const { map, mapPromise, seek, deseek } = require('../service/app.service');
8
7
 
9
8
  module.exports = class extends Model {
10
9
  constructor(schema, model, driver) {
@@ -48,45 +47,26 @@ module.exports = class extends Model {
48
47
  return this.referentials;
49
48
  }
50
49
 
51
- validate(query, data) {
52
- const { flags = {} } = query.toObject();
53
- const { validate = true } = flags;
54
-
55
- if (!validate) return Promise.resolve();
56
-
57
- return Promise.all(this.getFields().map((field) => {
58
- return Promise.all(ensureArray(map(data, (obj) => {
59
- if (obj == null) return Promise.resolve();
60
- return field.validate(query, obj[field.getKey()]);
61
- })));
62
- })).then(() => {
63
- return eventEmitter.emit('validate', query);
64
- });
65
- }
66
-
67
50
  /**
68
51
  * Convenience method to deserialize data from a data source (such as a database)
69
52
  */
70
53
  deserialize(mixed, query) {
71
- const { flags = {} } = query.toObject();
72
- const { pipeline = true } = flags;
73
54
  const shape = this.getShape();
74
55
 
75
56
  return new Promise((resolve, reject) => {
76
57
  if (!(mixed instanceof Stream)) {
77
- resolve(pipeline ? this.shapeObject(shape, mixed, query) : mixed);
58
+ resolve(this.shapeObject(shape, mixed, query));
78
59
  } else {
79
60
  const results = [];
80
- mixed.on('data', (data) => { results.push(pipeline ? this.shapeObject(shape, data, query) : data); });
61
+ mixed.on('data', (data) => { results.push(this.shapeObject(shape, data, query)); });
81
62
  mixed.on('end', () => { resolve(results); });
82
63
  mixed.on('error', reject);
83
64
  }
84
- }).then((results) => {
85
- return results.length && pipeline ? paginateResultSet(results, query) : results;
86
- });
65
+ }).then(rs => finalizeResults(rs, query));
87
66
  }
88
67
 
89
68
  getShape(crud = 'read', target = 'doc', paths = []) {
69
+ // Cache check
90
70
  const cacheKey = `${crud}:${target}`;
91
71
  if (this.shapesCache.has(cacheKey)) return this.shapesCache.get(cacheKey);
92
72
 
@@ -95,33 +75,39 @@ module.exports = class extends Model {
95
75
  const crudMap = { create: ['constructs'], update: ['restructs'], delete: ['destructs'], remove: ['destructs'] };
96
76
  const crudKeys = crudMap[crud] || [];
97
77
 
78
+ // Define target mapping
98
79
  const targetMap = {
99
- doc: ['defaultValue', 'ensureArrayValue', 'castValue', ...crudKeys, `$${serdes}rs`, 'instructs', 'transformers', `${serdes}rs`],
100
- where: ['castValue', `$${serdes}rs`, 'instructs'],
80
+ doc: ['defaultValue', 'castValue', 'ensureArrayValue', 'normalizers', 'instructs', ...crudKeys, `$${serdes}rs`, `${serdes}rs`, 'transforms'],
81
+ input: ['defaultValue', 'castValue', 'ensureArrayValue', 'normalizers', 'instructs', ...crudKeys, `$${serdes}rs`, `${serdes}rs`, 'transforms'],
82
+ // input: ['defaultValue', 'castValue', 'ensureArrayValue'],
83
+ where: ['castValue', 'instructs', `$${serdes}rs`],
101
84
  };
102
-
103
85
  const structureKeys = targetMap[target] || ['castValue'];
104
86
 
105
87
  // Create shape, recursive
106
88
  const shape = fields.map((field) => {
89
+ let instructed = false;
107
90
  const structures = field.getStructures();
108
- const [key, name, type, isArray] = [field.getKey(), field.getName(), field.getType(), field.isArray(), field.isIdField()];
91
+ const { key, name, type, isArray, isEmbedded, modelRef } = field.toObject();
109
92
  const [from, to] = serdes === 'serialize' ? [name, key] : [key, name];
110
- const path = paths.concat(to);
111
- const subShape = field.isEmbedded() ? field.getModelRef().getShape(crud, target, path) : null;
112
-
113
- structures.castValue = Pipeline.castValue;
114
- structures.defaultValue = Pipeline.defaultValue;
115
-
116
- structures.ensureArrayValue = ({ value }) => (value != null && isArray && !Array.isArray(value) ? [value] : value);
117
- const transformers = structureKeys.reduce((prev, struct) => prev.concat(structures[struct]), []).filter(Boolean);
118
- return { field, path, from, to, type, isArray, transformers, shape: subShape };
93
+ const actualTo = target === 'input' || target === 'splice' ? from : to;
94
+ const path = paths.concat(actualTo);
95
+ const subCrud = crud === 'update' && isArray ? 'create' : crud; // Due to limitation to update embedded array
96
+ const subShape = isEmbedded ? modelRef.getShape(subCrud, target, path) : null;
97
+ const transformers = structureKeys.reduce((prev, struct) => {
98
+ if (instructed) return prev;
99
+ const structs = structures[struct];
100
+ if (struct === 'instructs' && structs.length) instructed = true;
101
+ return prev.concat(structs);
102
+ }, []).filter(Boolean);
103
+ return { instructed, field, path, from, to: actualTo, type, isArray, transformers, validators: structures.validators, shape: subShape };
119
104
  });
120
105
 
121
106
  // Adding useful shape info
122
107
  shape.crud = crud;
123
108
  shape.model = this;
124
109
  shape.serdes = serdes;
110
+ shape.target = target;
125
111
 
126
112
  // Cache and return
127
113
  this.shapesCache.set(cacheKey, shape);
@@ -130,7 +116,11 @@ module.exports = class extends Model {
130
116
 
131
117
  shapeObject(shape, obj, query, root) {
132
118
  const { serdes, model } = shape;
133
- const { context, doc = {} } = query.toObject();
119
+ const { context, resolver, doc = {}, flags = {} } = query.toObject();
120
+ const { pipeline } = flags;
121
+
122
+ if (!pipeline) return obj;
123
+ // const filters = pipeline === true ? [] : Object.entries(pipeline).map(([k, v]) => (v === false ? k : null)).filter(Boolean);
134
124
 
135
125
  return map(obj, (parent) => {
136
126
  // "root" is the base of the object
@@ -141,19 +131,19 @@ module.exports = class extends Model {
141
131
  const rootPath = (p, hint) => (serdes === 'serialize' ? seek(root, p, hint) : deseek(shape, root, p, hint));
142
132
  const parentPath = (p, hint) => (serdes === 'serialize' ? seek(parent, p, hint) : deseek(shape, parent, p, hint));
143
133
 
144
- return shape.reduce((prev, { field, from, to, path, type, isArray, defaultValue, transformers = [], shape: subShape }) => {
134
+ return shape.reduce((prev, { instructed, field, from, to, path, type, isArray, defaultValue, transformers = [], shape: subShape }) => {
145
135
  const startValue = parent[from];
136
+ // transformers = filters.length ? transformers.filter() : transformers;
146
137
 
147
138
  // Transform value
148
139
  const transformedValue = transformers.reduce((value, t) => {
149
- const v = t({ model, field, path, docPath, rootPath, parentPath, startValue, value, context });
140
+ const v = t({ model, field, path, docPath, rootPath, parentPath, startValue, value, resolver, context });
150
141
  return v === undefined ? value : v;
151
142
  }, startValue);
152
143
 
153
- // if (`${field}` === 'searchability') console.log(startValue, transformedValue, transformers);
154
-
155
144
  // Determine if key should stay or be removed
156
- if (transformedValue === undefined && !Object.prototype.hasOwnProperty.call(parent, from)) return prev;
145
+ if (!instructed && transformedValue === undefined && !Object.prototype.hasOwnProperty.call(parent, from)) return prev;
146
+ if (!instructed && subShape && typeof transformedValue !== 'object') return prev;
157
147
 
158
148
  // Rename key & assign value
159
149
  prev[to] = (!subShape || transformedValue == null) ? transformedValue : this.shapeObject(subShape, transformedValue, query, root);
@@ -162,4 +152,32 @@ module.exports = class extends Model {
162
152
  }, {});
163
153
  });
164
154
  }
155
+
156
+ validateObject(shape, obj, query, root, silent = false) {
157
+ const { model } = shape;
158
+ const { context, resolver, doc = {}, flags = {} } = query.toObject();
159
+ const { validate = true } = flags;
160
+
161
+ if (!validate) return Promise.resolve();
162
+
163
+ return mapPromise(obj, (parent) => {
164
+ // "root" is the base of the object
165
+ root = root || parent;
166
+
167
+ // Lookup helper functions
168
+ const docPath = (p, hint) => seek(doc, p, hint);
169
+ const rootPath = (p, hint) => seek(root, p, hint);
170
+ const parentPath = (p, hint) => seek(parent, p, hint);
171
+
172
+ return Promise.all(shape.map(({ field, from, path, validators, shape: subShape }) => {
173
+ const value = parent[from]; // It hasn't been shaped yet
174
+
175
+ return Promise.all(validators.map(v => v({ model, field, path, docPath, rootPath, parentPath, startValue: value, value, resolver, context }))).then(() => {
176
+ return subShape ? this.validateObject(subShape, value, query, root, true) : Promise.resolve();
177
+ });
178
+ }));
179
+ }).then(() => {
180
+ return silent ? Promise.resolve() : eventEmitter.emit('validate', query.toObject());
181
+ });
182
+ }
165
183
  };
@@ -1,5 +1,5 @@
1
1
  const { uniqWith } = require('lodash');
2
- const { map, hashObject } = require('../service/app.service');
2
+ const { map, ensureArray, hashObject } = require('../service/app.service');
3
3
  const Boom = require('../core/Boom');
4
4
 
5
5
  module.exports = class Pipeline {
@@ -29,17 +29,17 @@ module.exports = class Pipeline {
29
29
  }, 'name', { value: name });
30
30
 
31
31
  // Attach enumerable method to the Pipeline
32
- Object.defineProperty(Pipeline, name, {
32
+ return Object.defineProperty(Pipeline, name, {
33
33
  value: wrapper,
34
34
  configurable,
35
35
  enumerable: true,
36
- });
36
+ })[name];
37
37
  }
38
38
 
39
39
  static factory(name, thunk, options = {}) {
40
40
  if (typeof thunk !== 'function') throw new Error(`Pipeline factory for "${name}" must be a thunk`);
41
41
  if (typeof thunk() !== 'function') throw new Error(`Factory thunk() for "${name}" must return a function`);
42
- Object.defineProperty(Pipeline, name, { value: Object.defineProperty(thunk, 'options', { value: options }) });
42
+ return Object.defineProperty(Pipeline, name, { value: (...args) => Object.defineProperty(thunk(...args), 'options', { value: options }) })[name];
43
43
  }
44
44
 
45
45
  // static wrapper(name, factory, { ignoreNull, itemize }) {
@@ -66,13 +66,24 @@ module.exports = class Pipeline {
66
66
  // Additional Transformers
67
67
  Pipeline.define('toTitleCase', ({ value }) => value.replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()));
68
68
  Pipeline.define('toSentenceCase', ({ value }) => value.charAt(0).toUpperCase() + value.slice(1));
69
+ Pipeline.define('toId', ({ model, value }) => model.idValue(value));
69
70
  Pipeline.define('toArray', ({ value }) => (Array.isArray(value) ? value : [value]), { itemize: false });
70
71
  Pipeline.define('toDate', ({ value }) => new Date(value), { configurable: true });
71
72
  Pipeline.define('timestamp', ({ value }) => Date.now(), { ignoreNull: false });
72
73
  Pipeline.define('createdAt', ({ value }) => value || Date.now(), { ignoreNull: false });
73
74
  Pipeline.define('dedupe', ({ value }) => uniqWith(value, (b, c) => hashObject(b) === hashObject(c)), { itemize: false });
74
75
  Pipeline.define('idKey', ({ model, value }) => (value == null ? model.idValue() : value), { ignoreNull: false });
75
- Pipeline.define('idField', ({ field, value }) => map(value, v => field.getIdModel().idValue(v.id || v)));
76
+ Pipeline.define('idField', ({ model, field, value }) => field.getIdModel().idValue(value.id || value));
77
+ Pipeline.define('ensureArrayValue', ({ field, value }) => (field.toObject().isArray && !Array.isArray(value) ? [value] : value), { itemize: false });
78
+
79
+ Pipeline.define('ensureId', ({ resolver, field, value }) => {
80
+ const { type } = field.toObject();
81
+ const ids = Array.from(new Set(ensureArray(value).map(v => `${v}`)));
82
+
83
+ return resolver.match(type).where({ id: ids }).count().then((count) => {
84
+ if (count !== ids.length) throw Boom.notFound(`${type} Not Found`);
85
+ });
86
+ }, { itemize: false });
76
87
 
77
88
  Pipeline.define('defaultValue', ({ field, value }) => {
78
89
  const { defaultValue } = field.toObject();
@@ -117,7 +128,7 @@ module.exports = class Pipeline {
117
128
  }, { ignoreNull: false });
118
129
 
119
130
  // A field cannot hold a reference to itself
120
- Pipeline.define('selfless', ({ model, field, parentPath, value }) => {
131
+ Pipeline.define('selfless', ({ model, field, parent, parentPath, value }) => {
121
132
  if (`${value}` === `${parentPath('id')}`) throw Boom.badRequest(`${model}.${field} cannot hold a reference to itself`);
122
133
  });
123
134
 
@@ -129,17 +140,17 @@ module.exports = class Pipeline {
129
140
  });
130
141
 
131
142
  // List of allowed values
132
- Pipeline.factory('allow', (...args) => function allow({ model, field, value }) {
143
+ Pipeline.factory('Allow', (...args) => function allow({ model, field, value }) {
133
144
  if (args.indexOf(value) === -1) throw Boom.badRequest(`${model}.${field} allows ${args}; found '${value}'`);
134
145
  });
135
146
 
136
147
  // List of disallowed values
137
- Pipeline.factory('deny', (...args) => function deny({ model, field, value }) {
148
+ Pipeline.factory('Deny', (...args) => function deny({ model, field, value }) {
138
149
  if (args.indexOf(value) > -1) throw Boom.badRequest(`${model}.${field} denys ${args}; found '${value}'`);
139
150
  });
140
151
 
141
152
  // Min/Max range
142
- Pipeline.factory('range', (min, max) => {
153
+ Pipeline.factory('Range', (min, max) => {
143
154
  if (min == null) min = undefined;
144
155
  if (max == null) max = undefined;
145
156
 
@@ -152,7 +163,6 @@ module.exports = class Pipeline {
152
163
  }
153
164
  };
154
165
 
155
-
156
166
  // const jsStringMethods = [
157
167
  // 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'indexOf', 'lastIndexOf', 'localeCompare',
158
168
  // 'normalize', 'padEnd', 'padStart', 'repeat', 'replace', 'search', 'slice', 'split', 'substr', 'substring',
package/src/data/Type.js CHANGED
@@ -11,20 +11,39 @@ module.exports = class extends Type {
11
11
  const type = this.field.getType();
12
12
  const enumType = this.field.getEnumRef();
13
13
  const scalarType = this.field.getScalarRef();
14
- const structures = { instructs: [], restructs: [], destructs: [], constructs: [], $serializers: [], serializers: [], $deserializers: [], deserializers: [], transformers: [] };
14
+ const structures = {
15
+ validators: [],
16
+ instructs: [],
17
+ restructs: [],
18
+ destructs: [],
19
+ constructs: [],
20
+ normalizers: [],
21
+ $serializers: [],
22
+ $deserializers: [],
23
+ serializers: [],
24
+ deserializers: [],
25
+ transforms: [],
26
+ };
15
27
 
16
- if (enumType) structures.serializers.push(Pipeline.define(`$allow:${type}`, Pipeline.allow(...enumType.getValue()), { configurable: true }));
28
+ // Built-in pipelines
29
+ structures.castValue = Pipeline.castValue;
30
+ structures.defaultValue = Pipeline.defaultValue;
31
+ structures.ensureArrayValue = Pipeline.ensureArrayValue;
32
+
33
+ if (enumType) structures.validators.push(Pipeline.define(`allow:${type}`, Pipeline.Allow(...enumType.getValue()), { configurable: true }));
17
34
  if (!scalarType) return structures;
18
35
 
19
36
  return Object.entries(scalarType.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
20
37
  if (!Array.isArray(value)) value = [value];
38
+ if (key === 'validate') prev.validators.push(...value.map(t => Pipeline[t]));
21
39
  if (key === 'instruct') prev.instructs.push(...value.map(t => Pipeline[t]));
22
40
  if (key === 'restruct') prev.restructs.push(...value.map(t => Pipeline[t]));
23
41
  if (key === 'destruct') prev.destructs.push(...value.map(t => Pipeline[t]));
24
42
  if (key === 'construct') prev.constructs.push(...value.map(t => Pipeline[t]));
43
+ if (key === 'transform') prev.transforms.push(...value.map(t => Pipeline[t]));
44
+ if (key === 'normalize') prev.normalizers.push(...value.map(t => Pipeline[t]));
25
45
  if (key === 'serialize') prev.serializers.push(...value.map(t => Pipeline[t]));
26
46
  if (key === 'deserialize') prev.deserializers.push(...value.map(t => Pipeline[t]));
27
- if (key === 'transform') prev.transformers.push(...value.map(t => Pipeline[t]));
28
47
  return prev;
29
48
  }, structures);
30
49
  }
@@ -1,6 +1,6 @@
1
1
  const Util = require('util');
2
2
  const { get } = require('lodash');
3
- const { MongoClient, ObjectID } = require('mongodb');
3
+ const { MongoClient, ObjectId } = require('mongodb');
4
4
  const { map, ensureArray, proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
5
5
 
6
6
  module.exports = class MongoDriver {
@@ -126,10 +126,10 @@ module.exports = class MongoDriver {
126
126
  }
127
127
 
128
128
  static idValue(value) {
129
- if (value instanceof ObjectID) return value;
129
+ if (value instanceof ObjectId) return value;
130
130
 
131
131
  try {
132
- const id = ObjectID(value);
132
+ const id = ObjectId(value);
133
133
  return id;
134
134
  } catch (e) {
135
135
  return value;
@@ -142,7 +142,10 @@ module.exports = class MongoDriver {
142
142
  const value = Reflect.get(target, prop, rec);
143
143
  if (typeof value === 'function') return value.bind(target);
144
144
  const $value = map(value, v => (typeof v === 'string' ? globToRegex(v, { nocase: true, regex: true }) : v));
145
- if (Array.isArray($value)) return { $in: $value };
145
+ if (Array.isArray($value)) {
146
+ // console.log(Util.inspect({ value, $value }, { depth: null, showHidden: false, colors: true }));
147
+ return { $in: $value };
148
+ }
146
149
  return $value;
147
150
  },
148
151
  }).toObject();
@@ -175,8 +175,10 @@ module.exports = class Field extends Node {
175
175
 
176
176
  initialize() {
177
177
  this.props = {
178
+ key: this.getKey(),
178
179
  name: this.getName(),
179
180
  type: this.getType(),
181
+ model: this.model,
180
182
  datatype: this.getDataType(),
181
183
  defaultValue: this.getDefaultValue(),
182
184
  isArray: this.isArray(),
@@ -187,6 +189,7 @@ module.exports = class Field extends Node {
187
189
  isIdField: this.isIdField(),
188
190
  isPrimaryKeyId: this.isPrimaryKeyId(),
189
191
  isPersistable: this.isPersistable(),
192
+ idModel: this.getIdModel(),
190
193
  modelRef: this.getModelRef(),
191
194
  virtualRef: this.getVirtualRef(),
192
195
  virtualField: this.getVirtualField(),
@@ -24,9 +24,8 @@ const Node = require('./Node');
24
24
  *
25
25
  */
26
26
  module.exports = class Schema extends TypeDefApi {
27
- constructor(schema, toExecutableSchema) {
27
+ constructor(schema) {
28
28
  super();
29
- this.toExecutableSchema = toExecutableSchema;
30
29
  this.schema = { typeDefs: [], resolvers: {}, schemaDirectives: {} };
31
30
  if (schema) this.mergeSchema(schema);
32
31
  }
@@ -121,13 +120,10 @@ module.exports = class Schema extends TypeDefApi {
121
120
  });
122
121
 
123
122
  this.schema.typeDefs = { kind: Kind.DOCUMENT, definitions };
123
+ // validateSchema(this.schema);
124
124
  return this;
125
125
  }
126
126
 
127
- makeExecutableSchema() {
128
- return this.toExecutableSchema(this.schema);
129
- }
130
-
131
127
  toObject() {
132
128
  return this.schema;
133
129
  }
@@ -163,8 +163,8 @@ module.exports = (schema) => {
163
163
  [modelName]: fieldResolvers,
164
164
  [`${modelName}Connection`]: {
165
165
  count: ({ count }) => count(),
166
- edges: ({ edges }) => edges().then(rs => rs.map(node => ({ cursor: get(node, '$$cursor'), node }))),
167
- pageInfo: ({ pageInfo }) => pageInfo().then(rs => get(rs, '$$pageInfo')),
166
+ edges: ({ edges }) => edges().then(rs => rs.map(node => ({ cursor: get(node, '$cursor'), node }))),
167
+ pageInfo: ({ pageInfo }) => pageInfo().then(rs => get(rs, '$pageInfo')),
168
168
  },
169
169
  });
170
170
  }, {
@@ -31,11 +31,11 @@ module.exports = (schema) => {
31
31
 
32
32
  directive @field(
33
33
  id: String # Specify the ModelRef this field FK References
34
+ ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)
34
35
  key: String # Specify db key
35
36
  persist: Boolean # Persist this field (default true)
36
37
  connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
37
38
  default: AutoGraphMixed # Define a default value
38
- ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)
39
39
  gqlScope: AutoGraphMixed # Dictate how GraphQL API behaves
40
40
  dalScope: AutoGraphMixed # Dictate how the DAL behaves
41
41
  fieldScope: AutoGraphMixed # Dictate how a FIELD may use me
@@ -44,13 +44,15 @@ module.exports = (schema) => {
44
44
  authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
45
45
 
46
46
  # Pipeline Structure
47
+ validate: [AutoGraphPipelineEnum!]
47
48
  instruct: [AutoGraphPipelineEnum!]
48
- destruct: [AutoGraphPipelineEnum!]
49
49
  restruct: [AutoGraphPipelineEnum!]
50
+ destruct: [AutoGraphPipelineEnum!]
50
51
  construct: [AutoGraphPipelineEnum!]
52
+ transform: [AutoGraphPipelineEnum!]
53
+ normalize: [AutoGraphPipelineEnum!]
51
54
  serialize: [AutoGraphPipelineEnum!]
52
55
  deserialize: [AutoGraphPipelineEnum!]
53
- transform: [AutoGraphPipelineEnum!]
54
56
  ) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
55
57
 
56
58
  directive @link(
@@ -17,7 +17,7 @@ module.exports = (schema) => {
17
17
  return `
18
18
  extend type ${modelName}${interfacesGQL} {
19
19
  ${id ? `id: ID! @field(key: "${id}", gqlScope: r)` : ''}
20
- ${createdAt ? `createdAt: AutoGraphDateTime @field(key: "${createdAt}", serialize: createdAt, gqlScope: r)` : ''}
20
+ ${createdAt ? `createdAt: AutoGraphDateTime @field(key: "${createdAt}", construct: createdAt, gqlScope: r)` : ''}
21
21
  ${updatedAt ? `updatedAt: AutoGraphDateTime @field(key: "${updatedAt}", serialize: timestamp, gqlScope: r)` : ''}
22
22
  }
23
23
  `;
@@ -272,6 +272,16 @@ module.exports = class Query {
272
272
  return this;
273
273
  }
274
274
 
275
+ payload(payload) {
276
+ this.props.payload = payload;
277
+ return this;
278
+ }
279
+
280
+ result(result) {
281
+ this.props.result = result;
282
+ return this;
283
+ }
284
+
275
285
  $doc($doc) {
276
286
  this.props.$doc = $doc;
277
287
  return this;
@@ -16,7 +16,7 @@ module.exports = class QueryResolver {
16
16
  const { args } = query.toObject();
17
17
  const [,,, info] = args;
18
18
 
19
- switch (getGQLReturnType(info.returnType)) {
19
+ switch (getGQLReturnType(`${info.returnType}`)) {
20
20
  case 'array': {
21
21
  return new QueryResolver(this.query.clone().method('findMany')).resolve();
22
22
  }
@@ -36,42 +36,35 @@ module.exports = class QueryResolver {
36
36
  }
37
37
  }
38
38
 
39
- async findOne(query) {
40
- await QueryService.resolveQuery(query);
41
-
42
- return createSystemEvent('Query', { query }, () => {
39
+ findOne(query) {
40
+ return createSystemEvent('Query', { query }, async () => {
43
41
  return this.resolver.resolve(query);
44
42
  });
45
43
  }
46
44
 
47
- async findMany(query) {
48
- await QueryService.resolveQuery(query);
49
-
50
- return createSystemEvent('Query', { query }, () => {
45
+ findMany(query) {
46
+ return createSystemEvent('Query', { query }, async () => {
51
47
  return this.resolver.resolve(query);
52
48
  });
53
49
  }
54
50
 
55
- async count(query) {
56
- await QueryService.resolveQuery(query);
57
-
58
- return createSystemEvent('Query', { query }, () => {
51
+ count(query) {
52
+ return createSystemEvent('Query', { query }, async () => {
59
53
  return this.resolver.resolve(query);
60
54
  });
61
55
  }
62
56
 
63
- async createOne(query) {
57
+ createOne(query) {
64
58
  const { model, input } = query.toObject();
65
- const shape = model.getShape('create');
66
-
67
- await QueryService.resolveQuery(query);
68
-
69
- return createSystemEvent('Mutation', { query }, async () => {
70
- const $input = model.shapeObject(shape, input, query);
71
- await model.validate(query, $input);
72
- const doc = await this.resolver.resolve(query.$input($input));
73
- query.doc(doc);
74
- return doc;
59
+ const inputShape = model.getShape('create', 'input');
60
+ const docShape = model.getShape('create', 'doc');
61
+ const doc = model.shapeObject(inputShape, {}, query); // We use input shape here
62
+ const merged = mergeDeep(doc, input);
63
+
64
+ return createSystemEvent('Mutation', { query: query.doc(doc).merged(merged) }, async () => {
65
+ const payload = model.shapeObject(inputShape, merged, query);
66
+ await model.validateObject(inputShape, payload, query.payload(payload));
67
+ return this.resolver.resolve(query.$input(model.shapeObject(docShape, payload, query)));
75
68
  });
76
69
  }
77
70
 
@@ -84,17 +77,16 @@ module.exports = class QueryResolver {
84
77
 
85
78
  async updateOne(query) {
86
79
  const { model, match, input } = query.toObject();
80
+ const inputShape = model.getShape('update', 'input');
81
+ const docShape = model.getShape('update', 'doc');
87
82
 
88
- return this.resolver.match(model).match(match).one({ required: true }).then(async (doc) => {
89
- const shape = model.getShape('update');
90
- const merged = model.shapeObject(shape, mergeDeep(doc, input), query);
91
-
92
- await QueryService.resolveQuery(query);
83
+ return this.resolver.match(model).match(match).one({ required: true }).then((doc) => {
84
+ const merged = mergeDeep(doc, input);
93
85
 
94
86
  return createSystemEvent('Mutation', { query: query.doc(doc).merged(merged) }, async () => {
95
- const $doc = model.shapeObject(shape, mergeDeep(doc, input), query);
96
- await model.validate(query, $doc);
97
- return this.resolver.resolve(query.$doc($doc));
87
+ const payload = model.shapeObject(inputShape, merged, query);
88
+ await model.validateObject(inputShape, payload, query.payload(payload));
89
+ return this.resolver.resolve(query.$doc(model.shapeObject(docShape, payload, query)));
98
90
  });
99
91
  });
100
92
  }
@@ -110,11 +102,9 @@ module.exports = class QueryResolver {
110
102
  }
111
103
 
112
104
  deleteOne(query) {
113
- const { model, id, flags } = query.toObject();
114
-
115
- return this.resolver.match(model).id(id).flags(flags).one({ required: true }).then(async (doc) => {
116
- await QueryService.resolveQuery(query);
105
+ const { model, id } = query.toObject();
117
106
 
107
+ return this.resolver.match(model).id(id).one({ required: true }).then(async (doc) => {
118
108
  return createSystemEvent('Mutation', { query: query.doc(doc) }, () => {
119
109
  return QueryService.resolveReferentialIntegrity(query).then(() => {
120
110
  return this.resolver.resolve(query).then(() => doc);
@@ -186,7 +176,10 @@ module.exports = class QueryResolver {
186
176
  }
187
177
 
188
178
  splice(query) {
189
- const { model, match, args, flags = {} } = query.toObject();
179
+ const { model, match, args } = query.toObject();
180
+ const docShape = model.getShape('update', 'doc');
181
+ const inputShape = model.getShape('update', 'input');
182
+ const spliceShape = model.getShape('update', 'splice');
190
183
  const [key, from, to] = args;
191
184
 
192
185
  // Can only splice arrays
@@ -194,20 +187,16 @@ module.exports = class QueryResolver {
194
187
  const isArray = field.isArray();
195
188
  if (!isArray) throw Boom.badRequest(`Cannot splice field '${model}.${field}'`);
196
189
 
197
- return this.resolver.match(model).match(match).flags(flags).one({ required: true }).then(async (doc) => {
190
+ return this.resolver.match(model).match(match).one({ required: true }).then(async (doc) => {
198
191
  const array = get(doc, key) || [];
199
- const paramShape = model.getShape('create', 'spliceTo');
200
- const $to = model.shapeObject(paramShape, { [key]: to }, query)[key] || to;
201
- const $from = model.shapeObject(paramShape, { [key]: from }, query)[key] || from;
192
+ const $to = model.shapeObject(spliceShape, { [key]: to }, query)[key] || to;
193
+ const $from = model.shapeObject(spliceShape, { [key]: from }, query)[key] || from;
202
194
  set(doc, key, DataService.spliceEmbeddedArray(array, $from, $to));
203
195
 
204
- await QueryService.resolveQuery(query);
205
-
206
196
  return createSystemEvent('Mutation', { query: query.method('updateOne').doc(doc).merged(doc) }, async () => {
207
- const shape = model.getShape('update');
208
- const $doc = model.shapeObject(shape, doc, query);
209
- await model.validate(query, $doc);
210
- return this.resolver.resolve(query.$doc($doc));
197
+ const payload = model.shapeObject(inputShape, doc, query);
198
+ await model.validateObject(inputShape, payload, query.payload(payload));
199
+ return this.resolver.resolve(query.$doc(model.shapeObject(docShape, payload, query)));
211
200
  });
212
201
  });
213
202
  }
@@ -223,9 +212,6 @@ module.exports = class QueryResolver {
223
212
  async resolve() {
224
213
  const { model, method, flags } = this.query.toObject();
225
214
 
226
- // const resolveQueryMethods = ['findOne', 'findMany', 'count', 'createOne', 'updateOne', 'deleteOne', 'splice'];
227
- // if (resolveQueryMethods.indexOf(method) > -1) await QueryService.resolveQuery(this.query);
228
-
229
215
  return this[method](this.query).then((data) => {
230
216
  if (flags.required && isEmpty(data)) throw Boom.notFound(`${model} Not Found`);
231
217
  if (data == null) return null; // Explicitly return null here
@@ -6,7 +6,7 @@ const { keyPaths, ensureArray, isPlainObject } = require('../service/app.service
6
6
  * This can happen because the where clause reaches into the schema via refs/virtual refs
7
7
  */
8
8
  exports.resolveWhereClause = (query) => {
9
- const { resolver, model, match: where = {}, flags = {} } = query.toObject();
9
+ const { resolver, model, match: where = {} } = query.toObject();
10
10
  const shape = model.getShape('create', 'where');
11
11
 
12
12
  const $where = Object.entries(where).reduce((prev, [from, value]) => {
@@ -16,12 +16,12 @@ exports.resolveWhereClause = (query) => {
16
16
  const { isVirtual, isEmbedded, modelRef, virtualRef } = el.field.toObject();
17
17
 
18
18
  if (isVirtual) {
19
- const ids = Promise.all(ensureArray(value).map(v => resolver.match(modelRef).where(isPlainObject(v) ? v : { id: v }).many(flags).then(docs => docs.map(doc => doc[virtualRef])))).then(results => uniq(flattenDeep(results)));
19
+ const ids = Promise.all(ensureArray(value).map(v => resolver.match(modelRef).where(isPlainObject(v) ? v : { id: v }).many().then(docs => docs.map(doc => doc[virtualRef])))).then(results => uniq(flattenDeep(results)));
20
20
  return Object.assign(prev, { id: ids });
21
21
  }
22
22
 
23
23
  if (modelRef && !isEmbedded) {
24
- const ids = Promise.all(ensureArray(value).map(v => (isPlainObject(v) ? resolver.match(modelRef).where(v).many(flags).then(docs => docs.map(doc => doc.id)) : Promise.resolve(v)))).then(results => uniq(flattenDeep(results)));
24
+ const ids = Promise.all(ensureArray(value).map(v => (isPlainObject(v) ? resolver.match(modelRef).where(v).many().then(docs => docs.map(doc => doc.id)) : Promise.resolve(v)))).then(results => uniq(flattenDeep(results)));
25
25
  return Object.assign(prev, { [from]: ids });
26
26
  }
27
27
 
@@ -67,7 +67,7 @@ exports.resolveSortBy = (query) => {
67
67
  };
68
68
 
69
69
  exports.resolveReferentialIntegrity = (query) => {
70
- const { id, model, resolver, transaction, flags } = query.toObject();
70
+ const { id, model, resolver, transaction } = query.toObject();
71
71
  const txn = resolver.transaction(transaction);
72
72
 
73
73
  return new Promise((resolve, reject) => {
@@ -79,18 +79,18 @@ exports.resolveReferentialIntegrity = (query) => {
79
79
  switch (op) {
80
80
  case 'cascade': {
81
81
  if (isArray) {
82
- txn.match(ref).where($where).flags(flags).pull(fieldStr, id);
82
+ txn.match(ref).where($where).pull(fieldStr, id);
83
83
  } else {
84
- txn.match(ref).where($where).flags(flags).remove();
84
+ txn.match(ref).where($where).remove();
85
85
  }
86
86
  break;
87
87
  }
88
88
  case 'nullify': {
89
- txn.match(ref).where($where).flags(flags).save({ [fieldStr]: null });
89
+ txn.match(ref).where($where).save({ [fieldStr]: null });
90
90
  break;
91
91
  }
92
92
  case 'restrict': {
93
- txn.match(ref).where($where).flags(flags).count().then(count => (count ? reject(new Error('Restricted')) : count));
93
+ txn.match(ref).where($where).count().then(count => (count ? reject(new Error('Restricted')) : count));
94
94
  break;
95
95
  }
96
96
  case 'defer': {
@@ -109,18 +109,3 @@ exports.resolveReferentialIntegrity = (query) => {
109
109
  }
110
110
  });
111
111
  };
112
-
113
- exports.resolveQuery = async (query) => {
114
- const { model, sort, native, batch, match } = query.toObject();
115
-
116
- if (!native) {
117
- const shape = model.getShape('create', 'where');
118
- const $where = batch ? match : await exports.resolveWhereClause(query);
119
- const $$where = model.shapeObject(shape, $where, query);
120
- query.match($$where);
121
- }
122
-
123
- if (sort) {
124
- query.$sort(exports.resolveSortBy(query));
125
- }
126
- };
@@ -2,7 +2,7 @@ const _ = require('lodash');
2
2
  const PicoMatch = require('picomatch');
3
3
  const FillRange = require('fill-range');
4
4
  const DeepMerge = require('deepmerge');
5
- const { ObjectID } = require('mongodb');
5
+ const { ObjectId } = require('mongodb');
6
6
  const ObjectHash = require('object-hash');
7
7
 
8
8
  // const combineMerge = (target, source, options) => {
@@ -32,15 +32,15 @@ exports.id = '3d896496-02a3-4ee5-8e42-2115eb215f7e';
32
32
  exports.ucFirst = string => string.charAt(0).toUpperCase() + string.slice(1);
33
33
  exports.lcFirst = string => string.charAt(0).toLowerCase() + string.slice(1);
34
34
  exports.isNumber = value => typeof value === 'number' && Number.isFinite(value);
35
- exports.isBasicObject = obj => obj != null && typeof obj === 'object' && !(ObjectID.isValid(obj)) && !(obj instanceof Date) && typeof (obj.then) !== 'function';
35
+ exports.isBasicObject = obj => obj != null && typeof obj === 'object' && !(ObjectId.isValid(obj)) && !(obj instanceof Date) && typeof (obj.then) !== 'function';
36
36
  exports.isPlainObject = obj => exports.isBasicObject(obj) && !Array.isArray(obj);
37
37
  exports.isScalarValue = value => typeof value !== 'object' && typeof value !== 'function';
38
38
  exports.isScalarDataType = value => ['String', 'Float', 'Int', 'Boolean', 'DateTime'].indexOf(value) > -1;
39
- exports.isIdValue = value => exports.isScalarValue(value) || value instanceof ObjectID;
39
+ exports.isIdValue = value => exports.isScalarValue(value) || value instanceof ObjectId;
40
40
  exports.mergeDeep = (...args) => DeepMerge.all(args, { isMergeableObject: obj => (exports.isPlainObject(obj) || Array.isArray(obj)), arrayMerge: smartMerge });
41
41
  exports.uniq = arr => [...new Set(arr.map(a => `${a}`))];
42
42
  exports.timeout = ms => new Promise(res => setTimeout(res, ms));
43
- exports.hashObject = obj => ObjectHash(obj, { respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedArrays: true, ignoreUnknown: true, replacer: r => (r instanceof ObjectID ? `${r}` : r) });
43
+ exports.hashObject = obj => ObjectHash(obj, { respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedArrays: true, ignoreUnknown: true, replacer: r => (r instanceof ObjectId ? `${r}` : r) });
44
44
  exports.globToRegex = (glob, options = {}) => PicoMatch.makeRe(glob, { ...options, expandRange: (a, b) => `(${FillRange(a, b, { toRegex: true })})` });
45
45
  exports.globToRegexp = (glob, options = {}) => PicoMatch.toRegex(exports.globToRegex(glob, options));
46
46
  exports.toGUID = (model, id) => Buffer.from(`${model},${`${id}`}`).toString('base64');
@@ -95,8 +95,7 @@ exports.map = (mixed, fn) => {
95
95
  if (mixed == null) return mixed;
96
96
  const isArray = Array.isArray(mixed);
97
97
  const arr = isArray ? mixed : [mixed];
98
- // const results = arr.map((...args) => fn(...args));
99
- const results = arr.map((el, i, a) => fn(el, isArray ? i : undefined, isArray ? a : undefined));
98
+ const results = isArray ? arr.map((...args) => fn(...args)) : arr.map(el => fn(el));
100
99
  return isArray ? results : results[0];
101
100
  };
102
101
 
@@ -105,6 +104,32 @@ exports.mapPromise = (mixed, fn) => {
105
104
  return Array.isArray(map) ? Promise.all(map) : Promise.resolve(map);
106
105
  };
107
106
 
107
+ exports.castCmp = (type, value) => {
108
+ switch (type) {
109
+ case 'String': {
110
+ return `${value}`;
111
+ }
112
+ case 'Float': case 'Number': {
113
+ const num = Number(value);
114
+ if (!Number.isNaN(num)) return num;
115
+ return value;
116
+ }
117
+ case 'Int': {
118
+ const num = Number(value);
119
+ if (!Number.isNaN(num)) return parseInt(value, 10);
120
+ return value;
121
+ }
122
+ case 'Boolean': {
123
+ if (value === 'true') return true;
124
+ if (value === 'false') return false;
125
+ return value;
126
+ }
127
+ default: {
128
+ return value;
129
+ }
130
+ }
131
+ };
132
+
108
133
  exports.objectContaining = (a, b) => {
109
134
  if (a === b) return true;
110
135
 
@@ -1,3 +1,4 @@
1
+ const QueryService = require('../query/QueryService');
1
2
  const EventEmitter = require('../core/EventEmitter');
2
3
  const { ucFirst } = require('./app.service');
3
4
 
@@ -8,19 +9,49 @@ const systemEvent = new EventEmitter().setMaxListeners(100).on('system', async (
8
9
  next(await eventEmitter.emit(type, data)); // Return result from user-defined middleware
9
10
  });
10
11
 
12
+ const makeEvent = (mixed) => {
13
+ const { query } = mixed;
14
+ const event = query.toObject();
15
+ event.query = query;
16
+ return event;
17
+ };
18
+
19
+ const makeMiddleware = () => {
20
+ return (mixed) => {
21
+ const { query } = mixed;
22
+ const { model, native, sort, match, batch } = query.toObject();
23
+
24
+ return new Promise(async (resolve) => {
25
+ if (!native) {
26
+ const whereShape = model.getShape('create', 'where');
27
+ const $where = batch ? match : await QueryService.resolveWhereClause(query);
28
+ const $$where = model.shapeObject(whereShape, $where, query);
29
+ query.match($$where);
30
+ }
31
+
32
+ if (sort) {
33
+ query.$sort(QueryService.resolveSortBy(query));
34
+ }
35
+
36
+ resolve();
37
+ });
38
+ };
39
+ };
40
+
11
41
  //
12
42
  exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
13
43
  let event = mixed;
44
+ let middleware = () => Promise.resolve();
14
45
  const type = ucFirst(name);
15
46
 
16
- if (name !== 'Setup' && name !== 'Response') {
17
- const { query } = mixed;
18
- event = query.toObject();
19
- event.query = query;
47
+ if (name !== 'Response') {
48
+ event = makeEvent(mixed);
49
+ middleware = makeMiddleware();
20
50
  }
21
51
 
22
- return systemEvent.emit('system', { type: `pre${type}`, data: event }).then((result) => {
23
- return (result !== undefined) ? result : thunk(); // Allowing middleware to dictate result
52
+ return systemEvent.emit('system', { type: `pre${type}`, data: event }).then(async (result) => {
53
+ if (result !== undefined) return result; // Allowing middleware to dictate result
54
+ return middleware(mixed).then(thunk);
24
55
  }).then((result) => {
25
56
  event.result = result;
26
57
  if (event.crud === 'create') event.doc = event.query.toObject().doc;
@@ -1,21 +0,0 @@
1
- const { graphql } = require('graphql');
2
-
3
- /**
4
- * GraphQL.
5
- *
6
- * This is a wrapper class to the underlying GraphQL Executable Schema.
7
- * It can be useful for testing and/or exercising the API as an outside caller would.
8
- *
9
- * Reference: https://github.com/graphql/graphql-js/blob/master/src/graphql.js#L32-L33
10
- */
11
- module.exports = class GraphQL {
12
- constructor(schema, resolver) {
13
- this.schema = schema.makeExecutableSchema();
14
- this.contextValue = resolver.getContext();
15
- }
16
-
17
- exec(source, variableValues) {
18
- const { schema, contextValue = {} } = this;
19
- return graphql({ schema, source, variableValues, contextValue });
20
- }
21
- };