@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.
- package/CHANGELOG.md +20 -3
- package/index.js +2 -8
- package/package.json +5 -7
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +32 -57
- package/src/core/Schema.js +5 -38
- package/src/data/.DS_Store +0 -0
- package/src/data/DataLoader.js +71 -32
- package/src/data/DataService.js +82 -59
- package/src/data/Field.js +59 -126
- package/src/data/Model.js +113 -105
- package/src/data/Pipeline.js +184 -0
- package/src/data/Type.js +38 -74
- package/src/driver/MongoDriver.js +27 -22
- package/src/graphql/.DS_Store +0 -0
- package/src/graphql/ast/Field.js +46 -24
- package/src/graphql/ast/Model.js +5 -16
- package/src/graphql/ast/Node.js +0 -25
- package/src/graphql/ast/Schema.js +105 -112
- package/src/graphql/extension/api.js +20 -18
- package/src/graphql/extension/framework.js +27 -33
- package/src/graphql/extension/type.js +2 -2
- package/src/query/Query.js +82 -14
- package/src/query/QueryBuilder.js +38 -30
- package/src/query/QueryBuilderTransaction.js +3 -3
- package/src/query/QueryResolver.js +77 -41
- package/src/query/QueryService.js +24 -42
- package/src/service/app.service.js +70 -13
- package/src/service/event.service.js +30 -73
- package/src/service/graphql.service.js +0 -9
- package/src/service/schema.service.js +5 -3
- package/src/core/GraphQL.js +0 -21
- 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 -205
- package/src/data/stream/DataHydrator.js +0 -58
- package/src/data/stream/ResultSet.js +0 -34
- package/src/data/stream/ResultSetItem.js +0 -158
- package/src/data/stream/ResultSetItemProxy.js +0 -161
- package/src/graphql/ast/SchemaDecorator.js +0 -141
- 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 {
|
|
5
|
-
const {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
schema
|
|
16
|
-
schema.
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
this.
|
|
134
|
-
this.initialize();
|
|
135
|
-
return this;
|
|
127
|
+
toObject() {
|
|
128
|
+
return this.schema;
|
|
136
129
|
}
|
|
137
130
|
|
|
138
|
-
|
|
139
|
-
return
|
|
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]: (
|
|
137
|
-
if (fieldName === 'id') return autograph.legacyMode ?
|
|
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:
|
|
145
|
-
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
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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: (
|
|
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: (
|
|
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
|
|
2
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
}
|
package/src/query/Query.js
CHANGED
|
@@ -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.
|
|
12
|
-
|
|
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 =
|
|
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 =
|
|
60
|
+
this.props.match = match;
|
|
48
61
|
return this;
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
select(select) {
|
|
52
|
-
this.props.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 =
|
|
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
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
349
|
+
cmd: this.props.cmd,
|
|
282
350
|
method: this.props.method,
|
|
283
351
|
where: this.props.match,
|
|
284
352
|
search: this.props.search,
|