safe-timeouts 0.1.2 → 0.1.3
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 +94 -5
- package/dist/index.d.mts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +33 -0
- package/dist/index.mjs +21 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**Deadline-based timeouts for async Node.js code with AbortSignal support.**
|
|
4
4
|
|
|
5
5
|

|
|
6
|
+

|
|
6
7
|

|
|
7
8
|

|
|
8
9
|

|
|
@@ -45,16 +46,20 @@ Node.js >= 16 is required.
|
|
|
45
46
|
## Basic usage
|
|
46
47
|
|
|
47
48
|
```ts
|
|
48
|
-
import { withTimeout, TimeoutError } from "safe-timeouts";
|
|
49
|
+
import { withTimeout, TimeoutError, safeAxios } from "safe-timeouts";
|
|
49
50
|
import axios from "axios";
|
|
50
51
|
|
|
51
52
|
try {
|
|
52
|
-
const
|
|
53
|
-
const res = await
|
|
53
|
+
const resultWithSafeAxios = await withTimeout(2000, async () => {
|
|
54
|
+
const res = await safeAxios.get("https://api.example.com/users"); // no signal to be passed.
|
|
55
|
+
return res.data;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const resultWithAxios = await withTimeout(2000, async (signal) => { // signal to be taken.
|
|
59
|
+
const res = await axios.get("https://api.example.com/users", {signal}); // signal to be passed.
|
|
54
60
|
return res.data;
|
|
55
61
|
});
|
|
56
62
|
|
|
57
|
-
console.log(result);
|
|
58
63
|
} catch (err) {
|
|
59
64
|
if (err instanceof TimeoutError) {
|
|
60
65
|
console.error("Request timed out");
|
|
@@ -94,7 +99,77 @@ This makes time budgets safe and deterministic.
|
|
|
94
99
|
|
|
95
100
|
---
|
|
96
101
|
|
|
97
|
-
##
|
|
102
|
+
## safeAxios (optional helper but recommended)
|
|
103
|
+
|
|
104
|
+
`safeAxios` is a **convenience wrapper** around Axios that automatically integrates with safe-timeouts.
|
|
105
|
+
|
|
106
|
+
When used inside withTimeout, HTTP requests are automatically cancellable.
|
|
107
|
+
When used outside withTimeout, it behaves exactly like a normal Axios instance.
|
|
108
|
+
|
|
109
|
+
Example
|
|
110
|
+
```ts
|
|
111
|
+
import { withTimeout, safeAxios } from "safe-timeouts";
|
|
112
|
+
|
|
113
|
+
await withTimeout(2000, async () => {
|
|
114
|
+
const res = await safeAxios.get("/users");
|
|
115
|
+
return res.data;
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
### Custom Axios instance can also be created
|
|
121
|
+
```ts
|
|
122
|
+
import { withTimeout, createSafeAxios } from "safe-timeouts";
|
|
123
|
+
|
|
124
|
+
const api = createSafeAxios({
|
|
125
|
+
baseURL: "https://api.example.com",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await withTimeout(1000, async () => {
|
|
129
|
+
await api.post("/sync");
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## How it works (context propagation)
|
|
134
|
+
|
|
135
|
+
safe-timeouts uses AsyncLocalStorage to propagate timeout context across async boundaries.
|
|
136
|
+
|
|
137
|
+
Example flow
|
|
138
|
+
```ts
|
|
139
|
+
await withTimeout(2000, async () => {
|
|
140
|
+
await controller();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
async function controller() {
|
|
144
|
+
return serviceA();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function serviceA() {
|
|
148
|
+
return serviceB();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function serviceB() {
|
|
152
|
+
return safeAxios.get("/users");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
Context flow diagram
|
|
157
|
+
withTimeout
|
|
158
|
+
└─ Async context (deadline + AbortController)
|
|
159
|
+
├─ controller()
|
|
160
|
+
│ └─ serviceA()
|
|
161
|
+
│ └─ serviceB()
|
|
162
|
+
│ └─ safeAxios.get()
|
|
163
|
+
│ └─ axios(request + signal)
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
The timeout context is created once
|
|
167
|
+
Node automatically propagates it across async calls
|
|
168
|
+
**safeAxios reads the context at request time When the deadline expires, the request is aborted**
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Using with services (multiple layers) without safeAxios
|
|
98
173
|
|
|
99
174
|
```ts
|
|
100
175
|
import axios from "axios";
|
|
@@ -136,6 +211,20 @@ These stop execution as soon as the deadline is exceeded:
|
|
|
136
211
|
Example:
|
|
137
212
|
|
|
138
213
|
```ts
|
|
214
|
+
// GET
|
|
215
|
+
await safeAxios.get(url); // 👈 No AbortSignal needed
|
|
216
|
+
// POST
|
|
217
|
+
await safeAxios.post(
|
|
218
|
+
url,
|
|
219
|
+
{ name: "Aryan", role: "admin" },
|
|
220
|
+
{
|
|
221
|
+
// 👈 No AbortSignal goes here
|
|
222
|
+
headers: {
|
|
223
|
+
"Content-Type": "application/json",
|
|
224
|
+
Authorization: "Bearer YOUR_TOKEN",
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
|
|
139
228
|
// GET
|
|
140
229
|
await axios.get(url, { signal }); // 👈 AbortSignal goes here
|
|
141
230
|
// POST
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as axios from 'axios';
|
|
2
|
+
import { AxiosRequestConfig, AxiosInstance } from 'axios';
|
|
1
3
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
4
|
|
|
3
5
|
declare function withTimeout<T>(ms: number, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
@@ -13,4 +15,12 @@ declare class TimeoutError extends Error {
|
|
|
13
15
|
constructor(message?: string);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Axios instance that automatically attaches AbortSignal
|
|
20
|
+
* from the current safe-timeout context (if present).
|
|
21
|
+
*/
|
|
22
|
+
declare function createSafeAxios(config?: AxiosRequestConfig): AxiosInstance;
|
|
23
|
+
|
|
24
|
+
declare const safeAxios: axios.AxiosInstance;
|
|
25
|
+
|
|
26
|
+
export { TimeoutError, createSafeAxios, safeAxios, timeoutContext, withTimeout };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as axios from 'axios';
|
|
2
|
+
import { AxiosRequestConfig, AxiosInstance } from 'axios';
|
|
1
3
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
4
|
|
|
3
5
|
declare function withTimeout<T>(ms: number, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
@@ -13,4 +15,12 @@ declare class TimeoutError extends Error {
|
|
|
13
15
|
constructor(message?: string);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Axios instance that automatically attaches AbortSignal
|
|
20
|
+
* from the current safe-timeout context (if present).
|
|
21
|
+
*/
|
|
22
|
+
declare function createSafeAxios(config?: AxiosRequestConfig): AxiosInstance;
|
|
23
|
+
|
|
24
|
+
declare const safeAxios: axios.AxiosInstance;
|
|
25
|
+
|
|
26
|
+
export { TimeoutError, createSafeAxios, safeAxios, timeoutContext, withTimeout };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,21 +17,47 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
TimeoutError: () => TimeoutError,
|
|
34
|
+
createSafeAxios: () => createSafeAxios,
|
|
35
|
+
safeAxios: () => safeAxios,
|
|
24
36
|
timeoutContext: () => timeoutContext,
|
|
25
37
|
withTimeout: () => withTimeout
|
|
26
38
|
});
|
|
27
39
|
module.exports = __toCommonJS(index_exports);
|
|
28
40
|
|
|
41
|
+
// src/safeAxios.ts
|
|
42
|
+
var import_axios = __toESM(require("axios"));
|
|
43
|
+
|
|
29
44
|
// src/context.ts
|
|
30
45
|
var import_node_async_hooks = require("async_hooks");
|
|
31
46
|
var timeoutContext = new import_node_async_hooks.AsyncLocalStorage();
|
|
32
47
|
|
|
48
|
+
// src/safeAxios.ts
|
|
49
|
+
function createSafeAxios(config) {
|
|
50
|
+
const instance = import_axios.default.create(config);
|
|
51
|
+
instance.interceptors.request.use((req) => {
|
|
52
|
+
const ctx = timeoutContext.getStore();
|
|
53
|
+
if (ctx?.controller && !req.signal) {
|
|
54
|
+
req.signal = ctx.controller.signal;
|
|
55
|
+
}
|
|
56
|
+
return req;
|
|
57
|
+
});
|
|
58
|
+
return instance;
|
|
59
|
+
}
|
|
60
|
+
|
|
33
61
|
// src/errors.ts
|
|
34
62
|
var TimeoutError = class extends Error {
|
|
35
63
|
constructor(message = "Operation timed out") {
|
|
@@ -69,9 +97,14 @@ async function withTimeout(ms, fn) {
|
|
|
69
97
|
clearTimeout(timer);
|
|
70
98
|
}
|
|
71
99
|
}
|
|
100
|
+
|
|
101
|
+
// src/index.ts
|
|
102
|
+
var safeAxios = createSafeAxios();
|
|
72
103
|
// Annotate the CommonJS export names for ESM import in node:
|
|
73
104
|
0 && (module.exports = {
|
|
74
105
|
TimeoutError,
|
|
106
|
+
createSafeAxios,
|
|
107
|
+
safeAxios,
|
|
75
108
|
timeoutContext,
|
|
76
109
|
withTimeout
|
|
77
110
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
// src/safeAxios.ts
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
1
4
|
// src/context.ts
|
|
2
5
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3
6
|
var timeoutContext = new AsyncLocalStorage();
|
|
4
7
|
|
|
8
|
+
// src/safeAxios.ts
|
|
9
|
+
function createSafeAxios(config) {
|
|
10
|
+
const instance = axios.create(config);
|
|
11
|
+
instance.interceptors.request.use((req) => {
|
|
12
|
+
const ctx = timeoutContext.getStore();
|
|
13
|
+
if (ctx?.controller && !req.signal) {
|
|
14
|
+
req.signal = ctx.controller.signal;
|
|
15
|
+
}
|
|
16
|
+
return req;
|
|
17
|
+
});
|
|
18
|
+
return instance;
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
// src/errors.ts
|
|
6
22
|
var TimeoutError = class extends Error {
|
|
7
23
|
constructor(message = "Operation timed out") {
|
|
@@ -41,8 +57,13 @@ async function withTimeout(ms, fn) {
|
|
|
41
57
|
clearTimeout(timer);
|
|
42
58
|
}
|
|
43
59
|
}
|
|
60
|
+
|
|
61
|
+
// src/index.ts
|
|
62
|
+
var safeAxios = createSafeAxios();
|
|
44
63
|
export {
|
|
45
64
|
TimeoutError,
|
|
65
|
+
createSafeAxios,
|
|
66
|
+
safeAxios,
|
|
46
67
|
timeoutContext,
|
|
47
68
|
withTimeout
|
|
48
69
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safe-timeouts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Deadline-based timeouts for async code in Node.js. Enforce end-to-end execution deadlines with automatic propagation and AbortSignal support.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -34,5 +34,8 @@
|
|
|
34
34
|
"@types/node": "^25.0.9",
|
|
35
35
|
"tsup": "^8.5.1",
|
|
36
36
|
"typescript": "^5.9.3"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"axios": "^1.13.2"
|
|
37
40
|
}
|
|
38
|
-
}
|
|
41
|
+
}
|