@coderich/autograph 0.9.15 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/index.js +2 -6
  3. package/package.json +13 -10
  4. package/src/.DS_Store +0 -0
  5. package/src/core/EventEmitter.js +2 -4
  6. package/src/core/Resolver.js +43 -59
  7. package/src/core/Schema.js +3 -36
  8. package/src/core/ServerResolver.js +7 -93
  9. package/src/data/DataLoader.js +68 -27
  10. package/src/data/DataService.js +59 -58
  11. package/src/data/Field.js +71 -96
  12. package/src/data/Model.js +95 -113
  13. package/src/data/Pipeline.js +174 -0
  14. package/src/data/Type.js +19 -60
  15. package/src/driver/MongoDriver.js +52 -27
  16. package/src/graphql/ast/Field.js +44 -26
  17. package/src/graphql/ast/Model.js +5 -16
  18. package/src/graphql/ast/Node.js +0 -32
  19. package/src/graphql/ast/Schema.js +107 -111
  20. package/src/graphql/extension/api.js +22 -35
  21. package/src/graphql/extension/framework.js +25 -33
  22. package/src/graphql/extension/type.js +2 -2
  23. package/src/query/Query.js +73 -15
  24. package/src/query/QueryBuilder.js +37 -28
  25. package/src/query/QueryBuilderTransaction.js +3 -3
  26. package/src/query/QueryResolver.js +93 -44
  27. package/src/query/QueryService.js +31 -34
  28. package/src/service/app.service.js +56 -9
  29. package/src/service/decorator.service.js +21 -288
  30. package/src/service/event.service.js +5 -79
  31. package/src/service/graphql.service.js +1 -1
  32. package/src/service/schema.service.js +5 -3
  33. package/src/core/Rule.js +0 -107
  34. package/src/core/SchemaDecorator.js +0 -46
  35. package/src/core/Transformer.js +0 -68
  36. package/src/data/Memoizer.js +0 -39
  37. package/src/data/ResultSet.js +0 -246
  38. package/src/graphql/ast/SchemaDecorator.js +0 -138
  39. package/src/graphql/directive/authz.directive.js +0 -84
