@platformatic/sql-graphql 0.0.23

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 (54) hide show
  1. package/.nyc_output/0681b2b7-587a-40f2-8242-257bc4c72456.json +1 -0
  2. package/.nyc_output/191054d4-6a1e-4a1b-87bb-f801ed78a223.json +1 -0
  3. package/.nyc_output/22ead0f6-e5a6-4647-a2ea-db2f42498081.json +1 -0
  4. package/.nyc_output/4e847e26-05cd-4063-9277-48aff86d6822.json +1 -0
  5. package/.nyc_output/5acfd416-02d1-497b-8dbd-69c0af167c62.json +1 -0
  6. package/.nyc_output/5ec04f76-9bcb-475d-b79e-f9304cc0026c.json +1 -0
  7. package/.nyc_output/5f3bb47a-0d2d-440a-a466-8ffa9ab3efaa.json +1 -0
  8. package/.nyc_output/71c2bd84-9a72-4e86-83c6-d587ce525fe4.json +1 -0
  9. package/.nyc_output/74cc1dab-7479-48e7-80b0-c97351d8856e.json +1 -0
  10. package/.nyc_output/9f5d12ba-bc3d-461c-a0e2-2a95d2020c19.json +1 -0
  11. package/.nyc_output/ac343b71-6f76-421c-a287-46d19bb48f89.json +1 -0
  12. package/.nyc_output/c1c0eecb-63a0-4258-81b6-50e7488bd67c.json +1 -0
  13. package/.nyc_output/c201a1cd-3105-4d18-bbbb-8d7fa674d439.json +1 -0
  14. package/.nyc_output/d6da0270-e9ed-41e2-bbf3-9262d1df683c.json +1 -0
  15. package/.nyc_output/processinfo/0681b2b7-587a-40f2-8242-257bc4c72456.json +1 -0
  16. package/.nyc_output/processinfo/191054d4-6a1e-4a1b-87bb-f801ed78a223.json +1 -0
  17. package/.nyc_output/processinfo/22ead0f6-e5a6-4647-a2ea-db2f42498081.json +1 -0
  18. package/.nyc_output/processinfo/4e847e26-05cd-4063-9277-48aff86d6822.json +1 -0
  19. package/.nyc_output/processinfo/5acfd416-02d1-497b-8dbd-69c0af167c62.json +1 -0
  20. package/.nyc_output/processinfo/5ec04f76-9bcb-475d-b79e-f9304cc0026c.json +1 -0
  21. package/.nyc_output/processinfo/5f3bb47a-0d2d-440a-a466-8ffa9ab3efaa.json +1 -0
  22. package/.nyc_output/processinfo/71c2bd84-9a72-4e86-83c6-d587ce525fe4.json +1 -0
  23. package/.nyc_output/processinfo/74cc1dab-7479-48e7-80b0-c97351d8856e.json +1 -0
  24. package/.nyc_output/processinfo/9f5d12ba-bc3d-461c-a0e2-2a95d2020c19.json +1 -0
  25. package/.nyc_output/processinfo/ac343b71-6f76-421c-a287-46d19bb48f89.json +1 -0
  26. package/.nyc_output/processinfo/c1c0eecb-63a0-4258-81b6-50e7488bd67c.json +1 -0
  27. package/.nyc_output/processinfo/c201a1cd-3105-4d18-bbbb-8d7fa674d439.json +1 -0
  28. package/.nyc_output/processinfo/d6da0270-e9ed-41e2-bbf3-9262d1df683c.json +1 -0
  29. package/.nyc_output/processinfo/index.json +1 -0
  30. package/.taprc +1 -0
  31. package/LICENSE +201 -0
  32. package/NOTICE +13 -0
  33. package/README.md +13 -0
  34. package/index.d.ts +40 -0
  35. package/index.js +117 -0
  36. package/lib/entity-to-type.js +248 -0
  37. package/lib/relationship.js +67 -0
  38. package/lib/utils.js +77 -0
  39. package/package.json +44 -0
  40. package/test/datatypes.test.js +708 -0
  41. package/test/federation.test.js +312 -0
  42. package/test/helper.js +70 -0
  43. package/test/hooks.test.js +660 -0
  44. package/test/ignore.test.js +117 -0
  45. package/test/insert.test.js +181 -0
  46. package/test/inserted_updated_at.test.js +517 -0
  47. package/test/nested.test.js +862 -0
  48. package/test/no-modification-of-entity.test.js +113 -0
  49. package/test/order_by.test.js +397 -0
  50. package/test/relations.test.js +60 -0
  51. package/test/simple.test.js +708 -0
  52. package/test/sqlite.test.js +276 -0
  53. package/test/types/index.test-d.ts +33 -0
  54. package/test/where.test.js +814 -0
