@coderich/autograph 0.10.0 → 0.10.3

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 (44) hide show
  1. package/CHANGELOG.md +20 -3
  2. package/index.js +2 -8
  3. package/package.json +5 -7
  4. package/src/.DS_Store +0 -0
  5. package/src/core/EventEmitter.js +2 -4
  6. package/src/core/Resolver.js +32 -57
  7. package/src/core/Schema.js +5 -38
  8. package/src/data/.DS_Store +0 -0
  9. package/src/data/DataLoader.js +71 -32
  10. package/src/data/DataService.js +82 -59
  11. package/src/data/Field.js +59 -126
  12. package/src/data/Model.js +113 -105
  13. package/src/data/Pipeline.js +184 -0
  14. package/src/data/Type.js +38 -74
  15. package/src/driver/MongoDriver.js +27 -22
  16. package/src/graphql/.DS_Store +0 -0
  17. package/src/graphql/ast/Field.js +46 -24
  18. package/src/graphql/ast/Model.js +5 -16
  19. package/src/graphql/ast/Node.js +0 -25
  20. package/src/graphql/ast/Schema.js +105 -112
  21. package/src/graphql/extension/api.js +20 -18
  22. package/src/graphql/extension/framework.js +27 -33
  23. package/src/graphql/extension/type.js +2 -2
  24. package/src/query/Query.js +82 -14
  25. package/src/query/QueryBuilder.js +38 -30
  26. package/src/query/QueryBuilderTransaction.js +3 -3
  27. package/src/query/QueryResolver.js +77 -41
  28. package/src/query/QueryService.js +24 -42
  29. package/src/service/app.service.js +70 -13
  30. package/src/service/event.service.js +30 -73
  31. package/src/service/graphql.service.js +0 -9
  32. package/src/service/schema.service.js +5 -3
  33. package/src/core/GraphQL.js +0 -21
  34. package/src/core/Rule.js +0 -107
  35. package/src/core/SchemaDecorator.js +0 -46
  36. package/src/core/Transformer.js +0 -68
  37. package/src/data/Memoizer.js +0 -39
  38. package/src/data/ResultSet.js +0 -205
  39. package/src/data/stream/DataHydrator.js +0 -58
  40. package/src/data/stream/ResultSet.js +0 -34
  41. package/src/data/stream/ResultSetItem.js +0 -158
  42. package/src/data/stream/ResultSetItemProxy.js +0 -161
  43. package/src/graphql/ast/SchemaDecorator.js +0 -141
  44. package/src/graphql/directive/authz.directive.js +0 -84
@@ -1,141 +1,134 @@
1
1
  const FS = require('fs');
2
2
  const Glob = require('glob');
3
3
  const Merge = require('deepmerge');
4
- const { nvl, uvl } = require('../../service/app.service');
5
- const { validateSchema, makeExecutableSchema, mergeASTSchema, mergeASTArray } = require('../../service/graphql.service');
4
+ const { Kind, print, parse, visit } = require('graphql');
5
+ const { mergeASTArray } = require('../../service/graphql.service');
6
+ const { deleteKeys } = require('../../service/app.service');
7
+ const frameworkExt = require('../extension/framework');
8
+ const typeExt = require('../extension/type');
9
+ const apiExt = require('../extension/api');
10
+ const TypeDefApi = require('./TypeDefApi');
6
11
  const Node = require('./Node');
7
- const Model = require('./Model');
8
12
 
