@coderich/autograph 0.8.16 → 0.9.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.9.x
4
+ - Subscriptions API
5
+ - postMutation no longer mutates "doc" and adds "result"
6
+
3
7
  ## v0.8.x
4
8
  - Engine 14+
5
9
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coderich/autograph",
3
3
  "author": "Richard Livolsi (coderich)",
4
- "version": "0.8.16",
4
+ "version": "0.9.3",
5
5
  "description": "AutoGraph",
6
6
  "keywords": [
7
7
  "graphql",
@@ -7,14 +7,10 @@ const QueryBuilder = require('../query/QueryBuilder');
7
7
 
8
8
  module.exports = class Resolver {
9
9
  constructor(schema, context = {}) {
10
- const models = schema.getModels();
10
+ this.models = schema.getModels();
11
11
  this.schema = schema;
12
12
  this.context = context;
13
- this.loaders = models.reduce((prev, model) => prev.set(model, new DataLoader(this, model)), new WeakMap());
14
-
15
- // DataLoader Proxy Methods
16
- this.clear = model => this.loaders.get(model).clearAll();
17
- this.clearAll = () => models.forEach(model => this.loaders.get(model).clearAll());
13
+ this.loaders = this.models.reduce((prev, model) => prev.set(`${model}`, new DataLoader(this, model)), new Map());
18
14
 
19
15
  //
20
16
  this.getSchema = () => this.schema;
@@ -66,7 +62,7 @@ module.exports = class Resolver {
66
62
  if (Object.prototype.hasOwnProperty.call(where, key) && where[key] == null) return Promise.resolve(null);
67
63
 
68
64
  //
69
- return this.loaders.get(model).load(query);
65
+ return this.loaders.get(`${model}`).load(query);
70
66
  }
71
67
  }
72
68
  }
@@ -93,4 +89,15 @@ module.exports = class Resolver {
93
89
  toResultSet(model, data) {
94
90
  return new ResultSet(new Query({ model: this.toModel(model), resolver: this }), data);
95
91
  }
92
+
93
+ // DataLoader Proxy Methods
94
+ clear(model) {
95
+ this.loaders.get(`${model}`).clearAll();
96
+ return this;
97
+ }
98
+
99
+ clearAll() {
100
+ this.models.forEach(model => this.clear(model));
101
+ return this;
102
+ }
96
103
  };
package/src/data/Field.js CHANGED
@@ -137,4 +137,14 @@ module.exports = class extends Field {
137
137
  return uvl(this.cast(results.pop()), value);
138
138
  });
139
139
  }
140
+
141
+ tform(query, value) {
142
+ // Determine transformers
143
+ const transformers = this.getTransformers();
144
+
145
+ // Transform
146
+ return transformers.reduce((prev, transformer) => {
147
+ return transformer(this, prev, query);
148
+ }, this.cast(value));
149
+ }
140
150
  };
