@coderich/autograph 0.9.15 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/index.js +2 -6
- package/package.json +13 -10
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +43 -59
- package/src/core/Schema.js +3 -36
- 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 +52 -27
- 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 +107 -111
- 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 -9
- package/src/service/decorator.service.js +21 -288
- package/src/service/event.service.js +5 -79
- package/src/service/graphql.service.js +1 -1
- 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/data/DataService.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
const { remove } = require('lodash');
|
|
2
|
-
const
|
|
3
|
-
const { isPlainObject, objectContaining, mergeDeep, map } = require('../service/app.service');
|
|
1
|
+
const { get, remove } = require('lodash');
|
|
2
|
+
const { map, isPlainObject, objectContaining, mergeDeep, ensureArray, keyPaths } = require('../service/app.service');
|
|
4
3
|
|
|
5
|
-
exports.paginateResultSet = (rs,
|
|
4
|
+
exports.paginateResultSet = (rs, query) => {
|
|
5
|
+
const { first, after, last, before, sort } = query.toObject();
|
|
6
|
+
const sortPaths = keyPaths(sort);
|
|
7
|
+
const limiter = first || last;
|
|
6
8
|
let hasNextPage = false;
|
|
7
9
|
let hasPreviousPage = false;
|
|
8
|
-
|
|
10
|
+
|
|
11
|
+
// Add $$cursor data
|
|
12
|
+
map(rs, (doc) => {
|
|
13
|
+
const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(doc, path) }), {});
|
|
14
|
+
const sortJSON = JSON.stringify(sortValues);
|
|
15
|
+
doc.$$cursor = Buffer.from(sortJSON).toString('base64');
|
|
16
|
+
});
|
|
9
17
|
|
|
10
18
|
// First try to take off the "bookends" ($gte | $lte)
|
|
11
19
|
if (rs.length && rs[0].$$cursor === after) {
|
|
@@ -34,64 +42,57 @@ exports.paginateResultSet = (rs, first, after, last, before) => {
|
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
// Add $$pageInfo data (hidden)
|
|
46
|
+
return Object.defineProperties(rs, {
|
|
47
|
+
$$pageInfo: {
|
|
48
|
+
get() {
|
|
49
|
+
return {
|
|
50
|
+
startCursor: get(rs, '0.$$cursor', ''),
|
|
51
|
+
endCursor: get(rs, `${rs.length - 1}.$$cursor`, ''),
|
|
52
|
+
hasPreviousPage,
|
|
53
|
+
hasNextPage,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
enumerable: false,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
38
59
|
};
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
* @param from <Array>
|
|
42
|
-
* @param to <Array>
|
|
43
|
-
*/
|
|
44
|
-
exports.spliceEmbeddedArray = (query, doc, key, from, to) => {
|
|
45
|
-
const { model } = query.toObject();
|
|
46
|
-
const field = model.getField(key);
|
|
47
|
-
const modelRef = field.getModelRef();
|
|
61
|
+
exports.spliceEmbeddedArray = (array, from, to) => {
|
|
48
62
|
const op = from && to ? 'edit' : (from ? 'pull' : 'push'); // eslint-disable-line no-nested-ternary
|
|
49
|
-
const promises = [];
|
|
50
|
-
|
|
51
|
-
// Can only splice arrays
|
|
52
|
-
if (!field || !field.isArray()) return Promise.reject(Boom.badRequest(`Cannot splice field '${key}'`));
|
|
53
|
-
|
|
54
|
-
// We have to deserialize because this normalizes the data (casting etc)
|
|
55
|
-
let $to = model.deserialize(query, { [key]: to })[key] || to;
|
|
56
|
-
const $from = model.deserialize(query, { [key]: from })[key] || from;
|
|
57
|
-
|
|
58
|
-
// If it's embedded we need to append default/create fields for insertion
|
|
59
|
-
if ($to && field.isEmbedded()) $to = $to.map(el => modelRef.appendDefaultFields(query, modelRef.appendCreateFields(el, true)));
|
|
60
63
|
|
|
61
64
|
// Convenience so the user does not have to explicity type out the same value over and over to replace
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
// Traverse the document till we find the segment to modify (in place)
|
|
65
|
-
return key.split('.').reduce((prev, segment, i, arr) => {
|
|
66
|
-
if (prev == null) return prev;
|
|
65
|
+
if (from && from.length > 1 && to && to.length === 1) to = Array.from(from).fill(to[0]);
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
67
|
+
switch (op) {
|
|
68
|
+
case 'edit': {
|
|
69
|
+
array.forEach((el, j) => {
|
|
70
|
+
ensureArray(from).forEach((val, k) => {
|
|
71
|
+
if (objectContaining(el, val)) array[j] = isPlainObject(el) ? mergeDeep(el, ensureArray(to)[k]) : ensureArray(to)[k];
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
// case 'edit': {
|
|
77
|
+
// ensureArray(from).forEach((f, i) => {
|
|
78
|
+
// const t = ensureArray(to)[i];
|
|
79
|
+
// const indexes = array.map((el, j) => (el === f ? j : -1)).filter(index => index !== -1);
|
|
80
|
+
// indexes.forEach(index => (array[index] = t));
|
|
81
|
+
// });
|
|
82
|
+
// break;
|
|
83
|
+
// }
|
|
84
|
+
case 'push': {
|
|
85
|
+
array.push(...to);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'pull': {
|
|
89
|
+
remove(array, el => from.find(val => objectContaining(el, val)));
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
default: {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
}, doc);
|
|
97
|
+
return array;
|
|
97
98
|
};
|
package/src/data/Field.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
const { isEmpty } = require('lodash');
|
|
1
2
|
const Type = require('./Type');
|
|
2
3
|
const Field = require('../graphql/ast/Field');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
4
|
+
const Boom = require('../core/Boom');
|
|
5
|
+
const Pipeline = require('./Pipeline');
|
|
6
|
+
const { isPlainObject, ensureArray } = require('../service/app.service');
|
|
6
7
|
|
|
7
8
|
module.exports = class extends Field {
|
|
8
9
|
constructor(model, field) {
|
|
@@ -11,115 +12,89 @@ module.exports = class extends Field {
|
|
|
11
12
|
this.model = model;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
getRules() {
|
|
21
|
-
const rules = [];
|
|
22
|
-
|
|
23
|
-
Object.entries(this.getDirectiveArgs('field', {})).forEach(([key, value]) => {
|
|
24
|
-
if (!Array.isArray(value)) value = [value];
|
|
25
|
-
if (key === 'enforce') rules.push(...value.map(r => Rule.getInstances()[r]));
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (this.isRequired() && this.isPersistable() && !this.isVirtual()) rules.push(Rule.required());
|
|
15
|
+
getStructures() {
|
|
16
|
+
// Grab structures from the underlying type
|
|
17
|
+
const structures = this.type.getStructures();
|
|
18
|
+
const { isRequired, isPersistable, isVirtual, isPrimaryKeyId, isIdField } = this.props;
|
|
29
19
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
getTransformers() {
|
|
34
|
-
const transformers = [];
|
|
35
|
-
|
|
36
|
-
Object.entries(this.getDirectiveArgs('field', {})).forEach(([key, value]) => {
|
|
20
|
+
// Structures defined on the field
|
|
21
|
+
const $structures = Object.entries(this.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
|
|
37
22
|
if (!Array.isArray(value)) value = [value];
|
|
38
|
-
if (key === '
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
23
|
+
if (key === 'instruct') prev.instructs.unshift(...value.map(t => Pipeline[t]));
|
|
24
|
+
if (key === 'restruct') prev.restructs.unshift(...value.map(t => Pipeline[t]));
|
|
25
|
+
if (key === 'destruct') prev.destructs.unshift(...value.map(t => Pipeline[t]));
|
|
26
|
+
if (key === 'construct') prev.constructs.unshift(...value.map(t => Pipeline[t]));
|
|
27
|
+
if (key === 'serialize') prev.serializers.unshift(...value.map(t => Pipeline[t]));
|
|
28
|
+
if (key === 'deserialize') prev.deserializers.unshift(...value.map(t => Pipeline[t]));
|
|
29
|
+
if (key === 'transform') prev.transformers.unshift(...value.map(t => Pipeline[t]));
|
|
30
|
+
return prev;
|
|
31
|
+
}, structures);
|
|
32
|
+
|
|
33
|
+
// IDs (first - shift)
|
|
34
|
+
if (isPrimaryKeyId) $structures.serializers.unshift(Pipeline.idKey);
|
|
35
|
+
if (isIdField) $structures.$serializers.unshift(Pipeline.idField);
|
|
36
|
+
|
|
37
|
+
// Required (last - push)
|
|
38
|
+
if (isRequired && isPersistable && !isVirtual) $structures.serializers.push(Pipeline.required);
|
|
39
|
+
|
|
40
|
+
return $structures;
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!Array.isArray(value)) value = [value];
|
|
49
|
-
if (key === 'serialize') transformers.push(...value.map(t => Transformer.getInstances()[t]));
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return transformers.concat(this.type.getSerializers());
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
getDeserializers() {
|
|
56
|
-
const transformers = [];
|
|
57
|
-
|
|
58
|
-
Object.entries(this.getDirectiveArgs('field', {})).forEach(([key, value]) => {
|
|
59
|
-
if (!Array.isArray(value)) value = [value];
|
|
60
|
-
if (key === 'deserialize') transformers.push(...value.map(t => Transformer.getInstances()[t]));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
return transformers.concat(this.type.getDeserializers());
|
|
64
|
-
}
|
|
43
|
+
async validate(query, value) {
|
|
44
|
+
if (value == null) return value;
|
|
45
|
+
const { resolver } = query.toObject();
|
|
46
|
+
const { type, modelRef, isEmbedded } = this.props;
|
|
65
47
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
48
|
+
if (modelRef && !isEmbedded) {
|
|
49
|
+
const ids = Array.from(new Set(ensureArray(value).map(v => `${v}`)));
|
|
50
|
+
await resolver.match(type).where({ id: ids }).count().then((count) => {
|
|
51
|
+
// if (type === 'Category') console.log(ids, count);
|
|
52
|
+
if (count !== ids.length) throw Boom.notFound(`${type} Not Found`);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
69
55
|
|
|
70
|
-
if (modelRef &&
|
|
56
|
+
if (modelRef && isPlainObject(ensureArray(value)[0])) return modelRef.validate(query, value); // Model delegation
|
|
71
57
|
|
|
72
|
-
return
|
|
73
|
-
return rule(this, value, query);
|
|
74
|
-
})).then((res) => {
|
|
75
|
-
if (modelRef && isPlainObject(ensureArray(value)[0])) return modelRef.validate(query, value); // Model delegation
|
|
76
|
-
return res;
|
|
77
|
-
});
|
|
58
|
+
return value;
|
|
78
59
|
}
|
|
79
60
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
transform(query, value, serdes = (() => { throw new Error('No Sir Sir SerDes!'); })()) {
|
|
84
|
-
// Determine value
|
|
85
|
-
const $value = serdes === 'serialize' ? this.resolveBoundValue(query, value) : uvl(value, this.getDefaultValue());
|
|
61
|
+
resolve(resolver, doc, args = {}) {
|
|
62
|
+
const { name, isArray, isScalar, isVirtual, isRequired, isEmbedded, modelRef, virtualField } = this.props;
|
|
63
|
+
const value = doc[name];
|
|
86
64
|
|
|
87
|
-
//
|
|
88
|
-
|
|
65
|
+
// Default resolver return immediately!
|
|
66
|
+
if (isScalar || isEmbedded) return value;
|
|
89
67
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
return transformer(this, prev, query);
|
|
93
|
-
}, this.cast($value));
|
|
94
|
-
}
|
|
68
|
+
// Ensure where clause for DB lookup
|
|
69
|
+
args.where = args.where || {};
|
|
95
70
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
71
|
+
if (isArray) {
|
|
72
|
+
if (isVirtual) {
|
|
73
|
+
if (isEmpty(args.where)) args.batch = `${virtualField}`;
|
|
74
|
+
args.where[virtualField] = doc.id;
|
|
75
|
+
return resolver.match(modelRef).merge(args).many();
|
|
76
|
+
}
|
|
102
77
|
|
|
103
|
-
|
|
104
|
-
|
|
78
|
+
// Not a "required" query + strip out nulls
|
|
79
|
+
if (isEmpty(args.where)) args.batch = 'id';
|
|
80
|
+
args.where.id = value;
|
|
81
|
+
return resolver.match(modelRef).merge(args).many();
|
|
82
|
+
}
|
|
105
83
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
84
|
+
if (isVirtual) {
|
|
85
|
+
if (isEmpty(args.where)) args.batch = `${virtualField}`;
|
|
86
|
+
args.where[virtualField] = doc.id;
|
|
87
|
+
return resolver.match(modelRef).merge(args).one();
|
|
88
|
+
}
|
|
111
89
|
|
|
112
|
-
|
|
113
|
-
return this.transform(query, value, 'deserialize');
|
|
90
|
+
return resolver.match(modelRef).id(value).one({ required: isRequired });
|
|
114
91
|
}
|
|
115
92
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return
|
|
122
|
-
return transformer(this, prev, query);
|
|
123
|
-
}, this.cast(value));
|
|
93
|
+
count(resolver, doc, args = {}) {
|
|
94
|
+
const { name, isVirtual, modelRef, virtualField } = this.props;
|
|
95
|
+
args.where = args.where || {};
|
|
96
|
+
if (isVirtual) args.where[virtualField] = doc.id;
|
|
97
|
+
else args.where.id = doc[name];
|
|
98
|
+
return resolver.match(modelRef).merge(args).count();
|
|
124
99
|
}
|
|
125
100
|
};
|
package/src/data/Model.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
const Stream = require('stream');
|
|
1
2
|
const Field = require('./Field');
|
|
3
|
+
const Pipeline = require('./Pipeline');
|
|
2
4
|
const Model = require('../graphql/ast/Model');
|
|
3
|
-
const {
|
|
5
|
+
const { paginateResultSet } = require('./DataService');
|
|
6
|
+
const { eventEmitter } = require('../service/event.service');
|
|
7
|
+
const { map, seek, deseek, ensureArray } = require('../service/app.service');
|
|
4
8
|
|
|
5
9
|
module.exports = class extends Model {
|
|
6
10
|
constructor(schema, model, driver) {
|
|
@@ -8,6 +12,7 @@ module.exports = class extends Model {
|
|
|
8
12
|
this.driver = driver;
|
|
9
13
|
this.fields = super.getFields().map(field => new Field(this, field));
|
|
10
14
|
this.namedQueries = {};
|
|
15
|
+
this.shapesCache = new Map();
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
raw() {
|
|
@@ -43,140 +48,117 @@ module.exports = class extends Model {
|
|
|
43
48
|
return this.referentials;
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
validate(query, data) {
|
|
52
|
+
const { flags = {} } = query.toObject();
|
|
53
|
+
const { validate = true } = flags;
|
|
54
|
+
|
|
55
|
+
if (!validate) return Promise.resolve();
|
|
56
|
+
|
|
57
|
+
return Promise.all(this.getFields().map((field) => {
|
|
58
|
+
return Promise.all(ensureArray(map(data, (obj) => {
|
|
59
|
+
if (obj == null) return Promise.resolve();
|
|
60
|
+
return field.validate(query, obj[field.getKey()]);
|
|
61
|
+
})));
|
|
62
|
+
})).then(() => {
|
|
63
|
+
return eventEmitter.emit('validate', query);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
/**
|
|
47
|
-
*
|
|
48
|
-
* while ensuring that all defaulted values are set appropriately
|
|
68
|
+
* Convenience method to deserialize data from a data source (such as a database)
|
|
49
69
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
deserialize(mixed, query) {
|
|
71
|
+
const { flags = {} } = query.toObject();
|
|
72
|
+
const { pipeline = true } = flags;
|
|
73
|
+
const shape = this.getShape();
|
|
74
|
+
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
if (!(mixed instanceof Stream)) {
|
|
77
|
+
resolve(pipeline ? this.shapeObject(shape, mixed, query) : mixed);
|
|
78
|
+
} else {
|
|
79
|
+
const results = [];
|
|
80
|
+
mixed.on('data', (data) => { results.push(pipeline ? this.shapeObject(shape, data, query) : data); });
|
|
81
|
+
mixed.on('end', () => { resolve(results); });
|
|
82
|
+
mixed.on('error', reject);
|
|
83
|
+
}
|
|
84
|
+
}).then((results) => {
|
|
85
|
+
return results.length && pipeline ? paginateResultSet(results, query) : results;
|
|
60
86
|
});
|
|
61
|
-
|
|
62
|
-
return input;
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
input.updatedAt = new Date();
|
|
89
|
+
getShape(crud = 'read', target = 'doc', paths = []) {
|
|
90
|
+
const cacheKey = `${crud}:${target}`;
|
|
91
|
+
if (this.shapesCache.has(cacheKey)) return this.shapesCache.get(cacheKey);
|
|
69
92
|
|
|
70
|
-
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
const serdes = crud === 'read' ? 'deserialize' : 'serialize';
|
|
94
|
+
const fields = serdes === 'deserialize' ? this.getSelectFields() : this.getPersistableFields();
|
|
95
|
+
const crudMap = { create: ['constructs'], update: ['restructs'], delete: ['destructs'], remove: ['destructs'] };
|
|
96
|
+
const crudKeys = crudMap[crud] || [];
|
|
74
97
|
|
|
75
|
-
|
|
76
|
-
|
|
98
|
+
const targetMap = {
|
|
99
|
+
doc: ['defaultValue', 'ensureArrayValue', 'castValue', ...crudKeys, `$${serdes}rs`, 'instructs', 'transformers', `${serdes}rs`],
|
|
100
|
+
where: ['castValue', `$${serdes}rs`, 'instructs'],
|
|
101
|
+
};
|
|
77
102
|
|
|
78
|
-
|
|
79
|
-
this.getDefaultedFields().filter(field => field.isPersistable()).forEach((field) => {
|
|
80
|
-
input[field] = field.resolveBoundValue(query, input[field]);
|
|
81
|
-
});
|
|
103
|
+
const structureKeys = targetMap[target] || ['castValue'];
|
|
82
104
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
// Create shape, recursive
|
|
106
|
+
const shape = fields.map((field) => {
|
|
107
|
+
const structures = field.getStructures();
|
|
108
|
+
const [key, name, type, isArray] = [field.getKey(), field.getName(), field.getType(), field.isArray(), field.isIdField()];
|
|
109
|
+
const [from, to] = serdes === 'serialize' ? [name, key] : [key, name];
|
|
110
|
+
const path = paths.concat(to);
|
|
111
|
+
const subShape = field.isEmbedded() ? field.getModelRef().getShape(crud, target, path) : null;
|
|
87
112
|
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
structures.castValue = Pipeline.castValue;
|
|
114
|
+
structures.defaultValue = Pipeline.defaultValue;
|
|
90
115
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return this.transform(query, data, 'serialize', minimal);
|
|
96
|
-
}
|
|
116
|
+
structures.ensureArrayValue = ({ value }) => (value != null && isArray && !Array.isArray(value) ? [value] : value);
|
|
117
|
+
const transformers = structureKeys.reduce((prev, struct) => prev.concat(structures[struct]), []).filter(Boolean);
|
|
118
|
+
return { field, path, from, to, type, isArray, transformers, shape: subShape };
|
|
119
|
+
});
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return this.transform(query, data, 'deserialize');
|
|
103
|
-
}
|
|
121
|
+
// Adding useful shape info
|
|
122
|
+
shape.crud = crud;
|
|
123
|
+
shape.model = this;
|
|
124
|
+
shape.serdes = serdes;
|
|
104
125
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
transform(query, data, serdes = (() => { throw new Error('No Sir Sir SerDes!'); })(), minimal = false) {
|
|
109
|
-
// Serialize always gets the bound values
|
|
110
|
-
const appendFields = (serdes === 'serialize' ? [...this.getBoundValueFields()] : []);
|
|
111
|
-
|
|
112
|
-
// Certain cases do not want custom serdes or defaults
|
|
113
|
-
if (!minimal) appendFields.push(...this[`get${ucFirst(serdes)}Fields`](), ...this.getDefaultFields());
|
|
114
|
-
|
|
115
|
-
// Transform all the data
|
|
116
|
-
return map(data, (doc) => {
|
|
117
|
-
// We want the appendFields + those in the data, deduped
|
|
118
|
-
const fields = [...new Set(appendFields.concat(Object.keys(doc).map(k => this.getField(k))))].filter(Boolean);
|
|
119
|
-
|
|
120
|
-
// Loop through the fields and delegate (renaming keys appropriately)
|
|
121
|
-
return fields.reduce((prev, field) => {
|
|
122
|
-
const [key, name] = serdes === 'serialize' ? [field.getKey(), field.getName()] : [field.getName(), field.getKey()];
|
|
123
|
-
prev[key] = field[serdes](query, doc[name], minimal);
|
|
124
|
-
return prev;
|
|
125
|
-
}, {});
|
|
126
|
-
});
|
|
126
|
+
// Cache and return
|
|
127
|
+
this.shapesCache.set(cacheKey, shape);
|
|
128
|
+
return shape;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const fields = Object.keys(doc).map(k => this.getField(k)).filter(Boolean);
|
|
131
|
+
shapeObject(shape, obj, query, root) {
|
|
132
|
+
const { serdes, model } = shape;
|
|
133
|
+
const { context, doc = {} } = query.toObject();
|
|
133
134
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
prev[key] = keysOnly ? doc[name] : field[serdes](query, doc[name], true);
|
|
138
|
-
return prev;
|
|
139
|
-
}, {});
|
|
140
|
-
});
|
|
141
|
-
}
|
|
135
|
+
return map(obj, (parent) => {
|
|
136
|
+
// "root" is the base of the object
|
|
137
|
+
root = root || parent;
|
|
142
138
|
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
// Lookup helper functions
|
|
140
|
+
const docPath = (p, hint) => seek(doc, p, hint); // doc is already serialized; so always a seek
|
|
141
|
+
const rootPath = (p, hint) => (serdes === 'serialize' ? seek(root, p, hint) : deseek(shape, root, p, hint));
|
|
142
|
+
const parentPath = (p, hint) => (serdes === 'serialize' ? seek(parent, p, hint) : deseek(shape, parent, p, hint));
|
|
145
143
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (obj == null) return Promise.resolve();
|
|
149
|
-
return field.validate(query, obj[field.getName()]);
|
|
150
|
-
})));
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
144
|
+
return shape.reduce((prev, { field, from, to, path, type, isArray, defaultValue, transformers = [], shape: subShape }) => {
|
|
145
|
+
const startValue = parent[from];
|
|
153
146
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return Object.assign(prev, { [key]: curr.tform(query, value) });
|
|
160
|
-
}, {});
|
|
161
|
-
});
|
|
162
|
-
}
|
|
147
|
+
// Transform value
|
|
148
|
+
const transformedValue = transformers.reduce((value, t) => {
|
|
149
|
+
const v = t({ model, field, path, docPath, rootPath, parentPath, startValue, value, context });
|
|
150
|
+
return v === undefined ? value : v;
|
|
151
|
+
}, startValue);
|
|
163
152
|
|
|
164
|
-
|
|
165
|
-
return this.getSelectFields().map((field) => {
|
|
166
|
-
const [from, to] = serdes === 'serialize' ? [field.getName(), field.getKey()] : [field.getKey(), field.getName()];
|
|
167
|
-
const shape = recursive && field.isEmbedded() ? field.getModelRef().getShape(serdes, recursive) : null;
|
|
168
|
-
return { from, to, type: field.getDataType(), isArray: field.isArray(), shape };
|
|
169
|
-
});
|
|
170
|
-
}
|
|
153
|
+
// if (`${field}` === 'searchability') console.log(startValue, transformedValue, transformers);
|
|
171
154
|
|
|
172
|
-
|
|
173
|
-
|
|
155
|
+
// Determine if key should stay or be removed
|
|
156
|
+
if (transformedValue === undefined && !Object.prototype.hasOwnProperty.call(parent, from)) return prev;
|
|
174
157
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return Object.assign(prev, { [to]: subShape ? this.shape(value, serdes, subShape) : value });
|
|
158
|
+
// Rename key & assign value
|
|
159
|
+
prev[to] = (!subShape || transformedValue == null) ? transformedValue : this.shapeObject(subShape, transformedValue, query, root);
|
|
160
|
+
|
|
161
|
+
return prev;
|
|
180
162
|
}, {});
|
|
181
163
|
});
|
|
182
164
|
}
|