You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
418 lines
18 KiB
418 lines
18 KiB
4 months ago
|
/**
|
||
|
*
|
||
|
* server
|
||
|
*
|
||
|
*/
|
||
|
import { OperationTypeNode, GraphQLSchema, ExecutionArgs, validate as graphqlValidate, GraphQLError, SubscriptionArgs } from 'graphql';
|
||
|
import { GRAPHQL_TRANSPORT_WS_PROTOCOL, ID, ConnectionInitMessage, SubscribeMessage, NextMessage, ErrorMessage, CompleteMessage, JSONMessageReplacer, JSONMessageReviver, PingMessage, PongMessage, ExecutionResult, ExecutionPatchResult } from './common';
|
||
|
/** @category Server */
|
||
|
export type OperationResult = Promise<AsyncGenerator<ExecutionResult | ExecutionPatchResult> | AsyncIterable<ExecutionResult | ExecutionPatchResult> | ExecutionResult> | AsyncGenerator<ExecutionResult | ExecutionPatchResult> | AsyncIterable<ExecutionResult | ExecutionPatchResult> | ExecutionResult;
|
||
|
/**
|
||
|
* A concrete GraphQL execution context value type.
|
||
|
*
|
||
|
* Mainly used because TypeScript collapses unions
|
||
|
* with `any` or `unknown` to `any` or `unknown`. So,
|
||
|
* we use a custom type to allow definitions such as
|
||
|
* the `context` server option.
|
||
|
*
|
||
|
* @category Server
|
||
|
*/
|
||
|
export type GraphQLExecutionContextValue = object | symbol | number | string | boolean | undefined | null;
|
||
|
/** @category Server */
|
||
|
export interface ServerOptions<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E = unknown> {
|
||
|
/**
|
||
|
* The GraphQL schema on which the operations
|
||
|
* will be executed and validated against.
|
||
|
*
|
||
|
* If a function is provided, it will be called on
|
||
|
* every subscription request allowing you to manipulate
|
||
|
* schema dynamically.
|
||
|
*
|
||
|
* If the schema is left undefined, you're trusted to
|
||
|
* provide one in the returned `ExecutionArgs` from the
|
||
|
* `onSubscribe` callback.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*/
|
||
|
schema?: GraphQLSchema | ((ctx: Context<P, E>, message: SubscribeMessage, args: Omit<ExecutionArgs, 'schema'>) => Promise<GraphQLSchema> | GraphQLSchema);
|
||
|
/**
|
||
|
* A value which is provided to every resolver and holds
|
||
|
* important contextual information like the currently
|
||
|
* logged in user, or access to a database.
|
||
|
*
|
||
|
* If you return from `onSubscribe`, and the returned value is
|
||
|
* missing the `contextValue` field, this context will be used
|
||
|
* instead.
|
||
|
*
|
||
|
* If you use the function signature, the final execution arguments
|
||
|
* will be passed in (also the returned value from `onSubscribe`).
|
||
|
* Since the context is injected on every subscribe, the `SubscribeMessage`
|
||
|
* with the regular `Context` will be passed in through the arguments too.
|
||
|
*
|
||
|
* Note that the context function is invoked on each operation only once.
|
||
|
* Meaning, for subscriptions, only at the point of initialising the subscription;
|
||
|
* not on every subscription event emission. Read more about the context lifecycle
|
||
|
* in subscriptions here: https://github.com/graphql/graphql-js/issues/894.
|
||
|
*/
|
||
|
context?: GraphQLExecutionContextValue | ((ctx: Context<P, E>, message: SubscribeMessage, args: ExecutionArgs) => Promise<GraphQLExecutionContextValue> | GraphQLExecutionContextValue);
|
||
|
/**
|
||
|
* The GraphQL root fields or resolvers to go
|
||
|
* alongside the schema. Learn more about them
|
||
|
* here: https://graphql.org/learn/execution/#root-fields-resolvers.
|
||
|
*
|
||
|
* If you return from `onSubscribe`, and the returned value is
|
||
|
* missing the `rootValue` field, the relevant operation root
|
||
|
* will be used instead.
|
||
|
*/
|
||
|
roots?: {
|
||
|
[operation in OperationTypeNode]?: Record<string, NonNullable<SubscriptionArgs['rootValue']>>;
|
||
|
};
|
||
|
/**
|
||
|
* A custom GraphQL validate function allowing you to apply your
|
||
|
* own validation rules.
|
||
|
*
|
||
|
* Returned, non-empty, array of `GraphQLError`s will be communicated
|
||
|
* to the client through the `ErrorMessage`. Use an empty array if the
|
||
|
* document is valid and no errors have been encountered.
|
||
|
*
|
||
|
* Will not be used when implementing a custom `onSubscribe`.
|
||
|
*
|
||
|
* Throwing an error from within this function will close the socket
|
||
|
* with the `Error` message in the close event reason.
|
||
|
*/
|
||
|
validate?: typeof graphqlValidate;
|
||
|
/**
|
||
|
* Is the `execute` function from GraphQL which is
|
||
|
* used to execute the query and mutation operations.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*/
|
||
|
execute?: (args: ExecutionArgs) => OperationResult;
|
||
|
/**
|
||
|
* Is the `subscribe` function from GraphQL which is
|
||
|
* used to execute the subscription operation.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*/
|
||
|
subscribe?: (args: ExecutionArgs) => OperationResult;
|
||
|
/**
|
||
|
* The amount of time for which the server will wait
|
||
|
* for `ConnectionInit` message.
|
||
|
*
|
||
|
* Set the value to `Infinity`, `''`, `0`, `null` or `undefined` to skip waiting.
|
||
|
*
|
||
|
* If the wait timeout has passed and the client
|
||
|
* has not sent the `ConnectionInit` message,
|
||
|
* the server will terminate the socket by
|
||
|
* dispatching a close event `4408: Connection initialisation timeout`
|
||
|
*
|
||
|
* @default 3_000 // 3 seconds
|
||
|
*/
|
||
|
connectionInitWaitTimeout?: number;
|
||
|
/**
|
||
|
* Is the connection callback called when the
|
||
|
* client requests the connection initialisation
|
||
|
* through the message `ConnectionInit`.
|
||
|
*
|
||
|
* The message payload (`connectionParams` from the
|
||
|
* client) is present in the `Context.connectionParams`.
|
||
|
*
|
||
|
* - Returning `true` or nothing from the callback will
|
||
|
* allow the client to connect.
|
||
|
*
|
||
|
* - Returning `false` from the callback will
|
||
|
* terminate the socket by dispatching the
|
||
|
* close event `4403: Forbidden`.
|
||
|
*
|
||
|
* - Returning a `Record` from the callback will
|
||
|
* allow the client to connect and pass the returned
|
||
|
* value to the client through the optional `payload`
|
||
|
* field in the `ConnectionAck` message.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*/
|
||
|
onConnect?: (ctx: Context<P, E>) => Promise<Record<string, unknown> | boolean | void> | Record<string, unknown> | boolean | void;
|
||
|
/**
|
||
|
* Called when the client disconnects for whatever reason after
|
||
|
* he successfully went through the connection initialisation phase.
|
||
|
* Provides the close event too. Beware that this callback happens
|
||
|
* AFTER all subscriptions have been gracefully completed and BEFORE
|
||
|
* the `onClose` callback.
|
||
|
*
|
||
|
* If you are interested in tracking the subscriptions completions,
|
||
|
* consider using the `onComplete` callback.
|
||
|
*
|
||
|
* This callback will be called EXCLUSIVELY if the client connection
|
||
|
* is acknowledged. Meaning, `onConnect` will be called before the `onDisconnect`.
|
||
|
*
|
||
|
* For tracking socket closures at any point in time, regardless
|
||
|
* of the connection state - consider using the `onClose` callback.
|
||
|
*/
|
||
|
onDisconnect?: (ctx: Context<P, E>, code: number, reason: string) => Promise<void> | void;
|
||
|
/**
|
||
|
* Called when the socket closes for whatever reason, at any
|
||
|
* point in time. Provides the close event too. Beware
|
||
|
* that this callback happens AFTER all subscriptions have
|
||
|
* been gracefully completed and AFTER the `onDisconnect` callback.
|
||
|
*
|
||
|
* If you are interested in tracking the subscriptions completions,
|
||
|
* consider using the `onComplete` callback.
|
||
|
*
|
||
|
* In comparison to `onDisconnect`, this callback will ALWAYS
|
||
|
* be called, regardless if the user successfully went through
|
||
|
* the connection initialisation or not. `onConnect` might not
|
||
|
* called before the `onClose`.
|
||
|
*/
|
||
|
onClose?: (ctx: Context<P, E>, code: number, reason: string) => Promise<void> | void;
|
||
|
/**
|
||
|
* The subscribe callback executed right after
|
||
|
* acknowledging the request before any payload
|
||
|
* processing has been performed.
|
||
|
*
|
||
|
* If you return `ExecutionArgs` from the callback,
|
||
|
* it will be used instead of trying to build one
|
||
|
* internally. In this case, you are responsible
|
||
|
* for providing a ready set of arguments which will
|
||
|
* be directly plugged in the operation execution.
|
||
|
*
|
||
|
* Omitting the fields `contextValue` or `rootValue`
|
||
|
* from the returned value will have the provided server
|
||
|
* options fill in the gaps.
|
||
|
*
|
||
|
* To report GraphQL errors simply return an array
|
||
|
* of them from the callback, they will be reported
|
||
|
* to the client through the error message.
|
||
|
*
|
||
|
* Useful for preparing the execution arguments
|
||
|
* following a custom logic. A typical use case are
|
||
|
* persisted queries, you can identify the query from
|
||
|
* the subscribe message and create the GraphQL operation
|
||
|
* execution args which are then returned by the function.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*/
|
||
|
onSubscribe?: (ctx: Context<P, E>, message: SubscribeMessage) => Promise<ExecutionArgs | readonly GraphQLError[] | void> | ExecutionArgs | readonly GraphQLError[] | void;
|
||
|
/**
|
||
|
* Executed after the operation call resolves. For streaming
|
||
|
* operations, triggering this callback does not necessarily
|
||
|
* mean that there is already a result available - it means
|
||
|
* that the subscription process for the stream has resolved
|
||
|
* and that the client is now subscribed.
|
||
|
*
|
||
|
* The `OperationResult` argument is the result of operation
|
||
|
* execution. It can be an iterator or already a value.
|
||
|
*
|
||
|
* If you want the single result and the events from a streaming
|
||
|
* operation, use the `onNext` callback.
|
||
|
*
|
||
|
* Use this callback to listen for subscribe operation and
|
||
|
* execution result manipulation.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*/
|
||
|
onOperation?: (ctx: Context<P, E>, message: SubscribeMessage, args: ExecutionArgs, result: OperationResult) => Promise<OperationResult | void> | OperationResult | void;
|
||
|
/**
|
||
|
* Executed after an error occurred right before it
|
||
|
* has been dispatched to the client.
|
||
|
*
|
||
|
* Use this callback to format the outgoing GraphQL
|
||
|
* errors before they reach the client.
|
||
|
*
|
||
|
* Returned result will be injected in the error message payload.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*/
|
||
|
onError?: (ctx: Context<P, E>, message: ErrorMessage, errors: readonly GraphQLError[]) => Promise<readonly GraphQLError[] | void> | readonly GraphQLError[] | void;
|
||
|
/**
|
||
|
* Executed after an operation has emitted a result right before
|
||
|
* that result has been sent to the client. Results from both
|
||
|
* single value and streaming operations will appear in this callback.
|
||
|
*
|
||
|
* Use this callback if you want to format the execution result
|
||
|
* before it reaches the client.
|
||
|
*
|
||
|
* Returned result will be injected in the next message payload.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*/
|
||
|
onNext?: (ctx: Context<P, E>, message: NextMessage, args: ExecutionArgs, result: ExecutionResult | ExecutionPatchResult) => Promise<ExecutionResult | ExecutionPatchResult | void> | ExecutionResult | ExecutionPatchResult | void;
|
||
|
/**
|
||
|
* The complete callback is executed after the
|
||
|
* operation has completed right before sending
|
||
|
* the complete message to the client.
|
||
|
*
|
||
|
* Throwing an error from within this function will
|
||
|
* close the socket with the `Error` message
|
||
|
* in the close event reason.
|
||
|
*
|
||
|
* Since the library makes sure to complete streaming
|
||
|
* operations even after an abrupt closure, this callback
|
||
|
* will still be called.
|
||
|
*/
|
||
|
onComplete?: (ctx: Context<P, E>, message: CompleteMessage) => Promise<void> | void;
|
||
|
/**
|
||
|
* An optional override for the JSON.parse function used to hydrate
|
||
|
* incoming messages to this server. Useful for parsing custom datatypes
|
||
|
* out of the incoming JSON.
|
||
|
*/
|
||
|
jsonMessageReviver?: JSONMessageReviver;
|
||
|
/**
|
||
|
* An optional override for the JSON.stringify function used to serialize
|
||
|
* outgoing messages to from server. Useful for serializing custom
|
||
|
* datatypes out to the client.
|
||
|
*/
|
||
|
jsonMessageReplacer?: JSONMessageReplacer;
|
||
|
}
|
||
|
/** @category Server */
|
||
|
export interface Server<E = undefined> {
|
||
|
/**
|
||
|
* New socket has been established. The lib will validate
|
||
|
* the protocol and use the socket accordingly. Returned promise
|
||
|
* will resolve after the socket closes.
|
||
|
*
|
||
|
* The second argument will be passed in the `extra` field
|
||
|
* of the `Context`. You may pass the initial request or the
|
||
|
* original WebSocket, if you need it down the road.
|
||
|
*
|
||
|
* Returns a function that should be called when the same socket
|
||
|
* has been closed, for whatever reason. The close code and reason
|
||
|
* must be passed for reporting to the `onDisconnect` callback. Returned
|
||
|
* promise will resolve once the internal cleanup is complete.
|
||
|
*/
|
||
|
opened(socket: WebSocket, ctxExtra: E): (code: number, reason: string) => Promise<void>;
|
||
|
}
|
||
|
/** @category Server */
|
||
|
export interface WebSocket {
|
||
|
/**
|
||
|
* The subprotocol of the WebSocket. Will be used
|
||
|
* to validate against the supported ones.
|
||
|
*/
|
||
|
readonly protocol: string;
|
||
|
/**
|
||
|
* Sends a message through the socket. Will always
|
||
|
* provide a `string` message.
|
||
|
*
|
||
|
* Please take care that the send is ready. Meaning,
|
||
|
* only provide a truly OPEN socket through the `opened`
|
||
|
* method of the `Server`.
|
||
|
*
|
||
|
* The returned promise is used to control the flow of data
|
||
|
* (like handling backpressure).
|
||
|
*/
|
||
|
send(data: string): Promise<void> | void;
|
||
|
/**
|
||
|
* Closes the socket gracefully. Will always provide
|
||
|
* the appropriate code and close reason. `onDisconnect`
|
||
|
* callback will be called.
|
||
|
*
|
||
|
* The returned promise is used to control the graceful
|
||
|
* closure.
|
||
|
*/
|
||
|
close(code: number, reason: string): Promise<void> | void;
|
||
|
/**
|
||
|
* Called when message is received. The library requires the data
|
||
|
* to be a `string`.
|
||
|
*
|
||
|
* All operations requested from the client will block the promise until
|
||
|
* completed, this means that the callback will not resolve until all
|
||
|
* subscription events have been emitted (or until the client has completed
|
||
|
* the stream), or until the query/mutation resolves.
|
||
|
*
|
||
|
* Exceptions raised during any phase of operation processing will
|
||
|
* reject the callback's promise, catch them and communicate them
|
||
|
* to your clients however you wish.
|
||
|
*/
|
||
|
onMessage(cb: (data: string) => Promise<void>): void;
|
||
|
/**
|
||
|
* Implement a listener for the `PingMessage` sent from the client to the server.
|
||
|
* If the client sent the ping with a payload, it will be passed through the
|
||
|
* first argument.
|
||
|
*
|
||
|
* If this listener is implemented, the server will NOT automatically reply
|
||
|
* to any pings from the client. Implementing it makes it your responsibility
|
||
|
* to decide how and when to respond.
|
||
|
*/
|
||
|
onPing?(payload: PingMessage['payload']): Promise<void> | void;
|
||
|
/**
|
||
|
* Implement a listener for the `PongMessage` sent from the client to the server.
|
||
|
* If the client sent the pong with a payload, it will be passed through the
|
||
|
* first argument.
|
||
|
*/
|
||
|
onPong?(payload: PongMessage['payload']): Promise<void> | void;
|
||
|
}
|
||
|
/** @category Server */
|
||
|
export interface Context<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E = unknown> {
|
||
|
/**
|
||
|
* Indicates that the `ConnectionInit` message
|
||
|
* has been received by the server. If this is
|
||
|
* `true`, the client wont be kicked off after
|
||
|
* the wait timeout has passed.
|
||
|
*/
|
||
|
readonly connectionInitReceived: boolean;
|
||
|
/**
|
||
|
* Indicates that the connection was acknowledged
|
||
|
* by having dispatched the `ConnectionAck` message
|
||
|
* to the related client.
|
||
|
*/
|
||
|
readonly acknowledged: boolean;
|
||
|
/** The parameters passed during the connection initialisation. */
|
||
|
readonly connectionParams?: Readonly<P>;
|
||
|
/**
|
||
|
* Holds the active subscriptions for this context. **All operations**
|
||
|
* that are taking place are aggregated here. The user is _subscribed_
|
||
|
* to an operation when waiting for result(s).
|
||
|
*
|
||
|
* If the subscription behind an ID is an `AsyncIterator` - the operation
|
||
|
* is streaming; on the contrary, if the subscription is `null` - it is simply
|
||
|
* a reservation, meaning - the operation resolves to a single result or is still
|
||
|
* pending/being prepared.
|
||
|
*/
|
||
|
readonly subscriptions: Record<ID, AsyncGenerator<unknown> | AsyncIterable<unknown> | null>;
|
||
|
/**
|
||
|
* An extra field where you can store your own context values
|
||
|
* to pass between callbacks.
|
||
|
*/
|
||
|
extra: E;
|
||
|
}
|
||
|
/**
|
||
|
* Makes a Protocol compliant WebSocket GraphQL server. The server
|
||
|
* is actually an API which is to be used with your favourite WebSocket
|
||
|
* server library!
|
||
|
*
|
||
|
* Read more about the [GraphQL over WebSocket Protocol](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverWebSocket.md).
|
||
|
*
|
||
|
* @category Server
|
||
|
*/
|
||
|
export declare function makeServer<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], E = unknown>(options: ServerOptions<P, E>): Server<E>;
|
||
|
/**
|
||
|
* Helper utility for choosing the "graphql-transport-ws" subprotocol from
|
||
|
* a set of WebSocket subprotocols.
|
||
|
*
|
||
|
* Accepts a set of already extracted WebSocket subprotocols or the raw
|
||
|
* Sec-WebSocket-Protocol header value. In either case, if the right
|
||
|
* protocol appears, it will be returned.
|
||
|
*
|
||
|
* By specification, the server should not provide a value with Sec-WebSocket-Protocol
|
||
|
* if it does not agree with client's subprotocols. The client has a responsibility
|
||
|
* to handle the connection afterwards.
|
||
|
*
|
||
|
* @category Server
|
||
|
*/
|
||
|
export declare function handleProtocols(protocols: Set<string> | string[] | string): typeof GRAPHQL_TRANSPORT_WS_PROTOCOL | false;
|