@autometa/http 1.4.20 → 2.0.0-rc.0

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.
Files changed (55) hide show
  1. package/README.md +107 -2
  2. package/dist/assertions/http-adapters.d.ts +35 -0
  3. package/dist/assertions/http-assertions-plugin.d.ts +16 -0
  4. package/dist/assertions/http-ensure.d.ts +42 -0
  5. package/dist/axios-transport.d.ts +22 -0
  6. package/dist/default-schema.d.ts +8 -0
  7. package/dist/fetch-transport.d.ts +21 -0
  8. package/dist/http-request.d.ts +109 -0
  9. package/dist/http-response.d.ts +77 -0
  10. package/dist/http.d.ts +300 -0
  11. package/dist/index.cjs +2076 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.ts +15 -1116
  14. package/dist/index.js +1727 -845
  15. package/dist/index.js.map +1 -1
  16. package/dist/plugins.d.ts +43 -0
  17. package/dist/request-meta.config.d.ts +56 -0
  18. package/dist/schema.map.d.ts +11 -0
  19. package/dist/transform-response.d.ts +1 -0
  20. package/dist/transport.d.ts +11 -0
  21. package/dist/types.d.ts +39 -0
  22. package/package.json +31 -31
  23. package/.eslintignore +0 -3
  24. package/.eslintrc.cjs +0 -4
  25. package/.turbo/turbo-lint$colon$fix.log +0 -4
  26. package/.turbo/turbo-prettify.log +0 -5
  27. package/.turbo/turbo-test.log +0 -120
  28. package/CHANGELOG.md +0 -335
  29. package/dist/esm/index.js +0 -1117
  30. package/dist/esm/index.js.map +0 -1
  31. package/dist/index.d.cts +0 -1116
  32. package/src/axios-client.ts +0 -35
  33. package/src/default-client-factory.axios.spec.ts +0 -9
  34. package/src/default-client-factory.other.spec.ts +0 -13
  35. package/src/default-client-factory.ts +0 -7
  36. package/src/default-schema.spec.ts +0 -74
  37. package/src/default-schema.ts +0 -127
  38. package/src/http-client.ts +0 -20
  39. package/src/http-request.spec.ts +0 -172
  40. package/src/http-request.ts +0 -201
  41. package/src/http-response.ts +0 -107
  42. package/src/http.spec.ts +0 -189
  43. package/src/http.ts +0 -907
  44. package/src/index.ts +0 -13
  45. package/src/node_modules/.vitest/deps/_metadata.json +0 -8
  46. package/src/node_modules/.vitest/deps/package.json +0 -3
  47. package/src/node_modules/.vitest/results.json +0 -1
  48. package/src/request-meta.config.spec.ts +0 -81
  49. package/src/request-meta.config.ts +0 -134
  50. package/src/request.config.ts +0 -34
  51. package/src/schema.map.spec.ts +0 -50
  52. package/src/schema.map.ts +0 -68
  53. package/src/transform-response.ts +0 -43
  54. package/src/types.ts +0 -37
  55. package/tsup.config.ts +0 -14
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Introduction
1
+ <!-- cspell:disable -->
2
2
 
