import { AbortController } from '@solana/event-target-impl'; import type { RpcTransport } from '@solana/rpc-spec'; import type { RpcResponse } from '@solana/rpc-spec-types'; type CoalescedRequest = { readonly abortController: AbortController; numConsumers: number; readonly responsePromise: Promise; }; type GetDeduplicationKeyFn = (payload: unknown) => string | undefined; // This used to be a `Symbol()`, but there's a bug in Node <21 where the `undici` library passes // the `reason` property of the `AbortSignal` straight to `Error.captureStackTrace()` without first // typechecking it. `Error.captureStackTrace()` fatals when given a `Symbol`. // See https://github.com/nodejs/undici/pull/2597 let EXPLICIT_ABORT_TOKEN: ReturnType; function createExplicitAbortToken() { // This function is an annoying workaround to prevent `process.env.NODE_ENV` from appearing at // the top level of this module and thwarting an optimizing compiler's attempt to tree-shake. return __DEV__ ? { EXPLICIT_ABORT_TOKEN: 'This object is thrown from the request that underlies a series of coalesced ' + 'requests when the last request in that series aborts', } : {}; } export function getRpcTransportWithRequestCoalescing( transport: TTransport, getDeduplicationKey: GetDeduplicationKeyFn, ): TTransport { let coalescedRequestsByDeduplicationKey: Record | undefined; return async function makeCoalescedHttpRequest( request: Parameters[0], ): Promise> { const { payload, signal } = request; const deduplicationKey = getDeduplicationKey(payload); if (deduplicationKey === undefined) { return await transport(request); } if (!coalescedRequestsByDeduplicationKey) { queueMicrotask(() => { coalescedRequestsByDeduplicationKey = undefined; }); coalescedRequestsByDeduplicationKey = {}; } if (coalescedRequestsByDeduplicationKey[deduplicationKey] == null) { const abortController = new AbortController(); const responsePromise = (async () => { try { return await transport({ ...request, signal: abortController.signal, }); } catch (e) { if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) { // We triggered this error when the last subscriber aborted. Letting this // error bubble up from here would cause runtime fatals where there should // be none. return; } throw e; } })(); coalescedRequestsByDeduplicationKey[deduplicationKey] = { abortController, numConsumers: 0, responsePromise, }; } const coalescedRequest = coalescedRequestsByDeduplicationKey[deduplicationKey]; coalescedRequest.numConsumers++; if (signal) { const responsePromise = coalescedRequest.responsePromise as Promise>; return await new Promise>((resolve, reject) => { const handleAbort = (e: AbortSignalEventMap['abort']) => { signal.removeEventListener('abort', handleAbort); coalescedRequest.numConsumers -= 1; queueMicrotask(() => { if (coalescedRequest.numConsumers === 0) { const abortController = coalescedRequest.abortController; abortController.abort((EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())); } }); // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject((e.target as AbortSignal).reason); }; signal.addEventListener('abort', handleAbort); responsePromise .then(resolve) .catch(reject) .finally(() => { signal.removeEventListener('abort', handleAbort); }); }); } else { return (await coalescedRequest.responsePromise) as RpcResponse; } } as TTransport; }