@monarcode/effetch 0.0.0-stage → 0.1.0-alpha.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/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +613 -2
- package/dist/Client.d.ts +367 -0
- package/dist/Client.js +110 -0
- package/dist/Errors.d.ts +224 -0
- package/dist/Errors.js +151 -0
- package/dist/Options.d.ts +462 -0
- package/dist/Path.d.ts +72 -0
- package/dist/Plugin.d.ts +553 -0
- package/dist/Plugin.js +7 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +5 -0
- package/dist/internal/body.js +70 -0
- package/dist/internal/object.js +6 -0
- package/dist/internal/path.js +23 -0
- package/dist/internal/pipeline.js +29 -0
- package/dist/internal/plugins.js +92 -0
- package/dist/internal/query.js +22 -0
- package/dist/internal/request.js +33 -0
- package/dist/internal/resolve.js +23 -0
- package/dist/internal/response.js +73 -0
- package/dist/internal/url.js +24 -0
- package/dist/plugins/bearer.d.ts +152 -0
- package/dist/plugins/bearer.js +63 -0
- package/dist/plugins/index.d.ts +32 -0
- package/dist/plugins/index.js +6 -0
- package/dist/plugins/logger.d.ts +66 -0
- package/dist/plugins/logger.js +67 -0
- package/dist/plugins/retry.d.ts +94 -0
- package/dist/plugins/retry.js +74 -0
- package/dist/plugins/timeout.d.ts +108 -0
- package/dist/plugins/timeout.js +84 -0
- package/package.json +85 -3
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { errorUrl } from "../internal/url.js";
|
|
2
|
+
import { plugin } from "../Plugin.js";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
5
|
+
import * as Cause from "effect/Cause";
|
|
6
|
+
import * as Clock from "effect/Clock";
|
|
7
|
+
import * as Exit from "effect/Exit";
|
|
8
|
+
|
|
9
|
+
//#region src/plugins/logger.ts
|
|
10
|
+
/**
|
|
11
|
+
* Logs one entry per call with Effect's logger: the method, the URL, the
|
|
12
|
+
* status and how long the response took, e.g.
|
|
13
|
+
* `GET https://api.example.com/todos 200 (35ms)`, or `… failed (35ms)`
|
|
14
|
+
* with the failure's cause.
|
|
15
|
+
*
|
|
16
|
+
* The same values are log annotations (`method`, `url`, `status`,
|
|
17
|
+
* `durationMs`). The URL has no query string, and no headers are logged,
|
|
18
|
+
* so tokens and cookies stay out of logs. The duration is measured with
|
|
19
|
+
* Effect's `Clock` until the response headers arrive, in whole
|
|
20
|
+
* milliseconds.
|
|
21
|
+
*
|
|
22
|
+
* Put it first in the plugin list, so it sees the whole call, retries
|
|
23
|
+
* included.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import * as Fetch from "@monarcode/effetch";
|
|
28
|
+
* import { logger } from "@monarcode/effetch/plugins";
|
|
29
|
+
*
|
|
30
|
+
* const make = Fetch.make({
|
|
31
|
+
* baseUrl: "https://api.example.com",
|
|
32
|
+
* plugins: [logger({ level: "Info" })],
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @since 0.1.0
|
|
37
|
+
*/
|
|
38
|
+
const logger = (options = {}) => {
|
|
39
|
+
const log = options.level === "Info" ? Effect.logInfo : Effect.logDebug;
|
|
40
|
+
return plugin({
|
|
41
|
+
name: "logger",
|
|
42
|
+
wrap: (client) => HttpClient.transform(client, (response, request) => Effect.gen(function* () {
|
|
43
|
+
const start = yield* Clock.currentTimeMillis;
|
|
44
|
+
const exit = yield* Effect.exit(response);
|
|
45
|
+
const durationMs = Math.round((yield* Clock.currentTimeMillis) - start);
|
|
46
|
+
const method = request.method;
|
|
47
|
+
const url = errorUrl(request.url);
|
|
48
|
+
if (Exit.isSuccess(exit)) {
|
|
49
|
+
const { status } = exit.value;
|
|
50
|
+
yield* Effect.annotateLogs(log(`${method} ${url} ${status} (${durationMs}ms)`), {
|
|
51
|
+
method,
|
|
52
|
+
url,
|
|
53
|
+
status,
|
|
54
|
+
durationMs
|
|
55
|
+
});
|
|
56
|
+
} else if (!Cause.hasInterruptsOnly(exit.cause)) yield* Effect.annotateLogs(log(`${method} ${url} failed (${durationMs}ms)`, exit.cause), {
|
|
57
|
+
method,
|
|
58
|
+
url,
|
|
59
|
+
durationMs
|
|
60
|
+
});
|
|
61
|
+
return yield* exit;
|
|
62
|
+
}))
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { logger };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as HttpMethod from "effect/http/HttpMethod";
|
|
2
|
+
import * as Schedule from "effect/Schedule";
|
|
3
|
+
|
|
4
|
+
import { Plugin } from "../Plugin.js";
|
|
5
|
+
//#region src/plugins/retry.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Options for {@link retry}.
|
|
8
|
+
*
|
|
9
|
+
* - `times`: how many times to retry, after the first attempt. Defaults to
|
|
10
|
+
* `2`. Calls can override it.
|
|
11
|
+
* - `schedule`: the delays between attempts. Defaults to an exponential
|
|
12
|
+
* backoff from 200ms, with jitter. `times` still caps the retries.
|
|
13
|
+
* - `methods`: the methods that are retried. Defaults to the idempotent
|
|
14
|
+
* ones: `GET`, `HEAD`, `OPTIONS`, `PUT` and `DELETE`.
|
|
15
|
+
* - `statuses`: the response statuses that are retried. Defaults to `408`,
|
|
16
|
+
* `429`, `500`, `502`, `503` and `504`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import * as Schedule from "effect/Schedule";
|
|
21
|
+
* import type { RetryOptions } from "@monarcode/effetch/plugins";
|
|
22
|
+
*
|
|
23
|
+
* const options: RetryOptions = {
|
|
24
|
+
* times: 3,
|
|
25
|
+
* schedule: Schedule.spaced("1 second"),
|
|
26
|
+
* methods: ["GET"],
|
|
27
|
+
* statuses: [503],
|
|
28
|
+
* };
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @since 0.1.0
|
|
32
|
+
*/
|
|
33
|
+
export interface RetryOptions {
|
|
34
|
+
/** How many times to retry after the first attempt. Defaults to `2`. */
|
|
35
|
+
readonly times?: number | undefined;
|
|
36
|
+
/** The delays between attempts. Defaults to jittered exponential backoff. */
|
|
37
|
+
readonly schedule?: Schedule.Schedule<unknown, unknown> | undefined;
|
|
38
|
+
/** The methods that are retried. Defaults to the idempotent ones. */
|
|
39
|
+
readonly methods?: ReadonlyArray<HttpMethod.HttpMethod> | undefined;
|
|
40
|
+
/** The statuses that are retried. Defaults to 408, 429, 500, 502–504. */
|
|
41
|
+
readonly statuses?: ReadonlyArray<number> | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The plugin made by {@link retry}. It adds no errors; calls can override
|
|
45
|
+
* `times` or skip it.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { retry, type RetryPlugin } from "@monarcode/effetch/plugins";
|
|
50
|
+
*
|
|
51
|
+
* const retryTwice: RetryPlugin = retry();
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @since 0.1.0
|
|
55
|
+
*/
|
|
56
|
+
export type RetryPlugin = Plugin<
|
|
57
|
+
"retry",
|
|
58
|
+
never,
|
|
59
|
+
never,
|
|
60
|
+
{
|
|
61
|
+
readonly times: number;
|
|
62
|
+
}
|
|
63
|
+
>;
|
|
64
|
+
/**
|
|
65
|
+
* Retries a call that failed with a `NetworkError` or got a retryable
|
|
66
|
+
* status, for the configured methods only (idempotent ones by default, so
|
|
67
|
+
* a `POST` is never sent twice).
|
|
68
|
+
*
|
|
69
|
+
* When the retries run out, the last response is returned unchanged, so a
|
|
70
|
+
* `503` still ends as an `HttpError` with that response's body, and the
|
|
71
|
+
* last `NetworkError` is the call's error. Other failures, a timeout from
|
|
72
|
+
* the `timeout` plugin included, aren't retried.
|
|
73
|
+
*
|
|
74
|
+
* It retries everything after it in the plugin list. Put it before
|
|
75
|
+
* `timeout` and `bearer`, so each attempt gets its own time limit and a
|
|
76
|
+
* fresh token.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* import * as Fetch from "@monarcode/effetch";
|
|
81
|
+
* import { retry } from "@monarcode/effetch/plugins";
|
|
82
|
+
*
|
|
83
|
+
* const make = Fetch.make({
|
|
84
|
+
* baseUrl: "https://api.example.com",
|
|
85
|
+
* plugins: [retry({ times: 3 })],
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* // Per call: api.get("/health", { plugins: { retry: { times: 0 } } })
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @since 0.1.0
|
|
92
|
+
*/
|
|
93
|
+
export declare const retry: (options?: RetryOptions) => RetryPlugin;
|
|
94
|
+
//#endregion
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { NetworkError } from "../Errors.js";
|
|
2
|
+
import { plugin } from "../Plugin.js";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
5
|
+
import * as Data from "effect/Data";
|
|
6
|
+
import * as Schedule from "effect/Schedule";
|
|
7
|
+
|
|
8
|
+
//#region src/plugins/retry.ts
|
|
9
|
+
const idempotentMethods = [
|
|
10
|
+
"GET",
|
|
11
|
+
"HEAD",
|
|
12
|
+
"OPTIONS",
|
|
13
|
+
"PUT",
|
|
14
|
+
"DELETE"
|
|
15
|
+
];
|
|
16
|
+
const transientStatuses = [
|
|
17
|
+
408,
|
|
18
|
+
429,
|
|
19
|
+
500,
|
|
20
|
+
502,
|
|
21
|
+
503,
|
|
22
|
+
504
|
|
23
|
+
];
|
|
24
|
+
var RetryableStatus = class extends Data.TaggedError("RetryableStatus") {};
|
|
25
|
+
/**
|
|
26
|
+
* Retries a call that failed with a `NetworkError` or got a retryable
|
|
27
|
+
* status, for the configured methods only (idempotent ones by default, so
|
|
28
|
+
* a `POST` is never sent twice).
|
|
29
|
+
*
|
|
30
|
+
* When the retries run out, the last response is returned unchanged, so a
|
|
31
|
+
* `503` still ends as an `HttpError` with that response's body, and the
|
|
32
|
+
* last `NetworkError` is the call's error. Other failures, a timeout from
|
|
33
|
+
* the `timeout` plugin included, aren't retried.
|
|
34
|
+
*
|
|
35
|
+
* It retries everything after it in the plugin list. Put it before
|
|
36
|
+
* `timeout` and `bearer`, so each attempt gets its own time limit and a
|
|
37
|
+
* fresh token.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import * as Fetch from "@monarcode/effetch";
|
|
42
|
+
* import { retry } from "@monarcode/effetch/plugins";
|
|
43
|
+
*
|
|
44
|
+
* const make = Fetch.make({
|
|
45
|
+
* baseUrl: "https://api.example.com",
|
|
46
|
+
* plugins: [retry({ times: 3 })],
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* // Per call: api.get("/health", { plugins: { retry: { times: 0 } } })
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @since 0.1.0
|
|
53
|
+
*/
|
|
54
|
+
const retry = (options = {}) => {
|
|
55
|
+
const methods = new Set(options.methods ?? idempotentMethods);
|
|
56
|
+
const statuses = new Set(options.statuses ?? transientStatuses);
|
|
57
|
+
const schedule = options.schedule ?? Schedule.jittered(Schedule.exponential("200 millis"));
|
|
58
|
+
return plugin({
|
|
59
|
+
name: "retry",
|
|
60
|
+
defaults: { times: options.times ?? 2 },
|
|
61
|
+
wrap: (client, callOptions) => HttpClient.transform(client, (response, request) => Effect.gen(function* () {
|
|
62
|
+
const { times } = yield* callOptions;
|
|
63
|
+
if (!(times > 0) || !methods.has(request.method)) return yield* response;
|
|
64
|
+
return yield* Effect.flatMap(response, (received) => statuses.has(received.status) ? Effect.fail(new RetryableStatus({ response: received })) : Effect.succeed(received)).pipe(Effect.retry({
|
|
65
|
+
schedule,
|
|
66
|
+
times,
|
|
67
|
+
while: (error) => error instanceof RetryableStatus || error instanceof NetworkError
|
|
68
|
+
}), Effect.catchTag("RetryableStatus", (error) => Effect.succeed(error.response)));
|
|
69
|
+
}))
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { retry };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as Duration from "effect/Duration";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
|
|
4
|
+
import { Plugin } from "../Plugin.js";
|
|
5
|
+
//#region src/plugins/timeout.d.ts
|
|
6
|
+
declare const TimeoutError_base: Schema.Class<
|
|
7
|
+
TimeoutError,
|
|
8
|
+
Schema.TaggedStruct<
|
|
9
|
+
"TimeoutError",
|
|
10
|
+
{
|
|
11
|
+
readonly method: Schema.String;
|
|
12
|
+
readonly url: Schema.String;
|
|
13
|
+
readonly duration: Schema.String;
|
|
14
|
+
}
|
|
15
|
+
>,
|
|
16
|
+
import("effect/Cause").YieldableError
|
|
17
|
+
>;
|
|
18
|
+
/**
|
|
19
|
+
* No response arrived within the {@link timeout} plugin's duration. The
|
|
20
|
+
* request was aborted.
|
|
21
|
+
*
|
|
22
|
+
* - `url` is the request URL without its query string.
|
|
23
|
+
* - `duration` is the limit that was exceeded, formatted (`"10s"`).
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import * as Effect from "effect/Effect";
|
|
28
|
+
* import type { TimeoutError } from "@monarcode/effetch/plugins";
|
|
29
|
+
*
|
|
30
|
+
* declare const call: Effect.Effect<string, TimeoutError>;
|
|
31
|
+
*
|
|
32
|
+
* const program = call.pipe(
|
|
33
|
+
* Effect.catchTag("TimeoutError", (error) =>
|
|
34
|
+
* Effect.succeed(`Gave up on ${error.url} after ${error.duration}`),
|
|
35
|
+
* ),
|
|
36
|
+
* );
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
*/
|
|
41
|
+
export declare class TimeoutError extends TimeoutError_base {
|
|
42
|
+
/** `"<method> <url>: timed out after <duration>"`. */
|
|
43
|
+
get message(): string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Options for {@link timeout}, and what a call can override:
|
|
47
|
+
* `plugins: { timeout: { duration } }`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* import type { TimeoutOptions } from "@monarcode/effetch/plugins";
|
|
52
|
+
*
|
|
53
|
+
* const options: TimeoutOptions = { duration: "10 seconds" };
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @since 0.1.0
|
|
57
|
+
*/
|
|
58
|
+
export interface TimeoutOptions {
|
|
59
|
+
/** How long to wait for the response headers. */
|
|
60
|
+
readonly duration: Duration.Input;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The plugin made by {@link timeout}: it adds {@link TimeoutError}, and
|
|
64
|
+
* calls can override its `duration` or skip it.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* import { timeout, type TimeoutPlugin } from "@monarcode/effetch/plugins";
|
|
69
|
+
*
|
|
70
|
+
* const tenSeconds: TimeoutPlugin = timeout({ duration: "10 seconds" });
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @since 0.1.0
|
|
74
|
+
*/
|
|
75
|
+
export type TimeoutPlugin = Plugin<
|
|
76
|
+
"timeout",
|
|
77
|
+
TimeoutError,
|
|
78
|
+
never,
|
|
79
|
+
TimeoutOptions
|
|
80
|
+
>;
|
|
81
|
+
/**
|
|
82
|
+
* Fails the call with a {@link TimeoutError}, and aborts the request, when
|
|
83
|
+
* the response headers don't arrive within `duration`.
|
|
84
|
+
*
|
|
85
|
+
* It covers the plugins after it in the list, but not reading the body: a
|
|
86
|
+
* large response that starts in time isn't cut off while it downloads.
|
|
87
|
+
* Put it after `retry` so each attempt gets the full duration. A timed-out
|
|
88
|
+
* attempt isn't retried.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import * as Fetch from "@monarcode/effetch";
|
|
93
|
+
* import { timeout } from "@monarcode/effetch/plugins";
|
|
94
|
+
*
|
|
95
|
+
* const make = Fetch.make({
|
|
96
|
+
* baseUrl: "https://api.example.com",
|
|
97
|
+
* plugins: [timeout({ duration: "10 seconds" })],
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* // Per call:
|
|
101
|
+
* // api.get("/export", { plugins: { timeout: { duration: "2 minutes" } } })
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @since 0.1.0
|
|
105
|
+
*/
|
|
106
|
+
export declare const timeout: (options: TimeoutOptions) => TimeoutPlugin;
|
|
107
|
+
//#endregion
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { errorUrl } from "../internal/url.js";
|
|
2
|
+
import { plugin } from "../Plugin.js";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as Duration from "effect/Duration";
|
|
7
|
+
|
|
8
|
+
//#region src/plugins/timeout.ts
|
|
9
|
+
/**
|
|
10
|
+
* No response arrived within the {@link timeout} plugin's duration. The
|
|
11
|
+
* request was aborted.
|
|
12
|
+
*
|
|
13
|
+
* - `url` is the request URL without its query string.
|
|
14
|
+
* - `duration` is the limit that was exceeded, formatted (`"10s"`).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import * as Effect from "effect/Effect";
|
|
19
|
+
* import type { TimeoutError } from "@monarcode/effetch/plugins";
|
|
20
|
+
*
|
|
21
|
+
* declare const call: Effect.Effect<string, TimeoutError>;
|
|
22
|
+
*
|
|
23
|
+
* const program = call.pipe(
|
|
24
|
+
* Effect.catchTag("TimeoutError", (error) =>
|
|
25
|
+
* Effect.succeed(`Gave up on ${error.url} after ${error.duration}`),
|
|
26
|
+
* ),
|
|
27
|
+
* );
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @since 0.1.0
|
|
31
|
+
*/
|
|
32
|
+
var TimeoutError = class extends Schema.TaggedError()("TimeoutError", {
|
|
33
|
+
method: Schema.String,
|
|
34
|
+
url: Schema.String,
|
|
35
|
+
duration: Schema.String
|
|
36
|
+
}) {
|
|
37
|
+
/** `"<method> <url>: timed out after <duration>"`. */
|
|
38
|
+
get message() {
|
|
39
|
+
return `${this.method} ${this.url}: timed out after ${this.duration}`;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Fails the call with a {@link TimeoutError}, and aborts the request, when
|
|
44
|
+
* the response headers don't arrive within `duration`.
|
|
45
|
+
*
|
|
46
|
+
* It covers the plugins after it in the list, but not reading the body: a
|
|
47
|
+
* large response that starts in time isn't cut off while it downloads.
|
|
48
|
+
* Put it after `retry` so each attempt gets the full duration. A timed-out
|
|
49
|
+
* attempt isn't retried.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import * as Fetch from "@monarcode/effetch";
|
|
54
|
+
* import { timeout } from "@monarcode/effetch/plugins";
|
|
55
|
+
*
|
|
56
|
+
* const make = Fetch.make({
|
|
57
|
+
* baseUrl: "https://api.example.com",
|
|
58
|
+
* plugins: [timeout({ duration: "10 seconds" })],
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* // Per call:
|
|
62
|
+
* // api.get("/export", { plugins: { timeout: { duration: "2 minutes" } } })
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @since 0.1.0
|
|
66
|
+
*/
|
|
67
|
+
const timeout = (options) => plugin({
|
|
68
|
+
name: "timeout",
|
|
69
|
+
defaults: { duration: options.duration },
|
|
70
|
+
wrap: (client, callOptions) => HttpClient.transform(client, (response, request) => Effect.gen(function* () {
|
|
71
|
+
const { duration } = yield* callOptions;
|
|
72
|
+
return yield* Effect.timeoutOrElse(response, {
|
|
73
|
+
duration,
|
|
74
|
+
orElse: () => Effect.fail(new TimeoutError({
|
|
75
|
+
method: request.method,
|
|
76
|
+
url: errorUrl(request.url),
|
|
77
|
+
duration: Duration.format(Duration.fromInputUnsafe(duration))
|
|
78
|
+
}))
|
|
79
|
+
});
|
|
80
|
+
}))
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { TimeoutError, timeout };
|
package/package.json
CHANGED
|
@@ -1,6 +1,88 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monarcode/effetch",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Typed, schema-driven HTTP calls on Effect v4's HttpClient: path params, object queries, decoded responses and plugins",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"effect",
|
|
7
|
+
"effect-ts",
|
|
8
|
+
"fetch",
|
|
9
|
+
"http",
|
|
10
|
+
"http-client",
|
|
11
|
+
"react-native",
|
|
12
|
+
"schema"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/monarcode/effetch#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/monarcode/effetch/issues"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/monarcode/effetch.git"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"CHANGELOG.md"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./plugins": {
|
|
39
|
+
"types": "./dist/plugins/index.d.ts",
|
|
40
|
+
"default": "./dist/plugins/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"registry": "https://registry.npmjs.org/"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
50
|
+
"@effect/language-service": "0.87.4",
|
|
51
|
+
"@effect/vitest": "4.0.2",
|
|
52
|
+
"@types/node": "26.6.5",
|
|
53
|
+
"effect": "4.0.2",
|
|
54
|
+
"husky": "9.1.7",
|
|
55
|
+
"oxfmt": "0.72.0",
|
|
56
|
+
"oxlint": "1.87.0",
|
|
57
|
+
"publint": "0.3.25",
|
|
58
|
+
"tsdown": "0.23.1",
|
|
59
|
+
"typescript": "6.0.3",
|
|
60
|
+
"vitest": "5.0.3"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"effect": "^4.0.0"
|
|
64
|
+
},
|
|
65
|
+
"devEngines": {
|
|
66
|
+
"runtime": {
|
|
67
|
+
"name": "node",
|
|
68
|
+
"version": ">=24",
|
|
69
|
+
"onFail": "error"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"lint": "oxlint .",
|
|
74
|
+
"lint:fix": "oxlint --fix .",
|
|
75
|
+
"lint:effect": "effect-language-service diagnostics --project tsconfig.json --format text",
|
|
76
|
+
"format": "oxfmt --write .",
|
|
77
|
+
"format:check": "oxfmt --check .",
|
|
78
|
+
"build": "tsdown",
|
|
79
|
+
"dev": "tsdown --watch",
|
|
80
|
+
"typecheck": "tsc --noEmit",
|
|
81
|
+
"check:docs": "node scripts/check-docs.mjs",
|
|
82
|
+
"test": "vitest run",
|
|
83
|
+
"test:watch": "vitest",
|
|
84
|
+
"check:package": "publint --strict && attw --pack . --profile esm-only",
|
|
85
|
+
"smoke": "bash scripts/smoke.sh",
|
|
86
|
+
"check": "pnpm run lint && pnpm run format:check && pnpm run typecheck && pnpm run check:docs && pnpm run lint:effect && pnpm test && pnpm run build && pnpm run check:package && pnpm run smoke"
|
|
87
|
+
}
|
|
6
88
|
}
|