@platformatic/sql-graphql 0.1.0 → 0.3.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.
Files changed (55) hide show
  1. package/.nyc_output/28567d89-45dc-4aa4-8d9d-4c8b713612e5.json +1 -0
  2. package/.nyc_output/{22ead0f6-e5a6-4647-a2ea-db2f42498081.json → 475e591b-b773-4ada-87d0-db538511c660.json} +0 -0
  3. package/.nyc_output/4956602f-f6bd-4aef-bc4c-3d019bf6d754.json +1 -0
  4. package/.nyc_output/4c93feaa-384f-4a7c-b7bf-070cecebf72c.json +1 -0
  5. package/.nyc_output/6e295e9b-655e-43fd-a92a-75d058bed16d.json +1 -0
  6. package/.nyc_output/79fec06e-8965-498f-a023-624db14cdca7.json +1 -0
  7. package/.nyc_output/7f365fa0-3cdb-4303-ad17-59e3639745cc.json +1 -0
  8. package/.nyc_output/92f8aaa6-0c3d-4833-8544-93e0be94a868.json +1 -0
  9. package/.nyc_output/a4edd7d2-26a6-46ca-a79e-bc52a9308d76.json +1 -0
  10. package/.nyc_output/b152f368-1d63-4d9d-bd3a-97bb6b761ca8.json +1 -0
  11. package/.nyc_output/cc4af553-54c0-4a1e-9980-b7d8534e9010.json +1 -0
  12. package/.nyc_output/e0eb89e2-5c3c-4b39-8237-5827918c9774.json +1 -0
  13. package/.nyc_output/e640b1f5-34e9-4ac5-8691-3fc4b925739b.json +1 -0
  14. package/.nyc_output/f3e7d9f6-b598-48c5-9ebe-18071958a7d8.json +1 -0
  15. package/.nyc_output/fb0cb3f2-69da-49fa-bd3c-f13eceee0d3d.json +1 -0
  16. package/.nyc_output/processinfo/{191054d4-6a1e-4a1b-87bb-f801ed78a223.json → 28567d89-45dc-4aa4-8d9d-4c8b713612e5.json} +1 -1
  17. package/.nyc_output/processinfo/475e591b-b773-4ada-87d0-db538511c660.json +1 -0
  18. package/.nyc_output/processinfo/{5acfd416-02d1-497b-8dbd-69c0af167c62.json → 4956602f-f6bd-4aef-bc4c-3d019bf6d754.json} +1 -1
  19. package/.nyc_output/processinfo/{74cc1dab-7479-48e7-80b0-c97351d8856e.json → 4c93feaa-384f-4a7c-b7bf-070cecebf72c.json} +1 -1
  20. package/.nyc_output/processinfo/{c201a1cd-3105-4d18-bbbb-8d7fa674d439.json → 6e295e9b-655e-43fd-a92a-75d058bed16d.json} +1 -1
  21. package/.nyc_output/processinfo/{0681b2b7-587a-40f2-8242-257bc4c72456.json → 79fec06e-8965-498f-a023-624db14cdca7.json} +1 -1
  22. package/.nyc_output/processinfo/{5ec04f76-9bcb-475d-b79e-f9304cc0026c.json → 7f365fa0-3cdb-4303-ad17-59e3639745cc.json} +1 -1
  23. package/.nyc_output/processinfo/{4e847e26-05cd-4063-9277-48aff86d6822.json → 92f8aaa6-0c3d-4833-8544-93e0be94a868.json} +1 -1
  24. package/.nyc_output/processinfo/{c1c0eecb-63a0-4258-81b6-50e7488bd67c.json → a4edd7d2-26a6-46ca-a79e-bc52a9308d76.json} +1 -1
  25. package/.nyc_output/processinfo/b152f368-1d63-4d9d-bd3a-97bb6b761ca8.json +1 -0
  26. package/.nyc_output/processinfo/{d6da0270-e9ed-41e2-bbf3-9262d1df683c.json → cc4af553-54c0-4a1e-9980-b7d8534e9010.json} +1 -1
  27. package/.nyc_output/processinfo/{5f3bb47a-0d2d-440a-a466-8ffa9ab3efaa.json → e0eb89e2-5c3c-4b39-8237-5827918c9774.json} +1 -1
  28. package/.nyc_output/processinfo/{9f5d12ba-bc3d-461c-a0e2-2a95d2020c19.json → e640b1f5-34e9-4ac5-8691-3fc4b925739b.json} +1 -1
  29. package/.nyc_output/processinfo/{ac343b71-6f76-421c-a287-46d19bb48f89.json → f3e7d9f6-b598-48c5-9ebe-18071958a7d8.json} +1 -1
  30. package/.nyc_output/processinfo/{71c2bd84-9a72-4e86-83c6-d587ce525fe4.json → fb0cb3f2-69da-49fa-bd3c-f13eceee0d3d.json} +1 -1
  31. package/.nyc_output/processinfo/index.json +1 -1
  32. package/index.d.ts +0 -1
  33. package/index.js +37 -33
  34. package/lib/entity-to-type.js +32 -8
  35. package/lib/relationship.js +1 -1
  36. package/lib/utils.js +3 -0
  37. package/package.json +4 -2
  38. package/test/count.test.js +126 -0
  39. package/test/helper.js +1 -0
  40. package/test/simple.test.js +196 -1
  41. package/test/types/index.test-d.ts +0 -1
  42. package/.nyc_output/0681b2b7-587a-40f2-8242-257bc4c72456.json +0 -1
  43. package/.nyc_output/191054d4-6a1e-4a1b-87bb-f801ed78a223.json +0 -1
  44. package/.nyc_output/4e847e26-05cd-4063-9277-48aff86d6822.json +0 -1
  45. package/.nyc_output/5acfd416-02d1-497b-8dbd-69c0af167c62.json +0 -1
  46. package/.nyc_output/5ec04f76-9bcb-475d-b79e-f9304cc0026c.json +0 -1
  47. package/.nyc_output/5f3bb47a-0d2d-440a-a466-8ffa9ab3efaa.json +0 -1
  48. package/.nyc_output/71c2bd84-9a72-4e86-83c6-d587ce525fe4.json +0 -1
  49. package/.nyc_output/74cc1dab-7479-48e7-80b0-c97351d8856e.json +0 -1
  50. package/.nyc_output/9f5d12ba-bc3d-461c-a0e2-2a95d2020c19.json +0 -1
  51. package/.nyc_output/ac343b71-6f76-421c-a287-46d19bb48f89.json +0 -1
  52. package/.nyc_output/c1c0eecb-63a0-4258-81b6-50e7488bd67c.json +0 -1
  53. package/.nyc_output/c201a1cd-3105-4d18-bbbb-8d7fa674d439.json +0 -1
  54. package/.nyc_output/d6da0270-e9ed-41e2-bbf3-9262d1df683c.json +0 -1
  55. package/.nyc_output/processinfo/22ead0f6-e5a6-4647-a2ea-db2f42498081.json +0 -1