package/src/data/Model.js CHANGED
@@ -115,7 +115,7 @@ module.exports = class extends Model {
115
115
  // Transform all the data
116
116
  return map(data, (doc) => {
117
117
  // We want the appendFields + those in the data, deduped
118
- const fields = [...new Set(appendFields.concat(Object.keys(doc).map(k => this.getField(k))))].filter(f => f);
118
+ const fields = [...new Set(appendFields.concat(Object.keys(doc).map(k => this.getField(k))))].filter(Boolean);
119
119
 
120
120
  // Loop through the fields and delegate (renaming keys appropriately)
121
121
  return fields.reduce((prev, field) => {
@@ -129,7 +129,7 @@ module.exports = class extends Model {
129
129
  normalize(query, data, serdes = (() => { throw new Error('No Sir Sir SerDes!'); }), keysOnly = false) {
130
130
  // Transform all the data
131
131
  return map(data, (doc) => {
132
- const fields = Object.keys(doc).map(k => this.getField(k)).filter(f => f);
132
+ const fields = Object.keys(doc).map(k => this.getField(k)).filter(Boolean);
133
133
 
134
134
  // Loop through the fields and delegate (renaming keys appropriately)
135
135
  return fields.reduce((prev, field) => {
@@ -150,4 +150,14 @@ module.exports = class extends Model {
150
150
  })));
151
151
  }));
152
152
  }
153
+
154
+ tform(query, data) {
155
+ return map(data, (doc) => {
156
+ return Object.keys(doc).map(k => this.getField(k)).filter(Boolean).reduce((prev, curr) => {
157
+ const key = curr.getName();
158
+ const value = doc[key];
159
+ return Object.assign(prev, { [key]: curr.tform(query, value) });
160
+ }, {});
161
+ });
162
+ }
153
163
  };
@@ -13,161 +13,6 @@ module.exports = class ResultSet {
13
13
  //
14
14
  const cache = new Map();
15
15
 
16
- // // Base definition all results have
17
- // const definition = {
18
- // id: {
19
- // get() { return doc.id || doc[model.idKey()]; },
20
- // set(id) { doc.id = id; }, // Embedded array of documents need to set id
21
- // enumerable: true,
22
- // },
23
-
24
- // $id: {
25
- // get() { return toGUID(model.getName(), this.id); },
26
- // enumerable: false,
27
- // },
28
-
29
- // $$cursor: {
30
- // get() {
31
- // const sortPaths = keyPaths(sort);
32
- // const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(this, path) }), {});
33
- // const sortJSON = JSON.stringify(sortValues);
34
- // return Buffer.from(sortJSON).toString('base64');
35
- // },
36
- // enumerable: false,
37
- // },
38
-
39
- // $$model: {
40
- // value: model,
41
- // enumerable: false,
42
- // },
43
-
44
- // $$data: {
45
- // value: data,
46
- // enumerable: false,
47
- // },
48
-
49
- // $$isResultSetItem: {
50
- // value: true,
51
- // enumerable: false,
52
- // },
53
-
54
- // $$save: {
55
- // get() { return input => resolver.match(model).id(this.id).save({ ...this, ...input }); },
56
- // enumerable: false,
57
- // },
58
-
59
- // $$remove: {
60
- // get() { return () => resolver.match(model).id(this.id).remove(); },
61
- // enumerable: false,
62
- // },
63
-
64
- // $$delete: {
65
- // get() { return () => resolver.match(model).id(this.id).delete(); },
66
- // enumerable: false,
67
- // },
68
-
69
- // toObject: {
70
- // get() {
71
- // return () => map(this, obj => Object.entries(obj).reduce((prev, [key, value]) => {
72
- // if (value === undefined) return prev;
73
- // prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
74
- // return prev;
75
- // }, {}));
76
- // },
77
- // enumerable: false,
78
- // configurable: true,
79
- // },
80
- // };
81
-
82
- // fields.forEach((field) => {
83
- // const key = field.getKey();
84
- // const name = field.getName();
85
- // const $name = `$${name}`;
86
- // const value = doc[key];
87
-
88
- // // Field attributes
89
- // definition[name] = {
90
- // get() {
91
- // if (cache.has(name)) return cache.get(name);
92
- // let $value = field.deserialize(query, value);
93
- // $value = $value != null && field.isEmbedded() ? new ResultSet(query.model(field.getModelRef()), $value, false) : $value;
94
- // cache.set(name, $value);
95
- // return $value;
96
- // },
97
- // set($value) {
98
- // cache.set(name, $value);
99
- // },
100
- // enumerable: true,
101
- // configurable: true, // Allows things like delete
102
- // };
103
-
104
- // // Hydrated field attributes
105
- // definition[`$${name}`] = {
106
- // get() {
107
- // return (args = {}) => {
108
- // // Ensure where clause
109
- // args.where = args.where || {};
110
-
111
- // // Cache
112
- // const cacheKey = `${$name}-${hashObject(args)}`;
113
- // if (cache.has(cacheKey)) return cache.get(cacheKey);
114
-
115
- // const promise = new Promise((resolve, reject) => {
116
- // (() => {
117
- // const $value = this[name];
118
-
119
- // if (field.isScalar() || field.isEmbedded()) return Promise.resolve($value);
120
-
121
- // const modelRef = field.getModelRef();
122
-
123
- // if (field.isArray()) {
124
- // if (field.isVirtual()) {
125
- // args.where[[field.getVirtualField()]] = this.id; // Is where[[field.getVirtualField()]] correct?
126
- // return resolver.match(modelRef).merge(args).many();
127
- // }
128
-
129
- // // Not a "required" query + strip out nulls
130
- // args.where.id = $value;
131
- // return resolver.match(modelRef).merge(args).many();
132
- // }
133
-
134
- // if (field.isVirtual()) {
135
- // args.where[[field.getVirtualField()]] = this.id;
136
- // return resolver.match(modelRef).merge(args).one();
137
- // }
138
-
139
- // return resolver.match(modelRef).id($value).one({ required: field.isRequired() });
140
- // })().then((results) => {
141
- // if (results == null) return field.resolve(query, results); // Allow field to determine
142
- // return mapPromise(results, result => field.resolve(query, result)).then(() => results); // Resolve the inside fields but still return "results"!!!!
143
- // }).then((resolved) => {
144
- // resolve(resolved);
145
- // }).catch((e) => {
146
- // reject(e);
147
- // });
148
- // });
149
-
150
- // cache.set(cacheKey, promise);
151
- // return promise;
152
- // };
153
- // },
154
- // enumerable: false,
155
- // };
156
-
157
- // // Field count (let's assume it's a Connection Type - meaning dont try with anything else)
158
- // definition[`$${name}:count`] = {
159
- // get() {
160
- // return (q = {}) => {
161
- // q.where = q.where || {};
162
- // if (field.isVirtual()) q.where[field.getVirtualField()] = this.id;
163
- // else q.where.id = this[name];
164
- // return resolver.match(field.getModelRef()).merge(q).count();
165
- // };
166
- // },
167
- // enumerable: false,
168
- // };
169
- // });
170
-
171
16
  const definition = fields.reduce((prev, field) => {
172
17
  const key = field.getKey();
173
18
  const name = field.getName();
@@ -284,11 +129,6 @@ module.exports = class ResultSet {
284
129
  enumerable: false,
285
130
  },
286
131
 
287
- $$data: {
288
- value: data,
289
- enumerable: false,
290
- },
291
-
292
132
  $$isResultSetItem: {
293
133
  value: true,
294
134
  enumerable: false,
@@ -0,0 +1,210 @@
1
+ const { get } = require('lodash');
2
+ const DataService = require('./DataService');
3
+ const { map, ensureArray, keyPaths, mapPromise, toGUID, hashObject } = require('../service/app.service');
4
+
5
+ module.exports = class ResultSet {
6
+ constructor(query, data, adjustForPagination = true) {
7
+ const { resolver, model, sort, first, after, last, before } = query.toObject();
8
+ const fields = model.getFields().filter(f => f.getName() !== 'id');
9
+
10
+ const rs = map(data, (doc) => {
11
+ if (doc == null || typeof doc !== 'object') return doc;
12
+
13
+ //
14
+ const cache = new Map();
15
+
16
+ // Base definition all results have
17
+ const definition = {
18
+ id: {
19
+ get() { return doc.id || doc[model.idKey()]; },
20
+ set(id) { doc.id = id; }, // Embedded array of documents need to set id
21
+ enumerable: true,
22
+ },
23
+
24
+ $id: {
25
+ get() { return toGUID(model.getName(), this.id); },
26
+ enumerable: false,
27
+ },
28
+
29
+ $$cursor: {
30
+ get() {
31
+ const sortPaths = keyPaths(sort);
32
+ const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(this, path) }), {});
33
+ const sortJSON = JSON.stringify(sortValues);
34
+ return Buffer.from(sortJSON).toString('base64');
35
+ },
36
+ enumerable: false,
37
+ },
38
+
39
+ $$model: {
40
+ value: model,
41
+ enumerable: false,
42
+ },
43
+
44
+ $$data: {
45
+ value: data,
46
+ enumerable: false,
47
+ },
48
+
49
+ $$isResultSetItem: {
50
+ value: true,
51
+ enumerable: false,
52
+ },
53
+
54
+ $$save: {
55
+ get() { return input => resolver.match(model).id(this.id).save({ ...this, ...input }); },
56
+ enumerable: false,
57
+ },
58
+
59
+ $$remove: {
60
+ get() { return () => resolver.match(model).id(this.id).remove(); },
61
+ enumerable: false,
62
+ },
63
+
64
+ $$delete: {
65
+ get() { return () => resolver.match(model).id(this.id).delete(); },
66
+ enumerable: false,
67
+ },
68
+
69
+ toObject: {
70
+ get() {
71
+ return () => map(this, obj => Object.entries(obj).reduce((prev, [key, value]) => {
72
+ if (value === undefined) return prev;
73
+ prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
74
+ return prev;
75
+ }, {}));
76
+ },
77
+ enumerable: false,
78
+ configurable: true,
79
+ },
80
+ };
81
+
82
+ fields.forEach((field) => {
83
+ const key = field.getKey();
84
+ const name = field.getName();
85
+ const $name = `$${name}`;
86
+ const value = doc[key];
87
+
88
+ // Field attributes
89
+ definition[name] = {
90
+ get() {
91
+ if (cache.has(name)) return cache.get(name);
92
+ let $value = field.deserialize(query, value);
93
+ $value = $value != null && field.isEmbedded() ? new ResultSet(query.model(field.getModelRef()), $value, false) : $value;
94
+ cache.set(name, $value);
95
+ return $value;
96
+ },
97
+ set($value) {
98
+ cache.set(name, $value);
99
+ },
100
+ enumerable: true,
101
+ configurable: true, // Allows things like delete
102
+ };
103
+
104
+ // Hydrated field attributes
105
+ definition[`$${name}`] = {
106
+ get() {
107
+ return (args = {}) => {
108
+ // Ensure where clause
109
+ args.where = args.where || {};
110
+
111
+ // Cache
112
+ const cacheKey = `${$name}-${hashObject(args)}`;
113
+ if (cache.has(cacheKey)) return cache.get(cacheKey);
114
+
115
+ const promise = new Promise((resolve, reject) => {
116
+ (() => {
117
+ const $value = this[name];
118
+
119
+ if (field.isScalar() || field.isEmbedded()) return Promise.resolve($value);
120
+
121
+ const modelRef = field.getModelRef();
122
+
123
+ if (field.isArray()) {
124
+ if (field.isVirtual()) {
125
+ args.where[[field.getVirtualField()]] = this.id; // Is where[[field.getVirtualField()]] correct?
126
+ return resolver.match(modelRef).merge(args).many();
127
+ }
128
+
129
+ // Not a "required" query + strip out nulls
130
+ args.where.id = $value;
131
+ return resolver.match(modelRef).merge(args).many();
132
+ }
133
+
134
+ if (field.isVirtual()) {
135
+ args.where[[field.getVirtualField()]] = this.id;
136
+ return resolver.match(modelRef).merge(args).one();
137
+ }
138
+
139
+ return resolver.match(modelRef).id($value).one({ required: field.isRequired() });
140
+ })().then((results) => {
141
+ if (results == null) return field.resolve(query, results); // Allow field to determine
142
+ return mapPromise(results, result => field.resolve(query, result)).then(() => results); // Resolve the inside fields but still return "results"!!!!
143
+ }).then((resolved) => {
144
+ resolve(resolved);
145
+ }).catch((e) => {
146
+ reject(e);
147
+ });
148
+ });
149
+
150
+ cache.set(cacheKey, promise);
151
+ return promise;
152
+ };
153
+ },
154
+ enumerable: false,
155
+ };
156
+
157
+ // Field count (let's assume it's a Connection Type - meaning dont try with anything else)
158
+ definition[`$${name}:count`] = {
159
+ get() {
160
+ return (q = {}) => {
161
+ q.where = q.where || {};
162
+ if (field.isVirtual()) q.where[field.getVirtualField()] = this.id;
163
+ else q.where.id = this[name];
164
+ return resolver.match(field.getModelRef()).merge(q).count();
165
+ };
166
+ },
167
+ enumerable: false,
168
+ };
169
+ });
170
+
171
+ // Create and return ResultSetItem
172
+ return Object.defineProperties({}, definition);
173
+ });
174
+
175
+ let hasNextPage = false;
176
+ let hasPreviousPage = false;
177
+ if (adjustForPagination && rs.length) (({ hasPreviousPage, hasNextPage } = DataService.paginateResultSet(rs, first, after, last, before)));
178
+
179
+ return Object.defineProperties(rs, {
180
+ $$pageInfo: {
181
+ get() {
182
+ const edges = ensureArray(rs);
183
+
184
+ return {
185
+ startCursor: get(edges, '0.$$cursor', ''),
186
+ endCursor: get(edges, `${edges.length - 1}.$$cursor`, ''),
187
+ hasPreviousPage,
188
+ hasNextPage,
189
+ };
190
+ },
191
+ enumerable: false,
192
+ },
193
+ $$isResultSet: {
194
+ value: true,
195
+ enumerable: false,
196
+ },
197
+ toObject: {
198
+ get() {
199
+ return () => map(this, doc => Object.entries(doc).reduce((prev, [key, value]) => {
200
+ if (value === undefined) return prev;
201
+ prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
202
+ return prev;
203
+ }, {}));
204
+ },
205
+ enumerable: false,
206
+ configurable: true,
207
+ },
208
+ });
209
+ }
210
+ };
@@ -0,0 +1,186 @@
1
+ const { get } = require('lodash');
2
+ const DataService = require('./DataService');
3
+ const { map, ensureArray, keyPaths, mapPromise, toGUID, hashObject } = require('../service/app.service');
4
+
5
+ module.exports = class ResultSet {
6
+ constructor(query, data, adjustForPagination = true) {
7
+ const { resolver, model, sort, first, after, last, before } = query.toObject();
8
+ const fields = model.getFields().filter(f => f.getName() !== 'id');
9
+
10
+ const rs = map(data, (doc) => {
11
+ if (doc == null || typeof doc !== 'object') return doc;
12
+
13
+ const cache = new Map();
14
+
15
+ const validKeys = [];
16
+
17
+ const definition = {
18
+ get id() { return doc.id || doc[model.idKey()]; },
19
+ get $id() { return toGUID(model.getName(), this.id); },
20
+ get $$data() { return data; },
21
+ get $$model() { return model; },
22
+ get $$isResultSetItem() { return true; },
23
+ get $$save() { return input => resolver.match(model).id(this.id).save({ ...this, ...input }); },
24
+ get $$remove() { return () => resolver.match(model).id(this.id).remove(); },
25
+ get $$delete() { return () => resolver.match(model).id(this.id).delete(); },
26
+ get $$cursor() {
27
+ return () => {
28
+ const sortPaths = keyPaths(sort);
29
+ const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(this, path) }), {});
30
+ const sortJSON = JSON.stringify(sortValues);
31
+ return Buffer.from(sortJSON).toString('base64');
32
+ };
33
+ },
34
+ get toObject() {
35
+ return () => validKeys.reduce((prev, key) => Object.assign(prev, { [key]: this[key] }), {});
36
+ },
37
+ };
38
+
39
+ fields.forEach((field) => {
40
+ const key = field.getKey();
41
+ const name = field.getName();
42
+ const $name = `$${name}`;
43
+ const value = doc[key];
44
+ validKeys.push(name);
45
+
46
+ // Field attributes
47
+ Object.assign(definition, {
48
+ get [name]() {
49
+ let $value = field.deserialize(query, value);
50
+ $value = $value != null && field.isEmbedded() ? new ResultSet(query.model(field.getModelRef()), $value, false) : $value;
51
+ return $value;
52
+ },
53
+ });
54
+
55
+ // Hydrated field attributes
56
+ Object.assign(definition, {
57
+ get [$name]() {
58
+ return (args = {}) => {
59
+ // Ensure where clause
60
+ args.where = args.where || {};
61
+
62
+ return new Promise((resolve, reject) => {
63
+ (() => {
64
+ const $value = this[name];
65
+
66
+ if (field.isScalar() || field.isEmbedded()) return Promise.resolve($value);
67
+
68
+ const modelRef = field.getModelRef();
69
+
70
+ if (field.isArray()) {
71
+ if (field.isVirtual()) {
72
+ args.where[[field.getVirtualField()]] = this.id; // Is where[[field.getVirtualField()]] correct?
73
+ return resolver.match(modelRef).merge(args).many();
74
+ }
75
+
76
+ // Not a "required" query + strip out nulls
77
+ args.where.id = $value;
78
+ return resolver.match(modelRef).merge(args).many();
79
+ }
80
+
81
+ if (field.isVirtual()) {
82
+ args.where[[field.getVirtualField()]] = this.id;
83
+ return resolver.match(modelRef).merge(args).one();
84
+ }
85
+
86
+ return resolver.match(modelRef).id($value).one({ required: field.isRequired() });
87
+ })().then((results) => {
88
+ if (results == null) return field.resolve(query, results); // Allow field to determine
89
+ return mapPromise(results, result => field.resolve(query, result)).then(() => results); // Resolve the inside fields but still return "results"!!!!
90
+ }).then((resolved) => {
91
+ resolve(resolved);
92
+ }).catch((e) => {
93
+ reject(e);
94
+ });
95
+ });
96
+ };
97
+ },
98
+ });
99
+
100
+ // Field count (let's assume it's a Connection Type - meaning dont try with anything else)
101
+ Object.assign(definition, {
102
+ get [`${$name}:count`]() {
103
+ return (q = {}) => {
104
+ q.where = q.where || {};
105
+ if (field.isVirtual()) q.where[field.getVirtualField()] = this.id;
106
+ else q.where.id = this[name];
107
+ return resolver.match(field.getModelRef()).merge(q).count();
108
+ };
109
+ },
110
+ });
111
+ });
112
+
113
+ // Create and return ResultSetItem
114
+ const idk = new Proxy(definition, {
115
+ get(target, prop, rec) {
116
+ if (cache.has(prop)) return cache.get(prop);
117
+ const value = Reflect.get(target, prop, rec);
118
+ if (typeof value === 'function') return value.bind(target);
119
+ cache.set(prop, value);
120
+ return value;
121
+ },
122
+ set(target, prop, value) {
123
+ cache.set(prop, value);
124
+ return true;
125
+ },
126
+ ownKeys() {
127
+ return validKeys;
128
+ },
129
+ getOwnPropertyDescriptor(target, prop) {
130
+ if (validKeys.indexOf(prop) === -1) {
131
+ return {
132
+ writable: true,
133
+ enumerable: true,
134
+ configurable: true,
135
+ };
136
+ }
137
+
138
+ return {
139
+ writable: false,
140
+ enumerable: false,
141
+ configurable: false,
142
+ };
143
+ },
144
+ });
145
+
146
+ // console.log(idk);
147
+ // // console.log(idk.toObject());
148
+ return idk;
149
+ });
150
+
151
+ let hasNextPage = false;
152
+ let hasPreviousPage = false;
153
+ if (adjustForPagination && rs.length) (({ hasPreviousPage, hasNextPage } = DataService.paginateResultSet(rs, first, after, last, before)));
154
+
155
+ return Object.defineProperties(rs, {
156
+ $$pageInfo: {
157
+ get() {
158
+ const edges = ensureArray(rs);
159
+
160
+ return {
161
+ startCursor: get(edges, '0.$$cursor', ''),
162
+ endCursor: get(edges, `${edges.length - 1}.$$cursor`, ''),
163
+ hasPreviousPage,
164
+ hasNextPage,
165
+ };
166
+ },
167
+ enumerable: false,
168
+ },
169
+ $$isResultSet: {
170
+ value: true,
171
+ enumerable: false,
172
+ },
173
+ toObject: {
174
+ get() {
175
+ return () => map(this, doc => Object.entries(doc).reduce((prev, [key, value]) => {
176
+ if (value === undefined) return prev;
177
+ prev[key] = get(value, '$$isResultSet') ? value.toObject() : value;
178
+ return prev;
179
+ }, {}));
180
+ },
181
+ enumerable: false,
182
+ configurable: true,
183
+ },
184
+ });
185
+ }
186
+ };
@@ -265,9 +265,9 @@ module.exports = class Node {
265
265
  switch (this.nodeType) {
266
266
  case 'model': {
267
267
  if (!this.isMarkedModel()) return '';
268
- return nvl(uvl(this.getDirectiveArg('model', 'gqlScope'), 'crud'), '');
268
+ return nvl(uvl(this.getDirectiveArg('model', 'gqlScope'), 'cruds'), '');
269
269
  }
270
- case 'field': return nvl(uvl(this.getDirectiveArg('field', 'gqlScope'), 'crud'), '');
270
+ case 'field': return nvl(uvl(this.getDirectiveArg('field', 'gqlScope'), 'cruds'), '');
271
271
  default: return '';
272
272
  }
273
273
  }
@@ -3,7 +3,7 @@ const { Kind } = require('graphql');
3
3
  const ServerResolver = require('../../core/ServerResolver');
4
4
  const { ucFirst, fromGUID } = require('../../service/app.service');
5
5
  const { findGQLModels } = require('../../service/schema.service');
6
- const { makeCreateAPI, makeReadAPI, makeUpdateAPI, makeDeleteAPI, makeInputSplice, makeQueryResolver, makeMutationResolver } = require('../../service/decorator.service');
6
+ const { makeCreateAPI, makeReadAPI, makeUpdateAPI, makeDeleteAPI, makeSubscriptionAPI, makeInputSplice, makeQueryResolver, makeMutationResolver } = require('../../service/decorator.service');
7
7
 
8
8
  const interfaceKinds = [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
9
9
 
@@ -20,6 +20,7 @@ const getGQLWhereFields = (model) => {
20
20
  module.exports = (schema) => {
21
21
  const resolver = new ServerResolver();
22
22
  const allModels = schema.getModels();
23
+ const entityModels = schema.getEntityModels();
23
24
  const markedModels = schema.getMarkedModels();
24
25
  const createModels = findGQLModels('c', markedModels, allModels);
25
26
  const readModels = findGQLModels('r', markedModels, allModels);
@@ -55,17 +56,56 @@ module.exports = (schema) => {
55
56
  extend ${interfaceKinds.indexOf(model.getKind()) > -1 ? 'interface' : 'type'} ${model.getName()} {
56
57
  ${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}${field.getExtendArgs()}: ${field.getPayloadType()}`)}
57
58
  }
59
+
58
60
  type ${model.getName()}Connection {
59
61
  pageInfo: PageInfo!
60
62
  edges: [${model.getName()}Edge]
61
63
  count: Int!
62
64
  }
65
+
63
66
  type ${model.getName()}Edge {
64
67
  node: ${model.getName()}
65
68
  cursor: String!
66
69
  }
67
70
  `),
68
71
 
72
+ ...entityModels.filter(model => model.hasGQLScope('s')).map(model => `
73
+ input ${model.getName()}SubscriptionInputFilter {
74
+ when: [SubscriptionWhenEnum!]! = [preEvent, postEvent]
75
+ where: ${model.getName()}SubscriptionInputWhere! = {}
76
+ }
77
+
78
+ input ${model.getName()}SubscriptionInputWhere {
79
+ ${getGQLWhereFields(model).map(field => `${field.getName()}: ${field.getModelRef() && !field.isFKReference() ? `${ucFirst(field.getDataRef())}InputWhere` : 'AutoGraphMixed'}`)}
80
+ }
81
+
82
+ type ${model.getName()}SubscriptionPayload {
83
+ event: ${model.getName()}SubscriptionPayloadEvent
84
+ query: ${model.getName()}SubscriptionQuery
85
+ }
86
+
87
+ type ${model.getName()}SubscriptionPayloadEvent {
88
+ crud: SubscriptionCrudEnum!
89
+ data: ${model.getName()}SubscriptionPayloadEventData!
90
+ }
91
+
92
+ type ${model.getName()}SubscriptionPayloadEventData {
93
+ ${getGQLWhereFields(model).map(field => `${field.getName()}: ${field.isFKReference() ? 'ID' : field.getGQLType()}`)}
94
+ }
95
+
96
+ interface ${model.getName()}SubscriptionQuery {
97
+ ${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}: ${field.getPayloadType()}`)}
98
+ }
99
+
100
+ type ${model.getName()}Create implements ${model.getName()}SubscriptionQuery {
101
+ ${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}: ${field.getPayloadType()}`)}
102
+ }
103
+
104
+ type ${model.getName()}Update implements ${model.getName()}SubscriptionQuery {
105
+ ${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}: ${field.getPayloadType()}`)}
106
+ }
107
+ `),
108
+
69
109
  ...spliceModels.map(model => `
70
110
  #input ${model.getName()}InputSplice {
71
111
  # with: ${model}InputWhere
@@ -82,14 +122,21 @@ module.exports = (schema) => {
82
122
 
83
123
  `type Query {
84
124
  node(id: ID!): Node
85
- ${schema.getEntityModels().map(model => makeReadAPI(model.getName(), model))}
125
+ ${entityModels.map(model => makeReadAPI(model.getName(), model))}
126
+ ${entityModels.map(model => makeReadAPI(`${model.getName()}Create`, model))}
127
+ ${entityModels.map(model => makeReadAPI(`${model.getName()}Update`, model))}
86
128
  }`,
87
129
 
88
130
  `type Mutation {
89
131
  _noop: String
90
- ${schema.getEntityModels().map(model => makeCreateAPI(model.getName(), model))}
91
- ${schema.getEntityModels().map(model => makeUpdateAPI(model.getName(), model))}
92
- ${schema.getEntityModels().map(model => makeDeleteAPI(model.getName(), model))}
132
+ ${entityModels.map(model => makeCreateAPI(model.getName(), model))}
133
+ ${entityModels.map(model => makeUpdateAPI(model.getName(), model))}
134
+ ${entityModels.map(model => makeDeleteAPI(model.getName(), model))}
135
+ }`,
136
+
137
+ `type Subscription {
138
+ _noop: String
139
+ ${entityModels.map(model => makeSubscriptionAPI(model.getName(), model))}
93
140
  }`,
94
141
  ]),
