Odoo GraphQL Subscription using Node, Express JS for Sample
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.

408 lines
16 KiB

4 months ago
/**
*
* client
*
*/
import { ExecutionResult } from 'graphql';
import { Sink, ID, Disposable, Message, ConnectionInitMessage, ConnectionAckMessage, PingMessage, PongMessage, SubscribePayload, JSONMessageReviver, JSONMessageReplacer } from './common';
/** This file is the entry point for browsers, re-export common elements. */
export * from './common';
/**
* WebSocket started connecting.
*
* @category Client
*/
export type EventConnecting = 'connecting';
/**
* WebSocket has opened.
*
* @category Client
*/
export type EventOpened = 'opened';
/**
* Open WebSocket connection has been acknowledged.
*
* @category Client
*/
export type EventConnected = 'connected';
/**
* `PingMessage` has been received or sent.
*
* @category Client
*/
export type EventPing = 'ping';
/**
* `PongMessage` has been received or sent.
*
* @category Client
*/
export type EventPong = 'pong';
/**
* A message has been received.
*
* @category Client
*/
export type EventMessage = 'message';
/**
* WebSocket connection has closed.
*
* @category Client
*/
export type EventClosed = 'closed';
/**
* WebSocket connection had an error or client had an internal error.
*
* @category Client
*/
export type EventError = 'error';
/**
* All events that could occur.
*
* @category Client
*/
export type Event = EventConnecting | EventOpened | EventConnected | EventPing | EventPong | EventMessage | EventClosed | EventError;
/** @category Client */
export type EventConnectingListener = (isRetry: boolean) => void;
/**
* The first argument is actually the `WebSocket`, but to avoid
* bundling DOM typings because the client can run in Node env too,
* you should assert the websocket type during implementation.
*
* @category Client
*/
export type EventOpenedListener = (socket: unknown) => void;
/**
* The first argument is actually the `WebSocket`, but to avoid
* bundling DOM typings because the client can run in Node env too,
* you should assert the websocket type during implementation.
*
* Also, the second argument is the optional payload that the server may
* send through the `ConnectionAck` message.
*
* @category Client
*/
export type EventConnectedListener = (socket: unknown, payload: ConnectionAckMessage['payload'], wasRetry: boolean) => void;
/**
* The first argument communicates whether the ping was received from the server.
* If `false`, the ping was sent by the client.
*
* @category Client
*/
export type EventPingListener = (received: boolean, payload: PingMessage['payload']) => void;
/**
* The first argument communicates whether the pong was received from the server.
* If `false`, the pong was sent by the client.
*
* @category Client
*/
export type EventPongListener = (received: boolean, payload: PongMessage['payload']) => void;
/**
* Called for all **valid** messages received by the client. Mainly useful for
* debugging and logging received messages.
*
* @category Client
*/
export type EventMessageListener = (message: Message) => void;
/**
* The argument is actually the websocket `CloseEvent`, but to avoid
* bundling DOM typings because the client can run in Node env too,
* you should assert the websocket type during implementation.
*
* @category Client
*/
export type EventClosedListener = (event: unknown) => void;
/**
* Events dispatched from the WebSocket `onerror` are handled in this listener,
* as well as all internal client errors that could throw.
*
* @category Client
*/
export type EventErrorListener = (error: unknown) => void;
/** @category Client */
export type EventListener<E extends Event> = E extends EventConnecting ? EventConnectingListener : E extends EventOpened ? EventOpenedListener : E extends EventConnected ? EventConnectedListener : E extends EventPing ? EventPingListener : E extends EventPong ? EventPongListener : E extends EventMessage ? EventMessageListener : E extends EventClosed ? EventClosedListener : E extends EventError ? EventErrorListener : never;
/**
* Configuration used for the GraphQL over WebSocket client.
*
* @category Client
*/
export interface ClientOptions<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload']> {
/**
* URL of the GraphQL over WebSocket Protocol compliant server to connect.
*
* If the option is a function, it will be called on every WebSocket connection attempt.
* Returning a promise is supported too and the connecting phase will stall until it
* resolves with the URL.
*
* A good use-case for having a function is when using the URL for authentication,
* where subsequent reconnects (due to auth) may have a refreshed identity token in
* the URL.
*/
url: string | (() => Promise<string> | string);
/**
* Optional parameters, passed through the `payload` field with the `ConnectionInit` message,
* that the client specifies when establishing a connection with the server. You can use this
* for securely passing arguments for authentication.
*
* If you decide to return a promise, keep in mind that the server might kick you off if it
* takes too long to resolve! Check the `connectionInitWaitTimeout` on the server for more info.
*
* Throwing an error from within this function will close the socket with the `Error` message
* in the close event reason.
*/
connectionParams?: P | (() => Promise<P> | P);
/**
* Controls when should the connection be established.
*
* - `false`: Establish a connection immediately. Use `onNonLazyError` to handle errors.
* - `true`: Establish a connection on first subscribe and close on last unsubscribe. Use
* the subscription sink's `error` to handle errors.
*
* @default true
*/
lazy?: boolean;
/**
* Used ONLY when the client is in non-lazy mode (`lazy = false`). When
* using this mode, the errors might have no sinks to report to; however,
* to avoid swallowing errors, consider using `onNonLazyError`, which will
* be called when either:
* - An unrecoverable error/close event occurs
* - Silent retry attempts have been exceeded
*
* After a client has errored out, it will NOT perform any automatic actions.
*
* The argument can be a websocket `CloseEvent` or an `Error`. To avoid bundling
* DOM types, you should derive and assert the correct type. When receiving:
* - A `CloseEvent`: retry attempts have been exceeded or the specific
* close event is labeled as fatal (read more in `retryAttempts`).
* - An `Error`: some internal issue has occured, all internal errors are
* fatal by nature.
*
* @default console.error
*/
onNonLazyError?: (errorOrCloseEvent: unknown) => void;
/**
* How long should the client wait before closing the socket after the last oparation has
* completed. This is meant to be used in combination with `lazy`. You might want to have
* a calmdown time before actually closing the connection. Kinda' like a lazy close "debounce".
*
* @default 0
*/
lazyCloseTimeout?: number;
/**
* The timout between dispatched keep-alive messages, naimly server pings. Internally
* dispatches the `PingMessage` type to the server and expects a `PongMessage` in response.
* This helps with making sure that the connection with the server is alive and working.
*
* Timeout countdown starts from the moment the socket was opened and subsequently
* after every received `PongMessage`.
*
* Note that NOTHING will happen automatically with the client if the server never
* responds to a `PingMessage` with a `PongMessage`. If you want the connection to close,
* you should implement your own logic on top of the client. A simple example looks like this:
*
* ```js
* import { createClient } from 'graphql-ws';
*
* let activeSocket, timedOut;
* createClient({
* url: 'ws://i.time.out:4000/after-5/seconds',
* keepAlive: 10_000, // ping server every 10 seconds
* on: {
* connected: (socket) => (activeSocket = socket),
* ping: (received) => {
* if (!received) // sent
* timedOut = setTimeout(() => {
* if (activeSocket.readyState === WebSocket.OPEN)
* activeSocket.close(4408, 'Request Timeout');
* }, 5_000); // wait 5 seconds for the pong and then close the connection
* },
* pong: (received) => {
* if (received) clearTimeout(timedOut); // pong is received, clear connection close timeout
* },
* },
* });
* ```
*
* @default 0
*/
keepAlive?: number;
/**
* The amount of time for which the client will wait
* for `ConnectionAck` message.
*
* Set the value to `Infinity`, `''`, `0`, `null` or `undefined` to skip waiting.
*
* If the wait timeout has passed and the server
* has not responded with `ConnectionAck` message,
* the client will terminate the socket by
* dispatching a close event `4418: Connection acknowledgement timeout`
*
* @default 0
*/
connectionAckWaitTimeout?: number;
/**
* Disable sending the `PongMessage` automatically.
*
* Useful for when integrating your own custom client pinger that performs
* custom actions before responding to a ping, or to pass along the optional pong
* message payload. Please check the readme recipes for a concrete example.
*/
disablePong?: boolean;
/**
* How many times should the client try to reconnect on abnormal socket closure before it errors out?
*
* The library classifies the following close events as fatal:
* - _All internal WebSocket fatal close codes (check `isFatalInternalCloseCode` in `src/client.ts` for exact list)_
* - `4500: Internal server error`
* - `4005: Internal client error`
* - `4400: Bad request`
* - `4004: Bad response`
* - `4401: Unauthorized` _tried subscribing before connect ack_
* - `4406: Subprotocol not acceptable`
* - `4409: Subscriber for <id> already exists` _distinction is very important_
* - `4429: Too many initialisation requests`
*
* In addition to the aforementioned close events, any _non-CloseEvent_ connection problem
* is considered fatal by default. However, this specific behaviour can be altered by using
* the `shouldRetry` option.
*
* These events are reported immediately and the client will not reconnect.
*
* @default 5
*/
retryAttempts?: number;
/**
* Control the wait time between retries. You may implement your own strategy
* by timing the resolution of the returned promise with the retries count.
* `retries` argument counts actual connection attempts, so it will begin with
* 0 after the first retryable disconnect.
*
* @default 'Randomised exponential backoff'
*/
retryWait?: (retries: number) => Promise<void>;
/**
* Check if the close event or connection error is fatal. If you return `false`,
* the client will fail immediately without additional retries; however, if you
* return `true`, the client will keep retrying until the `retryAttempts` have
* been exceeded.
*
* The argument is whatever has been thrown during the connection phase.
*
* Beware, the library classifies a few close events as fatal regardless of
* what is returned here. They are listed in the documentation of the `retryAttempts`
* option.
*
* @default 'Only `CloseEvent`s'
*/
shouldRetry?: (errOrCloseEvent: unknown) => boolean;
/**
* @deprecated Use `shouldRetry` instead.
*
* Check if the close event or connection error is fatal. If you return `true`,
* the client will fail immediately without additional retries; however, if you
* return `false`, the client will keep retrying until the `retryAttempts` have
* been exceeded.
*
* The argument is either a WebSocket `CloseEvent` or an error thrown during
* the connection phase.
*
* Beware, the library classifies a few close events as fatal regardless of
* what is returned. They are listed in the documentation of the `retryAttempts`
* option.
*
* @default 'Any non-`CloseEvent`'
*/
isFatalConnectionProblem?: (errOrCloseEvent: unknown) => boolean;
/**
* Register listeners before initialising the client. This way
* you can ensure to catch all client relevant emitted events.
*
* The listeners passed in will **always** be the first ones
* to get the emitted event before other registered listeners.
*/
on?: Partial<{
[event in Event]: EventListener<event>;
}>;
/**
* A custom WebSocket implementation to use instead of the
* one provided by the global scope. Mostly useful for when
* using the client outside of the browser environment.
*/
webSocketImpl?: unknown;
/**
* A custom ID generator for identifying subscriptions.
*
* The default generates a v4 UUID to be used as the ID using `Math`
* as the random number generator. Supply your own generator
* in case you need more uniqueness.
*
* Reference: https://gist.github.com/jed/982883
*/
generateID?: (payload: SubscribePayload) => ID;
/**
* An optional override for the JSON.parse function used to hydrate
* incoming messages to this client. 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 from this client. Useful for serializing custom
* datatypes out to the client.
*/
jsonMessageReplacer?: JSONMessageReplacer;
}
/** @category Client */
export interface Client extends Disposable {
/**
* Listens on the client which dispatches events about the socket state.
*/
on<E extends Event>(event: E, listener: EventListener<E>): () => void;
/**
* Subscribes through the WebSocket following the config parameters. It
* uses the `sink` to emit received data or errors. Returns a _cleanup_
* function used for dropping the subscription and cleaning stuff up.
*/
subscribe<Data = Record<string, unknown>, Extensions = unknown>(payload: SubscribePayload, sink: Sink<ExecutionResult<Data, Extensions>>): () => void;
/**
* Subscribes and iterates over emitted results from the WebSocket
* through the returned async iterator.
*/
iterate<Data = Record<string, unknown>, Extensions = unknown>(payload: SubscribePayload): AsyncIterableIterator<ExecutionResult<Data, Extensions>>;
/**
* Terminates the WebSocket abruptly and immediately.
*
* A close event `4499: Terminated` is issued to the current WebSocket and a
* syntetic {@link TerminatedCloseEvent} is immediately emitted without waiting for
* the one coming from `WebSocket.onclose`.
*
* Terminating is not considered fatal and a connection retry will occur as expected.
*
* Useful in cases where the WebSocket is stuck and not emitting any events;
* can happen on iOS Safari, see: https://github.com/enisdenjo/graphql-ws/discussions/290.
*/
terminate(): void;
}
/**
* Creates a disposable GraphQL over WebSocket client.
*
* @category Client
*/
export declare function createClient<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload']>(options: ClientOptions<P>): Client;
/**
* A syntetic close event `4499: Terminated` is issued to the current to immediately
* close the connection without waiting for the one coming from `WebSocket.onclose`.
*
* Terminating is not considered fatal and a connection retry will occur as expected.
*
* Useful in cases where the WebSocket is stuck and not emitting any events;
* can happen on iOS Safari, see: https://github.com/enisdenjo/graphql-ws/discussions/290.
*/
export declare class TerminatedCloseEvent extends Error {
name: string;
message: string;
code: number;
reason: string;
wasClean: boolean;
}