@platformatic/sql-graphql 0.10.0 → 0.11.0

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/index.js CHANGED
@@ -118,7 +118,21 @@ async function mapperToGraphql (app, opts) {
118
118
  }
119
119
  }
120
120
 
121
- let sdl = graphql.printSchema(new graphql.GraphQLSchema({ query, mutation, subscription }))
121
+ let sdl = ''
122
+ try {
123
+ sdl = graphql.printSchema(new graphql.GraphQLSchema({ query, mutation, subscription }))
124
+ } catch (error) {
125
+ // The next lines are excluded from test coverage:
126
+ // it's quite hard to test the following lines for all of the DB types
127
+ /* istanbul ignore next */
128
+ app.log.debug({ query, mutation, subscription }, 'GraphQL input schema')
129
+ /* istanbul ignore next */
130
+ const newError = new Error('Error printing the GraphQL schema')
131
+ /* istanbul ignore next */
132
+ newError.cause = error
133
+ /* istanbul ignore next */
134
+ throw newError
135
+ }
122
136
 
123
137
  if (opts.federationMetadata) {
124
138
  for (const replacement of federationReplacements) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-graphql",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Map SQL dbs to GraphQL",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -14,14 +14,14 @@
14
14
  },
15
15
  "homepage": "https://github.com/platformatic/platformatic#readme",
16
16
  "devDependencies": {
17
- "@platformatic/sql-events": "0.10.0",
18
- "@platformatic/sql-mapper": "0.10.0",
19
17
  "fastify": "^4.6.0",
20
18
  "snazzy": "^9.0.0",
21
19
  "standard": "^17.0.0",
22
20
  "tap": "^16.0.0",
23
21
  "tsd": "^0.25.0",
24
- "ws": "^8.9.0"
22
+ "ws": "^8.9.0",
23
+ "@platformatic/sql-events": "0.11.0",
24
+ "@platformatic/sql-mapper": "0.11.0"
25
25
  },