@@ -0,0 +1,248 @@
1
+ 'use strict'
2
+
3
+ const graphql = require('graphql')
4
+ const camelcase = require('camelcase')
5
+ const {
6
+ sqlTypeToGraphQL,
7
+ fromSelectionSet
8
+ } = require('./utils')
9
+
10
+ const ascDesc = new graphql.GraphQLEnumType({
11
+ name: 'OrderByDirection',
12
+ values: {
13
+ ASC: { value: 'ASC' },
14
+ DESC: { value: 'DESC' }
15
+ }
16
+ })
17
+
18
+ function constructGraph (app, entity, opts) {
19
+ const primaryKey = entity.primaryKey
20
+
21
+ const entityName = entity.name
22
+ const singular = entity.singularName
23
+ const plural = entity.pluralName
24
+
25
+ const {
26
+ queryTopFields,
27
+ mutationTopFields,
28
+ resolvers,
29
+ federationReplacements,
30
+ federationMetadata,
31
+ loaders
32
+ } = opts
33
+
34
+ const fields = {}
35
+
36
+ for (const key of Object.keys(entity.fields)) {
37
+ const field = entity.fields[key]
38
+ const meta = { field }
39
+ meta.type = sqlTypeToGraphQL(field.sqlType)
40
+ if (field.primaryKey) {
41
+ meta.primaryKeyType = field.type
42
+ meta.type = graphql.GraphQLID
43
+ } else if (field.foreignKey) {
44
+ meta.type = graphql.GraphQLID
45
+ }
46
+ fields[field.camelcase] = meta
47
+ }
48
+
49
+ const type = new graphql.GraphQLObjectType({
50
+ name: entityName,
51
+ fields
52
+ })
53
+
54
+ resolvers.Query = resolvers.Query || {}
55
+ resolvers.Mutation = resolvers.Mutation || {}
56
+ loaders.Query = loaders.Query || {}
57
+
58
+ const getBy = camelcase(['get', singular, 'by', primaryKey])
59
+
60
+ const whereArgType = new graphql.GraphQLInputObjectType({
61
+ name: `${entityName}WhereArguments`,
62
+ fields: Object.keys(fields).reduce((acc, field) => {
63
+ acc[field] = {
64
+ type: new graphql.GraphQLInputObjectType({
65
+ name: `${entityName}WhereArguments${field}`,
66
+ fields: {
67
+ eq: { type: fields[field].type },
68
+ neq: { type: fields[field].type },
69
+ gt: { type: fields[field].type },
70
+ gte: { type: fields[field].type },
71
+ lt: { type: fields[field].type },
72
+ lte: { type: fields[field].type },
73
+ in: { type: new graphql.GraphQLList(fields[field].type) },
74
+ nin: { type: new graphql.GraphQLList(fields[field].type) }
75
+ }
76
+ })
77
+ }
78
+ return acc
79
+ }, {})
80
+ })
81
+
82
+ queryTopFields[getBy] = {
83
+ type,
84
+ args: {
85
+ [primaryKey]: { type: new graphql.GraphQLNonNull(fields[primaryKey].type) }
86
+ }
87
+ }
88
+ loaders.Query[getBy] = {
89
+ loader (queries, ctx) {
90
+ const keys = []
91
+ for (const query of queries) {
92
+ keys.push(query.params[primaryKey])
93
+ }
94
+ return loadMany(keys, queries, ctx)
95
+ },
96
+ opts: {
97
+ cache: false
98
+ }
99
+ }
100
+
101
+ const orderByFields = new graphql.GraphQLEnumType({
102
+ name: `${entityName}OrderByField`,
103
+ values: Object.keys(fields).reduce((acc, field) => {
104
+ acc[field] = {
105
+ value: field
106
+ }
107
+ return acc
108
+ }, {})
109
+ })
110
+ queryTopFields[plural] = {
111
+ type: new graphql.GraphQLList(type),
112
+ args: {
113
+ limit: { type: graphql.GraphQLInt },
114
+ offset: { type: graphql.GraphQLInt },
115
+ orderBy: {
116
+ type: new graphql.GraphQLList(new graphql.GraphQLInputObjectType({
117
+ name: `${entityName}OrderByArguments`,
118
+ fields: {
119
+ field: { type: orderByFields },
120
+ direction: { type: new graphql.GraphQLNonNull(ascDesc) }
121
+ }
122
+ }))
123
+ },
124
+ where: { type: whereArgType }
125
+ }
126
+ }
127
+
128
+ resolvers.Query[plural] = (_, query, ctx, info) => {
129
+ const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
130
+ requestedFields.push(primaryKey)
131
+ return entity.find({ ...query, fields: requestedFields, ctx })
132
+ }
133
+
134
+ const inputType = new graphql.GraphQLInputObjectType({
135
+ name: `${entityName}Input`,
136
+ fields: Object.keys(fields).reduce((acc, field) => {
137
+ const meta = fields[field]
138
+ const actualField = meta.field
139
+ if (!actualField.autoTimestamp) {
140
+ acc[field] = meta
141
+ }
142
+ return acc
143
+ }, {})
144
+ })
145
+
146
+ const save = camelcase(['save', singular])
147
+
148
+ mutationTopFields[save] = {
149
+ type,
150
+ args: {
151
+ input: { type: new graphql.GraphQLNonNull(inputType) }
152
+ }
153
+ }
154
+
155
+ resolvers.Mutation[save] = async (_, { input }, ctx, info) => {
156
+ const fields = fromSelectionSet(info.fieldNodes[0].selectionSet)
157
+ return entity.save({ input, ctx, fields: [...fields] })
158
+ }
159
+
160
+ const insert = camelcase(['insert', plural])
161
+
162
+ mutationTopFields[insert] = {
163
+ type: new graphql.GraphQLList(type),
164
+ args: {
165
+ inputs: { type: new graphql.GraphQLNonNull(new graphql.GraphQLList(inputType)) }
166
+ }
167
+ }
168
+
169
+ resolvers.Mutation[insert] = (_, { inputs }, ctx, info) => {
170
+ const fields = fromSelectionSet(info.fieldNodes[0].selectionSet)
171
+ return entity.insert({ inputs, ctx, fields: [...fields] })
172
+ }
173
+
174
+ const deleteKey = camelcase(['delete', plural])
175
+ mutationTopFields[deleteKey] = {
176
+ type: new graphql.GraphQLList(type),
177
+ args: {
178
+ where: { type: whereArgType }
179
+ }
180
+ }
181
+
182
+ resolvers.Mutation[deleteKey] = (_, args, ctx, info) => {
183
+ const fields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
184
+ return entity.delete({ ...args, fields, ctx })
185
+ }
186
+
187
+ federationReplacements.push({
188
+ find: new RegExp(`type ${entityName}`),
189
+ replace: `type ${entityName} @key(fields: "${primaryKey}")`
190
+ })
191
+
192
+ if (federationMetadata) {
193
+ loaders[entityName] = loaders[entityName] || {}
194
+ loaders[entityName].__resolveReference = {
195
+ loader (queries, ctx) {
196
+ const keys = queries.map(({ obj }) => obj[primaryKey])
197
+ return loadMany(keys, queries, ctx)
198
+ },
199
+ opts: {
200
+ cache: false
201
+ }
202
+ }
203
+ }
204
+
205
+ return {
206
+ type,
207
+ entity,
208
+ loadMany,
209
+ fields
210
+ }
211
+
212
+ async function loadMany (keys, queries, ctx) {
213
+ const fields = getFields(queries)
214
+ const res = await entity.find({
215
+ where: {
216
+ [primaryKey]: {
217
+ in: keys
218
+ }
219
+ },
220
+ fields,
221
+ ctx
222
+ })
223
+
224
+ const map = {}
225
+
226
+ for (const row of res) {
227
+ map[row[primaryKey]] = row
228
+ }
229
+
230
+ const output = []
231
+ for (const key of keys) {
232
+ output.push(map[key])
233
+ }
234
+
235
+ return output
236
+ }
237
+
238
+ function getFields (queries) {
239
+ const fields = new Set()
240
+ fields.add(primaryKey)
241
+ for (const query of queries) {
242
+ fromSelectionSet(query.info.fieldNodes[0].selectionSet, fields)
243
+ }
244
+ return [...fields]
245
+ }
246
+ }
247
+
248
+ module.exports = constructGraph
@@ -0,0 +1,67 @@
1
+ 'use strict'
2
+
3
+ const camelcase = require('camelcase')
4
+ const {
5
+ fromSelectionSet
6
+ } = require('./utils')
7
+ const assert = require('assert')
8
+
9
+ module.exports = function establishRelations (app, relations, resolvers, loaders, queryTopFields, relationships, metaMap) {
10
+ const tablesTypeMap = {}
11
+ const entities = app.platformatic.entities
12
+ for (const key of Object.keys(entities)) {
13
+ const entity = entities[key]
14
+ tablesTypeMap[entity.table] = metaMap.get(entity)
15
+ }
16
+ for (const relation of relations) {
17
+ assert(relation.table_name, 'table_name is required')
18
+ assert(relation.foreign_table_name, 'foreign_table_name is required')
19
+
20
+ const current = tablesTypeMap[relation.table_name]
21
+ const foreign = tablesTypeMap[relation.foreign_table_name]
22
+ assert(foreign !== undefined, `No foreign table named "${relation.foreign_table_name}" was found`)
23
+
24
+ // current to foreign
25
+ {
26
+ const lowered = lowerCaseFirst(foreign.type)
27
+ if (!relationships[current.type] || relationships[current.type][lowered] !== false) {
28
+ current.fields[lowered] = { type: foreign.type }
29
+ const originalField = camelcase(relation.column_name)
30
+ delete current.fields[originalField]
31
+ loaders[current.type] = resolvers[current.type] || {}
32
+ loaders[current.type][lowered] = {
33
+ loader (queries, ctx) {
34
+ const keys = queries.map(({ obj }) => {
35
+ return obj[originalField]
36
+ })
37
+ return foreign.loadMany(keys, queries, ctx)
38
+ },
39
+ opts: {
40
+ cache: false
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ // foreign to current
47
+ {
48
+ const lowered = lowerCaseFirst(camelcase(current.entity.table))
49
+ if (!relationships[foreign.type] || relationships[foreign.type][lowered] !== false) {
50
+ foreign.fields[lowered] = queryTopFields[lowered]
51
+ resolvers[foreign.type] = resolvers[foreign.type] || {}
52
+ resolvers[foreign.type][lowered] = async function (obj, args, ctx, info) {
53
+ const fields = fromSelectionSet(info.fieldNodes[0].selectionSet, new Set())
54
+ const toSearch = { ...args, fields: [...fields], ctx }
55
+ toSearch.where = toSearch.where || {}
56
+ toSearch.where[camelcase(relation.column_name)] = { eq: obj.id }
57
+ return current.entity.find(toSearch)
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ function lowerCaseFirst (str) {
65
+ str = str.toString()
66
+ return str.charAt(0).toLowerCase() + str.slice(1)
67
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,77 @@
1
+ 'use strict'
2
+
3
+ const graphql = require('graphql')
4
+ const scalars = require('graphql-scalars')
5
+
6
+ // The sqlTypeToGraphQL is shared between
7
+ // all database adapters.
8
+ function sqlTypeToGraphQL (sqlType) {
9
+ // TODO support more types
10
+ /* istanbul ignore next */
11
+ switch (sqlType) {
12
+ case 'int':
13
+ return graphql.GraphQLInt
14
+ case 'integer':
15
+ return graphql.GraphQLInt
16
+ case 'tinyint':
17
+ return graphql.GraphQLBoolean
18
+ case 'smallint':
19
+ return graphql.GraphQLInt
20
+ case 'decimal':
21
+ return graphql.GraphQLInt
22
+ case 'bigint':
23
+ return graphql.GraphQLInt
24
+ case 'int2':
25
+ return graphql.GraphQLInt
26
+ case 'int4':
27
+ return graphql.GraphQLInt
28
+ case 'varchar':
29
+ return graphql.GraphQLString
30
+ case 'text':
31
+ return graphql.GraphQLString
32
+ case 'bool':
33
+ return graphql.GraphQLBoolean
34
+ case 'real':
35
+ return graphql.GraphQLFloat
36
+ case 'float8':
37
+ return graphql.GraphQLFloat
38
+ case 'double':
39
+ return graphql.GraphQLFloat
40
+ case 'double precision':
41
+ return graphql.GraphQLFloat
42
+ case 'numeric':
43
+ return graphql.GraphQLFloat
44
+ case 'float4':
45
+ return graphql.GraphQLFloat
46
+ case 'date':
47
+ return scalars.GraphQLDate
48
+ case 'time':
49
+ return graphql.GraphQLString
50
+ case 'timestamp':
51
+ return scalars.GraphQLDateTime
52
+ case 'uuid':
53
+ return graphql.GraphQLString
54
+ default:
55
+ return graphql.GraphQLString
56
+ }
57
+ }
58
+
59
+ function fromSelectionSet (selectionSet, fields = new Set()) {
60
+ /* istanbul ignore next */
61
+ for (const s of selectionSet.selections) {
62
+ if (s.kind === 'Field') {
63
+ fields.add(s.name.value)
64
+ } else if (s.kind === 'InlineFragment') {
65
+ fromSelectionSet(s.selectionSet, fields)
66
+ } else {
67
+ throw new Error('Unsupported kind: ' + s.kind)
68
+ }
69
+ }
70
+ return fields
71
+ }
72
+
73
+ module.exports = {
74
+ sqlTypeToGraphQL,
75
+ fromSelectionSet,
76
+ typeSym: Symbol('graphlType')
77
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@platformatic/sql-graphql",
3
+ "version": "0.0.23",
4
+ "description": "Map SQL dbs to GraphQL",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/plaformatic/platformatic.git"
9
+ },
10
+ "author": "Matteo Collina <hello@matteocollina.com>",
11
+ "license": "Apache-2.0",
12
+ "bugs": {
13
+ "url": "https://github.com/plaformatic/platformatic/issues"
14
+ },
15
+ "homepage": "https://github.com/plaformatic/platformatic#readme",
16
+ "devDependencies": {
17
+ "@platformatic/sql-mapper": "0.0.23",
18
+ "fastify": "^4.6.0",
19
+ "snazzy": "^9.0.0",
20
+ "standard": "^17.0.0",
21
+ "tap": "^16.0.0",
22
+ "tsd": "^0.24.0"
23
+ },
24
+ "dependencies": {
25
+ "camelcase": "^6.0.0",
26
+ "fastify-plugin": "^4.1.0",
27
+ "graphql": "^16.6.0",
28
+ "graphql-scalars": "^1.13.1",
29
+ "inflected": "^2.1.0",
30
+ "mercurius": "^11.0.0"
31
+ },
32
+ "tsd": {
33
+ "directory": "test/types"
34
+ },
35
+ "scripts": {
36
+ "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
+ "test:postgresql": "DB=postgresql tap test/*.test.js",
38
+ "test:mariadb": "DB=mariadb tap test/*.test.js",
39
+ "test:mysql": "DB=mysql tap test/*.test.js",
40
+ "test:mysql8": "DB=mysql8 tap test/*.test.js",
41
+ "test:sqlite": "DB=sqlite tap test/*.test.js",
42
+ "test:typescript": "tsd"
43
+ }
44
+ }