micro-ftch
Version:
Wrappers for built-in fetch() enabling killswitch, logging, concurrency limit and other features
308 lines • 11.2 kB
TypeScript
/**
* 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