shelving 1.184.1 → 1.185.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,6 +34,8 @@ export declare abstract class APIProvider<P = unknown, R = unknown> {
34
34
  * @throws {RequiredError} if this is a `HEAD` or `GET` request but `payload` is not a data object.
35
35
  */
36
36
  abstract getRequest<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Request;
37
+ /** Fetch a `Resource` using this provider (defaults to Javascript `fetch()` API). */
38
+ abstract fetch(request: Request): Promise<Response>;
37
39
  /**
38
40
  * Parse an HTTP `Response` for this endpoint.
39
41
  * - Non-2xx responses become `ResponseError`.
@@ -41,5 +43,5 @@ export declare abstract class APIProvider<P = unknown, R = unknown> {
41
43
  */
42
44
  abstract parseResponse<PP extends P, RR extends R>(_endpoint: Endpoint<PP, RR>, response: Response, caller?: AnyCaller): Promise<RR>;
43
45
  /** Send a payload to an `Endpoint` and retrieve the result. */
44
- abstract fetch<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
46
+ call<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
45
47
  }
@@ -1,3 +1,9 @@
1
1
  /** Provider for API endpoints rooted at a common base URL. */
2
2
  export class APIProvider {
3
+ /** Send a payload to an `Endpoint` and retrieve the result. */
4
+ async call(endpoint, payload, options, caller) {
5
+ const request = this.getRequest(endpoint, payload, options, caller);
6
+ const response = await this.fetch(request);
7
+ return this.parseResponse(endpoint, response, caller);
8
+ }
3
9
  }
@@ -4,8 +4,28 @@ import type { Nullish } from "../../util/null.js";
4
4
  import { type PossibleURIParams } from "../../util/uri.js";
5
5
  import { type PossibleURL, type URL, type URLString } from "../../util/url.js";
6
6
  import type { Endpoint } from "../endpoint/Endpoint.js";
