@coderich/autograph 0.9.15 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/index.js +2 -6
  3. package/package.json +13 -10
  4. package/src/.DS_Store +0 -0
  5. package/src/core/EventEmitter.js +2 -4
  6. package/src/core/Resolver.js +43 -59
  7. package/src/core/Schema.js +3 -36
  8. package/src/core/ServerResolver.js +7 -93
  9. package/src/data/DataLoader.js +68 -27
  10. package/src/data/DataService.js +59 -58
  11. package/src/data/Field.js +71 -96
  12. package/src/data/Model.js +95 -113
  13. package/src/data/Pipeline.js +174 -0
  14. package/src/data/Type.js +19 -60
  15. package/src/driver/MongoDriver.js +52 -27
  16. package/src/graphql/ast/Field.js +44 -26
  17. package/src/graphql/ast/Model.js +5 -16
  18. package/src/graphql/ast/Node.js +0 -32
  19. package/src/graphql/ast/Schema.js +107 -111
  20. package/src/graphql/extension/api.js +22 -35
  21. package/src/graphql/extension/framework.js +25 -33
  22. package/src/graphql/extension/type.js +2 -2
  23. package/src/query/Query.js +73 -15
  24. package/src/query/QueryBuilder.js +37 -28
  25. package/src/query/QueryBuilderTransaction.js +3 -3
  26. package/src/query/QueryResolver.js +93 -44
  27. package/src/query/QueryService.js +31 -34
  28. package/src/service/app.service.js +56 -9
  29. package/src/service/decorator.service.js +21 -288
  30. package/src/service/event.service.js +5 -79
  31. package/src/service/graphql.service.js +1 -1
  32. package/src/service/schema.service.js +5 -3
  33. package/src/core/Rule.js +0 -107
  34. package/src/core/SchemaDecorator.js +0 -46
  35. package/src/core/Transformer.js +0 -68
  36. package/src/data/Memoizer.js +0 -39
  37. package/src/data/ResultSet.js +0 -246
  38. package/src/graphql/ast/SchemaDecorator.js +0 -138
  39. package/src/graphql/directive/authz.directive.js +0 -84
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
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
+
3
26
  ## v0.9.x
4
27
  - Subscriptions API
5
28
  - 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 Rule = require('./src/core/Rule');
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.9.15",
4
+ "version": "0.10.1",
5
5
  "description": "AutoGraph",
6
6
  "keywords": [
7
7
  "graphql",
@@ -23,13 +23,14 @@
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"
31
31
  },
32
32
  "dependencies": {
33
+ "@graphql-tools/schema": "^8.3.14",
33
34
  "@hapi/boom": "^9.1.0",
34
35
  "axios": "^0.21.4",
35
36
  "dataloader": "^2.0.0",
@@ -37,22 +38,24 @@
37
38
  "fill-range": "^7.0.1",
38
39
  "glob": "^7.1.6",
39
40
  "graphql-fields": "^2.0.3",
40
- "graphql-tools": "^7.0.5",
41
41
  "lodash": "^4.17.21",
42
+ "mongodb": "^4.8.0",
42
43
  "object-hash": "^2.0.1",
43
44
  "picomatch": "^2.1.1",
44
- "uuid": "^3.3.3",
45
- "validator": "^12.2.0"
45
+ "uuid": "^3.3.3"
46
46
  },
47
47
  "devDependencies": {
48
- "@coderich/ratchet": "^1.5.4",
48
+ "@coderich/ratchet": "^1.5.7",
49
49
  "graphql": "^15.5.0",
50
- "mongodb": "3.6.9",
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
+ },
57
+ "peerDependencies": {
58
+ "graphql": "*"
56
59
  },
57
60
  "repository": {
58
61
  "type": "git",
package/src/.DS_Store CHANGED
Binary file
@@ -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, prev) {
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
- 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)
20
+ return results.find(r => r !== undefined); // There can be only one (result)
23
21
  });
24
22
  }
25
23
 
@@ -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
- this.getSchema = () => this.schema;
19
- this.getContext = () => this.context;
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,30 +52,37 @@ 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 new ResultSet(query, data);
61
+ return model.shapeObject(model.getShape(), data, query);
87
62
  });
88
63
  }
89
64
  default: {
90
- // This is needed in SF tests...
91
65
  const key = model.idKey();
92
- const { where, method } = query.toDriver();
93
- if (Object.prototype.hasOwnProperty.call(where, key) && isEmpty(where[key])) return Promise.resolve(method === 'findMany' ? [] : null);
94
-
95
- //
66
+ const { where } = query.toDriver();
67
+ const lookupValue = where[key];
68
+
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))) {
71
+ switch (method) {
72
+ case 'count': return Promise.resolve(0);
73
+ case 'findMany': return Promise.resolve([]);
74
+ default: return Promise.resolve(null);
75
+ }
76
+ }
77
+
78
+ // Go through DataLoader to cache results
96
79
  return this.loaders.get(`${model}`).load(query);
97
80
  }
98
81
  }
99
82
  }
100
83
 
101
84
  toModel(model) {
102
- const $model = model instanceof Model ? model : this.schema.getModel(model);
103
- return $model;
85
+ return model instanceof Model ? model : this.schema.getModel(model);
104
86
  }
105
87
 
106
88
  toModelMarked(model) {
@@ -117,23 +99,25 @@ module.exports = class Resolver {
117
99
  return entity;
118
100
  }
119
101
 
120
- toResultSet(model, data, method) {
121
- const crud = ['get', 'find', 'count'].indexOf(method) > -1 ? 'read' : method;
122
- const query = new Query({ model: this.toModel(model), resolver: this, crud });
123
- const result = new ResultSet(query, data);
124
- return createSystemEvent('Response', {
125
- model,
126
- crud,
127
- method,
128
- result,
129
- doc: result,
130
- merged: result,
131
- resolver: this,
132
- key: `${method}${model}`,
133
- context: this.getContext(),
134
- query: query.doc(result).merged(result),
135
- }, () => result);
136
- }
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
+ // }
137
121
 
138
122
  // DataLoader Proxy Methods
139
123
  clear(model) {
@@ -1,8 +1,5 @@
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
 
@@ -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
- createModels() {
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 GraphqlFields = require('graphql-fields');
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
- // Getter
16
- this.get = ({ autograph }, model, { id: guid }, required = false, info) => {
17
- const query = { fields: GraphqlFields(info, {}, { processArguments: true }) };
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
  };
@@ -1,40 +1,81 @@
1
1
  const FBDataLoader = require('dataloader');
2
- const ResultSet = require('./ResultSet');
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
- // let counter = 0;
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
- // 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 });
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
- }, { 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
- });
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
- // 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);
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()),