@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/index.js +2 -6
  3. package/package.json +10 -10
  4. package/src/.DS_Store +0 -0
  5. package/src/core/EventEmitter.js +2 -4
  6. package/src/core/Resolver.js +35 -58
  7. package/src/core/Schema.js +5 -38
  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 +53 -28
  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 +109 -112
  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 -35
  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 +0 -9
  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
@@ -1,141 +1,138 @@
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
- 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();
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 {
27
+ constructor(schema, toExecutableSchema) {
28
+ super();
29
+ this.toExecutableSchema = toExecutableSchema;
30
+ this.schema = { typeDefs: [], resolvers: {}, schemaDirectives: {} };
31
+ if (schema) this.mergeSchema(schema);
23
32
  }
24
33
 
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());
34
+ /**
35
+ * Synchronously merge a schema
36
+ */
37
+ mergeSchema(schema, options = {}) {
38
+ // Ensure this is a schema of sorts otherwise skip it
39
+ if (typeof schema !== 'string' && ['typeDefs', 'resolvers', 'schemaDirectives'].every(key => !schema[key])) return this;
40
+
41
+ // Here we want to normalize the schema into the shape { typeDefs, resolvers, schemaDirectives }
42
+ // We do NOT want to modify the schema object because that may cause unwanted side-effects.
43
+ const normalizedSchema = { ...schema };
44
+ if (typeof schema === 'string') normalizedSchema.typeDefs = [schema];
45
+ else if (schema.typeDefs && !Array.isArray(schema.typeDefs)) normalizedSchema.typeDefs = [schema.typeDefs];
46
+
47
+ // For typeDefs we want the AST so that it can be intelligently merged. Here we convert
48
+ // GQL strings to AST objects and also filter out anything that does not parse to AST.
49
+ if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) {
50
+ normalizedSchema.typeDefs = deleteKeys(normalizedSchema.typeDefs.map((td) => {
51
+ try {
52
+ const ast = typeof td === 'object' ? td : parse(td);
53
+ return ast.definitions;
54
+ } catch (e) {
55
+ return null;
56
+ }
57
+ }), ['loc']).filter(Boolean).flat();
58
+ }
59
+
60
+ // Now we're ready to merge the schema
61
+ const [left, right] = options.passive ? [normalizedSchema, this.schema] : [this.schema, normalizedSchema];
62
+ if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) this.schema.typeDefs = mergeASTArray(left.typeDefs.concat(right.typeDefs));
63
+ if (normalizedSchema.resolvers) this.schema.resolvers = Merge(left.resolvers, right.resolvers);
64
+ if (normalizedSchema.schemaDirectives) this.schema.schemaDirectives = Merge(left.schemaDirectives, right.schemaDirectives);
65
+
66
+ // Chaining
67
+ return this;
33
68
  }
34
69
 
35
70
  /**
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.
71
+ * Asynchronously load files from a given glob pattern and merge each schema
38
72
  */
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;
73
+ mergeSchemaFromFiles(globPattern, options) {
74
+ return new Promise((resolve, reject) => {
75
+ Glob(globPattern, options, (err, files) => {
76
+ if (err) return reject(err);
77
+
78
+ return Promise.all(files.map((file) => {
79
+ return new Promise((res) => {
80
+ if (file.endsWith('.js')) res(require(file)); // eslint-disable-line global-require,import/no-dynamic-require
81
+ else res(FS.readFileSync(file, 'utf8'));
82
+ }).then(schema => this.mergeSchema(schema, options));
83
+ })).then(() => resolve(this)).catch(e => reject(e));
45
84
  });
46
-
47
- return definition;
48
85
  });
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
- }
79
-
80
- getScalar(name) {
81
- return this.getScalars().find(scalar => scalar.getName() === name);
82
- }
83
-
84
- getScalars() {
85
- return this.scalars;
86
86
  }
87
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());
88
+ /**
89
+ * Traverses the current schema's typeDefs in order to keep the TypeDefApi in sync. This operation
90
+ * only needs to be called when typeDefs have been changed and you want to keep the data model in sync.
91
+ */
92
+ initialize() {
93
+ super.initialize(this.schema.typeDefs);
94
+ this.getModels().forEach(model => model.initialize());
95
+ return this;
102
96
  }
103
97
 