@@ -0,0 +1,174 @@
1
+ const { uniqWith } = require('lodash');
2
+ const { map, hashObject } = require('../service/app.service');
3
+ const Boom = require('../core/Boom');
4
+
5
+ module.exports = class Pipeline {
6
+ constructor() {
7
+ throw new Error('Pipeline is a singleton; use the static {define|factory} methods');
8
+ }
9
+
10
+ static define(name, factory, options = {}) {
11
+ // A factory must be a function
12
+ if (typeof factory !== 'function') throw new Error(`Pipeline definition for "${name}" must be a function`);
13
+
14
+ // Determine options; which may come from the factory function
15
+ const { ignoreNull = true, itemize = true, configurable = false } = Object.assign({}, factory.options, options);
16
+
17
+ const wrapper = Object.defineProperty((args) => {
18
+ if (ignoreNull && args.value == null) return args.value;
19
+
20
+ if (ignoreNull && itemize) {
21
+ return map(args.value, (val, index) => {
22
+ const v = factory({ ...args, value: val, index });
23
+ return v === undefined ? val : v;
24
+ });
25
+ }
26
+
27
+ const val = factory(args);
28
+ return val === undefined ? args.value : val;
29
+ }, 'name', { value: name });
30
+
31
+ // Attach enumerable method to the Pipeline
32
+ Object.defineProperty(Pipeline, name, {
33
+ value: wrapper,
34
+ configurable,
35
+ enumerable: true,
36
+ });
37
+ }
38
+
39
+ static factory(name, thunk, options = {}) {
40
+ if (typeof thunk !== 'function') throw new Error(`Pipeline factory for "${name}" must be a thunk`);
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 }) });
43
+ }
44
+
45
+ // static wrapper(name, factory, { ignoreNull, itemize }) {
46
+ // return Object.defineProperty((args) => {
47
+ // if (ignoreNull && args.value == null) return args.value;
48
+
49
+ // if (ignoreNull && itemize) {
50
+ // return map(args.value, (val, index) => {
51
+ // const v = factory({ ...args, value: val, index });
52
+ // return v === undefined ? val : v;
53
+ // });
54
+ // }
55
+
56
+ // const val = factory(args);
57
+ // return val === undefined ? args.value : val;
58
+ // }, 'name', { value: name });
59
+ // }
60
+
61
+ static createPresets() {
62
+ // Built-In Javascript String Transformers
63
+ const jsStringTransformers = ['toLowerCase', 'toUpperCase', 'toString', 'trim', 'trimEnd', 'trimStart'];
64
+ jsStringTransformers.forEach(name => Pipeline.define(`${name}`, ({ value }) => String(value)[name]()));
65
+
66
+ // Additional Transformers
67
+ Pipeline.define('toTitleCase', ({ value }) => value.replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()));
68
+ Pipeline.define('toSentenceCase', ({ value }) => value.charAt(0).toUpperCase() + value.slice(1));
69
+ Pipeline.define('toArray', ({ value }) => (Array.isArray(value) ? value : [value]), { itemize: false });
70
+ Pipeline.define('toDate', ({ value }) => new Date(value), { configurable: true });
71
+ Pipeline.define('timestamp', ({ value }) => Date.now(), { ignoreNull: false });
72
+ Pipeline.define('createdAt', ({ value }) => value || Date.now(), { ignoreNull: false });
73
+ Pipeline.define('dedupe', ({ value }) => uniqWith(value, (b, c) => hashObject(b) === hashObject(c)), { itemize: false });
74
+ 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
+
77
+ Pipeline.define('defaultValue', ({ field, value }) => {
78
+ const { defaultValue } = field.toObject();
79
+ return value === undefined ? defaultValue : value;
80
+ }, { ignoreNull: false });
81
+
82
+ Pipeline.define('castValue', ({ field, value }) => {
83
+ const { type, isEmbedded } = field.toObject();
84
+
85
+ if (isEmbedded) return value;
86
+
87
+ return map(value, (v) => {
88
+ switch (type) {
89
+ case 'String': {
90
+ return `${v}`;
91
+ }
92
+ case 'Float': case 'Number': {
93
+ const num = Number(v);
94
+ if (!Number.isNaN(num)) return num;
95
+ return v;
96
+ }
97
+ case 'Int': {
98
+ const num = Number(v);
99
+ if (!Number.isNaN(num)) return parseInt(v, 10);
100
+ return v;
101
+ }
102
+ case 'Boolean': {
103
+ if (v === 'true') return true;
104
+ if (v === 'false') return false;
105
+ return v;
106
+ }
107
+ default: {
108
+ return v;
109
+ }
110
+ }
111
+ });
112
+ }, { itemize: false });
113
+
114
+ // Required fields
115
+ Pipeline.define('required', ({ model, field, value }) => {
116
+ if (value == null) throw Boom.badRequest(`${model}.${field} is required`);
117
+ }, { ignoreNull: false });
118
+
119
+ // A field cannot hold a reference to itself
120
+ Pipeline.define('selfless', ({ model, field, parentPath, value }) => {
121
+ if (`${value}` === `${parentPath('id')}`) throw Boom.badRequest(`${model}.${field} cannot hold a reference to itself`);
122
+ });
123
+
124
+ // Once set it cannot be changed
125
+ Pipeline.define('immutable', ({ model, field, docPath, parentPath, path, value }) => {
126
+ const hint = { id: parentPath('id') };
127
+ const oldVal = docPath(path, hint);
128
+ if (oldVal !== undefined && value !== undefined && `${hashObject(oldVal)}` !== `${hashObject(value)}`) throw Boom.badRequest(`${model}.${field} is immutable; cannot be changed once set ${oldVal} -> ${value}`);
129
+ });
130
+
131
+ // List of allowed values
132
+ Pipeline.factory('allow', (...args) => function allow({ model, field, value }) {
133
+ if (args.indexOf(value) === -1) throw Boom.badRequest(`${model}.${field} allows ${args}; found '${value}'`);
134
+ });
135
+
136
+ // List of disallowed values
137
+ Pipeline.factory('deny', (...args) => function deny({ model, field, value }) {
138
+ if (args.indexOf(value) > -1) throw Boom.badRequest(`${model}.${field} denys ${args}; found '${value}'`);
139
+ });
140
+
141
+ // Min/Max range
142
+ Pipeline.factory('range', (min, max) => {
143
+ if (min == null) min = undefined;
144
+ if (max == null) max = undefined;
145
+
146
+ return function range({ model, field, value }) {
147
+ const num = +value; // Coerce to number if possible
148
+ const test = Number.isNaN(num) ? value.length : num;
149
+ if (test < min || test > max) throw Boom.badRequest(`${model}.${field} must satisfy range ${min}:${max}; found '${value}'`);
150
+ };
151
+ }, { itemize: false });
152
+ }
153
+ };
154
+
155
+
156
+ // const jsStringMethods = [
157
+ // 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'indexOf', 'lastIndexOf', 'localeCompare',
158
+ // 'normalize', 'padEnd', 'padStart', 'repeat', 'replace', 'search', 'slice', 'split', 'substr', 'substring',
159
+ // 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimEnd', 'trimStart', 'raw',
160
+ // ];
161
+
162
+ // Transformer.factory('toTitleCase', () => ({ value }) => value.replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()), { enumerable: true });
163
+ // Transformer.factory('toLocaleTitleCase', (...args) => ({ value }) => value.replace(/\w\S*/g, w => w.charAt(0).toLocaleUpperCase(...args) + w.slice(1).toLocaleLowerCase()));
164
+ // Transformer.factory('toSentenceCase', () => ({ value }) => value.charAt(0).toUpperCase() + value.slice(1), { enumerable: true });
165
+ // Transformer.factory('toLocaleSentenceCase', (...args) => ({ value }) => value.charAt(0).toLocaleUpperCase(...args) + value.slice(1));
166
+ // Transformer.factory('toArray', () => ({ value }) => (Array.isArray(value) ? value : [value]), { itemize: false, enumerable: true });
167
+ // Transformer.factory('toDate', () => ({ value }) => new Date(value), { enumerable: true, writable: true });
168
+ // Transformer.factory('dedupe', () => ({ value }) => uniqWith(value, (b, c) => hashObject(b) === hashObject(c)), { ignoreNull: false, enumerable: true });
169
+ // Transformer.factory('dedupeBy', key => ({ value }) => uniqWith(value, (b, c) => hashObject(b[key]) === hashObject(c[key])), { ignoreNull: false, enumerable: true });
170
+ // Transformer.factory('timestamp', () => () => Date.now(), { enumerable: true, ignoreNull: false });
171
+ // Transformer.factory('createdAt', () => ({ value }) => value || Date.now(), { enumerable: true, ignoreNull: false });
172
+ // Transformer.factory('first', () => ({ value }) => (Array.isArray(value) ? value[0] : value), { enumerable: true });
173
+ // Transformer.factory('get', path => ({ value }) => get(value, path), { enumerable: true });
174
+ // Transformer.factory('set', path => ({ value }) => set({}, path, value), { enumerable: true });
package/src/data/Type.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const Type = require('../graphql/ast/Type');
2
- const Rule = require('../core/Rule');
3
- const Transformer = require('../core/Transformer');
2
+ const Pipeline = require('./Pipeline');
4
3
 
