-
Notifications
You must be signed in to change notification settings - Fork 565
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support a factory function and promise as a schema input (#1497)
* Support a factory function and promise as a schema input * Less diff
- Loading branch information
Showing
5 changed files
with
259 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
--- | ||
'graphql-yoga': minor | ||
--- | ||
|
||
Support a schema factory function that runs per request or a promise to be resolved before the first request. | ||
|
||
```ts | ||
createYoga({ | ||
schema(request: Request) { | ||
return getSchemaForToken(request.headers.get('x-my-token')) | ||
}, | ||
}) | ||
``` | ||
|
||
```ts | ||
async function buildSchemaAsync() { | ||
const typeDefs = await fs.promises.readFile('./schema.graphql', 'utf8') | ||
const resolvers = await import('./resolvers.js') | ||
return makeExecutableSchema({ typeDefs, resolvers }) | ||
} | ||
|
||
createYoga({ | ||
schema: buildSchemaAsync(), | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { makeExecutableSchema } from '@graphql-tools/schema' | ||
import { GraphQLSchema } from 'graphql' | ||
import { createYoga, YogaInitialContext } from 'graphql-yoga' | ||
|
||
describe('useSchema', () => { | ||
it('should accept a factory function', async () => { | ||
let count = 0 | ||
const schemaFactory = async (request: Request) => { | ||
const countFromContext = request.headers.get('count') | ||
return makeExecutableSchema<YogaInitialContext>({ | ||
typeDefs: /* GraphQL */ ` | ||
type Query { | ||
foo${countFromContext}: Boolean | ||
} | ||
`, | ||
resolvers: { | ||
Query: { | ||
[`foo${countFromContext}`]: (_, __, { request }) => | ||
countFromContext === request.headers.get('count'), | ||
}, | ||
}, | ||
}) | ||
} | ||
const yoga = createYoga({ | ||
schema: schemaFactory, | ||
}) | ||
while (true) { | ||
if (count === 3) { | ||
break | ||
} | ||
count++ | ||
const query = /* GraphQL */ ` | ||
query { | ||
foo${count} | ||
} | ||
` | ||
const result = await yoga.fetch('http://localhost:3000/graphql', { | ||
method: 'POST', | ||
body: JSON.stringify({ query }), | ||
headers: { | ||
count: count.toString(), | ||
'Content-Type': 'application/json', | ||
}, | ||
}) | ||
const { data } = await result.json() | ||
expect(data).toEqual({ | ||
[`foo${count}`]: true, | ||
}) | ||
} | ||
expect.assertions(3) | ||
}) | ||
it('should accept a promise', async () => { | ||
const schemaPromise = new Promise<GraphQLSchema>((resolve) => { | ||
setTimeout(() => { | ||
resolve( | ||
makeExecutableSchema({ | ||
typeDefs: /* GraphQL */ ` | ||
type Query { | ||
foo: Boolean | ||
} | ||
`, | ||
resolvers: { | ||
Query: { | ||
foo: () => true, | ||
}, | ||
}, | ||
}), | ||
) | ||
}, 300) | ||
}) | ||
const yoga = createYoga({ | ||
schema: schemaPromise, | ||
}) | ||
const query = /* GraphQL */ ` | ||
query { | ||
foo | ||
} | ||
` | ||
const result = await yoga.fetch('http://localhost:3000/graphql', { | ||
method: 'POST', | ||
body: JSON.stringify({ query }), | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}) | ||
const { data } = await result.json() | ||
expect(data).toEqual({ | ||
foo: true, | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { makeExecutableSchema } from '@graphql-tools/schema' | ||
import { IResolvers, TypeSource } from '@graphql-tools/utils' | ||
import { GraphQLError, GraphQLSchema, isSchema } from 'graphql' | ||
import { Plugin, PromiseOrValue, YogaInitialContext } from 'graphql-yoga' | ||
|
||
// TODO: Will be removed later | ||
type TypeDefsAndResolvers<TContext, TRootValue = {}> = { | ||
typeDefs: TypeSource | ||
resolvers?: | ||
| IResolvers<TRootValue, TContext> | ||
| Array<IResolvers<TRootValue, TContext>> | ||
} | ||
|
||
export type YogaSchemaDefinition<TContext, TRootValue> = | ||
| TypeDefsAndResolvers<TContext, TRootValue> | ||
| PromiseOrValue<GraphQLSchema> | ||
| ((request: Request) => PromiseOrValue<GraphQLSchema>) | ||
|
||
// Will be moved to a seperate export later | ||
export function getDefaultSchema() { | ||
return makeExecutableSchema({ | ||
typeDefs: /* GraphQL */ ` | ||
""" | ||
Greetings from GraphQL Yoga! | ||
""" | ||
type Query { | ||
greetings: String | ||
} | ||
type Subscription { | ||
""" | ||
Current Time | ||
""" | ||
time: String | ||
} | ||
`, | ||
resolvers: { | ||
Query: { | ||
greetings: () => | ||
'This is the `greetings` field of the root `Query` type', | ||
}, | ||
Subscription: { | ||
time: { | ||
async *subscribe() { | ||
while (true) { | ||
yield { time: new Date().toISOString() } | ||
await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
} | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
export const useSchema = < | ||
TContext extends YogaInitialContext = YogaInitialContext, | ||
TRootValue = {}, | ||
>( | ||
schemaDef?: YogaSchemaDefinition<TContext, TRootValue>, | ||
): Plugin<TContext> => { | ||
if (schemaDef == null) { | ||
const schema = getDefaultSchema() | ||
return { | ||
onPluginInit({ setSchema }) { | ||
setSchema(schema) | ||
}, | ||
} | ||
} | ||
if ('typeDefs' in schemaDef) { | ||
const schema = makeExecutableSchema(schemaDef) | ||
return { | ||
onPluginInit({ setSchema }) { | ||
setSchema(schema) | ||
}, | ||
} | ||
} | ||
if (isSchema(schemaDef)) { | ||
return { | ||
onPluginInit({ setSchema }) { | ||
setSchema(schemaDef) | ||
}, | ||
} | ||
} | ||
if ('then' in schemaDef) { | ||
let schema: GraphQLSchema | undefined | ||
return { | ||
async onRequest() { | ||
if (!schema) { | ||
schema = await schemaDef | ||
} | ||
}, | ||
onEnveloped({ setSchema }) { | ||
if (!schema) { | ||
throw new GraphQLError( | ||
`You provide a promise of a schema but it hasn't been resolved yet. Make sure you use this plugin with GraphQL Yoga.`, | ||
{ | ||
extensions: { | ||
http: { | ||
status: 500, | ||
}, | ||
}, | ||
}, | ||
) | ||
} | ||
setSchema(schema) | ||
}, | ||
} | ||
} | ||
const schemaByRequest = new WeakMap<Request, GraphQLSchema>() | ||
return { | ||
async onRequest({ request }) { | ||
const schema = await schemaDef(request) | ||
schemaByRequest.set(request, schema) | ||
}, | ||
onEnveloped({ setSchema, context }) { | ||
if (context?.request) { | ||
const schema = schemaByRequest.get(context.request) | ||
if (schema) { | ||
setSchema(schema) | ||
} | ||
} else { | ||
throw new GraphQLError( | ||
'Request object is not available in the context. Make sure you use this plugin with GraphQL Yoga.', | ||
{ | ||
extensions: { | ||
http: { | ||
status: 500, | ||
}, | ||
}, | ||
}, | ||
) | ||
} | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters