Skip to content

Commit

Permalink
fix(graphql): Allow including 'File' scalar by default to be disabled (
Browse files Browse the repository at this point in the history
…#11540)

There was a problem introduced in v8 when we included the `File` scalar
by default. This meant a custom implementation by the user could be
clobbered by the new default. This change allows the user to supply
config to disable including it by default.

This is not how I would have loved to have done things here. Config in
two places is rubbish but given the organisation of this currently it
was generally unavoidable.
  • Loading branch information
Josh-Walker-GM committed Sep 19, 2024
1 parent d8d52b5 commit 0890d8f
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changesets/11540.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- fix(graphql): Allow including 'File' scalar by default to be disabled (#11540) by @Josh-Walker-GM

As of v8.0.0 a `File` scalar was added to your graphql schema by default. This could be problematic if you wanted to define your own `File` scalar.

With this change it is now possible to disable including this scalar by default. To see how to do so look at the `Default Scalar` section of the `Graphql` docs [here](https://docs.redwoodjs.com/docs/graphql#default-scalars)
37 changes: 37 additions & 0 deletions docs/docs/graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,43 @@ api | - deletePost Mutation
To fix these errors, simple declare with `@requireAuth` to enforce authentication or `@skipAuth` to keep the operation public on each as appropriate for your app's permissions needs.
## Default Scalars
Redwood includes a selection of scalar types by default.
Currently we allow you to control whether or not the `File` scalar is included automatically or not. By default we include the `File` scalar which maps to the standard `File` type. To disable this scalar you should add config to two places:
1. In your `redwood.toml` file like so:
```toml
[graphql]
includeScalars.File = false
```
2. In your `functions/graphql.ts` like so:
```typescript
export const handler = createGraphQLHandler({
authDecoder,
getCurrentUser,
loggerConfig: { logger, options: {} },
directives,
sdls,
services,
onException: () => {
// Disconnect from your database with an unhandled exception.
db.$disconnect()
},
// highlight-start
includeScalars: {
File: false,
},
// highlight-end
})
```
With those two config values added your schema will no longer contain the `File` scalar by default and you are free to add your own or continue without one.
## Custom Scalars
GraphQL scalar types give data meaning and validate that their values makes sense. Out of the box, GraphQL comes with `Int`, `Float`, `String`, `Boolean` and `ID`. While those can cover a wide variety of use cases, you may need more specific scalar types to better describe and validate your application's data.
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql-server/src/createGraphQLYoga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const createGraphQLYoga = ({
realtime,
trustedDocuments,
openTelemetryOptions,
includeScalars,
}: GraphQLYogaOptions) => {
let schema: GraphQLSchema
let redwoodDirectivePlugins = [] as Plugin[]
Expand Down Expand Up @@ -85,6 +86,7 @@ export const createGraphQLYoga = ({
directives: projectDirectives,
subscriptions: projectSubscriptions,
schemaOptions,
includeScalars,
})
} catch (e) {
logger.fatal(e as Error, '\n ⚠️ GraphQL server crashed \n')
Expand Down
12 changes: 11 additions & 1 deletion packages/graphql-server/src/makeMergedSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
ServicesGlobImports,
GraphQLTypeWithFields,
SdlGlobImports,
RedwoodScalarConfig,
} from './types'

const wrapWithOpenTelemetry = async (
Expand Down Expand Up @@ -358,11 +359,13 @@ export const makeMergedSchema = ({
schemaOptions = {},
directives,
subscriptions = [],
includeScalars,
}: {
sdls: SdlGlobImports
services: ServicesGlobImports
directives: RedwoodDirective[]
subscriptions: RedwoodSubscription[]
includeScalars?: RedwoodScalarConfig

/**
* A list of options passed to [makeExecutableSchema](https://www.graphql-tools.com/docs/generate-schema/#makeexecutableschemaoptions).
Expand All @@ -371,9 +374,16 @@ export const makeMergedSchema = ({
}) => {
const sdlSchemas = Object.values(sdls).map(({ schema }) => schema)

const rootEntries = [rootGqlSchema.schema]

// We cannot access the getConfig from project-config here so the user must supply it via a config option
if (includeScalars?.File !== false) {
rootEntries.push(rootGqlSchema.scalarSchemas.File)
}

const typeDefs = mergeTypes(
[
rootGqlSchema.schema,
...rootEntries,
...directives.map((directive) => directive.schema), // pick out schemas from directives
...subscriptions.map((subscription) => subscription.schema), // pick out schemas from subscriptions
...sdlSchemas, // pick out the schemas from sdls
Expand Down
8 changes: 7 additions & 1 deletion packages/graphql-server/src/rootSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export const schema = gql`
scalar JSON
scalar JSONObject
scalar Byte
scalar File
"""
The RedwoodJS Root Schema
Expand All @@ -52,6 +51,13 @@ export const schema = gql`
}
`

export const scalarSchemas = {
File: gql`
scalar File
`,
}
export type ScalarSchemaKeys = keyof typeof scalarSchemas

export interface Resolvers {
BigInt: typeof BigIntResolver
Date: typeof DateResolver
Expand Down
12 changes: 12 additions & 0 deletions packages/graphql-server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ export interface RedwoodOpenTelemetryConfig {
result: boolean
}

export interface RedwoodScalarConfig {
File?: boolean
}

/**
* GraphQLYogaOptions
*/
Expand Down Expand Up @@ -248,6 +252,14 @@ export type GraphQLYogaOptions = {
* @description Configure OpenTelemetry plugin behaviour
*/
openTelemetryOptions?: RedwoodOpenTelemetryConfig

/**
* @description Configure which scalars to include in the schema. This should match your
* `graphql.includeScalars` configuration in `redwood.toml`.
*
* The default is to include. You must set to `false` to exclude.
*/
includeScalars?: RedwoodScalarConfig
}

/**
Expand Down
18 changes: 12 additions & 6 deletions packages/internal/src/__tests__/clientPreset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { generateGraphQLSchema } from '../generate/graphqlSchema'

const { mockedGetConfig } = vi.hoisted(() => {
return {
mockedGetConfig: vi
.fn()
.mockReturnValue({ graphql: { trustedDocuments: false } }),
mockedGetConfig: vi.fn().mockReturnValue({
graphql: { trustedDocuments: false, includeScalars: { File: true } },
}),
}
})

Expand All @@ -34,12 +34,16 @@ beforeEach(() => {

afterEach(() => {
delete process.env.RWJS_CWD
mockedGetConfig.mockReturnValue({ graphql: { trustedDocuments: false } })
mockedGetConfig.mockReturnValue({
graphql: { trustedDocuments: false, includeScalars: { File: true } },
})
})

describe('Generate client preset', () => {
test('for web side', async () => {
mockedGetConfig.mockReturnValue({ graphql: { trustedDocuments: true } })
mockedGetConfig.mockReturnValue({
graphql: { trustedDocuments: true, includeScalars: { File: true } },
})
await generateGraphQLSchema()

const { clientPresetFiles, errors } = await generateClientPreset()
Expand All @@ -62,7 +66,9 @@ describe('Generate client preset', () => {
})

test('for api side', async () => {
mockedGetConfig.mockReturnValue({ graphql: { trustedDocuments: true } })
mockedGetConfig.mockReturnValue({
graphql: { trustedDocuments: true, includeScalars: { File: true } },
})
await generateGraphQLSchema()

const { trustedDocumentsStoreFile, errors } = await generateClientPreset()
Expand Down
39 changes: 27 additions & 12 deletions packages/internal/src/generate/graphqlCodeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,22 +274,37 @@ async function getPluginConfig(side: CodegenSide) {
`MergePrismaWithSdlTypes<Prisma${key}, MakeRelationsOptional<${key}, AllMappedModels>, AllMappedModels>`
})

type ScalarKeys =
| 'BigInt'
| 'DateTime'
| 'Date'
| 'JSON'
| 'JSONObject'
| 'Time'
| 'Byte'
| 'File'
const scalars: Partial<Record<ScalarKeys, string>> = {
// We need these, otherwise these scalars are mapped to any
BigInt: 'number',
// @Note: DateTime fields can be valid Date-strings, or the Date object in the api side. They're always strings on the web side.
DateTime: side === CodegenSide.WEB ? 'string' : 'Date | string',
Date: side === CodegenSide.WEB ? 'string' : 'Date | string',
JSON: 'Prisma.JsonValue',
JSONObject: 'Prisma.JsonObject',
Time: side === CodegenSide.WEB ? 'string' : 'Date | string',
Byte: 'Buffer',
}

const config = getConfig()
if (config.graphql.includeScalars.File) {
scalars.File = 'File'
}

const pluginConfig: CodegenTypes.PluginConfig &
rwTypescriptResolvers.TypeScriptResolversPluginConfig = {
makeResolverTypeCallable: true,
namingConvention: 'keep', // to allow camelCased query names
scalars: {
// We need these, otherwise these scalars are mapped to any
BigInt: 'number',
// @Note: DateTime fields can be valid Date-strings, or the Date object in the api side. They're always strings on the web side.
DateTime: side === CodegenSide.WEB ? 'string' : 'Date | string',
Date: side === CodegenSide.WEB ? 'string' : 'Date | string',
JSON: 'Prisma.JsonValue',
JSONObject: 'Prisma.JsonObject',
Time: side === CodegenSide.WEB ? 'string' : 'Date | string',
Byte: 'Buffer',
File: 'File',
},
scalars,
// prevent type names being PetQueryQuery, RW generators already append
// Query/Mutation/etc
omitOperationSuffix: true,
Expand Down
10 changes: 9 additions & 1 deletion packages/internal/src/generate/graphqlSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import { print } from 'graphql'
import terminalLink from 'terminal-link'

import { rootSchema } from '@redwoodjs/graphql-server'
import { getPaths, resolveFile } from '@redwoodjs/project-config'
import type { ScalarSchemaKeys } from '@redwoodjs/graphql-server/src/rootSchema'
import { getPaths, getConfig, resolveFile } from '@redwoodjs/project-config'

export const generateGraphQLSchema = async () => {
const redwoodProjectPaths = getPaths()
const redwoodProjectConfig = getConfig()

const schemaPointerMap = {
[print(rootSchema.schema)]: {},
Expand All @@ -25,6 +27,12 @@ export const generateGraphQLSchema = async () => {
'subscriptions/**/*.{js,ts}': {},
}

for (const [name, schema] of Object.entries(rootSchema.scalarSchemas)) {
if (redwoodProjectConfig.graphql.includeScalars[name as ScalarSchemaKeys]) {
schemaPointerMap[print(schema)] = {}
}
}

// If we're serverful and the user is using realtime, we need to include the live directive for realtime support.
// Note the `ERR_ prefix in`ERR_MODULE_NOT_FOUND`. Since we're using `await import`,
// if the package (here, `@redwoodjs/realtime`) can't be found, it throws this error, with the prefix.
Expand Down
3 changes: 3 additions & 0 deletions packages/project-config/src/__tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ describe('getConfig', () => {
},
"graphql": {
"fragments": false,
"includeScalars": {
"File": true,
},
"trustedDocuments": false,
},
"notifications": {
Expand Down
9 changes: 8 additions & 1 deletion packages/project-config/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ export interface Config {
graphql: {
fragments: boolean
trustedDocuments: boolean
includeScalars: {
File: boolean
}
}
notifications: {
versionUpdates: string[]
Expand Down Expand Up @@ -145,7 +148,11 @@ const DEFAULT_CONFIG: Config = {
serverConfig: './api/server.config.js',
debugPort: 18911,
},
graphql: { fragments: false, trustedDocuments: false },
graphql: {
fragments: false,
trustedDocuments: false,
includeScalars: { File: true },
},
browser: {
open: false,
},
Expand Down

0 comments on commit 0890d8f

Please sign in to comment.