From 3b0a532f57a940341555e3095735cd129ae0280a Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Mon, 15 May 2023 12:25:37 -0400 Subject: [PATCH 01/17] Move makeMergedSchema --- packages/graphql-server/src/__tests__/cors.test.ts | 2 +- .../__tests__/makeMergedSchema.test.ts | 6 +++--- .../src/functions/__tests__/authDecoders.test.ts | 2 +- .../src/functions/__tests__/healthCheck.test.ts | 2 +- .../src/functions/__tests__/readinessCheck.test.ts | 2 +- packages/graphql-server/src/functions/graphql.ts | 2 +- packages/graphql-server/src/index.ts | 4 +++- .../src/{makeMergedSchema => }/makeMergedSchema.ts | 6 +++--- .../graphql-server/src/plugins/__tests__/useArmor.test.ts | 2 +- .../src/plugins/__tests__/useRedwoodError.test.ts | 2 +- packages/graphql-server/src/types.ts | 7 +++++++ 11 files changed, 23 insertions(+), 14 deletions(-) rename packages/graphql-server/src/{makeMergedSchema => }/__tests__/makeMergedSchema.test.ts (99%) rename packages/graphql-server/src/{makeMergedSchema => }/makeMergedSchema.ts (98%) diff --git a/packages/graphql-server/src/__tests__/cors.test.ts b/packages/graphql-server/src/__tests__/cors.test.ts index 1426f68fdc6f..407698b172fa 100644 --- a/packages/graphql-server/src/__tests__/cors.test.ts +++ b/packages/graphql-server/src/__tests__/cors.test.ts @@ -4,7 +4,7 @@ import { createLogger } from '@redwoodjs/api/logger' import { createGraphQLHandler } from '../functions/graphql' -jest.mock('../makeMergedSchema/makeMergedSchema', () => { +jest.mock('../makeMergedSchema', () => { const { makeExecutableSchema } = require('@graphql-tools/schema') // Return executable schema return { diff --git a/packages/graphql-server/src/makeMergedSchema/__tests__/makeMergedSchema.test.ts b/packages/graphql-server/src/__tests__/makeMergedSchema.test.ts similarity index 99% rename from packages/graphql-server/src/makeMergedSchema/__tests__/makeMergedSchema.test.ts rename to packages/graphql-server/src/__tests__/makeMergedSchema.test.ts index 2cedb697830c..050b84ca46b0 100644 --- a/packages/graphql-server/src/makeMergedSchema/__tests__/makeMergedSchema.test.ts +++ b/packages/graphql-server/src/__tests__/makeMergedSchema.test.ts @@ -5,13 +5,13 @@ import { makeDirectivesForPlugin, createTransformerDirective, createValidatorDirective, -} from '../../directives/makeDirectives' +} from '../directives/makeDirectives' +import { makeMergedSchema } from '../makeMergedSchema' import { GraphQLTypeWithFields, ServicesGlobImports, SdlGlobImports, -} from '../../types' -import { makeMergedSchema } from '../makeMergedSchema' +} from '../types' jest.mock('@redwoodjs/project-config', () => { return { diff --git a/packages/graphql-server/src/functions/__tests__/authDecoders.test.ts b/packages/graphql-server/src/functions/__tests__/authDecoders.test.ts index 5823f1faa67e..8fc19030fbf8 100644 --- a/packages/graphql-server/src/functions/__tests__/authDecoders.test.ts +++ b/packages/graphql-server/src/functions/__tests__/authDecoders.test.ts @@ -4,7 +4,7 @@ import { createLogger } from '@redwoodjs/api/logger' import { createGraphQLHandler } from '../../functions/graphql' -jest.mock('../../makeMergedSchema/makeMergedSchema', () => { +jest.mock('../../makeMergedSchema', () => { const { makeExecutableSchema } = require('@graphql-tools/schema') // Return executable schema diff --git a/packages/graphql-server/src/functions/__tests__/healthCheck.test.ts b/packages/graphql-server/src/functions/__tests__/healthCheck.test.ts index 7cc73ca2f5b3..6e252baacc0a 100644 --- a/packages/graphql-server/src/functions/__tests__/healthCheck.test.ts +++ b/packages/graphql-server/src/functions/__tests__/healthCheck.test.ts @@ -4,7 +4,7 @@ import { createLogger } from '@redwoodjs/api/logger' import { createGraphQLHandler } from '../../functions/graphql' -jest.mock('../../makeMergedSchema/makeMergedSchema', () => { +jest.mock('../../makeMergedSchema', () => { const { makeExecutableSchema } = require('@graphql-tools/schema') // Return executable schema diff --git a/packages/graphql-server/src/functions/__tests__/readinessCheck.test.ts b/packages/graphql-server/src/functions/__tests__/readinessCheck.test.ts index 8fad81e76845..df1916a692f0 100644 --- a/packages/graphql-server/src/functions/__tests__/readinessCheck.test.ts +++ b/packages/graphql-server/src/functions/__tests__/readinessCheck.test.ts @@ -4,7 +4,7 @@ import { createLogger } from '@redwoodjs/api/logger' import { createGraphQLHandler } from '../../functions/graphql' -jest.mock('../../makeMergedSchema/makeMergedSchema', () => { +jest.mock('../../makeMergedSchema', () => { const { makeExecutableSchema } = require('@graphql-tools/schema') // Return executable schema diff --git a/packages/graphql-server/src/functions/graphql.ts b/packages/graphql-server/src/functions/graphql.ts index 4d76ad2188ff..b9881917ae58 100644 --- a/packages/graphql-server/src/functions/graphql.ts +++ b/packages/graphql-server/src/functions/graphql.ts @@ -14,7 +14,7 @@ import { getConfig } from '@redwoodjs/project-config' import { mapRwCorsOptionsToYoga } from '../cors' import { makeDirectivesForPlugin } from '../directives/makeDirectives' import { getAsyncStoreInstance } from '../globalContext' -import { makeMergedSchema } from '../makeMergedSchema/makeMergedSchema' +import { makeMergedSchema } from '../makeMergedSchema' import { useArmor, useRedwoodAuthContext, diff --git a/packages/graphql-server/src/index.ts b/packages/graphql-server/src/index.ts index af8475503b87..f87a46cddd46 100644 --- a/packages/graphql-server/src/index.ts +++ b/packages/graphql-server/src/index.ts @@ -6,13 +6,14 @@ export * from './globalContext' export * from './errors' export * from './functions/graphql' export * from './functions/useRequireAuth' -export * from './makeMergedSchema/makeMergedSchema' +export * from './makeMergedSchema' export * from './types' export { createValidatorDirective, createTransformerDirective, getDirectiveName, + makeDirectivesForPlugin, } from './directives/makeDirectives' export { @@ -26,6 +27,7 @@ export { TransformerDirectiveFunc, ValidateArgs, TransformArgs, + useRedwoodDirective, } from './plugins/useRedwoodDirective' export * as rootSchema from './rootSchema' diff --git a/packages/graphql-server/src/makeMergedSchema/makeMergedSchema.ts b/packages/graphql-server/src/makeMergedSchema.ts similarity index 98% rename from packages/graphql-server/src/makeMergedSchema/makeMergedSchema.ts rename to packages/graphql-server/src/makeMergedSchema.ts index b4909740d344..24c07d9c125a 100644 --- a/packages/graphql-server/src/makeMergedSchema/makeMergedSchema.ts +++ b/packages/graphql-server/src/makeMergedSchema.ts @@ -18,14 +18,14 @@ import omitBy from 'lodash.omitby' import { getConfig } from '@redwoodjs/project-config' -import type { RedwoodDirective } from '../plugins/useRedwoodDirective' -import * as rootGqlSchema from '../rootSchema' +import type { RedwoodDirective } from './plugins/useRedwoodDirective' +import * as rootGqlSchema from './rootSchema' import { Services, ServicesGlobImports, GraphQLTypeWithFields, SdlGlobImports, -} from '../types' +} from './types' const wrapWithOpenTelemetry = async ( func: any, diff --git a/packages/graphql-server/src/plugins/__tests__/useArmor.test.ts b/packages/graphql-server/src/plugins/__tests__/useArmor.test.ts index e8d7e9916d5a..2436892fc519 100644 --- a/packages/graphql-server/src/plugins/__tests__/useArmor.test.ts +++ b/packages/graphql-server/src/plugins/__tests__/useArmor.test.ts @@ -4,7 +4,7 @@ import { createLogger } from '@redwoodjs/api/logger' import { createGraphQLHandler } from '../../functions/graphql' -jest.mock('../../makeMergedSchema/makeMergedSchema', () => { +jest.mock('../../makeMergedSchema', () => { const { makeExecutableSchema } = require('@graphql-tools/schema') // Return executable schema diff --git a/packages/graphql-server/src/plugins/__tests__/useRedwoodError.test.ts b/packages/graphql-server/src/plugins/__tests__/useRedwoodError.test.ts index 191e8fa003fc..a841db63c41a 100644 --- a/packages/graphql-server/src/plugins/__tests__/useRedwoodError.test.ts +++ b/packages/graphql-server/src/plugins/__tests__/useRedwoodError.test.ts @@ -5,7 +5,7 @@ import { createLogger } from '@redwoodjs/api/logger' import { createGraphQLHandler } from '../../functions/graphql' -jest.mock('../../makeMergedSchema/makeMergedSchema', () => { +jest.mock('../../makeMergedSchema', () => { const { makeExecutableSchema } = require('@graphql-tools/schema') const { ForbiddenError } = require('@redwoodjs/graphql-server/dist/errors') const { EmailValidationError, RedwoodError } = require('@redwoodjs/api') diff --git a/packages/graphql-server/src/types.ts b/packages/graphql-server/src/types.ts index c410d78091c0..84a5d12b8f19 100644 --- a/packages/graphql-server/src/types.ts +++ b/packages/graphql-server/src/types.ts @@ -29,3 +29,10 @@ export interface MakeServicesInterface { export type MakeServices = (args: MakeServicesInterface) => ServicesGlobImports export type GraphQLTypeWithFields = GraphQLObjectType | GraphQLInterfaceType + +import type { + useRedwoodDirectiveReturn, + DirectivePluginOptions, +} from './plugins/useRedwoodDirective' + +export type { useRedwoodDirectiveReturn, DirectivePluginOptions } From 8299c9617dad24bb8ac405c64802844a15f071a4 Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Mon, 15 May 2023 12:52:42 -0400 Subject: [PATCH 02/17] refactor createGraphQLYoga --- .../graphql-server/src/createGraphQLYoga.ts | 193 ++++++++++++++++++ .../graphql-server/src/functions/graphql.ts | 193 +++--------------- .../graphql-server/src/functions/types.ts | 168 --------------- .../src/functions/useRequireAuth.ts | 3 +- packages/graphql-server/src/index.ts | 1 + .../graphql-server/src/plugins/useArmor.ts | 2 +- .../src/plugins/useRedwoodAuthContext.ts | 5 +- .../src/plugins/useRedwoodError.ts | 2 +- .../plugins/useRedwoodGlobalContextSetter.ts | 2 +- .../src/plugins/useRedwoodLogger.ts | 2 +- .../src/plugins/useRedwoodPopulateContext.ts | 5 +- packages/graphql-server/src/types.ts | 177 +++++++++++++++- 12 files changed, 396 insertions(+), 357 deletions(-) create mode 100644 packages/graphql-server/src/createGraphQLYoga.ts delete mode 100644 packages/graphql-server/src/functions/types.ts diff --git a/packages/graphql-server/src/createGraphQLYoga.ts b/packages/graphql-server/src/createGraphQLYoga.ts new file mode 100644 index 000000000000..e12e8e13d095 --- /dev/null +++ b/packages/graphql-server/src/createGraphQLYoga.ts @@ -0,0 +1,193 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { useDisableIntrospection } from '@envelop/disable-introspection' +import { useFilterAllowedOperations } from '@envelop/filter-operation-type' +import { GraphQLSchema, OperationTypeNode } from 'graphql' +import { Plugin, useReadinessCheck, createYoga } from 'graphql-yoga' + +import { mapRwCorsOptionsToYoga } from './cors' +import { makeDirectivesForPlugin } from './directives/makeDirectives' +import { makeMergedSchema } from './makeMergedSchema' +import { + useArmor, + useRedwoodAuthContext, + useRedwoodDirective, + useRedwoodError, + useRedwoodGlobalContextSetter, + useRedwoodOpenTelemetry, + useRedwoodLogger, + useRedwoodPopulateContext, +} from './plugins' +import type { + useRedwoodDirectiveReturn, + DirectivePluginOptions, +} from './plugins/useRedwoodDirective' +import type { GraphQLYogaOptions } from './types' + +export const createGraphQLYoga = ({ + healthCheckId, + loggerConfig, + context, + getCurrentUser, + onException, + generateGraphiQLHeader, + extraPlugins, + authDecoder, + cors, + services, + sdls, + directives = [], + armorConfig, + allowedOperations, + allowIntrospection, + defaultError = 'Something went wrong.', + graphiQLEndpoint = '/graphql', + schemaOptions, +}: GraphQLYogaOptions) => { + let schema: GraphQLSchema + let redwoodDirectivePlugins = [] as Plugin[] + const logger = loggerConfig.logger + + try { + // @NOTE: Directives are optional + const projectDirectives = makeDirectivesForPlugin(directives) + + if (projectDirectives.length > 0) { + ;(redwoodDirectivePlugins as useRedwoodDirectiveReturn[]) = + projectDirectives.map((directive) => + useRedwoodDirective(directive as DirectivePluginOptions) + ) + } + + schema = makeMergedSchema({ + sdls, + services, + directives: projectDirectives, + schemaOptions, + }) + } catch (e) { + logger.fatal(e as Error, '\n ⚠️ GraphQL server crashed \n') + + onException && onException() + + // Forcefully crash the graphql server + // so users know that a misconfiguration has happened + process.exit(1) + } + + try { + // Important: Plugins are executed in order of their usage, and inject functionality serially, + // so the order here matters + const isDevEnv = process.env.NODE_ENV === 'development' + + const plugins: Array> = [] + + if ( + (allowIntrospection == null && !isDevEnv) || + allowIntrospection === false + ) { + plugins.push(useDisableIntrospection()) + } + + // Custom Redwood plugins + plugins.push(useRedwoodAuthContext(getCurrentUser, authDecoder)) + plugins.push(useRedwoodGlobalContextSetter()) + + if (context) { + plugins.push(useRedwoodPopulateContext(context)) + } + + // Custom Redwood plugins + plugins.push(...redwoodDirectivePlugins) + + // Custom Redwood OpenTelemetry plugin + plugins.push(useRedwoodOpenTelemetry()) + + // Secure the GraphQL server + plugins.push(useArmor(logger, armorConfig)) + + // Only allow execution of specific operation types + plugins.push( + useFilterAllowedOperations( + allowedOperations || [ + OperationTypeNode.QUERY, + OperationTypeNode.MUTATION, + ] + ) + ) + + // App-defined plugins + if (extraPlugins && extraPlugins.length > 0) { + plugins.push(...extraPlugins) + } + + plugins.push(useRedwoodError(logger)) + + plugins.push( + useReadinessCheck({ + endpoint: graphiQLEndpoint + '/readiness', + check: async ({ request }) => { + try { + // if we can reach the health check endpoint ... + const response = await yoga.fetch( + new URL(graphiQLEndpoint + '/health', request.url) + ) + + const expectedHealthCheckId = healthCheckId || 'yoga' + + // ... and the health check id's match the request and response's + const status = + response.headers.get('x-yoga-id') === expectedHealthCheckId && + request.headers.get('x-yoga-id') === expectedHealthCheckId + + // then we're good to go (or not) + return status + } catch (err) { + logger.error(err) + return false + } + }, + }) + ) + + // Must be "last" in plugin chain, but before error masking + // so can process any data added to results and extensions + plugins.push(useRedwoodLogger(loggerConfig)) + + const yoga = createYoga({ + id: healthCheckId, + landingPage: isDevEnv, + schema, + plugins, + maskedErrors: { + errorMessage: defaultError, + isDev: isDevEnv, + }, + logging: logger, + healthCheckEndpoint: graphiQLEndpoint + '/health', + graphqlEndpoint: graphiQLEndpoint, + graphiql: isDevEnv + ? { + title: 'Redwood GraphQL Playground', + headers: generateGraphiQLHeader + ? generateGraphiQLHeader() + : `{"x-auth-comment": "See documentation: https://redwoodjs.com/docs/cli-commands#setup-graphiQL-headers on how to auto generate auth headers"}`, + defaultQuery: `query Redwood { +redwood { + version +} +}`, + headerEditorEnabled: true, + } + : false, + cors: (request: Request) => { + const requestOrigin = request.headers.get('origin') + return mapRwCorsOptionsToYoga(cors, requestOrigin) + }, + }) + + return { yoga, logger } + } catch (e) { + onException && onException() + throw e + } +} diff --git a/packages/graphql-server/src/functions/graphql.ts b/packages/graphql-server/src/functions/graphql.ts index b9881917ae58..68a925c8d94b 100644 --- a/packages/graphql-server/src/functions/graphql.ts +++ b/packages/graphql-server/src/functions/graphql.ts @@ -1,36 +1,13 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import { useDisableIntrospection } from '@envelop/disable-introspection' -import { useFilterAllowedOperations } from '@envelop/filter-operation-type' import type { APIGatewayProxyEvent, APIGatewayProxyResult, Context as LambdaContext, } from 'aws-lambda' -import { GraphQLSchema, OperationTypeNode } from 'graphql' -import { Plugin, useReadinessCheck, createYoga } from 'graphql-yoga' -import { getConfig } from '@redwoodjs/project-config' - -import { mapRwCorsOptionsToYoga } from '../cors' -import { makeDirectivesForPlugin } from '../directives/makeDirectives' +import { createGraphQLYoga } from '../createGraphQLYoga' import { getAsyncStoreInstance } from '../globalContext' -import { makeMergedSchema } from '../makeMergedSchema' -import { - useArmor, - useRedwoodAuthContext, - useRedwoodDirective, - useRedwoodError, - useRedwoodGlobalContextSetter, - useRedwoodOpenTelemetry, - useRedwoodLogger, - useRedwoodPopulateContext, -} from '../plugins' -import type { - useRedwoodDirectiveReturn, - DirectivePluginOptions, -} from '../plugins/useRedwoodDirective' +import type { GraphQLHandlerOptions } from '../types' -import type { GraphQLHandlerOptions } from './types' /** * Creates an Enveloped GraphQL Server, configured with default Redwood plugins * @@ -62,151 +39,6 @@ export const createGraphQLHandler = ({ graphiQLEndpoint = '/graphql', schemaOptions, }: GraphQLHandlerOptions) => { - let schema: GraphQLSchema - let redwoodDirectivePlugins = [] as Plugin[] - const logger = loggerConfig.logger - - try { - // @NOTE: Directives are optional - const projectDirectives = makeDirectivesForPlugin(directives) - - if (projectDirectives.length > 0) { - ;(redwoodDirectivePlugins as useRedwoodDirectiveReturn[]) = - projectDirectives.map((directive) => - useRedwoodDirective(directive as DirectivePluginOptions) - ) - } - - schema = makeMergedSchema({ - sdls, - services, - directives: projectDirectives, - schemaOptions, - }) - } catch (e) { - logger.fatal(e as Error, '\n ⚠️ GraphQL server crashed \n') - - // Forcefully crash the graphql server - // so users know that a misconfiguration has happened - process.exit(1) - } - - // Important: Plugins are executed in order of their usage, and inject functionality serially, - // so the order here matters - const isDevEnv = process.env.NODE_ENV === 'development' - - const plugins: Array> = [] - - if ( - (allowIntrospection == null && !isDevEnv) || - allowIntrospection === false - ) { - plugins.push(useDisableIntrospection()) - } - - // Custom Redwood plugins - plugins.push(useRedwoodAuthContext(getCurrentUser, authDecoder)) - plugins.push(useRedwoodGlobalContextSetter()) - - if (context) { - plugins.push(useRedwoodPopulateContext(context)) - } - - // Custom Redwood plugins - plugins.push(...redwoodDirectivePlugins) - - // Custom Redwood OpenTelemetry plugin - let openTelemetryPluginEnabled = false - try { - openTelemetryPluginEnabled = getConfig().experimental.opentelemetry.enabled - } catch (_error) { - // Swallow this error for the time being as we don't always have access to the - // config toml depending on the deploy environment - } - if (openTelemetryPluginEnabled) { - plugins.push(useRedwoodOpenTelemetry()) - } - - // Secure the GraphQL server - plugins.push(useArmor(logger, armorConfig)) - - // Only allow execution of specific operation types - plugins.push( - useFilterAllowedOperations( - allowedOperations || [OperationTypeNode.QUERY, OperationTypeNode.MUTATION] - ) - ) - - // App-defined plugins - if (extraPlugins && extraPlugins.length > 0) { - plugins.push(...extraPlugins) - } - - plugins.push(useRedwoodError(logger)) - - plugins.push( - useReadinessCheck({ - endpoint: graphiQLEndpoint + '/readiness', - check: async ({ request }) => { - try { - // if we can reach the health check endpoint ... - const response = await yoga.fetch( - new URL(graphiQLEndpoint + '/health', request.url) - ) - - const expectedHealthCheckId = healthCheckId || 'yoga' - - // ... and the health check id's match the request and response's - const status = - response.headers.get('x-yoga-id') === expectedHealthCheckId && - request.headers.get('x-yoga-id') === expectedHealthCheckId - - // then we're good to go (or not) - return status - } catch (err) { - logger.error(err) - return false - } - }, - }) - ) - - // Must be "last" in plugin chain, but before error masking - // so can process any data added to results and extensions - plugins.push(useRedwoodLogger(loggerConfig)) - - const yoga = createYoga({ - id: healthCheckId, - landingPage: isDevEnv, - schema, - plugins, - maskedErrors: { - errorMessage: defaultError, - isDev: isDevEnv, - }, - logging: logger, - healthCheckEndpoint: graphiQLEndpoint + '/health', - graphqlEndpoint: graphiQLEndpoint, - graphiql: isDevEnv - ? { - title: 'Redwood GraphQL Playground', - headers: generateGraphiQLHeader - ? generateGraphiQLHeader() - : `{"x-auth-comment": "See documentation: https://redwoodjs.com/docs/cli-commands#setup-graphiQL-headers on how to auto generate auth headers"}`, - defaultQuery: `query Redwood { - redwood { - version - } -}`, - headerEditorEnabled: true, - } - : false, - cors: (request: Request) => { - const requestOrigin = request.headers.get('origin') - return mapRwCorsOptionsToYoga(cors, requestOrigin) - }, - }) - const handlerFn = async ( event: APIGatewayProxyEvent, requestContext: LambdaContext @@ -216,6 +48,27 @@ export const createGraphQLHandler = ({ let lambdaResponse: APIGatewayProxyResult + const { yoga, logger } = createGraphQLYoga({ + healthCheckId, + loggerConfig, + context, + getCurrentUser, + onException, + generateGraphiQLHeader, + extraPlugins, + authDecoder, + cors, + services, + sdls, + directives, + armorConfig, + allowedOperations, + allowIntrospection, + defaultError, + graphiQLEndpoint, + schemaOptions, + }) + try { // url needs to be normalized const [, rest = ''] = event.path.split(graphiQLEndpoint) diff --git a/packages/graphql-server/src/functions/types.ts b/packages/graphql-server/src/functions/types.ts deleted file mode 100644 index 083af724c976..000000000000 --- a/packages/graphql-server/src/functions/types.ts +++ /dev/null @@ -1,168 +0,0 @@ -import type { AllowedOperations } from '@envelop/filter-operation-type' -import type { GraphQLArmorConfig } from '@escape.tech/graphql-armor-types' -import { IExecutableSchemaDefinition } from '@graphql-tools/schema' -import type { APIGatewayProxyEvent, Context as LambdaContext } from 'aws-lambda' -import type { Plugin } from 'graphql-yoga' - -import type { AuthContextPayload, Decoder } from '@redwoodjs/api' -import { CorsConfig } from '@redwoodjs/api' - -import { DirectiveGlobImports } from 'src/directives/makeDirectives' - -import { LoggerConfig } from '../plugins/useRedwoodLogger' -import { SdlGlobImports, ServicesGlobImports } from '../types' - -type ThenArg = T extends PromiseLike ? U : T - -export type GetCurrentUser = ( - decoded: AuthContextPayload[0], - raw: AuthContextPayload[1], - req?: AuthContextPayload[2] -) => Promise | string> - -export type GenerateGraphiQLHeader = () => string - -export type Context = Record -export type ContextFunction = (...args: any[]) => Context | Promise - -export type ArmorConfig = { - logContext?: boolean - logErrors?: boolean -} & GraphQLArmorConfig - -/** This is an interface so you can extend it inside your application when needed */ -export interface RedwoodGraphQLContext { - event: APIGatewayProxyEvent - requestContext: LambdaContext - currentUser?: ThenArg> | AuthContextPayload | null - - [index: string]: unknown -} - -/** - * GraphQLHandlerOptions - */ -export interface GraphQLHandlerOptions { - /** - * @description The identifier used in the GraphQL health check response. - * It verifies readiness when sent as a header in the readiness check request. - * - * By default, the identifier is `yoga` as seen in the HTTP response header `x-yoga-id: yoga` - */ - healthCheckId?: string - - /** - * @description Customize GraphQL Logger - * - * Collect resolver timings, and exposes trace data for - * an individual request under extensions as part of the GraphQL response. - */ - loggerConfig: LoggerConfig - - /** - * @description Modify the resolver and global context. - */ - context?: Context | ContextFunction - - /** - * @description An async function that maps the auth token retrieved from the - * request headers to an object. - * Is it executed when the `auth-provider` contains one of the supported - * providers. - */ - getCurrentUser?: GetCurrentUser - - /** - * @description A callback when an unhandled exception occurs. Use this to disconnect your prisma instance. - */ - onException?: () => void - - /** - * @description Services passed from the glob import: - * import services from 'src/services\/**\/*.{js,ts}' - */ - services: ServicesGlobImports - - /** - * @description SDLs (schema definitions) passed from the glob import: - * import sdls from 'src/graphql\/**\/*.{js,ts}' - */ - sdls: SdlGlobImports - - /** - * @description Directives passed from the glob import: - * import directives from 'src/directives/**\/*.{js,ts}' - */ - directives?: DirectiveGlobImports - - /** - * @description A list of options passed to [makeExecutableSchema] - * (https://www.graphql-tools.com/docs/generate-schema/#makeexecutableschemaoptions). - */ - schemaOptions?: Partial - - /** - * @description CORS configuration - */ - cors?: CorsConfig - - /** - * @description Customize GraphQL Armor plugin configuration - * - * @see https://escape-technologies.github.io/graphql-armor/docs/configuration/examples - */ - armorConfig?: ArmorConfig - - /** - * @description Customize the default error message used to mask errors. - * - * By default, the masked error message is "Something went wrong" - * - * @see https://github.com/dotansimha/envelop/blob/main/packages/core/docs/use-masked-errors.md - */ - defaultError?: string - - /** - * @description Only allows the specified operation types (e.g. subscription, query or mutation). - * - * By default, only allow query and mutation (ie, do not allow subscriptions). - * - * An array of GraphQL's OperationTypeNode enums: - * - OperationTypeNode.SUBSCRIPTION - * - OperationTypeNode.QUERY - * - OperationTypeNode.MUTATION - * - * @see https://github.com/dotansimha/envelop/tree/main/packages/plugins/filter-operation-type - */ - allowedOperations?: AllowedOperations - - /** - * @description Custom Envelop plugins - */ - extraPlugins?: Plugin[] - - /** - * @description Auth-provider specific token decoder - */ - authDecoder?: Decoder | Decoder[] - - /** - * @description Customize the GraphiQL Endpoint that appears in the location bar of the GraphQL Playground - * - * Defaults to '/graphql' as this value must match the name of the `graphql` function on the api-side. - */ - graphiQLEndpoint?: string - - /** - * @description Allow schema introspection. - * By default, schema introspection is disabled in production. Explicitly set this to true or false to override in all environments. - */ - allowIntrospection?: boolean - - /** - * @description Function that returns custom headers (as string) for GraphiQL. - * - * Headers must set auth-provider, Authorization and (if using dbAuth) the encrypted cookie. - */ - generateGraphiQLHeader?: GenerateGraphiQLHeader -} diff --git a/packages/graphql-server/src/functions/useRequireAuth.ts b/packages/graphql-server/src/functions/useRequireAuth.ts index fa6ff8bd7636..0a44d627013a 100644 --- a/packages/graphql-server/src/functions/useRequireAuth.ts +++ b/packages/graphql-server/src/functions/useRequireAuth.ts @@ -6,8 +6,7 @@ import { getAsyncStoreInstance, context as globalContext, } from '../globalContext' - -import type { GetCurrentUser } from './types' +import type { GetCurrentUser } from '../types' interface Args { authDecoder?: Decoder | Decoder[] diff --git a/packages/graphql-server/src/index.ts b/packages/graphql-server/src/index.ts index f87a46cddd46..d4de56574803 100644 --- a/packages/graphql-server/src/index.ts +++ b/packages/graphql-server/src/index.ts @@ -7,6 +7,7 @@ export * from './errors' export * from './functions/graphql' export * from './functions/useRequireAuth' export * from './makeMergedSchema' +export * from './createGraphQLYoga' export * from './types' export { diff --git a/packages/graphql-server/src/plugins/useArmor.ts b/packages/graphql-server/src/plugins/useArmor.ts index 7b80e1f8e54e..8ae799c97c48 100644 --- a/packages/graphql-server/src/plugins/useArmor.ts +++ b/packages/graphql-server/src/plugins/useArmor.ts @@ -3,7 +3,7 @@ import type { GraphQLError, ValidationContext } from 'graphql' import type { Logger } from '@redwoodjs/api/logger' -import { ArmorConfig } from '../functions/types' +import { ArmorConfig } from '../types' const armorConfigDefaultOptions: ArmorConfig = { logContext: false, diff --git a/packages/graphql-server/src/plugins/useRedwoodAuthContext.ts b/packages/graphql-server/src/plugins/useRedwoodAuthContext.ts index 531d55e1669a..3ea72dd5864d 100644 --- a/packages/graphql-server/src/plugins/useRedwoodAuthContext.ts +++ b/packages/graphql-server/src/plugins/useRedwoodAuthContext.ts @@ -7,10 +7,7 @@ import { } from '@redwoodjs/api' // import { AuthenticationError } from '../errors' -import { - RedwoodGraphQLContext, - GraphQLHandlerOptions, -} from '../functions/types' +import { RedwoodGraphQLContext, GraphQLHandlerOptions } from '../types' /** * Envelop plugin for injecting the current user into the GraphQL Context, diff --git a/packages/graphql-server/src/plugins/useRedwoodError.ts b/packages/graphql-server/src/plugins/useRedwoodError.ts index 630a001f7acf..797293f4c52c 100644 --- a/packages/graphql-server/src/plugins/useRedwoodError.ts +++ b/packages/graphql-server/src/plugins/useRedwoodError.ts @@ -7,7 +7,7 @@ import { import { RedwoodError } from '@redwoodjs/api' import type { Logger } from '@redwoodjs/api/logger' -import { RedwoodGraphQLContext } from '../functions/types' +import { RedwoodGraphQLContext } from '../types' /** * Converts RedwoodErrors to GraphQLErrors diff --git a/packages/graphql-server/src/plugins/useRedwoodGlobalContextSetter.ts b/packages/graphql-server/src/plugins/useRedwoodGlobalContextSetter.ts index 50c2c5c09315..432a45423206 100644 --- a/packages/graphql-server/src/plugins/useRedwoodGlobalContextSetter.ts +++ b/packages/graphql-server/src/plugins/useRedwoodGlobalContextSetter.ts @@ -1,7 +1,7 @@ import { Plugin } from 'graphql-yoga' -import { RedwoodGraphQLContext } from '../functions/types' import { setContext } from '../index' +import { RedwoodGraphQLContext } from '../types' /** * This Envelop plugin waits until the GraphQL context is done building and sets the diff --git a/packages/graphql-server/src/plugins/useRedwoodLogger.ts b/packages/graphql-server/src/plugins/useRedwoodLogger.ts index 75a1bf59bc89..73e6f7887ce4 100644 --- a/packages/graphql-server/src/plugins/useRedwoodLogger.ts +++ b/packages/graphql-server/src/plugins/useRedwoodLogger.ts @@ -10,7 +10,7 @@ import { v4 as uuidv4 } from 'uuid' import type { Logger, LevelWithSilent } from '@redwoodjs/api/logger' import { AuthenticationError, ForbiddenError } from '../errors' -import { RedwoodGraphQLContext } from '../functions/types' +import { RedwoodGraphQLContext } from '../types' /** * Options for request and response information to include in the log statements diff --git a/packages/graphql-server/src/plugins/useRedwoodPopulateContext.ts b/packages/graphql-server/src/plugins/useRedwoodPopulateContext.ts index 1e0b10d91b3e..7625252fe1ce 100644 --- a/packages/graphql-server/src/plugins/useRedwoodPopulateContext.ts +++ b/packages/graphql-server/src/plugins/useRedwoodPopulateContext.ts @@ -1,9 +1,6 @@ import { Plugin } from 'graphql-yoga' -import { - RedwoodGraphQLContext, - GraphQLHandlerOptions, -} from '../functions/types' +import { RedwoodGraphQLContext, GraphQLHandlerOptions } from '../types' /** * This Envelop plugin enriches the context on a per-request basis diff --git a/packages/graphql-server/src/types.ts b/packages/graphql-server/src/types.ts index 84a5d12b8f19..a1d634d432e5 100644 --- a/packages/graphql-server/src/types.ts +++ b/packages/graphql-server/src/types.ts @@ -1,4 +1,20 @@ +import type { AllowedOperations } from '@envelop/filter-operation-type' +import type { GraphQLArmorConfig } from '@escape.tech/graphql-armor-types' +import { IExecutableSchemaDefinition } from '@graphql-tools/schema' +import type { APIGatewayProxyEvent, Context as LambdaContext } from 'aws-lambda' import { GraphQLObjectType, GraphQLInterfaceType, DocumentNode } from 'graphql' +import type { Plugin } from 'graphql-yoga' + +import type { AuthContextPayload, Decoder } from '@redwoodjs/api' +import { CorsConfig } from '@redwoodjs/api' + +import { DirectiveGlobImports } from 'src/directives/makeDirectives' + +import type { + useRedwoodDirectiveReturn, + DirectivePluginOptions, +} from './plugins/useRedwoodDirective' +import { LoggerConfig } from './plugins/useRedwoodLogger' export type Resolver = (...args: unknown[]) => unknown export type Services = { @@ -6,6 +22,7 @@ export type Services = { } type ThenArg = T extends PromiseLike ? U : T + export type ResolverArgs = { root: ThenArg } export type SdlGlobImports = { @@ -30,9 +47,159 @@ export type MakeServices = (args: MakeServicesInterface) => ServicesGlobImports export type GraphQLTypeWithFields = GraphQLObjectType | GraphQLInterfaceType -import type { - useRedwoodDirectiveReturn, - DirectivePluginOptions, -} from './plugins/useRedwoodDirective' - export type { useRedwoodDirectiveReturn, DirectivePluginOptions } + +export type GetCurrentUser = ( + decoded: AuthContextPayload[0], + raw: AuthContextPayload[1], + req?: AuthContextPayload[2] +) => Promise | string> + +export type GenerateGraphiQLHeader = () => string + +export type Context = Record +export type ContextFunction = (...args: any[]) => Context | Promise + +export type ArmorConfig = { + logContext?: boolean + logErrors?: boolean +} & GraphQLArmorConfig + +/** This is an interface so you can extend it inside your application when needed */ +export interface RedwoodGraphQLContext { + event: APIGatewayProxyEvent + requestContext: LambdaContext + currentUser?: ThenArg> | AuthContextPayload | null + + [index: string]: unknown +} + +/** + * GraphQLYogaOptions + */ +export interface GraphQLYogaOptions { + /** + * @description The identifier used in the GraphQL health check response. + * It verifies readiness when sent as a header in the readiness check request. + * + * By default, the identifier is `yoga` as seen in the HTTP response header `x-yoga-id: yoga` + */ + healthCheckId?: string + + /** + * @description Customize GraphQL Logger + * + * Collect resolver timings, and exposes trace data for + * an individual request under extensions as part of the GraphQL response. + */ + loggerConfig: LoggerConfig + + /** + * @description Modify the resolver and global context. + */ + context?: Context | ContextFunction + + /** + * @description An async function that maps the auth token retrieved from the + * request headers to an object. + * Is it executed when the `auth-provider` contains one of the supported + * providers. + */ + getCurrentUser?: GetCurrentUser + + /** + * @description A callback when an unhandled exception occurs. Use this to disconnect your prisma instance. + */ + onException?: () => void + + /** + * @description Services passed from the glob import: + * import services from 'src/services\/**\/*.{js,ts}' + */ + services: ServicesGlobImports + + /** + * @description SDLs (schema definitions) passed from the glob import: + * import sdls from 'src/graphql\/**\/*.{js,ts}' + */ + sdls: SdlGlobImports + + /** + * @description Directives passed from the glob import: + * import directives from 'src/directives/**\/*.{js,ts}' + */ + directives?: DirectiveGlobImports + + /** + * @description A list of options passed to [makeExecutableSchema] + * (https://www.graphql-tools.com/docs/generate-schema/#makeexecutableschemaoptions). + */ + schemaOptions?: Partial + + /** + * @description CORS configuration + */ + cors?: CorsConfig + + /** + * @description Customize GraphQL Armor plugin configuration + * + * @see https://escape-technologies.github.io/graphql-armor/docs/configuration/examples + */ + armorConfig?: ArmorConfig + + /** + * @description Customize the default error message used to mask errors. + * + * By default, the masked error message is "Something went wrong" + * + * @see https://github.com/dotansimha/envelop/blob/main/packages/core/docs/use-masked-errors.md + */ + defaultError?: string + + /** + * @description Only allows the specified operation types (e.g. subscription, query or mutation). + * + * By default, only allow query and mutation (ie, do not allow subscriptions). + * + * An array of GraphQL's OperationTypeNode enums: + * - OperationTypeNode.SUBSCRIPTION + * - OperationTypeNode.QUERY + * - OperationTypeNode.MUTATION + * + * @see https://github.com/dotansimha/envelop/tree/main/packages/plugins/filter-operation-type + */ + allowedOperations?: AllowedOperations + + /** + * @description Custom Envelop plugins + */ + extraPlugins?: Plugin[] + + /** + * @description Auth-provider specific token decoder + */ + authDecoder?: Decoder | Decoder[] + + /** + * @description Customize the GraphiQL Endpoint that appears in the location bar of the GraphQL Playground + * + * Defaults to '/graphql' as this value must match the name of the `graphql` function on the api-side. + */ + graphiQLEndpoint?: string + + /** + * @description Allow schema introspection. + * By default, schema introspection is disabled in production. Explicitly set this to true or false to override in all environments. + */ + allowIntrospection?: boolean + + /** + * @description Function that returns custom headers (as string) for GraphiQL. + * + * Headers must set auth-provider, Authorization and (if using dbAuth) the encrypted cookie. + */ + generateGraphiQLHeader?: GenerateGraphiQLHeader +} + +export interface GraphQLHandlerOptions extends GraphQLYogaOptions {} From 331fc33f2433691d7cfce5113abd44792e2fab8b Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Mon, 15 May 2023 13:38:19 -0400 Subject: [PATCH 03/17] Make a Fastify plugin for graphql yoga --- packages/fastify/build.mjs | 1 + packages/fastify/src/graphql.ts | 55 +++++++++++++++++++ packages/fastify/src/index.ts | 1 + .../graphql-server/src/createGraphQLYoga.ts | 3 +- 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 packages/fastify/src/graphql.ts diff --git a/packages/fastify/build.mjs b/packages/fastify/build.mjs index 4d3bfd40de1a..bc79ea07c2f9 100644 --- a/packages/fastify/build.mjs +++ b/packages/fastify/build.mjs @@ -4,6 +4,7 @@ await esbuild.build({ entryPoints: [ 'src/api.ts', 'src/config.ts', + 'src/graphql.ts', 'src/index.ts', 'src/types.ts', 'src/web.ts', diff --git a/packages/fastify/src/graphql.ts b/packages/fastify/src/graphql.ts new file mode 100644 index 000000000000..f6b18a6e1e45 --- /dev/null +++ b/packages/fastify/src/graphql.ts @@ -0,0 +1,55 @@ +import type { FastifyInstance } from 'fastify' + +import type { GraphQLYogaOptions } from '@redwoodjs/graphql-server' +import { createGraphQLYoga } from '@redwoodjs/graphql-server' +/** + * Encapsulates the routes + * @param {FastifyInstance} fastify Encapsulated Fastify Instance + * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options + */ +export async function redwoodFastifyGraphQLServer( + fastify: FastifyInstance, + options: GraphQLYogaOptions +) { + try { + const { yoga } = createGraphQLYoga({ + loggerConfig: { + logger: options.loggerConfig.logger, + options: { query: true, data: true, level: 'trace' }, + }, + + services: options.services, + // schemaOptions: liveSchema, + sdls: options.sdls, + directives: options.directives, + graphiQLEndpoint: '/yoga', + allowIntrospection: true, + }) + + fastify.route({ + url: yoga.graphqlEndpoint, + method: ['GET', 'POST', 'OPTIONS'], + handler: async (req, reply) => { + const response = await yoga.handleNodeRequest(req, { + req, + reply, + }) + for (const [name, value] of response.headers) { + reply.header(name, value) + } + + reply.status(response.status) + + reply.send(response.body) + + return reply + }, + }) + + fastify.ready(() => { + console.log(`GraphQL Yoga Server endpoint at ${yoga.graphqlEndpoint}`) + }) + } catch (e) { + console.log(e) + } +} diff --git a/packages/fastify/src/index.ts b/packages/fastify/src/index.ts index 5a7b7350ed94..fe92be932b3e 100644 --- a/packages/fastify/src/index.ts +++ b/packages/fastify/src/index.ts @@ -10,6 +10,7 @@ export function createFastifyInstance(options?: FastifyServerOptions) { export { redwoodFastifyAPI } from './api.js' export { redwoodFastifyWeb } from './web.js' +export { redwoodFastifyGraphQLServer } from './graphql.js' export type * from './types.js' diff --git a/packages/graphql-server/src/createGraphQLYoga.ts b/packages/graphql-server/src/createGraphQLYoga.ts index e12e8e13d095..22b569d7169d 100644 --- a/packages/graphql-server/src/createGraphQLYoga.ts +++ b/packages/graphql-server/src/createGraphQLYoga.ts @@ -178,7 +178,8 @@ redwood { }`, headerEditorEnabled: true, } - : false, + : // : false, + true, cors: (request: Request) => { const requestOrigin = request.headers.get('origin') return mapRwCorsOptionsToYoga(cors, requestOrigin) From 1ab47a294b26ebf9479290ee2411ac3e8bd89f8a Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Mon, 15 May 2023 13:42:49 -0400 Subject: [PATCH 04/17] Set yoga options --- packages/fastify/src/graphql.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/fastify/src/graphql.ts b/packages/fastify/src/graphql.ts index f6b18a6e1e45..3ecdf743df21 100644 --- a/packages/fastify/src/graphql.ts +++ b/packages/fastify/src/graphql.ts @@ -12,19 +12,7 @@ export async function redwoodFastifyGraphQLServer( options: GraphQLYogaOptions ) { try { - const { yoga } = createGraphQLYoga({ - loggerConfig: { - logger: options.loggerConfig.logger, - options: { query: true, data: true, level: 'trace' }, - }, - - services: options.services, - // schemaOptions: liveSchema, - sdls: options.sdls, - directives: options.directives, - graphiQLEndpoint: '/yoga', - allowIntrospection: true, - }) + const { yoga } = createGraphQLYoga(options) fastify.route({ url: yoga.graphqlEndpoint, From bd6def54ab9f85f4f90d11aabb2b332ea84e5ce5 Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Mon, 15 May 2023 13:45:15 -0400 Subject: [PATCH 05/17] Adds graphql fastify plugin to template --- .../experimental/templates/server.ts.template | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/commands/experimental/templates/server.ts.template b/packages/cli/src/commands/experimental/templates/server.ts.template index 4a9680a49c98..6323d42fb6d9 100644 --- a/packages/cli/src/commands/experimental/templates/server.ts.template +++ b/packages/cli/src/commands/experimental/templates/server.ts.template @@ -1,5 +1,6 @@ import path from 'path' +// import { useLiveQuery } from '@envelop/live-query' import chalk from 'chalk' import { config } from 'dotenv-defaults' import Fastify from 'fastify' @@ -8,13 +9,26 @@ import { coerceRootPath, redwoodFastifyWeb, redwoodFastifyAPI, + redwoodFastifyGraphQLServer, DEFAULT_REDWOOD_FASTIFY_CONFIG, } from '@redwoodjs/fastify' import { getPaths, getConfig } from '@redwoodjs/project-config' +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' + +import { logger } from './lib/logger' + async function serve() { // Load .env files const redwoodProjectPaths = getPaths() + const redwoodConfig = getConfig() + + const apiRootPath = coerceRootPath(redwoodConfig.web.apiUrl) + const port = redwoodConfig.web.port + + const tsServer = Date.now() config({ path: path.join(redwoodProjectPaths.base, '.env'), @@ -22,7 +36,6 @@ async function serve() { multiline: true, }) - const tsServer = Date.now() console.log(chalk.italic.dim('Starting API and Web Servers...')) // Configure Fastify @@ -30,11 +43,6 @@ async function serve() { ...DEFAULT_REDWOOD_FASTIFY_CONFIG, }) - const redwoodConfig = getConfig() - - const apiRootPath = coerceRootPath(redwoodConfig.web.apiUrl) - const port = redwoodConfig.web.port - await fastify.register(redwoodFastifyWeb) await fastify.register(redwoodFastifyAPI, { @@ -43,6 +51,19 @@ async function serve() { }, }) + fastify.register(redwoodFastifyGraphQLServer, { + loggerConfig: { + logger: logger, + options: { query: true, data: true, level: 'trace' }, + }, + + graphiQLEndpoint: '/yoga', + sdls, + services, + directives, + allowIntrospection: true, + }) + // Start fastify.listen({ port }) @@ -55,7 +76,7 @@ async function serve() { console.log(`API serving from ${apiServer}`) console.log(`API listening on ${on}`) const graphqlEnd = chalk.magenta(`${apiRootPath}graphql`) - console.log(`GraphQL endpoint at ${graphqlEnd}`) + console.log(`GraphQL serverless function endpoint at ${graphqlEnd}`) }) process.on('exit', () => { From 143f2ac718d6fa0d0b62faa2202ccb830ebb1345 Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Tue, 16 May 2023 08:20:41 -0400 Subject: [PATCH 06/17] Whitespace --- .../cli/src/commands/experimental/templates/server.ts.template | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/src/commands/experimental/templates/server.ts.template b/packages/cli/src/commands/experimental/templates/server.ts.template index 6323d42fb6d9..2d8119f2f92e 100644 --- a/packages/cli/src/commands/experimental/templates/server.ts.template +++ b/packages/cli/src/commands/experimental/templates/server.ts.template @@ -56,7 +56,6 @@ async function serve() { logger: logger, options: { query: true, data: true, level: 'trace' }, }, - graphiQLEndpoint: '/yoga', sdls, services, From 6efefc1c3febc7c43432e902877ae2d7399aba67 Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Tue, 16 May 2023 11:05:38 -0400 Subject: [PATCH 07/17] Adds allowGraphiQL config setting and docs --- docs/docs/graphql.md | 10 ++- .../experimental/templates/server.ts.template | 1 + packages/fastify/src/graphql.ts | 2 +- .../graphql-server/src/createGraphQLYoga.ts | 65 +++++++++++++------ .../graphql-server/src/functions/graphql.ts | 2 + packages/graphql-server/src/types.ts | 6 ++ 6 files changed, 62 insertions(+), 24 deletions(-) diff --git a/docs/docs/graphql.md b/docs/docs/graphql.md index f1b83de0e34e..93408d3bcedd 100644 --- a/docs/docs/graphql.md +++ b/docs/docs/graphql.md @@ -1452,9 +1452,9 @@ The [GraphQL Playground](https://www.graphql-yoga.com/docs/features/graphiql) is > Because both introspection and the playground share possibly sensitive information about your data model, your data, your queries and mutations, best practices for deploying a GraphQL Server call to disable these in production, RedwoodJS **, by default, only enables introspection and the playground when running in development**. That is when `process.env.NODE_ENV === 'development'`. -However, there may be cases where you want to enable introspection. You can enable introspection by setting the `allowIntrospection` option to `true`. +However, there may be cases where you want to enable introspection as well as the GraphQL PLaygrouns. You can enable introspection by setting the `allowIntrospection` option to `true` and enable GraphiQL by setting `allowGraphiQL` to `true`. -Here is an example of `createGraphQLHandler` function with the `allowIntrospection` option set to `true`: +Here is an example of `createGraphQLHandler` function with the `allowIntrospection` and `allowGraphiQL` options set to `true`: ```ts {8} export const handler = createGraphQLHandler({ authDecoder, @@ -1464,6 +1464,7 @@ export const handler = createGraphQLHandler({ sdls, services, allowIntrospection: true, // 👈 enable introspection in all environments + allowGraphiQL: true, // 👈 enable introspection in all environments onException: () => { // Disconnect from your database with an unhandled exception. db.$disconnect() @@ -1475,8 +1476,13 @@ export const handler = createGraphQLHandler({ Enabling introspection in production may pose a security risk, as it allows users to access information about your schema, queries, and mutations. Use this option with caution and make sure to secure your GraphQL API properly. +The may be cases where one wants to allow introspection, but not GraphiQL. + +Or, you may want to enable GraphiQL, but not allow introspection; for example, to try out known queries, but not to share the entire set of possible operations and types. + ::: + ### GraphQL Armor Configuration [GraphQL Armor](https://escape.tech/graphql-armor/) is a middleware that adds a security layer the RedwoodJS GraphQL endpoint configured with sensible defaults. diff --git a/packages/cli/src/commands/experimental/templates/server.ts.template b/packages/cli/src/commands/experimental/templates/server.ts.template index 2d8119f2f92e..8e4a42c51a00 100644 --- a/packages/cli/src/commands/experimental/templates/server.ts.template +++ b/packages/cli/src/commands/experimental/templates/server.ts.template @@ -61,6 +61,7 @@ async function serve() { services, directives, allowIntrospection: true, + allowGraphiQL: true, }) // Start diff --git a/packages/fastify/src/graphql.ts b/packages/fastify/src/graphql.ts index 3ecdf743df21..b2e3c47b3703 100644 --- a/packages/fastify/src/graphql.ts +++ b/packages/fastify/src/graphql.ts @@ -22,12 +22,12 @@ export async function redwoodFastifyGraphQLServer( req, reply, }) + for (const [name, value] of response.headers) { reply.header(name, value) } reply.status(response.status) - reply.send(response.body) return reply diff --git a/packages/graphql-server/src/createGraphQLYoga.ts b/packages/graphql-server/src/createGraphQLYoga.ts index 22b569d7169d..6db433657932 100644 --- a/packages/graphql-server/src/createGraphQLYoga.ts +++ b/packages/graphql-server/src/createGraphQLYoga.ts @@ -39,6 +39,7 @@ export const createGraphQLYoga = ({ armorConfig, allowedOperations, allowIntrospection, + allowGraphiQL, defaultError = 'Something went wrong.', graphiQLEndpoint = '/graphql', schemaOptions, @@ -77,14 +78,50 @@ export const createGraphQLYoga = ({ try { // Important: Plugins are executed in order of their usage, and inject functionality serially, // so the order here matters - const isDevEnv = process.env.NODE_ENV === 'development' - const plugins: Array> = [] - if ( - (allowIntrospection == null && !isDevEnv) || - allowIntrospection === false - ) { + const isDevEnv = process.env.NODE_ENV === 'development' + const disableIntrospection = + (allowIntrospection === null && !isDevEnv) || allowIntrospection === false + const disableGraphQL = + (allowGraphiQL === null && !isDevEnv) || allowGraphiQL === false + + const defaultQuery = `query Redwood { + redwood { + version + } + }` + + // TODO: Once Studio is not experimental, can remove these generateGraphiQLHeaders + const authHeader = `{"x-auth-comment": "See documentation: https://redwoodjs.com/docs/cli-commands#setup-graphiQL-headers on how to auto generate auth headers"}` + + const graphiql = !disableGraphQL + ? { + title: 'Redwood GraphQL Playground', + headers: generateGraphiQLHeader + ? generateGraphiQLHeader() + : authHeader, + defaultQuery, + headerEditorEnabled: true, + } + : false + + logger.debug( + { + healthCheckId, + allowedOperations, + allowIntrospection, + defaultError, + disableIntrospection, + disableGraphQL, + allowGraphiQL, + graphiql, + graphiQLEndpoint, + }, + 'GraphiQL and Introspection Config' + ) + + if (disableIntrospection) { plugins.push(useDisableIntrospection()) } @@ -165,21 +202,7 @@ export const createGraphQLYoga = ({ logging: logger, healthCheckEndpoint: graphiQLEndpoint + '/health', graphqlEndpoint: graphiQLEndpoint, - graphiql: isDevEnv - ? { - title: 'Redwood GraphQL Playground', - headers: generateGraphiQLHeader - ? generateGraphiQLHeader() - : `{"x-auth-comment": "See documentation: https://redwoodjs.com/docs/cli-commands#setup-graphiQL-headers on how to auto generate auth headers"}`, - defaultQuery: `query Redwood { -redwood { - version -} -}`, - headerEditorEnabled: true, - } - : // : false, - true, + graphiql, cors: (request: Request) => { const requestOrigin = request.headers.get('origin') return mapRwCorsOptionsToYoga(cors, requestOrigin) diff --git a/packages/graphql-server/src/functions/graphql.ts b/packages/graphql-server/src/functions/graphql.ts index 68a925c8d94b..54e60fe1a781 100644 --- a/packages/graphql-server/src/functions/graphql.ts +++ b/packages/graphql-server/src/functions/graphql.ts @@ -35,6 +35,7 @@ export const createGraphQLHandler = ({ armorConfig, allowedOperations, allowIntrospection, + allowGraphiQL, defaultError = 'Something went wrong.', graphiQLEndpoint = '/graphql', schemaOptions, @@ -64,6 +65,7 @@ export const createGraphQLHandler = ({ armorConfig, allowedOperations, allowIntrospection, + allowGraphiQL, defaultError, graphiQLEndpoint, schemaOptions, diff --git a/packages/graphql-server/src/types.ts b/packages/graphql-server/src/types.ts index a1d634d432e5..92375ddb7250 100644 --- a/packages/graphql-server/src/types.ts +++ b/packages/graphql-server/src/types.ts @@ -188,6 +188,12 @@ export interface GraphQLYogaOptions { */ graphiQLEndpoint?: string + /** + * @description Allow GraphiQL playground. + * By default, GraphiQL playground is disabled in production. Explicitly set this to true or false to override in all environments. + */ + allowGraphiQL?: boolean + /** * @description Allow schema introspection. * By default, schema introspection is disabled in production. Explicitly set this to true or false to override in all environments. From 66706f0479eaa70c3189a842c32ca33823d50d61 Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Tue, 16 May 2023 11:38:04 -0400 Subject: [PATCH 08/17] fastify gql plugin awaits --- .../commands/experimental/templates/server.ts.template | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/experimental/templates/server.ts.template b/packages/cli/src/commands/experimental/templates/server.ts.template index 8e4a42c51a00..05dc6991342c 100644 --- a/packages/cli/src/commands/experimental/templates/server.ts.template +++ b/packages/cli/src/commands/experimental/templates/server.ts.template @@ -4,6 +4,7 @@ import path from 'path' import chalk from 'chalk' import { config } from 'dotenv-defaults' import Fastify from 'fastify' +import { OperationTypeNode } from 'graphql' import { coerceRootPath, @@ -51,7 +52,7 @@ async function serve() { }, }) - fastify.register(redwoodFastifyGraphQLServer, { + await fastify.register(redwoodFastifyGraphQLServer, { loggerConfig: { logger: logger, options: { query: true, data: true, level: 'trace' }, @@ -62,6 +63,11 @@ async function serve() { directives, allowIntrospection: true, allowGraphiQL: true, + allowedOperations: [ + OperationTypeNode.SUBSCRIPTION, + OperationTypeNode.QUERY, + OperationTypeNode.MUTATION, + ], }) // Start From 45479fdc53b89a39f378bce75a987892c089609d Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Wed, 17 May 2023 09:38:22 -0400 Subject: [PATCH 09/17] Merge in subscriptions into schema from project --- .../graphql-server/src/createGraphQLYoga.ts | 6 +++ .../graphql-server/src/makeMergedSchema.ts | 44 +++++++++++++++++-- .../src/subscriptions/makeSubscriptions.ts | 39 ++++++++++++++++ packages/graphql-server/src/types.ts | 7 +++ .../templates/api-globImports.d.ts.template | 1 + 5 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 packages/graphql-server/src/subscriptions/makeSubscriptions.ts diff --git a/packages/graphql-server/src/createGraphQLYoga.ts b/packages/graphql-server/src/createGraphQLYoga.ts index 6db433657932..a81847666905 100644 --- a/packages/graphql-server/src/createGraphQLYoga.ts +++ b/packages/graphql-server/src/createGraphQLYoga.ts @@ -21,6 +21,7 @@ import type { useRedwoodDirectiveReturn, DirectivePluginOptions, } from './plugins/useRedwoodDirective' +import { makeSubscriptions } from './subscriptions/makeSubscriptions' import type { GraphQLYogaOptions } from './types' export const createGraphQLYoga = ({ @@ -36,6 +37,7 @@ export const createGraphQLYoga = ({ services, sdls, directives = [], + subscriptions = [], armorConfig, allowedOperations, allowIntrospection, @@ -59,10 +61,14 @@ export const createGraphQLYoga = ({ ) } + // @NOTE: Subscriptions are optional and only work in the context of a server + const projectSubscriptions = makeSubscriptions(subscriptions) + schema = makeMergedSchema({ sdls, services, directives: projectDirectives, + subscriptions: projectSubscriptions, schemaOptions, }) } catch (e) { diff --git a/packages/graphql-server/src/makeMergedSchema.ts b/packages/graphql-server/src/makeMergedSchema.ts index 24c07d9c125a..fe4de955a759 100644 --- a/packages/graphql-server/src/makeMergedSchema.ts +++ b/packages/graphql-server/src/makeMergedSchema.ts @@ -4,7 +4,7 @@ import { makeExecutableSchema, IExecutableSchemaDefinition, } from '@graphql-tools/schema' -import { IResolvers } from '@graphql-tools/utils' +import { IResolvers, IResolverValidationOptions } from '@graphql-tools/utils' import * as opentelemetry from '@opentelemetry/api' import type { GraphQLSchema, @@ -20,6 +20,8 @@ import { getConfig } from '@redwoodjs/project-config' import type { RedwoodDirective } from './plugins/useRedwoodDirective' import * as rootGqlSchema from './rootSchema' +import type { RedwoodSubscription } from './subscriptions/makeSubscriptions' +// import { makeSubscriptions } from './subscriptions/makeSubscriptions' import { Services, ServicesGlobImports, @@ -239,7 +241,7 @@ const mergeResolversWithServices = ({ // after the type. // Example: export const MyType = { field: () => {} } let servicesForType = mergedServices - if (!['Query', 'Mutation'].includes(type.name)) { + if (!['Query', 'Mutation', 'Subscription'].includes(type.name)) { servicesForType = mergedServices?.[type.name] } @@ -330,15 +332,45 @@ const mergeTypes = ( }) } +const mergeResolversWithSubscriptions = ({ + schema, + subscriptions, + resolverValidationOptions, + inheritResolversFromInterfaces, +}: { + schema: GraphQLSchema + subscriptions: RedwoodSubscription[] + resolverValidationOptions?: IResolverValidationOptions | undefined + inheritResolversFromInterfaces?: boolean | undefined +}) => { + const subscriptionResolvers = { Subscription: {} } as IResolvers + + subscriptions?.forEach((subscription) => { + subscriptionResolvers['Subscription'] = { + ...subscriptionResolvers['Subscription'], + ...subscription.resolvers, + } + }) + + return addResolversToSchema({ + schema, + resolvers: subscriptionResolvers, + resolverValidationOptions, + inheritResolversFromInterfaces, + }) +} + export const makeMergedSchema = ({ sdls, services, schemaOptions = {}, directives, + subscriptions = [], }: { sdls: SdlGlobImports services: ServicesGlobImports directives: RedwoodDirective[] + subscriptions: RedwoodSubscription[] /** * A list of options passed to [makeExecutableSchema](https://www.graphql-tools.com/docs/generate-schema/#makeexecutableschemaoptions). @@ -351,6 +383,7 @@ export const makeMergedSchema = ({ [ rootGqlSchema.schema, ...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 ], { all: true } @@ -369,11 +402,16 @@ export const makeMergedSchema = ({ services, }) + const schemaWithSubscriptions = mergeResolversWithSubscriptions({ + schema, + subscriptions, + }) + const { resolverValidationOptions, inheritResolversFromInterfaces } = schemaOptions || {} return addResolversToSchema({ - schema, + schema: schemaWithSubscriptions, resolvers, resolverValidationOptions, inheritResolversFromInterfaces, diff --git a/packages/graphql-server/src/subscriptions/makeSubscriptions.ts b/packages/graphql-server/src/subscriptions/makeSubscriptions.ts new file mode 100644 index 000000000000..69f831ea8576 --- /dev/null +++ b/packages/graphql-server/src/subscriptions/makeSubscriptions.ts @@ -0,0 +1,39 @@ +import { DocumentNode } from 'graphql' +/* +We want SubscriptionsGlobs type to be an object with this shape: +But not fully supported in TS +{ + schema: DocumentNode // <-- required + [string]: RedwoodSubscription +} +*/ +export type SubscriptionGlobImports = Record + +export type RedwoodSubscription = { + schema: DocumentNode + resolvers: any + name: string +} + +export const makeSubscriptions = ( + SubscriptionGlobs: SubscriptionGlobImports +): RedwoodSubscription[] => { + return Object.entries(SubscriptionGlobs).flatMap( + ([importedGlobName, exports]) => { + // In case the Subscriptions get nested, their name comes as nested_directory_filename_Subscription + + // SubscriptionName is the filename without the Subscription extension + // slice gives us ['fileName', 'Subscription'], so we take the first one + const [SubscriptionNameFromFile] = importedGlobName.split('_').slice(-2) + + // We support exporting both Subscription name and default + const subscription = { + schema: exports.schema, + resolvers: exports[SubscriptionNameFromFile] || exports.default, + name: SubscriptionNameFromFile, + } as RedwoodSubscription + + return [subscription] + } + ) +} diff --git a/packages/graphql-server/src/types.ts b/packages/graphql-server/src/types.ts index 92375ddb7250..4629f97094d0 100644 --- a/packages/graphql-server/src/types.ts +++ b/packages/graphql-server/src/types.ts @@ -9,6 +9,7 @@ import type { AuthContextPayload, Decoder } from '@redwoodjs/api' import { CorsConfig } from '@redwoodjs/api' import { DirectiveGlobImports } from 'src/directives/makeDirectives' +import type { SubscriptionGlobImports } from 'src/subscriptions/makeSubscriptions' import type { useRedwoodDirectiveReturn, @@ -130,6 +131,12 @@ export interface GraphQLYogaOptions { */ directives?: DirectiveGlobImports + /** + * @description Subscriptions passed from the glob import: + * import subscriptions from 'src/subscriptions/**\/*.{js,ts}' + */ + subscriptions?: SubscriptionGlobImports + /** * @description A list of options passed to [makeExecutableSchema] * (https://www.graphql-tools.com/docs/generate-schema/#makeexecutableschemaoptions). diff --git a/packages/internal/src/generate/templates/api-globImports.d.ts.template b/packages/internal/src/generate/templates/api-globImports.d.ts.template index d7ecec563318..fc001070b8b7 100644 --- a/packages/internal/src/generate/templates/api-globImports.d.ts.template +++ b/packages/internal/src/generate/templates/api-globImports.d.ts.template @@ -1,3 +1,4 @@ declare module 'src/services/**/*.{js,ts}' declare module 'src/directives/**/*.{js,ts}' declare module 'src/graphql/**/*.sdl.{js,ts}' +declare module 'src/subscriptions/**/*.sdl.{js,ts}' From 29960833034651596ae62119bdc4498e27ba580a Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Wed, 17 May 2023 12:36:42 -0400 Subject: [PATCH 10/17] Get to green on tests --- .../src/__tests__/makeMergedSchema.test.ts | 1 + .../graphql-server/src/makeMergedSchema.ts | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/graphql-server/src/__tests__/makeMergedSchema.test.ts b/packages/graphql-server/src/__tests__/makeMergedSchema.test.ts index 050b84ca46b0..0e0be5f9b3de 100644 --- a/packages/graphql-server/src/__tests__/makeMergedSchema.test.ts +++ b/packages/graphql-server/src/__tests__/makeMergedSchema.test.ts @@ -138,6 +138,7 @@ describe('makeMergedSchema', () => { const schema = makeMergedSchema({ sdls, services, + subscriptions: [], directives: makeDirectivesForPlugin(directiveFiles), }) diff --git a/packages/graphql-server/src/makeMergedSchema.ts b/packages/graphql-server/src/makeMergedSchema.ts index fe4de955a759..e2f3c2a2c28c 100644 --- a/packages/graphql-server/src/makeMergedSchema.ts +++ b/packages/graphql-server/src/makeMergedSchema.ts @@ -343,21 +343,24 @@ const mergeResolversWithSubscriptions = ({ resolverValidationOptions?: IResolverValidationOptions | undefined inheritResolversFromInterfaces?: boolean | undefined }) => { - const subscriptionResolvers = { Subscription: {} } as IResolvers + if (subscriptions && subscriptions.length > 0) { + const subscriptionResolvers = { Subscription: {} } as IResolvers - subscriptions?.forEach((subscription) => { - subscriptionResolvers['Subscription'] = { - ...subscriptionResolvers['Subscription'], - ...subscription.resolvers, - } - }) + subscriptions?.forEach((subscription) => { + subscriptionResolvers['Subscription'] = { + ...subscriptionResolvers['Subscription'], + ...subscription.resolvers, + } + }) - return addResolversToSchema({ - schema, - resolvers: subscriptionResolvers, - resolverValidationOptions, - inheritResolversFromInterfaces, - }) + return addResolversToSchema({ + schema, + resolvers: subscriptionResolvers, + resolverValidationOptions, + inheritResolversFromInterfaces, + }) + } + return schema } export const makeMergedSchema = ({ From 3ded6109fe0ae2fdcd09789a535e5d5f29ba725c Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Thu, 18 May 2023 10:22:47 -0400 Subject: [PATCH 11/17] Fix subscriptions module api import --- .../src/generate/templates/api-globImports.d.ts.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/internal/src/generate/templates/api-globImports.d.ts.template b/packages/internal/src/generate/templates/api-globImports.d.ts.template index fc001070b8b7..51e248fd5f82 100644 --- a/packages/internal/src/generate/templates/api-globImports.d.ts.template +++ b/packages/internal/src/generate/templates/api-globImports.d.ts.template @@ -1,4 +1,4 @@ declare module 'src/services/**/*.{js,ts}' declare module 'src/directives/**/*.{js,ts}' declare module 'src/graphql/**/*.sdl.{js,ts}' -declare module 'src/subscriptions/**/*.sdl.{js,ts}' +declare module 'src/subscriptions/**/*.{js,ts}' From 3dd0ab21a3bd821aade4a95ba58516be03729e5b Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Thu, 18 May 2023 10:42:11 -0400 Subject: [PATCH 12/17] Tests makeSubscriptions --- .../src/__tests__/makeDirectives.test.ts | 2 +- .../src/__tests__/makeSubscriptions.test.ts | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 packages/graphql-server/src/__tests__/makeSubscriptions.test.ts diff --git a/packages/graphql-server/src/__tests__/makeDirectives.test.ts b/packages/graphql-server/src/__tests__/makeDirectives.test.ts index 36ed7c0f5035..e4a0af0a14eb 100644 --- a/packages/graphql-server/src/__tests__/makeDirectives.test.ts +++ b/packages/graphql-server/src/__tests__/makeDirectives.test.ts @@ -18,7 +18,7 @@ const bazingaSchema = gql` const barSchema = gql` directive @bar on FIELD_DEFINITION ` -test('Should map globs to defined structure correctly', async () => { +test('Should map directives globs to defined structure correctly', async () => { // Mocking what our import-dir plugin would do const directiveFiles = { foo_directive: { diff --git a/packages/graphql-server/src/__tests__/makeSubscriptions.test.ts b/packages/graphql-server/src/__tests__/makeSubscriptions.test.ts new file mode 100644 index 000000000000..d7ea6825de2c --- /dev/null +++ b/packages/graphql-server/src/__tests__/makeSubscriptions.test.ts @@ -0,0 +1,70 @@ +import gql from 'graphql-tag' + +import { makeSubscriptions } from '../subscriptions/makeSubscriptions' +const countdownSchema = gql` + type Subscription { + countdown(from: Int!, interval: Int!): Int! + } +` + +const newMessageSchema = gql` + type Message { + from: String + body: String + } + + type Subscription { + newMessage(roomId: ID!): Message! + } +` +describe('Should map subscription globs to defined structure correctly', () => { + it('Should map a subscribe correctly', async () => { + // Mocking what our import-dir plugin would do + const subscriptionFiles = { + countdown_subscription: { + schema: countdownSchema, + countdown: { + async *subscribe(_, { from, interval }) { + for (let i = from; i >= 0; i--) { + await new Promise((resolve) => + setTimeout(resolve, interval ?? 1000) + ) + yield { countdown: i } + } + }, + }, + }, + } + + const [countdownSubscription] = makeSubscriptions(subscriptionFiles) + + expect(countdownSubscription.schema.kind).toBe('Document') + expect(countdownSubscription.name).toBe('countdown') + expect(countdownSubscription.resolvers.subscribe).toBeDefined() + expect(countdownSubscription.resolvers.resolve).not.toBeDefined() + }) + + it('Should map a subscribe and resolve correctly', async () => { + // Mocking what our import-dir plugin would do + const subscriptionFiles = { + newMessage_subscription: { + schema: newMessageSchema, + newMessage: { + subscribe: (_, { roomId }) => { + return roomId + }, + resolve: (payload) => { + return payload + }, + }, + }, + } + + const [newMessageSubscription] = makeSubscriptions(subscriptionFiles) + + expect(newMessageSubscription.schema.kind).toBe('Document') + expect(newMessageSubscription.name).toBe('newMessage') + expect(newMessageSubscription.resolvers.subscribe).toBeDefined() + expect(newMessageSubscription.resolvers.resolve).toBeDefined() + }) +}) From cf04402a2fe5c4aa074c7a6d3958b05a1bd0280b Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Thu, 18 May 2023 11:33:59 -0400 Subject: [PATCH 13/17] Fastify graphql plugin needed graphqlserver package --- packages/fastify/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/fastify/package.json b/packages/fastify/package.json index 9c18879e8f72..68cbf7c83720 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -21,6 +21,7 @@ "@fastify/http-proxy": "9.1.0", "@fastify/static": "6.10.1", "@fastify/url-data": "5.3.1", + "@redwoodjs/graphql-server": "5.0.0", "@redwoodjs/project-config": "5.0.0", "ansi-colors": "4.1.3", "fast-glob": "3.2.12", diff --git a/yarn.lock b/yarn.lock index 906f84035a3e..1ee27e474d92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7160,6 +7160,7 @@ __metadata: "@fastify/http-proxy": 9.1.0 "@fastify/static": 6.10.1 "@fastify/url-data": 5.3.1 + "@redwoodjs/graphql-server": 5.0.0 "@redwoodjs/project-config": 5.0.0 "@types/aws-lambda": 8.10.115 "@types/lodash.escape": 4.0.7 From 71c41669169741a67c59545217b5aa6464f8cbda Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Thu, 18 May 2023 13:33:41 -0400 Subject: [PATCH 14/17] remove unneeded comments --- packages/graphql-server/src/makeMergedSchema.ts | 1 - packages/graphql-server/src/plugins/useRedwoodAuthContext.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/graphql-server/src/makeMergedSchema.ts b/packages/graphql-server/src/makeMergedSchema.ts index e2f3c2a2c28c..5779e680d934 100644 --- a/packages/graphql-server/src/makeMergedSchema.ts +++ b/packages/graphql-server/src/makeMergedSchema.ts @@ -21,7 +21,6 @@ import { getConfig } from '@redwoodjs/project-config' import type { RedwoodDirective } from './plugins/useRedwoodDirective' import * as rootGqlSchema from './rootSchema' import type { RedwoodSubscription } from './subscriptions/makeSubscriptions' -// import { makeSubscriptions } from './subscriptions/makeSubscriptions' import { Services, ServicesGlobImports, diff --git a/packages/graphql-server/src/plugins/useRedwoodAuthContext.ts b/packages/graphql-server/src/plugins/useRedwoodAuthContext.ts index 3ea72dd5864d..c957eb8f5885 100644 --- a/packages/graphql-server/src/plugins/useRedwoodAuthContext.ts +++ b/packages/graphql-server/src/plugins/useRedwoodAuthContext.ts @@ -6,7 +6,6 @@ import { Decoder, } from '@redwoodjs/api' -// import { AuthenticationError } from '../errors' import { RedwoodGraphQLContext, GraphQLHandlerOptions } from '../types' /** From 70ee2bde83d578a5a0ec23d0ee7ee898292745b6 Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Thu, 18 May 2023 16:11:19 -0400 Subject: [PATCH 15/17] Update docs/docs/graphql.md --- docs/docs/graphql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/graphql.md b/docs/docs/graphql.md index 93408d3bcedd..7d1b0629d742 100644 --- a/docs/docs/graphql.md +++ b/docs/docs/graphql.md @@ -1464,7 +1464,7 @@ export const handler = createGraphQLHandler({ sdls, services, allowIntrospection: true, // 👈 enable introspection in all environments - allowGraphiQL: true, // 👈 enable introspection in all environments + allowGraphiQL: true, // 👈 enable GraphiQL Playground in all environments onException: () => { // Disconnect from your database with an unhandled exception. db.$disconnect() From b16e4efd8c9f0aa373cff9e5385f60bce884af64 Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Thu, 18 May 2023 16:12:45 -0400 Subject: [PATCH 16/17] Adds HookHandlerDoneFunction --- packages/fastify/src/graphql.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/fastify/src/graphql.ts b/packages/fastify/src/graphql.ts index b2e3c47b3703..86e9ce8a6488 100644 --- a/packages/fastify/src/graphql.ts +++ b/packages/fastify/src/graphql.ts @@ -1,4 +1,4 @@ -import type { FastifyInstance } from 'fastify' +import type { FastifyInstance, HookHandlerDoneFunction } from 'fastify' import type { GraphQLYogaOptions } from '@redwoodjs/graphql-server' import { createGraphQLYoga } from '@redwoodjs/graphql-server' @@ -9,7 +9,8 @@ import { createGraphQLYoga } from '@redwoodjs/graphql-server' */ export async function redwoodFastifyGraphQLServer( fastify: FastifyInstance, - options: GraphQLYogaOptions + options: GraphQLYogaOptions, + done: HookHandlerDoneFunction ) { try { const { yoga } = createGraphQLYoga(options) @@ -37,6 +38,8 @@ export async function redwoodFastifyGraphQLServer( fastify.ready(() => { console.log(`GraphQL Yoga Server endpoint at ${yoga.graphqlEndpoint}`) }) + + done() } catch (e) { console.log(e) } From de551569d96e1860b4c0be21d8f61b8b49d4b2db Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Thu, 18 May 2023 16:13:40 -0400 Subject: [PATCH 17/17] Update packages/cli/src/commands/experimental/templates/server.ts.template Co-authored-by: Dominic Saadi --- .../cli/src/commands/experimental/templates/server.ts.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/experimental/templates/server.ts.template b/packages/cli/src/commands/experimental/templates/server.ts.template index 05dc6991342c..f359867cc49b 100644 --- a/packages/cli/src/commands/experimental/templates/server.ts.template +++ b/packages/cli/src/commands/experimental/templates/server.ts.template @@ -82,7 +82,7 @@ async function serve() { console.log(`API serving from ${apiServer}`) console.log(`API listening on ${on}`) const graphqlEnd = chalk.magenta(`${apiRootPath}graphql`) - console.log(`GraphQL serverless function endpoint at ${graphqlEnd}`) + console.log(`GraphQL function endpoint at ${graphqlEnd}`) }) process.on('exit', () => {