9
- const loadFile = file => FS.readFileSync(file, 'utf8');
10
- const reqFile = file => require(file); // eslint-disable-line global-require,import/no-dynamic-require
11
-
12
- module.exports = class Schema extends Node {
13
+ /**
14
+ * Schema
15
+ *
16
+ * This class helps facilitate dynamic modification of a schema before it is passed to makeExecutableSchema(). It allows
17
+ * for "intelligent" merging of schemas and exposes an API wrapper for typeDefs.
18
+ *
19
+ * A "schema" is defined by the following object attributes:
20
+ *
21
+ * typeDefs <String|Object> - GQL String or AST Object (also supports a mixed array of both)
22
+ * resolvers <Object> - GraphQL resolvers
23
+ * schemaDirectives <Object> - GraphQL directives
24
+ *
25
+ */
26
+ module.exports = class Schema extends TypeDefApi {
13
27
  constructor(schema) {
14
- // Ensure schema
15
- schema.resolvers = schema.resolvers || {};
16
- schema.schemaDirectives = schema.schemaDirectives || {};
17
- schema.context = schema.context || {};
18
-
19
- //
20
- super(schema.typeDefs);
21
- this.schema = schema;
22
- this.initialize();
28
+ super();
29
+ this.schema = { typeDefs: [], resolvers: {}, schemaDirectives: {} };
30
+ if (schema) this.mergeSchema(schema);
23
31
  }
24
32
 
25
- initialize() {
26
- const definitions = this.ast.definitions.map(d => new Node(d));
27
- this.models = definitions.filter(d => d.isModel()).map(d => new Model(this, d.getAST()));
28
- this.modelsByName = this.models.reduce((prev, model) => Object.assign(prev, { [model.getName()]: model }), {});
29
- this.modelsByKey = this.models.reduce((prev, model) => Object.assign(prev, { [model.getKey()]: model }), {});
30
- this.inputs = definitions.filter(d => d.isInput()).map(d => new Model(this, d.getAST()));
31
- this.scalars = definitions.filter(d => d.isScalar());
32
- this.enums = definitions.filter(d => d.isEnum());
33
+ /**
34
+ * Synchronously merge a schema
35
+ */
36
+ mergeSchema(schema, options = {}) {
37
+ // Ensure this is a schema of sorts otherwise skip it
38
+ if (typeof schema !== 'string' && ['typeDefs', 'resolvers', 'schemaDirectives'].every(key => !schema[key])) return this;
39
+
40
+ // Here we want to normalize the schema into the shape { typeDefs, resolvers, schemaDirectives }
41
+ // We do NOT want to modify the schema object because that may cause unwanted side-effects.
42
+ const normalizedSchema = { ...schema };
43
+ if (typeof schema === 'string') normalizedSchema.typeDefs = [schema];
44
+ else if (schema.typeDefs && !Array.isArray(schema.typeDefs)) normalizedSchema.typeDefs = [schema.typeDefs];
45
+
46
+ // For typeDefs we want the AST so that it can be intelligently merged. Here we convert
47
+ // GQL strings to AST objects and also filter out anything that does not parse to AST.
48
+ if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) {
49
+ normalizedSchema.typeDefs = deleteKeys(normalizedSchema.typeDefs.map((td) => {
50
+ try {
51
+ const ast = typeof td === 'object' ? td : parse(td);
52
+ return ast.definitions;
53
+ } catch (e) {
54
+ return null;
55
+ }
56
+ }), ['loc']).filter(Boolean).flat();
57
+ }
58
+
59
+ // Now we're ready to merge the schema
60
+ const [left, right] = options.passive ? [normalizedSchema, this.schema] : [this.schema, normalizedSchema];
61
+ if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) this.schema.typeDefs = mergeASTArray(left.typeDefs.concat(right.typeDefs));
62
+ if (normalizedSchema.resolvers) this.schema.resolvers = Merge(left.resolvers, right.resolvers);
63
+ if (normalizedSchema.schemaDirectives) this.schema.schemaDirectives = Merge(left.schemaDirectives, right.schemaDirectives);
64
+
65
+ // Chaining
66
+ return this;
33
67
  }
34
68
 
35
69
  /**
36
- * This is the final call used to pass in a schema to makeExecutableSchema.
37
- * Here I'm making last-minute modifications to the schema that is exposed to the API.
70
+ * Asynchronously load files from a given glob pattern and merge each schema
38
71
  */
39
- getSchema() {
40
- const definitions = this.ast.definitions.map((definition) => {
41
- definition.fields = (definition.fields || []).filter((f) => {
42
- const node = new Node(f, 'field');
43
- const scope = nvl(uvl(node.getDirectiveArg('field', 'gqlScope'), 'crud'), '');
44
- return scope.indexOf('r') > -1;
72
+ mergeSchemaFromFiles(globPattern, options) {
73
+ return new Promise((resolve, reject) => {
74
+ Glob(globPattern, options, (err, files) => {
75
+ if (err) return reject(err);
76
+
77
+ return Promise.all(files.map((file) => {
78
+ return new Promise((res) => {
79
+ if (file.endsWith('.js')) res(require(file)); // eslint-disable-line global-require,import/no-dynamic-require
80
+ else res(FS.readFileSync(file, 'utf8'));
81
+ }).then(schema => this.mergeSchema(schema, options));
82
+ })).then(() => resolve(this)).catch(e => reject(e));
45
83
  });
46
-
47
- return definition;
48
84
  });
49
-
50
- const ast = Object.assign({}, this.ast, { definitions });
51
- const schema = Object.assign({}, this.schema, { typeDefs: ast });
52
- validateSchema(schema);
53
- return schema;
54
- }
55
-
56
- getModel(name) {
57
- return this.modelsByName[name] || this.modelsByKey[name];
58
- }
59
-
60
- getModels() {
61
- return this.models;
62
- }
63
-
64
- getModelNames() {
65
- return this.getModels().map(model => model.getName());
66
- }
67
-
68
- getModelMap() {
69
- return this.getModels().reduce((prev, model) => Object.assign(prev, { [model.getName()]: model }), {});
70
- }
71
-
72
- getInput(name) {
73
- return this.getInputs().find(input => input.getName() === name);
74
- }
75
-
76
- getInputs() {
77
- return this.inputs;
78
85
  }
79
86
 
80
- getScalar(name) {
81
- return this.getScalars().find(scalar => scalar.getName() === name);
82
- }
83
-
84
- getScalars() {
85
- return this.scalars;
86
- }
87
-
88
- getEnum(name) {
89
- return this.getEnums().find(el => el.getName() === name);
90
- }
91
-
92
- getEnums() {
93
- return this.enums;
94
- }
95
-
96
- getMarkedModels() {
97
- return this.getModels().filter(model => model.isMarkedModel());
98
- }
99
-
100
- getEntityModels() {
101
- return this.getModels().filter(model => model.isEntity());
87
+ /**
88
+ * Traverses the current schema's typeDefs in order to keep the TypeDefApi in sync. This operation
89
+ * only needs to be called when typeDefs have been changed and you want to keep the data model in sync.
90
+ */
91
+ initialize() {
92
+ super.initialize(this.schema.typeDefs);
93
+ this.getModels().forEach(model => model.initialize());
94
+ return this;
102
95
  }
103
96
 
104
- getContext() {
105
- return this.schema.context;
97
+ /**
98
+ * Decorate the schema with Autograph's default api/definitions
99
+ */
100
+ decorate() {
101
+ this.initialize();
102
+ this.mergeSchema(frameworkExt(this), { passive: true });
103
+ this.mergeSchema(typeExt(this), { passive: true });
104
+ this.initialize();
105
+ this.mergeSchema(apiExt(this), { passive: true });
106
+ this.finalize();
107
+ return this;
106
108
  }
107
109
 
108
- loadDir(dir, options) {
109
- // Typedefs
110
- const typeDefs = Glob.sync(`${dir}/**/*.{gql,graphql}`, options).map(file => loadFile(file)).join('\n\n');
111
-
112
- // Possibly full schema definitions
113
- const schema = Glob.sync(`${dir}/**/*.js`, options).map(file => reqFile(file)).reduce((prev, data) => {
114
- return Merge(prev, data);
115
- }, {
116
- typeDefs: typeDefs.length ? typeDefs : undefined,
117
- context: {},
118
- resolvers: {},
119
- schemaDirectives: {},
110
+ /**
111
+ * This should be called once before passing to makeExecutableSchema()
112
+ */
113
+ finalize() {
114
+ const definitions = visit(this.schema.typeDefs, {
115
+ [Kind.FIELD_DEFINITION]: (node) => {
116
+ const scope = new Node(node, 'field').getDirectiveArg('field', 'gqlScope', 'crud');
117
+ if (scope === null || scope.indexOf('r') === -1) return null; // Delete node
118
+ return false; // Stop traversing this node
119
+ },
120
120
  });
121
121
 
122
- return this.extend(schema);
123
- }
124
-
125
- sextend(...schemas) {
126
- const definitions = schemas.filter(schema => schema.typeDefs).map(schema => mergeASTSchema(schema.typeDefs).definitions);
127
- this.ast.definitions = mergeASTArray(this.ast.definitions.concat(...definitions));
128
- this.schema.resolvers = Merge(schemas.reduce((prev, schema) => Merge(prev, schema.resolvers || {}), {}), this.schema.resolvers);
122
+ this.schema.typeDefs = { kind: Kind.DOCUMENT, definitions };
123
+ // validateSchema(this.schema);
129
124
  return this;
130
125
  }
131
126
 
132
- extend(...schemas) {
133
- this.sextend(...schemas);
134
- this.initialize();
135
- return this;
127
+ toObject() {
128
+ return this.schema;
136
129
  }
137
130
 
138
- makeExecutableSchema() {
139
- return makeExecutableSchema(this.getSchema());
131
+ toString() {
132
+ return print(this.typeDefs);
140
133
  }
141
134
  };
@@ -1,7 +1,7 @@
1
1
  const { get } = require('lodash');
2
2
  const { Kind } = require('graphql');
3
3
  const ServerResolver = require('../../core/ServerResolver');
4
- const { ucFirst, fromGUID } = require('../../service/app.service');
4
+ const { ucFirst, toGUID, fromGUID } = require('../../service/app.service');
5
5
  const { findGQLModels } = require('../../service/schema.service');
6
6
  const { makeCreateAPI, makeReadAPI, makeUpdateAPI, makeDeleteAPI, makeSubscriptionAPI, makeQueryResolver, makeMutationResolver } = require('../../service/decorator.service');
7
7
 
@@ -10,7 +10,6 @@ const interfaceKinds = [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTE
10
10
  const getGQLWhereFields = (model) => {
11
11
  return model.getFields().filter((field) => {
12
12
  if (!field.hasGQLScope('r')) return false;
13
- if (field.hasBoundValue() && !field.getDirectiveArg('value', 'passive')) return false;
14
13
  const modelRef = field.getModelRef();
15
14
  if (modelRef && !modelRef.isEmbedded() && !modelRef.isEntity()) return false;
16
15
  return true;
@@ -51,9 +50,9 @@ module.exports = (schema) => {
51
50
  ${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}${field.getExtendArgs()}: ${field.getPayloadType()}`)}
52
51
  }
53
52
  type ${model.getName()}Connection {
53
+ count: Int!
54
54
  pageInfo: PageInfo!
55
55
  edges: [${model.getName()}Edge]
56
- count: Int!
57
56
  }
58
57
  type ${model.getName()}Edge {
59
58
  node: ${model.getName()}
@@ -133,21 +132,20 @@ module.exports = (schema) => {
133
132
  const isConnection = field.isConnection();
134
133
 
135
134
  return Object.assign(def, {
136
- [fieldName]: (root, args, { autograph }) => {
137
- if (fieldName === 'id') return autograph.legacyMode ? root.id : root.$id;
138
-
139
- const $fieldName = `$${fieldName}`;
135
+ [fieldName]: (doc, args, { autograph }, info) => {
136
+ if (fieldName === 'id') return autograph.legacyMode ? doc.id : toGUID(modelName, doc.id);
140
137
 
138
+ // If this field is a connection we return thunks in order to delay query
139
+ // until the "Connection" resolver (below) is run
141
140
  if (isConnection) {
142
141
  return {
143
- args,
144
- edges: root[$fieldName], // Thunk to the data/edges
145
- pageInfo: root[$fieldName], // You still need the data/edges to get pageInfo!
146
- count: root[`${$fieldName}:count`], // Thunk to $$count
142
+ count: () => field.count(autograph.resolver, doc, args),
143
+ edges: () => field.resolve(autograph.resolver, doc, args),
144
+ pageInfo: () => field.resolve(autograph.resolver, doc, args),
147
145
  };
148
146
  }
149
147
 
150
- return root.$$isResultSetItem ? root[$fieldName](args) : root[fieldName];
148
+ return field.resolve(autograph.resolver, doc, args);
151
149
  },
152
150
  });
153
151
  }, {});
@@ -164,24 +162,28 @@ module.exports = (schema) => {
164
162
  return Object.assign(prev, {
165
163
  [modelName]: fieldResolvers,
166
164
  [`${modelName}Connection`]: {
167
- edges: ({ edges, args }) => edges(args).then(rs => rs.map(node => ({ cursor: get(node, '$$cursor'), node }))),
168
- pageInfo: ({ pageInfo, args }) => pageInfo(args).then(rs => get(rs, '$$pageInfo')),
169
- count: ({ count, args }) => count(args),
165
+ count: ({ count }) => count(),
166
+ edges: ({ edges }) => edges().then(rs => rs.map(node => ({ cursor: get(node, '$cursor'), node }))),
167
+ pageInfo: ({ pageInfo }) => pageInfo().then(rs => get(rs, '$pageInfo')),
170
168
  },
171
169
  });
172
170
  }, {
173
171
  Node: {
174
- __resolveType: (root, args, context, info) => root.__typename || fromGUID(root.$id)[0], // eslint-disable-line no-underscore-dangle
172
+ __resolveType: (doc, args, context, info) => doc.__typename, // eslint-disable-line no-underscore-dangle
175
173
  },
176
174
 
177
175
  Query: entityModels.reduce((prev, model) => {
178
176
  return Object.assign(prev, makeQueryResolver(model.getName(), model, resolver));
179
177
  }, {
180
- node: (root, args, context, info) => {
178
+ node: (doc, args, context, info) => {
181
179
  const { id } = args;
182
180
  const [modelName] = fromGUID(id);
183
181
  const model = schema.getModel(modelName);
184
- return resolver.get(context, model, args, false, info);
182
+ return resolver.get(context, model, args, false, info).then((result) => {
183
+ if (result == null) return result;
184
+ result.__typename = modelName; // eslint-disable-line no-underscore-dangle
185
+ return result;
186
+ });
185
187
  },
186
188
  }),
187
189
 
@@ -1,5 +1,6 @@
1
- const Rule = require('../../core/Rule');
2
- const Transformer = require('../../core/Transformer');
1
+ const Pipeline = require('../../data/Pipeline');
2
+
3
+ Pipeline.createPresets();
3
4
 
4
5
  module.exports = (schema) => {
5
6
  return {
@@ -7,51 +8,51 @@ module.exports = (schema) => {
7
8
  scalar AutoGraphMixed
8
9
  scalar AutoGraphDriver
9
10
  scalar AutoGraphDateTime @field(transform: toDate)
10
- enum AutoGraphEnforceEnum { ${Object.keys(Rule.getInstances()).join(' ')} }
11
- enum AutoGraphTransformEnum { ${Object.keys(Transformer.getInstances()).join(' ')} }
11
+ enum AutoGraphPipelineEnum { ${Object.keys(Pipeline).join(' ')} }
12
12
  enum AutoGraphAuthzEnum { private protected public }
13
- enum AutoGraphValueScopeEnum { self context }
14
13
  enum AutoGraphOnDeleteEnum { cascade nullify restrict defer }
15
14
  enum AutoGraphIndexEnum { unique }
16
15
 
17
16
  directive @model(
18
- key: String # Specify it's key during transit
17
+ id: String # Specify db key (default "id")
18
+ key: String # Specify db table/collection name
19
+ createdAt: String # Specify db key (default "createdAt")
20
+ updatedAt: String # Specify db key (default "updatedAt")
21
+ meta: AutoGraphMixed # Custom input "meta" field for mutations
22
+ embed: Boolean # Mark this an embedded model (default false)
23
+ persist: Boolean # Persist this model (default true)
19
24
  gqlScope: AutoGraphMixed # Dictate how GraphQL API behaves
20
25
  dalScope: AutoGraphMixed # Dictate how the DAL behaves
21
26
  fieldScope: AutoGraphMixed # Dictate how a FIELD may use me
22
- meta: AutoGraphMixed # Custom input 'meta' field for mutations
23
- embed: Boolean # Mark this an embedded model (default false)
24
- persist: Boolean # Persist this model (default true)
25
27
  driver: AutoGraphDriver # External data driver
26
28
  authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
27
29
  namespace: String # Logical grouping of models that can be globbed (useful for authz)
28
-
29
- # Override auto-gen
30
- id: String
31
- createdAt: String
32
- updatedAt: String
33
30
  ) on OBJECT | INTERFACE
34
31
 
35
32
  directive @field(
36
- key: String # Specify it's key during transit
33
+ id: String # Specify the ModelRef this field FK References
37
34
  ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)
35
+ key: String # Specify db key
36
+ persist: Boolean # Persist this field (default true)
37
+ connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
38
+ default: AutoGraphMixed # Define a default value
38
39
  gqlScope: AutoGraphMixed # Dictate how GraphQL API behaves
39
40
  dalScope: AutoGraphMixed # Dictate how the DAL behaves
40
41
  fieldScope: AutoGraphMixed # Dictate how a FIELD may use me
41
- persist: Boolean # Persist this field (default true)
42
- default: AutoGraphMixed # Define a default value
43
- connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
44
-
45
- noRepeat: Boolean
42
+ onDelete: AutoGraphOnDeleteEnum # onDelete behavior
46
43
 
47
44
  authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
48
- onDelete: AutoGraphOnDeleteEnum
49
45
 
50
- enforce: [AutoGraphEnforceEnum!] #
51
- resolve: [AutoGraphTransformEnum!] # Transforms when resolving
52
- transform: [AutoGraphTransformEnum!] # Transforms when serialize + deserialize
53
- serialize: [AutoGraphTransformEnum!] # Transforms when serialize
54
- deserialize: [AutoGraphTransformEnum!] # Transforms when deserialize
46
+ # Pipeline Structure
47
+ validate: [AutoGraphPipelineEnum!]
48
+ instruct: [AutoGraphPipelineEnum!]
49
+ restruct: [AutoGraphPipelineEnum!]
50
+ destruct: [AutoGraphPipelineEnum!]
51
+ construct: [AutoGraphPipelineEnum!]
52
+ transform: [AutoGraphPipelineEnum!]
53
+ normalize: [AutoGraphPipelineEnum!]
54
+ serialize: [AutoGraphPipelineEnum!]
55
+ deserialize: [AutoGraphPipelineEnum!]
55
56
  ) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
56
57
 
57
58
  directive @link(
@@ -60,13 +61,6 @@ module.exports = (schema) => {
60
61
  use: AutoGraphMixed # The VALUE to use (default's to @link'd value); useful for many-to-many relationships
61
62
  ) on FIELD_DEFINITION
62
63
 
63
- directive @value(
64
- path: String! # The path to the data
65
- # merge: Boolean # Deep merge the data? (default false - overwrite) [does not even look supported at the moment]
66
- passive: Boolean # If value exists leave it alone (default false)
67
- scope: AutoGraphValueScopeEnum # Where to look for the data (default self)
68
- ) on OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
69
-
70
64
  directive @index(
71
65
  name: String
72
66
  on: [AutoGraphMixed!]!
@@ -17,8 +17,8 @@ module.exports = (schema) => {
17
17
  return `
18
18
  extend type ${modelName}${interfacesGQL} {
19
19
  ${id ? `id: ID! @field(key: "${id}", gqlScope: r)` : ''}
20
- ${createdAt ? `createdAt: AutoGraphDateTime @field(key: "${createdAt}", gqlScope: r)` : ''}
21
- ${updatedAt ? `updatedAt: AutoGraphDateTime @field(key: "${updatedAt}", gqlScope: r)` : ''}
20
+ ${createdAt ? `createdAt: AutoGraphDateTime @field(key: "${createdAt}", construct: createdAt, gqlScope: r)` : ''}
21
+ ${updatedAt ? `updatedAt: AutoGraphDateTime @field(key: "${updatedAt}", serialize: timestamp, gqlScope: r)` : ''}
22
22
  }
23
23
  `;
24
24
  }
@@ -1,15 +1,21 @@
1
1
  const Boom = require('../core/Boom');
2
- const { unravelObject } = require('../service/app.service');
3
2
 
4
3
  module.exports = class Query {
5
4
  constructor(props = {}) {
6
5
  this.props = {};
7
6
  this.timers = {};
7
+ this.isCursorPaging = false;
8
+ this.isClassicPaging = false;
8
9
  this.props.joins = this.props.joins || [];
9
10
  this.props.match = this.props.match || {};
10
11
  this.props.options = this.props.options || {};
11
- this.isClassicPaging = false;
12
- this.isCursorPaging = false;
12
+ this.props.flags = this.props.flags || {
13
+ debug: false,
14
+ silent: false,
15
+ validate: true, // { externals }
16
+ pipeline: true, // { instruct, construct, destruct, restruct, serialize, deserialize, transform, rekey }
17
+ required: false,
18
+ };
13
19
  this.merge(props);
14
20
  }
15
21
 
@@ -22,12 +28,19 @@ module.exports = class Query {
22
28
  id(id) {
23
29
  this.propCheck('id', 'where', 'native', 'sort', 'skip', 'limit', 'before', 'after', 'first', 'last');
24
30
  this.props.id = id;
31
+ this.props.batch = 'id';
32
+ this.props.where = { id };
25
33
  return this.match({ id });
26
34
  }
27
35
 
36
+ batch(batch) {
37
+ this.props.batch = batch;
38
+ return this;
39
+ }
40
+
28
41
  where(where) {
29
42
  this.propCheck('where', 'id', 'native');
30
- this.props.where = unravelObject(where);
43
+ this.props.where = where;
31
44
  return this.match(where);
32
45
  }
33
46
 
@@ -44,12 +57,12 @@ module.exports = class Query {
44
57
  }
45
58
 
46
59
  match(match) {
47
- this.props.match = unravelObject(match);
60
+ this.props.match = match;
48
61
  return this;
49
62
  }
50
63
 
51
64
  select(select) {
52
- this.props.select = unravelObject(select);
65
+ this.props.select = select;
53
66
  return this;
54
67
  }
55
68
 
@@ -70,7 +83,7 @@ module.exports = class Query {
70
83
 
71
84
  sort(sort) {
72
85
  this.propCheck('sort', 'id');
73
- this.props.sort = unravelObject(sort);
86
+ this.props.sort = sort;
74
87
  return this;
75
88
  }
76
89
 
@@ -142,7 +155,7 @@ module.exports = class Query {
142
155
  }
143
156
 
144
157
  flags(flags) {
145
- this.props.flags = flags;
158
+ Object.assign(this.props.flags, flags);
146
159
  return this;
147
160
  }
148
161
 
@@ -161,6 +174,12 @@ module.exports = class Query {
161
174
 
162
175
  resolver(resolver) {
163
176
  this.props.resolver = resolver;
177
+ this.props.context = resolver.getContext();
178
+ return this;
179
+ }
180
+
181
+ context(context) {
182
+ this.props.context = context;
164
183
  return this;
165
184
  }
166
185
 
@@ -175,7 +194,7 @@ module.exports = class Query {
175
194
  }
176
195
 
177
196
  cmd(cmd) {
178
- this.props.cmd = cmd;
197
+ this.props.cmd = cmd; // Terminal cmd from QueryBuilder
179
198
  return this;
180
199
  }
181
200
 
@@ -185,14 +204,32 @@ module.exports = class Query {
185
204
  switch (method) {
186
205
  case 'createOne': case 'createMany': {
187
206
  this.props.crud = 'create';
207
+ this.props.key = `create${this.props.model}`;
188
208
  break;
189
209
  }
190
210
  case 'updateOne': case 'updateMany': {
191
211
  this.props.crud = 'update';
212
+ this.props.key = `update${this.props.model}`;
192
213
  break;
193
214
  }
194
- case 'deleteOne': case 'deleteMay': case 'removeOne': case 'removeMany': {
215
+ case 'deleteOne': case 'deleteMany': case 'removeOne': case 'removeMany': {
195
216
  this.props.crud = 'delete';
217
+ this.props.key = `delete${this.props.model}`;
218
+ break;
219
+ }
220
+ case 'count': {
221
+ this.props.crud = 'read';
222
+ this.props.key = `count${this.props.model}`;
223
+ break;
224
+ }
225
+ case 'findOne': {
226
+ this.props.crud = 'read';
227
+ this.props.key = `get${this.props.model}`;
228
+ break;
229
+ }
230
+ case 'findMany': {
231
+ this.props.crud = 'read';
232
+ this.props.key = `find${this.props.model}`;
196
233
  break;
197
234
  }
198
235
  default: {
@@ -209,6 +246,11 @@ module.exports = class Query {
209
246
  return this;
210
247
  }
211
248
 
249
+ key(key) {
250
+ this.props.key = key;
251
+ return this;
252
+ }
253
+
212
254
  input(input = {}) { // Allows .save(/* empty */);
213
255
  // delete input.id; // We do not want to allow changing id via input
214
256
  this.props.input = input;
@@ -230,6 +272,16 @@ module.exports = class Query {
230
272
  return this;
231
273
  }
232
274
 
275
+ payload(payload) {
276
+ this.props.payload = payload;
277
+ return this;
278
+ }
279
+
280
+ result(result) {
281
+ this.props.result = result;
282
+ return this;
283
+ }
284
+
233
285
  $doc($doc) {
234
286
  this.props.$doc = $doc;
235
287
  return this;
@@ -241,10 +293,13 @@ module.exports = class Query {
241
293
  }
242
294
 
243
295
  clone() {
244
- return new Query({ ...this.props });
296
+ const clone = new Query();
297
+ clone.props = { ...this.props };
298
+ return clone;
245
299
  }
246
300
 
247
301
  toDriver() {
302
+ const self = this;
248
303
  const { model } = this.props;
249
304
  const isSorted = Boolean(Object.keys(this.props.$sort || {}).length);
250
305
 
@@ -261,9 +316,22 @@ module.exports = class Query {
261
316
  skip: this.props.skip,
262
317
  limit: this.props.limit,
263
318
  first: isSorted ? this.props.first : undefined,
264
- after: isSorted && this.props.after ? model.normalize(this, JSON.parse(Buffer.from(this.props.after, 'base64').toString('ascii')), 'serialize') : undefined,
265
319
  last: isSorted ? this.props.last : undefined,
266
- before: isSorted && this.props.before ? model.normalize(this, JSON.parse(Buffer.from(this.props.before, 'base64').toString('ascii')), 'serialize') : undefined,
320
+ // before: isSorted && this.props.before ? model.normalize(this, JSON.parse(Buffer.from(this.props.before, 'base64').toString('ascii')), 'serialize') : undefined,
321
+ get before() {
322
+ if (!isSorted || !self.props.before) return undefined;
323
+ const shape = model.getShape('create', 'sort');
324
+ const before = JSON.parse(Buffer.from(self.props.before, 'base64').toString('ascii'));
325
+ const $before = model.shapeObject(shape, before, self);
326
+ return $before;
327
+ },
328
+ get after() {
329
+ if (!isSorted || !self.props.after) return undefined;
330
+ const shape = model.getShape('create', 'sort');
331
+ const after = JSON.parse(Buffer.from(self.props.after, 'base64').toString('ascii'));
332
+ const $after = model.shapeObject(shape, after, self);
333
+ return $after;
334
+ },
267
335
  options: this.props.options,
268
336
  input: this.props.$input,
269
337
  flags: this.props.flags,
@@ -278,7 +346,7 @@ module.exports = class Query {
278
346
 
279
347
  getCacheKey() {
280
348
  return {
281
- // model: `${this.props.model}`,
349
+ cmd: this.props.cmd,
282
350
  method: this.props.method,
283
351
  where: this.props.match,
284
352
  search: this.props.search,