@jamx/http 0.1.0 → 0.1.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.
- package/README.md +75 -4
- package/build/internal/core.d.ts +57 -12
- package/build/internal/core.d.ts.map +1 -1
- package/build/internal/core.js +49 -14
- package/build/internal/decoders.d.ts +2 -1
- package/build/internal/decoders.d.ts.map +1 -1
- package/build/internal/decoders.js +12 -10
- package/build/internal/interceptors.d.ts +38 -34
- package/build/internal/interceptors.d.ts.map +1 -1
- package/build/internal/interceptors.js +122 -90
- package/build/internal/statuses/validators.d.ts +15 -15
- package/build/internal/statuses/validators.d.ts.map +1 -1
- package/build/internal/statuses/validators.js +40 -16
- package/build/internal/types.d.ts +1 -1
- package/build/internal/types.d.ts.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,10 +13,11 @@ pnpm add @jamx/http
|
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
15
|
import {
|
|
16
|
-
|
|
16
|
+
compose,
|
|
17
17
|
createMemoryCacheStore,
|
|
18
18
|
defaultFetch,
|
|
19
19
|
withAuth,
|
|
20
|
+
withBaseUrl,
|
|
20
21
|
withCache,
|
|
21
22
|
withHeaders,
|
|
22
23
|
withRetry,
|
|
@@ -25,14 +26,16 @@ import {
|
|
|
25
26
|
|
|
26
27
|
const cache = createMemoryCacheStore();
|
|
27
28
|
|
|
28
|
-
const handler =
|
|
29
|
+
const handler = compose(
|
|
29
30
|
withTimeout(250),
|
|
31
|
+
withBaseUrl("https://api.example.com/v1"),
|
|
30
32
|
withHeaders({ accept: "application/json" }),
|
|
31
33
|
withAuth("demo-token"),
|
|
32
|
-
withJsonBody({ tenant: "team-a" }),
|
|
33
34
|
withCache({ store: cache }),
|
|
34
35
|
withRetry({ retries: 1 }),
|
|
35
36
|
)(defaultFetch);
|
|
37
|
+
|
|
38
|
+
const response = await handler("/users/42");
|
|
36
39
|
```
|
|
37
40
|
|
|
38
41
|
## Core APIs
|
|
@@ -44,6 +47,7 @@ import {
|
|
|
44
47
|
defaultContext,
|
|
45
48
|
defaultFetch,
|
|
46
49
|
expectStatus,
|
|
50
|
+
normalizeInput,
|
|
47
51
|
} from "@jamx/http";
|
|
48
52
|
|
|
49
53
|
const customFetch = createFetchHandler(defaultContext);
|
|
@@ -56,7 +60,71 @@ const user = await decodeJson(expectStatus(response, 200), decodeUser);
|
|
|
56
60
|
- `defaultFetch` is `createFetchHandler(defaultContext)`.
|
|
57
61
|
- `createFetchHandler(...)` is useful when you want to inject a mocked or custom
|
|
58
62
|
fetch implementation.
|
|
59
|
-
- `
|
|
63
|
+
- `compose(...)` returns an executable handler with a composed result.
|
|
64
|
+
- `normalizeInput(...)` converts either `{ input, init }` or fetch-style
|
|
65
|
+
`(input, init?)` into the normalized request shape used by interceptors.
|
|
66
|
+
|
|
67
|
+
## Interceptors
|
|
68
|
+
|
|
69
|
+
Interceptors receive a grouped request object and a `next` function.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { compose, defaultFetch, defineInterceptor } from "@jamx/http";
|
|
73
|
+
|
|
74
|
+
const withTenant = defineInterceptor(async ({ request, next }) => {
|
|
75
|
+
const headers = new Headers(request.init?.headers);
|
|
76
|
+
headers.set("x-tenant", "team-a");
|
|
77
|
+
|
|
78
|
+
return next({
|
|
79
|
+
input: request.input,
|
|
80
|
+
init: { ...request.init, headers },
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const handler = compose(withTenant)(defaultFetch);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The request shape mirrors `fetch(input, init?)`:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
type Input = {
|
|
91
|
+
input: RequestInfo | URL;
|
|
92
|
+
init?: RequestInit;
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`next` accepts all of these forms:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
await next();
|
|
100
|
+
await next({ input: "https://api.example.com/users" });
|
|
101
|
+
await next("https://api.example.com/users");
|
|
102
|
+
await next("https://api.example.com/users", {
|
|
103
|
+
headers: { accept: "application/json" },
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`compose` keeps requests in this normalized shape until the terminal fetch
|
|
108
|
+
handler runs. That allows `withBaseUrl` to resolve relative paths before a
|
|
109
|
+
platform `Request` is constructed:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const api = compose(withBaseUrl("https://api.example.com/v1"))(defaultFetch);
|
|
113
|
+
|
|
114
|
+
await api("/users?role=admin");
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Use `normalizeInput` when a helper accepts either normalized input or fetch-style
|
|
118
|
+
arguments:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const normalized = normalizeInput("https://api.example.com/users", {
|
|
122
|
+
method: "POST",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
normalized.input;
|
|
126
|
+
normalized.init;
|
|
127
|
+
```
|
|
60
128
|
|
|
61
129
|
## Decoder Helpers
|
|
62
130
|
|
|
@@ -83,6 +151,9 @@ result type.
|
|
|
83
151
|
- `withBaseUrl` rebases request paths onto a configured base URL.
|
|
84
152
|
- Put request-shaping interceptors like `withHeaders` and `withAuth` before
|
|
85
153
|
`withCache` so cache keys can include the final request headers.
|
|
154
|
+
- Static `withHeaders(...)` and `withAuth(...)` values can run before or after
|
|
155
|
+
`withBaseUrl`. Callback forms that receive a concrete `Request` should run
|
|
156
|
+
after `withBaseUrl` when the original input may be relative.
|
|
86
157
|
- `withRetry` only retries idempotent methods by default. Pass
|
|
87
158
|
`methods: ["POST"]` if you need to opt a write request into replay.
|
|
88
159
|
- `validate(result, schema)` accepts an `Either` plus a Standard Schema
|
package/build/internal/core.d.ts
CHANGED
|
@@ -14,15 +14,38 @@ export interface Context {
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
export declare const defaultContext: Context;
|
|
17
|
+
/**
|
|
18
|
+
* Normalized fetch input carried through an interceptor chain.
|
|
19
|
+
*
|
|
20
|
+
* This mirrors the platform `fetch(input, init?)` call shape while keeping the
|
|
21
|
+
* pair together as a single value for interceptors and handlers.
|
|
22
|
+
*/
|
|
17
23
|
export interface Input {
|
|
18
24
|
input: RequestInfo | URL;
|
|
19
25
|
init?: RequestInit;
|
|
20
26
|
}
|
|
21
27
|
export type Result = Either<FetchError, Response>;
|
|
22
|
-
export type Handler = (request:
|
|
23
|
-
|
|
28
|
+
export type Handler<TResult extends AnyEither = Result> = ((request: Input) => Promise<TResult>) & ExecutableHandler<TResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Continues an interceptor chain.
|
|
31
|
+
*
|
|
32
|
+
* Calling `next()` forwards the current request unchanged. Passing either a
|
|
33
|
+
* normalized `Input` object or fetch-style `(input, init?)` replaces the request
|
|
34
|
+
* seen by downstream interceptors and the final handler.
|
|
35
|
+
*/
|
|
36
|
+
export interface Next<R extends AnyEither> {
|
|
37
|
+
(): Promise<R>;
|
|
38
|
+
(request: Input): Promise<R>;
|
|
39
|
+
(input: RequestInfo | URL, init?: RequestInit): Promise<R>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Context passed to each interceptor.
|
|
43
|
+
*
|
|
44
|
+
* `request` is the normalized `{ input, init }` form, so relative URLs can move
|
|
45
|
+
* through the chain until an interceptor such as `withBaseUrl` resolves them.
|
|
46
|
+
*/
|
|
24
47
|
export interface InterceptorContext<T extends AnyEither> {
|
|
25
|
-
request:
|
|
48
|
+
request: Input;
|
|
26
49
|
next: Next<T>;
|
|
27
50
|
}
|
|
28
51
|
export interface Chain extends InterceptorContext<Result> {
|
|
@@ -53,13 +76,9 @@ export interface ExecutableHandler<TResult extends AnyEither> {
|
|
|
53
76
|
*/
|
|
54
77
|
export declare class FetchError extends Error {
|
|
55
78
|
name: string;
|
|
79
|
+
readonly _tag = "FetchError";
|
|
56
80
|
constructor(message?: string, cause?: unknown);
|
|
57
81
|
}
|
|
58
|
-
export declare class TimeoutError extends FetchError {
|
|
59
|
-
name: string;
|
|
60
|
-
readonly timeoutMs: number;
|
|
61
|
-
constructor(timeoutMs: number, cause?: unknown);
|
|
62
|
-
}
|
|
63
82
|
/**
|
|
64
83
|
* Creates a `fetch`-backed handler that returns `Either<FetchError, Response>`.
|
|
65
84
|
*
|
|
@@ -82,21 +101,25 @@ export declare const createFetchHandler: (context: Context) => Handler;
|
|
|
82
101
|
* const result = await defaultFetch("https://example.com/users");
|
|
83
102
|
* ```
|
|
84
103
|
*/
|
|
85
|
-
export declare const defaultFetch: Handler
|
|
104
|
+
export declare const defaultFetch: Handler<Result>;
|
|
86
105
|
/**
|
|
87
106
|
* Composes one or more interceptors into an executable HTTP handler.
|
|
88
107
|
*
|
|
108
|
+
* `compose` keeps the original fetch-style input in normalized `{ input, init }`
|
|
109
|
+
* form until the terminal handler runs. This lets URL-shaping interceptors
|
|
110
|
+
* resolve relative paths before a platform `Request` is constructed.
|
|
111
|
+
*
|
|
89
112
|
* @example
|
|
90
113
|
* ```ts
|
|
91
|
-
* import {
|
|
114
|
+
* import { compose, defaultFetch, withAuth, withTimeout } from "@jamx/http";
|
|
92
115
|
*
|
|
93
|
-
* const handler =
|
|
116
|
+
* const handler = compose(
|
|
94
117
|
* withTimeout(250),
|
|
95
118
|
* withAuth("demo-token"),
|
|
96
119
|
* )(defaultFetch);
|
|
97
120
|
* ```
|
|
98
121
|
*/
|
|
99
|
-
export declare const
|
|
122
|
+
export declare const compose: <const TInterceptors extends readonly AnyInterceptor[]>(...interceptors: TInterceptors) => <THandler extends Handler>(handler: THandler) => ExecutableHandler<ComposeInterceptorsResult<TInterceptors>>;
|
|
100
123
|
/**
|
|
101
124
|
* Defines an interceptor while preserving its inferred result type.
|
|
102
125
|
*
|
|
@@ -111,5 +134,27 @@ export declare const composeInterceptors: <const TInterceptors extends readonly
|
|
|
111
134
|
* ```
|
|
112
135
|
*/
|
|
113
136
|
export declare const defineInterceptor: <TInterceptor extends (args: Chain) => Promise<AnyEither>>(interceptor: TInterceptor) => TInterceptor;
|
|
137
|
+
/**
|
|
138
|
+
* Converts supported handler and `next` call forms into normalized `Input`.
|
|
139
|
+
*
|
|
140
|
+
* It accepts either an existing `{ input, init }` object or the platform
|
|
141
|
+
* fetch-style pair `(input, init?)`. Existing normalized values are returned as
|
|
142
|
+
* is so interceptors can pass request objects through without cloning.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* import { normalizeInput } from "@jamx/http";
|
|
147
|
+
*
|
|
148
|
+
* normalizeInput("https://example.com", {
|
|
149
|
+
* headers: { accept: "application/json" },
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* normalizeInput({
|
|
153
|
+
* input: new URL("https://example.com"),
|
|
154
|
+
* init: { method: "POST" },
|
|
155
|
+
* });
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export declare function normalizeInput(request: Input | RequestInfo | URL, init?: RequestInit): Input;
|
|
114
159
|
export {};
|
|
115
160
|
//# sourceMappingURL=core.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/internal/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAW,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,OAE5B,CAAC;AAEF,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,WAAW,GAAG,GAAG,CAAC;IACzB,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAElD,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/internal/core.ts"],"names":[],"mappings":"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"}
|
package/build/internal/core.js
CHANGED
|
@@ -19,13 +19,7 @@ export class FetchError extends Error {
|
|
|
19
19
|
constructor(message = "Fetch request failed.", cause) {
|
|
20
20
|
super(message, { cause });
|
|
21
21
|
this.name = "FetchError";
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
export class TimeoutError extends FetchError {
|
|
25
|
-
constructor(timeoutMs, cause) {
|
|
26
|
-
super(`Fetch request timed out after ${timeoutMs}ms.`, cause);
|
|
27
|
-
this.name = "TimeoutError";
|
|
28
|
-
this.timeoutMs = timeoutMs;
|
|
22
|
+
this._tag = "FetchError";
|
|
29
23
|
}
|
|
30
24
|
}
|
|
31
25
|
/**
|
|
@@ -40,15 +34,17 @@ export class TimeoutError extends FetchError {
|
|
|
40
34
|
* ```
|
|
41
35
|
*/
|
|
42
36
|
export const createFetchHandler = (context) => {
|
|
43
|
-
|
|
37
|
+
const handler = async (request, init) => {
|
|
38
|
+
const input = normalizeInput(request, init);
|
|
44
39
|
try {
|
|
45
|
-
const response = await context.fetch(
|
|
40
|
+
const response = await context.fetch(new Request(input.input, input.init));
|
|
46
41
|
return ok(response);
|
|
47
42
|
}
|
|
48
43
|
catch (error) {
|
|
49
44
|
return err(normalizeError(error));
|
|
50
45
|
}
|
|
51
46
|
};
|
|
47
|
+
return handler;
|
|
52
48
|
};
|
|
53
49
|
/**
|
|
54
50
|
* Ready-to-use HTTP handler backed by `globalThis.fetch`.
|
|
@@ -64,19 +60,23 @@ export const defaultFetch = createFetchHandler(defaultContext);
|
|
|
64
60
|
/**
|
|
65
61
|
* Composes one or more interceptors into an executable HTTP handler.
|
|
66
62
|
*
|
|
63
|
+
* `compose` keeps the original fetch-style input in normalized `{ input, init }`
|
|
64
|
+
* form until the terminal handler runs. This lets URL-shaping interceptors
|
|
65
|
+
* resolve relative paths before a platform `Request` is constructed.
|
|
66
|
+
*
|
|
67
67
|
* @example
|
|
68
68
|
* ```ts
|
|
69
|
-
* import {
|
|
69
|
+
* import { compose, defaultFetch, withAuth, withTimeout } from "@jamx/http";
|
|
70
70
|
*
|
|
71
|
-
* const handler =
|
|
71
|
+
* const handler = compose(
|
|
72
72
|
* withTimeout(250),
|
|
73
73
|
* withAuth("demo-token"),
|
|
74
74
|
* )(defaultFetch);
|
|
75
75
|
* ```
|
|
76
76
|
*/
|
|
77
|
-
export const
|
|
77
|
+
export const compose = (...interceptors) => (handler) => {
|
|
78
78
|
const execute = async (input, init) => {
|
|
79
|
-
const request =
|
|
79
|
+
const request = { input, init };
|
|
80
80
|
const dispatch = async (index, currentRequest) => {
|
|
81
81
|
const interceptor = interceptors[index];
|
|
82
82
|
if (!interceptor) {
|
|
@@ -84,7 +84,9 @@ export const composeInterceptors = (...interceptors) => (handler) => {
|
|
|
84
84
|
}
|
|
85
85
|
return interceptor({
|
|
86
86
|
request: currentRequest,
|
|
87
|
-
next: (nextRequest
|
|
87
|
+
next: (nextRequest, init) => dispatch(index + 1, nextRequest === undefined
|
|
88
|
+
? currentRequest
|
|
89
|
+
: normalizeInput(nextRequest, init)),
|
|
88
90
|
});
|
|
89
91
|
};
|
|
90
92
|
return dispatch(0, request);
|
|
@@ -105,6 +107,39 @@ export const composeInterceptors = (...interceptors) => (handler) => {
|
|
|
105
107
|
* ```
|
|
106
108
|
*/
|
|
107
109
|
export const defineInterceptor = (interceptor) => interceptor;
|
|
110
|
+
/**
|
|
111
|
+
* Converts supported handler and `next` call forms into normalized `Input`.
|
|
112
|
+
*
|
|
113
|
+
* It accepts either an existing `{ input, init }` object or the platform
|
|
114
|
+
* fetch-style pair `(input, init?)`. Existing normalized values are returned as
|
|
115
|
+
* is so interceptors can pass request objects through without cloning.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* import { normalizeInput } from "@jamx/http";
|
|
120
|
+
*
|
|
121
|
+
* normalizeInput("https://example.com", {
|
|
122
|
+
* headers: { accept: "application/json" },
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* normalizeInput({
|
|
126
|
+
* input: new URL("https://example.com"),
|
|
127
|
+
* init: { method: "POST" },
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function normalizeInput(request, init) {
|
|
132
|
+
if (isInput(request))
|
|
133
|
+
return request;
|
|
134
|
+
return { input: request, init };
|
|
135
|
+
}
|
|
136
|
+
function isInput(request) {
|
|
137
|
+
return (typeof request === "object" &&
|
|
138
|
+
request !== null &&
|
|
139
|
+
"input" in request &&
|
|
140
|
+
!(request instanceof Request) &&
|
|
141
|
+
!(request instanceof URL));
|
|
142
|
+
}
|
|
108
143
|
function normalizeError(error) {
|
|
109
144
|
if (error instanceof FetchError) {
|
|
110
145
|
return error;
|
|
@@ -42,12 +42,13 @@ export declare class SchemaError extends Error {
|
|
|
42
42
|
* const result = await validate(parsed, schema);
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
|
-
export declare
|
|
45
|
+
export declare function validate<TError, TDecodedValue, TSchema extends StandardSchemaV1<TDecodedValue, any>>(result: Either<TError, TDecodedValue>, schema: TSchema): Promise<Either<TError | SchemaError, StandardSchemaV1.InferOutput<TSchema>>>;
|
|
46
46
|
/**
|
|
47
47
|
* Error returned when a body helper cannot parse a response payload.
|
|
48
48
|
*/
|
|
49
49
|
export declare class ParseError extends Error {
|
|
50
50
|
name: string;
|
|
51
|
+
readonly _tag = "ParseError";
|
|
51
52
|
readonly response: Response;
|
|
52
53
|
readonly operation: "json" | "text" | "empty";
|
|
53
54
|
constructor(response: Response, operation: "json" | "text" | "empty", cause?: unknown);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decoders.d.ts","sourceRoot":"","sources":["../../src/internal/decoders.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAS,KAAK,MAAM,EAAkB,MAAM,aAAa,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,MAAM,SAAS,WAAW,GAAG,WAAW,IAAI,CACtE,KAAK,EAAE,OAAO,KACX,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,EACN,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,SAAS,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,KACrC,OAAO,CAAC,MAAM,EAAE,YAAY,CAAY,CAAC;AAE5C;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,IAAI,SAAiB;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;gBAGrD,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAC7C,KAAK,CAAC,EAAE,OAAO;CAMlB;AAED;;;;;;;;;;;;GAYG;AACH,
|
|
1
|
+
{"version":3,"file":"decoders.d.ts","sourceRoot":"","sources":["../../src/internal/decoders.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,16 +38,17 @@ export class SchemaError extends Error {
|
|
|
38
38
|
* const result = await validate(parsed, schema);
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})();
|
|
41
|
+
export function validate(result, schema) {
|
|
42
|
+
return (async () => {
|
|
43
|
+
if (isErr(result))
|
|
44
|
+
return result;
|
|
45
|
+
const validated = await schema["~standard"].validate(result.value);
|
|
46
|
+
if (validated.issues) {
|
|
47
|
+
return err(new SchemaError(schema["~standard"].vendor, validated.issues, validated));
|
|
48
|
+
}
|
|
49
|
+
return ok(validated.value);
|
|
50
|
+
})();
|
|
51
|
+
}
|
|
51
52
|
/**
|
|
52
53
|
* Error returned when a body helper cannot parse a response payload.
|
|
53
54
|
*/
|
|
@@ -55,6 +56,7 @@ export class ParseError extends Error {
|
|
|
55
56
|
constructor(response, operation, cause) {
|
|
56
57
|
super(`Failed to parse response body as ${operation}.`, { cause });
|
|
57
58
|
this.name = "ParseError";
|
|
59
|
+
this._tag = "ParseError";
|
|
58
60
|
this.response = response;
|
|
59
61
|
this.operation = operation;
|
|
60
62
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Input, type Result } from "./core.js";
|
|
2
2
|
import type { Left } from "./either.js";
|
|
3
3
|
/**
|
|
4
4
|
* Rebases a request URL onto a configured base URL while preserving the
|
|
@@ -6,87 +6,91 @@ import type { Left } from "./either.js";
|
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
|
9
|
-
* import {
|
|
9
|
+
* import { compose, defaultFetch, withBaseUrl } from "@jamx/http";
|
|
10
10
|
*
|
|
11
|
-
* const fetcher =
|
|
12
|
-
* withBaseUrl("https://api.example.com/v1"),
|
|
13
|
-
* )(defaultFetch);
|
|
11
|
+
* const fetcher = compose(withBaseUrl("https://api.example.com/v1"))(defaultFetch);
|
|
14
12
|
* ```
|
|
15
13
|
*/
|
|
16
|
-
export declare
|
|
14
|
+
export declare function withBaseUrl(base_url: string | URL): ({ request, next }: import("./core.js").Chain) => Promise<Result>;
|
|
17
15
|
/**
|
|
18
16
|
* Merges additional headers into the outgoing request.
|
|
19
17
|
*
|
|
20
18
|
* @example
|
|
21
19
|
* ```ts
|
|
22
|
-
* import {
|
|
20
|
+
* import { compose, defaultFetch, withHeaders } from "@jamx/http";
|
|
23
21
|
*
|
|
24
|
-
* const fetcher =
|
|
22
|
+
* const fetcher = compose(
|
|
25
23
|
* withHeaders({ accept: "application/json" }),
|
|
26
24
|
* )(defaultFetch);
|
|
27
25
|
* ```
|
|
28
26
|
*/
|
|
29
|
-
export declare
|
|
27
|
+
export declare function withHeaders(headers: HeadersInit | ((request: Request) => HeadersInit)): ({ request, next }: import("./core.js").Chain) => Promise<Result>;
|
|
30
28
|
/**
|
|
31
29
|
* Adds an `Authorization` header using the given token and scheme.
|
|
32
30
|
*
|
|
33
31
|
* @example
|
|
34
32
|
* ```ts
|
|
35
|
-
* import {
|
|
33
|
+
* import { compose, defaultFetch, withAuth } from "@jamx/http";
|
|
36
34
|
*
|
|
37
|
-
* const fetcher =
|
|
35
|
+
* const fetcher = compose(withAuth("demo-token"))(defaultFetch);
|
|
38
36
|
* ```
|
|
39
37
|
*/
|
|
40
|
-
export declare
|
|
38
|
+
export declare function withAuth(token: string | ((request: Request) => string), scheme?: string): ({ request, next }: import("./core.js").Chain) => Promise<Result>;
|
|
41
39
|
/**
|
|
42
40
|
* Aborts requests that take longer than the given timeout.
|
|
43
41
|
*
|
|
44
42
|
* @example
|
|
45
43
|
* ```ts
|
|
46
|
-
* import {
|
|
44
|
+
* import { compose, defaultFetch, withTimeout } from "@jamx/http";
|
|
47
45
|
*
|
|
48
|
-
* const fetcher =
|
|
46
|
+
* const fetcher = compose(withTimeout(500))(defaultFetch);
|
|
49
47
|
* ```
|
|
50
48
|
*/
|
|
51
|
-
export declare
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
export declare class TimeoutError extends Error {
|
|
50
|
+
name: string;
|
|
51
|
+
readonly _tag = "FetchError";
|
|
52
|
+
readonly timeoutMs: number;
|
|
53
|
+
constructor(timeoutMs: number, cause?: unknown);
|
|
56
54
|
}
|
|
55
|
+
export declare const withTimeout: (timeoutMs: number) => ({ request, next }: import("./core.js").Chain) => Promise<Result | Left<TimeoutError>>;
|
|
57
56
|
/**
|
|
58
57
|
* Retries idempotent requests when they fail with transport errors or `5xx`
|
|
59
58
|
* responses.
|
|
60
59
|
*
|
|
61
60
|
* @example
|
|
62
61
|
* ```ts
|
|
63
|
-
* import {
|
|
62
|
+
* import { compose, defaultFetch, withRetry } from "@jamx/http";
|
|
64
63
|
*
|
|
65
|
-
* const fetcher =
|
|
64
|
+
* const fetcher = compose(withRetry({ retries: 2 }))(defaultFetch);
|
|
66
65
|
* ```
|
|
67
66
|
*/
|
|
68
|
-
export
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
export interface CacheOptions {
|
|
74
|
-
store?: CacheStore;
|
|
75
|
-
key?: (request: Request) => string;
|
|
76
|
-
shouldCache?: (result: Result, request: Request) => boolean;
|
|
67
|
+
export interface RetryOptions {
|
|
68
|
+
retries: number;
|
|
69
|
+
methods?: readonly string[];
|
|
70
|
+
shouldRetry?: (result: Result, attempt: number, request: Input) => boolean | Promise<boolean>;
|
|
77
71
|
}
|
|
72
|
+
export declare function withRetry(options: RetryOptions): ({ request, next }: import("./core.js").Chain) => Promise<Result>;
|
|
78
73
|
/**
|
|
79
74
|
* Caches successful `GET` responses in a store.
|
|
80
75
|
*
|
|
81
76
|
* @example
|
|
82
77
|
* ```ts
|
|
83
|
-
* import {
|
|
78
|
+
* import { compose, createMemoryCacheStore, defaultFetch, withCache } from "@jamx/http";
|
|
84
79
|
*
|
|
85
80
|
* const store = createMemoryCacheStore();
|
|
86
|
-
* const fetcher =
|
|
81
|
+
* const fetcher = compose(withCache({ store }))(defaultFetch);
|
|
87
82
|
* ```
|
|
88
83
|
*/
|
|
89
|
-
export
|
|
84
|
+
export interface CacheStore {
|
|
85
|
+
get(key: string): Response | undefined | Promise<Response | undefined>;
|
|
86
|
+
set(key: string, response: Response): void | Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
export interface CacheOptions {
|
|
89
|
+
store?: CacheStore;
|
|
90
|
+
key?: (request: Input) => string;
|
|
91
|
+
shouldCache?: (result: Result, request: Input) => boolean;
|
|
92
|
+
}
|
|
93
|
+
export declare function withCache(options?: CacheOptions): ({ request, next }: import("./core.js").Chain) => Promise<Result>;
|
|
90
94
|
/**
|
|
91
95
|
* Creates an in-memory cache store compatible with `withCache`.
|
|
92
96
|
*
|
|
@@ -97,5 +101,5 @@ export declare const withCache: (options?: CacheOptions) => ({ request, next }:
|
|
|
97
101
|
* const store = createMemoryCacheStore();
|
|
98
102
|
* ```
|
|
99
103
|
*/
|
|
100
|
-
export declare
|
|
104
|
+
export declare function createMemoryCacheStore(): CacheStore;
|
|
101
105
|
//# sourceMappingURL=interceptors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interceptors.d.ts","sourceRoot":"","sources":["../../src/internal/interceptors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,
|
|
1
|
+
{"version":3,"file":"interceptors.d.ts","sourceRoot":"","sources":["../../src/internal/interceptors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,KAAK,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,qEAgBjD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,WAAW,CAAC,qEAmB3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,EAC9C,MAAM,SAAW,qEAUlB;AAED;;;;;;;;;GASG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,IAAI,SAAkB;IACtB,QAAQ,CAAC,IAAI,gBAAgB;IAE7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI/C;AAED,eAAO,MAAM,WAAW,GAAI,WAAW,MAAM,2FA4BzC,CAAC;AA4BL;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,KACX,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,qEAkB9C;AAyBD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IACvE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,KAAK,MAAM,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,KAAK,OAAO,CAAC;CAC3D;AAED,wBAAgB,SAAS,CAAC,OAAO,GAAE,YAAiB,qEAwBnD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,IAAI,UAAU,CAYnD"}
|