@coderich/autograph 0.8.13 → 0.9.0
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 +4 -0
- package/package.json +1 -1
- package/src/core/EventEmitter.js +4 -2
- package/src/core/Resolver.js +18 -8
- package/src/data/DataLoader.js +29 -71
- package/src/data/Field.js +10 -0
- package/src/data/Model.js +12 -2
- package/src/data/ResultSet.js +128 -133
- package/src/data/ResultSet2.js +210 -0
- package/src/data/ResultSet3.js +186 -0
- package/src/graphql/ast/Node.js +2 -2
- package/src/graphql/extension/api.js +40 -8
- package/src/graphql/extension/type.js +2 -0
- package/src/query/Query.js +1 -1
- package/src/service/decorator.service.js +13 -0
- package/src/service/event.service.js +4 -6
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/core/EventEmitter.js
CHANGED
|
@@ -8,7 +8,7 @@ const { ensureArray } = require('../service/app.service');
|
|
|
8
8
|
* If it expects more than 1 we block and wait for it to finish.
|
|
9
9
|
*/
|
|
10
10
|
module.exports = class extends EventEmitter {
|
|
11
|
-
emit(event, data) {
|
|
11
|
+
emit(event, data, prev) {
|
|
12
12
|
return Promise.all(this.rawListeners(event).map((wrapper) => {
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
14
14
|
const next = result => resolve(result); // If a result is passed this will bypass middleware thunk()
|
|
@@ -17,7 +17,9 @@ module.exports = class extends EventEmitter {
|
|
|
17
17
|
if (numArgs < 2) next();
|
|
18
18
|
});
|
|
19
19
|
})).then((results) => {
|
|
20
|
-
|
|
20
|
+
if (event === 'preMutation') return this.emit('validate', data, results);
|
|
21
|
+
const target = event === 'validate' ? prev : results;
|
|
22
|
+
return target.find(r => r !== undefined); // There can be only one (result)
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
25
|
|
package/src/core/Resolver.js
CHANGED
|
@@ -7,14 +7,10 @@ const QueryBuilder = require('../query/QueryBuilder');
|
|
|
7
7
|
|
|
8
8
|
module.exports = class Resolver {
|
|
9
9
|
constructor(schema, context = {}) {
|
|
10
|
+
this.models = schema.getModels();
|
|
10
11
|
this.schema = schema;
|
|
11
12
|
this.context = context;
|
|
12
|
-
this.
|
|
13
|
-
|
|
14
|
-
// DataLoader Proxy Methods
|
|
15
|
-
this.clear = key => this.loader.clear(key);
|
|
16
|
-
this.clearAll = () => this.loader.clearAll();
|
|
17
|
-
this.prime = (key, value) => this.loader.prime(key, value);
|
|
13
|
+
this.loaders = this.models.reduce((prev, model) => prev.set(model, new DataLoader(this, model)), new WeakMap());
|
|
18
14
|
|
|
19
15
|
//
|
|
20
16
|
this.getSchema = () => this.schema;
|
|
@@ -55,15 +51,18 @@ module.exports = class Resolver {
|
|
|
55
51
|
switch (crud) {
|
|
56
52
|
case 'create': case 'update': case 'delete': {
|
|
57
53
|
return model.getDriver().resolve(query.toDriver()).then((data) => {
|
|
58
|
-
this.
|
|
54
|
+
this.clear(model);
|
|
59
55
|
return new ResultSet(query, data);
|
|
60
56
|
});
|
|
61
57
|
}
|
|
62
58
|
default: {
|
|
59
|
+
// This is needed in SF tests...
|
|
63
60
|
const key = model.idKey();
|
|
64
61
|
const { where } = query.toDriver();
|
|
65
62
|
if (Object.prototype.hasOwnProperty.call(where, key) && where[key] == null) return Promise.resolve(null);
|
|
66
|
-
|
|
63
|
+
|
|
64
|
+
//
|
|
65
|
+
return this.loaders.get(model).load(query);
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
68
|
}
|
|
@@ -90,4 +89,15 @@ module.exports = class Resolver {
|
|
|
90
89
|
toResultSet(model, data) {
|
|
91
90
|
return new ResultSet(new Query({ model: this.toModel(model), resolver: this }), data);
|
|
92
91
|
}
|
|
92
|
+
|
|
93
|
+
// DataLoader Proxy Methods
|
|
94
|
+
clear(model) {
|
|
95
|
+
this.loaders.get(this.toModel(model)).clearAll();
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
clearAll() {
|
|
100
|
+
this.models.forEach(model => this.clear(model));
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
93
103
|
};
|
package/src/data/DataLoader.js
CHANGED
|
@@ -1,84 +1,42 @@
|
|
|
1
|
-
// const { flatten } = require('lodash');
|
|
2
1
|
const FBDataLoader = require('dataloader');
|
|
3
2
|
const ResultSet = require('./ResultSet');
|
|
3
|
+
const Query = require('../query/Query');
|
|
4
4
|
const { hashObject } = require('../service/app.service');
|
|
5
5
|
|
|
6
6
|
// let counter = 0;
|
|
7
7
|
module.exports = class DataLoader extends FBDataLoader {
|
|
8
|
-
constructor() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// console.time(timeID);
|
|
12
|
-
|
|
13
|
-
// const queriesByModel = queries.reduce((prev, query, i) => {
|
|
14
|
-
// const toDriver = query.toDriver();
|
|
15
|
-
// const { model, method } = query.toObject();
|
|
16
|
-
// const key = model.idKey();
|
|
17
|
-
// prev[model] = (prev[model] || { query, model, key, get: {}, find: [] });
|
|
18
|
-
// toDriver.$index = i;
|
|
19
|
-
|
|
20
|
-
// if (method === 'findOne' && Object.prototype.hasOwnProperty.call(toDriver.where, key) && !Array.isArray(toDriver.where[key])) {
|
|
21
|
-
// const { [key]: id, ...rest } = toDriver.where;
|
|
22
|
-
// const hash = hashObject(rest);
|
|
23
|
-
// prev[model].get[hash] = prev[model].get[hash] || [];
|
|
24
|
-
// prev[model].get[hash].push(toDriver);
|
|
25
|
-
// return prev;
|
|
26
|
-
// }
|
|
27
|
-
|
|
28
|
-
// prev[model].find.push(toDriver);
|
|
29
|
-
// return prev;
|
|
30
|
-
// }, {});
|
|
31
|
-
|
|
32
|
-
// return new Promise((resolve, reject) => {
|
|
33
|
-
// const results = [];
|
|
34
|
-
|
|
35
|
-
// Promise.all(Object.values(queriesByModel).map(({ query, model, key, get, find }) => {
|
|
36
|
-
// return Promise.all(flatten([
|
|
37
|
-
// ...find.map(q => model.getDriver().resolve(q)),
|
|
38
|
-
// ...Object.values(get).map((set) => {
|
|
39
|
-
// const ids = [...new Set(set.map(({ where }) => where[key]))];
|
|
40
|
-
// const toDriver = { ...set[0] };
|
|
41
|
-
// toDriver.method = 'findMany';
|
|
42
|
-
// toDriver.where[key] = ids;
|
|
43
|
-
// return model.getDriver().resolve(toDriver).then(data => (typeof data === 'object' ? new ResultSet(query, data) : data));
|
|
44
|
-
// // return model.getDriver().resolve(toDriver);
|
|
45
|
-
// }),
|
|
46
|
-
// ]));
|
|
47
|
-
// })).then((resultsByModel) => {
|
|
48
|
-
// console.timeEnd(timeID);
|
|
49
|
-
// resultsByModel.forEach((modelResults, i) => {
|
|
50
|
-
// const { key, get, find } = Object.values(queriesByModel)[i];
|
|
8
|
+
constructor(resolver, model) {
|
|
9
|
+
const idKey = model.idKey();
|
|
10
|
+
const driver = model.getDriver();
|
|
51
11
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
12
|
+
return new FBDataLoader((queries) => {
|
|
13
|
+
// The idea is to group the "findOne by id" queries together to make 1 query instead
|
|
14
|
+
const { findOneByIdQueries, allOtherQueries } = queries.reduce((prev, query, i) => {
|
|
15
|
+
const { id, method } = query.toObject();
|
|
16
|
+
const key = method === 'findOne' && id ? 'findOneByIdQueries' : 'allOtherQueries';
|
|
17
|
+
prev[key].push({ id, query, i });
|
|
18
|
+
return prev;
|
|
19
|
+
}, { findOneByIdQueries: [], allOtherQueries: [] });
|
|
20
|
+
|
|
21
|
+
// Aggregate ids
|
|
22
|
+
const ids = Array.from(new Set(findOneByIdQueries.map(el => `${el.id}`)));
|
|
23
|
+
const batchQuery = new Query({ resolver, model, method: 'findMany', crud: 'read' });
|
|
24
|
+
const batchWhere = model.transform(batchQuery, { id: ids }, 'serialize', true);
|
|
25
|
+
const promises = [Promise.all(allOtherQueries.map(({ query, i }) => driver.resolve(query.toDriver()).then(data => ({ data, query, i }))))];
|
|
26
|
+
if (ids.length) promises.push(driver.resolve(batchQuery.where(batchWhere).toDriver()).then(results => findOneByIdQueries.map(({ query, id, i }) => ({ i, query, data: results.find(r => `${r[idKey]}` === `${id}`) || null }))));
|
|
27
|
+
|
|
28
|
+
return Promise.all(promises).then((results) => {
|
|
29
|
+
const sorted = results.flat().filter(Boolean).sort((a, b) => a.i - b.i);
|
|
30
|
+
return sorted.map(({ query, data }) => (data != null && typeof data === 'object' ? new ResultSet(query, data) : data));
|
|
31
|
+
});
|
|
63
32
|
|
|
64
|
-
//
|
|
65
|
-
//
|
|
33
|
+
// return Promise.all(queries.map((query) => {
|
|
34
|
+
// return driver.resolve(query.toDriver()).then((data) => {
|
|
35
|
+
// return (data != null && typeof data === 'object' ? new ResultSet(query, data) : data);
|
|
66
36
|
// });
|
|
67
|
-
// });
|
|
68
|
-
|
|
69
|
-
return Promise.all(queries.map((query) => {
|
|
70
|
-
const { model } = query.toObject();
|
|
71
|
-
return model.getDriver().resolve(query.toDriver()).then((data) => {
|
|
72
|
-
return (data != null && typeof data === 'object' ? new ResultSet(query, data) : data);
|
|
73
|
-
});
|
|
74
|
-
})).then((results) => {
|
|
75
|
-
// console.timeEnd(timeID);
|
|
76
|
-
// console.log(new Date().getTime());
|
|
77
|
-
return results;
|
|
78
|
-
});
|
|
37
|
+
// }));
|
|
79
38
|
}, {
|
|
80
|
-
|
|
81
|
-
// maxBatchSize: 50,
|
|
39
|
+
cache: true,
|
|
82
40
|
cacheKeyFn: query => hashObject(query.getCacheKey()),
|
|
83
41
|
});
|
|
84
42
|
}
|
package/src/data/Field.js
CHANGED
|
@@ -137,4 +137,14 @@ module.exports = class extends Field {
|
|
|
137
137
|
return uvl(this.cast(results.pop()), value);
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
|
+
|
|
141
|
+
tform(query, value) {
|
|
142
|
+
// Determine transformers
|
|
143
|
+
const transformers = this.getTransformers();
|
|
144
|
+
|
|
145
|
+
// Transform
|
|
146
|
+
return transformers.reduce((prev, transformer) => {
|
|
147
|
+
return transformer(this, prev, query);
|
|
148
|
+
}, this.cast(value));
|
|
149
|
+
}
|
|
140
150
|
};
|
package/src/data/Model.js
CHANGED
|
@@ -115,7 +115,7 @@ module.exports = class extends Model {
|
|
|
115
115
|
// Transform all the data
|
|
116
116
|
return map(data, (doc) => {
|
|
117
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(
|
|
118
|
+
const fields = [...new Set(appendFields.concat(Object.keys(doc).map(k => this.getField(k))))].filter(Boolean);
|
|
119
119
|
|
|
120
120
|
// Loop through the fields and delegate (renaming keys appropriately)
|
|
121
121
|
return fields.reduce((prev, field) => {
|
|
@@ -129,7 +129,7 @@ module.exports = class extends Model {
|
|
|
129
129
|
normalize(query, data, serdes = (() => { throw new Error('No Sir Sir SerDes!'); }), keysOnly = false) {
|
|
130
130
|
// Transform all the data
|
|
131
131
|
return map(data, (doc) => {
|
|
132
|
-
const fields = Object.keys(doc).map(k => this.getField(k)).filter(
|
|
132
|
+
const fields = Object.keys(doc).map(k => this.getField(k)).filter(Boolean);
|
|
133
133
|
|
|
134
134
|
// Loop through the fields and delegate (renaming keys appropriately)
|
|
135
135
|
return fields.reduce((prev, field) => {
|
|
@@ -150,4 +150,14 @@ module.exports = class extends Model {
|
|
|
150
150
|
})));
|
|
151
151
|
}));
|
|
152
152
|
}
|
|
153
|
+
|
|
154
|
+
tform(query, data) {
|
|
155
|
+
return map(data, (doc) => {
|
|
156
|
+
return Object.keys(doc).map(k => this.getField(k)).filter(Boolean).reduce((prev, curr) => {
|
|
157
|
+
const key = curr.getName();
|
|
158
|
+
const value = doc[key];
|
|
159
|
+
return Object.assign(prev, { [key]: curr.tform(query, value) });
|
|
160
|
+
}, {});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
153
163
|
};
|
package/src/data/ResultSet.js
CHANGED
|
@@ -13,162 +13,157 @@ module.exports = class ResultSet {
|
|
|
13
13
|
//
|
|
14
14
|
const cache = new Map();
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
configurable: true, // Allows things like delete
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Hydrated field attributes
|
|
41
|
-
prev[`$${name}`] = {
|
|
42
|
-
get() {
|
|
43
|
-
return (args = {}) => {
|
|
44
|
-
// Ensure where clause
|
|
45
|
-
args.where = args.where || {};
|
|
46
|
-
|
|
47
|
-
// Cache
|
|
48
|
-
const cacheKey = `${$name}-${hashObject(args)}`;
|
|
49
|
-
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
16
|
+
const definition = fields.reduce((prev, field) => {
|
|
17
|
+
const key = field.getKey();
|
|
18
|
+
const name = field.getName();
|
|
19
|
+
const $name = `$${name}`;
|
|
20
|
+
const value = doc[key];
|
|
21
|
+
|
|
22
|
+
// Field attributes
|
|
23
|
+
prev[name] = {
|
|
24
|
+
get() {
|
|
25
|
+
if (cache.has(name)) return cache.get(name);
|
|
26
|
+
let $value = field.deserialize(query, value);
|
|
27
|
+
$value = $value != null && field.isEmbedded() ? new ResultSet(query.model(field.getModelRef()), $value, false) : $value;
|
|
28
|
+
cache.set(name, $value);
|
|
29
|
+
return $value;
|
|
30
|
+
},
|
|
31
|
+
set($value) {
|
|
32
|
+
cache.set(name, $value);
|
|
33
|
+
},
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true, // Allows things like delete
|
|
36
|
+
};
|
|
50
37
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
// Hydrated field attributes
|
|
39
|
+
prev[`$${name}`] = {
|
|
40
|
+
get() {
|
|
41
|
+
return (args = {}) => {
|
|
42
|
+
// Ensure where clause
|
|
43
|
+
args.where = args.where || {};
|
|
54
44
|
|
|
55
|
-
|
|
45
|
+
// Cache
|
|
46
|
+
const cacheKey = `${$name}-${hashObject(args)}`;
|
|
47
|
+
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
const promise = new Promise((resolve, reject) => {
|
|
50
|
+
(() => {
|
|
51
|
+
const $value = this[name];
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
if (field.isVirtual()) {
|
|
61
|
-
args.where[[field.getVirtualField()]] = this.id; // Is where[[field.getVirtualField()]] correct?
|
|
62
|
-
return resolver.match(modelRef).merge(args).many();
|
|
63
|
-
}
|
|
53
|
+
if (field.isScalar() || field.isEmbedded()) return Promise.resolve($value);
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
args.where.id = $value;
|
|
67
|
-
return resolver.match(modelRef).merge(args).many();
|
|
68
|
-
}
|
|
55
|
+
const modelRef = field.getModelRef();
|
|
69
56
|
|
|
57
|
+
if (field.isArray()) {
|
|
70
58
|
if (field.isVirtual()) {
|
|
71
|
-
args.where[[field.getVirtualField()]] = this.id;
|
|
72
|
-
return resolver.match(modelRef).merge(args).
|
|
59
|
+
args.where[[field.getVirtualField()]] = this.id; // Is where[[field.getVirtualField()]] correct?
|
|
60
|
+
return resolver.match(modelRef).merge(args).many();
|
|
73
61
|
}
|
|
74
62
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
63
|
+
// Not a "required" query + strip out nulls
|
|
64
|
+
args.where.id = $value;
|
|
65
|
+
return resolver.match(modelRef).merge(args).many();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (field.isVirtual()) {
|
|
69
|
+
args.where[[field.getVirtualField()]] = this.id;
|
|
70
|
+
return resolver.match(modelRef).merge(args).one();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return resolver.match(modelRef).id($value).one({ required: field.isRequired() });
|
|
74
|
+
})().then((results) => {
|
|
75
|
+
if (results == null) return field.resolve(query, results); // Allow field to determine
|
|
76
|
+
return mapPromise(results, result => field.resolve(query, result)).then(() => results); // Resolve the inside fields but still return "results"!!!!
|
|
77
|
+
}).then((resolved) => {
|
|
78
|
+
resolve(resolved);
|
|
79
|
+
}).catch((e) => {
|
|
80
|
+
reject(e);
|
|
84
81
|
});
|
|
82
|
+
});
|
|
85
83
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
},
|
|
90
|
-
enumerable: false,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// Field count (let's assume it's a Connection Type - meaning dont try with anything else)
|
|
94
|
-
prev[`$${name}:count`] = {
|
|
95
|
-
get() {
|
|
96
|
-
return (q = {}) => {
|
|
97
|
-
q.where = q.where || {};
|
|
98
|
-
if (field.isVirtual()) q.where[field.getVirtualField()] = this.id;
|
|
99
|
-
else q.where.id = this[name];
|
|
100
|
-
return resolver.match(field.getModelRef()).merge(q).count();
|
|
101
|
-
};
|
|
102
|
-
},
|
|
103
|
-
enumerable: false,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return prev;
|
|
107
|
-
}, {
|
|
108
|
-
id: {
|
|
109
|
-
get() { return doc.id || doc[model.idKey()]; },
|
|
110
|
-
set(id) { doc.id = id; }, // Embedded array of documents need to set id
|
|
111
|
-
enumerable: true,
|
|
84
|
+
cache.set(cacheKey, promise);
|
|
85
|
+
return promise;
|
|
86
|
+
};
|
|
112
87
|
},
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
88
|
+
enumerable: false,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Field count (let's assume it's a Connection Type - meaning dont try with anything else)
|
|
92
|
+
prev[`$${name}:count`] = {
|
|
93
|
+
get() {
|
|
94
|
+
return (q = {}) => {
|
|
95
|
+
q.where = q.where || {};
|
|
96
|
+
if (field.isVirtual()) q.where[field.getVirtualField()] = this.id;
|
|
97
|
+
else q.where.id = this[name];
|
|
98
|
+
return resolver.match(field.getModelRef()).merge(q).count();
|
|
99
|
+
};
|
|
117
100
|
},
|
|
101
|
+
enumerable: false,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return prev;
|
|
105
|
+
}, {
|
|
106
|
+
id: {
|
|
107
|
+
get() { return doc.id || doc[model.idKey()]; },
|
|
108
|
+
set(id) { doc.id = id; }, // Embedded array of documents need to set id
|
|
109
|
+
enumerable: true,
|
|
110
|
+
},
|
|
118
111
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const sortJSON = JSON.stringify(sortValues);
|
|
124
|
-
return Buffer.from(sortJSON).toString('base64');
|
|
125
|
-
},
|
|
126
|
-
enumerable: false,
|
|
127
|
-
},
|
|
112
|
+
$id: {
|
|
113
|
+
get() { return toGUID(model.getName(), this.id); },
|
|
114
|
+
enumerable: false,
|
|
115
|
+
},
|
|
128
116
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
$$cursor: {
|
|
118
|
+
get() {
|
|
119
|
+
const sortPaths = keyPaths(sort);
|
|
120
|
+
const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(this, path) }), {});
|
|
121
|
+
const sortJSON = JSON.stringify(sortValues);
|
|
122
|
+
return Buffer.from(sortJSON).toString('base64');
|
|
132
123
|
},
|
|
124
|
+
enumerable: false,
|
|
125
|
+
},
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
$$model: {
|
|
128
|
+
value: model,
|
|
129
|
+
enumerable: false,
|
|
130
|
+
},
|
|
138
131
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
132
|
+
$$isResultSetItem: {
|
|
133
|
+
value: true,
|
|
134
|
+
enumerable: false,
|
|
135
|
+
},
|
|
143
136
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
137
|
+
$$save: {
|
|
138
|
+
get() { return input => resolver.match(model).id(this.id).save({ ...this, ...input }); },
|
|
139
|
+
enumerable: false,
|
|
140
|
+
},
|
|
148
141
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
$$remove: {
|
|
143
|
+
get() { return () => resolver.match(model).id(this.id).remove(); },
|
|
144
|
+
enumerable: false,
|
|
145
|
+
},
|
|
153
146
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
147
|
+
$$delete: {
|
|
148
|
+
get() { return () => resolver.match(model).id(this.id).delete(); },
|
|
149
|
+
enumerable: false,
|
|
150
|
+
},
|
|
158
151
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
},
|
|
167
|
-
enumerable: false,
|
|
168
|
-
configurable: true,
|
|
152
|
+
toObject: {
|
|
153
|
+
get() {
|
|
154
|
+
return () => map(this, obj => Object.entries(obj).reduce((prev, [key, value]) => {
|
|
155
|
+
if (value === undefined) return prev;
|
|
156
|
+
prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
|
|
157
|
+
return prev;
|
|
158
|
+
}, {}));
|
|
169
159
|
},
|
|
170
|
-
|
|
171
|
-
|
|
160
|
+
enumerable: false,
|
|
161
|
+
configurable: true,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Create and return ResultSetItem
|
|
166
|
+
return Object.defineProperties({}, definition);
|
|
172
167
|
});
|
|
173
168
|
|
|
174
169
|
let hasNextPage = false;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
const { get } = require('lodash');
|
|
2
|
+
const DataService = require('./DataService');
|
|
3
|
+
const { map, ensureArray, keyPaths, mapPromise, toGUID, hashObject } = require('../service/app.service');
|
|
4
|
+
|
|
5
|
+
module.exports = class ResultSet {
|
|
6
|
+
constructor(query, data, adjustForPagination = true) {
|
|
7
|
+
const { resolver, model, sort, first, after, last, before } = query.toObject();
|
|
8
|
+
const fields = model.getFields().filter(f => f.getName() !== 'id');
|
|
9
|
+
|
|
10
|
+
const rs = map(data, (doc) => {
|
|
11
|
+
if (doc == null || typeof doc !== 'object') return doc;
|
|
12
|
+
|
|
13
|
+
//
|
|
14
|
+
const cache = new Map();
|
|
15
|
+
|
|
16
|
+
// Base definition all results have
|
|
17
|
+
const definition = {
|
|
18
|
+
id: {
|
|
19
|
+
get() { return doc.id || doc[model.idKey()]; },
|
|
20
|
+
set(id) { doc.id = id; }, // Embedded array of documents need to set id
|
|
21
|
+
enumerable: true,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
$id: {
|
|
25
|
+
get() { return toGUID(model.getName(), this.id); },
|
|
26
|
+
enumerable: false,
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
$$cursor: {
|
|
30
|
+
get() {
|
|
31
|
+
const sortPaths = keyPaths(sort);
|
|
32
|
+
const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(this, path) }), {});
|
|
33
|
+
const sortJSON = JSON.stringify(sortValues);
|
|
34
|
+
return Buffer.from(sortJSON).toString('base64');
|
|
35
|
+
},
|
|
36
|
+
enumerable: false,
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
$$model: {
|
|
40
|
+
value: model,
|
|
41
|
+
enumerable: false,
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
$$data: {
|
|
45
|
+
value: data,
|
|
46
|
+
enumerable: false,
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
$$isResultSetItem: {
|
|
50
|
+
value: true,
|
|
51
|
+
enumerable: false,
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
$$save: {
|
|
55
|
+
get() { return input => resolver.match(model).id(this.id).save({ ...this, ...input }); },
|
|
56
|
+
enumerable: false,
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
$$remove: {
|
|
60
|
+
get() { return () => resolver.match(model).id(this.id).remove(); },
|
|
61
|
+
enumerable: false,
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
$$delete: {
|
|
65
|
+
get() { return () => resolver.match(model).id(this.id).delete(); },
|
|
66
|
+
enumerable: false,
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
toObject: {
|
|
70
|
+
get() {
|
|
71
|
+
return () => map(this, obj => Object.entries(obj).reduce((prev, [key, value]) => {
|
|
72
|
+
if (value === undefined) return prev;
|
|
73
|
+
prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
|
|
74
|
+
return prev;
|
|
75
|
+
}, {}));
|
|
76
|
+
},
|
|
77
|
+
enumerable: false,
|
|
78
|
+
configurable: true,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
fields.forEach((field) => {
|
|
83
|
+
const key = field.getKey();
|
|
84
|
+
const name = field.getName();
|
|
85
|
+
const $name = `$${name}`;
|
|
86
|
+
const value = doc[key];
|
|
87
|
+
|
|
88
|
+
// Field attributes
|
|
89
|
+
definition[name] = {
|
|
90
|
+
get() {
|
|
91
|
+
if (cache.has(name)) return cache.get(name);
|
|
92
|
+
let $value = field.deserialize(query, value);
|
|
93
|
+
$value = $value != null && field.isEmbedded() ? new ResultSet(query.model(field.getModelRef()), $value, false) : $value;
|
|
94
|
+
cache.set(name, $value);
|
|
95
|
+
return $value;
|
|
96
|
+
},
|
|
97
|
+
set($value) {
|
|
98
|
+
cache.set(name, $value);
|
|
99
|
+
},
|
|
100
|
+
enumerable: true,
|
|
101
|
+
configurable: true, // Allows things like delete
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Hydrated field attributes
|
|
105
|
+
definition[`$${name}`] = {
|
|
106
|
+
get() {
|
|
107
|
+
return (args = {}) => {
|
|
108
|
+
// Ensure where clause
|
|
109
|
+
args.where = args.where || {};
|
|
110
|
+
|
|
111
|
+
// Cache
|
|
112
|
+
const cacheKey = `${$name}-${hashObject(args)}`;
|
|
113
|
+
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
114
|
+
|
|
115
|
+
const promise = new Promise((resolve, reject) => {
|
|
116
|
+
(() => {
|
|
117
|
+
const $value = this[name];
|
|
118
|
+
|
|
119
|
+
if (field.isScalar() || field.isEmbedded()) return Promise.resolve($value);
|
|
120
|
+
|
|
121
|
+
const modelRef = field.getModelRef();
|
|
122
|
+
|
|
123
|
+
if (field.isArray()) {
|
|
124
|
+
if (field.isVirtual()) {
|
|
125
|
+
args.where[[field.getVirtualField()]] = this.id; // Is where[[field.getVirtualField()]] correct?
|
|
126
|
+
return resolver.match(modelRef).merge(args).many();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Not a "required" query + strip out nulls
|
|
130
|
+
args.where.id = $value;
|
|
131
|
+
return resolver.match(modelRef).merge(args).many();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (field.isVirtual()) {
|
|
135
|
+
args.where[[field.getVirtualField()]] = this.id;
|
|
136
|
+
return resolver.match(modelRef).merge(args).one();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return resolver.match(modelRef).id($value).one({ required: field.isRequired() });
|
|
140
|
+
})().then((results) => {
|
|
141
|
+
if (results == null) return field.resolve(query, results); // Allow field to determine
|
|
142
|
+
return mapPromise(results, result => field.resolve(query, result)).then(() => results); // Resolve the inside fields but still return "results"!!!!
|
|
143
|
+
}).then((resolved) => {
|
|
144
|
+
resolve(resolved);
|
|
145
|
+
}).catch((e) => {
|
|
146
|
+
reject(e);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
cache.set(cacheKey, promise);
|
|
151
|
+
return promise;
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
enumerable: false,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Field count (let's assume it's a Connection Type - meaning dont try with anything else)
|
|
158
|
+
definition[`$${name}:count`] = {
|
|
159
|
+
get() {
|
|
160
|
+
return (q = {}) => {
|
|
161
|
+
q.where = q.where || {};
|
|
162
|
+
if (field.isVirtual()) q.where[field.getVirtualField()] = this.id;
|
|
163
|
+
else q.where.id = this[name];
|
|
164
|
+
return resolver.match(field.getModelRef()).merge(q).count();
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
enumerable: false,
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Create and return ResultSetItem
|
|
172
|
+
return Object.defineProperties({}, definition);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
let hasNextPage = false;
|
|
176
|
+
let hasPreviousPage = false;
|
|
177
|
+
if (adjustForPagination && rs.length) (({ hasPreviousPage, hasNextPage } = DataService.paginateResultSet(rs, first, after, last, before)));
|
|
178
|
+
|
|
179
|
+
return Object.defineProperties(rs, {
|
|
180
|
+
$$pageInfo: {
|
|
181
|
+
get() {
|
|
182
|
+
const edges = ensureArray(rs);
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
startCursor: get(edges, '0.$$cursor', ''),
|
|
186
|
+
endCursor: get(edges, `${edges.length - 1}.$$cursor`, ''),
|
|
187
|
+
hasPreviousPage,
|
|
188
|
+
hasNextPage,
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
enumerable: false,
|
|
192
|
+
},
|
|
193
|
+
$$isResultSet: {
|
|
194
|
+
value: true,
|
|
195
|
+
enumerable: false,
|
|
196
|
+
},
|
|
197
|
+
toObject: {
|
|
198
|
+
get() {
|
|
199
|
+
return () => map(this, doc => Object.entries(doc).reduce((prev, [key, value]) => {
|
|
200
|
+
if (value === undefined) return prev;
|
|
201
|
+
prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
|
|
202
|
+
return prev;
|
|
203
|
+
}, {}));
|
|
204
|
+
},
|
|
205
|
+
enumerable: false,
|
|
206
|
+
configurable: true,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const { get } = require('lodash');
|
|
2
|
+
const DataService = require('./DataService');
|
|
3
|
+
const { map, ensureArray, keyPaths, mapPromise, toGUID, hashObject } = require('../service/app.service');
|
|
4
|
+
|
|
5
|
+
module.exports = class ResultSet {
|
|
6
|
+
constructor(query, data, adjustForPagination = true) {
|
|
7
|
+
const { resolver, model, sort, first, after, last, before } = query.toObject();
|
|
8
|
+
const fields = model.getFields().filter(f => f.getName() !== 'id');
|
|
9
|
+
|
|
10
|
+
const rs = map(data, (doc) => {
|
|
11
|
+
if (doc == null || typeof doc !== 'object') return doc;
|
|
12
|
+
|
|
13
|
+
const cache = new Map();
|
|
14
|
+
|
|
15
|
+
const validKeys = [];
|
|
16
|
+
|
|
17
|
+
const definition = {
|
|
18
|
+
get id() { return doc.id || doc[model.idKey()]; },
|
|
19
|
+
get $id() { return toGUID(model.getName(), this.id); },
|
|
20
|
+
get $$data() { return data; },
|
|
21
|
+
get $$model() { return model; },
|
|
22
|
+
get $$isResultSetItem() { return true; },
|
|
23
|
+
get $$save() { return input => resolver.match(model).id(this.id).save({ ...this, ...input }); },
|
|
24
|
+
get $$remove() { return () => resolver.match(model).id(this.id).remove(); },
|
|
25
|
+
get $$delete() { return () => resolver.match(model).id(this.id).delete(); },
|
|
26
|
+
get $$cursor() {
|
|
27
|
+
return () => {
|
|
28
|
+
const sortPaths = keyPaths(sort);
|
|
29
|
+
const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(this, path) }), {});
|
|
30
|
+
const sortJSON = JSON.stringify(sortValues);
|
|
31
|
+
return Buffer.from(sortJSON).toString('base64');
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
get toObject() {
|
|
35
|
+
return () => validKeys.reduce((prev, key) => Object.assign(prev, { [key]: this[key] }), {});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
fields.forEach((field) => {
|
|
40
|
+
const key = field.getKey();
|
|
41
|
+
const name = field.getName();
|
|
42
|
+
const $name = `$${name}`;
|
|
43
|
+
const value = doc[key];
|
|
44
|
+
validKeys.push(name);
|
|
45
|
+
|
|
46
|
+
// Field attributes
|
|
47
|
+
Object.assign(definition, {
|
|
48
|
+
get [name]() {
|
|
49
|
+
let $value = field.deserialize(query, value);
|
|
50
|
+
$value = $value != null && field.isEmbedded() ? new ResultSet(query.model(field.getModelRef()), $value, false) : $value;
|
|
51
|
+
return $value;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Hydrated field attributes
|
|
56
|
+
Object.assign(definition, {
|
|
57
|
+
get [$name]() {
|
|
58
|
+
return (args = {}) => {
|
|
59
|
+
// Ensure where clause
|
|
60
|
+
args.where = args.where || {};
|
|
61
|
+
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
(() => {
|
|
64
|
+
const $value = this[name];
|
|
65
|
+
|
|
66
|
+
if (field.isScalar() || field.isEmbedded()) return Promise.resolve($value);
|
|
67
|
+
|
|
68
|
+
const modelRef = field.getModelRef();
|
|
69
|
+
|
|
70
|
+
if (field.isArray()) {
|
|
71
|
+
if (field.isVirtual()) {
|
|
72
|
+
args.where[[field.getVirtualField()]] = this.id; // Is where[[field.getVirtualField()]] correct?
|
|
73
|
+
return resolver.match(modelRef).merge(args).many();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Not a "required" query + strip out nulls
|
|
77
|
+
args.where.id = $value;
|
|
78
|
+
return resolver.match(modelRef).merge(args).many();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (field.isVirtual()) {
|
|
82
|
+
args.where[[field.getVirtualField()]] = this.id;
|
|
83
|
+
return resolver.match(modelRef).merge(args).one();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return resolver.match(modelRef).id($value).one({ required: field.isRequired() });
|
|
87
|
+
})().then((results) => {
|
|
88
|
+
if (results == null) return field.resolve(query, results); // Allow field to determine
|
|
89
|
+
return mapPromise(results, result => field.resolve(query, result)).then(() => results); // Resolve the inside fields but still return "results"!!!!
|
|
90
|
+
}).then((resolved) => {
|
|
91
|
+
resolve(resolved);
|
|
92
|
+
}).catch((e) => {
|
|
93
|
+
reject(e);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Field count (let's assume it's a Connection Type - meaning dont try with anything else)
|
|
101
|
+
Object.assign(definition, {
|
|
102
|
+
get [`${$name}:count`]() {
|
|
103
|
+
return (q = {}) => {
|
|
104
|
+
q.where = q.where || {};
|
|
105
|
+
if (field.isVirtual()) q.where[field.getVirtualField()] = this.id;
|
|
106
|
+
else q.where.id = this[name];
|
|
107
|
+
return resolver.match(field.getModelRef()).merge(q).count();
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Create and return ResultSetItem
|
|
114
|
+
const idk = new Proxy(definition, {
|
|
115
|
+
get(target, prop, rec) {
|
|
116
|
+
if (cache.has(prop)) return cache.get(prop);
|
|
117
|
+
const value = Reflect.get(target, prop, rec);
|
|
118
|
+
if (typeof value === 'function') return value.bind(target);
|
|
119
|
+
cache.set(prop, value);
|
|
120
|
+
return value;
|
|
121
|
+
},
|
|
122
|
+
set(target, prop, value) {
|
|
123
|
+
cache.set(prop, value);
|
|
124
|
+
return true;
|
|
125
|
+
},
|
|
126
|
+
ownKeys() {
|
|
127
|
+
return validKeys;
|
|
128
|
+
},
|
|
129
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
130
|
+
if (validKeys.indexOf(prop) === -1) {
|
|
131
|
+
return {
|
|
132
|
+
writable: true,
|
|
133
|
+
enumerable: true,
|
|
134
|
+
configurable: true,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
writable: false,
|
|
140
|
+
enumerable: false,
|
|
141
|
+
configurable: false,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// console.log(idk);
|
|
147
|
+
// // console.log(idk.toObject());
|
|
148
|
+
return idk;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
let hasNextPage = false;
|
|
152
|
+
let hasPreviousPage = false;
|
|
153
|
+
if (adjustForPagination && rs.length) (({ hasPreviousPage, hasNextPage } = DataService.paginateResultSet(rs, first, after, last, before)));
|
|
154
|
+
|
|
155
|
+
return Object.defineProperties(rs, {
|
|
156
|
+
$$pageInfo: {
|
|
157
|
+
get() {
|
|
158
|
+
const edges = ensureArray(rs);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
startCursor: get(edges, '0.$$cursor', ''),
|
|
162
|
+
endCursor: get(edges, `${edges.length - 1}.$$cursor`, ''),
|
|
163
|
+
hasPreviousPage,
|
|
164
|
+
hasNextPage,
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
enumerable: false,
|
|
168
|
+
},
|
|
169
|
+
$$isResultSet: {
|
|
170
|
+
value: true,
|
|
171
|
+
enumerable: false,
|
|
172
|
+
},
|
|
173
|
+
toObject: {
|
|
174
|
+
get() {
|
|
175
|
+
return () => map(this, doc => Object.entries(doc).reduce((prev, [key, value]) => {
|
|
176
|
+
if (value === undefined) return prev;
|
|
177
|
+
prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
|
|
178
|
+
return prev;
|
|
179
|
+
}, {}));
|
|
180
|
+
},
|
|
181
|
+
enumerable: false,
|
|
182
|
+
configurable: true,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
package/src/graphql/ast/Node.js
CHANGED
|
@@ -265,9 +265,9 @@ module.exports = class Node {
|
|
|
265
265
|
switch (this.nodeType) {
|
|
266
266
|
case 'model': {
|
|
267
267
|
if (!this.isMarkedModel()) return '';
|
|
268
|
-
return nvl(uvl(this.getDirectiveArg('model', 'gqlScope'), '
|
|
268
|
+
return nvl(uvl(this.getDirectiveArg('model', 'gqlScope'), 'cruds'), '');
|
|
269
269
|
}
|
|
270
|
-
case 'field': return nvl(uvl(this.getDirectiveArg('field', 'gqlScope'), '
|
|
270
|
+
case 'field': return nvl(uvl(this.getDirectiveArg('field', 'gqlScope'), 'cruds'), '');
|
|
271
271
|
default: return '';
|
|
272
272
|
}
|
|
273
273
|
}
|
|
@@ -3,7 +3,7 @@ const { Kind } = require('graphql');
|
|
|
3
3
|
const ServerResolver = require('../../core/ServerResolver');
|
|
4
4
|
const { ucFirst, fromGUID } = require('../../service/app.service');
|
|
5
5
|
const { findGQLModels } = require('../../service/schema.service');
|
|
6
|
-
const { makeCreateAPI, makeReadAPI, makeUpdateAPI, makeDeleteAPI, makeInputSplice, makeQueryResolver, makeMutationResolver } = require('../../service/decorator.service');
|
|
6
|
+
const { makeCreateAPI, makeReadAPI, makeUpdateAPI, makeDeleteAPI, makeSubscriptionAPI, makeInputSplice, makeQueryResolver, makeMutationResolver } = require('../../service/decorator.service');
|
|
7
7
|
|
|
8
8
|
const interfaceKinds = [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
|
|
9
9
|
|
|
@@ -20,6 +20,7 @@ const getGQLWhereFields = (model) => {
|
|
|
20
20
|
module.exports = (schema) => {
|
|
21
21
|
const resolver = new ServerResolver();
|
|
22
22
|
const allModels = schema.getModels();
|
|
23
|
+
const entityModels = schema.getEntityModels();
|
|
23
24
|
const markedModels = schema.getMarkedModels();
|
|
24
25
|
const createModels = findGQLModels('c', markedModels, allModels);
|
|
25
26
|
const readModels = findGQLModels('r', markedModels, allModels);
|
|
@@ -55,17 +56,43 @@ module.exports = (schema) => {
|
|
|
55
56
|
extend ${interfaceKinds.indexOf(model.getKind()) > -1 ? 'interface' : 'type'} ${model.getName()} {
|
|
56
57
|
${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}${field.getExtendArgs()}: ${field.getPayloadType()}`)}
|
|
57
58
|
}
|
|
59
|
+
|
|
58
60
|
type ${model.getName()}Connection {
|
|
59
61
|
pageInfo: PageInfo!
|
|
60
62
|
edges: [${model.getName()}Edge]
|
|
61
63
|
count: Int!
|
|
62
64
|
}
|
|
65
|
+
|
|
63
66
|
type ${model.getName()}Edge {
|
|
64
67
|
node: ${model.getName()}
|
|
65
68
|
cursor: String!
|
|
66
69
|
}
|
|
67
70
|
`),
|
|
68
71
|
|
|
72
|
+
...entityModels.map(model => `
|
|
73
|
+
input ${model.getName()}SubscriptionInputWhere {
|
|
74
|
+
${getGQLWhereFields(model).filter(field => field.isBasicType() || field.isEmbedded()).map(field => `${field.getName()}: ${field.getModelRef() ? `${ucFirst(field.getDataRef())}InputWhere` : 'AutoGraphMixed'}`)}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type ${model.getName()}SubscriptionPayloadEventData {
|
|
78
|
+
${getGQLWhereFields(model).filter(field => field.isBasicType() || field.isEmbedded()).map(field => `${field.getName()}: ${field.getGQLType()}`)}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type ${model.getName()}SubscriptionPayloadEvent {
|
|
82
|
+
crud: SubscriptionCrudEnum!
|
|
83
|
+
data: ${model.getName()}SubscriptionPayloadEventData!
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type ${model.getName()}SubscriptionPayload {
|
|
87
|
+
event: ${model.getName()}SubscriptionPayloadEvent
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
input ${model.getName()}SubscriptionInputFilter {
|
|
91
|
+
when: SubscriptionWhenEnum! = anytime
|
|
92
|
+
where: ${model.getName()}SubscriptionInputWhere! = {}
|
|
93
|
+
}
|
|
94
|
+
`),
|
|
95
|
+
|
|
69
96
|
...spliceModels.map(model => `
|
|
70
97
|
#input ${model.getName()}InputSplice {
|
|
71
98
|
# with: ${model}InputWhere
|
|
@@ -82,14 +109,19 @@ module.exports = (schema) => {
|
|
|
82
109
|
|
|
83
110
|
`type Query {
|
|
84
111
|
node(id: ID!): Node
|
|
85
|
-
${
|
|
112
|
+
${entityModels.map(model => makeReadAPI(model.getName(), model))}
|
|
86
113
|
}`,
|
|
87
114
|
|
|
88
115
|
`type Mutation {
|
|
89
116
|
_noop: String
|
|
90
|
-
${
|
|
91
|
-
${
|
|
92
|
-
${
|
|
117
|
+
${entityModels.map(model => makeCreateAPI(model.getName(), model))}
|
|
118
|
+
${entityModels.map(model => makeUpdateAPI(model.getName(), model))}
|
|
119
|
+
${entityModels.map(model => makeDeleteAPI(model.getName(), model))}
|
|
120
|
+
}`,
|
|
121
|
+
|
|
122
|
+
`type Subscription {
|
|
123
|
+
_noop: String
|
|
124
|
+
${entityModels.map(model => makeSubscriptionAPI(model.getName(), model))}
|
|
93
125
|
}`,
|
|
94
126
|
]),
|
|
95
127
|
resolvers: readModels.reduce((prev, model) => {
|
|
@@ -130,10 +162,10 @@ module.exports = (schema) => {
|
|
|
130
162
|
});
|
|
131
163
|
}, {
|
|
132
164
|
Node: {
|
|
133
|
-
__resolveType: (root, args, context, info) => root.__typename || fromGUID(root.$id)[0],
|
|
165
|
+
__resolveType: (root, args, context, info) => root.__typename || fromGUID(root.$id)[0], // eslint-disable-line no-underscore-dangle
|
|
134
166
|
},
|
|
135
167
|
|
|
136
|
-
Query:
|
|
168
|
+
Query: entityModels.reduce((prev, model) => {
|
|
137
169
|
return Object.assign(prev, makeQueryResolver(model.getName(), model, resolver));
|
|
138
170
|
}, {
|
|
139
171
|
node: (root, args, context, info) => {
|
|
@@ -144,7 +176,7 @@ module.exports = (schema) => {
|
|
|
144
176
|
},
|
|
145
177
|
}),
|
|
146
178
|
|
|
147
|
-
Mutation:
|
|
179
|
+
Mutation: entityModels.reduce((prev, model) => {
|
|
148
180
|
return Object.assign(prev, makeMutationResolver(model.getName(), model, resolver));
|
|
149
181
|
}, {}),
|
|
150
182
|
}),
|
|
@@ -23,6 +23,8 @@ module.exports = (schema) => {
|
|
|
23
23
|
}).concat(`
|
|
24
24
|
interface Node { id: ID! }
|
|
25
25
|
enum SortOrderEnum { asc desc }
|
|
26
|
+
enum SubscriptionCrudEnum { create update delete } # Not going to support "read"
|
|
27
|
+
enum SubscriptionWhenEnum { anytime preEvent postEvent }
|
|
26
28
|
`),
|
|
27
29
|
});
|
|
28
30
|
};
|
package/src/query/Query.js
CHANGED
|
@@ -343,6 +343,19 @@ exports.makeDeleteAPI = (name, model, parent) => {
|
|
|
343
343
|
return gql;
|
|
344
344
|
};
|
|
345
345
|
|
|
346
|
+
exports.makeSubscriptionAPI = (name, model, parent) => {
|
|
347
|
+
let gql = '';
|
|
348
|
+
|
|
349
|
+
if (model.hasGQLScope('s')) {
|
|
350
|
+
gql += `${name} (
|
|
351
|
+
on: [SubscriptionCrudEnum!]! = [create, update, delete]
|
|
352
|
+
filter: ${name}SubscriptionInputFilter
|
|
353
|
+
): ${name}SubscriptionPayload!`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return gql;
|
|
357
|
+
};
|
|
358
|
+
|
|
346
359
|
// Resolvers
|
|
347
360
|
exports.makeQueryResolver = (name, model, resolver, embeds = []) => {
|
|
348
361
|
const obj = {};
|
|
@@ -19,13 +19,14 @@ exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
|
19
19
|
|
|
20
20
|
if (name !== 'Setup') {
|
|
21
21
|
const { method, query } = mixed;
|
|
22
|
-
const { resolver, model, meta, doc, id, input, sort, merged, native, root } = query.toObject();
|
|
22
|
+
const { resolver, model, meta, doc, id, input, sort, merged, native, root, crud } = query.toObject();
|
|
23
23
|
|
|
24
24
|
event = {
|
|
25
25
|
context: resolver.getContext(),
|
|
26
26
|
key: `${method}${model}`,
|
|
27
27
|
resolver,
|
|
28
28
|
method,
|
|
29
|
+
crud,
|
|
29
30
|
model,
|
|
30
31
|
meta,
|
|
31
32
|
id,
|
|
@@ -57,11 +58,8 @@ exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
|
57
58
|
if (result !== undefined) return result; // Allowing middleware to dictate result
|
|
58
59
|
return middleware().then(thunk);
|
|
59
60
|
}).then((result) => {
|
|
60
|
-
event.doc = result; // You do actually need this...
|
|
61
|
-
|
|
62
|
-
return Promise.resolve(result);
|
|
63
|
-
}).then((result) => {
|
|
64
|
-
event.doc = result; // You do actually need this...
|
|
61
|
+
// event.doc = result; // You do actually need this...
|
|
62
|
+
event.result = result;
|
|
65
63
|
return systemEvent.emit('system', { type: `post${type}`, data: event }).then(finalResult => finalResult || result);
|
|
66
64
|
});
|
|
67
65
|
};
|