|
1 |
| -export function helloWorld() { |
2 |
| - return 'Hello World!'; |
| 1 | +import type { |
| 2 | + AzureFunction, |
| 3 | + Context, |
| 4 | + HttpRequest, |
| 5 | + HttpRequestHeaders, |
| 6 | +} from '@azure/functions'; |
| 7 | +import { |
| 8 | + ApolloServer, |
| 9 | + BaseContext, |
| 10 | + ContextFunction, |
| 11 | + HeaderMap, |
| 12 | + HTTPGraphQLRequest, |
| 13 | +} from '@apollo/server'; |
| 14 | +import type { WithRequired } from '@apollo/utils.withrequired'; |
| 15 | + |
| 16 | +export interface AzureFunctionsContextFunctionArgument { |
| 17 | + context: Context; |
| 18 | +} |
| 19 | + |
| 20 | +export interface AzureFunctionsMiddlewareOptions<TContext extends BaseContext> { |
| 21 | + context?: ContextFunction<[AzureFunctionsContextFunctionArgument], TContext>; |
| 22 | +} |
| 23 | + |
| 24 | +const defaultContext: ContextFunction< |
| 25 | + [AzureFunctionsContextFunctionArgument], |
| 26 | + any |
| 27 | +> = async () => ({}); |
| 28 | + |
| 29 | +export function startServerAndCreateHandler( |
| 30 | + server: ApolloServer<BaseContext>, |
| 31 | + options?: AzureFunctionsMiddlewareOptions<BaseContext>, |
| 32 | +): AzureFunction; |
| 33 | +export function startServerAndCreateHandler<TContext extends BaseContext>( |
| 34 | + server: ApolloServer<TContext>, |
| 35 | + options: WithRequired<AzureFunctionsMiddlewareOptions<TContext>, 'context'>, |
| 36 | +): AzureFunction; |
| 37 | +export function startServerAndCreateHandler<TContext extends BaseContext>( |
| 38 | + server: ApolloServer<TContext>, |
| 39 | + options?: AzureFunctionsMiddlewareOptions<TContext>, |
| 40 | +): AzureFunction { |
| 41 | + server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests(); |
| 42 | + return async (context: Context, req: HttpRequest) => { |
| 43 | + const contextFunction = options?.context ?? defaultContext; |
| 44 | + try { |
| 45 | + const normalizedRequest = normalizeRequest(req); |
| 46 | + |
| 47 | + const { body, headers, status } = await server.executeHTTPGraphQLRequest({ |
| 48 | + httpGraphQLRequest: normalizedRequest, |
| 49 | + context: () => contextFunction({ context }), |
| 50 | + }); |
| 51 | + |
| 52 | + if (body.kind === 'chunked') { |
| 53 | + throw Error('Incremental delivery not implemented'); |
| 54 | + } |
| 55 | + |
| 56 | + return { |
| 57 | + status: status || 200, |
| 58 | + headers: { |
| 59 | + ...Object.fromEntries(headers), |
| 60 | + 'content-length': Buffer.byteLength(body.string).toString(), |
| 61 | + }, |
| 62 | + body: body.string, |
| 63 | + }; |
| 64 | + } catch (e) { |
| 65 | + context.log.error('Failure processing GraphQL request', e); |
| 66 | + return { |
| 67 | + status: 400, |
| 68 | + body: (e as Error).message, |
| 69 | + }; |
| 70 | + } |
| 71 | + }; |
| 72 | +} |
| 73 | + |
| 74 | +function normalizeRequest(req: HttpRequest): HTTPGraphQLRequest { |
| 75 | + if (!req.method) { |
| 76 | + throw new Error('No method'); |
| 77 | + } |
| 78 | + |
| 79 | + return { |
| 80 | + method: req.method, |
| 81 | + headers: normalizeHeaders(req.headers), |
| 82 | + search: new URL(req.url).search, |
| 83 | + body: parseBody(req.body, req.headers['content-type']), |
| 84 | + }; |
| 85 | +} |
| 86 | + |
| 87 | +function parseBody( |
| 88 | + body: string | null | undefined, |
| 89 | + contentType: string | undefined, |
| 90 | +): object | string { |
| 91 | + if (body) { |
| 92 | + if (contentType === 'application/json' && typeof body === 'string') { |
| 93 | + return JSON.parse(body); |
| 94 | + } |
| 95 | + if (contentType === 'text/plain') { |
| 96 | + return body; |
| 97 | + } |
| 98 | + } |
| 99 | + return ''; |
| 100 | +} |
| 101 | + |
| 102 | +function normalizeHeaders(headers: HttpRequestHeaders): HeaderMap { |
| 103 | + const headerMap = new HeaderMap(); |
| 104 | + for (const [key, value] of Object.entries(headers)) { |
| 105 | + headerMap.set(key, value ?? ''); |
| 106 | + } |
| 107 | + return headerMap; |
3 | 108 | }
|
0 commit comments