@platformatic/sql-graphql 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/index.js +19 -2
  2. package/lib/entity-to-type.js +1 -0
  3. package/lib/relationship.js +2 -2
  4. package/lib/subscriptions.js +89 -0
  5. package/package.json +6 -4
  6. package/test/helper.js +9 -0
  7. package/test/relations.test.js +145 -0
  8. package/test/subscription.test.js +336 -0
  9. package/.nyc_output/0ebb7c74-e9d0-4427-873f-707c97b71e3a.json +0 -1
  10. package/.nyc_output/1705f757-0633-4d57-bdd8-74da5e87c7f2.json +0 -1
  11. package/.nyc_output/18dca461-a16b-4426-8125-755b1714aa40.json +0 -1
  12. package/.nyc_output/1f6f25f8-9751-4755-94d9-99047f9e7f95.json +0 -1
  13. package/.nyc_output/25c88717-db5b-4879-a56e-cf27a8748dc0.json +0 -1
  14. package/.nyc_output/354b1308-2042-4a6c-9cf3-07be876efbb3.json +0 -1
  15. package/.nyc_output/415ccadc-7e96-410e-ae66-6ca11ce92b8c.json +0 -1
  16. package/.nyc_output/5f643acb-71a2-42a1-8e2c-3c830382e6c3.json +0 -1
  17. package/.nyc_output/7cc4fa46-505d-4868-802f-b22374f1aa0f.json +0 -1
  18. package/.nyc_output/90d66f5a-41c4-4c50-95f5-078e68e2ee4d.json +0 -1
  19. package/.nyc_output/a5752887-ebcd-4670-bc8e-8d75a3c1ae51.json +0 -1
  20. package/.nyc_output/a9d0b301-bd1a-4076-bdd5-e311adb96683.json +0 -1
  21. package/.nyc_output/cb3b5287-a592-492a-bf10-ecb456437154.json +0 -1
  22. package/.nyc_output/d13b13a2-ef5a-4e88-949e-77c62f98abd2.json +0 -1
  23. package/.nyc_output/ea305ecd-1796-4c6b-a9e4-503ca63f2d42.json +0 -1
  24. package/.nyc_output/fc62d83a-4179-44bf-9bbb-b306531568e9.json +0 -1
  25. package/.nyc_output/processinfo/0ebb7c74-e9d0-4427-873f-707c97b71e3a.json +0 -1
  26. package/.nyc_output/processinfo/1705f757-0633-4d57-bdd8-74da5e87c7f2.json +0 -1
  27. package/.nyc_output/processinfo/18dca461-a16b-4426-8125-755b1714aa40.json +0 -1
  28. package/.nyc_output/processinfo/1f6f25f8-9751-4755-94d9-99047f9e7f95.json +0 -1
  29. package/.nyc_output/processinfo/25c88717-db5b-4879-a56e-cf27a8748dc0.json +0 -1
  30. package/.nyc_output/processinfo/354b1308-2042-4a6c-9cf3-07be876efbb3.json +0 -1
  31. package/.nyc_output/processinfo/415ccadc-7e96-410e-ae66-6ca11ce92b8c.json +0 -1
  32. package/.nyc_output/processinfo/5f643acb-71a2-42a1-8e2c-3c830382e6c3.json +0 -1
  33. package/.nyc_output/processinfo/7cc4fa46-505d-4868-802f-b22374f1aa0f.json +0 -1
  34. package/.nyc_output/processinfo/90d66f5a-41c4-4c50-95f5-078e68e2ee4d.json +0 -1
  35. package/.nyc_output/processinfo/a5752887-ebcd-4670-bc8e-8d75a3c1ae51.json +0 -1
  36. package/.nyc_output/processinfo/a9d0b301-bd1a-4076-bdd5-e311adb96683.json +0 -1
  37. package/.nyc_output/processinfo/cb3b5287-a592-492a-bf10-ecb456437154.json +0 -1
  38. package/.nyc_output/processinfo/d13b13a2-ef5a-4e88-949e-77c62f98abd2.json +0 -1
  39. package/.nyc_output/processinfo/ea305ecd-1796-4c6b-a9e4-503ca63f2d42.json +0 -1
  40. package/.nyc_output/processinfo/fc62d83a-4179-44bf-9bbb-b306531568e9.json +0 -1
  41. package/.nyc_output/processinfo/index.json +0 -1