95
142
  resolvers: readModels.reduce((prev, model) => {
@@ -120,6 +167,15 @@ module.exports = (schema) => {
120
167
  });
121
168
  }, {});
122
169
 
170
+ if (model.isEntity() && model.hasGQLScope('s')) {
171
+ prev[`${model.getName()}SubscriptionQuery`] = {
172
+ __resolveType: root => root.__typename, // eslint-disable-line no-underscore-dangle
173
+ ...fieldResolvers,
174
+ };
175
+ prev[`${model.getName()}Create`] = fieldResolvers;
176
+ prev[`${model.getName()}Update`] = fieldResolvers;
177
+ }
178
+
123
179
  return Object.assign(prev, {
124
180
  [modelName]: fieldResolvers,
125
181
  [`${modelName}Connection`]: {
@@ -130,10 +186,10 @@ module.exports = (schema) => {
130
186
  });
131
187
  }, {
132
188
  Node: {
133
- __resolveType: (root, args, context, info) => root.__typename || fromGUID(root.$id)[0],
189
+ __resolveType: (root, args, context, info) => root.__typename || fromGUID(root.$id)[0], // eslint-disable-line no-underscore-dangle
134
190
  },
135
191
 
136
- Query: schema.getEntityModels().reduce((prev, model) => {
192
+ Query: entityModels.reduce((prev, model) => {
137
193
  return Object.assign(prev, makeQueryResolver(model.getName(), model, resolver));
138
194
  }, {
139
195
  node: (root, args, context, info) => {
@@ -144,7 +200,7 @@ module.exports = (schema) => {
144
200
  },
145
201
  }),
146
202
 
147
- Mutation: schema.getEntityModels().reduce((prev, model) => {
203
+ Mutation: entityModels.reduce((prev, model) => {
148
204
  return Object.assign(prev, makeMutationResolver(model.getName(), model, resolver));
149
205
  }, {}),
150
206
  }),
@@ -9,9 +9,13 @@ module.exports = (schema) => {
9
9
  const createdAt = model.getDirectiveArg('model', 'createdAt', 'createdAt');
10
10
  const updatedAt = model.getDirectiveArg('model', 'updatedAt', 'updatedAt');
11
11
 
12
- if (model.getKind() === 'ObjectTypeDefinition') {
12
+ if (model.getKind() === 'ObjectTypeDefinition' && (id || createdAt || updatedAt)) {
13
+ const interfaces = [];
14
+ if (id) interfaces.push('Node');
15
+ const interfacesGQL = interfaces.length ? ' implements'.concat(' ', interfaces.join(' & ')) : '';
16
+
13
17
  return `
14
- extend type ${modelName} ${id ? 'implements Node' : ''} {
18
+ extend type ${modelName}${interfacesGQL} {
15
19
  ${id ? `id: ID! @field(key: "${id}", gqlScope: r)` : ''}
16
20
  ${createdAt ? `createdAt: AutoGraphDateTime @field(key: "${createdAt}", gqlScope: r)` : ''}
17
21
  ${updatedAt ? `updatedAt: AutoGraphDateTime @field(key: "${updatedAt}", gqlScope: r)` : ''}
@@ -23,6 +27,8 @@ module.exports = (schema) => {
23
27
  }).concat(`
24
28
  interface Node { id: ID! }
25
29
  enum SortOrderEnum { asc desc }
30
+ enum SubscriptionCrudEnum { create update delete } # Not going to support "read"
31
+ enum SubscriptionWhenEnum { preEvent postEvent }
26
32
  `),
27
33
  });
28
34
  };
@@ -343,6 +343,19 @@ exports.makeDeleteAPI = (name, model, parent) => {
343
343
  return gql;
344
344
  };
345
345
 
346
+ exports.makeSubscriptionAPI = (name, model, parent) => {
347
+ let gql = '';
348
+
349
+ if (model.hasGQLScope('s')) {
350
+ gql += `${name} (
351
+ on: [SubscriptionCrudEnum!]! = [create, update, delete]
352
+ filter: ${name}SubscriptionInputFilter
353
+ ): ${name}SubscriptionPayload!`;
354
+ }
355
+
356
+ return gql;
357
+ };
358
+
346
359
  // Resolvers
347
360
  exports.makeQueryResolver = (name, model, resolver, embeds = []) => {
348
361
  const obj = {};
@@ -13,19 +13,20 @@ const systemEvent = new EventEmitter().setMaxListeners(100).on('system', async (
13
13
 
14
14
  //
15
15
  exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
16
- let event;
17
- let middleware;
16
+ let event = mixed;
17
+ let middleware = () => Promise.resolve();
18
18
  const type = ucFirst(name);
19
19
 
20
- if (name !== 'Setup') {
20
+ if (name !== 'Setup' && name !== 'Response') {
21
21
  const { method, query } = mixed;
22
- const { resolver, model, meta, doc, id, input, sort, merged, native, root } = query.toObject();
22
+ const { resolver, model, meta, doc, id, input, sort, merged, native, root, crud } = query.toObject();
23
23
 
24
24
  event = {
25
25
  context: resolver.getContext(),
26
26
  key: `${method}${model}`,
27
27
  resolver,
28
28
  method,
29
+ crud,
29
30
  model,
30
31
  meta,
31
32
  id,
@@ -48,17 +49,18 @@ exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
48
49
 
49
50
  resolve();
50
51
  });
51
- } else {
52
- middleware = () => Promise.resolve();
53
- event = mixed;
54
52
  }
55
53
 
56
54
  return systemEvent.emit('system', { type: `pre${type}`, data: event }).then((result) => {
57
55
  if (result !== undefined) return result; // Allowing middleware to dictate result
58
56
  return middleware().then(thunk);
59
57
  }).then((result) => {
60
- event.doc = result; // You do actually need this...
61
- return systemEvent.emit('system', { type: `post${type}`, data: event }).then(finalResult => finalResult || result);
58
+ event.result = result;
59
+ return systemEvent.emit('system', { type: `post${type}`, data: event }).then((postResult = result) => postResult);
60
+ }).then((result) => {
61
+ if (name === 'Response') return result;
62
+ event.result = result;
63
+ return exports.createSystemEvent('Response', event, (finalResult = result) => finalResult);
62
64
  });
63
65
  };
64
66