3
- There's nothing here yet
3
+ # @autometa/http
4
+
5
+ Composable HTTP client utilities with transport adapters, fluent request builders, and batteries-included behaviors (error mapping, retries, logging, streaming).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @autometa/http
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { http } from "@autometa/http";
17
+
18
+ const client = http().baseUrl("https://api.example.com");
19
+
20
+ const response = await client
21
+ .get("/users")
22
+ .query({ limit: 25 })
23
+ .execute();
24
+
25
+ console.log(response.status); // 200
26
+ console.log(response.body); // Parsed JSON payload
27
+ ```
28
+
29
+ ## Features
30
+
31
+ - Typed error hierarchy (`HTTPError`, `HTTPTransportError`, `HTTPSchemaValidationError`) with rich metadata
32
+ - AbortSignal propagation for cancellable requests
33
+ - Configurable retry policies (max attempts, delays, custom retry predicate)
34
+ - Scoped timeouts via `sharedTimeout()` / `timeout()` with automatic aborts
35
+ - Plugin system with logging hooks (see `createHttpLogger`)
36
+ - Streaming responses via `stream()`/`asStream()` without JSON parsing
37
+
38
+ ## Error Handling
39
+
40
+ Errors thrown by the client expose status, request metadata, and transport details. Catch specific subclasses when you need to branch logic:
41
+
42
+ ```ts
43
+ import { HTTPTransportError } from "@autometa/http";
44
+
45
+ try {
46
+ await http().get("/slow").execute();
47
+ } catch (error) {
48
+ if (error instanceof HTTPTransportError) {
49
+ console.error("Transport failure", error.transport);
50
+ }
51
+ }
52
+ ```
53
+
54
+ ## Retries & Delays
55
+
56
+ ```ts
57
+ await http()
58
+ .get("/flaky")
59
+ .retry(config =>
60
+ config
61
+ .maxAttempts(3)
62
+ .delay(fn => fn.exponential({ base: 100, max: 2000 }))
63
+ .shouldRetry(({ response }) => response?.status === 503)
64
+ )
65
+ .execute();
66
+ ```
67
+
68
+ ## Streaming Responses
69
+ ## Timeouts
70
+
71
+ Apply timeouts without wiring your own `AbortController`. Timeouts compose with existing signals and respect retries:
72
+
73
+ ```ts
74
+ await http()
75
+ .timeout(5000)
76
+ .get();
77
+
78
+ // combine with a per-request signal
79
+ const controller = new AbortController();
80
+ await http()
81
+ .timeout(2000)
82
+ .get({ signal: controller.signal });
83
+ ```
84
+
85
+ Timeouts use `AbortController` under the hood and can be shared (`sharedTimeout`) or scoped per request.
86
+
87
+ Set `stream()` when you want the raw response stream. Transports bypass JSON parsing and validation so you can pipe the body directly:
88
+
89
+ ```ts
90
+ const stream = await http()
91
+ .get("/events")
92
+ .stream()
93
+ .execute();
94
+
95
+ for await (const chunk of stream.body) {
96
+ process.stdout.write(chunk);
97
+ }
98
+ ```
99
+
100
+ ## Testing
101
+
102
+ The package ships with Vitest unit suites and integration tests that exercise the fluent builder against live-like transports. Run everything with:
103
+
104
+ ```bash
105
+ pnpm --filter @autometa/http test
106
+ ```
107
+
108
+ Integration tests are the recommended place to add coverage for additional transport behaviors (such as verifying streaming support end-to-end).
@@ -0,0 +1,35 @@
1
+ type HeadersLike = {
2
+ get(name: string): string | null;
3
+ has?(name: string): boolean;
4
+ entries?: () => IterableIterator<[string, string]>;
5
+ [Symbol.iterator]?: () => IterableIterator<[string, string]>;
6
+ };
7
+ type HeaderSource = HeadersLike | Record<string, unknown>;
8
+ export type HttpResponseLike = {
9
+ status: number;
10
+ statusText?: string;
11
+ headers: HeaderSource;
12
+ data?: unknown;
13
+ raw?: unknown;
14
+ };
15
+ /**
16
+ * Adapts an @autometa/http `HTTPResponse` (or any similarly-shaped object) into a
17
+ * stable `HttpResponseLike` for HTTP assertions.
18
+ */
19
+ export declare function fromHttpResponse<T extends {
20
+ headers: Record<string, string>;
21
+ }>(response: T & {
22
+ status: number;
23
+ statusText?: string;
24
+ data?: unknown;
25
+ }): HttpResponseLike;
26
+ /**
27
+ * Adapts a fetch-style response (or any similarly-shaped object) into a stable
28
+ * `HttpResponseLike` for HTTP assertions.
29
+ */
30
+ export declare function fromFetchResponse(response: {
31
+ status: number;
32
+ statusText?: string;
33
+ headers: HeadersLike;
34
+ }, data?: unknown): HttpResponseLike;
35
+ export {};
@@ -0,0 +1,16 @@
1
+ import type { AssertionPlugin, EnsureOptions } from "@autometa/assertions";
2
+ import type { HTTPResponse } from "../http-response";
3
+ import { type HttpEnsureChain, type HttpResponseLike } from "./http-ensure";
4
+ /**
5
+ * Callable facet attached as `ensure.http(...)`.
6
+ *
7
+ * Supports plugin-level negation: `ensure.not.http(response)`.
8
+ * Also supports chain-level negation: `ensure.http(response).not...`.
9
+ */
10
+ export type HttpAssertionsFacet = <T = unknown>(response: HttpResponseLike | HTTPResponse<T>, options?: EnsureOptions) => HttpEnsureChain<HttpResponseLike>;
11
+ /**
12
+ * Assertion plugin that provides HTTP response matchers as a facet.
13
+ *
14
+ * This keeps the base `ensure(value)` matcher chain domain-agnostic.
15
+ */
16
+ export declare const httpAssertionsPlugin: <World>() => AssertionPlugin<World, HttpAssertionsFacet>;
@@ -0,0 +1,42 @@
1
+ import { type EnsureOptions } from "@autometa/assertions";
2
+ type HeadersLike = {
3
+ get(name: string): string | null;
4
+ has?(name: string): boolean;
5
+ entries?: () => IterableIterator<[string, string]>;
6
+ [Symbol.iterator]?: () => IterableIterator<[string, string]>;
7
+ };
8
+ type HeaderSource = HeadersLike | Record<string, unknown>;
9
+ export type HttpResponseLike = {
10
+ status: number;
11
+ statusText?: string;
12
+ headers: HeaderSource;
13
+ data?: unknown;
14
+ raw?: unknown;
15
+ };
16
+ export type StatusExpectation = number | `${1 | 2 | 3 | 4 | 5}xx` | readonly [number, number] | {
17
+ readonly min: number;
18
+ readonly max: number;
19
+ } | ((status: number) => boolean);
20
+ export type HeaderExpectation = string | RegExp | readonly string[] | ((value: string) => boolean);
21
+ export interface CacheControlExpectation {
22
+ readonly cacheability?: "public" | "private";
23
+ readonly maxAge?: number | {
24
+ readonly min?: number;
25
+ readonly max?: number;
26
+ };
27
+ readonly sMaxAge?: number;
28
+ readonly revalidate?: boolean;
29
+ readonly immutable?: boolean;
30
+ }
31
+ export interface HttpEnsureChain<T extends HttpResponseLike = HttpResponseLike> {
32
+ readonly value: T;
33
+ readonly not: HttpEnsureChain<T>;
34
+ toHaveStatus(expectation: StatusExpectation): HttpEnsureChain<T>;
35
+ toHaveHeader(name: string, expectation?: HeaderExpectation): HttpEnsureChain<T>;
36
+ toBeCacheable(expectation?: CacheControlExpectation): HttpEnsureChain<T>;
37
+ toHaveCorrelationId(headerName?: string): HttpEnsureChain<T>;
38
+ }
39
+ export declare function ensureHttp<T extends HttpResponseLike>(response: T, options?: EnsureOptions & {
40
+ readonly negated?: boolean;
41
+ }): HttpEnsureChain<T>;
42
+ export {};
@@ -0,0 +1,22 @@
1
+ /// <reference types="node" />
2
+ import type { HTTPTransport } from "./transport";
3
+ export interface AxiosRequestConfigLike extends Record<string, unknown> {
4
+ url?: string;
5
+ method?: string;
6
+ headers?: Record<string, string | number | boolean | null | undefined>;
7
+ params?: Record<string, unknown>;
8
+ data?: unknown;
9
+ validateStatus?: (status: number) => boolean;
10
+ signal?: AbortSignal;
11
+ responseType?: string;
12
+ }
13
+ export interface AxiosResponseLike<T = unknown> {
14
+ status: number;
15
+ statusText: string;
16
+ data: T;
17
+ headers?: Record<string, string | string[]>;
18
+ }
19
+ export interface AxiosLike {
20
+ request<T = unknown, R = AxiosResponseLike<T>>(config: AxiosRequestConfigLike): Promise<R>;
21
+ }
22
+ export declare function createAxiosTransport(axios: AxiosLike): HTTPTransport<AxiosRequestConfigLike>;
@@ -0,0 +1,8 @@
1
+ export declare function AnySchema<T = unknown>(data: T): T;
2
+ export declare function EmptySchema(data: unknown): null | undefined;
3
+ export declare function NullSchema(data: unknown): null;
4
+ export declare function UndefinedSchema(data: unknown): undefined;
5
+ export declare function BooleanSchema(data: unknown): boolean;
6
+ export declare function NumberSchema(data: unknown): number;
7
+ export declare function StringSchema(data: unknown): string;
8
+ export declare function JSONSchema<T = unknown>(data: unknown): T;
@@ -0,0 +1,21 @@
1
+ /// <reference types="node" />
2
+ import type { HTTPTransport } from "./transport";
3
+ export interface FetchRequestOptions extends Record<string, unknown> {
4
+ headers?: Record<string, string | number | boolean | null | undefined>;
5
+ body?: unknown;
6
+ method?: string;
7
+ signal?: AbortSignal;
8
+ streamResponse?: boolean;
9
+ }
10
+ export interface FetchResponseLike {
11
+ status: number;
12
+ statusText: string;
13
+ headers: HeadersLike;
14
+ text(): Promise<string>;
15
+ body?: unknown;
16
+ }
17
+ export interface HeadersLike {
18
+ forEach(callback: (value: string, key: string) => void): void;
19
+ }
20
+ export type FetchLike = (input: string, init?: FetchRequestOptions) => Promise<FetchResponseLike>;
21
+ export declare function createFetchTransport(fetchImpl?: FetchLike): HTTPTransport<FetchRequestOptions>;
@@ -0,0 +1,109 @@
1
+ import type { HTTPMethod, QueryParamSerializationOptions, QueryParamValue, QueryParamPrimitive } from "./types";
2
+ export type HeaderPrimitive = string | number | boolean | null | undefined;
3
+ export type HeaderFactory = (() => HeaderPrimitive | Promise<HeaderPrimitive>) | undefined;
4
+ export type ParamPrimitive = QueryParamPrimitive;
5
+ export type ParamDictionary = Record<string, ParamValue>;
6
+ export type ParamValue = QueryParamValue | undefined;
7
+ export interface RequestConfigBasic<T = unknown> {
8
+ headers?: Record<string, string>;
9
+ params?: Record<string, ParamValue>;
10
+ baseUrl?: string;
11
+ route?: string[];
12
+ method?: HTTPMethod;
13
+ data?: T;
14
+ queryOptions?: QueryParamSerializationOptions;
15
+ }
16
+ /**
17
+ * Represents the request payload sent via {@link HTTP} including URL, headers and metadata.
18
+ */
19
+ export declare class HTTPRequest<T = unknown> {
20
+ /**
21
+ * Normalised header collection that will be sent with the request.
22
+ */
23
+ headers: Record<string, string>;
24
+ params: Record<string, ParamValue>;
25
+ baseUrl: string | undefined;
26
+ route: string[];
27
+ method: HTTPMethod | undefined;
28
+ data: T | undefined;
29
+ queryOptions: QueryParamSerializationOptions;
30
+ constructor(config?: RequestConfigBasic<T>);
31
+ /**
32
+ * Full request URL derived from {@link baseUrl}, {@link route} and {@link params}.
33
+ */
34
+ get fullUrl(): string;
35
+ /**
36
+ * Creates a deep copy of an existing request instance.
37
+ */
38
+ static derive<T>(original: HTTPRequest<T>): HTTPRequest<T>;
39
+ }
40
+ /**
41
+ * Fluent utility used to construct {@link HTTPRequest} instances while keeping internal state safe.
42
+ */
43
+ export declare class HTTPRequestBuilder<T extends HTTPRequest<unknown>> {
44
+ private requestInstance;
45
+ private dynamicHeaders;
46
+ private queryOptions;
47
+ constructor(request?: T | (() => T));
48
+ /**
49
+ * Initializes a new builder for the default {@link HTTPRequest} type.
50
+ */
51
+ static create<T extends HTTPRequest<unknown>>(): HTTPRequestBuilder<T>;
52
+ /**
53
+ * Exposes the underlying request without defensive cloning.
54
+ */
55
+ get request(): T;
56
+ /**
57
+ * Resolves asynchronous header factories into concrete header values on demand.
58
+ */
59
+ resolveDynamicHeaders(request?: HTTPRequest): Promise<this>;
60
+ /**
61
+ * Sets the root URL (protocol, host and optional base path).
62
+ */
63
+ url(url: string): this;
64
+ /**
65
+ * Appends one or more path segments to the current request route.
66
+ */
67
+ route(...segments: (string | number | boolean)[]): this;
68
+ /**
69
+ * Adds or removes a query parameter value.
70
+ */
71
+ param(name: string, value: ParamValue): this;
72
+ /**
73
+ * Merges a dictionary of query parameters into the request.
74
+ */
75
+ params(dict: Record<string, unknown>): this;
76
+ queryFormat(options: QueryParamSerializationOptions): this;
77
+ /**
78
+ * Sets the request body payload. Passing `undefined` removes the body.
79
+ */
80
+ data<K>(data: K | undefined): this;
81
+ /**
82
+ * Sets a single header using direct values or a lazy factory.
83
+ */
84
+ header(name: string, value: HeaderPrimitive | HeaderPrimitive[] | (() => HeaderPrimitive | Promise<HeaderPrimitive>)): this;
85
+ /**
86
+ * Replaces or merges multiple headers in one call.
87
+ */
88
+ headers(dict: Record<string, HeaderPrimitive | HeaderPrimitive[]>): this;
89
+ /**
90
+ * Stores the HTTP verb ensuring consistent casing.
91
+ */
92
+ method(method: HTTPMethod): this;
93
+ /**
94
+ * Returns a copy-on-write builder pointing at the same request state.
95
+ */
96
+ derive(): HTTPRequestBuilder<T>;
97
+ /**
98
+ * Produces a deep copy of the builder and the underlying request.
99
+ */
100
+ clone(): HTTPRequestBuilder<T>;
101
+ /**
102
+ * Returns the current request without resolving header factories.
103
+ */
104
+ build(): HTTPRequest<T>;
105
+ /**
106
+ * Resolves lazy headers before returning the request.
107
+ */
108
+ buildAsync(): Promise<HTTPRequest<T>>;
109
+ }
@@ -0,0 +1,77 @@
1
+ import type { HTTPRequest } from "./http-request";
2
+ import type { StatusCode } from "./types";
3
+ /**
4
+ * Immutable view of a completed HTTP response produced by {@link HTTP}.
5
+ *
6
+ * Use {@link HTTPResponseBuilder} to clone or modify instances in a safe way.
7
+ */
8
+ export declare class HTTPResponse<T = unknown> {
9
+ /**
10
+ * HTTP status code returned by the remote service.
11
+ */
12
+ status: StatusCode;
13
+ /**
14
+ * Human-readable text accompanying {@link status}.
15
+ */
16
+ statusText: string;
17
+ /**
18
+ * Response payload after all transformations and schema validation.
19
+ */
20
+ data: T;
21
+ /**
22
+ * Normalised response headers keyed by lowercase header names.
23
+ */
24
+ headers: Record<string, string>;
25
+ /**
26
+ * Original request that produced this response.
27
+ */
28
+ request: HTTPRequest<unknown>;
29
+ /**
30
+ * Creates a shallow clone from an existing response instance.
31
+ */
32
+ static fromRaw<T>(response: HTTPResponse<T>): HTTPResponse<T>;
33
+ mapData<K>(value: K): HTTPResponse<K>;
34
+ mapData<K>(transform: (response: T) => K): HTTPResponse<K>;
35
+ }
36
+ /**
37
+ * Fluent builder used by {@link HTTP} to create {@link HTTPResponse} instances.
38
+ */
39
+ export declare class HTTPResponseBuilder {
40
+ private response;
41
+ /**
42
+ * Creates a new empty builder.
43
+ */
44
+ static create(): HTTPResponseBuilder;
45
+ /**
46
+ * Produces a new builder seeded with the current response state.
47
+ */
48
+ derive(): HTTPResponseBuilder;
49
+ /**
50
+ * Sets the response status code.
51
+ */
52
+ status(code: StatusCode): this;
53
+ /**
54
+ * Sets the textual status message.
55
+ */
56
+ statusText(text: string): this;
57
+ /**
58
+ * Attaches the response payload.
59
+ */
60
+ data<T>(data: T): this;
61
+ /**
62
+ * Replaces the entire headers collection.
63
+ */
64
+ headers(dict: Record<string, string>): this;
65
+ /**
66
+ * Sets or overrides a single response header.
67
+ */
68
+ header(name: string, value: string): this;
69
+ /**
70
+ * References the originating request.
71
+ */
72
+ request(request: HTTPRequest<unknown>): this;
73
+ /**
74
+ * Builds the immutable {@link HTTPResponse} instance.
75
+ */
76
+ build(): HTTPResponse<unknown>;
77
+ }