package/index.js CHANGED
@@ -5,6 +5,7 @@ const constructGraph = require('./lib/entity-to-type')
5
5
  const mercurius = require('mercurius')
6
6
  const graphql = require('graphql')
7
7
  const establishRelations = require('./lib/relationship')
8
+ const setupSubscriptions = require('./lib/subscriptions')
8
9
  const scalars = require('graphql-scalars')
9
10
 
10
11
  async function mapperToGraphql (app, opts) {
@@ -24,6 +25,8 @@ async function mapperToGraphql (app, opts) {
24
25
  federationReplacements,
25
26
  federationMetadata: opts.federationMetadata
26
27
  }
28
+ const metaMap = new Map()
29
+
27
30
  if (Object.keys(mapper.entities).length === 0) {
28
31
  // no schema
29
32
  queryTopFields.hello = { type: graphql.GraphQLString }
@@ -31,7 +34,6 @@ async function mapperToGraphql (app, opts) {
31
34
  hello: () => 'Hello Platformatic!'
32
35
  }
33
36
  } else {
34
- const metaMap = new Map()
35
37
  for (const entity of Object.values(mapper.entities)) {
36
38
  relations.push(...entity.relations)
37
39
  const meta = constructGraph(app, entity, graphOpts)
@@ -85,7 +87,21 @@ async function mapperToGraphql (app, opts) {
85
87
  delete resolvers.Mutation
86
88
  }
87
89
 
88
- let sdl = graphql.printSchema(new graphql.GraphQLSchema({ query, mutation }))
90
+ let subscription
91
+ if (app.platformatic.mq) {
92
+ const entitiesList = Object.keys(app.platformatic.entities)
93
+ const ignoreList = opts.subscriptionIgnore || []
94
+
95
+ if (entitiesList.length - ignoreList.length > 0) {
96
+ opts.subscription = {
97
+ emitter: app.platformatic.mq
98
+ }
99
+ // TODO support ignoring some of those
100
+ subscription = setupSubscriptions(app, metaMap, resolvers, ignoreList)
101
+ }
102
+ }
103
+
104
+ let sdl = graphql.printSchema(new graphql.GraphQLSchema({ query, mutation, subscription }))
89
105
 
90
106
  if (opts.federationMetadata) {
91
107
  for (const replacement of federationReplacements) {
@@ -108,6 +124,7 @@ async function mapperToGraphql (app, opts) {
108
124
  }
109
125
 
110
126
  opts.graphiql = opts.graphiql !== false
127
+
111
128
  await app.register(mercurius, {
112
129
  ...opts,
113
130
  schema: sdl,
@@ -229,6 +229,7 @@ function constructGraph (app, entity, opts) {
229
229
  type,
230
230
  entity,
231
231
  loadMany,
232
+ getFields,
232
233
  fields
233
234
  }
234
235
 
@@ -16,6 +16,7 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
16
16
  for (const relation of relations) {
17
17
  assert(relation.table_name, 'table_name is required')
18
18
  assert(relation.foreign_table_name, 'foreign_table_name is required')
19
+ assert(relation.column_name.toLowerCase().endsWith('id'), 'The column with id reference must be ended with `id` postfix')
19
20
 
20
21
  const current = tablesTypeMap[relation.table_name]
21
22
  const foreign = tablesTypeMap[relation.foreign_table_name]
@@ -23,7 +24,6 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
23
24
 
24
25
  // current to foreign
25
26
  {
26
- assert(relation.column_name.toLowerCase().endsWith('id'), 'The column with id reference must be ended with `id` postfix')
27
27
  const lowered = lowerCaseFirst(camelcase(cutOutIdEnding(relation.column_name)))
28
28
  if (!relationships[current.type] || relationships[current.type][lowered] !== false) {
29
29
  current.fields[lowered] = { type: foreign.type }
@@ -79,7 +79,7 @@ function lowerCaseFirst (str) {
79
79
 
80
80
  function cutOutIdEnding (str) {
81
81
  str = str.toString()
82
- return str.slice(0, str.toLowerCase().indexOf('id'))
82
+ return str.slice(0, str.toLowerCase().lastIndexOf('id'))
83
83
  }
84
84
 
85
85
  function findPrimaryColumnName (camelCasedFields) {
@@ -0,0 +1,89 @@
1
+ 'use strict'
2
+
3
+ const graphql = require('graphql')
4
+
5
+ function setupSubscriptions (app, metaMap, resolvers, ignores) {
6
+ const fields = {}
7
+ resolvers.Subscription = {}
8
+ for (const [field, meta] of metaMap) {
9
+ if (ignores.indexOf(field.singularName) >= 0) {
10
+ continue
11
+ }
12
+ const { type } = meta
13
+ const saved = `${field.singularName}Saved`
14
+ fields[saved] = {
15
+ type
16
+ }
17
+ resolvers.Subscription[saved] = {
18
+ subscribe: async function * (_, query, ctx, info) {
19
+ const log = ctx.reply.request.log
20
+ const { pubsub } = ctx
21
+ const entity = app.platformatic.entities[field.singularName]
22
+ const topic = await entity.getSubscriptionTopic({ action: 'save', ctx })
23
+ log.trace({ topic }, 'subscribed')
24
+ const res = await pubsub.subscribe(topic)
25
+ const primaryKey = entity.primaryKey
26
+ const fields = meta.getFields([{ info }])
27
+
28
+ for await (const msg of res) {
29
+ // TODO optimize this by not calling find() if the subscriber is only asking for the id.
30
+ try {
31
+ log.trace({ msg, entity: field.singularName }, 'graphql subscription augmenting data')
32
+ // The alternative to augmenting the data is installing a resolver for every type
33
+ // of a given entity that loads up the type via a dataloader.
34
+ const found = await entity.find({ where: { [primaryKey]: { eq: msg[primaryKey] } }, fields, ctx })
35
+ // The following could happen in case of a race condition
36
+ // testing it would be very hard, so we skip it for now
37
+ /* istanbul ignore next */
38
+ if (found.length === 0) {
39
+ log.warn({ ...msg.payload, entity: field.singularName }, 'graphql subscription could not find element')
40
+ continue
41
+ }
42
+ const toYield = { [saved]: found[0] }
43
+ log.trace({ yield: toYield, entity: field.singularName }, 'graphql subscription augmented data')
44
+ yield toYield
45
+ } catch (err) {
46
+ /* istanbul ignore next */
47
+ log.warn({ err, entity: field.singularName }, 'graphql subscription error')
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ const deleted = `${field.singularName}Deleted`
54
+ fields[deleted] = {
55
+ type: new graphql.GraphQLObjectType({
56
+ name: `${field.name}Deleted`,
57
+ fields: {
58
+ [field.primaryKey]: {
59
+ type: meta.fields[field.primaryKey].type
60
+ }
61
+ }
62
+ })
63
+ }
64
+ resolvers.Subscription[deleted] = {
65
+ subscribe: async (_, query, ctx, info) => {
66
+ const { pubsub } = ctx
67
+ const entity = app.platformatic.entities[field.singularName]
68
+ const topic = await entity.getSubscriptionTopic({ action: 'delete', ctx })
69
+ const res = await pubsub.subscribe(topic)
70
+ ctx.reply.request.log.trace({ topic }, 'subscribed')
71
+ return wrap(res, deleted)
72
+ }
73
+ }
74
+ }
75
+ const subscription = new graphql.GraphQLObjectType({
76
+ name: 'Subscription',
77
+ fields
78
+ })
79
+
80
+ return subscription
81
+ }
82
+
83
+ async function * wrap (iterator, key) {
84
+ for await (const msg of iterator) {
85
+ yield { [key]: msg }
86
+ }
87
+ }
88
+
89
+ module.exports = setupSubscriptions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-graphql",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Map SQL dbs to GraphQL",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -14,12 +14,14 @@
14
14
  },
15
15
  "homepage": "https://github.com/plaformatic/platformatic#readme",
16
16
  "devDependencies": {
17
- "@platformatic/sql-mapper": "0.4.0",
17
+ "@platformatic/sql-events": "0.5.1",
18
+ "@platformatic/sql-mapper": "0.5.1",
18
19
  "fastify": "^4.6.0",
19
20
  "snazzy": "^9.0.0",
20
21
  "standard": "^17.0.0",
21
22
  "tap": "^16.0.0",
22
- "tsd": "^0.24.0"
23
+ "tsd": "^0.24.0",
24
+ "ws": "^8.9.0"
23
25
  },
24
26
  "dependencies": {
25
27
  "camelcase": "^6.0.0",
@@ -28,7 +30,7 @@
28
30
  "graphql-scalars": "^1.13.1",
29
31
  "graphql-type-json": "^0.3.2",
30
32
  "inflected": "^2.1.0",
31
- "mercurius": "^11.0.0"
33
+ "mercurius": "^11.2.0"
32
34
  },
33
35
  "tsd": {
34
36
  "directory": "test/types"
package/test/helper.js CHANGED
@@ -77,4 +77,13 @@ module.exports.clear = async function (db, sql) {
77
77
  await db.query(sql`DROP TABLE authors`)
78
78
  } catch (err) {
79
79
  }
80
+
81
+ try {
82
+ await db.query(sql`DROP TABLE organizations`)
83
+ } catch (err) {
84
+ }
85
+ try {
86
+ await db.query(sql`DROP TABLE individuals`)
87
+ } catch (err) {
88
+ }
80
89
  }
@@ -239,3 +239,148 @@ test('should handle multi references', async ({ pass, teardown, same, equal }) =
239
239
  }, 'query authors response')
240
240
  }
241
241
  })
242
+
243
+ test('cut out id exactly from ending when forming a name of relation', async ({ pass, teardown, same, equal }) => {
244
+ const app = fastify()
245
+ app.register(sqlMapper, {
246
+ ...connInfo,
247
+ async onDatabaseLoad (db, sql) {
248
+ pass('onDatabaseLoad called')
249
+
250
+ await clear(db, sql)
251
+
252
+ if (isMysql) {
253
+ await db.query(sql`
254
+ CREATE TABLE individuals (
255
+ id SERIAL PRIMARY KEY,
256
+ name VARCHAR(255)
257
+ );
258
+ CREATE TABLE organizations (
259
+ id SERIAL PRIMARY KEY,
260
+ name VARCHAR(42),
261
+ individual_id BIGINT UNSIGNED,
262
+ FOREIGN KEY (individual_id) REFERENCES individuals(id) ON DELETE CASCADE
263
+ );
264
+ `)
265
+ } else if (isSQLite) {
266
+ await db.query(sql`
267
+ CREATE TABLE individuals (
268
+ id INTEGER PRIMARY KEY,
269
+ name VARCHAR(255)
270
+ );
271
+ `)
272
+ await db.query(sql`
273
+ CREATE TABLE organizations (
274
+ id INTEGER PRIMARY KEY,
275
+ name VARCHAR(42),
276
+ individual_id BIGINT UNSIGNED,
277
+ FOREIGN KEY (individual_id) REFERENCES individuals(id) ON DELETE CASCADE
278
+ );
279
+ `)
280
+ } else {
281
+ await db.query(sql`
282
+ CREATE TABLE individuals (
283
+ id SERIAL PRIMARY KEY,
284
+ name VARCHAR(42)
285
+ );
286
+ CREATE TABLE organizations (
287
+ id SERIAL PRIMARY KEY,
288
+ name VARCHAR(42),
289
+ individual_id INTEGER REFERENCES individuals(id)
290
+ );
291
+ `)
292
+ }
293
+ }
294
+ })
295
+ app.register(sqlGraphQL)
296
+ teardown(app.close.bind(app))
297
+
298
+ await app.ready()
299
+
300
+ const individuals = [{
301
+ id: 1,
302
+ name: 'Mark'
303
+ }]
304
+
305
+ const organization = [{
306
+ id: 1,
307
+ name: 'Platformatic',
308
+ individualId: 1
309
+ }]
310
+
311
+ {
312
+ const res = await app.inject({
313
+ method: 'POST',
314
+ url: '/graphql',
315
+ body: {
316
+ query: `
317
+ mutation batch($inputs : [IndividualInput]!) {
318
+ insertIndividuals(inputs: $inputs) {
319
+ id
320
+ name
321
+ }
322
+ }
323
+ `,
324
+ variables: {
325
+ inputs: individuals
326
+ }
327
+ }
328
+ })
329
+ equal(res.statusCode, 200, 'individuals status code')
330
+ }
331
+
332
+ {
333
+ const res = await app.inject({
334
+ method: 'POST',
335
+ url: '/graphql',
336
+ body: {
337
+ query: `
338
+ mutation batch($inputs : [OrganizationInput]!) {
339
+ insertOrganizations(inputs: $inputs) {
340
+ id
341
+ name
342
+ }
343
+ }
344
+ `,
345
+ variables: {
346
+ inputs: organization
347
+ }
348
+ }
349
+ })
350
+ equal(res.statusCode, 200, 'organization status code')
351
+ }
352
+
353
+ {
354
+ const res = await app.inject({
355
+ method: 'POST',
356
+ url: '/graphql',
357
+ body: {
358
+ query: `
359
+ query {
360
+ organizations {
361
+ id
362
+ name
363
+ individual {
364
+ id
365
+ name
366
+ }
367
+ }
368
+ }
369
+ `
370
+ }
371
+ })
372
+ equal(res.statusCode, 200, 'query organization')
373
+ same(res.json(), {
374
+ data: {
375
+ organizations: [{
376
+ id: 1,
377
+ name: 'Platformatic',
378
+ individual: {
379
+ id: 1,
380
+ name: 'Mark'
381
+ }
382
+ }]
383
+ }
384
+ }, 'query organization response')
385
+ }
386
+ })
@@ -0,0 +1,336 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const Fastify = require('fastify')
5
+ const WebSocket = require('ws')
6
+ const { once } = require('events')
7
+ const sqlGraphQL = require('..')
8
+ const sqlMapper = require('@platformatic/sql-mapper')
9
+ const sqlEvents = require('@platformatic/sql-events')
10
+ const { clear, connInfo, isSQLite } = require('./helper')
11
+ const stream = require('stream')
12
+ const graphql = require('graphql')
13
+
14
+ async function createBasicPages (db, sql) {
15
+ if (isSQLite) {
16
+ await db.query(sql`CREATE TABLE pages (
17
+ id INTEGER PRIMARY KEY,
18
+ title VARCHAR(42)
19
+ );`)
20
+ } else {
21
+ await db.query(sql`CREATE TABLE pages (
22
+ id SERIAL PRIMARY KEY,
23
+ title VARCHAR(42)
24
+ );`)
25
+ }
26
+ }
27
+
28
+ function createWebSocketClient (t, app) {
29
+ const ws = new WebSocket('ws://localhost:' + (app.server.address()).port + '/graphql', 'graphql-ws')
30
+ const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8', objectMode: true })
31
+ t.teardown(client.destroy.bind(client))
32
+ client.setEncoding('utf8')
33
+ return { client, ws }
34
+ }
35
+
36
+ test('subscription - crud', { only: true }, async t => {
37
+ const app = Fastify()
38
+ t.teardown(() => app.close())
39
+
40
+ app.register(sqlMapper, {
41
+ ...connInfo,
42
+ async onDatabaseLoad (db, sql) {
43
+ await clear(db, sql)
44
+ await createBasicPages(db, sql)
45
+ }
46
+ })
47
+ app.register(sqlEvents)
48
+ app.register(sqlGraphQL)
49
+
50
+ await app.listen({ port: 0 })
51
+
52
+ const { client } = createWebSocketClient(t, app)
53
+
54
+ client.write(JSON.stringify({
55
+ type: 'connection_init'
56
+ }))
57
+
58
+ {
59
+ const query = `subscription {
60
+ pageSaved {
61
+ id
62
+ title
63
+ }
64
+ }`
65
+ client.write(JSON.stringify({
66
+ id: 1,
67
+ type: 'start',
68
+ payload: {
69
+ query
70
+ }
71
+ }))
72
+ }
73
+
74
+ {
75
+ const query = `subscription {
76
+ pageDeleted {
77
+ id
78
+ }
79
+ }`
80
+ client.write(JSON.stringify({
81
+ id: 1,
82
+ type: 'start',
83
+ payload: {
84
+ query
85
+ }
86
+ }))
87
+ }
88
+
89
+ {
90
+ const [chunk] = await once(client, 'data')
91
+ const data = JSON.parse(chunk)
92
+ t.equal(data.type, 'connection_ack')
93
+ }
94
+
95
+ t.comment('sending mutation')
96
+
97
+ await app.inject({
98
+ method: 'POST',
99
+ url: '/graphql',
100
+ body: {
101
+ query: `
102
+ mutation {
103
+ savePage(input: { title: "Hello World" }) {
104
+ id
105
+ }
106
+ }
107
+ `
108
+ }
109
+ })
110
+
111
+ t.comment('mutation sent')
112
+
113
+ {
114
+ const [chunk] = await once(client, 'data')
115
+ const data = JSON.parse(chunk)
116
+ t.same(data, {
117
+ id: 1,
118
+ type: 'data',
119
+ payload: {
120
+ data: {
121
+ pageSaved: {
122
+ id: '1',
123
+ title: 'Hello World'
124
+ }
125
+ }
126
+ }
127
+ })
128
+ }
129
+
130
+ t.comment('updating entity')
131
+
132
+ await app.inject({
133
+ method: 'POST',
134
+ url: '/graphql',
135
+ body: {
136
+ query: `
137
+ mutation {
138
+ savePage(input: { id: 1, title: "Harry Potter" }) {
139
+ id
140
+ }
141
+ }
142
+ `
143
+ }
144
+ })
145
+
146
+ t.comment('entity updated')
147
+
148
+ {
149
+ const [chunk] = await once(client, 'data')
150
+ const data = JSON.parse(chunk)
151
+ t.same(data, {
152
+ id: 1,
153
+ type: 'data',
154
+ payload: {
155
+ data: {
156
+ pageSaved: {
157
+ id: '1',
158
+ title: 'Harry Potter'
159
+ }
160
+ }
161
+ }
162
+ })
163
+ }
164
+
165
+ t.comment('deleting entity')
166
+
167
+ await app.inject({
168
+ method: 'POST',
169
+ url: '/graphql',
170
+ body: {
171
+ query: `
172
+ mutation {
173
+ deletePages(where: { id: { eq: 1 } }) {
174
+ id
175
+ }
176
+ }
177
+ `
178
+ }
179
+ })
180
+
181
+ t.comment('entity deleted')
182
+
183
+ {
184
+ const [chunk] = await once(client, 'data')
185
+ const data = JSON.parse(chunk)
186
+ t.same(data, {
187
+ id: '1',
188
+ type: 'data',
189
+ payload: {
190
+ data: {
191
+ pageDeleted: {
192
+ id: '1'
193
+ }
194
+ }
195
+ }
196
+ })
197
+ }
198
+
199
+ t.comment('sending mutation')
200
+
201
+ {
202
+ const [
203
+ received,
204
+ stored
205
+ ] = await Promise.all(([
206
+ (async function () {
207
+ const res = await app.inject({
208
+ method: 'POST',
209
+ url: '/graphql',
210
+ body: {
211
+ query: `
212
+ mutation batch($inputs : [PageInput]!) {
213
+ insertPages (inputs: $inputs) {
214
+ id
215
+ title
216
+ }
217
+ }
218
+ `,
219
+ variables: {
220
+ inputs: [
221
+ { title: 'Page 1' },
222
+ { title: 'Page 2' },
223
+ { title: 'Page 3' }
224
+ ]
225
+ }
226
+ }
227
+ })
228
+ t.comment('mutation sent')
229
+ const pages = res.json().data.insertPages
230
+ t.comment(JSON.stringify(pages, null, 2))
231
+ return pages
232
+ })(),
233
+ (async function () {
234
+ const pages = []
235
+ const second = new stream.PassThrough({ objectMode: true })
236
+ client.pipe(second)
237
+ for await (const chunk of second) {
238
+ const data = JSON.parse(chunk)
239
+ pages.push(data.payload.data.pageSaved)
240
+ if (pages.length === 3) {
241
+ break
242
+ }
243
+ }
244
+ t.comment('received all pages', JSON.stringify(pages, null, 2))
245
+ return pages
246
+ })()
247
+ ]))
248
+
249
+ t.same(received, stored)
250
+ }
251
+ })
252
+
253
+ test('subscription - ignore', async t => {
254
+ const app = Fastify()
255
+ t.teardown(() => app.close())
256
+
257
+ app.register(sqlMapper, {
258
+ ...connInfo,
259
+ async onDatabaseLoad (db, sql) {
260
+ await clear(db, sql)
261
+ await createBasicPages(db, sql)
262
+ }
263
+ })
264
+ app.register(sqlEvents)
265
+ app.register(sqlGraphQL, {
266
+ subscriptionIgnore: ['page']
267
+ })
268
+
269
+ await app.ready()
270
+ t.equal(graphql.printSchema(app.graphql.schema).indexOf('type Subscription'), -1)
271
+ })
272
+
273
+ test('subscription - crud with two schemas and a ignore', async t => {
274
+ const app = Fastify()
275
+ t.teardown(() => app.close())
276
+
277
+ app.register(sqlMapper, {
278
+ ...connInfo,
279
+ async onDatabaseLoad (db, sql) {
280
+ await clear(db, sql)
281
+ await createBasicPages(db, sql)
282
+
283
+ if (isSQLite) {
284
+ await db.query(sql`CREATE TABLE categories (
285
+ id INTEGER PRIMARY KEY,
286
+ name VARCHAR(42)
287
+ );`)
288
+ } else {
289
+ await db.query(sql`CREATE TABLE categories (
290
+ id SERIAL PRIMARY KEY,
291
+ name VARCHAR(42)
292
+ );`)
293
+ }
294
+ }
295
+ })
296
+ app.register(sqlEvents)
297
+ app.register(sqlGraphQL, {
298
+ subscriptionIgnore: ['category']
299
+ })
300
+
301
+ await app.listen({ port: 0 })
302
+
303
+ const { client } = createWebSocketClient(t, app)
304
+ t.teardown(() => client.destroy())
305
+
306
+ client.write(JSON.stringify({
307
+ type: 'connection_init'
308
+ }))
309
+
310
+ {
311
+ const [chunk] = await once(client, 'data')
312
+ const data = JSON.parse(chunk)
313
+ t.equal(data.type, 'connection_ack')
314
+ }
315
+
316
+ {
317
+ const query = `subscription {
318
+ categorySaved {
319
+ id
320
+ }
321
+ }`
322
+ client.write(JSON.stringify({
323
+ id: 1,
324
+ type: 'start',
325
+ payload: {
326
+ query
327
+ }
328
+ }))
329
+ }
330
+
331
+ {
332
+ const [chunk] = await once(client, 'data')
333
+ const data = JSON.parse(chunk)
334
+ t.equal(data.payload, 'The subscription field "categorySaved" is not defined.')
335
+ }
336
+ })