26
26
  "dependencies": {
27
27
  "camelcase": "^6.0.0",
@@ -0,0 +1,517 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const sqlGraphQL = require('..')
5
+ const sqlMapper = require('@platformatic/sql-mapper')
6
+ const fastify = require('fastify')
7
+ const { clear, connInfo, isSQLite, isMysql } = require('./helper')
8
+ const { setTimeout } = require('timers/promises')
9
+
10
+ async function createBasicPages (db, sql) {
11
+ if (isSQLite) {
12
+ await db.query(sql`CREATE TABLE pages (
13
+ id INTEGER PRIMARY KEY,
14
+ title VARCHAR(42),
15
+ created_at TIMESTAMP,
16
+ updated_at TIMESTAMP
17
+ );`)
18
+ } else if (isMysql) {
19
+ await db.query(sql`CREATE TABLE pages (
20
+ id SERIAL PRIMARY KEY,
21
+ title VARCHAR(42),
22
+ created_at TIMESTAMP NULL DEFAULT NULL,
23
+ updated_at TIMESTAMP NULL DEFAULT NULL
24
+ );`)
25
+ } else {
26
+ await db.query(sql`CREATE TABLE pages (
27
+ id SERIAL PRIMARY KEY,
28
+ title VARCHAR(42),
29
+ created_at TIMESTAMP,
30
+ updated_at TIMESTAMP
31
+ );`)
32
+ }
33
+ }
34
+
35
+ test('created_at updated_at happy path', async ({ pass, teardown, same, equal, not, comment }) => {
36
+ const app = fastify()
37
+ app.register(sqlMapper, {
38
+ ...connInfo,
39
+ async onDatabaseLoad (db, sql) {
40
+ pass('onDatabaseLoad called')
41
+
42
+ await clear(db, sql)
43
+ await createBasicPages(db, sql)
44
+ }
45
+ })
46
+ app.register(sqlGraphQL)
47
+ teardown(app.close.bind(app))
48
+
49
+ await app.ready()
50
+
51
+ let original
52
+
53
+ {
54
+ const res = await app.inject({
55
+ method: 'POST',
56
+ url: '/graphql',
57
+ body: {
58
+ query: `
59
+ mutation {
60
+ savePage(input: { title: "Hello" }) {
61
+ id
62
+ title
63
+ createdAt
64
+ updatedAt
65
+ }
66
+ }
67
+ `
68
+ }
69
+ })
70
+ equal(res.statusCode, 200, 'savePage status code')
71
+ const data = res.json().data
72
+ not(data.savePage.createdAt, null, 'createdAt')
73
+ not(data.savePage.updatedAt, null, 'updatedAt')
74
+ comment(`createdAt: ${data.savePage.createdAt}`)
75
+ comment(`updatedAt: ${data.savePage.updatedAt}`)
76
+ original = data.savePage
77
+ }
78
+
79
+ {
80
+ const res = await app.inject({
81
+ method: 'POST',
82
+ url: '/graphql',
83
+ body: {
84
+ query: `
85
+ query {
86
+ getPageById(id: 1) {
87
+ id
88
+ title
89
+ createdAt
90
+ updatedAt
91
+ }
92
+ }
93
+ `
94
+ }
95
+ })
96
+ equal(res.statusCode, 200, 'pages status code')
97
+ const data = res.json().data
98
+ equal(data.getPageById.createdAt, original.createdAt, 'createdAt')
99
+ equal(data.getPageById.updatedAt, original.updatedAt, 'updatedAt')
100
+ comment(`createdAt: ${data.getPageById.createdAt}`)
101
+ comment(`updatedAt: ${data.getPageById.updatedAt}`)
102
+ }
103
+
104
+ await setTimeout(1000) // await 1s
105
+
106
+ let updated
107
+ {
108
+ const res = await app.inject({
109
+ method: 'POST',
110
+ url: '/graphql',
111
+ body: {
112
+ query: `
113
+ mutation {
114
+ savePage(input: { id: 1, title: "Hello World" }) {
115
+ id
116
+ title
117
+ createdAt
118
+ updatedAt
119
+ }
120
+ }
121
+ `
122
+ }
123
+ })
124
+ const data = res.json().data
125
+ equal(data.savePage.createdAt, original.createdAt, 'createdAt')
126
+ not(data.savePage.updatedAt, original.updatedAt, 'updatedAt')
127
+ updated = data.savePage
128
+ comment(`createdAt: ${data.savePage.createdAt}`)
129
+ comment(`updatedAt: ${data.savePage.updatedAt}`)
130
+ }
131
+
132
+ {
133
+ const res = await app.inject({
134
+ method: 'POST',
135
+ url: '/graphql',
136
+ body: {
137
+ query: `
138
+ query {
139
+ getPageById(id: 1) {
140
+ id
141
+ title
142
+ createdAt
143
+ updatedAt
144
+ }
145
+ }
146
+ `
147
+ }
148
+ })
149
+ equal(res.statusCode, 200, 'pages status code')
150
+ const data = res.json().data
151
+ equal(data.getPageById.createdAt, updated.createdAt, 'createdAt')
152
+ equal(data.getPageById.updatedAt, updated.updatedAt, 'updatedAt')
153
+ comment(`createdAt: ${data.getPageById.createdAt}`)
154
+ comment(`updatedAt: ${data.getPageById.updatedAt}`)
155
+ }
156
+ })
157
+
158
+ test('cannot set created_at', async ({ pass, teardown, same, equal, not, comment }) => {
159
+ const app = fastify()
160
+ app.register(sqlMapper, {
161
+ ...connInfo,
162
+ async onDatabaseLoad (db, sql) {
163
+ pass('onDatabaseLoad called')
164
+
165
+ await clear(db, sql)
166
+ await createBasicPages(db, sql)
167
+ }
168
+ })
169
+ app.register(sqlGraphQL)
170
+ teardown(app.close.bind(app))
171
+
172
+ await app.ready()
173
+
174
+ {
175
+ const res = await app.inject({
176
+ method: 'POST',
177
+ url: '/graphql',
178
+ body: {
179
+ query: `
180
+ mutation {
181
+ savePage(input: { title: "Hello", createdAt: "${new Date().toISOString()}" }) {
182
+ id
183
+ title
184
+ createdAt
185
+ updatedAt
186
+ }
187
+ }
188
+ `
189
+ }
190
+ })
191
+ equal(res.statusCode, 400, 'savePage status code')
192
+ const data = res.json()
193
+ equal(data.errors[0].message, 'Field "createdAt" is not defined by type "PageInput".')
194
+ }
195
+ })
196
+
197
+ test('cannot set updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
198
+ const app = fastify()
199
+ app.register(sqlMapper, {
200
+ ...connInfo,
201
+ async onDatabaseLoad (db, sql) {
202
+ pass('onDatabaseLoad called')
203
+
204
+ await clear(db, sql)
205
+ await createBasicPages(db, sql)
206
+ }
207
+ })
208
+ app.register(sqlGraphQL)
209
+ teardown(app.close.bind(app))
210
+
211
+ await app.ready()
212
+
213
+ {
214
+ const res = await app.inject({
215
+ method: 'POST',
216
+ url: '/graphql',
217
+ body: {
218
+ query: `
219
+ mutation {
220
+ savePage(input: { title: "Hello" }) {
221
+ id
222
+ title
223
+ createdAt
224
+ updatedAt
225
+ }
226
+ }
227
+ `
228
+ }
229
+ })
230
+ equal(res.statusCode, 200, 'savePage status code')
231
+ const data = res.json().data
232
+ not(data.savePage.createdAt, null, 'createdAt')
233
+ not(data.savePage.updatedAt, null, 'updatedAt')
234
+ comment(`createdAt: ${data.savePage.createdAt}`)
235
+ comment(`updatedAt: ${data.savePage.updatedAt}`)
236
+ }
237
+
238
+ {
239
+ const res = await app.inject({
240
+ method: 'POST',
241
+ url: '/graphql',
242
+ body: {
243
+ query: `
244
+ mutation {
245
+ savePage(input: { id: 1, title: "Hello World", updatedAt: "${new Date().toISOString()}" }) {
246
+ id
247
+ title
248
+ createdAt
249
+ updatedAt
250
+ }
251
+ }
252
+ `
253
+ }
254
+ })
255
+ equal(res.statusCode, 400, 'savePage status code')
256
+ const data = res.json()
257
+ equal(data.errors[0].message, 'Field "updatedAt" is not defined by type "PageInput".')
258
+ }
259
+ })
260
+
261
+ test('do not assign created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
262
+ const app = fastify()
263
+ app.register(sqlMapper, {
264
+ ...connInfo,
265
+ autoTimestamp: false,
266
+ async onDatabaseLoad (db, sql) {
267
+ pass('onDatabaseLoad called')
268
+
269
+ await clear(db, sql)
270
+ await createBasicPages(db, sql)
271
+ }
272
+ })
273
+ app.register(sqlGraphQL)
274
+ teardown(app.close.bind(app))
275
+
276
+ await app.ready()
277
+
278
+ {
279
+ const res = await app.inject({
280
+ method: 'POST',
281
+ url: '/graphql',
282
+ body: {
283
+ query: `
284
+ mutation {
285
+ savePage(input: { title: "Hello" }) {
286
+ id
287
+ title
288
+ createdAt
289
+ updatedAt
290
+ }
291
+ }
292
+ `
293
+ }
294
+ })
295
+ equal(res.statusCode, 200, 'savePage status code')
296
+ const data = res.json().data
297
+ equal(data.savePage.createdAt, null, 'createdAt')
298
+ equal(data.savePage.updatedAt, null, 'updatedAt')
299
+ }
300
+
301
+ {
302
+ const res = await app.inject({
303
+ method: 'POST',
304
+ url: '/graphql',
305
+ body: {
306
+ query: `
307
+ query {
308
+ getPageById(id: 1) {
309
+ id
310
+ title
311
+ createdAt
312
+ updatedAt
313
+ }
314
+ }
315
+ `
316
+ }
317
+ })
318
+ equal(res.statusCode, 200, 'pages status code')
319
+ const data = res.json().data
320
+ equal(data.getPageById.createdAt, null, 'createdAt')
321
+ equal(data.getPageById.updatedAt, null, 'updatedAt')
322
+ }
323
+
324
+ {
325
+ const res = await app.inject({
326
+ method: 'POST',
327
+ url: '/graphql',
328
+ body: {
329
+ query: `
330
+ mutation {
331
+ savePage(input: { id: 1, title: "Hello World" }) {
332
+ id
333
+ title
334
+ createdAt
335
+ updatedAt
336
+ }
337
+ }
338
+ `
339
+ }
340
+ })
341
+ const data = res.json().data
342
+ equal(data.savePage.createdAt, null, 'createdAt')
343
+ equal(data.savePage.updatedAt, null, 'updatedAt')
344
+ }
345
+
346
+ {
347
+ const res = await app.inject({
348
+ method: 'POST',
349
+ url: '/graphql',
350
+ body: {
351
+ query: `
352
+ query {
353
+ getPageById(id: 1) {
354
+ id
355
+ title
356
+ createdAt
357
+ updatedAt
358
+ }
359
+ }
360
+ `
361
+ }
362
+ })
363
+ equal(res.statusCode, 200, 'pages status code')
364
+ const data = res.json().data
365
+ equal(data.getPageById.createdAt, null, 'createdAt')
366
+ equal(data.getPageById.updatedAt, null, 'updatedAt')
367
+ }
368
+ })
369
+
370
+ test('bulk insert adds created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
371
+ const app = fastify()
372
+ app.register(sqlMapper, {
373
+ ...connInfo,
374
+ async onDatabaseLoad (db, sql) {
375
+ pass('onDatabaseLoad called')
376
+
377
+ await clear(db, sql)
378
+ await createBasicPages(db, sql)
379
+ }
380
+ })
381
+ app.register(sqlGraphQL)
382
+ teardown(app.close.bind(app))
383
+
384
+ await app.ready()
385
+
386
+ {
387
+ const res = await app.inject({
388
+ method: 'POST',
389
+ url: '/graphql',
390
+ body: {
391
+ query: `
392
+ mutation {
393
+ savePage(input: { title: "Hello" }) {
394
+ id
395
+ title
396
+ createdAt
397
+ updatedAt
398
+ }
399
+ }
400
+ `
401
+ }
402
+ })
403
+ equal(res.statusCode, 200, 'savePage status code')
404
+ const data = res.json().data
405
+ not(data.savePage.createdAt, null, 'createdAt')
406
+ not(data.savePage.updatedAt, null, 'updatedAt')
407
+ }
408
+
409
+ {
410
+ const res = await app.inject({
411
+ method: 'POST',
412
+ url: '/graphql',
413
+ body: {
414
+ query: `
415
+ mutation batch($inputs : [PageInput]!) {
416
+ insertPages (inputs: $inputs) {
417
+ id
418
+ title
419
+ createdAt
420
+ updatedAt
421
+ }
422
+ }
423
+ `,
424
+ variables: {
425
+ inputs: [
426
+ { title: 'Page 1' },
427
+ { title: 'Page 2' },
428
+ { title: 'Page 3' }
429
+ ]
430
+ }
431
+ }
432
+ })
433
+ equal(res.statusCode, 200, 'savePage status code')
434
+ const pages = res.json().data.insertPages
435
+ for (const page of pages) {
436
+ not(page.createdAt, null, 'createdAt')
437
+ not(page.updatedAt, null, 'updatedAt')
438
+ equal(page.createdAt, page.updatedAt, 'createdAt === updatedAt')
439
+ }
440
+ }
441
+
442
+ {
443
+ const res = await app.inject({
444
+ method: 'POST',
445
+ url: '/graphql',
446
+ body: {
447
+ query: `
448
+ query {
449
+ pages {
450
+ id
451
+ title
452
+ createdAt
453
+ updatedAt
454
+ }
455
+ }
456
+ `
457
+ }
458
+ })
459
+ equal(res.statusCode, 200, 'pages status code')
460
+ const pages = res.json().data.pages
461
+ for (const page of pages) {
462
+ not(page.createdAt, null, 'createdAt')
463
+ not(page.updatedAt, null, 'updatedAt')
464
+ equal(page.createdAt, page.updatedAt, 'createdAt === updatedAt')
465
+ }
466
+ }
467
+ })
468
+
469
+ test('bulk insert with autoTimestamp=false do not had created_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
470
+ const app = fastify()
471
+ app.register(sqlMapper, {
472
+ ...connInfo,
473
+ autoTimestamp: false,
474
+ async onDatabaseLoad (db, sql) {
475
+ pass('onDatabaseLoad called')
476
+
477
+ await clear(db, sql)
478
+ await createBasicPages(db, sql)
479
+ }
480
+ })
481
+ app.register(sqlGraphQL)
482
+ teardown(app.close.bind(app))
483
+
484
+ await app.ready()
485
+
486
+ {
487
+ const res = await app.inject({
488
+ method: 'POST',
489
+ url: '/graphql',
490
+ body: {
491
+ query: `
492
+ mutation batch($inputs : [PageInput]!) {
493
+ insertPages (inputs: $inputs) {
494
+ id
495
+ title
496
+ createdAt
497
+ updatedAt
498
+ }
499
+ }
500
+ `,
501
+ variables: {
502
+ inputs: [
503
+ { title: 'Page 1' },
504
+ { title: 'Page 2' },
505
+ { title: 'Page 3' }
506
+ ]
507
+ }
508
+ }
509
+ })
510
+ equal(res.statusCode, 200, 'savePage status code')
511
+ const pages = res.json().data.insertPages
512
+ for (const page of pages) {
513
+ equal(page.createdAt, null, 'createdAt')
514
+ equal(page.updatedAt, null, 'updatedAt')
515
+ }
516
+ }
517
+ })
package/test/helper.js CHANGED
@@ -96,4 +96,19 @@ module.exports.clear = async function (db, sql) {
96
96
  await db.query(sql`DROP Type simple_enum`)
97
97
  } catch (err) {
98
98
  }
