@coderich/autograph 0.9.16 → 0.10.2
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 +24 -0
- package/index.js +2 -6
- package/package.json +10 -10
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +35 -58
- package/src/core/Schema.js +5 -38
- package/src/core/ServerResolver.js +7 -93
- package/src/data/DataLoader.js +68 -27
- package/src/data/DataService.js +59 -58
- package/src/data/Field.js +71 -96
- package/src/data/Model.js +95 -113
- package/src/data/Pipeline.js +174 -0
- package/src/data/Type.js +19 -60
- package/src/driver/MongoDriver.js +53 -28
- package/src/graphql/ast/Field.js +44 -26
- package/src/graphql/ast/Model.js +5 -16
- package/src/graphql/ast/Node.js +0 -32
- package/src/graphql/ast/Schema.js +109 -112
- package/src/graphql/extension/api.js +22 -35
- package/src/graphql/extension/framework.js +25 -33
- package/src/graphql/extension/type.js +2 -2
- package/src/query/Query.js +73 -15
- package/src/query/QueryBuilder.js +37 -28
- package/src/query/QueryBuilderTransaction.js +3 -3
- package/src/query/QueryResolver.js +93 -44
- package/src/query/QueryService.js +31 -34
- package/src/service/app.service.js +56 -35
- package/src/service/decorator.service.js +21 -288
- package/src/service/event.service.js +5 -79
- package/src/service/graphql.service.js +0 -9
- package/src/service/schema.service.js +5 -3
- package/src/core/Rule.js +0 -107
- package/src/core/SchemaDecorator.js +0 -46
- package/src/core/Transformer.js +0 -68
- package/src/data/Memoizer.js +0 -39
- package/src/data/ResultSet.js +0 -246
- package/src/graphql/ast/SchemaDecorator.js +0 -138
- 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
|
|
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
|
-
|
|
12
|
-
const
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 (
|
|
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(
|
|
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 = {},
|
|
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) {
|
|
@@ -58,13 +58,13 @@ module.exports = class MongoDriver {
|
|
|
58
58
|
|
|
59
59
|
return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query, true), options, flags).then((cursor) => {
|
|
60
60
|
return cursor.next().then((doc) => {
|
|
61
|
-
return
|
|
61
|
+
return doc ? doc.count : 0;
|
|
62
62
|
});
|
|
63
63
|
});
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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;
|
package/src/graphql/ast/Field.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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 (
|
|
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
|
};
|
package/src/graphql/ast/Model.js
CHANGED
|
@@ -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
|
};
|
package/src/graphql/ast/Node.js
CHANGED
|
@@ -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
|
*
|