Files
FrenoCorp/node_modules/ox/core/RpcResponse.ts
Michael Freno 7c684a42cc FRE-600: Fix code review blockers
- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 00:08:01 -04:00

598 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Errors, RpcRequest } from '../index.js'
import type {
Compute,
IsNarrowable,
IsNever,
OneOf,
UnionPartialBy,
} from './internal/types.js'
/** A JSON-RPC response object as per the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification#request_object). */
export type RpcResponse<
result = unknown,
error extends ErrorObject = ErrorObject,
> = Compute<
{
id: number
jsonrpc: '2.0'
} & OneOf<{ result: result } | { error: error }>
>
/** JSON-RPC error object as per the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification#error_object). */
export type ErrorObject = {
code: number
message: string
data?: unknown | undefined
}
/**
* A type-safe interface to instantiate a JSON-RPC response object as per the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification#response_object).
*
* @example
* ### Instantiating a Response Object
*
* ```ts twoslash
* import { RpcResponse } from 'ox'
*
* const response = RpcResponse.from({
* id: 0,
* jsonrpc: '2.0',
* result: '0x69420',
* })
* ```
*
* @example
* ### Type-safe Instantiation
*
* If you have a JSON-RPC request object, you can use it to strongly-type the response. If a `request` is provided,
* then the `id` and `jsonrpc` properties will be overridden with the values from the request.
*
* ```ts twoslash
* import { RpcRequest, RpcResponse } from 'ox'
*
* const request = RpcRequest.from({ id: 0, method: 'eth_blockNumber' })
*
* const response = RpcResponse.from(
* { result: '0x69420' },
* { request },
* )
* ```
*
* @param response - Opaque JSON-RPC response object.
* @param options - Parsing options.
* @returns Typed JSON-RPC result, or response object (if `raw` is `true`).
*/
export function from<
request extends RpcRequest.RpcRequest | undefined = undefined,
const response =
| (request extends RpcRequest.RpcRequest
? request['_returnType']
: RpcResponse)
| unknown,
>(
response: from.Response<request, response>,
options?: from.Options<request>,
): Compute<from.ReturnType<response>>
// eslint-disable-next-line jsdoc/require-jsdoc
export function from(response: RpcResponse, options: any = {}): RpcResponse {
const { request } = options
return {
...response,
id: response.id ?? request?.id,
jsonrpc: response.jsonrpc ?? request.jsonrpc,
}
}
export declare namespace from {
type Response<
request extends RpcRequest.RpcRequest | undefined = undefined,
response = unknown,
> = response &
(request extends RpcRequest.RpcRequest
? UnionPartialBy<RpcResponse<request['_returnType']>, 'id' | 'jsonrpc'>
: RpcResponse)
type Options<
request extends RpcRequest.RpcRequest | undefined =
| RpcRequest.RpcRequest
| undefined,
> = {
request?: request | RpcRequest.RpcRequest | undefined
}
type ReturnType<response> = IsNarrowable<response, RpcResponse> extends true
? RpcResponse
: response & Readonly<{ id: number; jsonrpc: '2.0' }>
}
/**
* A type-safe interface to parse a JSON-RPC response object as per the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification#response_object), and extract the result.
*
* @example
* ```ts twoslash
* import { RpcRequest, RpcResponse } from 'ox'
*
* // 1. Create a request store.
* const store = RpcRequest.createStore()
*
* // 2. Get a request object.
* const request = store.prepare({
* method: 'eth_getBlockByNumber',
* params: ['0x1', false],
* })
*
* // 3. Send the JSON-RPC request via HTTP.
* const block = await fetch('https://1.rpc.thirdweb.com', {
* body: JSON.stringify(request),
* headers: {
* 'Content-Type': 'application/json',
* },
* method: 'POST',
* })
* .then((response) => response.json())
* // 4. Parse the JSON-RPC response into a type-safe result. // [!code focus]
* .then((response) => RpcResponse.parse(response, { request })) // [!code focus]
*
* block // [!code focus]
* // ^?
*
*
*
*
*
*
*
*
*
*
*
* ```
*
* :::tip
*
* If you don't need the return type, you can omit the options entirely.
*
* ```ts twoslash
* // @noErrors
* import { RpcResponse } from 'ox'
*
* const block = await fetch('https://1.rpc.thirdweb.com', {})
* .then((response) => response.json())
* .then((response) => RpcResponse.parse(response, { request })) // [!code --]
* .then(RpcResponse.parse) // [!code ++]
* ```
* :::
*
* @example
* ### Raw Mode
*
* If `raw` is `true`, the response will be returned as an object with `result` and `error` properties instead of returning the `result` directly and throwing errors.
*
* ```ts twoslash
* import { RpcRequest, RpcResponse } from 'ox'
*
* const store = RpcRequest.createStore()
*
* const request = store.prepare({
* method: 'eth_blockNumber',
* })
*
* const response = RpcResponse.parse({}, {
* request,
* raw: true, // [!code hl]
* })
*
* response.result
* // ^?
*
*
* response.error
* // ^?
*
*
* ```
*
* @param response - Opaque JSON-RPC response object.
* @param options - Parsing options.
* @returns Typed JSON-RPC result, or response object (if `raw` is `true`).
*/
export function parse<
const response extends RpcResponse | unknown,
returnType,
raw extends boolean = false,
>(
response: response,
options: parse.Options<returnType, raw> = {},
): parse.ReturnType<
unknown extends response
? returnType
: response extends RpcResponse
? response extends { result: infer result }
? result
: never
: returnType,
raw
> {
const { raw = false } = options
const response_ = response as RpcResponse
if (raw) return response as never
if (response_.error) throw parseError(response_.error)
return response_.result as never
}
export declare namespace parse {
type Options<returnType, raw extends boolean = false> = {
/**
* JSON-RPC Method that was used to make the request. Used for typing the response.
*/
request?:
| {
_returnType: returnType
}
| RpcRequest.RpcRequest
| undefined
/**
* Enables raw mode responses will return an object with `result` and `error` properties instead of returning the `result` directly and throwing errors.
*
* - `true`: a JSON-RPC response object will be returned with `result` and `error` properties.
* - `false`: the JSON-RPC response object's `result` property will be returned directly, and JSON-RPC Errors will be thrown.
*
* @default false
*/
raw?: raw | boolean | undefined
}
type ReturnType<returnType, raw extends boolean = false> = Compute<
raw extends true ? RpcResponse<returnType> : returnType
>
type ErrorType =
| ParseError
| InvalidInputError
| ResourceNotFoundError
| ResourceUnavailableError
| TransactionRejectedError
| MethodNotSupportedError
| LimitExceededError
| VersionNotSupportedError
| InvalidRequestError
| MethodNotFoundError
| InvalidParamsError
| InternalError
| BaseErrorType
| Errors.GlobalErrorType
}
/**
* Parses a JSON-RPC error object into an error instance.
*
* @example
* ```ts twoslash
* import { RpcResponse } from 'ox'
*
* const error = RpcResponse.parseError({ code: -32000, message: 'unsupported method' })
*
* error
* // ^?
*
* ```
*
* @param errorObject - JSON-RPC error object.
* @returns Error instance.
*/
export function parseError<const errorObject extends ErrorObject | unknown>(
errorObject: errorObject | ErrorObject,
): parseError.ReturnType<errorObject> {
const errorObject_ = errorObject as ErrorObject
const { code } = errorObject_
if (code === InternalError.code)
return new InternalError(errorObject_) as never
if (code === InvalidInputError.code)
return new InvalidInputError(errorObject_) as never
if (code === InvalidParamsError.code)
return new InvalidParamsError(errorObject_) as never
if (code === InvalidRequestError.code)
return new InvalidRequestError(errorObject_) as never
if (code === LimitExceededError.code)
return new LimitExceededError(errorObject_) as never
if (code === MethodNotFoundError.code)
return new MethodNotFoundError(errorObject_) as never
if (code === MethodNotSupportedError.code)
return new MethodNotSupportedError(errorObject_) as never
if (code === ParseError.code) return new ParseError(errorObject_) as never
if (code === ResourceNotFoundError.code)
return new ResourceNotFoundError(errorObject_) as never
if (code === ResourceUnavailableError.code)
return new ResourceUnavailableError(errorObject_) as never
if (code === TransactionRejectedError.code)
return new TransactionRejectedError(errorObject_) as never
if (code === VersionNotSupportedError.code)
return new VersionNotSupportedError(errorObject_) as never
return new InternalError({
data: errorObject_,
message: errorObject_.message,
}) as never
}
export declare namespace parseError {
type ReturnType<
errorObject extends ErrorObject | unknown,
//
error = errorObject extends ErrorObject
?
| (errorObject['code'] extends InternalError['code']
? InternalError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? InternalError
: never)
| (errorObject['code'] extends InvalidInputError['code']
? InvalidInputError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? InvalidInputError
: never)
| (errorObject['code'] extends ResourceNotFoundError['code']
? ResourceNotFoundError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? ResourceNotFoundError
: never)
| (errorObject['code'] extends ResourceUnavailableError['code']
? ResourceUnavailableError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? ResourceUnavailableError
: never)
| (errorObject['code'] extends TransactionRejectedError['code']
? TransactionRejectedError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? TransactionRejectedError
: never)
| (errorObject['code'] extends ParseError['code']
? ParseError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? ParseError
: never)
| (errorObject['code'] extends MethodNotSupportedError['code']
? MethodNotSupportedError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? MethodNotSupportedError
: never)
| (errorObject['code'] extends LimitExceededError['code']
? LimitExceededError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? LimitExceededError
: never)
| (errorObject['code'] extends VersionNotSupportedError['code']
? VersionNotSupportedError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? VersionNotSupportedError
: never)
| (errorObject['code'] extends InvalidRequestError['code']
? InvalidRequestError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? InvalidRequestError
: never)
| (errorObject['code'] extends MethodNotFoundError['code']
? MethodNotFoundError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? MethodNotFoundError
: never)
| (errorObject['code'] extends InvalidParamsError['code']
? InvalidParamsError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? InvalidParamsError
: never)
| (IsNarrowable<errorObject['code'], number> extends false
? BaseError
: never)
: parseError.ReturnType<ErrorObject>,
> = IsNever<error> extends true ? BaseError : error
}
export type BaseErrorType = BaseError & { name: 'BaseError' }
/** Thrown when a JSON-RPC error has occurred. */
export class BaseError extends Error {
override name = 'RpcResponse.BaseError'
readonly code: number
readonly data?: unknown | undefined
constructor(errorObject: ErrorObject) {
const { code, message, data } = errorObject
super(message)
this.code = code
this.data = data
}
}
/** Thrown when the input to a JSON-RPC method is invalid. */
export class InvalidInputError extends BaseError {
static readonly code = -32000
override readonly code = -32000
override readonly name = 'RpcResponse.InvalidInputError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: InvalidInputError.code,
data: parameters.data,
message: parameters.message ?? 'Missing or invalid parameters.',
})
}
}
/** Thrown when a JSON-RPC resource is not found. */
export class ResourceNotFoundError extends BaseError {
static readonly code = -32001
override readonly code = -32001
override readonly name = 'RpcResponse.ResourceNotFoundError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: ResourceNotFoundError.code,
data: parameters.data,
message: parameters.message ?? 'Requested resource not found.',
})
}
}
/** Thrown when a JSON-RPC resource is unavailable. */
export class ResourceUnavailableError extends BaseError {
static readonly code = -32002
override readonly code = -32002
override readonly name = 'RpcResponse.ResourceUnavailableError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: ResourceUnavailableError.code,
data: parameters.data,
message: parameters.message ?? 'Requested resource not available.',
})
}
}
/** Thrown when a JSON-RPC transaction is rejected. */
export class TransactionRejectedError extends BaseError {
static readonly code = -32003
override readonly code = -32003
override readonly name = 'RpcResponse.TransactionRejectedError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: TransactionRejectedError.code,
data: parameters.data,
message: parameters.message ?? 'Transaction creation failed.',
})
}
}
/** Thrown when a JSON-RPC method is not supported. */
export class MethodNotSupportedError extends BaseError {
static readonly code = -32004
override readonly code = -32004
override readonly name = 'RpcResponse.MethodNotSupportedError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: MethodNotSupportedError.code,
data: parameters.data,
message: parameters.message ?? 'Method is not implemented.',
})
}
}
/** Thrown when a rate-limit is exceeded. */
export class LimitExceededError extends BaseError {
static readonly code = -32005
override readonly code = -32005
override readonly name = 'RpcResponse.LimitExceededError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: LimitExceededError.code,
data: parameters.data,
message: parameters.message ?? 'Rate limit exceeded.',
})
}
}
/** Thrown when a JSON-RPC version is not supported. */
export class VersionNotSupportedError extends BaseError {
static readonly code = -32006
override readonly code = -32006
override readonly name = 'RpcResponse.VersionNotSupportedError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: VersionNotSupportedError.code,
data: parameters.data,
message: parameters.message ?? 'JSON-RPC version not supported.',
})
}
}
/** Thrown when a JSON-RPC request is invalid. */
export class InvalidRequestError extends BaseError {
static readonly code = -32600
override readonly code = -32600
override readonly name = 'RpcResponse.InvalidRequestError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: InvalidRequestError.code,
data: parameters.data,
message: parameters.message ?? 'Input is not a valid JSON-RPC request.',
})
}
}
/** Thrown when a JSON-RPC method is not found. */
export class MethodNotFoundError extends BaseError {
static readonly code = -32601
override readonly code = -32601
override readonly name = 'RpcResponse.MethodNotFoundError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: MethodNotFoundError.code,
data: parameters.data,
message: parameters.message ?? 'Method does not exist.',
})
}
}
/** Thrown when the parameters to a JSON-RPC method are invalid. */
export class InvalidParamsError extends BaseError {
static readonly code = -32602
override readonly code = -32602
override readonly name = 'RpcResponse.InvalidParamsError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: InvalidParamsError.code,
data: parameters.data,
message: parameters.message ?? 'Invalid method parameters.',
})
}
}
/** Thrown when an internal JSON-RPC error has occurred. */
export class InternalError extends BaseError {
static readonly code = -32603
override readonly code = -32603
override readonly name = 'RpcResponse.InternalError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: InternalError.code,
data: parameters.data,
message: parameters.message ?? 'Internal JSON-RPC error.',
})
}
}
/** Thrown when a JSON-RPC response is invalid. */
export class ParseError extends BaseError {
static readonly code = -32700
override readonly code = -32700
override readonly name = 'RpcResponse.ParseError'
constructor(parameters: Partial<Omit<ErrorObject, 'code'>> = {}) {
super({
code: ParseError.code,
data: parameters.data,
message: parameters.message ?? 'Failed to parse JSON-RPC response.',
})
}
}