5
4
  module.exports = class extends Type {
6
5
  constructor(field) {
@@ -8,65 +7,25 @@ module.exports = class extends Type {
8
7
  this.field = field;
9
8
  }
10
9
 
11
- getRules() {
12
- const rules = [];
13
- const scalarType = this.field.getScalarRef();
10
+ getStructures() {
11
+ const type = this.field.getType();
14
12
  const enumType = this.field.getEnumRef();
15
-
16
- if (scalarType) {
17
- Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
18
- if (!Array.isArray(value)) value = [value];
19
- if (key === 'enforce') rules.push(...value.map(r => Rule.getInstances()[r]));
20
- });
21
- }
22
-
23
- if (enumType) {
24
- const values = enumType.getValue();
25
- rules.push(Rule.allow(...values));
26
- }
27
-
28
- return rules;
29
- }
30
-
31
- getTransformers() {
32
- const transformers = [];
33
- const scalarType = this.field.getScalarRef();
34
-
35
- if (scalarType) {
36
- Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
37
- if (!Array.isArray(value)) value = [value];
38
- if (key === 'transform') transformers.push(...value.map(t => Transformer.getInstances()[t]));
39
- });
40
- }
41
-
42
- return transformers;
43
- }
44
-
45
- getSerializers() {
46
- const transformers = [];
47
13
  const scalarType = this.field.getScalarRef();
48
-
49
- if (scalarType) {
50
- Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
51
- if (!Array.isArray(value)) value = [value];
52
- if (key === 'serialize') transformers.push(...value.map(t => Transformer.getInstances()[t]));
53
- });
54
- }
55
-
56
- return transformers;
57
- }
58
-
59
- getDeserializers() {
60
- const transformers = [];
61
- const scalarType = this.field.getScalarRef();
62
-
63
- if (scalarType) {
64
- Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
65
- if (!Array.isArray(value)) value = [value];
66
- if (key === 'deserialize') transformers.push(...value.map(t => Transformer.getInstances()[t]));
67
- });
68
- }
69
-
70
- return transformers;
14
+ const structures = { instructs: [], restructs: [], destructs: [], constructs: [], $serializers: [], serializers: [], $deserializers: [], deserializers: [], transformers: [] };
15
+
16
+ if (enumType) structures.serializers.push(Pipeline.define(`$allow:${type}`, Pipeline.allow(...enumType.getValue()), { configurable: true }));
17
+ if (!scalarType) return structures;
18
+
19
+ return Object.entries(scalarType.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
20
+ if (!Array.isArray(value)) value = [value];
21
+ if (key === 'instruct') prev.instructs.push(...value.map(t => Pipeline[t]));
22
+ if (key === 'restruct') prev.restructs.push(...value.map(t => Pipeline[t]));
23
+ if (key === 'destruct') prev.destructs.push(...value.map(t => Pipeline[t]));
24
+ if (key === 'construct') prev.constructs.push(...value.map(t => Pipeline[t]));
25
+ if (key === 'serialize') prev.serializers.push(...value.map(t => Pipeline[t]));
26
+ if (key === 'deserialize') prev.deserializers.push(...value.map(t => Pipeline[t]));
27
+ if (key === 'transform') prev.transformers.push(...value.map(t => Pipeline[t]));
28
+ return prev;
29
+ }, structures);
71
30
  }