package/index.js CHANGED
@@ -9,7 +9,6 @@ const scalars = require('graphql-scalars')
9
9
 
10
10
  async function mapperToGraphql (app, opts) {
11
11
  const mapper = app.platformatic
12
- const autoTimestamp = opts.autoTimestamp
13
12
  const queryTopFields = {}
14
13
  const mutationTopFields = {}
15
14
  const resolvers = {}
@@ -23,42 +22,48 @@ async function mapperToGraphql (app, opts) {
23
22
  resolvers,
24
23
  loaders,
25
24
  federationReplacements,
26
- federationMetadata: opts.federationMetadata,
27
- autoTimestamp
28
- }
29
-
30
- const metaMap = new Map()
31
- for (const entity of Object.values(mapper.entities)) {
32
- relations.push(...entity.relations)
33
- const meta = constructGraph(app, entity, graphOpts)
34
- metaMap.set(entity, meta)
25
+ federationMetadata: opts.federationMetadata
35
26
  }
27
+ if (Object.keys(mapper.entities).length === 0) {
28
+ // no schema
29
+ queryTopFields.hello = { type: graphql.GraphQLString }
30
+ resolvers.Query = {
31
+ hello: () => 'Hello Platformatic!'
32
+ }
33
+ } else {
34
+ const metaMap = new Map()
35
+ for (const entity of Object.values(mapper.entities)) {
36
+ relations.push(...entity.relations)
37
+ const meta = constructGraph(app, entity, graphOpts)
38
+ metaMap.set(entity, meta)
39
+ }
36
40
 
37
- establishRelations(app, relations, resolvers, loaders, queryTopFields, opts.resolvers || {}, metaMap)
41
+ establishRelations(app, relations, resolvers, loaders, queryTopFields, opts.resolvers || {}, metaMap)
38
42
 
39
- if (opts.resolvers) {
40
- for (const key of Object.keys(opts.resolvers)) {
41
- if (!resolvers[key]) {
42
- resolvers[key] = {}
43
- }
43
+ if (opts.resolvers) {
44
+ for (const key of Object.keys(opts.resolvers)) {
45
+ if (!resolvers[key]) {
46
+ resolvers[key] = {}
47
+ }
44
48
 
45
- const type = opts.resolvers[key]
46
- for (const resolver of Object.keys(type)) {
47
- if (type[resolver] === false) {
48
- if (resolvers[key][resolver]) {
49
- delete resolvers[key][resolver]
50
- }
51
- if (loaders[key]) {
52
- delete loaders[key][resolver]
49
+ const type = opts.resolvers[key]
50
+ for (const resolver of Object.keys(type)) {
51
+ if (type[resolver] === false) {
52
+ if (resolvers[key][resolver]) {
53
+ delete resolvers[key][resolver]
54
+ }
55
+ if (loaders[key]) {
56
+ delete loaders[key][resolver]
57
+ }
58
+ /* istanbul ignore else */
59
+ if (key === 'Mutation') {
60
+ delete mutationTopFields[resolver]
61
+ } else if (key === 'Query') {
62
+ delete queryTopFields[resolver]
63
+ }
64
+ } else {
65
+ resolvers[key][resolver] = type[resolver]
53
66
  }
54
- /* istanbul ignore else */
55
- if (key === 'Mutation') {
56
- delete mutationTopFields[resolver]
57
- } else if (key === 'Query') {
58
- delete queryTopFields[resolver]
59
- }
60
- } else {
61
- resolvers[key][resolver] = type[resolver]
62
67
  }
63
68
  }
64
69
  }
@@ -93,7 +98,6 @@ async function mapperToGraphql (app, opts) {
93
98
  sdl += '\n'
94
99
  sdl += opts.schema
95
100
  }
96
-
97
101
  // Ignoriring because SQLite doesn't support dates
98
102
  /* istanbul ignore next */
99
103
  if (sdl.match(/scalar Date\n/)) {
@@ -16,12 +16,12 @@ const ascDesc = new graphql.GraphQLEnumType({
16
16
  })
17
17
 
18
18
  function constructGraph (app, entity, opts) {
19
- const primaryKey = entity.primaryKey
20
-
19
+ const primaryKey = camelcase(entity.primaryKey)
20
+ const relationalFields = entity.relations
21
+ .map((relation) => relation.column_name)
21
22
  const entityName = entity.name
22
23
  const singular = entity.singularName
23
24
  const plural = entity.pluralName
24
-
25
25
  const {
26
26
  queryTopFields,
27
27
  mutationTopFields,
@@ -128,7 +128,7 @@ function constructGraph (app, entity, opts) {
128
128
  resolvers.Query[plural] = (_, query, ctx, info) => {
129
129
  const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
130
130
  requestedFields.push(primaryKey)
131
- return entity.find({ ...query, fields: requestedFields, ctx })
131
+ return entity.find({ ...query, fields: [...requestedFields, ...relationalFields], ctx })
132
132
  }
133
133
 
134
134
  const inputType = new graphql.GraphQLInputObjectType({
@@ -143,6 +143,29 @@ function constructGraph (app, entity, opts) {
143
143
  }, {})
144
144
  })
145
145
 
146
+ const count = camelcase(['count', plural])
147
+
148
+ const countType = new graphql.GraphQLObjectType({
149
+ name: `${plural}Count`,
150
+ fields: {
151
+ total: { type: graphql.GraphQLInt }
152
+ }
153
+ })
154
+
155
+ queryTopFields[count] = {
156
+ type: countType,
157
+ args: {
158
+ where: { type: whereArgType }
159
+ }
160
+ }
161
+
162
+ resolvers.Query[count] = async (_, query, ctx, info) => {
163
+ const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
164
+ requestedFields.push(primaryKey)
165
+ const total = await entity.count({ ...query, fields: [...requestedFields, ...relationalFields], ctx })
166
+ return { total }
167
+ }
168
+
146
169
  const save = camelcase(['save', singular])
147
170
 
148
171
  mutationTopFields[save] = {
@@ -154,7 +177,7 @@ function constructGraph (app, entity, opts) {
154
177
 
155
178
  resolvers.Mutation[save] = async (_, { input }, ctx, info) => {
156
179
  const fields = fromSelectionSet(info.fieldNodes[0].selectionSet)
157
- return entity.save({ input, ctx, fields: [...fields] })
180
+ return entity.save({ input, ctx, fields: [...fields, ...relationalFields] })
158
181
  }
159
182
 
160
183
  const insert = camelcase(['insert', plural])
@@ -168,7 +191,7 @@ function constructGraph (app, entity, opts) {
168
191
 
169
192
  resolvers.Mutation[insert] = (_, { inputs }, ctx, info) => {
170
193
  const fields = fromSelectionSet(info.fieldNodes[0].selectionSet)
171
- return entity.insert({ inputs, ctx, fields: [...fields] })
194
+ return entity.insert({ inputs, ctx, fields: [...fields, ...relationalFields] })
172
195
  }
173
196
 
174
197
  const deleteKey = camelcase(['delete', plural])
@@ -181,7 +204,7 @@ function constructGraph (app, entity, opts) {
181
204
 
182
205
  resolvers.Mutation[deleteKey] = (_, args, ctx, info) => {
183
206
  const fields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
184
- return entity.delete({ ...args, fields, ctx })
207
+ return entity.delete({ ...args, fields: [...fields, ...relationalFields], ctx })
185
208
  }
186
209
 
187
210
  federationReplacements.push({
@@ -236,11 +259,12 @@ function constructGraph (app, entity, opts) {
236
259
  }
237
260
 
238
261
  function getFields (queries) {
239
- const fields = new Set()
262
+ const fields = new Set([...relationalFields])
240
263
  fields.add(primaryKey)
241
264
  for (const query of queries) {
242
265
  fromSelectionSet(query.info.fieldNodes[0].selectionSet, fields)
243
266
  }
267
+ fields.delete(undefined)
244
268
  return [...fields]
245
269
  }
246
270
  }
@@ -51,7 +51,7 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
51
51
  resolvers[foreign.type] = resolvers[foreign.type] || {}
52
52
  resolvers[foreign.type][lowered] = async function (obj, args, ctx, info) {
53
53
  const fields = fromSelectionSet(info.fieldNodes[0].selectionSet, new Set())
54
- const toSearch = { ...args, fields: [...fields], ctx }
54
+ const toSearch = { ...args, fields: [...fields, relation.column_name], ctx }
55
55
  toSearch.where = toSearch.where || {}
56
56
  toSearch.where[camelcase(relation.column_name)] = { eq: obj.id }
57
57
  return current.entity.find(toSearch)
package/lib/utils.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const graphql = require('graphql')
4
4
  const scalars = require('graphql-scalars')
5
+ const { GraphQLJSONObject } = require('graphql-type-json')
5
6
 
6
7
  // The sqlTypeToGraphQL is shared between
7
8
  // all database adapters.
@@ -51,6 +52,8 @@ function sqlTypeToGraphQL (sqlType) {
51
52
  return scalars.GraphQLDateTime
52
53
  case 'uuid':
53
54
  return graphql.GraphQLString
55
+ case 'json':
56
+ return GraphQLJSONObject
54
57
  default:
55
58
  return graphql.GraphQLString
56
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-graphql",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Map SQL dbs to GraphQL",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "homepage": "https://github.com/plaformatic/platformatic#readme",
16
16
  "devDependencies": {
17
- "@platformatic/sql-mapper": "0.1.0",
17
+ "@platformatic/sql-mapper": "0.3.0",
18
18
  "fastify": "^4.6.0",
19
19
  "snazzy": "^9.0.0",
20
20
  "standard": "^17.0.0",
@@ -26,6 +26,7 @@
26
26
  "fastify-plugin": "^4.1.0",
27
27
  "graphql": "^16.6.0",
28
28
  "graphql-scalars": "^1.13.1",
29
+ "graphql-type-json": "^0.3.2",
29
30
  "inflected": "^2.1.0",
30
31
  "mercurius": "^11.0.0"
31
32
  },
@@ -33,6 +34,7 @@
33
34
  "directory": "test/types"
34
35
  },
35
36
  "scripts": {
37
+ "lint": "standard",
36
38
  "test": "standard | snazzy && npm run test:typescript && npm run test:postgresql && npm run test:mariadb && npm run test:mysql && npm run test:mysql8 && npm run test:sqlite",
37
39
  "test:postgresql": "DB=postgresql tap test/*.test.js",
38
40
  "test:mariadb": "DB=mariadb tap test/*.test.js",
@@ -0,0 +1,126 @@
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 } = require('./helper')
8
+
9
+ test('count', async ({ pass, teardown, same, equal }) => {
10
+ const app = fastify()
11
+ app.register(sqlMapper, {
12
+ ...connInfo,
13
+ async onDatabaseLoad (db, sql) {
14
+ pass('onDatabaseLoad called')
15
+
16
+ await clear(db, sql)
17
+
18
+ if (isSQLite) {
19
+ await db.query(sql`CREATE TABLE posts (
20
+ id INTEGER PRIMARY KEY,
21
+ title VARCHAR(42),
22
+ long_text TEXT,
23
+ counter INTEGER
24
+ );`)
25
+ } else {
26
+ await db.query(sql`CREATE TABLE posts (
27
+ id SERIAL PRIMARY KEY,
28
+ title VARCHAR(42),
29
+ long_text TEXT,
30
+ counter INTEGER
31
+ );`)
32
+ }
33
+ }
34
+ })
35
+ app.register(sqlGraphQL)
36
+ teardown(app.close.bind(app))
37
+
38
+ await app.ready()
39
+
40
+ const posts = [{
41
+ title: 'Dog',
42
+ longText: 'Foo',
43
+ counter: 10
44
+ }, {
45
+ title: 'Cat',
46
+ longText: 'Bar',
47
+ counter: 20
48
+ }, {
49
+ title: 'Mouse',
50
+ longText: 'Baz',
51
+ counter: 30
52
+ }, {
53
+ title: 'Duck',
54
+ longText: 'A duck tale',
55
+ counter: 40
56
+ }]
57
+
58
+ {
59
+ const res = await app.inject({
60
+ method: 'POST',
61
+ url: '/graphql',
62
+ body: {
63
+ query: `
64
+ mutation batch($inputs : [PostInput]!) {
65
+ insertPosts(inputs: $inputs) {
66
+ id
67
+ title
68
+ }
69
+ }
70
+ `,
71
+ variables: {
72
+ inputs: posts
73
+ }
74
+ }
75
+ })
76
+ equal(res.statusCode, 200, 'posts status code')
77
+ }
78
+
79
+ {
80
+ const res = await app.inject({
81
+ method: 'POST',
82
+ url: '/graphql',
83
+ body: {
84
+ query: `
85
+ query {
86
+ countPosts {
87
+ total
88
+ }
89
+ }
90
+ `
91
+ }
92
+ })
93
+ equal(res.statusCode, 200, 'posts status code')
94
+ same(res.json(), {
95
+ data: {
96
+ countPosts: {
97
+ total: 4
98
+ }
99
+ }
100
+ }, 'posts response')
101
+ }
102
+
103
+ {
104
+ const res = await app.inject({
105
+ method: 'POST',
106
+ url: '/graphql',
107
+ body: {
108
+ query: `
109
+ query {
110
+ countPosts(where: { counter: { gt: 20 } }) {
111
+ total
112
+ }
113
+ }
114
+ `
115
+ }
116
+ })
117
+ equal(res.statusCode, 200, 'posts status code')
118
+ same(res.json(), {
119
+ data: {
120
+ countPosts: {
121
+ total: 2
122
+ }
123
+ }
124
+ }, 'posts response')
125
+ }
126
+ })
package/test/helper.js CHANGED
@@ -13,6 +13,7 @@ if (!process.env.DB || process.env.DB === 'postgresql') {
13
13
  connInfo.connectionString = 'mysql://root@127.0.0.1:3307/graph'
14
14
  connInfo.poolSize = 10
15
15
  module.exports.isMysql = true
16
+ module.exports.isMariaDB = true
16
17
  } else if (process.env.DB === 'mysql') {
17
18
  connInfo.connectionString = 'mysql://root@127.0.0.1/graph'
18
19
  connInfo.poolSize = 10
@@ -4,7 +4,7 @@ const { test } = require('tap')
4
4
  const sqlGraphQL = require('..')
5
5
  const sqlMapper = require('@platformatic/sql-mapper')
6
6
  const fastify = require('fastify')
7
- const { clear, connInfo, isSQLite, isMysql } = require('./helper')
7
+ const { clear, connInfo, isSQLite, isMysql, isMariaDB } = require('./helper')
8
8
 
9
9
  async function createBasicPages (db, sql) {
10
10
  if (isSQLite) {
@@ -706,3 +706,198 @@ test('graphiql can be disabled', async ({ pass, teardown, same, equal }) => {
706
706
  const res = await app.inject('/graphiql')
707
707
  equal(res.statusCode, 404)
708
708
  })
709
+
710
+ test('default query hello should be created when no entities are found', async ({ pass, teardown, same, equal }) => {
711
+ const app = fastify()
712
+ app.register(sqlMapper, {
713
+ ...connInfo,
714
+ async onDatabaseLoad (db, sql) {
715
+ pass('onDatabaseLoad called')
716
+ await clear(db, sql)
717
+ }
718
+ })
719
+ app.register(sqlGraphQL)
720
+ teardown(app.close.bind(app))
721
+
722
+ const res = await app.inject({
723
+ method: 'POST',
724
+ url: '/graphql',
725
+ body: {
726
+ query: 'query { hello }'
727
+ }
728
+ })
729
+ equal(res.statusCode, 200)
730
+ same(res.json(), {
731
+ data: {
732
+ hello: 'Hello Platformatic!'
733
+ }
734
+ })
735
+ })
736
+
737
+ test('default query hello should not be created when entities are found', async ({ pass, teardown, same, equal }) => {
738
+ const app = fastify()
739
+ app.register(sqlMapper, {
740
+ ...connInfo,
741
+ async onDatabaseLoad (db, sql) {
742
+ pass('onDatabaseLoad called')
743
+ await clear(db, sql)
744
+ await createBasicPages(db, sql)
745
+ }
746
+ })
747
+ app.register(sqlGraphQL)
748
+ teardown(app.close.bind(app))
749
+
750
+ const res = await app.inject({
751
+ method: 'POST',
752
+ url: '/graphql',
753
+ body: {
754
+ query: 'query { hello }'
755
+ }
756
+ })
757
+ equal(res.statusCode, 400)
758
+ const json = res.json()
759
+ same(json.data, null)
760
+ same(json.errors[0].message, 'Cannot query field "hello" on type "Query".')
761
+ })
762
+
763
+ test('primary key snake_case', async ({ pass, teardown, same, equal }) => {
764
+ async function createBasicPagesWithSnakeCasePK (db, sql) {
765
+ if (isSQLite) {
766
+ await db.query(sql`CREATE TABLE pages (
767
+ page_id INTEGER PRIMARY KEY,
768
+ title VARCHAR(42)
769
+ );`)
770
+ } else {
771
+ await db.query(sql`CREATE TABLE pages (
772
+ page_id SERIAL PRIMARY KEY,
773
+ title VARCHAR(42)
774
+ );`)
775
+ }
776
+ }
777
+
778
+ const app = fastify()
779
+ app.register(sqlMapper, {
780
+ ...connInfo,
781
+ async onDatabaseLoad (db, sql) {
782
+ pass('onDatabaseLoad called')
783
+
784
+ await clear(db, sql)
785
+ await createBasicPagesWithSnakeCasePK(db, sql)
786
+ }
787
+ })
788
+ app.register(sqlGraphQL, {
789
+ graphiql: false
790
+ })
791
+ teardown(app.close.bind(app))
792
+
793
+ const res = await app.inject('/graphiql')
794
+ equal(res.statusCode, 404)
795
+
796
+ {
797
+ const res = await app.inject({
798
+ method: 'POST',
799
+ url: '/graphql',
800
+ body: {
801
+ query: `
802
+ mutation {
803
+ savePage(input: { title: "Hello" }) {
804
+ pageId
805
+ title
806
+ }
807
+ }
808
+ `
809
+ }
810
+ })
811
+ equal(res.statusCode, 200, 'savePage status code')
812
+ same(res.json(), {
813
+ data: {
814
+ savePage: {
815
+ pageId: 1,
816
+ title: 'Hello'
817
+ }
818
+ }
819
+ }, 'savePage response')
820
+ }
821
+
822
+ {
823
+ const res = await app.inject({
824
+ method: 'POST',
825
+ url: '/graphql',
826
+ body: {
827
+ query: `
828
+ query {
829
+ getPageByPageId(pageId: 1) {
830
+ pageId
831
+ title
832
+ }
833
+ }
834
+ `
835
+ }
836
+ })
837
+ equal(res.statusCode, 200, 'pages status code')
838
+ same(res.json(), {
839
+ data: {
840
+ getPageByPageId: {
841
+ pageId: 1,
842
+ title: 'Hello'
843
+ }
844
+ }
845
+ }, 'pages response')
846
+ }
847
+ })
848
+
849
+ test('deserialize JSON columns', { skip: isSQLite }, async (t) => {
850
+ const { pass, teardown, same } = t
851
+ const app = fastify()
852
+ const jsonData = {
853
+ foo: 'bar',
854
+ baz: 42,
855
+ items: ['foo', 'bar'],
856
+ nested: {
857
+ hello: 'world'
858
+ }
859
+ }
860
+ app.register(sqlMapper, {
861
+ ...connInfo,
862
+ async onDatabaseLoad (db, sql) {
863
+ pass('onDatabaseLoad called')
864
+
865
+ await clear(db, sql)
866
+
867
+ await db.query(sql`CREATE TABLE pages (
868
+ id SERIAL PRIMARY KEY,
869
+ title VARCHAR(255) NOT NULL,
870
+ metadata JSON NOT NULL
871
+ );`)
872
+
873
+ await db.query(sql`INSERT INTO pages (id, title, metadata) VALUES (1, 'Hello World', ${JSON.stringify(jsonData)})`)
874
+ }
875
+ })
876
+ app.register(sqlGraphQL, {
877
+ graphiql: false
878
+ })
879
+ teardown(app.close.bind(app))
880
+
881
+ await app.ready()
882
+ const res = await app.inject({
883
+ method: 'POST',
884
+ url: '/graphql',
885
+ body: {
886
+ query: `
887
+ query {
888
+ getPageById(id: 1) {
889
+ id
890
+ title
891
+ metadata
892
+ }
893
+ }
894
+ `
895
+ }
896
+ })
897
+ const json = res.json()
898
+ if (isMariaDB) {
899
+ same(json.data.getPageById.metadata, JSON.stringify(jsonData))
900
+ } else {
901
+ same(json.data.getPageById.metadata, jsonData)
902
+ }
903
+ })
@@ -5,7 +5,6 @@ import plugin, { SQLGraphQLPluginOptions } from '../../index'
5
5
 
6
6
  const pluginOptions: SQLGraphQLPluginOptions = {
7
7
  graphiql: true,
8
- autoTimestamp: true,
9
8
  federationMetadata: true,
10
9
  resolvers: {
11
10
  Mutation: {