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 CHANGED
@@ -3,6 +3,7 @@
3
3
  **Deadline-based timeouts for async Node.js code with AbortSignal support.**
4
4
 
5
5
  ![NPM Version](https://img.shields.io/npm/v/safe-timeouts)
6
+ ![NPM Downloads](https://img.shields.io/npm/dw/safe-timeouts)
6
7
  ![GitHub package.json version](https://img.shields.io/github/package-json/v/yetanotheraryan/safe-timeouts?style=flat-square&color=blue)
7
8
  ![GitHub last commit](https://img.shields.io/github/last-commit/yetanotheraryan/safe-timeouts)
8
9
  ![GitHub contributors](https://img.shields.io/github/contributors/yetanotheraryan/safe-timeouts)
@@ -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 result = await withTimeout(2000, async (signal) => {
53
- const res = await axios.get("https://api.example.com/users", { signal });
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
- ## Using with services (multiple layers)
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
- export { TimeoutError, timeoutContext, withTimeout };
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
- export { TimeoutError, timeoutContext, withTimeout };
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.2",
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
+ }