@coderich/autograph 0.9.16 → 0.10.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/CHANGELOG.md +24 -0
- package/index.js +2 -6
- package/package.json +10 -10
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +35 -58
- package/src/core/Schema.js +5 -38
- package/src/core/ServerResolver.js +7 -93
- package/src/data/DataLoader.js +68 -27
- package/src/data/DataService.js +59 -58
- package/src/data/Field.js +71 -96
- package/src/data/Model.js +95 -113
- package/src/data/Pipeline.js +174 -0
- package/src/data/Type.js +19 -60
- package/src/driver/MongoDriver.js +53 -28
- package/src/graphql/ast/Field.js +44 -26
- package/src/graphql/ast/Model.js +5 -16
- package/src/graphql/ast/Node.js +0 -32
- package/src/graphql/ast/Schema.js +109 -112
- package/src/graphql/extension/api.js +22 -35
- package/src/graphql/extension/framework.js +25 -33
- package/src/graphql/extension/type.js +2 -2
- package/src/query/Query.js +73 -15
- package/src/query/QueryBuilder.js +37 -28
- package/src/query/QueryBuilderTransaction.js +3 -3
- package/src/query/QueryResolver.js +93 -44
- package/src/query/QueryService.js +31 -34
- package/src/service/app.service.js +56 -35
- package/src/service/decorator.service.js +21 -288
- package/src/service/event.service.js +5 -79
- package/src/service/graphql.service.js +0 -9
- package/src/service/schema.service.js +5 -3
- package/src/core/Rule.js +0 -107
- package/src/core/SchemaDecorator.js +0 -46
- package/src/core/Transformer.js +0 -68
- package/src/data/Memoizer.js +0 -39
- package/src/data/ResultSet.js +0 -246
- package/src/graphql/ast/SchemaDecorator.js +0 -138
- package/src/graphql/directive/authz.directive.js +0 -84
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## v0.10.x
|
|
4
|
+
- Replaced ResultSet -> POJOs
|
|
5
|
+
- Removed all $$ magic resolver methods
|
|
6
|
+
- Removed all $ magic field methods
|
|
7
|
+
- Removed .toObject()
|
|
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 toId Transform -> use @field(id: '')
|
|
15
|
+
- Removed Model.tform() -> use Model.shapeObject(shape, data)
|
|
16
|
+
- Removed Resolver.toResultSet() -> ? TBD ?
|
|
17
|
+
- Removed Transformer + Rule -> use Pipeline
|
|
18
|
+
- Removed many pre-defined rules + transformers
|
|
19
|
+
- Pre-defined names start with $ (eg. $toLowerCase)
|
|
20
|
+
- Moved "validator" to dev dependency -> isEmail
|
|
21
|
+
- Added QueryBuilder.resolve() terminal command
|
|
22
|
+
- Exported SchemaDecorator -> Schema
|
|
23
|
+
- Removed embedded schema SystemEvents (internal emitter also removed)
|
|
24
|
+
- Removed spread of arguments in QueryBuilder terminal commands (must pass in array)
|
|
25
|
+
- Must pass makeExecutableSchema to Schema constructor
|
|
26
|
+
|
|
3
27
|
## v0.9.x
|
|
4
28
|
- Subscriptions API
|
|
5
29
|
- postMutation no longer mutates "doc" and adds "result"
|
package/index.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
const Schema = require('./src/core/Schema');
|
|
2
|
-
const SchemaDecorator = require('./src/core/SchemaDecorator');
|
|
3
2
|
const GraphQL = require('./src/core/GraphQL');
|
|
4
3
|
const Resolver = require('./src/core/Resolver');
|
|
5
|
-
const
|
|
4
|
+
const Pipeline = require('./src/data/Pipeline');
|
|
6
5
|
const Driver = require('./src/driver');
|
|
7
|
-
const Transformer = require('./src/core/Transformer');
|
|
8
6
|
const { eventEmitter: Emitter } = require('./src/service/event.service');
|
|
9
7
|
|
|
10
8
|
module.exports = {
|
|
11
9
|
Schema,
|
|
12
|
-
SchemaDecorator,
|
|
13
10
|
GraphQL,
|
|
14
11
|
Resolver,
|
|
15
|
-
Rule,
|
|
16
12
|
Driver,
|
|
17
|
-
Transformer,
|
|
18
13
|
Emitter,
|
|
14
|
+
Pipeline,
|
|
19
15
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderich/autograph",
|
|
3
3
|
"author": "Richard Livolsi (coderich)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.2",
|
|
5
5
|
"description": "AutoGraph",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"graphql",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"start": "APP_ROOT_PATH=$(pwd) node ./test/server",
|
|
26
|
-
"test": "APP_ROOT_PATH=$(pwd) ratchet test",
|
|
27
|
-
"test:debug": "APP_ROOT_PATH=$(pwd) node --inspect-brk ./node_modules/jest/bin/jest.js --watch --runInBand",
|
|
26
|
+
"test": "APP_ROOT_PATH=$(pwd) ratchet test --forceExit",
|
|
27
|
+
"test:debug": "APP_ROOT_PATH=$(pwd) node --inspect-brk ./node_modules/jest/bin/jest.js --watch --runInBand --logHeapUsage",
|
|
28
28
|
"lint": "APP_ROOT_PATH=$(pwd) ratchet lint",
|
|
29
29
|
"inspect": "APP_ROOT_PATH=$(pwd) node --expose-gc --inspect=9222 ./src/server",
|
|
30
30
|
"ratchet": "ratchet"
|
|
@@ -37,22 +37,22 @@
|
|
|
37
37
|
"fill-range": "^7.0.1",
|
|
38
38
|
"glob": "^7.1.6",
|
|
39
39
|
"graphql-fields": "^2.0.3",
|
|
40
|
-
"graphql-tools": "^7.0.5",
|
|
41
40
|
"lodash": "^4.17.21",
|
|
41
|
+
"mongodb": "^4.8.0",
|
|
42
42
|
"object-hash": "^2.0.1",
|
|
43
43
|
"picomatch": "^2.1.1",
|
|
44
|
-
"uuid": "^3.3.3"
|
|
45
|
-
"validator": "^12.2.0"
|
|
44
|
+
"uuid": "^3.3.3"
|
|
46
45
|
},
|
|
47
46
|
"devDependencies": {
|
|
48
|
-
"@coderich/ratchet": "^1.5.
|
|
47
|
+
"@coderich/ratchet": "^1.5.7",
|
|
48
|
+
"@graphql-tools/schema": "^8.3.14",
|
|
49
49
|
"graphql": "^15.5.0",
|
|
50
|
-
"mongodb": "
|
|
51
|
-
"mongodb-memory-server": "^6.9.6",
|
|
50
|
+
"mongodb-memory-server": "^8.7.2",
|
|
52
51
|
"neo4j-driver": "^4.0.0",
|
|
53
52
|
"neodb": "^3.0.0",
|
|
54
53
|
"redis": "^2.8.0",
|
|
55
|
-
"redis-mock": "^0.47.0"
|
|
54
|
+
"redis-mock": "^0.47.0",
|
|
55
|
+
"validator": "^13.7.0"
|
|
56
56
|
},
|
|
57
57
|
"repository": {
|
|
58
58
|
"type": "git",
|
package/src/.DS_Store
CHANGED
|
Binary file
|
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) {
|
|
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,9 +17,7 @@ module.exports = class extends EventEmitter {
|
|
|
17
17
|
if (numArgs < 2) next();
|
|
18
18
|
});
|
|
19
19
|
})).then((results) => {
|
|
20
|
-
|
|
21
|
-
const target = event === 'validate' ? prev : results;
|
|
22
|
-
return target.find(r => r !== undefined); // There can be only one (result)
|
|
20
|
+
return results.find(r => r !== undefined); // There can be only one (result)
|
|
23
21
|
});
|
|
24
22
|
}
|
|
25
23
|
|
package/src/core/Resolver.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
const { isEmpty } = require('lodash');
|
|
2
1
|
const Model = require('../data/Model');
|
|
3
|
-
const Query = require('../query/Query');
|
|
4
|
-
const ResultSet = require('../data/ResultSet');
|
|
5
2
|
const DataLoader = require('../data/DataLoader');
|
|
6
3
|
const DataTransaction = require('../data/DataTransaction');
|
|
7
4
|
const QueryBuilder = require('../query/QueryBuilder');
|
|
8
|
-
const { createSystemEvent } = require('../service/event.service');
|
|
9
5
|
|
|
10
6
|
module.exports = class Resolver {
|
|
11
7
|
constructor(schema, context = {}) {
|
|
12
|
-
this.models = schema.getModels();
|
|
13
8
|
this.schema = schema;
|
|
14
9
|
this.context = context;
|
|
10
|
+
this.models = schema.getModels();
|
|
15
11
|
this.loaders = this.models.reduce((prev, model) => prev.set(`${model}`, new DataLoader(this, model)), new Map());
|
|
12
|
+
}
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
getSchema() {
|
|
15
|
+
return this.schema;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getContext() {
|
|
19
|
+
return this.context;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -38,31 +38,6 @@ module.exports = class Resolver {
|
|
|
38
38
|
*/
|
|
39
39
|
raw(model) {
|
|
40
40
|
return this.toModelEntity(model).raw();
|
|
41
|
-
// const entity = this.toModelEntity(model);
|
|
42
|
-
// const driver = entity.raw();
|
|
43
|
-
// if (!method) return driver;
|
|
44
|
-
|
|
45
|
-
// const resolver = this;
|
|
46
|
-
// const crud = ['get', 'find', 'count'].indexOf(method) > -1 ? 'read' : method;
|
|
47
|
-
// const query = new Query({ model: entity, resolver, crud });
|
|
48
|
-
|
|
49
|
-
// return new Proxy(driver, {
|
|
50
|
-
// get(target, prop, rec) {
|
|
51
|
-
// const value = Reflect.get(target, prop, rec);
|
|
52
|
-
|
|
53
|
-
// if (typeof value === 'function') {
|
|
54
|
-
// return (...args) => {
|
|
55
|
-
// return value.bind(target)(...args).then((result) => {
|
|
56
|
-
// const doc = resolver.toResultSet(model, result);
|
|
57
|
-
// createSystemEvent('Response', { method, query: query.doc(doc) });
|
|
58
|
-
// return result;
|
|
59
|
-
// });
|
|
60
|
-
// };
|
|
61
|
-
// }
|
|
62
|
-
|
|
63
|
-
// return value;
|
|
64
|
-
// },
|
|
65
|
-
// });
|
|
66
41
|
}
|
|
67
42
|
|
|
68
43
|
/**
|
|
@@ -77,21 +52,22 @@ module.exports = class Resolver {
|
|
|
77
52
|
}
|
|
78
53
|
|
|
79
54
|
resolve(query) {
|
|
80
|
-
const { model, crud } = query.toObject();
|
|
55
|
+
const { model, crud, method } = query.toObject();
|
|
81
56
|
|
|
82
57
|
switch (crud) {
|
|
83
58
|
case 'create': case 'update': case 'delete': {
|
|
84
59
|
return model.getDriver().resolve(query.toDriver()).then((data) => {
|
|
85
60
|
this.clear(model);
|
|
86
|
-
return
|
|
61
|
+
return model.shapeObject(model.getShape(), data, query);
|
|
87
62
|
});
|
|
88
63
|
}
|
|
89
64
|
default: {
|
|
90
65
|
const key = model.idKey();
|
|
91
|
-
const { where
|
|
66
|
+
const { where } = query.toDriver();
|
|
67
|
+
const lookupValue = where[key];
|
|
92
68
|
|
|
93
|
-
// This
|
|
94
|
-
if (Object.prototype.hasOwnProperty.call(where, key) &&
|
|
69
|
+
// This is a shortcut to prevent making unnecessary query
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(where, key) && (lookupValue == null || (Array.isArray(lookupValue) && lookupValue.length === 0))) {
|
|
95
71
|
switch (method) {
|
|
96
72
|
case 'count': return Promise.resolve(0);
|
|
97
73
|
case 'findMany': return Promise.resolve([]);
|
|
@@ -99,15 +75,14 @@ module.exports = class Resolver {
|
|
|
99
75
|
}
|
|
100
76
|
}
|
|
101
77
|
|
|
102
|
-
//
|
|
78
|
+
// Go through DataLoader to cache results
|
|
103
79
|
return this.loaders.get(`${model}`).load(query);
|
|
104
80
|
}
|
|
105
81
|
}
|
|
106
82
|
}
|
|
107
83
|
|
|
108
84
|
toModel(model) {
|
|
109
|
-
|
|
110
|
-
return $model;
|
|
85
|
+
return model instanceof Model ? model : this.schema.getModel(model);
|
|
111
86
|
}
|
|
112
87
|
|
|
113
88
|
toModelMarked(model) {
|
|
@@ -124,23 +99,25 @@ module.exports = class Resolver {
|
|
|
124
99
|
return entity;
|
|
125
100
|
}
|
|
126
101
|
|
|
127
|
-
toResultSet(model, data, method) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
102
|
+
// toResultSet(model, data, method) {
|
|
103
|
+
// const crud = ['get', 'find', 'count'].indexOf(method) > -1 ? 'read' : method;
|
|
104
|
+
// const doc = model.shape(data);
|
|
105
|
+
// const result = doc;
|
|
106
|
+
// const merged = doc;
|
|
107
|
+
|
|
108
|
+
// return createSystemEvent('Response', {
|
|
109
|
+
// model,
|
|
110
|
+
// crud,
|
|
111
|
+
// method,
|
|
112
|
+
// result,
|
|
113
|
+
// doc,
|
|
114
|
+
// merged,
|
|
115
|
+
// resolver: this,
|
|
116
|
+
// key: `${method}${model}`,
|
|
117
|
+
// context: this.getContext(),
|
|
118
|
+
// query: query.doc(result).merged(result),
|
|
119
|
+
// }, () => result);
|
|
120
|
+
// }
|
|
144
121
|
|
|
145
122
|
// DataLoader Proxy Methods
|
|
146
123
|
clear(model) {
|
package/src/core/Schema.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
const Model = require('../data/Model');
|
|
2
2
|
const Schema = require('../graphql/ast/Schema');
|
|
3
|
-
const apiExt = require('../graphql/extension/api');
|
|
4
|
-
const typeExt = require('../graphql/extension/type');
|
|
5
|
-
const frameworkExt = require('../graphql/extension/framework');
|
|
6
3
|
const { identifyOnDeletes } = require('../service/schema.service');
|
|
7
4
|
const { createSystemEvent } = require('../service/event.service');
|
|
8
5
|
|
|
9
6
|
// Export class
|
|
10
7
|
module.exports = class extends Schema {
|
|
11
|
-
constructor(schema, stores) {
|
|
12
|
-
super(schema);
|
|
8
|
+
constructor(schema, stores, toExecutableSchema) {
|
|
9
|
+
super(schema, toExecutableSchema);
|
|
13
10
|
|
|
14
11
|
// Create drivers
|
|
15
12
|
this.drivers = Object.entries(stores).reduce((prev, [key, value]) => {
|
|
@@ -23,9 +20,6 @@ module.exports = class extends Schema {
|
|
|
23
20
|
},
|
|
24
21
|
});
|
|
25
22
|
}, {});
|
|
26
|
-
|
|
27
|
-
// Create models
|
|
28
|
-
this.createModels();
|
|
29
23
|
}
|
|
30
24
|
|
|
31
25
|
setup() {
|
|
@@ -43,38 +37,11 @@ module.exports = class extends Schema {
|
|
|
43
37
|
});
|
|
44
38
|
}
|
|
45
39
|
|
|
46
|
-
|
|
40
|
+
initialize() {
|
|
41
|
+
super.initialize();
|
|
47
42
|
this.models = super.getModels().map(model => new Model(this, model, this.drivers[model.getDriverName()]));
|
|
43
|
+
this.models.forEach(model => model.initialize());
|
|
48
44
|
this.models.forEach(model => model.referentialIntegrity(identifyOnDeletes(this.models, model)));
|
|
49
|
-
this.modelsByName = this.models.reduce((prev, model) => Object.assign(prev, { [model.getName()]: model }), {});
|
|
50
|
-
this.modelsByKey = this.models.reduce((prev, model) => Object.assign(prev, { [model.getKey()]: model }), {});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
loadDir(dir, options) {
|
|
54
|
-
super.loadDir(dir, options);
|
|
55
|
-
this.createModels();
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
extend(...schemas) {
|
|
60
|
-
super.extend(...schemas);
|
|
61
|
-
this.createModels();
|
|
62
45
|
return this;
|
|
63
46
|
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Called a runtime to get the full server api schema. Done this way because the
|
|
67
|
-
* end-user needs a chance to call Transformer.factory() etc (thus cannot be moved to constructor)
|
|
68
|
-
*/
|
|
69
|
-
getServerApiSchema() {
|
|
70
|
-
this.extend(frameworkExt(this), typeExt(this));
|
|
71
|
-
this.sextend(apiExt(this));
|
|
72
|
-
return super.getSchema();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
makeServerApiSchema() {
|
|
76
|
-
this.extend(frameworkExt(this), typeExt(this));
|
|
77
|
-
this.sextend(apiExt(this));
|
|
78
|
-
return super.makeExecutableSchema();
|
|
79
|
-
}
|
|
80
47
|
};
|
|
@@ -1,101 +1,15 @@
|
|
|
1
|
-
const
|
|
2
|
-
const Boom = require('./Boom');
|
|
3
|
-
const { unrollGuid, guidToId, ensureArray, promiseChain } = require('../service/app.service');
|
|
4
|
-
|
|
5
|
-
const normalizeQuery = (args = {}, info) => {
|
|
6
|
-
const query = { fields: GraphqlFields(info, {}, { processArguments: true }), ...args };
|
|
7
|
-
return query;
|
|
8
|
-
// const { fields = {} } = query;
|
|
9
|
-
// const { first, last, before, after } = args;
|
|
10
|
-
// return Object.assign(query, { pagination: { first, last, before, after }, fields: _.get(fields, 'edges.node') });
|
|
11
|
-
};
|
|
1
|
+
const { unrollGuid, guidToId } = require('../service/app.service');
|
|
12
2
|
|
|
13
3
|
module.exports = class ServerResolver {
|
|
14
4
|
constructor() {
|
|
15
|
-
//
|
|
16
|
-
this.get = ({ autograph }, model, { id: guid }, required = false,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return autograph.resolver.match(model).id(guidToId(autograph, guid)).merge(query).one().then((doc) => {
|
|
20
|
-
if (!doc && required) throw Boom.notFound(`${model} Not Found`);
|
|
21
|
-
return doc;
|
|
22
|
-
});
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Query
|
|
26
|
-
this.query = ({ autograph }, model, args, info) => autograph.resolver.match(model).merge(normalizeQuery(args, info)).many();
|
|
27
|
-
this.count = ({ autograph }, model, args, info) => autograph.resolver.match(model).merge(args).count();
|
|
5
|
+
// Queries
|
|
6
|
+
this.get = ({ autograph }, model, { id: guid }, required = false, query) => autograph.resolver.match(model).id(guidToId(autograph, guid)).select(query.fields).one({ required });
|
|
7
|
+
this.query = ({ autograph }, model, args, query) => autograph.resolver.match(model).select(query.fields).merge(args).many();
|
|
8
|
+
this.count = ({ autograph }, model, args, query) => autograph.resolver.match(model).merge(args).count();
|
|
28
9
|
|
|
29
10
|
// Mutations
|
|
30
|
-
this.create = ({ autograph }, model, { input, meta }, query) => autograph.resolver.match(model).meta(meta).save(unrollGuid(autograph, model, input));
|
|
11
|
+
this.create = ({ autograph }, model, { input, meta }, query) => autograph.resolver.match(model).select(query.fields).meta(meta).save(unrollGuid(autograph, model, input));
|
|
31
12
|
this.delete = ({ autograph }, model, { id: guid, meta }, query) => autograph.resolver.match(model).id(guidToId(autograph, guid)).select(query.fields).meta(meta).remove();
|
|
32
|
-
|
|
33
|
-
this.update = ({ autograph }, model, args, query) => {
|
|
34
|
-
const { resolver } = autograph;
|
|
35
|
-
const { id: guid, input, splice = {}, meta } = args;
|
|
36
|
-
|
|
37
|
-
return autograph.resolver.match(model).id(guidToId(autograph, guid)).select(query.fields).meta(meta).save(unrollGuid(autograph, model, input)).then((result) => {
|
|
38
|
-
if (!Object.keys(splice).length) return result;
|
|
39
|
-
|
|
40
|
-
const txn = resolver.transaction();
|
|
41
|
-
|
|
42
|
-
return promiseChain(Object.entries(splice).map(([key, { with: from, put: to, splice: subSplice }]) => {
|
|
43
|
-
to = to ? ensureArray(to) : to;
|
|
44
|
-
from = from ? ensureArray(from) : from;
|
|
45
|
-
const selectAll = Boolean(from && from.length === 0);
|
|
46
|
-
|
|
47
|
-
return async () => {
|
|
48
|
-
if (subSplice) {
|
|
49
|
-
console.log('we have to subSplice this');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (selectAll) {
|
|
53
|
-
// Empty
|
|
54
|
-
if (to === null || (to && to.length === 0)) {
|
|
55
|
-
txn.match(model).id(result.id).select(query.fields).meta(meta).save({ [key]: to });
|
|
56
|
-
return txn.exec();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Replace
|
|
60
|
-
if (to) {
|
|
61
|
-
const modelRef = model.getField(key).getModelRef();
|
|
62
|
-
const createTo = await resolver.match(modelRef).save(...to);
|
|
63
|
-
// const createTo = await Promise.all(to.map(el => modelRef.appendDefaultValues(el).then(r => modelRef.appendCreateFields(r, true))));
|
|
64
|
-
txn.match(model).id(result.id).select(query.fields).meta(meta).save({ [key]: createTo });
|
|
65
|
-
return txn.exec();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Nonsense
|
|
69
|
-
return Promise.resolve([result]);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Update
|
|
73
|
-
if (from && to) {
|
|
74
|
-
txn.match(model).id(result.id).meta(meta).splice(key, from, to);
|
|
75
|
-
return txn.exec();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Remove
|
|
79
|
-
if (from && to === null) {
|
|
80
|
-
txn.match(model).id(result.id).meta(meta).splice(key, from);
|
|
81
|
-
return txn.exec();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Add
|
|
85
|
-
if (!from && to) {
|
|
86
|
-
txn.match(model).id(result.id).meta(meta).splice(key, null, to);
|
|
87
|
-
return txn.exec();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Nonsense
|
|
91
|
-
return Promise.resolve([result]);
|
|
92
|
-
};
|
|
93
|
-
})).then((results) => {
|
|
94
|
-
return txn.commit().then(() => {
|
|
95
|
-
return results.pop().pop();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
};
|
|
13
|
+
this.update = ({ autograph }, model, { id: guid, meta, input }, query) => autograph.resolver.match(model).id(guidToId(autograph, guid)).select(query.fields).meta(meta).save(unrollGuid(autograph, model, input));
|
|
100
14
|
}
|
|
101
15
|
};
|
package/src/data/DataLoader.js
CHANGED
|
@@ -1,40 +1,81 @@
|
|
|
1
1
|
const FBDataLoader = require('dataloader');
|
|
2
|
-
const
|
|
2
|
+
const { map, ensureArray, hashObject } = require('../service/app.service');
|
|
3
3
|
const Query = require('../query/Query');
|
|
4
|
-
const { hashObject } = require('../service/app.service');
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
const handleData = (data, model, query) => {
|
|
6
|
+
if (data == null || typeof data !== 'object') return data;
|
|
7
|
+
return model.deserialize(data, query);
|
|
8
|
+
};
|
|
9
|
+
|
|
7
10
|
module.exports = class DataLoader extends FBDataLoader {
|
|
8
11
|
constructor(resolver, model) {
|
|
9
|
-
const idKey = model.idKey();
|
|
10
12
|
const driver = model.getDriver();
|
|
11
13
|
|
|
12
14
|
return new FBDataLoader((queries) => {
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
let performBatchQuery = false; // If we don't have to batch it's faster to resolve normal
|
|
16
|
+
const defaultBatchName = '__default__'; // Something that won't collide with an actual field name
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Batch queries can save resources and network round-trip latency. However, we have to be careful to
|
|
20
|
+
* preserve the order and adhere to the DataLoader API. This step simply creates a map of batch
|
|
21
|
+
* queries to run; saving the order ("i") along with useful meta information
|
|
22
|
+
*/
|
|
23
|
+
const batchQueries = queries.reduce((prev, query, i) => {
|
|
24
|
+
const { batch = defaultBatchName, where, cmd } = query.toObject();
|
|
25
|
+
const key = batch && (cmd === 'one' || cmd === 'many') ? batch : defaultBatchName;
|
|
26
|
+
if (key !== defaultBatchName) performBatchQuery = true;
|
|
27
|
+
prev[key] = prev[key] || [];
|
|
28
|
+
prev[key].push({ query, where, cmd, i });
|
|
18
29
|
return prev;
|
|
19
|
-
}, {
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
});
|
|
30
|
+
}, {});
|
|
31
|
+
|
|
32
|
+
// Don't batch unless it's worth it!
|
|
33
|
+
if (!performBatchQuery) {
|
|
34
|
+
return Promise.all(queries.map((query) => {
|
|
35
|
+
return driver.resolve(query.toDriver()).then(data => handleData(data, model, query));
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* We have reduced the number of queries down to a smaller set of batch queries to run. The dance
|
|
41
|
+
* performed below retreives the data and then expands the results back into the original queries
|
|
42
|
+
*/
|
|
43
|
+
const whereShape = model.getShape('create', 'where');
|
|
44
|
+
|
|
45
|
+
// console.log(Object.entries(batchQueries).map(([key, value]) => ({ [key]: value.length })));
|
|
46
|
+
|
|
47
|
+
return Promise.all(Object.entries(batchQueries).map(([key, values]) => {
|
|
48
|
+
switch (key) {
|
|
49
|
+
case defaultBatchName: {
|
|
50
|
+
return values.map(({ query, i }) => driver.resolve(query.toDriver()).then(data => handleData(data, model, query)).then(data => ({ data, i })));
|
|
51
|
+
}
|
|
52
|
+
default: {
|
|
53
|
+
const keys = Array.from(new Set(values.map(({ where }) => map(where[key], el => `${el}`)).flat()));
|
|
54
|
+
const batchQuery = new Query({ resolver, model, method: 'findMany', crud: 'read' });
|
|
55
|
+
const batchWhere = model.shapeObject(whereShape, { [key]: keys }, batchQuery); // This will add back instructs etc
|
|
56
|
+
|
|
57
|
+
return driver.resolve(batchQuery.where(batchWhere).toDriver()).then(data => handleData(data, model, batchQuery)).then((results) => {
|
|
58
|
+
// One-time data transformation on results to make matching back faster (below)
|
|
59
|
+
const resultsByKey = results.reduce((prev, row) => {
|
|
60
|
+
ensureArray(row[key]).forEach((id) => {
|
|
61
|
+
prev[id] = prev[id] || [];
|
|
62
|
+
prev[id].push(row);
|
|
63
|
+
});
|
|
64
|
+
return prev;
|
|
65
|
+
}, {});
|
|
66
|
+
|
|
67
|
+
// Match back
|
|
68
|
+
return values.map(({ where, cmd, i }) => {
|
|
69
|
+
const targets = ensureArray(where[key]).map(t => `${t}`);
|
|
70
|
+
const data = targets.map(t => resultsByKey[t] || null).flat();
|
|
71
|
+
return { i, data: cmd === 'many' ? data.filter(d => d != null) : data[0] };
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}).flat()).then((results) => {
|
|
77
|
+
return results.flat().sort((a, b) => a.i - b.i).map(({ data }) => data);
|
|
78
|
+
});
|
|
38
79
|
}, {
|
|
39
80
|
cache: true,
|
|
40
81
|
cacheKeyFn: query => hashObject(query.getCacheKey()),
|