@platformatic/sql-graphql 3.0.0-alpha.4 → 3.0.0-alpha.6

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/eslint.config.js CHANGED
@@ -1,3 +1,3 @@
1
- 'use strict'
1
+ import neostandard from 'neostandard'
2
2
 
3
- module.exports = require('neostandard')({ ts: true })
3
+ export default neostandard({ ts: true })
package/index.js CHANGED
@@ -1,16 +1,14 @@
1
- 'use strict'
2
-
3
- const fp = require('fastify-plugin')
4
- const constructGraph = require('./lib/entity-to-type')
5
- const mercurius = require('mercurius')
6
- const graphql = require('graphql')
7
- const { mercuriusFederationPlugin } = require('@mercuriusjs/federation')
8
- const { findNearestString } = require('@platformatic/foundation')
9
- const establishRelations = require('./lib/relationship')
10
- const setupSubscriptions = require('./lib/subscriptions')
11
- const setupTelemetry = require('./lib/telemetry')
12
- const scalars = require('graphql-scalars')
13
- const errors = require('./lib/errors')
1
+ import { mercuriusFederationPlugin } from '@mercuriusjs/federation'
2
+ import { findNearestString } from '@platformatic/foundation'
3
+ import fp from 'fastify-plugin'
4
+ import { GraphQLObjectType, GraphQLSchema, GraphQLString, printSchema } from 'graphql'
5
+ import { GraphQLDate, GraphQLDateTime } from 'graphql-scalars'
6
+ import mercurius from 'mercurius'
7
+ import { constructGraph } from './lib/entity-to-type.js'
8
+ import { ErrorPrintingGraphQLSchema } from './lib/errors.js'
9
+ import { establishRelations } from './lib/relationship.js'
10
+ import { setupSubscriptions } from './lib/subscriptions.js'
11
+ import { setupTelemetry } from './lib/telemetry.js'
14
12
 
