@coderich/autograph 0.11.0 → 0.11.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/package.json +6 -9
- package/src/core/EventEmitter.js +4 -4
- package/src/data/DataTransaction.js +84 -149
- package/src/data/Model.js +23 -8
- package/src/data/Pipeline.js +15 -24
- package/src/data/Type.js +1 -1
- package/src/driver/MongoDriver.js +23 -23
- package/src/graphql/ast/Schema.js +9 -13
- package/src/graphql/extension/api.js +1 -3
- package/src/graphql/extension/framework.js +10 -1
- package/src/query/Query.js +7 -9
- package/src/query/QueryBuilder.js +4 -4
- package/src/query/QueryResolver.js +15 -6
- package/src/query/QueryService.js +5 -1
- package/src/service/app.service.js +2 -22
- package/src/service/event.service.js +1 -1
- package/src/service/schema.service.js +0 -47
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderich/autograph",
|
|
3
3
|
"author": "Richard Livolsi (coderich)",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.2",
|
|
5
5
|
"description": "AutoGraph",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"graphql",
|
|
@@ -30,26 +30,23 @@
|
|
|
30
30
|
"ratchet": "ratchet"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"@coderich/util": "1.0.5",
|
|
33
34
|
"@hapi/boom": "^9.1.0",
|
|
34
35
|
"dataloader": "^2.0.0",
|
|
35
36
|
"deepmerge": "^4.2.2",
|
|
36
37
|
"fill-range": "^7.0.1",
|
|
37
|
-
"glob": "^
|
|
38
|
+
"glob": "^9.3.5",
|
|
38
39
|
"graphql-fields": "^2.0.3",
|
|
39
40
|
"lodash": "^4.17.21",
|
|
40
|
-
"mongodb": "
|
|
41
|
-
"object-hash": "^
|
|
41
|
+
"mongodb": "4.17.0",
|
|
42
|
+
"object-hash": "^3.0.0",
|
|
42
43
|
"picomatch": "^2.1.1"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"@coderich/ratchet": "^1.5.
|
|
46
|
+
"@coderich/ratchet": "^1.5.8",
|
|
46
47
|
"@graphql-tools/schema": "^9.0.1",
|
|
47
48
|
"graphql": "^15.5.0",
|
|
48
49
|
"mongodb-memory-server": "^8.7.2",
|
|
49
|
-
"neo4j-driver": "^4.0.0",
|
|
50
|
-
"neodb": "^3.0.0",
|
|
51
|
-
"redis": "^2.8.0",
|
|
52
|
-
"redis-mock": "^0.47.0",
|
|
53
50
|
"validator": "^13.7.0"
|
|
54
51
|
},
|
|
55
52
|
"peerDependencies": {
|
package/src/core/EventEmitter.js
CHANGED
|
@@ -31,8 +31,8 @@ module.exports = class extends EventEmitter {
|
|
|
31
31
|
const { key } = event;
|
|
32
32
|
|
|
33
33
|
if (ensureArray(keys).indexOf(key) > -1) {
|
|
34
|
-
await fn(event, next);
|
|
35
34
|
if (numArgs < 2) next();
|
|
35
|
+
await fn(event, next);
|
|
36
36
|
} else {
|
|
37
37
|
next();
|
|
38
38
|
}
|
|
@@ -49,8 +49,8 @@ module.exports = class extends EventEmitter {
|
|
|
49
49
|
const { key } = event;
|
|
50
50
|
|
|
51
51
|
if (ensureArray(keys).indexOf(key) > -1) {
|
|
52
|
-
await fn(event, next);
|
|
53
52
|
if (numArgs < 2) next();
|
|
53
|
+
await fn(event, next);
|
|
54
54
|
} else {
|
|
55
55
|
next();
|
|
56
56
|
}
|
|
@@ -67,8 +67,8 @@ module.exports = class extends EventEmitter {
|
|
|
67
67
|
const { model } = event;
|
|
68
68
|
|
|
69
69
|
if (ensureArray(models).indexOf(`${model}`) > -1) {
|
|
70
|
-
await fn(event, next);
|
|
71
70
|
if (numArgs < 2) next();
|
|
71
|
+
await fn(event, next);
|
|
72
72
|
} else {
|
|
73
73
|
next();
|
|
74
74
|
}
|
|
@@ -85,8 +85,8 @@ module.exports = class extends EventEmitter {
|
|
|
85
85
|
const { model } = event;
|
|
86
86
|
|
|
87
87
|
if (ensureArray(models).indexOf(`${model}`) > -1) {
|
|
88
|
-
await fn(event, next);
|
|
89
88
|
if (numArgs < 2) next();
|
|
89
|
+
await fn(event, next);
|
|
90
90
|
} else {
|
|
91
91
|
next();
|
|
92
92
|
}
|
|
@@ -1,161 +1,96 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { get } = require('lodash');
|
|
2
2
|
const TreeMap = require('./TreeMap');
|
|
3
3
|
const QueryBuilderTransaction = require('../query/QueryBuilderTransaction');
|
|
4
4
|
|
|
5
|
+
const makeMap = (resolver) => {
|
|
6
|
+
let resolve, reject;
|
|
7
|
+
const map = new TreeMap();
|
|
8
|
+
map.promise = new Promise((good, bad) => { resolve = good; reject = bad; });
|
|
9
|
+
map.resolve = resolve;
|
|
10
|
+
map.reject = reject;
|
|
11
|
+
|
|
12
|
+
map.ready = () => {
|
|
13
|
+
const elements = map.elements();
|
|
14
|
+
const notReady = elements.filter(el => !el.marker);
|
|
15
|
+
if (notReady.length) return [undefined, undefined];
|
|
16
|
+
let rollbackIndex = elements.findIndex(el => el.marker === 'rollback');
|
|
17
|
+
if (rollbackIndex === -1) rollbackIndex = Infinity;
|
|
18
|
+
return [elements.slice(0, rollbackIndex), elements.slice(rollbackIndex)];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
map.perform = () => {
|
|
22
|
+
const [commits, rollbacks] = map.ready();
|
|
23
|
+
|
|
24
|
+
if (commits && rollbacks) {
|
|
25
|
+
const rollbackData = rollbacks.map(tnx => tnx.data).flat();
|
|
26
|
+
const commitData = commits.map(tnx => tnx.data).flat();
|
|
27
|
+
|
|
28
|
+
Promise.all(rollbackData.map(rbd => rbd.$rollback())).then(() => {
|
|
29
|
+
if (commits.length) resolver.clearAll();
|
|
30
|
+
Promise.all(commitData.map(cd => cd.$commit())).then(d => map.resolve(d));
|
|
31
|
+
}).catch(e => map.reject(e));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return map.promise;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return map;
|
|
38
|
+
};
|
|
39
|
+
|
|
5
40
|
module.exports = class DataTransaction {
|
|
6
41
|
constructor(resolver, parentTxn) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
map.reject = reject;
|
|
13
|
-
|
|
14
|
-
map.ready = () => {
|
|
15
|
-
const elements = map.elements();
|
|
16
|
-
const notReady = elements.filter(el => !el.marker);
|
|
17
|
-
if (notReady.length) return [undefined, undefined];
|
|
18
|
-
let rollbackIndex = elements.findIndex(el => el.marker === 'rollback');
|
|
19
|
-
if (rollbackIndex === -1) rollbackIndex = Infinity;
|
|
20
|
-
return [elements.slice(0, rollbackIndex), elements.slice(rollbackIndex)];
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
map.perform = () => {
|
|
24
|
-
const [commits, rollbacks] = map.ready();
|
|
25
|
-
|
|
26
|
-
if (commits && rollbacks) {
|
|
27
|
-
const rollbackData = flatten(rollbacks.map(tnx => tnx.data));
|
|
28
|
-
const commitData = flatten(commits.map(tnx => tnx.data));
|
|
29
|
-
|
|
30
|
-
Promise.all(rollbackData.map(rbd => rbd.$rollback())).then(() => {
|
|
31
|
-
if (commits.length) resolver.clearAll();
|
|
32
|
-
Promise.all(commitData.map(cd => cd.$commit())).then(d => map.resolve(d));
|
|
33
|
-
}).catch(e => map.reject(e));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return map.promise;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
return map;
|
|
40
|
-
})();
|
|
41
|
-
|
|
42
|
-
// Create txn
|
|
43
|
-
const txn = ((data, driverMap, txMap) => {
|
|
44
|
-
return {
|
|
45
|
-
get match() {
|
|
46
|
-
return (modelName) => {
|
|
47
|
-
const model = resolver.toModelMarked(modelName);
|
|
48
|
-
const driver = model.getDriver();
|
|
49
|
-
if (!driverMap.has(driver)) driverMap.set(driver, []);
|
|
50
|
-
const op = new QueryBuilderTransaction(resolver, model, this);
|
|
51
|
-
driverMap.get(driver).push(op);
|
|
52
|
-
return op;
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
get exec() {
|
|
56
|
-
return () => {
|
|
57
|
-
return Promise.all(Array.from(driverMap.entries()).map(([driver, ops]) => {
|
|
58
|
-
if (driver.getDirectives().transactions === false) {
|
|
59
|
-
return Promise.all(ops.map(op => op.exec())).then((results) => {
|
|
60
|
-
results.$commit = () => resolver.clearAll();
|
|
61
|
-
results.$rollback = () => resolver.clearAll();
|
|
62
|
-
return results;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return driver.transaction(ops);
|
|
67
|
-
})).then((results) => {
|
|
68
|
-
data = results;
|
|
69
|
-
return flatten(results);
|
|
70
|
-
});
|
|
71
|
-
};
|
|
72
|
-
},
|
|
73
|
-
get run() {
|
|
74
|
-
return () => {
|
|
75
|
-
return this.exec().then((results) => {
|
|
76
|
-
if (txMap.root(this) === this) return this.commit().then(() => results);
|
|
77
|
-
this.commit();
|
|
78
|
-
return results;
|
|
79
|
-
}).catch((e) => {
|
|
80
|
-
if (txMap.root(this) === this) return this.rollback().then(() => Promise.reject(e));
|
|
81
|
-
this.rollback();
|
|
82
|
-
throw e;
|
|
83
|
-
});
|
|
84
|
-
};
|
|
85
|
-
},
|
|
86
|
-
get commit() {
|
|
87
|
-
return () => {
|
|
88
|
-
if (this.marker !== 'rollback') this.marker = 'commit';
|
|
89
|
-
return txMap.perform();
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
get rollback() {
|
|
93
|
-
return () => {
|
|
94
|
-
this.marker = 'rollback';
|
|
95
|
-
return txMap.perform();
|
|
96
|
-
};
|
|
97
|
-
},
|
|
98
|
-
get data() {
|
|
99
|
-
return data;
|
|
100
|
-
},
|
|
101
|
-
get txnMap() {
|
|
102
|
-
return txMap;
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
})([], new Map(), txnMap);
|
|
106
|
-
|
|
107
|
-
// Save txn to map
|
|
108
|
-
txnMap.add(parentTxn, txn);
|
|
109
|
-
|
|
110
|
-
// Return to caller
|
|
111
|
-
return txn;
|
|
42
|
+
this.data = [];
|
|
43
|
+
this.resolver = resolver;
|
|
44
|
+
this.driverMap = new Map();
|
|
45
|
+
this.txnMap = get(parentTxn, 'txnMap') || makeMap(resolver);
|
|
46
|
+
this.txnMap.add(parentTxn, this);
|
|
112
47
|
}
|
|
113
48
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// exec() {
|
|
124
|
-
// return Promise.all(Array.from(this.driverMap.entries()).map(([driver, ops]) => {
|
|
125
|
-
// if (driver.getDirectives().transactions === false) {
|
|
126
|
-
// return Promise.all(ops.map(op => op.exec())).then((results) => {
|
|
127
|
-
// results.$commit = () => this.resolver.clearAll();
|
|
128
|
-
// results.$rollback = () => this.resolver.clearAll();
|
|
129
|
-
// return results;
|
|
130
|
-
// });
|
|
131
|
-
// }
|
|
49
|
+
match(modelish) {
|
|
50
|
+
const model = this.resolver.toModelMarked(modelish);
|
|
51
|
+
const driver = model.getDriver();
|
|
52
|
+
if (!this.driverMap.has(driver)) this.driverMap.set(driver, []);
|
|
53
|
+
const op = new QueryBuilderTransaction(this.resolver, model, this);
|
|
54
|
+
this.driverMap.get(driver).push(op);
|
|
55
|
+
return op;
|
|
56
|
+
}
|
|
132
57
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
58
|
+
exec() {
|
|
59
|
+
return Promise.all(Array.from(this.driverMap.entries()).map(([driver, ops]) => {
|
|
60
|
+
if (driver.getDirectives().transactions === false) {
|
|
61
|
+
return Promise.all(ops.map(op => op.exec())).then((results) => {
|
|
62
|
+
results.$commit = () => this.resolver.clearAll();
|
|
63
|
+
results.$rollback = () => this.resolver.clearAll();
|
|
64
|
+
return results;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return driver.transaction(ops);
|
|
69
|
+
})).then((results) => {
|
|
70
|
+
this.data = results;
|
|
71
|
+
return results.flat();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
139
74
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
75
|
+
run() {
|
|
76
|
+
return this.exec().then((results) => {
|
|
77
|
+
if (this.txnMap.root(this) === this) return this.commit().then(() => results);
|
|
78
|
+
this.commit();
|
|
79
|
+
return results;
|
|
80
|
+
}).catch((e) => {
|
|
81
|
+
if (this.txnMap.root(this) === this) return this.rollback().then(() => Promise.reject(e));
|
|
82
|
+
this.rollback();
|
|
83
|
+
throw e;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
151
86
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
87
|
+
commit() {
|
|
88
|
+
if (this.marker !== 'rollback') this.marker = 'commit';
|
|
89
|
+
return this.txnMap.perform();
|
|
90
|
+
}
|
|
156
91
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
92
|
+
rollback() {
|
|
93
|
+
this.marker = 'rollback';
|
|
94
|
+
return this.txnMap.perform();
|
|
95
|
+
}
|
|
161
96
|
};
|
package/src/data/Model.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const Stream = require('stream');
|
|
2
|
+
const { get } = require('lodash');
|
|
3
|
+
const { flatten } = require('@coderich/util');
|
|
2
4
|
const Field = require('./Field');
|
|
3
5
|
const Model = require('../graphql/ast/Model');
|
|
4
6
|
const { eventEmitter } = require('../service/event.service');
|
|
@@ -129,13 +131,14 @@ module.exports = class extends Model {
|
|
|
129
131
|
shape.model = this;
|
|
130
132
|
shape.serdes = serdes;
|
|
131
133
|
shape.target = target;
|
|
134
|
+
// console.log(shape.modelRef);
|
|
132
135
|
|
|
133
136
|
// Cache and return
|
|
134
137
|
this.shapesCache.set(cacheKey, shape);
|
|
135
138
|
return shape;
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
shapeObject(shape, obj, query, root) {
|
|
141
|
+
shapeObject(shape, obj, query, root, base, toFlat = false) {
|
|
139
142
|
const { serdes, model } = shape;
|
|
140
143
|
const { context, resolver, doc = {}, flags = {} } = query.toObject();
|
|
141
144
|
const { pipeline } = flags;
|
|
@@ -143,8 +146,11 @@ module.exports = class extends Model {
|
|
|
143
146
|
if (!pipeline) return obj;
|
|
144
147
|
// const filters = pipeline === true ? [] : Object.entries(pipeline).map(([k, v]) => (v === false ? k : null)).filter(Boolean);
|
|
145
148
|
|
|
149
|
+
// base is the base model
|
|
150
|
+
base = base || model;
|
|
151
|
+
|
|
146
152
|
return map(obj, (parent) => {
|
|
147
|
-
//
|
|
153
|
+
// root is the base data object
|
|
148
154
|
root = root || parent;
|
|
149
155
|
|
|
150
156
|
// Lookup helper functions
|
|
@@ -158,7 +164,7 @@ module.exports = class extends Model {
|
|
|
158
164
|
|
|
159
165
|
// Transform value
|
|
160
166
|
const transformedValue = transformers.reduce((value, t) => {
|
|
161
|
-
const v = t({ model, field, path, docPath, rootPath, parentPath, startValue, value, resolver, context });
|
|
167
|
+
const v = t({ base, model, field, path, docPath, rootPath, parentPath, startValue, value, resolver, context });
|
|
162
168
|
return v === undefined ? value : v;
|
|
163
169
|
}, startValue);
|
|
164
170
|
|
|
@@ -167,22 +173,31 @@ module.exports = class extends Model {
|
|
|
167
173
|
if (!instructed && subShape && typeof transformedValue !== 'object') return prev;
|
|
168
174
|
|
|
169
175
|
// Rename key & assign value
|
|
170
|
-
prev[to] = (!subShape || transformedValue == null) ? transformedValue : this.shapeObject(subShape, transformedValue, query, root);
|
|
176
|
+
prev[to] = (!subShape || transformedValue == null) ? transformedValue : this.shapeObject(subShape, transformedValue, query, root, base, toFlat);
|
|
177
|
+
|
|
178
|
+
if (toFlat && get(doc, to) && field.getModelRef()) {
|
|
179
|
+
const val = prev[to];
|
|
180
|
+
delete prev[to];
|
|
181
|
+
Object.assign(prev, flatten({ [to]: val }, { safe: true, depth: 1 }));
|
|
182
|
+
}
|
|
171
183
|
|
|
172
184
|
return prev;
|
|
173
185
|
}, {});
|
|
174
186
|
});
|
|
175
187
|
}
|
|
176
188
|
|
|
177
|
-
validateObject(shape, obj, query, root, silent = false) {
|
|
189
|
+
validateObject(shape, obj, query, root, base, silent = false) {
|
|
178
190
|
const { model } = shape;
|
|
179
191
|
const { context, resolver, doc = {}, flags = {} } = query.toObject();
|
|
180
192
|
const { validate = true } = flags;
|
|
181
193
|
|
|
182
194
|
if (!validate) return Promise.resolve();
|
|
183
195
|
|
|
196
|
+
// base is the base model
|
|
197
|
+
base = base || model;
|
|
198
|
+
|
|
184
199
|
return mapPromise(obj, (parent) => {
|
|
185
|
-
//
|
|
200
|
+
// root is the base data object
|
|
186
201
|
root = root || parent;
|
|
187
202
|
|
|
188
203
|
// Lookup helper functions
|
|
@@ -195,10 +210,10 @@ module.exports = class extends Model {
|
|
|
195
210
|
|
|
196
211
|
return Promise.all(validators.map((v) => {
|
|
197
212
|
return new Promise((resolve, reject) => {
|
|
198
|
-
return Promise.resolve(v({ model, field, path, docPath, rootPath, parentPath, startValue: value, value, resolver, context })).then(resolve).catch(reject);
|
|
213
|
+
return Promise.resolve(v({ base, model, field, path, docPath, rootPath, parentPath, startValue: value, value, resolver, context })).then(resolve).catch(reject);
|
|
199
214
|
});
|
|
200
215
|
})).then(() => {
|
|
201
|
-
return subShape ? this.validateObject(subShape, value, query, root, true) : Promise.resolve();
|
|
216
|
+
return subShape ? this.validateObject(subShape, value, query, root, base, true) : Promise.resolve();
|
|
202
217
|
});
|
|
203
218
|
}));
|
|
204
219
|
}).then(() => {
|
package/src/data/Pipeline.js
CHANGED
|
@@ -42,22 +42,6 @@ module.exports = class Pipeline {
|
|
|
42
42
|
return Object.defineProperty(Pipeline, name, { value: (...args) => Object.defineProperty(thunk(...args), 'options', { value: options }) })[name];
|
|
43
43
|
}
|
|
44
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
45
|
static createPresets() {
|
|
62
46
|
// Built-In Javascript String Transformers
|
|
63
47
|
const jsStringTransformers = ['toLowerCase', 'toUpperCase', 'toString', 'trim', 'trimEnd', 'trimStart'];
|
|
@@ -76,12 +60,13 @@ module.exports = class Pipeline {
|
|
|
76
60
|
Pipeline.define('idField', ({ model, field, value }) => field.getIdModel().idValue(value.id || value));
|
|
77
61
|
Pipeline.define('ensureArrayValue', ({ field, value }) => (field.toObject().isArray && !Array.isArray(value) ? [value] : value), { itemize: false });
|
|
78
62
|
|
|
79
|
-
Pipeline.define('ensureId', ({ resolver, field, value }) => {
|
|
63
|
+
Pipeline.define('ensureId', ({ resolver, model, field, value }) => {
|
|
64
|
+
const path = `${model}.${field}`;
|
|
80
65
|
const { type } = field.toObject();
|
|
81
66
|
const ids = Array.from(new Set(ensureArray(value).map(v => `${v}`)));
|
|
82
67
|
|
|
83
68
|
return resolver.match(type).where({ id: ids }).count().then((count) => {
|
|
84
|
-
if (count !== ids.length) throw Boom.notFound(`${type} Not Found
|
|
69
|
+
if (count !== ids.length) throw Boom.notFound(`${type} Not Found`, { path });
|
|
85
70
|
});
|
|
86
71
|
}, { itemize: false });
|
|
87
72
|
|
|
@@ -124,29 +109,34 @@ module.exports = class Pipeline {
|
|
|
124
109
|
|
|
125
110
|
// Required fields
|
|
126
111
|
Pipeline.define('required', ({ model, field, value }) => {
|
|
127
|
-
|
|
112
|
+
const path = `${model}.${field}`;
|
|
113
|
+
if (value == null) throw Boom.badRequest(`${path} is required`, { path });
|
|
128
114
|
}, { ignoreNull: false });
|
|
129
115
|
|
|
130
116
|
// A field cannot hold a reference to itself
|
|
131
117
|
Pipeline.define('selfless', ({ model, field, parent, parentPath, value }) => {
|
|
132
|
-
|
|
118
|
+
const path = `${model}.${field}`;
|
|
119
|
+
if (`${value}` === `${parentPath('id')}`) throw Boom.badData(`${path} cannot hold a reference to itself`, { path });
|
|
133
120
|
});
|
|
134
121
|
|
|
135
122
|
// Once set it cannot be changed
|
|
136
123
|
Pipeline.define('immutable', ({ model, field, docPath, parentPath, path, value }) => {
|
|
124
|
+
const $path = `${model}.${field}`;
|
|
137
125
|
const hint = { id: parentPath('id') };
|
|
138
126
|
const oldVal = docPath(path, hint);
|
|
139
|
-
if (oldVal !== undefined && value !== undefined && `${hashObject(oldVal)}` !== `${hashObject(value)}`) throw Boom.
|
|
127
|
+
if (oldVal !== undefined && value !== undefined && `${hashObject(oldVal)}` !== `${hashObject(value)}`) throw Boom.badData(`${$path} is immutable; cannot be changed once set ${oldVal} -> ${value}`, { path: $path });
|
|
140
128
|
});
|
|
141
129
|
|
|
142
130
|
// List of allowed values
|
|
143
131
|
Pipeline.factory('Allow', (...args) => function allow({ model, field, value }) {
|
|
144
|
-
|
|
132
|
+
const path = `${model}.${field}`;
|
|
133
|
+
if (args.indexOf(value) === -1) throw Boom.badData(`${path} allows ${args}; found '${value}'`, { path });
|
|
145
134
|
});
|
|
146
135
|
|
|
147
136
|
// List of disallowed values
|
|
148
137
|
Pipeline.factory('Deny', (...args) => function deny({ model, field, value }) {
|
|
149
|
-
|
|
138
|
+
const path = `${model}.${field}`;
|
|
139
|
+
if (args.indexOf(value) > -1) throw Boom.badData(`${path} denys ${args}; found '${value}'`, { path });
|
|
150
140
|
});
|
|
151
141
|
|
|
152
142
|
// Min/Max range
|
|
@@ -155,9 +145,10 @@ module.exports = class Pipeline {
|
|
|
155
145
|
if (max == null) max = undefined;
|
|
156
146
|
|
|
157
147
|
return function range({ model, field, value }) {
|
|
148
|
+
const path = `${model}.${field}`;
|
|
158
149
|
const num = +value; // Coerce to number if possible
|
|
159
150
|
const test = Number.isNaN(num) ? value.length : num;
|
|
160
|
-
if (test < min || test > max) throw Boom.
|
|
151
|
+
if (test < min || test > max) throw Boom.badData(`${path} must satisfy range ${min}:${max}; found '${value}'`, { path });
|
|
161
152
|
};
|
|
162
153
|
}, { itemize: false });
|
|
163
154
|
}
|
package/src/data/Type.js
CHANGED
|
@@ -30,7 +30,7 @@ module.exports = class extends Type {
|
|
|
30
30
|
structures.defaultValue = Pipeline.defaultValue;
|
|
31
31
|
structures.ensureArrayValue = Pipeline.ensureArrayValue;
|
|
32
32
|
|
|
33
|
-
if (enumType) structures.validators.push(Pipeline.define(`allow
|
|
33
|
+
if (enumType) structures.validators.push(Pipeline.define(`allow${type}`, Pipeline.Allow(...enumType.getValue()), { configurable: true }));
|
|
34
34
|
if (!scalarType) return structures;
|
|
35
35
|
|
|
36
36
|
return Object.entries(scalarType.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const Util = require('util');
|
|
2
2
|
const { get } = require('lodash');
|
|
3
|
+
const { unflatten } = require('@coderich/util');
|
|
3
4
|
const { MongoClient, ObjectId } = require('mongodb');
|
|
4
5
|
const { map, ensureArray, proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
|
|
5
6
|
|
|
6
7
|
module.exports = class MongoDriver {
|
|
7
8
|
constructor(config) {
|
|
8
9
|
this.config = config;
|
|
10
|
+
this.config.query = this.config.query || {};
|
|
9
11
|
this.connection = this.connect();
|
|
10
12
|
this.getDirectives = () => get(config, 'directives', {});
|
|
11
13
|
}
|
|
@@ -27,7 +29,7 @@ module.exports = class MongoDriver {
|
|
|
27
29
|
query(collection, method, ...args) {
|
|
28
30
|
if (get(args[args.length - 1], 'debug') === true) console.log(collection, method, Util.inspect(args, { depth: null, showHidden: false, colors: true }));
|
|
29
31
|
if (method === 'aggregate') args.splice(2);
|
|
30
|
-
return this.raw(collection)[method](
|
|
32
|
+
return this.raw(collection)[method](args[0], args[1]);
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
resolve(query) {
|
|
@@ -48,15 +50,15 @@ module.exports = class MongoDriver {
|
|
|
48
50
|
|
|
49
51
|
findMany(query) {
|
|
50
52
|
const { model, options = {}, flags } = query;
|
|
51
|
-
|
|
52
|
-
return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query), options, flags).then(cursor => cursor.stream());
|
|
53
|
+
const $options = { ...this.config.query, ...options };
|
|
54
|
+
return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query), $options, flags).then(cursor => cursor.stream());
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
count(query) {
|
|
56
58
|
const { model, options = {}, flags } = query;
|
|
57
|
-
|
|
59
|
+
const $options = { ...this.config.query, ...options };
|
|
58
60
|
|
|
59
|
-
return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query, true), options, flags).then((cursor) => {
|
|
61
|
+
return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query, true), $options, flags).then((cursor) => {
|
|
60
62
|
return cursor.next().then((doc) => {
|
|
61
63
|
return doc ? doc.count : 0;
|
|
62
64
|
});
|
|
@@ -70,7 +72,7 @@ module.exports = class MongoDriver {
|
|
|
70
72
|
|
|
71
73
|
updateOne({ model, where, $doc, options, flags }) {
|
|
72
74
|
const $update = { $set: $doc };
|
|
73
|
-
return this.query(model, 'updateOne', where, $update, options, flags).then(() => $doc);
|
|
75
|
+
return this.query(model, 'updateOne', where, $update, options, flags).then(() => unflatten($doc, { safe: true }));
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
deleteOne({ model, where, options, flags }) {
|
|
@@ -97,25 +99,23 @@ module.exports = class MongoDriver {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
transaction(ops) {
|
|
100
|
-
|
|
102
|
+
return promiseRetry(() => {
|
|
101
103
|
// Create session and start transaction
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
return this.connection.then(client => client.startSession({ readPreference: { mode: 'primary' } })).then((session) => {
|
|
105
|
+
session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } });
|
|
106
|
+
const close = () => { session.endSession(); };
|
|
107
|
+
|
|
108
|
+
// Execute each operation with session
|
|
109
|
+
return Promise.all(ops.map(op => op.exec({ session }))).then((results) => {
|
|
110
|
+
results.$commit = () => session.commitTransaction().then(close);
|
|
111
|
+
results.$rollback = () => session.abortTransaction().then(close);
|
|
112
|
+
return results;
|
|
113
|
+
}).catch((e) => {
|
|
114
|
+
close();
|
|
115
|
+
throw e;
|
|
116
|
+
});
|
|
114
117
|
});
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Retry promise conditionally
|
|
118
|
-
return promiseRetry(promise, 200, 5, e => e.errorLabels && e.errorLabels.indexOf('TransientTransactionError') > -1);
|
|
118
|
+
}, 200, 5, e => e.errorLabels && e.errorLabels.indexOf('TransientTransactionError') > -1);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
static idKey() {
|
|
@@ -70,18 +70,14 @@ module.exports = class Schema extends TypeDefApi {
|
|
|
70
70
|
* Asynchronously load files from a given glob pattern and merge each schema
|
|
71
71
|
*/
|
|
72
72
|
mergeSchemaFromFiles(globPattern, options) {
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}).then(schema => this.mergeSchema(schema, options));
|
|
82
|
-
})).then(() => resolve(this)).catch(e => reject(e));
|
|
83
|
-
});
|
|
84
|
-
});
|
|
73
|
+
return Glob(globPattern, options).then((files) => {
|
|
74
|
+
return Promise.all(files.map((file) => {
|
|
75
|
+
return new Promise((res) => {
|
|
76
|
+
if (file.endsWith('.js')) res(require(file)); // eslint-disable-line global-require,import/no-dynamic-require
|
|
77
|
+
else res(FS.readFileSync(file, 'utf8'));
|
|
78
|
+
}).then(schema => this.mergeSchema(schema, options));
|
|
79
|
+
}));
|
|
80
|
+
}).then(() => this);
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
/**
|
|
@@ -128,6 +124,6 @@ module.exports = class Schema extends TypeDefApi {
|
|
|
128
124
|
}
|
|
129
125
|
|
|
130
126
|
toString() {
|
|
131
|
-
return print(this.typeDefs);
|
|
127
|
+
return print(this.schema.typeDefs);
|
|
132
128
|
}
|
|
133
129
|
};
|
|
@@ -9,7 +9,7 @@ const interfaceKinds = [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTE
|
|
|
9
9
|
|
|
10
10
|
const getGQLWhereFields = (model) => {
|
|
11
11
|
return model.getFields().filter((field) => {
|
|
12
|
-
if (!field.hasGQLScope('r')) return false;
|
|
12
|
+
if (!field.isPersistable() || !field.hasGQLScope('r')) return false;
|
|
13
13
|
const modelRef = field.getModelRef();
|
|
14
14
|
if (modelRef && !modelRef.isEmbedded() && !modelRef.isEntity()) return false;
|
|
15
15
|
return true;
|
|
@@ -107,8 +107,6 @@ module.exports = (schema) => {
|
|
|
107
107
|
`type Query {
|
|
108
108
|
node(id: ID!): Node
|
|
109
109
|
${entityModels.map(model => makeReadAPI(model.getName(), model))}
|
|
110
|
-
${entityModels.map(model => makeReadAPI(`${model.getName()}Create`, model))}
|
|
111
|
-
${entityModels.map(model => makeReadAPI(`${model.getName()}Update`, model))}
|
|
112
110
|
}`,
|
|
113
111
|
|
|
114
112
|
`type Mutation {
|
|
@@ -8,7 +8,6 @@ module.exports = (schema) => {
|
|
|
8
8
|
scalar AutoGraphMixed
|
|
9
9
|
scalar AutoGraphDriver
|
|
10
10
|
scalar AutoGraphDateTime @field(transform: toDate)
|
|
11
|
-
enum AutoGraphSchemaEnum { ${schema.getModels().map(m => m.getName()).join(' ')} }
|
|
12
11
|
enum AutoGraphPipelineEnum { ${Object.keys(Pipeline).join(' ')} }
|
|
13
12
|
enum AutoGraphAuthzEnum { private protected public }
|
|
14
13
|
enum AutoGraphOnDeleteEnum { cascade nullify restrict defer }
|
|
@@ -28,6 +27,12 @@ module.exports = (schema) => {
|
|
|
28
27
|
fieldScope: AutoGraphMixed # Dictate how a FIELD may use me
|
|
29
28
|
authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
|
|
30
29
|
namespace: String # Logical grouping of models that can be globbed (useful for authz)
|
|
30
|
+
|
|
31
|
+
# FOR TRANSITION TO NEW VERSION
|
|
32
|
+
crud: AutoGraphMixed # CRUD API
|
|
33
|
+
scope: AutoGraphMixed #
|
|
34
|
+
source: AutoGraphMixed # Data source (default: "default")
|
|
35
|
+
decorate: AutoGraphMixed # Decorator (default: "default")
|
|
31
36
|
) on OBJECT | INTERFACE
|
|
32
37
|
|
|
33
38
|
directive @field(
|
|
@@ -54,6 +59,10 @@ module.exports = (schema) => {
|
|
|
54
59
|
transform: [AutoGraphPipelineEnum!]
|
|
55
60
|
serialize: [AutoGraphPipelineEnum!]
|
|
56
61
|
deserialize: [AutoGraphPipelineEnum!]
|
|
62
|
+
|
|
63
|
+
# FOR TRANSITION TO NEW VERSION
|
|
64
|
+
crud: AutoGraphMixed # CRUD API
|
|
65
|
+
finalize: [AutoGraphPipelineEnum!]
|
|
57
66
|
) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
|
|
58
67
|
|
|
59
68
|
directive @link(
|
package/src/query/Query.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const Boom = require('../core/Boom');
|
|
2
|
-
|
|
3
1
|
module.exports = class Query {
|
|
4
2
|
constructor(props = {}) {
|
|
5
3
|
this.props = {};
|
|
@@ -21,7 +19,7 @@ module.exports = class Query {
|
|
|
21
19
|
|
|
22
20
|
propCheck(prop, ...checks) {
|
|
23
21
|
checks.forEach((check) => {
|
|
24
|
-
if (this.props[check]) throw
|
|
22
|
+
if (this.props[check]) throw new Error(`Cannot use "${prop}" while using "${check}"`);
|
|
25
23
|
});
|
|
26
24
|
}
|
|
27
25
|
|
|
@@ -98,7 +96,7 @@ module.exports = class Query {
|
|
|
98
96
|
|
|
99
97
|
skip(skip) {
|
|
100
98
|
this.propCheck('skip', 'id');
|
|
101
|
-
if (this.isCursorPaging) throw
|
|
99
|
+
if (this.isCursorPaging) throw new Error('Cannot use "skip" while using Cursor-Style Pagination');
|
|
102
100
|
this.isClassicPaging = true;
|
|
103
101
|
this.props.skip = skip;
|
|
104
102
|
return this;
|
|
@@ -106,7 +104,7 @@ module.exports = class Query {
|
|
|
106
104
|
|
|
107
105
|
limit(limit) {
|
|
108
106
|
this.propCheck('limit', 'id');
|
|
109
|
-
if (this.isCursorPaging) throw
|
|
107
|
+
if (this.isCursorPaging) throw new Error('Cannot use "limit" while using Cursor-Style Pagination');
|
|
110
108
|
this.isClassicPaging = true;
|
|
111
109
|
this.props.limit = limit;
|
|
112
110
|
return this;
|
|
@@ -114,7 +112,7 @@ module.exports = class Query {
|
|
|
114
112
|
|
|
115
113
|
first(first) {
|
|
116
114
|
this.propCheck('first', 'id', 'last');
|
|
117
|
-
if (this.isClassicPaging) throw
|
|
115
|
+
if (this.isClassicPaging) throw new Error('Cannot use "first" while using Classic-Style Pagination');
|
|
118
116
|
this.isCursorPaging = true;
|
|
119
117
|
this.props.first = first + 2; // Adding 2 for pagination meta info (hasNext hasPrev)
|
|
120
118
|
return this;
|
|
@@ -122,7 +120,7 @@ module.exports = class Query {
|
|
|
122
120
|
|
|
123
121
|
last(last) {
|
|
124
122
|
this.propCheck('last', 'id', 'first');
|
|
125
|
-
if (this.isClassicPaging) throw
|
|
123
|
+
if (this.isClassicPaging) throw new Error('Cannot use "last" while using Classic-Style Pagination');
|
|
126
124
|
this.isCursorPaging = true;
|
|
127
125
|
this.props.last = last + 2; // Adding 2 for pagination meta info (hasNext hasPrev)
|
|
128
126
|
return this;
|
|
@@ -130,7 +128,7 @@ module.exports = class Query {
|
|
|
130
128
|
|
|
131
129
|
before(before) {
|
|
132
130
|
this.propCheck('before', 'id');
|
|
133
|
-
if (this.isClassicPaging) throw
|
|
131
|
+
if (this.isClassicPaging) throw new Error('Cannot use "before" while using Classic-Style Pagination');
|
|
134
132
|
this.isCursorPaging = true;
|
|
135
133
|
this.props.before = before;
|
|
136
134
|
return this;
|
|
@@ -138,7 +136,7 @@ module.exports = class Query {
|
|
|
138
136
|
|
|
139
137
|
after(after) {
|
|
140
138
|
this.propCheck('after', 'id');
|
|
141
|
-
if (this.isClassicPaging) throw
|
|
139
|
+
if (this.isClassicPaging) throw new Error('Cannot use "after" while using Classic-Style Pagination');
|
|
142
140
|
this.isCursorPaging = true;
|
|
143
141
|
this.props.after = after;
|
|
144
142
|
return this;
|
|
@@ -10,7 +10,7 @@ const { unravelObject } = require('../service/app.service');
|
|
|
10
10
|
*/
|
|
11
11
|
module.exports = class QueryBuilder {
|
|
12
12
|
constructor(resolver, model) {
|
|
13
|
-
this.terminated = false; // Prevent accidental re-use of the QueryBuilder
|
|
13
|
+
// this.terminated = false; // Prevent accidental re-use of the QueryBuilder
|
|
14
14
|
this.query = new Query({ model, resolver });
|
|
15
15
|
|
|
16
16
|
// Chainable commands
|
|
@@ -58,10 +58,10 @@ module.exports = class QueryBuilder {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
execute(cmd, args) {
|
|
61
|
-
// Do not allow re-use
|
|
62
|
-
if (this.terminated) return Promise.reject(new Error('This query has already been executed'));
|
|
61
|
+
// // Do not allow re-use
|
|
62
|
+
// if (this.terminated) return Promise.reject(new Error('This query has already been executed'));
|
|
63
|
+
// this.terminated = true;
|
|
63
64
|
|
|
64
|
-
this.terminated = true;
|
|
65
65
|
let method, crud, input, flags = {};
|
|
66
66
|
const { id, where } = this.query.toObject();
|
|
67
67
|
|
|
@@ -3,7 +3,7 @@ const Boom = require('../core/Boom');
|
|
|
3
3
|
const QueryService = require('./QueryService');
|
|
4
4
|
const DataService = require('../data/DataService');
|
|
5
5
|
const { createSystemEvent } = require('../service/event.service');
|
|
6
|
-
const {
|
|
6
|
+
const { mergeDeep, hashObject, getGQLReturnType } = require('../service/app.service');
|
|
7
7
|
|
|
8
8
|
module.exports = class QueryResolver {
|
|
9
9
|
constructor(query) {
|
|
@@ -76,19 +76,27 @@ module.exports = class QueryResolver {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async updateOne(query) {
|
|
79
|
-
const { model, match, input } = query.toObject();
|
|
79
|
+
const { model, match, input, flags } = query.toObject();
|
|
80
80
|
const inputShape = model.getShape('update', 'input');
|
|
81
81
|
const docShape = model.getShape('update', 'doc');
|
|
82
82
|
|
|
83
83
|
return this.resolver.match(model).match(match).one({ required: true }).then((doc) => {
|
|
84
|
-
if (objectIntersectionEqual(doc, input)) return doc; // If no changes do not perform query
|
|
85
|
-
|
|
86
84
|
const merged = mergeDeep(doc, input);
|
|
85
|
+
const docHash = hashObject(doc);
|
|
86
|
+
const skipUnchanged = get(flags, 'skipUnchanged');
|
|
87
|
+
|
|
88
|
+
// Prevent udpates when no data has changed
|
|
89
|
+
if (skipUnchanged && docHash === hashObject(merged)) return doc;
|
|
87
90
|
|
|
88
91
|
return createSystemEvent('Mutation', { query: query.doc(doc).merged(merged) }, async () => {
|
|
92
|
+
// Prevent udpates when no data has changed (because merged can be mutated)
|
|
93
|
+
if (skipUnchanged && docHash === hashObject(merged)) return doc;
|
|
94
|
+
|
|
95
|
+
// Process
|
|
89
96
|
const payload = model.shapeObject(inputShape, merged, query);
|
|
90
97
|
await model.validateObject(inputShape, payload, query.payload(payload));
|
|
91
|
-
|
|
98
|
+
const $doc = model.shapeObject(docShape, payload, query, undefined, undefined, true);
|
|
99
|
+
return this.resolver.resolve(query.$doc($doc));
|
|
92
100
|
});
|
|
93
101
|
});
|
|
94
102
|
}
|
|
@@ -187,7 +195,8 @@ module.exports = class QueryResolver {
|
|
|
187
195
|
// Can only splice arrays
|
|
188
196
|
const field = model.getField(key);
|
|
189
197
|
const isArray = field.isArray();
|
|
190
|
-
|
|
198
|
+
const path = `${model}.${field}`;
|
|
199
|
+
if (!isArray) throw Boom.badRequest(`Cannot splice field '${path}'`, { path });
|
|
191
200
|
|
|
192
201
|
return this.resolver.match(model).match(match).one({ required: true }).then(async (doc) => {
|
|
193
202
|
const array = get(doc, key) || [];
|
|
@@ -44,6 +44,7 @@ exports.resolveSortBy = (query) => {
|
|
|
44
44
|
const { model, sort = {} } = query.toObject();
|
|
45
45
|
const shape = model.getShape('create', 'sortBy');
|
|
46
46
|
const $sort = model.shapeObject(shape, sort, query);
|
|
47
|
+
const deletions = [];
|
|
47
48
|
|
|
48
49
|
// Because normalize casts the value (sometimes to an array) need special handling
|
|
49
50
|
keyPaths($sort).forEach((path) => {
|
|
@@ -55,7 +56,7 @@ exports.resolveSortBy = (query) => {
|
|
|
55
56
|
|
|
56
57
|
// If you need to sort by something that's in another FK document
|
|
57
58
|
if (join) {
|
|
58
|
-
|
|
59
|
+
deletions.push(attr); // Keep track of what to delete
|
|
59
60
|
query.joins(Object.assign(join, { as: `_.${field}`, left: true }));
|
|
60
61
|
path = `_.${path}`;
|
|
61
62
|
}
|
|
@@ -63,6 +64,9 @@ exports.resolveSortBy = (query) => {
|
|
|
63
64
|
set($sort, path, val.toLowerCase() === 'asc' ? 1 : -1);
|
|
64
65
|
});
|
|
65
66
|
|
|
67
|
+
// Delete the sorts on the "base" collection because you're sorting by _.path.to.it (above)
|
|
68
|
+
deletions.forEach(attr => delete $sort[attr]);
|
|
69
|
+
|
|
66
70
|
return $sort;
|
|
67
71
|
};
|
|
68
72
|
|
|
@@ -40,7 +40,7 @@ exports.isIdValue = value => exports.isScalarValue(value) || value instanceof Ob
|
|
|
40
40
|
exports.mergeDeep = (...args) => DeepMerge.all(args, { isMergeableObject: obj => (exports.isPlainObject(obj) || Array.isArray(obj)), arrayMerge: smartMerge });
|
|
41
41
|
exports.uniq = arr => [...new Set(arr.map(a => `${a}`))];
|
|
42
42
|
exports.timeout = ms => new Promise(res => setTimeout(res, ms));
|
|
43
|
-
exports.hashObject = obj => ObjectHash(obj, { respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedArrays: true, ignoreUnknown: true, replacer: r => (r
|
|
43
|
+
exports.hashObject = obj => ObjectHash(obj, { respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedArrays: true, ignoreUnknown: true, replacer: r => (ObjectId.isValid(r) ? `${r}` : r) });
|
|
44
44
|
exports.globToRegex = (glob, options = {}) => PicoMatch.makeRe(glob, { ...options, expandRange: (a, b) => `(${FillRange(a, b, { toRegex: true })})` });
|
|
45
45
|
exports.globToRegexp = (glob, options = {}) => PicoMatch.toRegex(exports.globToRegex(glob, options));
|
|
46
46
|
exports.toGUID = (model, id) => Buffer.from(`${model},${`${id}`}`).toString('base64');
|
|
@@ -130,9 +130,6 @@ exports.castCmp = (type, value) => {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
/**
|
|
134
|
-
* Returns true if b is a subset of a
|
|
135
|
-
*/
|
|
136
133
|
exports.objectContaining = (a, b) => {
|
|
137
134
|
if (a === b) return true;
|
|
138
135
|
|
|
@@ -140,7 +137,7 @@ exports.objectContaining = (a, b) => {
|
|
|
140
137
|
return exports.keyPathLeafs(b).every((leaf) => {
|
|
141
138
|
const $a = _.get(a, leaf, { a: 'a' });
|
|
142
139
|
const $b = _.get(b, leaf, { b: 'b' });
|
|
143
|
-
if (Array.isArray($b)) return $b.
|
|
140
|
+
if (Array.isArray($b)) return $b.some(bb => exports.ensureArray($a).some(aa => exports.objectContaining(aa, bb)));
|
|
144
141
|
if (exports.isScalarValue($a) && exports.isScalarValue($b)) return PicoMatch.isMatch(`${$a}`, `${$b}`, { nocase: true });
|
|
145
142
|
return exports.hashObject($a) === exports.hashObject($b);
|
|
146
143
|
});
|
|
@@ -149,23 +146,6 @@ exports.objectContaining = (a, b) => {
|
|
|
149
146
|
return exports.hashObject(a) === exports.hashObject(b);
|
|
150
147
|
};
|
|
151
148
|
|
|
152
|
-
/**
|
|
153
|
-
* Returns true if (b intersection a) are equal
|
|
154
|
-
*/
|
|
155
|
-
exports.objectIntersectionEqual = (a, b) => {
|
|
156
|
-
if (a === b) return true;
|
|
157
|
-
|
|
158
|
-
if (exports.isPlainObject(b)) {
|
|
159
|
-
return exports.keyPathLeafs(b).every((leaf) => {
|
|
160
|
-
const $a = _.get(a, leaf, { a: 'a' });
|
|
161
|
-
const $b = _.get(b, leaf, { b: 'b' });
|
|
162
|
-
return exports.hashObject($a) === exports.hashObject($b);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return exports.hashObject(a) === exports.hashObject(b);
|
|
167
|
-
};
|
|
168
|
-
|
|
169
149
|
/**
|
|
170
150
|
* Transform an object with dot.notation keys into an expanded object.
|
|
171
151
|
* eg. { 'user.name': 'richard' } => { user: { name: 'richard' } }
|
|
@@ -3,53 +3,6 @@
|
|
|
3
3
|
// https://graphql.org/graphql-js/utilities/
|
|
4
4
|
|
|
5
5
|
const { uniqWith } = require('lodash');
|
|
6
|
-
const { GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, GraphQLEnumType, GraphQLInputObjectType } = require('graphql');
|
|
7
|
-
|
|
8
|
-
exports.getSchemaData = (schema) => {
|
|
9
|
-
const operations = ['Query', 'Mutation', 'Subscription'];
|
|
10
|
-
|
|
11
|
-
return Object.entries(schema.getTypeMap()).reduce((prev, [key, value]) => {
|
|
12
|
-
let type;
|
|
13
|
-
|
|
14
|
-
if (value instanceof GraphQLScalarType) {
|
|
15
|
-
type = 'scalars';
|
|
16
|
-
} else if (value instanceof GraphQLEnumType) {
|
|
17
|
-
type = 'enums';
|
|
18
|
-
} else if (value instanceof GraphQLUnionType) {
|
|
19
|
-
type = 'unions';
|
|
20
|
-
} else if (value instanceof GraphQLInterfaceType) {
|
|
21
|
-
type = 'interfaces';
|
|
22
|
-
} else if (value instanceof GraphQLInputObjectType) {
|
|
23
|
-
type = 'inputs';
|
|
24
|
-
} else if (value instanceof GraphQLObjectType) {
|
|
25
|
-
if (operations.includes(key)) {
|
|
26
|
-
type = 'operations';
|
|
27
|
-
} else {
|
|
28
|
-
type = 'models';
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (type) {
|
|
33
|
-
if (!key.startsWith('__')) {
|
|
34
|
-
prev[type][key] = value;
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
console.log(`Unknown schema type { ${key}: ${value} }`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return prev;
|
|
41
|
-
}, {
|
|
42
|
-
enums: {},
|
|
43
|
-
models: {},
|
|
44
|
-
inputs: {},
|
|
45
|
-
unions: {},
|
|
46
|
-
scalars: {},
|
|
47
|
-
operations: {},
|
|
48
|
-
directives: {},
|
|
49
|
-
interfaces: {},
|
|
50
|
-
enumerations: {},
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
6
|
|
|
54
7
|
exports.identifyOnDeletes = (models, parentModel) => {
|
|
55
8
|
return models.reduce((prev, model) => {
|