104
- getContext() {
105
- return this.schema.context;
98
+ /**
99
+ * Decorate the schema with Autograph's default api/definitions
100
+ */
101
+ decorate() {
102
+ this.initialize();
103
+ this.mergeSchema(frameworkExt(this), { passive: true });
104
+ this.mergeSchema(typeExt(this), { passive: true });
105
+ this.initialize();
106
+ this.mergeSchema(apiExt(this), { passive: true });
107
+ this.finalize();
108
+ return this;
106
109
  }
107
110
 
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: {},
111
+ /**
112
+ * This should be called once before passing to makeExecutableSchema()
113
+ */
114
+ finalize() {
115
+ const definitions = visit(this.schema.typeDefs, {
116
+ [Kind.FIELD_DEFINITION]: (node) => {
117
+ const scope = new Node(node, 'field').getDirectiveArg('field', 'gqlScope', 'crud');
118
+ if (scope === null || scope.indexOf('r') === -1) return null; // Delete node
119
+ return false; // Stop traversing this node
120
+ },
120
121
  });
121
122
 
122
- return this.extend(schema);
123
+ this.schema.typeDefs = { kind: Kind.DOCUMENT, definitions };
124
+ return this;
123
125
  }
124
126
 
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);
129
- return this;
127
+ makeExecutableSchema() {
128
+ return this.toExecutableSchema(this.schema);
130
129
  }
131
130
 
