UNPKG

micro-ftch

Version:

Wrappers for built-in fetch() enabling killswitch, logging, concurrency limit and other features

308 lines 11.2 kB
/** * Wrappers for {@link https://developer.mozilla.org/en-US/docs/Web/API/fetch | built-in fetch()} * enabling killswitch, logging, concurrency limit, and other features. Fetch is great, but its * usage in secure environments is complicated. The library makes it simple. * @module * @example * Wrap fetch once, then compose JSON-RPC batching and replay support on top. * ```js * import { ftch, jsonrpc, replayable } from 'micro-ftch'; * * let enabled = true; * const events = []; * const net = ftch(fetch, { * isValidRequest: () => enabled, * log: (url, options) => events.push({ url, method: options.method }), * timeout: 5000, * concurrencyLimit: 10, * }); * const res = await net('https://example.com'); * * const rpc = jsonrpc(net, 'http://rpc_node/', { * headers: {}, * batchSize: 20, * }); * const res1 = await rpc.call('method', 'arg0', 'arg1'); * const res2 = await rpc.callNamed('method', { arg0: '0', arg1: '1' }); * * const replayNet = replayable(net); * const replayRpc = jsonrpc(replayNet, 'http://rpc_node/', { * headers: {}, * batchSize: 20, * }); * const replayRes = await replayRpc.call('method', 'arg0', 'arg1'); * * await net('https://user:pwd@httpbin.org/basic-auth/user/pwd'); * ``` */ declare function limit(concurrencyLimit: number): <T>(fn: () => Promise<T>) => Promise<T>; /** Arguments for built-in fetch, with added timeout support. */ export type FetchOpts = RequestInit & { /** Abort the request after this many milliseconds. */ timeout?: number; }; /** * Built-in fetch, or function conforming to its interface. * Shared by `ftch`, `jsonrpc`, and `replayable`. */ export type FetchFn = (url: string, opts?: FetchOpts) => Promise<{ headers: Headers; ok: boolean; redirected: boolean; status: number; statusText: string; type: ResponseType; url: string; json: () => Promise<any>; text: () => Promise<string>; arrayBuffer: () => Promise<ArrayBuffer>; }>; /** Options for `ftch`. */ export type FtchOpts = { /** * Returns `false` to block a request before or after it runs. * @param url - Request URL about to be fetched. * @returns `true` when the request should be allowed. */ isValidRequest?: (url?: string) => boolean; /** * Alias for `isValidRequest`. * @param url - Request URL about to be fetched. * @returns `true` when the request should be allowed. */ killswitch?: (url?: string) => boolean; /** Maximum number of wrapped requests allowed to run at once. */ concurrencyLimit?: number; /** Default timeout in milliseconds for wrapped requests. */ timeout?: number; /** * Observes every request before it is sent. * @param url - Request URL. * @param opts - Request options passed to the wrapped fetch. See {@link FetchOpts}. */ log?: (url: string, opts: FetchOpts) => void; }; /** * Small wrapper over fetch function * @param fetchFunction - Fetch implementation to wrap. * @param opts - Wrapper configuration like timeout, killswitch, and logging. See {@link FtchOpts}. * @returns Wrapped fetch function with timeout, auth parsing, and optional request gating. * @throws If the killswitch hook is invalid or a wrapped request is blocked by the network policy. {@link Error} * @example * Add a simple network killswitch around an existing fetch implementation. * ```js * import { ftch } from 'micro-ftch'; * let enabled = true; * const net = ftch(fetch, { isValidRequest: () => enabled }); * await net('https://example.com'); * enabled = false; * ``` * @example * Force wrapped requests to run one at a time. * ```js * import { ftch } from 'micro-ftch'; * const net = ftch(fetch, { concurrencyLimit: 1 }); * await Promise.all([net('https://example.com/1'), net('https://example.com/2')]); * ``` * @example * Apply the same timeout to every request made through the wrapper. * ```js * import { ftch } from 'micro-ftch'; * const net = ftch(fetch, { timeout: 1000 }); * await net('https://example.com'); * ``` * @example * Capture a structured request log without changing the call sites. * ```js * import { ftch } from 'micro-ftch'; * const events = []; * const net = ftch(fetch, { * log: (url, options) => events.push({ url, method: options.method }), * }); * await net('https://example.com'); * ``` * @example * User info in the URL becomes the Authorization header automatically. * ```js * import { ftch } from 'micro-ftch'; * const net = ftch(fetch); * await net('https://user:pwd@example.com/private'); * ``` */ export declare function ftch(fetchFunction: FetchFn, opts?: FtchOpts): FetchFn; /** Minimal JSON-RPC client interface. */ export type JsonrpcInterface = { /** * Calls a JSON-RPC method with positional parameters. * @param method - JSON-RPC method name. * @param args - Positional JSON-RPC params. * @returns Decoded JSON-RPC result. */ call: (method: string, ...args: any[]) => Promise<any>; /** * Calls a JSON-RPC method with named parameters. * @param method - JSON-RPC method name. * @param args - Named JSON-RPC params. * @returns Decoded JSON-RPC result. */ callNamed: (method: string, args: Record<string, any>) => Promise<any>; }; type NetworkOpts = { batchSize?: number; headers?: Record<string, string>; }; type RpcErrorResponse = { code: number; message: string; }; /** * JSON-RPC server error wrapper. * @param error - JSON-RPC error payload. * @example * Inspect the JSON-RPC error code and message from a failed response. * ```js * import { RpcError } from 'micro-ftch'; * const err = new RpcError({ code: -32000, message: 'oops' }); * console.log(err.code, err.message); * ``` */ export declare class RpcError extends Error { readonly code: number; constructor(error: RpcErrorResponse); } /** * Small utility class for Jsonrpc * @param fetchFunction - Fetch implementation used for transport. * @param rpcUrl - JSON-RPC endpoint URL. * @param options - Batching and header configuration. See {@link NetworkOpts}. * @example * Create a batched JSON-RPC client and call it with positional and named params. * ```js * import { JsonrpcProvider } from 'micro-ftch'; * const rpc = new JsonrpcProvider(fetch, 'http://rpc_node/', { * headers: {}, * batchSize: 20, * }); * const res = await rpc.call('method', 'arg0', 'arg1'); * const res2 = await rpc.callNamed('method', { arg0: '0', arg1: '1' }); * ``` */ export declare class JsonrpcProvider implements JsonrpcInterface { private batchSize; private headers; private queue; private fetchFunction; readonly rpcUrl: string; constructor(fetchFunction: FetchFn, rpcUrl: string, options?: NetworkOpts); private fetchJson; private jsonError; private batchProcess; private rpcBatch; private rpc; call(method: string, ...args: any[]): Promise<any>; callNamed(method: string, params: Record<string, any>): Promise<any>; } /** * Batched JSON-RPC functionality. * @param fetchFunction - Fetch implementation used for transport. * @param rpcUrl - JSON-RPC endpoint URL. * @param options - Batching and header configuration. See {@link NetworkOpts}. * @returns Configured JSON-RPC provider. * @example * Create a batched JSON-RPC helper. * ```js * import { jsonrpc } from 'micro-ftch'; * const rpc = jsonrpc(fetch, 'http://rpc_node/', { * headers: {}, * batchSize: 20, * }); * const res = await rpc.call('method', 'arg0', 'arg1'); * const res2 = await rpc.callNamed('method', { arg0: '0', arg1: '1' }); * ``` */ export declare function jsonrpc(fetchFunction: FetchFn, rpcUrl: string, options?: NetworkOpts): JsonrpcProvider; /** * Builds a replay bucket key from the request URL and fetch options. * @param url - Request URL. * @param opt - Fetch options used for the request. * @returns Stable string key used for capture and replay. */ type GetKeyFn = (url: string, opt: FetchOpts) => string; /** Options for replayable(). */ export type ReplayOpts = { /** Throw instead of using the wrapped fetch when a request is missing from the log. */ offline?: boolean; /** Custom request-key function used for capture and replay. */ getKey?: GetKeyFn; }; /** replayable() return function, with additional logging helpers. */ export type ReplayFn = FetchFn & { /** Captured request/response payloads keyed by the replay fingerprint. */ logs: Record<string, any>; /** Keys that have been read or written through this replay wrapper. */ accessed: Set<string>; /** * Exports only the log entries touched through this wrapper. * @returns JSON string that can seed another `replayable()` instance. */ export: () => string; }; /** * Log & replay network requests without actually calling network code. * @param fetchFunction - Wrapped fetch implementation used to capture new responses. * @param logs - Captured request/response map, usually from `JSON.parse(replay.export())`. * @param opts - Replay configuration such as offline mode or custom keying. See {@link ReplayOpts}. * @returns Fetch-compatible wrapper with log export helpers. * @example * Record live responses once, then export the captured log. * ```js * import { ftch as createFtch, replayable } from 'micro-ftch'; * const ftch = createFtch(fetch); * const replayCapture = replayable(ftch); * await replayCapture('https://example.com/1'); * await replayCapture('https://example.com/2'); * const logs = replayCapture.export(); * ``` * @example * Replay cached responses from a previously exported log snapshot. * ```js * import { ftch as createFtch, replayable } from 'micro-ftch'; * const ftch = createFtch(fetch); * const logs = { '{"method":"GET"}': '{"ok":true}' }; * const replay = replayable(ftch, logs, { * offline: true, * getKey: (_url, opt = {}) => JSON.stringify({ method: opt.method || 'GET' }), * }); * await replay('https://example.com/1'); * ``` * @example * Offline mode throws instead of making a new request. * ```js * import { ftch as createFtch, replayable } from 'micro-ftch'; * const ftch = createFtch(fetch); * const logs = { '{"url":"https://example.com/1","opt":{"headers":{}}}': '{"ok":true}' }; * const replayTestOffline = replayable(ftch, logs, { offline: true }); * await replayTestOffline('https://example.com/1'); * ``` * @example * Collapse multiple URLs into one replay bucket when the HTTP method is what matters. * ```ts * import { ftch as createFtch, replayable, type FetchOpts } from 'micro-ftch'; * const ftch = createFtch(fetch); * const getKey = (_url: string, opt: FetchOpts = {}) => * JSON.stringify({ method: opt.method || 'GET' }); * const replay = replayable( * ftch, * { '{"method":"GET"}': '{"ok":true}' }, * { getKey, offline: true } * ); * await replay('https://example.com/1', { method: 'GET' }); * ``` */ export declare function replayable(fetchFunction: FetchFn, logs?: Record<string, string>, opts?: ReplayOpts): ReplayFn; /** Internal methods for test purposes only. */ export declare const _TEST: { limit: typeof limit; }; export {}; //# sourceMappingURL=index.d.ts.map