@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
package/src/core/Rule.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
const { get } = require('lodash');
|
|
2
|
-
const isEmail = require('validator/lib/isEmail');
|
|
3
|
-
const Boom = require('./Boom');
|
|
4
|
-
const { map, ensureArray, hashObject } = require('../service/app.service');
|
|
5
|
-
|
|
6
|
-
const instances = {};
|
|
7
|
-
const jsStringMethods = ['endsWith', 'includes', 'match', 'search', 'startsWith'];
|
|
8
|
-
|
|
9
|
-
class Rule {
|
|
10
|
-
constructor(thunk, options = {}, name = 'Unknown') {
|
|
11
|
-
const {
|
|
12
|
-
ignoreNull = true,
|
|
13
|
-
itemize = true,
|
|
14
|
-
toError = (field, value, msg) => Boom.notAcceptable(`Rule (${name}) failed for { ${field.getModel()}.${field}: ${value} }`),
|
|
15
|
-
} = (options || {});
|
|
16
|
-
|
|
17
|
-
return Object.defineProperty((field, val, query) => {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
if (ignoreNull && val == null) return resolve();
|
|
20
|
-
|
|
21
|
-
if (ignoreNull && itemize) {
|
|
22
|
-
return Promise.all(ensureArray(map(val, async (v) => {
|
|
23
|
-
const err = await thunk(field, v, query);
|
|
24
|
-
if (err) return Promise.reject(toError(field, v));
|
|
25
|
-
return Promise.resolve();
|
|
26
|
-
}))).then(v => resolve()).catch(e => reject(e));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return Promise.all([(async () => {
|
|
30
|
-
const err = await thunk(field, val, query);
|
|
31
|
-
if (err) return Promise.reject(toError(field, val));
|
|
32
|
-
return Promise.resolve();
|
|
33
|
-
})()]).then(v => resolve()).catch(e => reject(e));
|
|
34
|
-
});
|
|
35
|
-
}, 'type', { value: 'rule' });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static factory(name, thunk, options = {}) {
|
|
39
|
-
return Object.defineProperty(Rule, name, {
|
|
40
|
-
value: (...args) => Object.defineProperty(new Rule(thunk(...args), options, name), 'method', { value: name }),
|
|
41
|
-
writable: options.writable,
|
|
42
|
-
enumerable: options.enumerable,
|
|
43
|
-
})[name];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static extend(name, instance) {
|
|
47
|
-
const invalidArg = () => { throw new Error('Invalid argument; expected Rule factory instance'); };
|
|
48
|
-
const { method = invalidArg(), type = invalidArg() } = instance;
|
|
49
|
-
if (type !== 'rule' || !Rule[method]) invalidArg();
|
|
50
|
-
return (instances[name] = instance);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
static getInstances() {
|
|
54
|
-
const defaultRules = Object.entries(Rule).map(([name, method]) => ({ name, instance: method() }));
|
|
55
|
-
const customRules = Object.entries(instances).map(([name, instance]) => ({ name, instance }));
|
|
56
|
-
const rules = defaultRules.concat(customRules);
|
|
57
|
-
return rules.reduce((prev, { name, instance }) => Object.assign(prev, { [name]: instance }), {});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Factory methods
|
|
62
|
-
jsStringMethods.forEach(name => Rule.factory(name, (...args) => (f, v) => !String(v)[name](...args)));
|
|
63
|
-
|
|
64
|
-
// Ensures Foreign Key relationships
|
|
65
|
-
Rule.factory('ensureId', () => (f, v, q) => {
|
|
66
|
-
const { resolver } = q.toObject();
|
|
67
|
-
return resolver.match(f.getType()).id(v).one().then(doc => Boolean(doc == null));
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// Enforces required fields (only during create)
|
|
71
|
-
Rule.factory('required', () => (f, v, q) => {
|
|
72
|
-
const { crud, input } = q.toObject();
|
|
73
|
-
return (crud === 'create' ? v == null : Object.prototype.hasOwnProperty.call(input, f.getName()) && v == null);
|
|
74
|
-
}, { ignoreNull: false, enumerable: true });
|
|
75
|
-
|
|
76
|
-
// A field cannot hold a reference to itself (model)
|
|
77
|
-
Rule.factory('selfless', () => (f, v, q) => {
|
|
78
|
-
const { doc } = q.toObject();
|
|
79
|
-
if (`${v}` === `${get(doc, 'id')}`) throw Boom.badRequest(`${f.getModel()}.${f.getName()} cannot hold a reference to itself`);
|
|
80
|
-
return false;
|
|
81
|
-
}, { enumerable: true });
|
|
82
|
-
|
|
83
|
-
// Once set it cannot be changed
|
|
84
|
-
Rule.factory('immutable', () => (f, v, q) => {
|
|
85
|
-
const { doc, crud } = q.toObject();
|
|
86
|
-
const path = `${f.getModel()}.${f.getName()}`;
|
|
87
|
-
const p = path.substr(path.indexOf('.') + 1);
|
|
88
|
-
const oldVal = get(doc, p);
|
|
89
|
-
if (crud === 'update' && oldVal !== undefined && v !== undefined && `${hashObject(v)}` !== `${hashObject(oldVal)}`) throw Boom.badRequest(`${path} is immutable; cannot be changed once set`);
|
|
90
|
-
return false;
|
|
91
|
-
}, { enumerable: true });
|
|
92
|
-
|
|
93
|
-
Rule.factory('allow', (...args) => (f, v) => args.indexOf(v) === -1);
|
|
94
|
-
Rule.factory('deny', (...args) => (f, v) => args.indexOf(v) > -1);
|
|
95
|
-
Rule.factory('range', (min, max) => {
|
|
96
|
-
if (min == null) min = undefined;
|
|
97
|
-
if (max == null) max = undefined;
|
|
98
|
-
return (f, v) => {
|
|
99
|
-
const num = +v; // Coerce to number if possible
|
|
100
|
-
const test = Number.isNaN(num) ? v.length : num;
|
|
101
|
-
return test < min || test > max;
|
|
102
|
-
};
|
|
103
|
-
}, { itemize: false });
|
|
104
|
-
Rule.factory('email', () => (f, v) => !isEmail(v), { enumerable: true });
|
|
105
|
-
Rule.factory('distinct', () => (f, v) => false, { enumerable: true });
|
|
106
|
-
|
|
107
|
-
module.exports = Rule;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
const Model = require('../data/Model');
|
|
2
|
-
const SchemaDecorator = require('../graphql/ast/SchemaDecorator');
|
|
3
|
-
const { identifyOnDeletes } = require('../service/schema.service');
|
|
4
|
-
const { createSystemEvent } = require('../service/event.service');
|
|
5
|
-
|
|
6
|
-
// Export class
|
|
7
|
-
module.exports = class extends SchemaDecorator {
|
|
8
|
-
constructor(schema, stores) {
|
|
9
|
-
super(schema);
|
|
10
|
-
|
|
11
|
-
// Create drivers
|
|
12
|
-
this.drivers = Object.entries(stores).reduce((prev, [key, value]) => {
|
|
13
|
-
const { Driver } = value;
|
|
14
|
-
|
|
15
|
-
return Object.assign(prev, {
|
|
16
|
-
[key]: {
|
|
17
|
-
dao: new Driver(value, this),
|
|
18
|
-
idKey: Driver.idKey,
|
|
19
|
-
idValue: Driver.idValue,
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
}, {});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
setup() {
|
|
26
|
-
return createSystemEvent('Setup', this, () => {
|
|
27
|
-
const entities = this.models.filter(m => m.isEntity());
|
|
28
|
-
|
|
29
|
-
// Create model indexes
|
|
30
|
-
return Promise.all(entities.map(async (model) => {
|
|
31
|
-
const key = model.getKey();
|
|
32
|
-
const indexes = model.getIndexes();
|
|
33
|
-
const driver = model.getDriver();
|
|
34
|
-
if (driver.createCollection) await driver.createCollection(key);
|
|
35
|
-
return driver.createIndexes(key, indexes);
|
|
36
|
-
}));
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
initialize() {
|
|
41
|
-
super.initialize();
|
|
42
|
-
this.models = super.getModels().map(model => new Model(this, model, this.drivers[model.getDriverName()]));
|
|
43
|
-
this.models.forEach(model => model.referentialIntegrity(identifyOnDeletes(this.models, model)));
|
|
44
|
-
return this;
|
|
45
|
-
}
|
|
46
|
-
};
|
package/src/core/Transformer.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
const { get, set, uniqWith } = require('lodash');
|
|
2
|
-
const { map, serialize, castCmp, hashObject } = require('../service/app.service');
|
|
3
|
-
|
|
4
|
-
const instances = {};
|
|
5
|
-
let allInstances;
|
|
6
|
-
|
|
7
|
-
const jsStringMethods = [
|
|
8
|
-
'charAt', 'charCodeAt', 'codePointAt', 'concat', 'indexOf', 'lastIndexOf', 'localeCompare',
|
|
9
|
-
'normalize', 'padEnd', 'padStart', 'repeat', 'replace', 'search', 'slice', 'split', 'substr', 'substring',
|
|
10
|
-
'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimEnd', 'trimStart', 'raw',
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
class Transformer {
|
|
14
|
-
constructor(thunk, options = {}) {
|
|
15
|
-
const { ignoreNull = true, itemize = true } = (options || {});
|
|
16
|
-
|
|
17
|
-
return Object.defineProperty((field, val, query) => {
|
|
18
|
-
if (ignoreNull && val == null) return val;
|
|
19
|
-
if (ignoreNull && itemize) return map(val, v => thunk(field, v, query));
|
|
20
|
-
return thunk(field, val, query);
|
|
21
|
-
}, 'type', { value: 'transformer' });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static factory(name, thunk, options = {}) {
|
|
25
|
-
return Object.defineProperty(Transformer, name, {
|
|
26
|
-
value: (...args) => Object.defineProperty(new Transformer(thunk(...args), options), 'method', { value: name }),
|
|
27
|
-
writable: options.writable,
|
|
28
|
-
enumerable: options.enumerable,
|
|
29
|
-
})[name];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
static extend(name, instance) {
|
|
33
|
-
const invalidArg = () => { throw new Error('Invalid argument; expected Transformer factory instance'); };
|
|
34
|
-
const { method = invalidArg(), type = invalidArg() } = instance;
|
|
35
|
-
if (type !== 'transformer' || !Transformer[method]) invalidArg();
|
|
36
|
-
return (instances[name] = instance);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
static getInstances() {
|
|
40
|
-
if (allInstances) return allInstances;
|
|
41
|
-
const defaultTransformers = Object.entries(Transformer).map(([name, method]) => ({ name, instance: method() }));
|
|
42
|
-
const customTransformers = Object.entries(instances).map(([name, instance]) => ({ name, instance }));
|
|
43
|
-
const transformers = defaultTransformers.concat(customTransformers);
|
|
44
|
-
allInstances = transformers.reduce((prev, { name, instance }) => Object.assign(prev, { [name]: instance }), {});
|
|
45
|
-
return allInstances;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Factory methods
|
|
50
|
-
const enumerables = ['toLowerCase', 'toUpperCase', 'trim', 'trimEnd', 'trimStart', 'toString'];
|
|
51
|
-
jsStringMethods.forEach(name => Transformer.factory(name, (...args) => (f, v) => String(v)[name](...args), { enumerable: enumerables.indexOf(name) > -1 }));
|
|
52
|
-
Transformer.factory('toTitleCase', () => (f, v) => v.replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()), { enumerable: true });
|
|
53
|
-
Transformer.factory('toLocaleTitleCase', (...args) => (f, v) => v.replace(/\w\S*/g, w => w.charAt(0).toLocaleUpperCase(...args) + w.slice(1).toLocaleLowerCase()));
|
|
54
|
-
Transformer.factory('toSentenceCase', () => (f, v) => v.charAt(0).toUpperCase() + v.slice(1), { enumerable: true });
|
|
55
|
-
Transformer.factory('toLocaleSentenceCase', (...args) => (f, v) => v.charAt(0).toLocaleUpperCase(...args) + v.slice(1));
|
|
56
|
-
Transformer.factory('toId', () => (f, v) => f.getModel().idValue(v), { enumerable: true });
|
|
57
|
-
Transformer.factory('toArray', () => (f, v) => (Array.isArray(v) ? v : [v]), { itemize: false, enumerable: true });
|
|
58
|
-
Transformer.factory('toDate', () => (f, v) => new Date(v), { enumerable: true, writable: true });
|
|
59
|
-
Transformer.factory('dedupe', () => (f, a) => uniqWith(a, (b, c) => hashObject(b) === hashObject(c)), { ignoreNull: false, enumerable: true });
|
|
60
|
-
Transformer.factory('dedupeBy', key => (f, a) => uniqWith(a, (b, c) => hashObject(b[key]) === hashObject(c[key])), { ignoreNull: false, enumerable: true });
|
|
61
|
-
Transformer.factory('timestamp', () => (f, v) => Date.now(), { enumerable: true });
|
|
62
|
-
Transformer.factory('first', () => (f, v) => (Array.isArray(v) ? v[0] : v), { enumerable: true });
|
|
63
|
-
Transformer.factory('get', path => (f, v) => get(v, path), { enumerable: true });
|
|
64
|
-
Transformer.factory('set', path => (f, v) => set({}, path, v), { enumerable: true });
|
|
65
|
-
Transformer.factory('cast', type => (f, v) => castCmp(type, v));
|
|
66
|
-
Transformer.factory('serialize', () => (f, v) => serialize(f, v));
|
|
67
|
-
|
|
68
|
-
module.exports = Transformer;
|
package/src/data/Memoizer.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memoizer.
|
|
3
|
-
*
|
|
4
|
-
* Keeps a memoized cache of function call results; executing them once and reusing the result.
|
|
5
|
-
*
|
|
6
|
-
* @param {Object} src - the object|function to memoize
|
|
7
|
-
* @param {Array} whitelist - whitelist of src methods to memoize (defaults to enumerable functions)
|
|
8
|
-
*/
|
|
9
|
-
module.exports = class Memoizer {
|
|
10
|
-
constructor(src, whitelist = Object.keys(src).filter(k => typeof src[k] === 'function')) {
|
|
11
|
-
const cache = {};
|
|
12
|
-
|
|
13
|
-
return new Proxy(src, {
|
|
14
|
-
// This gets called when accessing properties of an object
|
|
15
|
-
get(target, prop, rec) {
|
|
16
|
-
const value = Reflect.get(target, prop, rec);
|
|
17
|
-
|
|
18
|
-
if (typeof value === 'function') {
|
|
19
|
-
if (whitelist.indexOf(prop) === -1) return value.bind(target);
|
|
20
|
-
|
|
21
|
-
return (...args) => {
|
|
22
|
-
const key = `${prop}:${JSON.stringify(args)}`;
|
|
23
|
-
cache[key] = Object.prototype.hasOwnProperty.call(cache, key) ? cache[key] : value.bind(target)(...args);
|
|
24
|
-
return cache[key];
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return value;
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
// This gets called when src is a function
|
|
32
|
-
apply(target, thisArg, args) {
|
|
33
|
-
const key = JSON.stringify(args);
|
|
34
|
-
cache[key] = Object.prototype.hasOwnProperty.call(cache, key) ? cache[key] : target.call(thisArg, ...args);
|
|
35
|
-
return cache[key];
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
};
|
package/src/data/ResultSet.js
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
const { get } = require('lodash');
|
|
2
|
-
const DataService = require('./DataService');
|
|
3
|
-
const { map, ensureArray, keyPaths, toGUID } = require('../service/app.service');
|
|
4
|
-
|
|
5
|
-
const modelCache = new WeakMap();
|
|
6
|
-
|
|
7
|
-
module.exports = class ResultSet {
|
|
8
|
-
constructor(query, data, adjustForPagination = true) {
|
|
9
|
-
if (data == null) return data;
|
|
10
|
-
|
|
11
|
-
const { resolver, model, sort, first, after, last, before } = query.toObject();
|
|
12
|
-
|
|
13
|
-
ResultSet.ensureModelCache(model);
|
|
14
|
-
|
|
15
|
-
const { template, fieldDefs } = modelCache.get(model);
|
|
16
|
-
|
|
17
|
-
const rs = map(data, (doc) => {
|
|
18
|
-
if (doc == null || typeof doc !== 'object') return doc;
|
|
19
|
-
|
|
20
|
-
const instance = Object.create(template, {
|
|
21
|
-
$$services: {
|
|
22
|
-
value: {
|
|
23
|
-
cache: new Map(),
|
|
24
|
-
data: doc,
|
|
25
|
-
resolver,
|
|
26
|
-
query,
|
|
27
|
-
sort,
|
|
28
|
-
},
|
|
29
|
-
enumerable: false,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return new Proxy(instance, {
|
|
34
|
-
ownKeys(target) {
|
|
35
|
-
return Reflect.ownKeys(target).concat('id', fieldDefs.map(d => d.name));
|
|
36
|
-
},
|
|
37
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
38
|
-
return (prop === 'id' || fieldDefs.find(el => el.name === prop)) ? { enumerable: true, configurable: true } : Reflect.getOwnPropertyDescriptor(target, prop);
|
|
39
|
-
},
|
|
40
|
-
getPrototypeOf() {
|
|
41
|
-
return { $$services: instance.$$services };
|
|
42
|
-
},
|
|
43
|
-
deleteProperty(target, prop) {
|
|
44
|
-
const { key = prop } = fieldDefs.find(d => d.name === prop);
|
|
45
|
-
delete instance[prop];
|
|
46
|
-
delete instance.$$services.data[key];
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
let hasNextPage = false;
|
|
52
|
-
let hasPreviousPage = false;
|
|
53
|
-
if (adjustForPagination && rs.length) (({ hasPreviousPage, hasNextPage } = DataService.paginateResultSet(rs, first, after, last, before)));
|
|
54
|
-
|
|
55
|
-
return Object.defineProperties(rs, {
|
|
56
|
-
$$pageInfo: {
|
|
57
|
-
get() {
|
|
58
|
-
const edges = ensureArray(rs);
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
startCursor: get(edges, '0.$$cursor', ''),
|
|
62
|
-
endCursor: get(edges, `${edges.length - 1}.$$cursor`, ''),
|
|
63
|
-
hasPreviousPage,
|
|
64
|
-
hasNextPage,
|
|
65
|
-
};
|
|
66
|
-
},
|
|
67
|
-
enumerable: false,
|
|
68
|
-
},
|
|
69
|
-
$$isResultSet: {
|
|
70
|
-
value: true,
|
|
71
|
-
enumerable: false,
|
|
72
|
-
},
|
|
73
|
-
toObject: {
|
|
74
|
-
get() {
|
|
75
|
-
return () => map(this, doc => Object.entries(doc).reduce((prev, [key, value]) => {
|
|
76
|
-
if (value === undefined) return prev;
|
|
77
|
-
prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
|
|
78
|
-
return prev;
|
|
79
|
-
}, {}));
|
|
80
|
-
},
|
|
81
|
-
enumerable: false,
|
|
82
|
-
configurable: true,
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
static ensureModelCache(model) {
|
|
88
|
-
if (!modelCache.has(model)) {
|
|
89
|
-
const fields = model.getFields().filter(f => f.getName() !== 'id');
|
|
90
|
-
|
|
91
|
-
const fieldDefs = fields.map(field => ({
|
|
92
|
-
field,
|
|
93
|
-
key: field.getKey(),
|
|
94
|
-
name: field.getName(),
|
|
95
|
-
isArray: field.isArray(),
|
|
96
|
-
isScalar: field.isScalar(),
|
|
97
|
-
isVirtual: field.isVirtual(),
|
|
98
|
-
isRequired: field.isRequired(),
|
|
99
|
-
isEmbedded: field.isEmbedded(),
|
|
100
|
-
modelRef: field.getModelRef(),
|
|
101
|
-
virtualField: field.getVirtualField(),
|
|
102
|
-
// deserialize: field.deserialize.bind(field),
|
|
103
|
-
// fieldResolve: field.resolve.bind(field),
|
|
104
|
-
get useDefaultResolver() { return Boolean(this.isScalar || this.isEmbedded); },
|
|
105
|
-
}));
|
|
106
|
-
|
|
107
|
-
const template = ResultSet.makeModelTemplate(model, fieldDefs);
|
|
108
|
-
|
|
109
|
-
modelCache.set(model, { template, fieldDefs });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
static makeModelTemplate(model, fieldDefs) {
|
|
114
|
-
const definition = fieldDefs.reduce((prev, fieldDef) => {
|
|
115
|
-
const { field, key, name, isArray, isVirtual, isRequired, isEmbedded, modelRef, virtualField, useDefaultResolver } = fieldDef;
|
|
116
|
-
const $name = `$${name}`;
|
|
117
|
-
|
|
118
|
-
// Deserialized field attributes
|
|
119
|
-
prev[name] = {
|
|
120
|
-
get() {
|
|
121
|
-
if (this.$$services.cache.has(name)) return this.$$services.cache.get(name);
|
|
122
|
-
let $value = field.deserialize(this.$$services.query, this.$$services.data[key]);
|
|
123
|
-
if ($value != null && isEmbedded) $value = new ResultSet(this.$$services.query.model(modelRef), $value, false);
|
|
124
|
-
this.$$services.cache.set(name, $value);
|
|
125
|
-
return $value;
|
|
126
|
-
},
|
|
127
|
-
set($value) {
|
|
128
|
-
this.$$services.cache.set(name, $value);
|
|
129
|
-
},
|
|
130
|
-
enumerable: true,
|
|
131
|
-
configurable: true, // Allows things like delete
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Fully deserialized, hydrated, and resolved field attributes
|
|
135
|
-
prev[$name] = {
|
|
136
|
-
get() {
|
|
137
|
-
return (args = {}) => {
|
|
138
|
-
// Grab deserialized value
|
|
139
|
-
const $value = this[name];
|
|
140
|
-
|
|
141
|
-
// Default resolver return immediately!
|
|
142
|
-
if (useDefaultResolver) return $value;
|
|
143
|
-
|
|
144
|
-
// Ensure where clause for DB lookup
|
|
145
|
-
args.where = args.where || {};
|
|
146
|
-
|
|
147
|
-
if (isArray) {
|
|
148
|
-
if (isVirtual) {
|
|
149
|
-
args.where[[virtualField]] = this.id; // Is where[[virtualField]] correct?
|
|
150
|
-
return this.$$services.resolver.match(modelRef).merge(args).many();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Not a "required" query + strip out nulls
|
|
154
|
-
args.where.id = $value;
|
|
155
|
-
return this.$$services.resolver.match(modelRef).merge(args).many();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (isVirtual) {
|
|
159
|
-
args.where[[virtualField]] = this.id;
|
|
160
|
-
return this.$$services.resolver.match(modelRef).merge(args).one();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return this.$$services.resolver.match(modelRef).id($value).one({ required: isRequired });
|
|
164
|
-
};
|
|
165
|
-
},
|
|
166
|
-
enumerable: false,
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// Field count (let's assume it's a Connection Type - meaning dont try with anything else)
|
|
170
|
-
prev[`${$name}:count`] = {
|
|
171
|
-
get() {
|
|
172
|
-
return (q = {}) => {
|
|
173
|
-
q.where = q.where || {};
|
|
174
|
-
if (isVirtual) q.where[virtualField] = this.id;
|
|
175
|
-
else q.where.id = this[name];
|
|
176
|
-
return this.$$services.resolver.match(modelRef).merge(q).count();
|
|
177
|
-
};
|
|
178
|
-
},
|
|
179
|
-
enumerable: false,
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
return prev;
|
|
183
|
-
}, {
|
|
184
|
-
id: {
|
|
185
|
-
get() { return this.$$services.data.id || this.$$services.data[model.idKey()]; },
|
|
186
|
-
set(id) { this.$$services.data.id = id; }, // Embedded array of documents need to set id
|
|
187
|
-
enumerable: true,
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
$id: {
|
|
191
|
-
get() { return toGUID(model.getName(), this.id); },
|
|
192
|
-
enumerable: false,
|
|
193
|
-
},
|
|
194
|
-
|
|
195
|
-
$$cursor: {
|
|
196
|
-
get() {
|
|
197
|
-
const sortPaths = keyPaths(this.$$services.sort);
|
|
198
|
-
const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(this, path) }), {});
|
|
199
|
-
const sortJSON = JSON.stringify(sortValues);
|
|
200
|
-
return Buffer.from(sortJSON).toString('base64');
|
|
201
|
-
},
|
|
202
|
-
enumerable: false,
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
$$model: {
|
|
206
|
-
value: model,
|
|
207
|
-
enumerable: false,
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
$$isResultSetItem: {
|
|
211
|
-
value: true,
|
|
212
|
-
enumerable: false,
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
$$save: {
|
|
216
|
-
get() { return input => this.$$services.resolver.match(model).id(this.id).save({ ...this, ...input }); },
|
|
217
|
-
enumerable: false,
|
|
218
|
-
},
|
|
219
|
-
|
|
220
|
-
$$remove: {
|
|
221
|
-
get() { return () => this.$$services.resolver.match(model).id(this.id).remove(); },
|
|
222
|
-
enumerable: false,
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
$$delete: {
|
|
226
|
-
get() { return () => this.$$services.resolver.match(model).id(this.id).delete(); },
|
|
227
|
-
enumerable: false,
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
toObject: {
|
|
231
|
-
get() {
|
|
232
|
-
return () => map(this, obj => Object.entries(obj).reduce((prev, [key, value]) => {
|
|
233
|
-
if (value === undefined) return prev;
|
|
234
|
-
prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
|
|
235
|
-
return prev;
|
|
236
|
-
}, {}));
|
|
237
|
-
},
|
|
238
|
-
enumerable: false,
|
|
239
|
-
configurable: true,
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
// return Object.defineProperties({}, definition);
|
|
244
|
-
return Object.create(null, definition);
|
|
245
|
-
}
|
|
246
|
-
};
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
const FS = require('fs');
|
|
2
|
-
const Glob = require('glob');
|
|
3
|
-
const Merge = require('deepmerge');
|
|
4
|
-
const { Kind, print, parse, visit } = require('graphql');
|
|
5
|
-
const { mergeASTArray, makeExecutableSchema } = require('../../service/graphql.service');
|
|
6
|
-
const { deleteKeys } = require('../../service/app.service');
|
|
7
|
-
const frameworkExt = require('../extension/framework');
|
|
8
|
-
const typeExt = require('../extension/type');
|
|
9
|
-
const apiExt = require('../extension/api');
|
|
10
|
-
const TypeDefApi = require('./TypeDefApi');
|
|
11
|
-
const Node = require('./Node');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* SchemaDecorator
|
|
15
|
-
*
|
|
16
|
-
* This class helps facilitate dynamic modification of a schema before it is passed to makeExecutableSchema(). It allows
|
|
17
|
-
* for "intelligent" merging of schemas and exposes an API wrapper for typeDefs.
|
|
18
|
-
*
|
|
19
|
-
* A "schema" is defined by the following object attributes:
|
|
20
|
-
*
|
|
21
|
-
* context <Object> - Globally shared object by all resolvers
|
|
22
|
-
* typeDefs <String|Object> - GQL String or AST Object (also supports a mixed array of both)
|
|
23
|
-
* resolvers <Object> - GraphQL resolvers
|
|
24
|
-
* schemaDirectives <Object> - GraphQL directives
|
|
25
|
-
*
|
|
26
|
-
*/
|
|
27
|
-
module.exports = class SchemaDecorator extends TypeDefApi {
|
|
28
|
-
constructor(schema) {
|
|
29
|
-
super();
|
|
30
|
-
this.schema = { context: {}, typeDefs: [], resolvers: {}, schemaDirectives: {} };
|
|
31
|
-
if (schema) this.mergeSchema(schema);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Synchronously merge a schema
|
|
36
|
-
*/
|
|
37
|
-
mergeSchema(schema, options = {}) {
|
|
38
|
-
// Here we want to normalize the schema into the shape { context, typeDefs, resolvers, schemaDirectives }
|
|
39
|
-
// We do NOT want to modify the schema object because that may cause unwanted side-effects.
|
|
40
|
-
const normalizedSchema = { ...schema };
|
|
41
|
-
if (typeof schema === 'string') normalizedSchema.typeDefs = [schema];
|
|
42
|
-
else if (schema.typeDefs && !Array.isArray(schema.typeDefs)) normalizedSchema.typeDefs = [schema.typeDefs];
|
|
43
|
-
|
|
44
|
-
// For typeDefs we want the AST so that it can be intelligently merged. Here we convert
|
|
45
|
-
// GQL strings to AST objects and also filter out anything that does not parse to AST.
|
|
46
|
-
if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) {
|
|
47
|
-
normalizedSchema.typeDefs = deleteKeys(normalizedSchema.typeDefs.map((td) => {
|
|
48
|
-
try {
|
|
49
|
-
const ast = typeof td === 'object' ? td : parse(td);
|
|
50
|
-
return ast.definitions;
|
|
51
|
-
} catch (e) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}), ['loc']).filter(Boolean).flat();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Now we're ready to merge the schema
|
|
58
|
-
const [left, right] = options.passive ? [normalizedSchema, this.schema] : [this.schema, normalizedSchema];
|
|
59
|
-
if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) this.schema.typeDefs = mergeASTArray(left.typeDefs.concat(right.typeDefs));
|
|
60
|
-
if (normalizedSchema.context) this.schema.context = Merge(left.context, right.context);
|
|
61
|
-
if (normalizedSchema.resolvers) this.schema.resolvers = Merge(left.resolvers, right.resolvers);
|
|
62
|
-
if (normalizedSchema.schemaDirectives) this.schema.schemaDirectives = Merge(left.schemaDirectives, right.schemaDirectives);
|
|
63
|
-
|
|
64
|
-
// Chaining
|
|
65
|
-
return this;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Asynchronously load files from a given glob pattern and merge each schema
|
|
70
|
-
*/
|
|
71
|
-
mergeSchemaFromFiles(globPattern, options) {
|
|
72
|
-
return new Promise((resolve, reject) => {
|
|
73
|
-
Glob(globPattern, options, (err, files) => {
|
|
74
|
-
if (err) return reject(err);
|
|
75
|
-
|
|
76
|
-
return Promise.all(files.map((file) => {
|
|
77
|
-
return new Promise((res) => {
|
|
78
|
-
if (file.endsWith('.js')) res(require(file)); // eslint-disable-line global-require,import/no-dynamic-require
|
|
79
|
-
else res(FS.readFileSync(file, 'utf8'));
|
|
80
|
-
}).then(schema => this.mergeSchema(schema, options));
|
|
81
|
-
})).then(() => resolve(this)).catch(e => reject(e));
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Traverses the current schema's typeDefs in order to keep the TypeDefApi in sync. This operation
|
|
88
|
-
* only needs to be called when typeDefs have been changed and you want to keep the data model in sync.
|
|
89
|
-
*/
|
|
90
|
-
initialize() {
|
|
91
|
-
return super.initialize(this.schema.typeDefs);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Decorate the schema with Autograph's default api/definitions
|
|
96
|
-
*/
|
|
97
|
-
decorate() {
|
|
98
|
-
this.initialize();
|
|
99
|
-
this.mergeSchema(frameworkExt(this), { passive: true });
|
|
100
|
-
this.mergeSchema(typeExt(this), { passive: true });
|
|
101
|
-
this.initialize();
|
|
102
|
-
this.mergeSchema(apiExt(this), { passive: true });
|
|
103
|
-
this.finalize();
|
|
104
|
-
return this;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* This should be called once before passing to makeExecutableSchema()
|
|
109
|
-
*/
|
|
110
|
-
finalize() {
|
|
111
|
-
const definitions = visit(this.schema.typeDefs, {
|
|
112
|
-
[Kind.FIELD_DEFINITION]: (node) => {
|
|
113
|
-
const scope = new Node(node, 'field').getDirectiveArg('field', 'gqlScope', 'crud');
|
|
114
|
-
if (scope === null || scope.indexOf('r') === -1) return null; // Delete node
|
|
115
|
-
return false; // Stop traversing this node
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
this.schema.typeDefs = { kind: Kind.DOCUMENT, definitions };
|
|
120
|
-
return this;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
makeExecutableSchema() {
|
|
124
|
-
return makeExecutableSchema(this.schema);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
getContext() {
|
|
128
|
-
return this.schema.context;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
toObject() {
|
|
132
|
-
return this.schema;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
toString() {
|
|
136
|
-
return print(this.typeDefs);
|
|
137
|
-
}
|
|
138
|
-
};
|