7
- import type { APIProvider } from "./APIProvider.js";
8
- export declare class ClientAPIProvider<P = unknown, R = unknown> implements APIProvider<P, R> {
7
+ import { APIProvider } from "./APIProvider.js";
8
+ /** Options for a `ClientAPIProvider`. */
9
+ export interface ClientAPIProviderOptions {
10
+ /** The common base URL for all rendered endpoint requests. */
11
+ readonly url: PossibleURL;
12
+ /**
13
+ * Options used for HTTP requests created with `this.getRequest()` and `this.fetch()`
14
+ * - Omits `signal` because it's not relevant at the provider level.
15
+ */
16
+ readonly options?: Omit<RequestOptions, "signal">;
17
+ /** Timeout in milliseconds, or `undefined` for no timeout. */
18
+ readonly timeout?: number | undefined;
19
+ }
20
+ /**
21
+ * A client-side API provider that sends requests over the network using `fetch()`.
22
+ * - Can be used on a server environment to make outgoing API calls, or in a browser environment to call a server API.
23
+ * - Renders endpoint paths and query params into the URL and sends body payloads as JSON.
24
+ * - Parses JSON responses and throws `ResponseError` for non-2xx responses.
25
+ * - Extendable with custom request-building and response-parsing logic by overriding `getRequest()` and `parseResponse()`.
26
+ * - Wrap in `ValidationAPIProvider` to add automatic validation of request payloads and response results against endpoint schemas.
27
+ */
28
+ export declare class ClientAPIProvider<P = unknown, R = unknown> extends APIProvider<P, R> {
9
29
  /** The common base URL for all rendered endpoint requests. */
10
30
  readonly url: URLString;
11
31
  /** Default options used for HTTP requests created with `this.getRequest()` and `this.fetch()` */
@@ -21,18 +41,6 @@ export declare class ClientAPIProvider<P = unknown, R = unknown> implements APIP
21
41
  /** Internal implementation function for `getRequest()` used for requests that have a body. */
22
42
  protected _getBodyRequest(method: RequestBodyMethod, //
23
43
  url: PossibleURL, payload: P, options: RequestOptions, caller: AnyCaller): Request;
44
+ fetch(request: Request): Promise<Response>;
24
45
  parseResponse<PP extends P, RR extends R>(_endpoint: Endpoint<PP, RR>, response: Response, caller?: AnyCaller): Promise<RR>;
25
- fetch<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
26
- }
27
- /** Options for an `APIProvider`. */
28
- export interface ClientAPIProviderOptions {
29
- /** The common base URL for all rendered endpoint requests. */
30
- readonly url: PossibleURL;
31
- /**
32
- * Options used for HTTP requests created with `this.getRequest()` and `this.fetch()`
33
- * - Omits `signal` because it's not relevant at the provider level.
34
- */
35
- readonly options?: Omit<RequestOptions, "signal">;
36
- /** Timeout in milliseconds, or `undefined` for no timeout. */
37
- readonly timeout?: number | undefined;
38
46
  }
@@ -5,7 +5,16 @@ import { assertRequestHeadPayload, getHeadRequest, getRequest, isRequestHeadMeth
5
5
  import { omitProps } from "../../util/object.js";
6
6
  import { withURIParams } from "../../util/uri.js";
7
7
  import { requireBaseURL, requireURL } from "../../util/url.js";
8
- export class ClientAPIProvider {
8
+ import { APIProvider } from "./APIProvider.js";
9
+ /**
10
+ * A client-side API provider that sends requests over the network using `fetch()`.
11
+ * - Can be used on a server environment to make outgoing API calls, or in a browser environment to call a server API.
12
+ * - Renders endpoint paths and query params into the URL and sends body payloads as JSON.
13
+ * - Parses JSON responses and throws `ResponseError` for non-2xx responses.
14
+ * - Extendable with custom request-building and response-parsing logic by overriding `getRequest()` and `parseResponse()`.
15
+ * - Wrap in `ValidationAPIProvider` to add automatic validation of request payloads and response results against endpoint schemas.
16
+ */
17
+ export class ClientAPIProvider extends APIProvider {
9
18
  /** The common base URL for all rendered endpoint requests. */
10
19
  url;
11
20
  /** Default options used for HTTP requests created with `this.getRequest()` and `this.fetch()` */
@@ -13,6 +22,7 @@ export class ClientAPIProvider {
13
22
  /** Timeout in milliseconds, or `undefined` for no timeout. */
14
23
  timeout;
15
24
  constructor({ url, options = {}, timeout }) {
25
+ super();
16
26
  this.url = requireBaseURL(url, undefined, ClientAPIProvider);
17
27
  this.options = options;
18
28
  this.timeout = timeout;
@@ -53,6 +63,10 @@ export class ClientAPIProvider {
53
63
  url, payload, options, caller) {
54
64
  return getRequest(method, url, payload, options, caller);
55
65
  }
66
+ // Override to set default functionality of a client provider to send requests over the network with `fetch()` and parse responses with `parseResponse()`.
67
+ async fetch(request) {
68
+ return fetch(request);
69
+ }
56
70
  async parseResponse(_endpoint, response, caller = this.parseResponse) {
57
71
  const { ok, status } = response;
58
72
  const content = await parseResponseBody(response, caller);
@@ -60,9 +74,4 @@ export class ClientAPIProvider {
60
74
  throw new ResponseError(getMessage(content) ?? `Error ${status}`, { code: status, cause: response, caller });
61
75
  return content;
62
76
  }
63
- async fetch(endpoint, payload, options, caller = this.fetch) {
64
- const request = this.getRequest(endpoint, payload, options, caller);
65
- const response = await fetch(request);
66
- return this.parseResponse(endpoint, response, caller);
67
- }
68
77
  }
@@ -4,5 +4,5 @@ import type { Endpoint } from "../endpoint/Endpoint.js";
4
4
  import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
5
5
  /** Provider that logs API operations to the console. */
6
6
  export declare class DebugAPIProvider<P, R> extends ThroughAPIProvider<P, R> {
7
- fetch<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
7
+ call<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
8
8
  }
@@ -1,15 +1,37 @@
1
+ import { debugFullRequest, debugFullResponse } from "../../util/debug.js";
1
2
  import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
2
3
  /** Provider that logs API operations to the console. */
3
4
  export class DebugAPIProvider extends ThroughAPIProvider {
4
- async fetch(endpoint, payload, options, caller = this.fetch) {
5
+ async call(endpoint, payload, options, caller = this.call) {
6
+ // Turn the payload into a request and debug it before sending.
7
+ let request;
5
8
  try {
6
- console.debug("⋯ FETCH", endpoint.method, endpoint.path, payload);
7
- const result = await super.fetch(endpoint, payload, options, caller);
8
- console.debug("↩ FETCH", endpoint.method, endpoint.path, result);
9
+ request = this.getRequest(endpoint, payload, options, caller);
10
+ }
11
+ catch (reason) {
12
+ console.error("✘ FETCH", this.url, endpoint.toString(), payload, reason);
13
+ throw reason;
14
+ }
15
+ const debuggedRequest = await debugFullRequest(request);
16
+ console.debug("… FETCH", this.url, endpoint.toString(), payload, debuggedRequest);
17
+ // Fetch the response and debug if it throws.
18
+ let response;
19
+ try {
20
+ response = await this.fetch(request);
21
+ }
22
+ catch (reason) {
23
+ console.error("✘ FETCH", this.url, endpoint.toString(), payload, debuggedRequest, reason);
24
+ throw reason;
25
+ }
26
+ const debuggedResponse = await debugFullResponse(response);
27
+ // Convert the result or any parsing error.
28
+ try {
29
+ const result = await this.parseResponse(endpoint, response, caller);
30
+ console.debug("✔ FETCH", this.url, endpoint.toString(), payload, debuggedRequest, debuggedResponse, result);
9
31
  return result;
10
32
  }
11
33
  catch (reason) {
12
- console.error("✘ FETCH", endpoint.method, endpoint.path, payload, reason);
34
+ console.error("✘ FETCH", this.url, endpoint.toString(), payload, debuggedRequest, debuggedResponse, reason);
13
35
  throw reason;
14
36
  }
15
37
  }
@@ -1,14 +1,20 @@
1
1
  import type { AnyCaller } from "../../util/function.js";
2
2
  import type { RequestHandler, RequestOptions } from "../../util/http.js";
3
3
  import type { AnyEndpoint, Endpoint } from "../endpoint/Endpoint.js";
4
- import { ClientAPIProvider } from "./ClientAPIProvider.js";
4
+ import type { APIProvider } from "./APIProvider.js";
5
5
  import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
6
- /** A structured log entry emitted by `MockAPIProvider` for one of its provider operations. */
7
- export type MockAPICall = {
8
- readonly type: "fetch";
6
+ export type MockAPIFetchCall = {
7
+ readonly request: Request;
8
+ readonly response: Response;
9
+ };
10
+ export type MockAPIRequestCall = {
9
11
  readonly endpoint: AnyEndpoint;
10
12
  readonly payload: unknown;
13
+ readonly options: RequestOptions | undefined;
11
14
  readonly request: Request;
15
+ };
16
+ export type MockAPIResponseCall = {
17
+ readonly endpoint: AnyEndpoint;
12
18
  readonly response: Response;
13
19
  readonly result: unknown;
14
20
  };
@@ -18,8 +24,12 @@ export type MockAPICall = {
18
24
  * - The source provider's `fetch()` is never called — this provider intercepts all fetches and routes them through a `RequestHandler`.
19
25
  */
20
26
  export declare class MockAPIProvider<P = unknown, R = unknown> extends ThroughAPIProvider<P, R> {
21
- readonly calls: MockAPICall[];
27
+ readonly requestCalls: MockAPIRequestCall[];
28
+ readonly fetchCalls: MockAPIFetchCall[];
29
+ readonly responseCalls: MockAPIResponseCall[];
22
30
  readonly handler: RequestHandler;
23
- constructor(handler?: RequestHandler, source?: ClientAPIProvider<P, R>);
24
- fetch<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
31
+ constructor(handler?: RequestHandler, source?: APIProvider<P, R>);
32
+ getRequest<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Request;
33
+ parseResponse<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, response: Response, caller?: AnyCaller): Promise<RR>;
34
+ fetch(request: Request): Promise<Response>;
25
35
  }
@@ -1,8 +1,9 @@
1
+ import { debugRequest } from "../../util/debug.js";
1
2
  import { ClientAPIProvider } from "./ClientAPIProvider.js";
2
3
  import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
3
4
  /** Default handler just echoes back the input request as text. */
4
- async function _passthroughHandler(request) {
5
- return new Response(await request.text());
5
+ async function _mockHandler(request) {
6
+ return new Response(`Mocked response to ${debugRequest(request)}`, { status: 200, statusText: "OK" });
6
7
  }
7
8
  /**
8
9
  * Provider that logs API calls without sending network requests.
@@ -10,18 +11,30 @@ async function _passthroughHandler(request) {
10
11
  * - The source provider's `fetch()` is never called — this provider intercepts all fetches and routes them through a `RequestHandler`.
11
12
  */
12
13
  export class MockAPIProvider extends ThroughAPIProvider {
13
- calls = [];
14
+ requestCalls = [];
15
+ fetchCalls = [];
16
+ responseCalls = [];
14
17
  handler;
15
- constructor(handler = _passthroughHandler, source = new ClientAPIProvider({ url: "https://api.mock.com" })) {
18
+ constructor(handler = _mockHandler, source = new ClientAPIProvider({ url: "https://api.mock.com" })) {
16
19
  super(source);
17
20
  this.handler = handler;
18
21
  }
19
- // Log a `fetch()` call without using the network.
20
- async fetch(endpoint, payload, options = {}, caller = this.fetch) {
21
- const request = this.getRequest(endpoint, payload, options, caller);
22
- const response = await this.handler(request);
23
- const result = await this.parseResponse(endpoint, response, caller);
24
- this.calls.push({ type: "fetch", endpoint, payload, request, response, result });
22
+ // Override `getRequest()` to log the endpoint and payload before delegating to the source provider for request building.
23
+ getRequest(endpoint, payload, options, caller = this.getRequest) {
24
+ const request = super.getRequest(endpoint, payload, options, caller);
25
+ this.requestCalls.push({ endpoint, payload, options, request });
26
+ return request;
27
+ }
28
+ // Override `parseResponse()` to log the response and result.
29
+ async parseResponse(endpoint, response, caller = this.parseResponse) {
30
+ const result = await super.parseResponse(endpoint, response, caller);
31
+ this.responseCalls.push({ endpoint, response, result });
25
32
  return result;
26
33
  }
34
+ // Override `fetch()` to route through the handler instead of the network.
35
+ async fetch(request) {
36
+ const response = await this.handler(request);
37
+ this.fetchCalls.push({ request, response });
38
+ return response;
39
+ }
27
40
  }
@@ -13,6 +13,6 @@ import { MockAPIProvider } from "./MockAPIProvider.js";
13
13
  */
14
14
  export class MockEndpointAPIProvider extends MockAPIProvider {
15
15
  constructor(handlers, context, source) {
16
- super(request => handleEndpoints(this.url, handlers, request, context, this.fetch), source);
16
+ super(request => handleEndpoints(this.url, handlers, request, context, this.call), source);
17
17
  }
18
18
  }
@@ -3,17 +3,17 @@ import type { RequestOptions } from "../../util/http.js";
3
3
  import type { Sourceable } from "../../util/source.js";
4
4
  import type { URL, URLString } from "../../util/url.js";
5
5
  import type { Endpoint } from "../endpoint/Endpoint.js";
6
- import type { APIProvider } from "./APIProvider.js";
6
+ import { APIProvider } from "./APIProvider.js";
7
7
  /**
8
8
  * Provider wrapper that delegates API operations to a source provider.
9
9
  * - Extend this when you want to intercept only selected API operations, such as injecting auth headers or logging.
10
10
  */
11
- export declare class ThroughAPIProvider<P, R> implements APIProvider<P, R>, Sourceable<APIProvider<P, R>> {
11
+ export declare class ThroughAPIProvider<P, R> extends APIProvider<P, R> implements Sourceable<APIProvider<P, R>> {
12
12
  get url(): URLString;
13
13
  readonly source: APIProvider<P, R>;
14
14
  constructor(source: APIProvider<P, R>);
15
15
  renderURL<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, caller?: AnyCaller): URL;
16
16
  getRequest<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Request;
17
17
  parseResponse<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, response: Response, caller?: AnyCaller): Promise<RR>;
18
- fetch<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
18
+ fetch(request: Request): Promise<Response>;
19
19
  }
@@ -1,13 +1,15 @@
1
+ import { APIProvider } from "./APIProvider.js";
1
2
  /**
2
3
  * Provider wrapper that delegates API operations to a source provider.
3
4
  * - Extend this when you want to intercept only selected API operations, such as injecting auth headers or logging.
4
5
  */
5
- export class ThroughAPIProvider {
6
+ export class ThroughAPIProvider extends APIProvider {
6
7
  get url() {
7
8
  return this.source.url;
8
9
  }
9
10
  source;
10
11
  constructor(source) {
12
+ super();
11
13
  this.source = source;
12
14
  }
13
15
  renderURL(endpoint, payload, caller = this.renderURL) {
@@ -19,7 +21,7 @@ export class ThroughAPIProvider {
19
21
  parseResponse(endpoint, response, caller = this.parseResponse) {
20
22
  return this.source.parseResponse(endpoint, response, caller);
21
23
  }
22
- fetch(endpoint, payload, options, caller = this.fetch) {
23
- return this.source.fetch(endpoint, payload, options, caller);
24
+ fetch(request) {
25
+ return this.source.fetch(request);
24
26
  }
25
27
  }
@@ -4,5 +4,6 @@ import type { Endpoint } from "../endpoint/Endpoint.js";
4
4
  import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
5
5
  /** Validate an asynchronous source provider (source can have any type because validation guarantees the type). */
6
6
  export declare class ValidationAPIProvider<P, R> extends ThroughAPIProvider<P, R> {
7
- fetch<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
7
+ getRequest<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Request;
8
+ parseResponse<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, response: Response, caller?: AnyCaller): Promise<RR>;
8
9
  }
@@ -2,22 +2,19 @@ import { ResponseError } from "../../error/ResponseError.js";
2
2
  import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
3
3
  /** Validate an asynchronous source provider (source can have any type because validation guarantees the type). */
4
4
  export class ValidationAPIProvider extends ThroughAPIProvider {
5
- async fetch(endpoint, payload, options, caller = this.fetch) {
5
+ getRequest(endpoint, payload, options, caller = this.getRequest) {
6
6
  // Validate payload — let thrown strings bubble up as user-readable messages for e.g. form handlers.
7
- const validPayload = endpoint.payload.validate(payload);
8
- // Call through to source (raw transport, no schema validation).
9
- const content = await this.source.fetch(endpoint, validPayload, options, caller);
10
- // Validate result — wrap in ResponseError as this is a server/transport problem, not user error.
11
- return _validateResult(endpoint, content, caller);
7
+ return super.getRequest(endpoint, endpoint.payload.validate(payload), options, caller);
12
8
  }
13
- }
14
- function _validateResult(endpoint, content, caller) {
15
- try {
16
- return endpoint.result.validate(content);
17
- }
18
- catch (thrown) {
19
- if (typeof thrown === "string")
20
- throw new ResponseError(`Invalid result for ${endpoint}:\n${thrown}`, { endpoint, code: 422, caller });
21
- throw thrown;
9
+ async parseResponse(endpoint, response, caller = this.parseResponse) {
10
+ try {
11
+ // Validate result — wrap in ResponseError as this is a server/transport problem, not user error.
12
+ return endpoint.result.validate(await super.parseResponse(endpoint, response, caller));
13
+ }
14
+ catch (thrown) {
15
+ if (typeof thrown === "string")
16
+ throw new ResponseError(`Invalid result for ${endpoint}:\n${thrown}`, { provider: this, endpoint, code: 422, caller });
17
+ throw thrown;
18
+ }
22
19
  }
23
20
  }
@@ -5,7 +5,7 @@ import type { PossibleURL } from "../../util/url.js";
5
5
  import type { Endpoint } from "../endpoint/Endpoint.js";
6
6
  import { ClientAPIProvider } from "./ClientAPIProvider.js";
7
7
  /** API provider that always sends request bodies as XML and parses responses as plain text. */
8
- export declare class XMLAPIProvider<P extends Data = Data, R = string> extends ClientAPIProvider<P, R> {
8
+ export declare class XMLAPIProvider<P extends Data = Data, R extends string = string> extends ClientAPIProvider<P, R> {
9
9
  protected _getBodyRequest(method: RequestBodyMethod, url: PossibleURL, payload: P, options: RequestOptions, caller: AnyCaller): Request;
10
10
  /**
11
11
  * Parse a text `Response` for an endpoint.
@@ -93,7 +93,7 @@ export class EndpointStore extends Store {
93
93
  /** Fetch the result from the API endpoint now. */
94
94
  async _fetch(abort) {
95
95
  try {
96
- const value = await this.provider.fetch(this.endpoint, this._payload, { signal: abort.signal });
96
+ const value = await this.provider.call(this.endpoint, this._payload, { signal: abort.signal });
97
97
  this.reason = undefined;
98
98
  this.value = value;
99
99
  }
@@ -15,6 +15,7 @@ export declare class Collection<N extends string = string, I extends Identifier
15
15
  /** Schema for a complete item (id + data). */
16
16
  readonly item: DataSchema<Item<I, T>>;
17
17
  constructor(name: N, id: Schema<I>, data: Schemas<T> | DataSchema<T>);
18
+ toString(): string;
18
19
  }
19
20
  /** Shortcut factory for creating a Collection. */
20
21
  export declare function COLLECTION<K extends string, I extends Identifier, T extends Data>(name: K, id: Schema<I>, data: Schemas<T> | DataSchema<T>): Collection<K, I, T>;
@@ -24,6 +24,9 @@ export class Collection extends DataSchema {
24
24
  this.id = id;
25
25
  this.item = ITEM(id, dataSchema);
26
26
  }
27
+ toString() {
28
+ return this.name;
29
+ }
27
30
  }
28
31
  /** Shortcut factory for creating a Collection. */
29
32
  export function COLLECTION(name, id, data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.184.1",
3
+ "version": "1.185.1",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
package/util/debug.d.ts CHANGED
@@ -6,6 +6,25 @@ import type { ImmutableSet } from "./set.js";
6
6
  export declare function debug(value: unknown, depth?: number): string;
7
7
  /** Debug a string. */
8
8
  export declare const debugString: (value: string) => string;
9
+ /** Debug a set of `Headers` as a string. */
10
+ export declare function debugHeaders(headers: Headers): string;
11
+ /**
12
+ * Debug a full `Request` as a string including its body.
13
+ * - Clones the request before reading the body so the original request can still be sent or parsed later.
14
+ * - Omits the body section when the request body is empty.
15
+ */
16
+ export declare function debugFullRequest(request: Request): Promise<string>;
17
+ /**
18
+ * Debug a full `Response` as a string including its headers and body.
19
+ * - Clones the response before reading the body so the original response can still be parsed later.
20
+ * - Omits the headers section when there are no headers.
21
+ * - Omits the body section when the response body is empty.
22
+ */
23
+ export declare function debugFullResponse(response: Response): Promise<string>;
24
+ /** Debug a `Request` as a string. */
25
+ export declare function debugRequest(request: Request): string;
26
+ /** Debug a `Response` as a string. */
27
+ export declare function debugResponse(response: Response): string;
9
28
  /** Debug an array. */
10
29
  export declare function debugArray(value: ImmutableArray, depth?: number): string;
11
30
  /** Debug a set. */
package/util/debug.js CHANGED
@@ -19,6 +19,12 @@ export function debug(value, depth = 1) {
19
19
  return value.toISOString();
20
20
  if (value instanceof Error)
21
21
  return value.toString();
22
+ if (value instanceof Request)
23
+ return debugRequest(value);
24
+ if (value instanceof Response)
25
+ return debugResponse(value);
26
+ if (value instanceof Headers)
27
+ return debugHeaders(value);
22
28
  // biome-ignore lint/suspicious/useIsArray: Intential in this context.
23
29
  if (value instanceof Array)
24
30
  return debugArray(value, depth);
@@ -45,6 +51,43 @@ const ESCAPE_LIST = {
45
51
  "\v": "\\v",
46
52
  };
47
53
  const _escapeChar = (char) => ESCAPE_LIST[char] || `\\x${char.charCodeAt(0).toString(16).padStart(2, "00")}`;
54
+ /** Debug a set of `Headers` as a string. */
55
+ export function debugHeaders(headers) {
56
+ return Array.from(headers, ([key, value]) => `${key}: ${value}`).join("\n");
57
+ }
58
+ /**
59
+ * Debug a full `Request` as a string including its body.
60
+ * - Clones the request before reading the body so the original request can still be sent or parsed later.
61
+ * - Omits the body section when the request body is empty.
62
+ */
63
+ export async function debugFullRequest(request) {
64
+ return _debugFullMessage(debugRequest(request), request);
65
+ }
66
+ /**
67
+ * Debug a full `Response` as a string including its headers and body.
68
+ * - Clones the response before reading the body so the original response can still be parsed later.
69
+ * - Omits the headers section when there are no headers.
70
+ * - Omits the body section when the response body is empty.
71
+ */
72
+ export async function debugFullResponse(response) {
73
+ return _debugFullMessage(debugResponse(response), response);
74
+ }
75
+ async function _debugFullMessage(head, message) {
76
+ const headers = debugHeaders(message.headers);
77
+ const body = await _debugMessageBody(message);
78
+ return `${head}${headers && `\n${headers}`}${body && `\n\n${body}`}`;
79
+ }
80
+ async function _debugMessageBody(message) {
81
+ return message.bodyUsed ? "[body used]" : await message.clone().text();
82
+ }
83
+ /** Debug a `Request` as a string. */
84
+ export function debugRequest(request) {
85
+ return `${request.method} ${request.url}`;
86
+ }
87
+ /** Debug a `Response` as a string. */
88
+ export function debugResponse(response) {
89
+ return `${response.status} ${response.statusText}`;
90
+ }
48
91
  /** Debug an array. */
49
92
  export function debugArray(value, depth = 1) {
50
93
  const prototype = Object.getPrototypeOf(value);