@coderich/autograph 0.12.0 → 0.12.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 +12 -14
- package/src/core/EventEmitter.js +4 -4
- package/src/data/DataTransaction.js +84 -149
- package/src/data/Model.js +11 -2
- package/src/data/Pipeline.js +15 -24
- package/src/data/Type.js +1 -1
- package/src/driver/MongoDriver.js +26 -28
- package/src/driver/index.js +0 -1
- package/src/graphql/ast/Node.js +2 -3
- package/src/graphql/ast/Schema.js +9 -13
- package/src/graphql/extension/api.js +4 -4
- package/src/graphql/extension/framework.js +13 -3
- package/src/query/Query.js +7 -9
- package/src/query/QueryResolver.js +15 -4
- package/src/query/QueryService.js +5 -1
- package/src/service/app.service.js +1 -1
- package/src/service/schema.service.js +0 -47
- package/CHANGELOG.md +0 -41
- package/src/.DS_Store +0 -0
- package/src/core/.DS_Store +0 -0
- package/src/data/.DS_Store +0 -0
- package/src/driver/.DS_Store +0 -0
- package/src/graphql/.DS_Store +0 -0
- package/src/graphql/ast/.DS_Store +0 -0
- package/src/graphql/extension/.DS_Store +0 -0
- package/src/query/.DS_Store +0 -0
- package/src/service/.DS_Store +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderich/autograph",
|
|
3
3
|
"author": "Richard Livolsi (coderich)",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.2",
|
|
5
5
|
"description": "AutoGraph",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"graphql",
|
|
@@ -19,34 +19,32 @@
|
|
|
19
19
|
"index.js"
|
|
20
20
|
],
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=22.0.0"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"start": "APP_ROOT_PATH=$(pwd) node ./test/server",
|
|
26
|
-
"test": "
|
|
27
|
-
"
|
|
28
|
-
"lint": "
|
|
29
|
-
"inspect": "APP_ROOT_PATH=$(pwd) node --expose-gc --inspect=9222 ./src/server",
|
|
30
|
-
"ratchet": "ratchet"
|
|
26
|
+
"test": "jest --config=jest.config.js",
|
|
27
|
+
"dev": "coderich-dev",
|
|
28
|
+
"lint": "eslint ./"
|
|
31
29
|
},
|
|
32
30
|
"dependencies": {
|
|
31
|
+
"@coderich/util": "2.0.1",
|
|
33
32
|
"@hapi/boom": "^9.1.0",
|
|
34
33
|
"dataloader": "^2.0.0",
|
|
35
34
|
"deepmerge": "^4.2.2",
|
|
36
35
|
"fill-range": "^7.0.1",
|
|
37
|
-
"
|
|
38
|
-
"glob": "^7.1.6",
|
|
36
|
+
"glob": "10.4.5",
|
|
39
37
|
"graphql-fields": "^2.0.3",
|
|
40
38
|
"lodash": "^4.17.21",
|
|
41
|
-
"mongodb": "
|
|
42
|
-
"object-hash": "^
|
|
39
|
+
"mongodb": "6.9.0",
|
|
40
|
+
"object-hash": "^3.0.0",
|
|
43
41
|
"picomatch": "^2.1.1"
|
|
44
42
|
},
|
|
45
43
|
"devDependencies": {
|
|
46
|
-
"@coderich/
|
|
44
|
+
"@coderich/dev": "0.5.1",
|
|
47
45
|
"@graphql-tools/schema": "^9.0.1",
|
|
48
46
|
"graphql": "^15.5.0",
|
|
49
|
-
"mongodb-memory-server": "
|
|
47
|
+
"mongodb-memory-server": "10.0.0",
|
|
50
48
|
"validator": "^13.7.0"
|
|
51
49
|
},
|
|
52
50
|
"peerDependencies": {
|
|
@@ -56,4 +54,4 @@
|
|
|
56
54
|
"type": "git",
|
|
57
55
|
"url": "git@github.com:coderich/autograph.git"
|
|
58
56
|
}
|
|
59
|
-
}
|
|
57
|
+
}
|
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, base) {
|
|
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;
|
|
@@ -170,7 +173,13 @@ module.exports = class extends Model {
|
|
|
170
173
|
if (!instructed && subShape && typeof transformedValue !== 'object') return prev;
|
|
171
174
|
|
|
172
175
|
// Rename key & assign value
|
|
173
|
-
prev[to] = (!subShape || transformedValue == null) ? transformedValue : this.shapeObject(subShape, transformedValue, query, root, base);
|
|
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
|
+
}
|
|
174
183
|
|
|
175
184
|
return prev;
|
|
176
185
|
}, {});
|
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,12 +1,13 @@
|
|
|
1
1
|
const Util = require('util');
|
|
2
|
-
const Flat = require('flat');
|
|
3
2
|
const { get } = require('lodash');
|
|
3
|
+
const { unflatten } = require('@coderich/util');
|
|
4
4
|
const { MongoClient, ObjectId } = require('mongodb');
|
|
5
5
|
const { map, ensureArray, proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
|
|
6
6
|
|
|
7
7
|
module.exports = class MongoDriver {
|
|
8
8
|
constructor(config) {
|
|
9
9
|
this.config = config;
|
|
10
|
+
this.config.query = this.config.query || {};
|
|
10
11
|
this.connection = this.connect();
|
|
11
12
|
this.getDirectives = () => get(config, 'directives', {});
|
|
12
13
|
}
|
|
@@ -28,7 +29,7 @@ module.exports = class MongoDriver {
|
|
|
28
29
|
query(collection, method, ...args) {
|
|
29
30
|
if (get(args[args.length - 1], 'debug') === true) console.log(collection, method, Util.inspect(args, { depth: null, showHidden: false, colors: true }));
|
|
30
31
|
if (method === 'aggregate') args.splice(2);
|
|
31
|
-
return this.raw(collection)[method](
|
|
32
|
+
return this.raw(collection)[method](args[0], args[1]);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
resolve(query) {
|
|
@@ -49,15 +50,15 @@ module.exports = class MongoDriver {
|
|
|
49
50
|
|
|
50
51
|
findMany(query) {
|
|
51
52
|
const { model, options = {}, flags } = query;
|
|
52
|
-
|
|
53
|
-
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());
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
count(query) {
|
|
57
58
|
const { model, options = {}, flags } = query;
|
|
58
|
-
|
|
59
|
+
const $options = { ...this.config.query, ...options };
|
|
59
60
|
|
|
60
|
-
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) => {
|
|
61
62
|
return cursor.next().then((doc) => {
|
|
62
63
|
return doc ? doc.count : 0;
|
|
63
64
|
});
|
|
@@ -69,8 +70,8 @@ module.exports = class MongoDriver {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
updateOne({ model, where, $doc, options, flags }) {
|
|
72
|
-
const $update = { $set:
|
|
73
|
-
return this.query(model, 'updateOne', where, $update, options, flags).then(() => $doc);
|
|
73
|
+
const $update = { $set: $doc };
|
|
74
|
+
return this.query(model, 'updateOne', where, $update, options, flags).then(() => unflatten($doc, { safe: true }));
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
deleteOne({ model, where, options, flags }) {
|
|
@@ -97,25 +98,23 @@ module.exports = class MongoDriver {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
transaction(ops) {
|
|
100
|
-
|
|
101
|
+
return promiseRetry(() => {
|
|
101
102
|
// Create session and start transaction
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
103
|
+
return this.connection.then(client => client.startSession({ readPreference: { mode: 'primary' } })).then((session) => {
|
|
104
|
+
session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } });
|
|
105
|
+
const close = () => { session.endSession(); };
|
|
106
|
+
|
|
107
|
+
// Execute each operation with session
|
|
108
|
+
return Promise.all(ops.map(op => op.exec({ session }))).then((results) => {
|
|
109
|
+
results.$commit = () => session.commitTransaction().then(close);
|
|
110
|
+
results.$rollback = () => session.abortTransaction().then(close);
|
|
111
|
+
return results;
|
|
112
|
+
}).catch((e) => {
|
|
113
|
+
close();
|
|
114
|
+
throw e;
|
|
115
|
+
});
|
|
114
116
|
});
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Retry promise conditionally
|
|
118
|
-
return promiseRetry(promise, 200, 5, e => e.errorLabels && e.errorLabels.indexOf('TransientTransactionError') > -1);
|
|
117
|
+
}, 200, 5, e => e.errorLabels && e.errorLabels.indexOf('TransientTransactionError') > -1);
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
static idKey() {
|
|
@@ -126,9 +125,9 @@ module.exports = class MongoDriver {
|
|
|
126
125
|
if (value instanceof ObjectId) return value;
|
|
127
126
|
|
|
128
127
|
try {
|
|
129
|
-
const id = ObjectId(value);
|
|
128
|
+
const id = new ObjectId(value);
|
|
130
129
|
return id;
|
|
131
|
-
} catch
|
|
130
|
+
} catch {
|
|
132
131
|
return value;
|
|
133
132
|
}
|
|
134
133
|
}
|
|
@@ -140,7 +139,6 @@ module.exports = class MongoDriver {
|
|
|
140
139
|
if (typeof value === 'function') return value.bind(target);
|
|
141
140
|
const $value = map(value, v => (typeof v === 'string' ? globToRegex(v, { nocase: true, regex: true }) : v));
|
|
142
141
|
if (Array.isArray($value)) {
|
|
143
|
-
// console.log(Util.inspect({ value, $value }, { depth: null, showHidden: false, colors: true }));
|
|
144
142
|
return { $in: $value };
|
|
145
143
|
}
|
|
146
144
|
return $value;
|
package/src/driver/index.js
CHANGED
package/src/graphql/ast/Node.js
CHANGED
|
@@ -117,7 +117,7 @@ module.exports = class Node {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
getVirtualRef() {
|
|
120
|
-
return this.getDirectiveArg('
|
|
120
|
+
return this.getDirectiveArg('link', 'by');
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
getAuthz() {
|
|
@@ -153,7 +153,7 @@ module.exports = class Node {
|
|
|
153
153
|
* Is the field virtual; does it's value come from another model
|
|
154
154
|
*/
|
|
155
155
|
isVirtual() {
|
|
156
|
-
return Boolean(this.getDirectiveArg('
|
|
156
|
+
return Boolean(this.getDirectiveArg('link', 'by'));
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
@@ -268,7 +268,6 @@ module.exports = class Node {
|
|
|
268
268
|
return Boolean(this.getDirectiveArg('field', 'resolve'));
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
|
|
272
271
|
// Create
|
|
273
272
|
isCreatable() {
|
|
274
273
|
return Boolean(this.getDALScope().toLowerCase().indexOf('c') > -1);
|
|
@@ -50,7 +50,7 @@ module.exports = class Schema extends TypeDefApi {
|
|
|
50
50
|
try {
|
|
51
51
|
const ast = typeof td === 'object' ? td : parse(td);
|
|
52
52
|
return ast.definitions;
|
|
53
|
-
} catch
|
|
53
|
+
} catch {
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
56
|
}), ['loc']).filter(Boolean).flat();
|
|
@@ -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.glob(globPattern, options).then((files) => {
|
|
74
|
+
return Promise.all(files.sort().map((file) => {
|
|
75
|
+
return new Promise((res) => {
|
|
76
|
+
if (file.endsWith('.js')) res(require(file));
|
|
77
|
+
else res(FS.readFileSync(file, 'utf8'));
|
|
78
|
+
}).then(schema => this.mergeSchema(schema, options));
|
|
79
|
+
}));
|
|
80
|
+
}).then(() => this);
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
/**
|
|
@@ -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;
|
|
@@ -150,7 +150,7 @@ module.exports = (schema) => {
|
|
|
150
150
|
|
|
151
151
|
if (model.isEntity() && model.hasGQLScope('s')) {
|
|
152
152
|
prev[`${model.getName()}SubscriptionQuery`] = {
|
|
153
|
-
__resolveType: root => root.__typename,
|
|
153
|
+
__resolveType: root => root.__typename,
|
|
154
154
|
...fieldResolvers,
|
|
155
155
|
};
|
|
156
156
|
prev[`${model.getName()}Create`] = fieldResolvers;
|
|
@@ -167,7 +167,7 @@ module.exports = (schema) => {
|
|
|
167
167
|
});
|
|
168
168
|
}, {
|
|
169
169
|
Node: {
|
|
170
|
-
__resolveType: (doc, args, context, info) => doc.__typename,
|
|
170
|
+
__resolveType: (doc, args, context, info) => doc.__typename,
|
|
171
171
|
},
|
|
172
172
|
|
|
173
173
|
Query: entityModels.reduce((prev, model) => {
|
|
@@ -179,7 +179,7 @@ module.exports = (schema) => {
|
|
|
179
179
|
const model = schema.getModel(modelName);
|
|
180
180
|
return resolver.get(context, model, args, false, info).then((result) => {
|
|
181
181
|
if (result == null) return result;
|
|
182
|
-
result.__typename = modelName;
|
|
182
|
+
result.__typename = modelName;
|
|
183
183
|
return result;
|
|
184
184
|
});
|
|
185
185
|
},
|
|
@@ -27,6 +27,12 @@ module.exports = (schema) => {
|
|
|
27
27
|
fieldScope: AutoGraphMixed # Dictate how a FIELD may use me
|
|
28
28
|
authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
|
|
29
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")
|
|
30
36
|
) on OBJECT | INTERFACE
|
|
31
37
|
|
|
32
38
|
directive @field(
|
|
@@ -53,12 +59,16 @@ module.exports = (schema) => {
|
|
|
53
59
|
transform: [AutoGraphPipelineEnum!]
|
|
54
60
|
serialize: [AutoGraphPipelineEnum!]
|
|
55
61
|
deserialize: [AutoGraphPipelineEnum!]
|
|
62
|
+
|
|
63
|
+
# FOR TRANSITION TO NEW VERSION
|
|
64
|
+
crud: AutoGraphMixed # CRUD API
|
|
65
|
+
finalize: [AutoGraphPipelineEnum!]
|
|
56
66
|
) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
|
|
57
67
|
|
|
58
|
-
directive @
|
|
59
|
-
to: AutoGraphMixed # The MODEL to
|
|
68
|
+
directive @link(
|
|
69
|
+
to: AutoGraphMixed # The MODEL to link to (default's to modelRef)
|
|
60
70
|
by: AutoGraphMixed! # The FIELD to match yourself by
|
|
61
|
-
use: AutoGraphMixed # The VALUE to use (default's to @
|
|
71
|
+
use: AutoGraphMixed # The VALUE to use (default's to @link'd value); useful for many-to-many relationships
|
|
62
72
|
) on FIELD_DEFINITION
|
|
63
73
|
|
|
64
74
|
directive @index(
|
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;
|
|
@@ -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 { mergeDeep, getGQLReturnType } = require('../service/app.service');
|
|
6
|
+
const { mergeDeep, hashObject, getGQLReturnType } = require('../service/app.service');
|
|
7
7
|
|
|
8
8
|
module.exports = class QueryResolver {
|
|
9
9
|
constructor(query) {
|
|
@@ -76,17 +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
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;
|
|
85
90
|
|
|
86
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
|
|
87
96
|
const payload = model.shapeObject(inputShape, merged, query);
|
|
88
97
|
await model.validateObject(inputShape, payload, query.payload(payload));
|
|
89
|
-
|
|
98
|
+
const $doc = model.shapeObject(docShape, payload, query, undefined, undefined, true);
|
|
99
|
+
return this.resolver.resolve(query.$doc($doc));
|
|
90
100
|
});
|
|
91
101
|
});
|
|
92
102
|
}
|
|
@@ -185,7 +195,8 @@ module.exports = class QueryResolver {
|
|
|
185
195
|
// Can only splice arrays
|
|
186
196
|
const field = model.getField(key);
|
|
187
197
|
const isArray = field.isArray();
|
|
188
|
-
|
|
198
|
+
const path = `${model}.${field}`;
|
|
199
|
+
if (!isArray) throw Boom.badRequest(`Cannot splice field '${path}'`, { path });
|
|
189
200
|
|
|
190
201
|
return this.resolver.match(model).match(match).one({ required: true }).then(async (doc) => {
|
|
191
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');
|
|
@@ -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) => {
|
package/CHANGELOG.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# CHANGELOG
|
|
2
|
-
|
|
3
|
-
## v0.10.x
|
|
4
|
-
- Replaced ResultSet -> POJOs
|
|
5
|
-
- Removed all $field methods (auto populated)
|
|
6
|
-
- Removed .toObject()
|
|
7
|
-
- $model $save remove $delete $lookup $cursor $pageInfo
|
|
8
|
-
- Removed embedded API completely
|
|
9
|
-
- Removed Directives
|
|
10
|
-
- embedApi -> no replacement
|
|
11
|
-
- enforce -> use pipeline methods
|
|
12
|
-
- resolve -> use graphql resolvers
|
|
13
|
-
- @value -> use @field.instruct directive
|
|
14
|
-
- Removed Model.tform() -> use Model.shapeObject(shape, data)
|
|
15
|
-
- Removed Transformer + Rule -> use Pipeline
|
|
16
|
-
- Removed many pre-defined rules + transformers
|
|
17
|
-
- Moved "validator" to dev dependency -> isEmail
|
|
18
|
-
- Added QueryBuilder.resolve() terminal command
|
|
19
|
-
- Exported SchemaDecorator -> Schema
|
|
20
|
-
- Removed embedded schema SystemEvents (internal emitter also removed)
|
|
21
|
-
- Removed spread of arguments in QueryBuilder terminal commands (must pass in array)
|
|
22
|
-
- Mutate "merged" instead of "input"
|
|
23
|
-
- Validate "payload"
|
|
24
|
-
|
|
25
|
-
## v0.9.x
|
|
26
|
-
- Subscriptions API
|
|
27
|
-
- postMutation no longer mutates "doc" and adds "result"
|
|
28
|
-
- Added onDelete defer option
|
|
29
|
-
|
|
30
|
-
## v0.8.x
|
|
31
|
-
- Engine 14+
|
|
32
|
-
|
|
33
|
-
## v0.7.x
|
|
34
|
-
- Complete overhaul of Query to Mongo Driver (pagination, sorting, counts, etc)
|
|
35
|
-
- Removed countModel Queries from the API (now available as `count` property on `Connetion` types)
|
|
36
|
-
- Dropped Neo4J (temporarily)
|
|
37
|
-
|
|
38
|
-
## v0.6.x
|
|
39
|
-
- Mongo driver no longer checks for `version` directive
|
|
40
|
-
- Models no longer share a Connection type; removing the need to use `... on Model` for GraphQL queries
|
|
41
|
-
- Added `@field(connection: Boolean)` parameter to specifically indicate fields that should return a Connection type
|
package/src/.DS_Store
DELETED
|
Binary file
|
package/src/core/.DS_Store
DELETED
|
Binary file
|
package/src/data/.DS_Store
DELETED
|
Binary file
|
package/src/driver/.DS_Store
DELETED
|
Binary file
|
package/src/graphql/.DS_Store
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/query/.DS_Store
DELETED
|
Binary file
|
package/src/service/.DS_Store
DELETED
|
Binary file
|