72
31
  };
@@ -1,19 +1,18 @@
1
1
  const Util = require('util');
2
- const { get, has } = require('lodash');
2
+ const { get } = require('lodash');
3
3
  const { MongoClient, ObjectID } = require('mongodb');
4
- const { proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
4
+ const { map, ensureArray, proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
5
5
 
6
6
  module.exports = class MongoDriver {
7
- constructor(config, schema) {
7
+ constructor(config) {
8
8
  this.config = config;
9
- this.schema = schema;
10
9
  this.connection = this.connect();
11
10
  this.getDirectives = () => get(config, 'directives', {});
12
11
  }
13
12
 
14
13
  connect() {
15
14
  const { uri, options = {} } = this.config;
16
- options.ignoreUndefined = true;
15
+ options.ignoreUndefined = false;
17
16
  return MongoClient.connect(uri, options);
18
17
  }
19
18
 
@@ -26,7 +25,8 @@ module.exports = class MongoDriver {
26
25
  }
27
26
 
28
27
  query(collection, method, ...args) {
29
- if (has(args[args.length - 1], 'debug')) console.log(collection, method, Util.inspect(args, { depth: null, showHidden: false, colors: true }));
28
+ if (get(args[args.length - 1], 'debug') === true) console.log(collection, method, Util.inspect(args, { depth: null, showHidden: false, colors: true }));
29
+ if (method === 'aggregate') args.splice(2);
30
30
  return this.raw(collection)[method](...args);
31
31
  }
32
32
 
@@ -37,19 +37,19 @@ module.exports = class MongoDriver {
37
37
  }
38
38
 
39
39
  findOne(query) {
40
- return this.findMany(Object.assign(query, { first: 1 })).then(docs => docs[0]);
40
+ return this.findMany(Object.assign(query, { first: 1 })).then((stream) => {
41
+ return new Promise((resolve, reject) => {
42
+ stream.on('data', resolve);
43
+ stream.on('error', reject);
44
+ stream.on('end', resolve);
45
+ });
46
+ });
41
47
  }
42
48
 
43
49
  findMany(query) {
44
- const { model, options = {}, last, flags } = query;
50
+ const { model, options = {}, flags } = query;
45
51
  Object.assign(options, this.config.query || {});
46
-
47
- return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query), options, flags).then((cursor) => {
48
- return cursor.toArray().then((results) => {
49
- if (last) return results.splice(-last);
50
- return results;
51
- });
52
- });
52
+ return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query), options, flags).then(cursor => cursor.stream());
53
53
  }
