type RpcRequest = {
    jsonrpc: "2.0";
    method: string;
    id: string | number;
    params: unknown;
};
/**
 * A JSONRPC-2.0 error.
 */
type RpcError = {
    code: number;
    message: string;
    data: any;
};
/**
 * An incoming message for a specific subscription
 */
type RpcSubscriptionMessage<T> = {
    type: "message";
    id: string | number;
    value: T;
};
type RpcResponse<T> = {
    type: "ok";
    id: string | number;
    value: T;
} | {
    type: "error";
    id: string | number;
    value: RpcError;
} | RpcSubscriptionMessage<T>;

type SocketOptions = {
    WebSocket: unknown;
};

declare function ws(host: string, socket_options?: SocketOptions): {
    query: (id: string | number, payload: RpcRequest) => Promise<RpcResponse<any>>;
    mutate: (id: string | number, payload: RpcRequest) => Promise<RpcResponse<any>>;
    subscribe: (id: string | number, on_data: ((value: any) => void) | undefined) => () => void;
};

type HttpOptions = {
    fetch?: typeof fetch;
};
declare function http(host: string, http_options?: HttpOptions): {
    query: (_id: string | number, payload: RpcRequest) => Promise<RpcResponse<unknown> | null>;
    mutate: (_id: string | number, payload: RpcRequest) => Promise<RpcResponse<unknown> | null>;
};

type MultiOptions = {
    ws?: SocketOptions;
    http?: HttpOptions;
};
/**
 * Transport that combines both `http` for query and mutate, and `ws` for subscriptions.
 */
declare function multi(host: string, options?: MultiOptions): {
    query: (id: string | number, payload: RpcRequest) => Promise<RpcResponse<unknown> | null>;
    mutate: (id: string | number, payload: RpcRequest) => Promise<RpcResponse<unknown> | null>;
    subscribe: (id: string | number, on_data: ((value: any) => void) | undefined) => () => void;
};

type ClientBuilder<Server> = (host: string) => Server;
/**
 * Interface required for a transport.
 */
type Transport = {
    /**
     * Make a request that is a query, meaning that it is safe to be cached.
     */
    query: (id: string | number, payload: RpcRequest) => Promise<RpcResponse<unknown> | null>;
    /**
     * Make a request that is a mutation, meaning that it should not be cached.
     */
    mutate: (id: string | number, payload: RpcRequest) => Promise<RpcResponse<unknown> | null>;
    /**
     * Start a subscription, calling `on_data` for every message from the server. An unsubscribe
     * method must be returned, which must terminate the subscription when called.
     */
    subscribe?: (id: string | number, on_data?: (value: any) => void) => () => void;
};

type Query<Args extends any[], Return> = {
    query: (...args: Args) => Promise<Return>;
};

type Mutation<Args extends any[], Return> = {
    mutate: (...args: Args) => Promise<Return>;
};

type StreamHandlers<T> = {
    on_data: (data: T) => void;
    on_error: (error: Error) => void;
    on_end: () => void;
};
type StreamHandler<T> = ((data: T) => void) | Partial<StreamHandlers<T>>;
type StreamUnsubscribe = () => void;
/**
 * Helper type to add handler to a list of arguments, in a way that it will be named.
 */
type AddHandler<Arr extends any[], Item> = [...Arr, handler: StreamHandler<Item>];
type Subscription<Args extends any[], Item> = {
    subscribe: (...args: AddHandler<Args, Item>) => StreamUnsubscribe;
};

/**
 * A handler function which must always take the path, represented as an array of strings.
 */
type HandlerFn<TArgs extends any[], TReturn> = (path: string[], ...args: TArgs) => TReturn;
/**
 * Strips the `path` parameter (first parameter) from a `HandlerFn`. This represents the function
 * that is exposed to the end-user.
 */
type StripPath<F> = F extends HandlerFn<infer TArgs, infer TReturn> ? (...args: TArgs) => TReturn : never;
/**
 * A collection of plugins, meaning handlers that include the path parameter.
 */
type Plugins = Record<string, HandlerFn<any[], any>>;
/**
 * For all available handlers, will produce a handler that has the `path` parameter stripped from
 * it.
 */
type Handlers<THandlers extends Plugins> = {
    [K in keyof THandlers]: StripPath<THandlers[K]>;
};

/**
 * Determines if the provided type has a nested object, or is just made up of functions.
 */
type HasNestedObject<T> = {
    [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never;
};
type AtEdge<T, Yes, No> = T extends HasNestedObject<T> ? Yes : No;
/**
 * Inject the provided plugins into the edges of the server.
 */
type InjectPlugins<TServer, TPlugins extends Plugins> = AtEdge<TServer, TServer & TPlugins, {
    [K in keyof TServer]: InjectPlugins<TServer[K], TPlugins>;
}>;
/**
 * Build a new client for a server.
 */
declare function build_client<Server>(client: Transport): Server;
/**
 * Build a new client and inject the following plugins.
 */
declare function build_client<Server, TPlugins extends Plugins>(transport: Transport, plugins: TPlugins): InjectPlugins<Server, Handlers<TPlugins>>;

export { type ClientBuilder, type HandlerFn, type HttpOptions, type MultiOptions, type Mutation, type Plugins, type Query, type SocketOptions, type StreamHandler, type StreamUnsubscribe, type Subscription, type Transport, build_client, http, multi, ws };
