@coderich/autograph 0.10.0 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -3
- package/index.js +2 -8
- package/package.json +5 -7
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +32 -57
- package/src/core/Schema.js +5 -38
- package/src/data/.DS_Store +0 -0
- package/src/data/DataLoader.js +71 -32
- package/src/data/DataService.js +82 -59
- package/src/data/Field.js +59 -126
- package/src/data/Model.js +113 -105
- package/src/data/Pipeline.js +184 -0
- package/src/data/Type.js +38 -74
- package/src/driver/MongoDriver.js +27 -22
- package/src/graphql/.DS_Store +0 -0
- package/src/graphql/ast/Field.js +46 -24
- package/src/graphql/ast/Model.js +5 -16
- package/src/graphql/ast/Node.js +0 -25
- package/src/graphql/ast/Schema.js +105 -112
- package/src/graphql/extension/api.js +20 -18
- package/src/graphql/extension/framework.js +27 -33
- package/src/graphql/extension/type.js +2 -2
- package/src/query/Query.js +82 -14
- package/src/query/QueryBuilder.js +38 -30
- package/src/query/QueryBuilderTransaction.js +3 -3
- package/src/query/QueryResolver.js +77 -41
- package/src/query/QueryService.js +24 -42
- package/src/service/app.service.js +70 -13
- package/src/service/event.service.js +30 -73
- package/src/service/graphql.service.js +0 -9
- package/src/service/schema.service.js +5 -3
- package/src/core/GraphQL.js +0 -21
- 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 -205
- package/src/data/stream/DataHydrator.js +0 -58
- package/src/data/stream/ResultSet.js +0 -34
- package/src/data/stream/ResultSetItem.js +0 -158
- package/src/data/stream/ResultSetItemProxy.js +0 -161
- package/src/graphql/ast/SchemaDecorator.js +0 -141
- package/src/graphql/directive/authz.directive.js +0 -84
|
@@ -1,46 +1,32 @@
|
|
|
1
1
|
const QueryService = require('../query/QueryService');
|
|
2
2
|
const EventEmitter = require('../core/EventEmitter');
|
|
3
|
-
const {
|
|
3
|
+
const { ucFirst } = require('./app.service');
|
|
4
4
|
|
|
5
5
|
// Event emitters
|
|
6
6
|
const eventEmitter = new EventEmitter().setMaxListeners(100);
|
|
7
|
-
const internalEmitter = new EventEmitter().setMaxListeners(100);
|
|
8
7
|
const systemEvent = new EventEmitter().setMaxListeners(100).on('system', async (event, next) => {
|
|
9
8
|
const { type, data } = event;
|
|
10
|
-
await internalEmitter.emit(type, data);
|
|
11
9
|
next(await eventEmitter.emit(type, data)); // Return result from user-defined middleware
|
|
12
10
|
});
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (name !== 'Setup' && name !== 'Response') {
|
|
21
|
-
const { method, query } = mixed;
|
|
22
|
-
const { resolver, model, meta, doc, id, input, sort, merged, native, root, crud } = query.toObject();
|
|
12
|
+
const makeEvent = (mixed) => {
|
|
13
|
+
const { query } = mixed;
|
|
14
|
+
const event = query.toObject();
|
|
15
|
+
event.query = query;
|
|
16
|
+
return event;
|
|
17
|
+
};
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
method,
|
|
29
|
-
crud,
|
|
30
|
-
model,
|
|
31
|
-
meta,
|
|
32
|
-
id,
|
|
33
|
-
input,
|
|
34
|
-
query,
|
|
35
|
-
doc,
|
|
36
|
-
merged,
|
|
37
|
-
root,
|
|
38
|
-
};
|
|
19
|
+
const makeMiddleware = () => {
|
|
20
|
+
return (mixed) => {
|
|
21
|
+
const { query } = mixed;
|
|
22
|
+
const { model, native, sort, match, batch } = query.toObject();
|
|
39
23
|
|
|
40
|
-
|
|
24
|
+
return new Promise(async (resolve) => {
|
|
41
25
|
if (!native) {
|
|
42
|
-
const
|
|
43
|
-
|
|
26
|
+
const whereShape = model.getShape('create', 'where');
|
|
27
|
+
const $where = batch ? match : await QueryService.resolveWhereClause(query);
|
|
28
|
+
const $$where = model.shapeObject(whereShape, $where, query);
|
|
29
|
+
query.match($$where);
|
|
44
30
|
}
|
|
45
31
|
|
|
46
32
|
if (sort) {
|
|
@@ -49,11 +35,23 @@ exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
|
49
35
|
|
|
50
36
|
resolve();
|
|
51
37
|
});
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
//
|
|
42
|
+
exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
43
|
+
let event = mixed;
|
|
44
|
+
let middleware = () => Promise.resolve();
|
|
45
|
+
const type = ucFirst(name);
|
|
46
|
+
|
|
47
|
+
if (name !== 'Response') {
|
|
48
|
+
event = makeEvent(mixed);
|
|
49
|
+
middleware = makeMiddleware();
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
return systemEvent.emit('system', { type: `pre${type}`, data: event }).then((result) => {
|
|
52
|
+
return systemEvent.emit('system', { type: `pre${type}`, data: event }).then(async (result) => {
|
|
55
53
|
if (result !== undefined) return result; // Allowing middleware to dictate result
|
|
56
|
-
return middleware().then(thunk);
|
|
54
|
+
return middleware(mixed).then(thunk);
|
|
57
55
|
}).then((result) => {
|
|
58
56
|
event.result = result;
|
|
59
57
|
if (event.crud === 'create') event.doc = event.query.toObject().doc;
|
|
@@ -66,44 +64,3 @@ exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
|
66
64
|
};
|
|
67
65
|
|
|
68
66
|
exports.eventEmitter = eventEmitter;
|
|
69
|
-
exports.internalEmitter = internalEmitter;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Hook into the pre event only!
|
|
74
|
-
*
|
|
75
|
-
* Kick off system events for embedded fields
|
|
76
|
-
*/
|
|
77
|
-
const eventHandler = (event) => {
|
|
78
|
-
const { model, input, method, doc = input, query } = event;
|
|
79
|
-
|
|
80
|
-
return Promise.all(model.getEmbeddedFields().map((field) => {
|
|
81
|
-
return new Promise((resolve, reject) => {
|
|
82
|
-
if (Object.prototype.hasOwnProperty.call(input || {}, field.getName())) {
|
|
83
|
-
let i = 0;
|
|
84
|
-
const value = input[field.getName()];
|
|
85
|
-
const values = ensureArray(value).filter(el => el != null);
|
|
86
|
-
const newModel = field.getModelRef();
|
|
87
|
-
|
|
88
|
-
if (values.length) {
|
|
89
|
-
values.forEach((val) => {
|
|
90
|
-
const clone = query.clone().model(newModel).input(val).doc(doc);
|
|
91
|
-
exports.createSystemEvent('Mutation', { method, query: clone }, () => {
|
|
92
|
-
if (++i >= values.length) resolve();
|
|
93
|
-
}).catch(e => reject(e));
|
|
94
|
-
// const newEvent = { parent: doc, key: `${method}${field}`, method, model: newModel, resolver, query: new Query(resolver, newModel, { meta }), input: val };
|
|
95
|
-
// exports.createSystemEvent('Mutation', newEvent, () => {
|
|
96
|
-
// if (++i >= values.length) resolve();
|
|
97
|
-
// }).catch(e => reject(e));
|
|
98
|
-
});
|
|
99
|
-
} else {
|
|
100
|
-
resolve();
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
resolve();
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
}));
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
internalEmitter.on('preMutation', async (event, next) => eventHandler(event).then(next)); // Only preMutation!
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const { get } = require('lodash');
|
|
2
2
|
const { Kind, parse, print } = require('graphql');
|
|
3
|
-
const { validate } = require('graphql/validation');
|
|
4
|
-
const { makeExecutableSchema } = require('@graphql-tools/schema');
|
|
5
3
|
|
|
6
4
|
//
|
|
7
5
|
const mergePairs = [
|
|
@@ -81,13 +79,6 @@ exports.mergeASTArray = (arr) => {
|
|
|
81
79
|
}, []).filter(el => !el.deleteFlag);
|
|
82
80
|
};
|
|
83
81
|
|
|
84
|
-
exports.validateSchema = (ast) => {
|
|
85
|
-
const errs = validate(makeExecutableSchema(ast), ast.typeDefs).filter(({ message }) => message.indexOf('not executable') === -1).map(({ message }) => message);
|
|
86
|
-
if (errs.length) throw new Error(errs.join('\n'));
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
exports.makeExecutableSchema = makeExecutableSchema;
|
|
90
|
-
|
|
91
82
|
exports.toAST = (a) => {
|
|
92
83
|
if (typeof a === 'string') return parse(a);
|
|
93
84
|
if (Array.isArray(a)) return parse(a.map(e => exports.toGQL(e)).join('\n\n'));
|
|
@@ -54,11 +54,13 @@ exports.getSchemaData = (schema) => {
|
|
|
54
54
|
exports.identifyOnDeletes = (models, parentModel) => {
|
|
55
55
|
return models.reduce((prev, model) => {
|
|
56
56
|
model.getOnDeleteFields().forEach((field) => {
|
|
57
|
-
|
|
57
|
+
const { modelRef, isArray } = field.toObject();
|
|
58
|
+
|
|
59
|
+
if (`${modelRef}` === `${parentModel}`) {
|
|
58
60
|
if (model.isEntity()) {
|
|
59
|
-
prev.push({ model, field, isArray
|
|
61
|
+
prev.push({ model, field, isArray, op: field.getOnDelete() });
|
|
60
62
|
} else {
|
|
61
|
-
prev.push(...exports.identifyOnDeletes(models, model).map(od => Object.assign(od, { fieldRef: field, isArray
|
|
63
|
+
prev.push(...exports.identifyOnDeletes(models, model).map(od => Object.assign(od, { fieldRef: field, isArray, op: field.getOnDelete() })));
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
});
|
package/src/core/GraphQL.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const { graphql } = require('graphql');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GraphQL.
|
|
5
|
-
*
|
|
6
|
-
* This is a wrapper class to the underlying GraphQL Executable Schema.
|
|
7
|
-
* It can be useful for testing and/or exercising the API as an outside caller would.
|
|
8
|
-
*
|
|
9
|
-
* Reference: https://github.com/graphql/graphql-js/blob/master/src/graphql.js#L32-L33
|
|
10
|
-
*/
|
|
11
|
-
module.exports = class GraphQL {
|
|
12
|
-
constructor(schema, resolver) {
|
|
13
|
-
this.schema = schema.makeExecutableSchema();
|
|
14
|
-
this.contextValue = resolver.getContext();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
exec(source, variableValues) {
|
|
18
|
-
const { schema, contextValue = {} } = this;
|
|
19
|
-
return graphql({ schema, source, variableValues, contextValue });
|
|
20
|
-
}
|
|
21
|
-
};
|
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,205 +0,0 @@
|
|
|
1
|
-
const { get } = require('lodash');
|
|
2
|
-
const DataService = require('./DataService');
|
|
3
|
-
const { map, ensureArray, keyPaths, mapPromise, toGUID, hashObject } = require('../service/app.service');
|
|
4
|
-
|
|
5
|
-
module.exports = class ResultSet {
|
|
6
|
-
constructor(query, data, adjustForPagination = true) {
|
|
7
|
-
if (data == null) return data;
|
|
8
|
-
const { resolver, model, sort, first, after, last, before } = query.toObject();
|
|
9
|
-
const fields = model.getFields().filter(f => f.getName() !== 'id');
|
|
10
|
-
|
|
11
|
-
const rs = map(data, (doc) => {
|
|
12
|
-
if (doc == null || typeof doc !== 'object') return doc;
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
const cache = new Map();
|
|
16
|
-
|
|
17
|
-
const definition = fields.reduce((prev, field) => {
|
|
18
|
-
const key = field.getKey();
|
|
19
|
-
const name = field.getName();
|
|
20
|
-
const $name = `$${name}`;
|
|
21
|
-
const value = doc[key];
|
|
22
|
-
|
|
23
|
-
// Field attributes
|
|
24
|
-
prev[name] = {
|
|
25
|
-
get() {
|
|
26
|
-
if (cache.has(name)) return cache.get(name);
|
|
27
|
-
let $value = field.deserialize(query, value);
|
|
28
|
-
$value = $value != null && field.isEmbedded() ? new ResultSet(query.model(field.getModelRef()), $value, false) : $value;
|
|
29
|
-
cache.set(name, $value);
|
|
30
|
-
return $value;
|
|
31
|
-
},
|
|
32
|
-
set($value) {
|
|
33
|
-
cache.set(name, $value);
|
|
34
|
-
},
|
|
35
|
-
enumerable: true,
|
|
36
|
-
configurable: true, // Allows things like delete
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// Hydrated field attributes
|
|
40
|
-
prev[`$${name}`] = {
|
|
41
|
-
get() {
|
|
42
|
-
return (args = {}) => {
|
|
43
|
-
// Ensure where clause
|
|
44
|
-
args.where = args.where || {};
|
|
45
|
-
|
|
46
|
-
// Cache
|
|
47
|
-
const cacheKey = `${$name}-${hashObject(args)}`;
|
|
48
|
-
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
49
|
-
|
|
50
|
-
const promise = new Promise((resolve, reject) => {
|
|
51
|
-
(() => {
|
|
52
|
-
const $value = this[name];
|
|
53
|
-
|
|
54
|
-
if (field.isScalar() || field.isEmbedded()) return Promise.resolve($value);
|
|
55
|
-
|
|
56
|
-
const modelRef = field.getModelRef();
|
|
57
|
-
|
|
58
|
-
if (field.isArray()) {
|
|
59
|
-
if (field.isVirtual()) {
|
|
60
|
-
args.where[[field.getVirtualField()]] = this.id; // Is where[[field.getVirtualField()]] correct?
|
|
61
|
-
return resolver.match(modelRef).merge(args).many();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Not a "required" query + strip out nulls
|
|
65
|
-
args.where.id = $value;
|
|
66
|
-
return resolver.match(modelRef).merge(args).many();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (field.isVirtual()) {
|
|
70
|
-
args.where[[field.getVirtualField()]] = this.id;
|
|
71
|
-
return resolver.match(modelRef).merge(args).one();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return resolver.match(modelRef).id($value).one({ required: field.isRequired() });
|
|
75
|
-
})().then((results) => {
|
|
76
|
-
if (results == null) return field.resolve(query, results); // Allow field to determine
|
|
77
|
-
return mapPromise(results, result => field.resolve(query, result)).then(() => results); // Resolve the inside fields but still return "results"!!!!
|
|
78
|
-
}).then((resolved) => {
|
|
79
|
-
resolve(resolved);
|
|
80
|
-
}).catch((e) => {
|
|
81
|
-
reject(e);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
cache.set(cacheKey, promise);
|
|
86
|
-
return promise;
|
|
87
|
-
};
|
|
88
|
-
},
|
|
89
|
-
enumerable: false,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// Field count (let's assume it's a Connection Type - meaning dont try with anything else)
|
|
93
|
-
prev[`$${name}:count`] = {
|
|
94
|
-
get() {
|
|
95
|
-
return (q = {}) => {
|
|
96
|
-
q.where = q.where || {};
|
|
97
|
-
if (field.isVirtual()) q.where[field.getVirtualField()] = this.id;
|
|
98
|
-
else q.where.id = this[name];
|
|
99
|
-
return resolver.match(field.getModelRef()).merge(q).count();
|
|
100
|
-
};
|
|
101
|
-
},
|
|
102
|
-
enumerable: false,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
return prev;
|
|
106
|
-
}, {
|
|
107
|
-
id: {
|
|
108
|
-
get() { return doc.id || doc[model.idKey()]; },
|
|
109
|
-
set(id) { doc.id = id; }, // Embedded array of documents need to set id
|
|
110
|
-
enumerable: true,
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
$id: {
|
|
114
|
-
get() { return toGUID(model.getName(), this.id); },
|
|
115
|
-
enumerable: false,
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
$$cursor: {
|
|
119
|
-
get() {
|
|
120
|
-
const sortPaths = keyPaths(sort);
|
|
121
|
-
const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(this, path) }), {});
|
|
122
|
-
const sortJSON = JSON.stringify(sortValues);
|
|
123
|
-
return Buffer.from(sortJSON).toString('base64');
|
|
124
|
-
},
|
|
125
|
-
enumerable: false,
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
$$model: {
|
|
129
|
-
value: model,
|
|
130
|
-
enumerable: false,
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
$$isResultSetItem: {
|
|
134
|
-
value: true,
|
|
135
|
-
enumerable: false,
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
$$save: {
|
|
139
|
-
get() { return input => resolver.match(model).id(this.id).save({ ...this, ...input }); },
|
|
140
|
-
enumerable: false,
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
$$remove: {
|
|
144
|
-
get() { return () => resolver.match(model).id(this.id).remove(); },
|
|
145
|
-
enumerable: false,
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
$$delete: {
|
|
149
|
-
get() { return () => resolver.match(model).id(this.id).delete(); },
|
|
150
|
-
enumerable: false,
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
toObject: {
|
|
154
|
-
get() {
|
|
155
|
-
return () => map(this, obj => Object.entries(obj).reduce((prev, [key, value]) => {
|
|
156
|
-
if (value === undefined) return prev;
|
|
157
|
-
prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
|
|
158
|
-
return prev;
|
|
159
|
-
}, {}));
|
|
160
|
-
},
|
|
161
|
-
enumerable: false,
|
|
162
|
-
configurable: true,
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// Create and return ResultSetItem
|
|
167
|
-
return Object.defineProperties({}, definition);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
let hasNextPage = false;
|
|
171
|
-
let hasPreviousPage = false;
|
|
172
|
-
if (adjustForPagination && rs.length) (({ hasPreviousPage, hasNextPage } = DataService.paginateResultSet(rs, first, after, last, before)));
|
|
173
|
-
|
|
174
|
-
return Object.defineProperties(rs, {
|
|
175
|
-
$$pageInfo: {
|
|
176
|
-
get() {
|
|
177
|
-
const edges = ensureArray(rs);
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
startCursor: get(edges, '0.$$cursor', ''),
|
|
181
|
-
endCursor: get(edges, `${edges.length - 1}.$$cursor`, ''),
|
|
182
|
-
hasPreviousPage,
|
|
183
|
-
hasNextPage,
|
|
184
|
-
};
|
|
185
|
-
},
|
|
186
|
-
enumerable: false,
|
|
187
|
-
},
|
|
188
|
-
$$isResultSet: {
|
|
189
|
-
value: true,
|
|
190
|
-
enumerable: false,
|
|
191
|
-
},
|
|
192
|
-
toObject: {
|
|
193
|
-
get() {
|
|
194
|
-
return () => map(this, doc => Object.entries(doc).reduce((prev, [key, value]) => {
|
|
195
|
-
if (value === undefined) return prev;
|
|
196
|
-
prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
|
|
197
|
-
return prev;
|
|
198
|
-
}, {}));
|
|
199
|
-
},
|
|
200
|
-
enumerable: false,
|
|
201
|
-
configurable: true,
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
};
|