132
- extend(...schemas) {
133
- this.sextend(...schemas);
134
- this.initialize();
135
- return this;
131
+ toObject() {
132
+ return this.schema;
136
133
  }
137
134
 
138
- makeExecutableSchema() {
139
- return makeExecutableSchema(this.getSchema());
135
+ toString() {
136
+ return print(this.typeDefs);
140
137
  }
141
138
  };
@@ -1,16 +1,15 @@
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
- const { makeCreateAPI, makeReadAPI, makeUpdateAPI, makeDeleteAPI, makeSubscriptionAPI, makeInputSplice, makeQueryResolver, makeMutationResolver } = require('../../service/decorator.service');
6
+ const { makeCreateAPI, makeReadAPI, makeUpdateAPI, makeDeleteAPI, makeSubscriptionAPI, makeQueryResolver, makeMutationResolver } = require('../../service/decorator.service');
7
7
 
8
8
  const interfaceKinds = [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
9
9
 
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;
@@ -25,8 +24,6 @@ module.exports = (schema) => {
25
24
  const createModels = findGQLModels('c', markedModels, allModels);
26
25
  const readModels = findGQLModels('r', markedModels, allModels);
27
26
  const updateModels = findGQLModels('u', markedModels, allModels);
28
- const deleteModels = findGQLModels('d', markedModels, allModels);
29
- const spliceModels = [...new Set([...createModels, ...updateModels, ...deleteModels])];
30
27
 
31
28
  return ({
32
29
  typeDefs: [
@@ -40,7 +37,6 @@ module.exports = (schema) => {
40
37
  input ${model.getName()}InputUpdate {
41
38
  ${model.getFields().filter(field => field.hasGQLScope('u') && !field.isVirtual()).map(field => `${field.getName()}: ${field.getGQLType('InputUpdate')}`)}
42
39
  }
43
- # ${makeInputSplice(model)}
44
40
  `),
45
41
 
46
42
  ...readModels.map(model => `
@@ -50,19 +46,14 @@ module.exports = (schema) => {
50
46
  input ${model.getName()}InputSort {
51
47
  ${getGQLWhereFields(model).map(field => `${field.getName()}: ${field.getModelRef() ? `${ucFirst(field.getDataRef())}InputSort` : 'SortOrderEnum'}`)}
52
48
  }
53
- `),
54
-
55
- ...readModels.map(model => `
56
49
  extend ${interfaceKinds.indexOf(model.getKind()) > -1 ? 'interface' : 'type'} ${model.getName()} {
57
50
  ${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}${field.getExtendArgs()}: ${field.getPayloadType()}`)}
58
51
  }
59
-
60
52
  type ${model.getName()}Connection {
53
+ count: Int!
61
54
  pageInfo: PageInfo!
62
55
  edges: [${model.getName()}Edge]
63
- count: Int!
64
56
  }
65
-
66
57
  type ${model.getName()}Edge {
67
58
  node: ${model.getName()}
68
59
  cursor: String!
@@ -90,7 +81,7 @@ module.exports = (schema) => {
90
81
  }
91
82
 
92
83
  type ${model.getName()}SubscriptionPayloadEventData {
93
- ${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}: ${field.getSubscriptionType()}`)}
84
+ ${getGQLWhereFields(model).map(field => `${field.getName()}: ${field.getSubscriptionType()}`)}
94
85
  }
95
86
 
96
87
  interface ${model.getName()}SubscriptionQuery {
@@ -105,13 +96,6 @@ module.exports = (schema) => {
105
96
  ${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}: ${field.getPayloadType()}`)}
106
97
  }
107
98
  `),
108
-
109
- ...spliceModels.map(model => `
110
- #input ${model.getName()}InputSplice {
111
- # with: ${model}InputWhere
112
- # put: ${model}InputUpdate
113
- #}
114
- `),
115
99
  ].concat([
116
100
  `type PageInfo {
117
101
  startCursor: String!
@@ -148,21 +132,20 @@ module.exports = (schema) => {
148
132
  const isConnection = field.isConnection();
149
133
 
150
134
  return Object.assign(def, {
151
- [fieldName]: (root, args, { autograph }) => {
152
- if (fieldName === 'id') return autograph.legacyMode ? root.id : root.$id;
153
-
154
- const $fieldName = `$${fieldName}`;
135
+ [fieldName]: (doc, args, { autograph }, info) => {
136
+ if (fieldName === 'id') return autograph.legacyMode ? doc.id : toGUID(modelName, doc.id);
155
137
 
138
+ // If this field is a connection we return thunks in order to delay query
139
+ // until the "Connection" resolver (below) is run
156
140
  if (isConnection) {
157
141
  return {
158
- args,
159
- edges: root[$fieldName], // Thunk to the data/edges
160
- pageInfo: root[$fieldName], // You still need the data/edges to get pageInfo!
161
- 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),
162
145
  };
163
146
  }
164
147
 
165
- return root.$$isResultSetItem ? root[$fieldName](args) : root[fieldName];
148
+ return field.resolve(autograph.resolver, doc, args);
166
149
  },
167
150
  });
168
151
  }, {});
@@ -179,24 +162,28 @@ module.exports = (schema) => {
179
162
  return Object.assign(prev, {
180
163
  [modelName]: fieldResolvers,
181
164
  [`${modelName}Connection`]: {
182
- edges: ({ edges, args }) => edges(args).then(rs => rs.map(node => ({ cursor: get(node, '$$cursor'), node }))),
183
- pageInfo: ({ pageInfo, args }) => pageInfo(args).then(rs => get(rs, '$$pageInfo')),
184
- 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')),
185
168
  },
186
169
  });
187
170
  }, {
188
171
  Node: {
189
- __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
190
173
  },
191
174
 
192
175
  Query: entityModels.reduce((prev, model) => {
193
176
  return Object.assign(prev, makeQueryResolver(model.getName(), model, resolver));
194
177
  }, {
195
- node: (root, args, context, info) => {
178
+ node: (doc, args, context, info) => {
196
179
  const { id } = args;
197
180
  const [modelName] = fromGUID(id);
198
181
  const model = schema.getModel(modelName);
199
- 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
+ });
200
187
  },
201
188
  }),
202
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,49 @@ 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
34
+ key: String # Specify db key
35
+ persist: Boolean # Persist this field (default true)
36
+ connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
37
+ default: AutoGraphMixed # Define a default value
37
38
  ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)
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
- embedApi: Boolean # Should we also create an embedded API from this (default false)
44
- connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
45
-
46
- noRepeat: Boolean
42
+ onDelete: AutoGraphOnDeleteEnum # onDelete behavior
47
43
 
48
44
  authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
49
- onDelete: AutoGraphOnDeleteEnum
50
45
 
51
- enforce: [AutoGraphEnforceEnum!] #
52
- transform: [AutoGraphTransformEnum!] # Transforms when serialize + deserialize
53
- serialize: [AutoGraphTransformEnum!] # Transforms when serialize
54
- deserialize: [AutoGraphTransformEnum!] # Transforms when deserialize
46
+ # Pipeline Structure
47
+ instruct: [AutoGraphPipelineEnum!]
48
+ destruct: [AutoGraphPipelineEnum!]
49
+ restruct: [AutoGraphPipelineEnum!]
50
+ construct: [AutoGraphPipelineEnum!]
51
+ serialize: [AutoGraphPipelineEnum!]
52
+ deserialize: [AutoGraphPipelineEnum!]
53
+ transform: [AutoGraphPipelineEnum!]
55
54
  ) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
56
55
 
57
56
  directive @link(
@@ -60,13 +59,6 @@ module.exports = (schema) => {
60
59
  use: AutoGraphMixed # The VALUE to use (default's to @link'd value); useful for many-to-many relationships
61
60
  ) on FIELD_DEFINITION
62
61
 
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
62
  directive @index(
71
63
  name: String
72
64
  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}", serialize: createdAt, gqlScope: r)` : ''}
21
+ ${updatedAt ? `updatedAt: AutoGraphDateTime @field(key: "${updatedAt}", serialize: timestamp, gqlScope: r)` : ''}
22
22
  }
23
23
  `;
24
24
  }