Skip to content

Commit 0081d3a

Browse files
fix(graphql): Allow including 'File' scalar by default to be disabled (#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.
1 parent 09c2f06 commit 0081d3a

File tree

11 files changed

+133
-22
lines changed

11 files changed

+133
-22
lines changed

.changesets/11540.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- fix(graphql): Allow including 'File' scalar by default to be disabled (#11540) by @Josh-Walker-GM
2+
3+
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.
4+
5+
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)

docs/docs/graphql.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,43 @@ api | - deletePost Mutation
689689
690690
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.
691691
692+
## Default Scalars
693+
694+
Redwood includes a selection of scalar types by default.
695+
696+
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:
697+
698+
1. In your `redwood.toml` file like so:
699+
700+
```toml
701+
[graphql]
702+
includeScalars.File = false
703+
```
704+
705+
2. In your `functions/graphql.ts` like so:
706+
707+
```typescript
708+
export const handler = createGraphQLHandler({
709+
authDecoder,
710+
getCurrentUser,
711+
loggerConfig: { logger, options: {} },
712+
directives,
713+
sdls,
714+
services,
715+
onException: () => {
716+
// Disconnect from your database with an unhandled exception.
717+
db.$disconnect()
718+
},
719+
// highlight-start
720+
includeScalars: {
721+
File: false,
722+
},
723+
// highlight-end
724+
})
725+
```
726+
727+
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.
728+
692729
## Custom Scalars
693730
694731
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.

packages/graphql-server/src/createGraphQLYoga.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const createGraphQLYoga = ({
5252
realtime,
5353
trustedDocuments,
5454
openTelemetryOptions,
55+
includeScalars,
5556
}: GraphQLYogaOptions) => {
5657
let schema: GraphQLSchema
5758
let redwoodDirectivePlugins = [] as Plugin[]
@@ -85,6 +86,7 @@ export const createGraphQLYoga = ({
8586
directives: projectDirectives,
8687
subscriptions: projectSubscriptions,
8788
schemaOptions,
89+
includeScalars,
8890
})
8991
} catch (e) {
9092
logger.fatal(e as Error, '\n ⚠️ GraphQL server crashed \n')

packages/graphql-server/src/makeMergedSchema.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
ServicesGlobImports,
2727
GraphQLTypeWithFields,
2828
SdlGlobImports,
29+
RedwoodScalarConfig,
2930
} from './types'
3031

3132
const wrapWithOpenTelemetry = async (
@@ -358,11 +359,13 @@ export const makeMergedSchema = ({
358359
schemaOptions = {},
359360
directives,
360361
subscriptions = [],
362+
includeScalars,
361363
}: {
362364
sdls: SdlGlobImports
363365
services: ServicesGlobImports
364366
directives: RedwoodDirective[]
365367
subscriptions: RedwoodSubscription[]
368+
includeScalars?: RedwoodScalarConfig
366369

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

377+
const rootEntries = [rootGqlSchema.schema]
378+
379+
// We cannot access the getConfig from project-config here so the user must supply it via a config option
380+
if (includeScalars?.File !== false) {
381+
rootEntries.push(rootGqlSchema.scalarSchemas.File)
382+
}
383+
374384
const typeDefs = mergeTypes(
375385
[
376-
rootGqlSchema.schema,
386+
...rootEntries,
377387
...directives.map((directive) => directive.schema), // pick out schemas from directives
378388
...subscriptions.map((subscription) => subscription.schema), // pick out schemas from subscriptions
379389
...sdlSchemas, // pick out the schemas from sdls

packages/graphql-server/src/rootSchema.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export const schema = gql`
2727
scalar JSON
2828
scalar JSONObject
2929
scalar Byte
30-
scalar File
3130
3231
"""
3332
The RedwoodJS Root Schema
@@ -52,6 +51,13 @@ export const schema = gql`
5251
}
5352
`
5453

54+
export const scalarSchemas = {
55+
File: gql`
56+
scalar File
57+
`,
58+
}
59+
export type ScalarSchemaKeys = keyof typeof scalarSchemas
60+
5561
export interface Resolvers {
5662
BigInt: typeof BigIntResolver
5763
Date: typeof DateResolver

packages/graphql-server/src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ export interface RedwoodOpenTelemetryConfig {
9696
result: boolean
9797
}
9898

99+
export interface RedwoodScalarConfig {
100+
File?: boolean
101+
}
102+
99103
/**
100104
* GraphQLYogaOptions
101105
*/
@@ -248,6 +252,14 @@ export type GraphQLYogaOptions = {
248252
* @description Configure OpenTelemetry plugin behaviour
249253
*/
250254
openTelemetryOptions?: RedwoodOpenTelemetryConfig
255+
256+
/**
257+
* @description Configure which scalars to include in the schema. This should match your
258+
* `graphql.includeScalars` configuration in `redwood.toml`.
259+
*
260+
* The default is to include. You must set to `false` to exclude.
261+
*/
262+
includeScalars?: RedwoodScalarConfig
251263
}
252264

253265
/**

packages/internal/src/__tests__/clientPreset.test.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { generateGraphQLSchema } from '../generate/graphqlSchema'
99

1010
const { mockedGetConfig } = vi.hoisted(() => {
1111
return {
12-
mockedGetConfig: vi
13-
.fn()
14-
.mockReturnValue({ graphql: { trustedDocuments: false } }),
12+
mockedGetConfig: vi.fn().mockReturnValue({
13+
graphql: { trustedDocuments: false, includeScalars: { File: true } },
14+
}),
1515
}
1616
})
1717

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

3535
afterEach(() => {
3636
delete process.env.RWJS_CWD
37-
mockedGetConfig.mockReturnValue({ graphql: { trustedDocuments: false } })
37+
mockedGetConfig.mockReturnValue({
38+
graphql: { trustedDocuments: false, includeScalars: { File: true } },
39+
})
3840
})
3941

4042
describe('Generate client preset', () => {
4143
test('for web side', async () => {
42-
mockedGetConfig.mockReturnValue({ graphql: { trustedDocuments: true } })
44+
mockedGetConfig.mockReturnValue({
45+
graphql: { trustedDocuments: true, includeScalars: { File: true } },
46+
})
4347
await generateGraphQLSchema()
4448

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

6468
test('for api side', async () => {
65-
mockedGetConfig.mockReturnValue({ graphql: { trustedDocuments: true } })
69+
mockedGetConfig.mockReturnValue({
70+
graphql: { trustedDocuments: true, includeScalars: { File: true } },
71+
})
6672
await generateGraphQLSchema()
6773

6874
const { trustedDocumentsStoreFile, errors } = await generateClientPreset()

packages/internal/src/generate/graphqlCodeGen.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -274,22 +274,37 @@ async function getPluginConfig(side: CodegenSide) {
274274
`MergePrismaWithSdlTypes<Prisma${key}, MakeRelationsOptional<${key}, AllMappedModels>, AllMappedModels>`
275275
})
276276

277+
type ScalarKeys =
278+
| 'BigInt'
279+
| 'DateTime'
280+
| 'Date'
281+
| 'JSON'
282+
| 'JSONObject'
283+
| 'Time'
284+
| 'Byte'
285+
| 'File'
286+
const scalars: Partial<Record<ScalarKeys, string>> = {
287+
// We need these, otherwise these scalars are mapped to any
288+
BigInt: 'number',
289+
// @Note: DateTime fields can be valid Date-strings, or the Date object in the api side. They're always strings on the web side.
290+
DateTime: side === CodegenSide.WEB ? 'string' : 'Date | string',
291+
Date: side === CodegenSide.WEB ? 'string' : 'Date | string',
292+
JSON: 'Prisma.JsonValue',
293+
JSONObject: 'Prisma.JsonObject',
294+
Time: side === CodegenSide.WEB ? 'string' : 'Date | string',
295+
Byte: 'Buffer',
296+
}
297+
298+
const config = getConfig()
299+
if (config.graphql.includeScalars.File) {
300+
scalars.File = 'File'
301+
}
302+
277303
const pluginConfig: CodegenTypes.PluginConfig &
278304
rwTypescriptResolvers.TypeScriptResolversPluginConfig = {
279305
makeResolverTypeCallable: true,
280306
namingConvention: 'keep', // to allow camelCased query names
281-
scalars: {
282-
// We need these, otherwise these scalars are mapped to any
283-
BigInt: 'number',
284-
// @Note: DateTime fields can be valid Date-strings, or the Date object in the api side. They're always strings on the web side.
285-
DateTime: side === CodegenSide.WEB ? 'string' : 'Date | string',
286-
Date: side === CodegenSide.WEB ? 'string' : 'Date | string',
287-
JSON: 'Prisma.JsonValue',
288-
JSONObject: 'Prisma.JsonObject',
289-
Time: side === CodegenSide.WEB ? 'string' : 'Date | string',
290-
Byte: 'Buffer',
291-
File: 'File',
292-
},
307+
scalars,
293308
// prevent type names being PetQueryQuery, RW generators already append
294309
// Query/Mutation/etc
295310
omitOperationSuffix: true,

packages/internal/src/generate/graphqlSchema.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import { print } from 'graphql'
1313
import terminalLink from 'terminal-link'
1414

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

1819
export const generateGraphQLSchema = async () => {
1920
const redwoodProjectPaths = getPaths()
21+
const redwoodProjectConfig = getConfig()
2022

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

30+
for (const [name, schema] of Object.entries(rootSchema.scalarSchemas)) {
31+
if (redwoodProjectConfig.graphql.includeScalars[name as ScalarSchemaKeys]) {
32+
schemaPointerMap[print(schema)] = {}
33+
}
34+
}
35+
2836
// If we're serverful and the user is using realtime, we need to include the live directive for realtime support.
2937
// Note the `ERR_ prefix in`ERR_MODULE_NOT_FOUND`. Since we're using `await import`,
3038
// if the package (here, `@redwoodjs/realtime`) can't be found, it throws this error, with the prefix.

packages/project-config/src/__tests__/config.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ describe('getConfig', () => {
8181
},
8282
"graphql": {
8383
"fragments": false,
84+
"includeScalars": {
85+
"File": true,
86+
},
8487
"trustedDocuments": false,
8588
},
8689
"notifications": {

0 commit comments

Comments
 (0)