99
+
100
+ try {
101
+ await db.query(sql`DROP TABLE "myschema"."mytable"`)
102
+ } catch (err) {
103
+ }
104
+
105
+ try {
106
+ await db.query(sql`DROP TABLE "myschema"."template"`)
107
+ } catch (err) {
108
+ }
109
+
110
+ try {
111
+ await db.query(sql`DROP SCHEMA "myschema"`)
112
+ } catch (err) {
113
+ }
99
114
  }
@@ -36,6 +36,10 @@ test('inserted_at updated_at happy path', async ({ pass, teardown, same, equal,
36
36
  const app = fastify()
37
37
  app.register(sqlMapper, {
38
38
  ...connInfo,
39
+ autoTimestamp: {
40
+ createdAt: 'inserted_at',
41
+ updatedAt: 'updated_at'
42
+ },
39
43
  async onDatabaseLoad (db, sql) {
40
44
  pass('onDatabaseLoad called')
41
45
 
@@ -159,6 +163,10 @@ test('cannot set inserted_at', async ({ pass, teardown, same, equal, not, commen
159
163
  const app = fastify()
160
164
  app.register(sqlMapper, {
161
165
  ...connInfo,
166
+ autoTimestamp: {
167
+ createdAt: 'inserted_at',
168
+ updatedAt: 'updated_at'
169
+ },
162
170
  async onDatabaseLoad (db, sql) {
163
171
  pass('onDatabaseLoad called')
164
172
 
@@ -198,6 +206,10 @@ test('cannot set updated_at', async ({ pass, teardown, same, equal, not, comment
198
206
  const app = fastify()
199
207
  app.register(sqlMapper, {
200
208
  ...connInfo,
209
+ autoTimestamp: {
210
+ createdAt: 'inserted_at',
211
+ updatedAt: 'updated_at'
212
+ },
201
213
  async onDatabaseLoad (db, sql) {
202
214
  pass('onDatabaseLoad called')
203
215
 
@@ -371,6 +383,10 @@ test('bulk insert adds inserted_at updated_at', async ({ pass, teardown, same, e
371
383
  const app = fastify()
372
384
  app.register(sqlMapper, {
373
385
  ...connInfo,
386
+ autoTimestamp: {
387
+ createdAt: 'inserted_at',
388
+ updatedAt: 'updated_at'
389
+ },
374
390
  async onDatabaseLoad (db, sql) {
375
391
  pass('onDatabaseLoad called')
376
392
 
@@ -466,7 +482,7 @@ test('bulk insert adds inserted_at updated_at', async ({ pass, teardown, same, e
466
482
  }
467
483
  })
468
484
 
469
- test('bulk insert with autoTimestamp=false do not hadd inserted_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
485
+ test('bulk insert with autoTimestamp=false do not had inserted_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
470
486
  const app = fastify()
471
487
  app.register(sqlMapper, {
472
488
  ...connInfo,
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const sqlGraphQL = require('..')
5
+ const sqlMapper = require('@platformatic/sql-mapper')
6
+ const fastify = require('fastify')
7
+ const { clear, connInfo, isPg } = require('./helper')
8
+
9
+ const connInfoWithSchema = {
10
+ ...connInfo,
11
+ schema: ['public', 'myschema']
12
+ }
13
+
14
+ test('should catch GraphQL print schema errors', { skip: !isPg }, async ({ pass, teardown, same }) => {
15
+ const app = fastify()
16
+ app.register(sqlMapper, {
17
+ ...connInfoWithSchema,
18
+ async onDatabaseLoad (db, sql) {
19
+ pass('onDatabaseLoad called')
20
+
21
+ await clear(db, sql)
22
+
23
+ await db.query(sql`
24
+ CREATE SCHEMA IF NOT EXISTS "myschema";
25
+
26
+ CREATE TABLE IF NOT EXISTS "myschema"."template" (
27
+ "id" uuid NOT NULL,
28
+ PRIMARY KEY ("id")
29
+ );
30
+
31
+ CREATE TABLE IF NOT EXISTS "myschema"."mytable" (
32
+ "id" int4 NOT NULL,
33
+ "template_id" uuid,
34
+ CONSTRAINT "mytable_template" FOREIGN KEY ("template_id") REFERENCES "myschema"."template"("id"),
35
+ PRIMARY KEY ("id")
36
+ );`)
37
+ }
38
+ })
39
+
40
+ app.register(sqlGraphQL)
41
+ teardown(app.close.bind(app))
42
+
43
+ try {
44
+ await app.ready()
45
+ same(true, false, 'We check that this line never runs, since we should catch the previous error')
46
+ } catch (error) {
47
+ same(error.name, 'Error')
48
+ same(error.message, 'Error printing the GraphQL schema')
49
+ same(error.cause, new Error('MyschemaTemplate.mytable field config must be an object.'))
50
+ }
51
+ })