Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(graphql): Allow including 'File' scalar by default to be disabled #11540

Merged
merged 4 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is better than what I had - the config is at least consistent looking between toml and Yoga config. Nice.

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
Loading