15
13
  async function mapperToGraphql (app, opts) {
16
14
  const mapper = app.platformatic
@@ -28,19 +26,18 @@ async function mapperToGraphql (app, opts) {
28
26
  resolvers,
29
27
  loaders,
30
28
  federationReplacements,
31
- federationMetadata: opts.federationMetadata,
29
+ federationMetadata: opts.federationMetadata
32
30
  }
33
31
  const metaMap = new Map()
34
32
 
35
33
  if (Object.keys(mapper.entities).length === 0) {
36
34
  // no schema
37
- queryTopFields.hello = { type: graphql.GraphQLString }
35
+ queryTopFields.hello = { type: GraphQLString }
38
36
  resolvers.Query = {
39
- hello: () => 'Hello Platformatic!',
37
+ hello: () => 'Hello Platformatic!'
40
38
  }
41
39
  } else {
42
- const entitiesNames = Object.values(mapper.entities)
43
- .map(entity => entity.singularName)
40
+ const entitiesNames = Object.values(mapper.entities).map(entity => entity.singularName)
44
41
 
45
42
  for (const ignoredEntity of Object.keys(ignore)) {
46
43
  if (!entitiesNames.includes(ignoredEntity)) {
@@ -94,17 +91,18 @@ async function mapperToGraphql (app, opts) {
94
91
  }
95
92
  }
96
93
 
97
- const query = new graphql.GraphQLObjectType({
94
+ const query = new GraphQLObjectType({
98
95
  name: 'Query',
99
- fields: queryTopFields,
96
+ fields: queryTopFields
100
97
  })
101
98
 
102
- const mutation = Object.keys(mutationTopFields).length > 0
103
- ? new graphql.GraphQLObjectType({
104
- name: 'Mutation',
105
- fields: mutationTopFields,
106
- })
107
- : null
99
+ const mutation =
100
+ Object.keys(mutationTopFields).length > 0
101
+ ? new GraphQLObjectType({
102
+ name: 'Mutation',
103
+ fields: mutationTopFields
104
+ })
105
+ : null
108
106
 
109
107
  if (!mutation) {
110
108
  delete resolvers.Mutation
@@ -130,7 +128,7 @@ async function mapperToGraphql (app, opts) {
130
128
 
131
129
  if (entitiesList.length > 0) {
132
130
  opts.subscription = {
133
- emitter: app.platformatic.mq,
131
+ emitter: app.platformatic.mq
134
132
  }
135
133
  // TODO support ignoring some of those
136
134
  subscription = setupSubscriptions(app, metaMap, resolvers, ignoreList)
@@ -139,14 +137,14 @@ async function mapperToGraphql (app, opts) {
139
137
 
140
138
  let sdl = ''
141
139
  try {
142
- sdl = graphql.printSchema(new graphql.GraphQLSchema({ query, mutation, subscription }))
140
+ sdl = printSchema(new GraphQLSchema({ query, mutation, subscription }))
143
141
  } catch (error) {
144
142
  // The next lines are excluded from test coverage:
145
143
  // it's quite hard to test the following lines for all of the DB types
146
144
  /* istanbul ignore next */
147
145
  app.log.debug({ query, mutation, subscription }, 'GraphQL input schema')
148
146
  /* istanbul ignore next */
149
- const newError = new errors.ErrorPrintingGraphQLSchema()
147
+ const newError = new ErrorPrintingGraphQLSchema()
150
148
  /* istanbul ignore next */
151
149
  newError.cause = error
152
150
  /* istanbul ignore next */
@@ -167,10 +165,10 @@ async function mapperToGraphql (app, opts) {
167
165
  // Ignoriring because SQLite doesn't support dates
168
166
  /* istanbul ignore next */
169
167
  if (sdl.match(/scalar Date\n/)) {
170
- resolvers.Date = scalars.GraphQLDate
168
+ resolvers.Date = GraphQLDate
171
169
  }
172
170
  if (sdl.indexOf('scalar DateTime') >= 0) {
173
- resolvers.DateTime = scalars.GraphQLDateTime
171
+ resolvers.DateTime = GraphQLDateTime
174
172
  }
175
173
 
176
174
  opts.graphiql = opts.graphiql !== false
@@ -184,7 +182,7 @@ async function mapperToGraphql (app, opts) {
184
182
  ...opts,
185
183
  schema: sdl,
186
184
  loaders,
187
- resolvers,
185
+ resolvers
188
186
  })
189
187
 
190
188
  if (app.openTelemetry) {
@@ -194,5 +192,5 @@ async function mapperToGraphql (app, opts) {
194
192
  app.log.debug({ schema: sdl }, 'computed schema')
195
193
  }
196
194
 
197
- module.exports = fp(mapperToGraphql)
198
- module.exports.errors = errors
195
+ export default fp(mapperToGraphql)
196
+ export * as errors from './lib/errors.js'
@@ -1,52 +1,48 @@
1
- 'use strict'
2
-
3
- const graphql = require('graphql')
4
- const camelcase = require('camelcase')
5
- const { findNearestString } = require('@platformatic/foundation')
6
- const {
7
- sqlTypeToGraphQL,
8
- fromSelectionSet,
9
- } = require('./utils')
10
- const errors = require('./errors')
11
-
12
- const ascDesc = new graphql.GraphQLEnumType({
1
+ import { findNearestString } from '@platformatic/foundation'
2
+ import camelcase from 'camelcase'
3
+ import {
4
+ GraphQLEnumType,
5
+ GraphQLID,
6
+ GraphQLInputObjectType,
7
+ GraphQLInt,
8
+ GraphQLList,
9
+ GraphQLNonNull,
10
+ GraphQLObjectType,
11
+ GraphQLScalarType
12
+ } from 'graphql'
13
+ import { UnableToGenerateGraphQLEnumTypeError } from './errors.js'
14
+ import { fromSelectionSet, sqlTypeToGraphQL } from './utils.js'
15
+
16
+ const ascDesc = new GraphQLEnumType({
13
17
  name: 'OrderByDirection',
14
18
  values: {
15
19
  ASC: { value: 'ASC' },
16
- DESC: { value: 'DESC' },
17
- },
20
+ DESC: { value: 'DESC' }
21
+ }
18
22
  })
19
23
 
20
- const limitType = new graphql.GraphQLScalarType({
24
+ const limitType = new GraphQLScalarType({
21
25
  name: 'LimitInt',
22
- description: 'Limit will be applied by default if not passed. If the provided value exceeds the maximum allowed value a validation error will be thrown',
26
+ description:
27
+ 'Limit will be applied by default if not passed. If the provided value exceeds the maximum allowed value a validation error will be thrown'
23
28
  })
24
29
 
25
- function constructGraph (app, entity, opts, ignore) {
26
- const primaryKeys = Array.from(entity.primaryKeys).map((key) => camelcase(key))
27
- const relationalFields = entity.relations
28
- .map((relation) => relation.column_name)
30
+ export function constructGraph (app, entity, opts, ignore) {
31
+ const primaryKeys = Array.from(entity.primaryKeys).map(key => camelcase(key))
32
+ const relationalFields = entity.relations.map(relation => relation.column_name)
29
33
  const entityName = entity.name
30
34
  const singular = entity.singularName
31
35
  const plural = entity.pluralName
32
- const {
33
- queryTopFields,
34
- mutationTopFields,
35
- resolvers,
36
- federationReplacements,
37
- federationMetadata,
38
- loaders,
39
- } = opts
40
-
41
- const entityFieldsNames = Object.values(entity.fields)
42
- .map(field => field.camelcase)
36
+ const { queryTopFields, mutationTopFields, resolvers, federationReplacements, federationMetadata, loaders } = opts
37
+
38
+ const entityFieldsNames = Object.values(entity.fields).map(field => field.camelcase)
43
39
 
44
40
  for (const ignoredField of Object.keys(ignore)) {
45
41
  if (!entityFieldsNames.includes(ignoredField)) {
46
42
  const nearestField = findNearestString(entityFieldsNames, ignoredField)
47
43
  app.log.warn(
48
- `Ignored graphql field "${ignoredField}" not found in entity "${entity.singularName}".` +
49
- ` Did you mean "${nearestField}"?`
44
+ `Ignored graphql field "${ignoredField}" not found in entity "${entity.singularName}".` +
45
+ ` Did you mean "${nearestField}"?`
50
46
  )
51
47
  }
52
48
  }
@@ -66,7 +62,7 @@ function constructGraph (app, entity, opts, ignore) {
66
62
  /* istanbul ignore next */
67
63
  if (field.isArray) {
68
64
  const listType = sqlTypeToGraphQL(field.sqlType)
69
- meta.type = new graphql.GraphQLList(listType)
65
+ meta.type = new GraphQLList(listType)
70
66
  } else if (field.enum) {
71
67
  const enumValues = field.enum.reduce((acc, enumValue, index) => {
72
68
  let key = enumValue.replace(/[^\w]/g, '_')
@@ -85,38 +81,46 @@ function constructGraph (app, entity, opts, ignore) {
85
81
  }, {})
86
82
  try {
87
83
  const name = camelcase([entityName, key])
88
- meta.type = new graphql.GraphQLEnumType({ name, values: enumValues })
84
+ meta.type = new GraphQLEnumType({ name, values: enumValues })
89
85
  } catch (error) {
90
86
  app.log.error({ key, enumValues, entityName, table: entity.table, schema: entity.schema })
91
- throw new errors.UnableToGenerateGraphQLEnumTypeError()
87
+ throw new UnableToGenerateGraphQLEnumTypeError()
92
88
  }
93
89
  } else {
94
90
  meta.type = sqlTypeToGraphQL(field.sqlType)
95
91
  }
96
92
  if (field.primaryKey) {
97
93
  meta.primaryKeyType = field.type
98
- meta.type = graphql.GraphQLID
94
+ meta.type = GraphQLID
99
95
  } else if (field.foreignKey) {
100
- meta.type = graphql.GraphQLID
96
+ meta.type = GraphQLID
101
97
  }
102
98
  fields[field.camelcase] = meta
103
99
  }
104
100
 
105
- const type = new graphql.GraphQLObjectType({
101
+ const type = new GraphQLObjectType({
106
102
  name: entityName,
107
- fields,
103
+ fields
108
104
  })
109
105
 
110
106
  resolvers.Query = resolvers.Query || {}
111
107
  resolvers.Mutation = resolvers.Mutation || {}
112
108
  loaders.Query = loaders.Query || {}
113
109
 
114
- const getBy = camelcase(['get', singular, ...(primaryKeys.map((key, i) => {
115
- if (i === 0) {
116
- return ['by', key]
117
- }
118
- return ['and', key]
119
- }).flat())].join('_'))
110
+ const getBy = camelcase(
111
+ [
112
+ 'get',
113
+ singular,
114
+ ...primaryKeys
115
+ .map((key, i) => {
116
+ if (i === 0) {
117
+ return ['by', key]
118
+ }
119
+ return ['and', key]
120
+ })
121
+ .flat()
122
+ ].join('_')
123
+ )
120
124
 
121
125
  const whereFields = Object.keys(fields).reduce((acc, field) => {
122
126
  let graphqlFields
@@ -130,50 +134,52 @@ function constructGraph (app, entity, opts, ignore) {
130
134
  lt: { type: fields[field].type },
131
135
  lte: { type: fields[field].type },
132
136
  like: { type: fields[field].type },
133
- in: { type: new graphql.GraphQLList(fields[field].type) },
134
- nin: { type: new graphql.GraphQLList(fields[field].type) },
137
+ in: { type: new GraphQLList(fields[field].type) },
138
+ nin: { type: new GraphQLList(fields[field].type) }
135
139
  }
136
140
  } else {
137
141
  graphqlFields = {
138
142
  any: { type: fields[field].type.ofType },
139
143
  all: { type: fields[field].type.ofType },
140
- contains: { type: new graphql.GraphQLList(fields[field].type) },
141
- contained: { type: new graphql.GraphQLList(fields[field].type) },
142
- overlaps: { type: new graphql.GraphQLList(fields[field].type) },
144
+ contains: { type: new GraphQLList(fields[field].type) },
145
+ contained: { type: new GraphQLList(fields[field].type) },
146
+ overlaps: { type: new GraphQLList(fields[field].type) }
143
147
  }
144
148
  }
145
149
  acc[field] = {
146
- type: new graphql.GraphQLInputObjectType({
150
+ type: new GraphQLInputObjectType({
147
151
  name: `${entityName}WhereArguments${field}`,
148
- fields: graphqlFields,
149
- }),
152
+ fields: graphqlFields
153
+ })
150
154
  }
151
155
  return acc
152
156
  }, {})
153
157
 
154
- const whereArgType = new graphql.GraphQLInputObjectType({
158
+ const whereArgType = new GraphQLInputObjectType({
155
159
  name: `${entityName}WhereArguments`,
156
160
  fields: {
157
161
  ...whereFields,
158
162
  or: {
159
- type: new graphql.GraphQLList(new graphql.GraphQLInputObjectType({
160
- name: `${entityName}WhereArgumentsOr`,
161
- fields: {
162
- ...whereFields,
163
- },
164
- })),
165
- },
166
- },
163
+ type: new GraphQLList(
164
+ new GraphQLInputObjectType({
165
+ name: `${entityName}WhereArgumentsOr`,
166
+ fields: {
167
+ ...whereFields
168
+ }
169
+ })
170
+ )
171
+ }
172
+ }
167
173
  })
168
174
 
169
175
  queryTopFields[getBy] = {
170
176
  type,
171
177
  args: {
172
- ...(primaryKeys.reduce((acc, primaryKey) => {
173
- acc[primaryKey] = { type: new graphql.GraphQLNonNull(fields[primaryKey].type) }
178
+ ...primaryKeys.reduce((acc, primaryKey) => {
179
+ acc[primaryKey] = { type: new GraphQLNonNull(fields[primaryKey].type) }
174
180
  return acc
175
- }, {})),
176
- },
181
+ }, {})
182
+ }
177
183
  }
178
184
  loaders.Query[getBy] = {
179
185
  loader (queries, ctx) {
@@ -188,49 +194,51 @@ function constructGraph (app, entity, opts, ignore) {
188
194
  return loadMany(keys, queries, ctx)
189
195
  },
190
196
  opts: {
191
- cache: false,
192
- },
197
+ cache: false
198
+ }
193
199
  }
194
200
 
195
- const orderByFields = new graphql.GraphQLEnumType({
201
+ const orderByFields = new GraphQLEnumType({
196
202
  name: `${entityName}OrderByField`,
197
203
  values: Object.keys(fields).reduce((acc, field) => {
198
204
  /* istanbul ignore else */
199
205
  if (!fields[field].isArray) {
200
206
  acc[field] = {
201
- value: field,
207
+ value: field
202
208
  }
203
209
  }
204
210
  return acc
205
- }, {}),
211
+ }, {})
206
212
  })
207
213
  queryTopFields[plural] = {
208
- type: new graphql.GraphQLList(type),
214
+ type: new GraphQLList(type),
209
215
  args: {
210
216
  limit: { type: limitType },
211
- offset: { type: graphql.GraphQLInt },
217
+ offset: { type: GraphQLInt },
212
218
  orderBy: {
213
- type: new graphql.GraphQLList(new graphql.GraphQLInputObjectType({
214
- name: `${entityName}OrderByArguments`,
215
- fields: {
216
- field: { type: orderByFields },
217
- direction: { type: new graphql.GraphQLNonNull(ascDesc) },
218
- },
219
- })),
219
+ type: new GraphQLList(
220
+ new GraphQLInputObjectType({
221
+ name: `${entityName}OrderByArguments`,
222
+ fields: {
223
+ field: { type: orderByFields },
224
+ direction: { type: new GraphQLNonNull(ascDesc) }
225
+ }
226
+ })
227
+ )
220
228
  },
221
- where: { type: whereArgType },
222
- },
229
+ where: { type: whereArgType }
230
+ }
223
231
  }
224
232
 
225
233
  resolvers.Query[plural] = (_, query, ctx, info) => {
226
- const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
234
+ const requestedFields = info.fieldNodes[0].selectionSet.selections.map(s => s.name.value)
227
235
  for (const primaryKey of primaryKeys) {
228
236
  requestedFields.push(primaryKey)
229
237
  }
230
238
  return entity.find({ ...query, fields: [...requestedFields, ...relationalFields], ctx })
231
239
  }
232
240
 
233
- const inputType = new graphql.GraphQLInputObjectType({
241
+ const inputType = new GraphQLInputObjectType({
234
242
  name: `${entityName}Input`,
235
243
  fields: Object.keys(fields).reduce((acc, field) => {
236
244
  const meta = fields[field]
@@ -239,27 +247,27 @@ function constructGraph (app, entity, opts, ignore) {
239
247
  acc[field] = meta
240
248
  }
241
249
  return acc
242
- }, {}),
250
+ }, {})
243
251
  })
244
252
 
245
253
  const count = camelcase(['count', plural])
246
254
 
247
- const countType = new graphql.GraphQLObjectType({
255
+ const countType = new GraphQLObjectType({
248
256
  name: `${plural}Count`,
249
257
  fields: {
250
- total: { type: graphql.GraphQLInt },
251
- },
258
+ total: { type: GraphQLInt }
259
+ }
252
260
  })
253
261
 
254
262
  queryTopFields[count] = {
255
263
  type: countType,
256
264
  args: {
257
- where: { type: whereArgType },
258
- },
265
+ where: { type: whereArgType }
266
+ }
259
267
  }
260
268
 
261
269
  resolvers.Query[count] = async (_, query, ctx, info) => {
262
- const requestedFields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
270
+ const requestedFields = info.fieldNodes[0].selectionSet.selections.map(s => s.name.value)
263
271
  for (const primaryKey of primaryKeys) {
264
272
  requestedFields.push(primaryKey)
265
273
  }
@@ -272,8 +280,8 @@ function constructGraph (app, entity, opts, ignore) {
272
280
  mutationTopFields[save] = {
273
281
  type,
274
282
  args: {
275
- input: { type: new graphql.GraphQLNonNull(inputType) },
276
- },
283
+ input: { type: new GraphQLNonNull(inputType) }
284
+ }
277
285
  }
278
286
 
279
287
  resolvers.Mutation[save] = async (_, { input }, ctx, info) => {
@@ -284,10 +292,10 @@ function constructGraph (app, entity, opts, ignore) {
284
292
  const insert = camelcase(['insert', plural])
285
293
 
286
294
  mutationTopFields[insert] = {
287
- type: new graphql.GraphQLList(type),
295
+ type: new GraphQLList(type),
288
296
  args: {
289
- inputs: { type: new graphql.GraphQLNonNull(new graphql.GraphQLList(inputType)) },
290
- },
297
+ inputs: { type: new GraphQLNonNull(new GraphQLList(inputType)) }
298
+ }
291
299
  }
292
300
 
293
301
  resolvers.Mutation[insert] = (_, { inputs }, ctx, info) => {
@@ -297,20 +305,20 @@ function constructGraph (app, entity, opts, ignore) {
297
305
 
298
306
  const deleteKey = camelcase(['delete', plural])
299
307
  mutationTopFields[deleteKey] = {
300
- type: new graphql.GraphQLList(type),
308
+ type: new GraphQLList(type),
301
309
  args: {
302
- where: { type: whereArgType },
303
- },
310
+ where: { type: whereArgType }
311
+ }
304
312
  }
305
313
 
306
314
  resolvers.Mutation[deleteKey] = (_, args, ctx, info) => {
307
- const fields = info.fieldNodes[0].selectionSet.selections.map((s) => s.name.value)
315
+ const fields = info.fieldNodes[0].selectionSet.selections.map(s => s.name.value)
308
316
  return entity.delete({ ...args, fields: [...fields, ...relationalFields], ctx })
309
317
  }
310
318
 
311
319
  federationReplacements.push({
312
320
  find: new RegExp(`type ${entityName}`),
313
- replace: `type ${entityName} @key(fields: "${primaryKeys}")`,
321
+ replace: `type ${entityName} @key(fields: "${primaryKeys}")`
314
322
  })
315
323
 
316
324
  if (federationMetadata) {
@@ -328,8 +336,8 @@ function constructGraph (app, entity, opts, ignore) {
328
336
  return loadMany(keys, queries, ctx)
329
337
  },
330
338
  opts: {
331
- cache: false,
332
- },
339
+ cache: false
340
+ }
333
341
  }
334
342
  }
335
343
 
@@ -339,7 +347,7 @@ function constructGraph (app, entity, opts, ignore) {
339
347
  loadMany,
340
348
  getFields,
341
349
  relationalFields,
342
- fields,
350
+ fields
343
351
  }
344
352
 
345
353
  async function loadMany (keys, queries, ctx) {
@@ -357,7 +365,7 @@ function constructGraph (app, entity, opts, ignore) {
357
365
  acc[key].in.push(value)
358
366
  } else {
359
367
  acc[key] = {
360
- in: [value],
368
+ in: [value]
361
369
  }
362
370
  }
363
371
  return acc
@@ -369,16 +377,14 @@ function constructGraph (app, entity, opts, ignore) {
369
377
  where,
370
378
  fields,
371
379
  limit,
372
- ctx,
380
+ ctx
373
381
  })
374
382
 
375
383
  const matchedRaws = []
376
384
  // TODO this is extremely inefficient
377
385
  // we need a better data structure
378
386
  for (const pair of keys) {
379
- const matchedRaw = rows.find((raw) =>
380
- pair.every(({ key, value }) => raw[key] === value)
381
- ) || null
387
+ const matchedRaw = rows.find(raw => pair.every(({ key, value }) => raw[key] === value)) || null
382
388
  matchedRaws.push(matchedRaw)
383
389
  }
384
390
 
@@ -397,5 +403,3 @@ function constructGraph (app, entity, opts, ignore) {
397
403
  return [...fields]
398
404
  }
399
405
  }
400
-
401
- module.exports = constructGraph
package/lib/errors.js CHANGED
@@ -1,11 +1,13 @@
1
- 'use strict'
1
+ import createError from '@fastify/error'
2
2
 
3
- const createError = require('@fastify/error')
3
+ export const ERROR_PREFIX = 'PLT_SQL_GRAPHQL'
4
4
 
5
- const ERROR_PREFIX = 'PLT_SQL_GRAPHQL'
6
-
7
- module.exports = {
8
- UnableToGenerateGraphQLEnumTypeError: createError(`${ERROR_PREFIX}_UNABLE_GENERATE_GRAPHQL_ENUM_TYPE`, 'Unable to generate GraphQLEnumType'),
9
- UnsupportedKindError: createError(`${ERROR_PREFIX}_UNSUPPORTED_KIND`, 'Unsupported kind: %s'),
10
- ErrorPrintingGraphQLSchema: createError(`${ERROR_PREFIX}_ERROR_PRINTING_GRAPHQL_SCHEMA`, 'Error printing the GraphQL schema'),
11
- }
5
+ export const UnableToGenerateGraphQLEnumTypeError = createError(
6
+ `${ERROR_PREFIX}_UNABLE_GENERATE_GRAPHQL_ENUM_TYPE`,
7
+ 'Unable to generate GraphQLEnumType'
8
+ )
9
+ export const UnsupportedKindError = createError(`${ERROR_PREFIX}_UNSUPPORTED_KIND`, 'Unsupported kind: %s')
10
+ export const ErrorPrintingGraphQLSchema = createError(
11
+ `${ERROR_PREFIX}_ERROR_PRINTING_GRAPHQL_SCHEMA`,
12
+ 'Error printing the GraphQL schema'
13
+ )
@@ -1,21 +1,25 @@
1
- 'use strict'
2
-
3
1
  /* eslint camelcase: 0 */
4
2
 
5
- const camelcase = require('camelcase')
6
- const {
7
- fromSelectionSet,
8
- } = require('./utils')
9
- const assert = require('assert')
3
+ import assert from 'assert'
4
+ import camelcase from 'camelcase'
5
+ import { fromSelectionSet } from './utils.js'
10
6
 
11
- module.exports = function establishRelations (app, relations, resolvers, loaders, queryTopFields, relationships, metaMap) {
7
+ export function establishRelations (app, relations, resolvers, loaders, queryTopFields, relationships, metaMap) {
12
8
  const entitiesTypeMap = {}
13
9
  const entities = app.platformatic.entities
14
10
  for (const key of Object.keys(entities)) {
15
11
  entitiesTypeMap[key] = metaMap.get(entities[key])
16
12
  }
17
13
  for (const relation of relations) {
18
- const { table_name, foreign_table_name, column_name, foreign_column_name, entityName, foreignEntityName, loweredTableWithSchemaName } = relation
14
+ const {
15
+ table_name,
16
+ foreign_table_name,
17
+ column_name,
18
+ foreign_column_name,
19
+ entityName,
20
+ foreignEntityName,
21
+ loweredTableWithSchemaName
22
+ } = relation
19
23
  const enhanceAssertLogMsg = `(table: "${table_name}", foreign table: "${foreign_table_name}", column: "${column_name}")`
20
24
 
21
25
  assert(table_name, `table_name is required ${enhanceAssertLogMsg}`)
@@ -23,7 +27,10 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
23
27
 
24
28
  const current = entitiesTypeMap[entityName]
25
29
  const foreign = entitiesTypeMap[foreignEntityName]
26
- assert(foreign !== undefined, `No foreign table named "${foreign_table_name}" was found ${enhanceAssertLogMsg}. Relation: ${JSON.stringify(relation, null, 2)}`)
30
+ assert(
31
+ foreign !== undefined,
32
+ `No foreign table named "${foreign_table_name}" was found ${enhanceAssertLogMsg}. Relation: ${JSON.stringify(relation, null, 2)}`
33
+ )
27
34
 
28
35
  // current to foreign, we skip this if the foreign table has a composite primary key
29
36
  // TODO implement support for this case
@@ -40,17 +47,15 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
40
47
  loader (queries, ctx) {
41
48
  const keys = []
42
49
  for (const { obj } of queries) {
43
- const value = obj[originalField] != null
44
- ? obj[originalField].toString()
45
- : null
50
+ const value = obj[originalField] != null ? obj[originalField].toString() : null
46
51
 
47
52
  keys.push([{ key, value }])
48
53
  }
49
54
  return foreign.loadMany(keys, queries, ctx)
50
55
  },
51
56
  opts: {
52
- cache: false,
53
- },
57
+ cache: false
58
+ }
54
59
  }
55
60
  }
56
61
  }
@@ -76,7 +81,10 @@ module.exports = function establishRelations (app, relations, resolvers, loaders
76
81
  resolvers[foreign.type][lowered] = async function (obj, args, ctx, info) {
77
82
  const previousResponse = await previousResolve(obj, args, ctx, info)
78
83
  const currentResponse = await resolveRelation(obj, args, ctx, info)
79
- return uniqueBy([...previousResponse, ...currentResponse], findPrimaryColumnName(current.entity.camelCasedFields))
84
+ return uniqueBy(
85
+ [...previousResponse, ...currentResponse],
86
+ findPrimaryColumnName(current.entity.camelCasedFields)
87
+ )
80
88
  }
81
89
  }
82
90
  }
@@ -109,4 +117,4 @@ function unique (arr) {
109
117
  function uniqueBy (arr, key) {
110
118
  const uniqueValues = unique(arr.map(el => el[key]))
111
119
  return uniqueValues.map(value => arr.find(el => el[key] === value))
112
- };
120
+ }
@@ -1,9 +1,7 @@
1
- 'use strict'
1
+ import camelcase from 'camelcase'
2
+ import { GraphQLObjectType } from 'graphql'
2
3
 
3
- const graphql = require('graphql')
4
- const camelcase = require('camelcase')
5
-
6
- function setupSubscriptions (app, metaMap, resolvers, ignores) {
4
+ export function setupSubscriptions (app, metaMap, resolvers, ignores) {
7
5
  const fields = {}
8
6
  resolvers.Subscription = {}
9
7
  for (const [field, meta] of metaMap) {
@@ -21,7 +19,7 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
21
19
  const { type } = meta
22
20
  const saved = `${field.singularName}Saved`
23
21
  fields[saved] = {
24
- type,
22
+ type
25
23
  }
26
24
  resolvers.Subscription[saved] = {
27
25
  subscribe: async function * (_, query, ctx, info) {
@@ -54,19 +52,19 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
54
52
  log.warn({ err, entity: field.singularName }, 'graphql subscription error')
55
53
  }
56
54
  }
57
- },
55
+ }
58
56
  }
59
57
 
60
58
  const deleted = `${field.singularName}Deleted`
61
59
  fields[deleted] = {
62
- type: new graphql.GraphQLObjectType({
60
+ type: new GraphQLObjectType({
63
61
  name: `${field.name}Deleted`,
64
62
  fields: {
65
63
  [primaryKey]: {
66
- type: meta.fields[primaryKey].type,
67
- },
68
- },
69
- }),
64
+ type: meta.fields[primaryKey].type
65
+ }
66
+ }
67
+ })
70
68
  }
71
69
  resolvers.Subscription[deleted] = {
72
70
  subscribe: async (_, query, ctx, info) => {
@@ -75,12 +73,12 @@ function setupSubscriptions (app, metaMap, resolvers, ignores) {
75
73
  const res = await pubsub.subscribe(topic)
76
74
  ctx.reply.request.log.trace({ topic }, 'subscribed')
77
75
  return wrap(res, deleted)
78
- },
76
+ }
79
77
  }
80
78
  }
81
- const subscription = new graphql.GraphQLObjectType({
79
+ const subscription = new GraphQLObjectType({
82
80
  name: 'Subscription',
83
- fields,
81
+ fields
84
82
  })
85
83
 
86
84
  return subscription
@@ -91,5 +89,3 @@ async function * wrap (iterator, key) {
91
89
  yield { [key]: msg }
92
90
  }
93
91
  }
94
-
95
- module.exports = setupSubscriptions
package/lib/telemetry.js CHANGED
@@ -1,33 +1,33 @@
1
- 'use strict'
1
+ function telemetryWrapper (app, fn, operationType, operationName) {
2
+ return async function (...args) {
3
+ const { startSpan, endSpan } = app.openTelemetry
4
+ const context = args[2]
5
+ const request = context?.reply?.request
6
+ const document = JSON.stringify(request?.body)
2
7
 
3
- const telemetryWrapper = (app, fn, operationType, operationName) => async (...args) => {
4
- const { startSpan, endSpan } = app.openTelemetry
5
- const context = args[2]
6
- const request = context?.reply?.request
7
- const document = JSON.stringify(request?.body)
8
-
9
- // GraphQL semantic conventions, see: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/graphql.md#semantic-conventions-for-graphql-server
10
- const telemetryAttributes = {
11
- 'graphql.document': document,
12
- 'graphql.operation.name': operationName,
13
- 'graphql.operation.type': operationType,
14
- }
15
- const spanName = `${operationType} ${operationName}`
16
- const ctx = request.span?.context
8
+ // GraphQL semantic conventions, see: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/graphql.md#semantic-conventions-for-graphql-server
9
+ const telemetryAttributes = {
10
+ 'graphql.document': document,
11
+ 'graphql.operation.name': operationName,
12
+ 'graphql.operation.type': operationType
13
+ }
14
+ const spanName = `${operationType} ${operationName}`
15
+ const ctx = request.span?.context
17
16
 
18
- const span = startSpan(spanName, ctx, telemetryAttributes)
19
- try {
20
- const result = await fn(...args)
21
- endSpan(span)
22
- return result
23
- // We ignore this because in sqlite it's HARD to have a resolver exception without a schema validation exception first.
24
- } catch (err) /* istanbul ignore next */ {
25
- endSpan(span, err)
26
- throw err
17
+ const span = startSpan(spanName, ctx, telemetryAttributes)
18
+ try {
19
+ const result = await fn(...args)
20
+ endSpan(span)
21
+ return result
22
+ // We ignore this because in sqlite it's HARD to have a resolver exception without a schema validation exception first.
23
+ } catch (err) /* istanbul ignore next */ {
24
+ endSpan(span, err)
25
+ throw err
26
+ }
27
27
  }
28
28
  }
29
29
 
30
- const setupTelemetry = app => {
30
+ export function setupTelemetry (app) {
31
31
  /* istanbul ignore next */
32
32
  if (!app || !app.graphql) {
33
33
  app.log.warn('No graphql plugin found, skipping telemetry setup')
@@ -52,5 +52,3 @@ const setupTelemetry = app => {
52
52
  }
53
53
  }
54
54
  }
55
-
56
- module.exports = setupTelemetry
package/lib/utils.js CHANGED
@@ -1,13 +1,11 @@
1
- 'use strict'
2
-
3
- const graphql = require('graphql')
4
- const scalars = require('graphql-scalars')
5
- const { GraphQLJSONObject } = require('graphql-type-json')
6
- const errors = require('./errors')
1
+ import * as graphql from 'graphql'
2
+ import * as scalars from 'graphql-scalars'
3
+ import { GraphQLJSONObject } from 'graphql-type-json'
4
+ import { UnsupportedKindError } from './errors.js'
7
5
 
8
6
  // The sqlTypeToGraphQL is shared between
9
7
  // all database adapters.
10
- function sqlTypeToGraphQL (sqlType) {
8
+ export function sqlTypeToGraphQL (sqlType) {
11
9
  // TODO support more types
12
10
  /* istanbul ignore next */
13
11
  switch (sqlType) {
@@ -68,7 +66,7 @@ function sqlTypeToGraphQL (sqlType) {
68
66
  }
69
67
  }
70
68
 
71
- function fromSelectionSet (selectionSet, fields = new Set()) {
69
+ export function fromSelectionSet (selectionSet, fields = new Set()) {
72
70
  /* istanbul ignore next */
73
71
  for (const s of selectionSet.selections) {
74
72
  if (s.kind === 'Field') {
@@ -76,14 +74,10 @@ function fromSelectionSet (selectionSet, fields = new Set()) {
76
74
  } else if (s.kind === 'InlineFragment') {
77
75
  fromSelectionSet(s.selectionSet, fields)
78
76
  } else {
79
- throw new errors.UnsupportedKindError(s.kind)
77
+ throw new UnsupportedKindError(s.kind)
80
78
  }
81
79
  }
82
80
  return fields
83
81
  }
84
82
 
85
- module.exports = {
86
- sqlTypeToGraphQL,
87
- fromSelectionSet,
88
- typeSym: Symbol('graphlType'),
89
- }
83
+ export const typeSym = Symbol('graphlType')
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@platformatic/sql-graphql",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0-alpha.6",
4
4
  "description": "Map SQL dbs to GraphQL",
5
5
  "main": "index.js",
6
+ "type": "module",
6
7
  "types": "index.d.ts",
7
8
  "repository": {
8
9
  "type": "git",
@@ -16,9 +17,8 @@
16
17
  "homepage": "https://github.com/platformatic/platformatic#readme",
17
18
  "devDependencies": {
18
19
  "@matteo.collina/snap": "^0.3.0",
19
- "@matteo.collina/tspl": "^0.2.0",
20
20
  "@mercuriusjs/gateway": "^5.0.0",
21
- "borp": "^0.20.0",
21
+ "cleaner-spec-reporter": "^0.5.0",
22
22
  "eslint": "9",
23
23
  "fastify": "^5.0.0",
24
24
  "fastify-tsconfig": "^3.0.0",
@@ -27,9 +27,9 @@
27
27
  "typescript": "^5.5.4",
28
28
  "why-is-node-running": "^2.2.2",
29
29
  "ws": "^8.16.0",
30
- "@platformatic/sql-events": "3.0.0-alpha.4",
31
- "@platformatic/sql-mapper": "3.0.0-alpha.4",
32
- "@platformatic/telemetry": "3.0.0-alpha.4"
30
+ "@platformatic/telemetry": "3.0.0-alpha.6",
31
+ "@platformatic/sql-mapper": "3.0.0-alpha.6",
32
+ "@platformatic/sql-events": "3.0.0-alpha.6"
33
33
  },
34
34
  "dependencies": {
35
35
  "@fastify/error": "^4.0.0",
@@ -41,7 +41,7 @@
41
41
  "graphql-type-json": "^0.3.2",
42
42
  "inflected": "^2.1.0",
43
43
  "mercurius": "^16.0.0",
44
- "@platformatic/foundation": "3.0.0-alpha.4"
44
+ "@platformatic/foundation": "3.0.0-alpha.6"
45
45
  },
46
46
  "tsd": {
47
47
  "directory": "test/types"
@@ -51,12 +51,12 @@
51
51
  },
52
52
  "scripts": {
53
53
  "lint": "eslint",
54
- "test": "npm run lint && npm run test:typescript && npm run test:postgresql && npm run test:mariadb && npm run test:mysql && npm run test:mysql8 && npm run test:sqlite",
55
- "test:postgresql": "DB=postgresql borp --timeout=1200000 --concurrency=1 test/*.test.js",
56
- "test:mariadb": "DB=mariadb borp --timeout=1200000 --concurrency=1 test/*.test.js",
57
- "test:mysql": "DB=mysql borp --timeout=1200000 --concurrency=1 test/*.test.js",
58
- "test:mysql8": "DB=mysql8 borp --timeout=1200000 --concurrency=1 test/*.test.js",
59
- "test:sqlite": "DB=sqlite borp --timeout=1200000 --concurrency=1 test/*.test.js",
54
+ "test": "npm run test:typescript && npm run test:postgresql && npm run test:mariadb && npm run test:mysql && npm run test:mysql8 && npm run test:sqlite",
55
+ "test:postgresql": "DB=postgresql node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js",
56
+ "test:mariadb": "DB=mariadb node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js",
57
+ "test:mysql": "DB=mysql node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js",
58
+ "test:mysql8": "DB=mysql8 node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js",
59
+ "test:sqlite": "DB=sqlite node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js",
60
60
  "test:typescript": "tsd"
61
61
  }
62
62
  }