54
54
 
55
55
  count(query) {
@@ -64,7 +64,7 @@ module.exports = class MongoDriver {
64
64
  }
65
65
 
66
66
  createOne({ model, input, options, flags }) {
67
- return this.query(model, 'insertOne', input, options, flags).then(result => Object.assign(input, { _id: result.insertedId }));
67
+ return this.query(model, 'insertOne', input, options, flags).then(result => Object.assign(input, { id: result.insertedId }));
68
68
  }
69
69
 
70
70
  updateOne({ model, where, $doc, options, flags }) {
@@ -77,7 +77,7 @@ module.exports = class MongoDriver {
77
77
  }
78
78
 
79
79
  deleteOne({ model, where, options, flags }) {
80
- return this.query(model, 'deleteOne', where, options, flags);
80
+ return this.query(model, 'deleteOne', where, options, flags).then(() => true);
81
81
  }
82
82
 
83
83
  dropModel(model) {
@@ -140,10 +140,10 @@ module.exports = class MongoDriver {
140
140
  return proxyDeep(toKeyObj(where), {
141
141
  get(target, prop, rec) {
142
142
  const value = Reflect.get(target, prop, rec);
143
- if (Array.isArray(value)) return { $in: value };
144
143
  if (typeof value === 'function') return value.bind(target);
145
- if (typeof value === 'string') { return globToRegex(value, { nocase: true, regex: true }); }
146
- return value;
144
+ const $value = map(value, v => (typeof v === 'string' ? globToRegex(v, { nocase: true, regex: true }) : v));
145
+ if (Array.isArray($value)) return { $in: $value };
146
+ return $value;
147
147
  },
148
148
  }).toObject();
149
149
  }
@@ -151,18 +151,40 @@ module.exports = class MongoDriver {
151
151
  static getAddFields(query) {
152
152
  const { shape, where } = query;
153
153
 
154
- return shape.reduce((prev, { from, type }) => {
155
- const value = where[from];
154
+ return shape.reduce((prev, { from, type, isArray }) => {
155
+ // Basic checks to see if worth converting for regex
156
+ let value = where[from];
156
157
  if (value === undefined) return prev;
157
158
  if (!isScalarDataType(type)) return prev;
158
- const stype = String((type === 'Float' || type === 'Int' ? 'Number' : type)).toLowerCase();
159
- if (String(typeof value) === `${stype}`) return prev;
160
- return Object.assign(prev, { [from]: { $toString: `$${from}` } });
159
+
160
+ // Do regex conversion
161
+ if (isArray) value = value.$in || value; // Where clause does not always use $in
162
+ if (!ensureArray(value).some(el => el instanceof RegExp)) return prev;
163
+ const conversion = isArray ? { $map: { input: `$${from}`, as: 'el', in: { $toString: '$$el' } } } : { $toString: `$${from}` };
164
+ return Object.assign(prev, { [from]: conversion });
161
165
  }, {});
162
166
  }
163
167
 
168
+ static getProjectFields(parentShape, currentShape = { _id: 0, id: '$_id' }, isEmbedded, isEmbeddedArray, path = []) {
169
+ return parentShape.reduce((project, value) => {
170
+ const { from, to, shape: subShape, isArray } = value;
171
+ const $key = isEmbedded && isEmbeddedArray ? `$$embedded.${from}` : `$${path.concat(from).join('.')}`;
172
+
173
+ if (subShape) {
174
+ const $project = MongoDriver.getProjectFields(subShape, {}, true, isArray, path.concat(from));
175
+ Object.assign(project, { [to]: isArray ? { $map: { input: $key, as: 'embedded', in: $project } } : $project });
176
+ } else if (isEmbedded) {
177
+ Object.assign(project, { [to]: $key });
178
+ } else {
179
+ Object.assign(project, { [to]: from === to ? 1 : $key });
180
+ }
181
+
182
+ return project;
183
+ }, currentShape);
184
+ }
185
+
164
186
  static aggregateQuery(query, count = false) {
165
- const { where: $match, sort, skip, limit, joins } = query;
187
+ const { where: $match, sort = {}, skip, limit, joins, after, before, first } = query;
166
188
  const $aggregate = [{ $match }];
167
189
 
168
190
  // Used for $regex matching
@@ -191,10 +213,13 @@ module.exports = class MongoDriver {
191
213
  if (limit) $aggregate.push({ $limit: limit });
192
214
 
193
215
  // Pagination
194
- const { after, before, first } = query;
195
216
  if (after) $aggregate.push({ $match: { $or: Object.entries(after).reduce((prev, [key, value]) => prev.concat({ [key]: { [sort[key] === 1 ? '$gte' : '$lte']: value } }), []) } });
196
217
  if (before) $aggregate.push({ $match: { $or: Object.entries(before).reduce((prev, [key, value]) => prev.concat({ [key]: { [sort[key] === 1 ? '$lte' : '$gte']: value } }), []) } });
197
218
  if (first) $aggregate.push({ $limit: first });
219
+
220
+ // // Projection
221
+ // const $project = MongoDriver.getProjectFields(shape);
222
+ // $aggregate.push({ $project });
198
223
  }
199
224
 
200
225
  return $aggregate;
@@ -1,4 +1,3 @@
1
- const { get } = require('lodash');
2
1
  const Node = require('./Node');
3
2
  const Type = require('./Type');
4
3
  const { uvl } = require('../../service/app.service');
@@ -44,27 +43,6 @@ module.exports = class Field extends Node {
44
43
  return this.getDirectiveArg('field', 'default');
45
44
  }
46
45
 
47
- resolveBoundValue(query, initialValue) {
48
- // If no bound value, return default value
49
- const defaultValue = uvl(initialValue, this.getDefaultValue());
50
- if (!this.hasBoundValue()) return defaultValue;
51
-
52
- // Grab @value definition, if passive then check for initialValue
53
- const { scope, path, passive = false } = this.getDirectiveArgs('value');
54
- if (passive && initialValue !== undefined) return initialValue;
55
-
56
- // Resolve @value
57
- switch (scope) {
58
- case 'context': {
59
- const { resolver } = query.toObject();
60
- const context = resolver.getContext();
61
- const value = get(context, path);
62
- return uvl((typeof value === 'function') ? value() : value, defaultValue);
63
- }
64
- default: return this[path];
65
- }
66
- }
67
-
68
46
  // Model Methods
69
47
  getSchema() {
70
48
  return this.model.getSchema();
@@ -75,7 +53,8 @@ module.exports = class Field extends Node {
75
53
  }
76
54
 
77
55
  getModelRef() {
78
- return this.schema.getModel(this.getType());
56
+ const refType = this.getDirectiveArg('field', 'id', this.getType());
57
+ return this.schema.getModel(refType);
79
58
  }
80
59
 
81
60
  getFieldRef() {
@@ -90,6 +69,10 @@ module.exports = class Field extends Node {
90
69
  return model ? model.getField(this.getVirtualRef()) : null;
91
70
  }
92
71
 
72
+ getIdModel() {
73
+ return this.getModelRef() || this.getModel();
74
+ }
75
+
93
76
  resolveField() {
94
77
  const field = this.getVirtualField() || this;
95
78
  return field === this ? this : field.resolveField();
@@ -101,7 +84,7 @@ module.exports = class Field extends Node {
101
84
  }
102
85
 
103
86
  isDefaulted() {
104
- return Boolean(this.hasBoundValue() || this.getDefaultValue() != null);
87
+ return Boolean(this.getDefaultValue() != null);
105
88
  }
106
89
 
107
90
  isRequired() {
@@ -117,6 +100,16 @@ module.exports = class Field extends Node {
117
100
  return Boolean(modelRef && !this.isEmbedded());
118
101
  }
119
102
 
103
+ isIdField() {
104
+ return this.isPrimaryKeyId() || this.isFKReference();
105
+ }
106
+
107
+ isPrimaryKeyId() {
108
+ const key = this.getKey();
109
+ const idKey = this.getModel().idKey();
110
+ return key === idKey;
111
+ }
112
+
120
113
  getJoinInfo() {
121
114
  const modelRef = this.getModelRef();
122
115
  if (!modelRef || this.isEmbedded()) return null;
@@ -145,12 +138,11 @@ module.exports = class Field extends Node {
145
138
  // GQL Schema Methods
146
139
  getGQLType(suffix, options = {}) {
147
140
  let type = this.getType();
148
- // if (suffix === 'InputUpdate' && this.isSpliceable()) suffix = 'InputSplice';
149
141
  const modelType = `${type}${suffix}`;
150
142
  if (suffix && !this.isScalar()) type = this.isEmbedded() ? modelType : 'ID';
151
143
  type = this.isArray() ? `[${type}${this.isArrayElementRequired() ? '!' : ''}]` : type;
152
144
  if (!suffix && this.isRequired()) type += '!';
153
- if (!options.splice && suffix === 'InputCreate' && this.isRequired() && !this.isDefaulted()) type += '!';
145
+ if (suffix === 'InputCreate' && this.isRequired() && !this.isDefaulted()) type += '!';
154
146
  return type;
155
147
  }
156
148
 
@@ -180,4 +172,30 @@ module.exports = class Field extends Node {
180
172
  if (this.isFKReference()) return this.isArray() ? '[ID]' : 'ID';
181
173
  return this.getGQLType();
182
174
  }
175
+
176
+ initialize() {
177
+ this.props = {
178
+ name: this.getName(),
179
+ type: this.getType(),
180
+ datatype: this.getDataType(),
181
+ defaultValue: this.getDefaultValue(),
182
+ isArray: this.isArray(),
183
+ isScalar: this.isScalar(),
184
+ isVirtual: this.isVirtual(),
185
+ isRequired: this.isRequired(),
186
+ isEmbedded: this.isEmbedded(),
187
+ isIdField: this.isIdField(),
188
+ isPrimaryKeyId: this.isPrimaryKeyId(),
189
+ isPersistable: this.isPersistable(),
190
+ modelRef: this.getModelRef(),
191
+ virtualRef: this.getVirtualRef(),
192
+ virtualField: this.getVirtualField(),
193
+ };
194
+
195
+ return this;
196
+ }
197
+
198
+ toObject() {
199
+ return this.props;
200
+ }
183
201
  };
@@ -92,10 +92,6 @@ module.exports = class Model extends Node {
92
92
  return this.getFields().filter(field => field.isDefaulted());
93
93
  }
94
94
 
95
- getBoundValueFields() {
96
- return this.getFields().filter(field => field.hasBoundValue());
97
- }
98
-
99
95
  getDataRefFields() {
100
96
  return this.getFields().filter(field => Boolean(field.getDataRef()));
101
97
  }
@@ -104,10 +100,6 @@ module.exports = class Model extends Node {
104
100
  return this.getFields().filter(field => Boolean(field.getModelRef()));
105
101
  }
106
102
 
107
- // getDataRefFields() {
108
- // return this.fields.filter(field => Boolean(field.getDataRef() && !field.isEmbedded()));
109
- // }
110
-
111
103
  getEmbeddedFields() {
112
104
  return this.getFields().filter(field => field.isEmbedded());
113
105
  }
@@ -128,14 +120,6 @@ module.exports = class Model extends Node {
128
120
  return this.getFields().filter(field => field.isPersistable());
129
121
  }
130
122
 
131
- getSerializeFields() {
132
- return this.getFields().filter(field => field.getSerializers().length);
133
- }
134
-
135
- getDeserializeFields() {
136
- return this.getFields().filter(field => field.getDeserializers().length);
137
- }
138
-
139
123
  // Misc
140
124
  getIndexes() {
141
125
  return this.getDirectives('index').map((d) => {
@@ -153,4 +137,9 @@ module.exports = class Model extends Node {
153
137
  }, {});
154
138
  });
155
139
  }
140
+
141
+ initialize() {
142
+ this.fields.forEach(field => field.initialize());
143
+ return this;
144
+ }
156
145
  };
@@ -2,7 +2,6 @@ const { get } = require('lodash');
2
2
  const { Kind } = require('graphql');
3
3
  const { nvl, uvl } = require('../../service/app.service');
4
4
  const { mergeAST } = require('../../service/graphql.service');
5
- // const Memoizer = require('../../data/Memoizer');
6
5
 
7
6
  const operations = ['Query', 'Mutation', 'Subscription'];
8
7
  const modelKinds = [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION, Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
@@ -18,7 +17,6 @@ module.exports = class Node {
18
17
  this.toString = () => this.getName();
19
18
  this.nodeType = nodeType;
20
19
  this.name = get(this.ast, 'name.value');
21
- // return new Memoizer(this, Object.getOwnPropertyNames(Node.prototype).filter(m => ['getContext'].indexOf(m) === -1));
22
20
  }
23
21
 
24
22
  // Basic AST Methods
@@ -130,14 +128,6 @@ module.exports = class Node {
130
128
  return this.getDirectiveArg('model', 'meta');
131
129
  }
132
130
 
133
- getSerialize() {
134
- return this.getDirectiveArg('field', 'serialize', this.getDirectiveArg('model', 'serialize'));
135
- }
136
-
137
- getDeserialize() {
138
- return this.getDirectiveArg('field', 'deserialize', this.getDirectiveArg('model', 'deserialize'));
139
- }
140
-
141
131
  // Booleans
142
132
  isModel() {
143
133
  return Boolean(modelKinds.some(k => this.getKind() === k) && operations.every(o => this.getName() !== o));
@@ -166,13 +156,6 @@ module.exports = class Node {
166
156
  return Boolean(this.getDirectiveArg('link', 'by'));
167
157
  }
168
158
 
169
- /**
170
- * Does the model/field have a bound @value directive
171
- */
172
- hasBoundValue() {
173
- return Boolean(this.getDirective('value'));
174
- }
175
-
176
159
  /**
177
160
  * Is a model annotated with @model
178
161
  */
@@ -208,21 +191,6 @@ module.exports = class Node {
208
191
  }
209
192
  }
210
193
 
211
- /**
212
- * Is this API embedded in another document
213
- */
214
- isEmbeddedApi() {
215
- return this.isEmbedded() && Boolean(this.getDirectiveArg('field', 'embedApi'));
216
- }
217
-
218
- /**
219
- * Can the field be changed after it's set
220
- */
221
- isImmutable() {
222
- const enforce = this.getDirectiveArg('field', 'enforce', '');
223
- return Boolean(JSON.stringify(enforce).indexOf('immutable') > -1);
224
- }
225
-
226
194
  /**
227
195
  * Define it's behavior at the Data Access Layer
228
196
  *