safe-timeouts 0.1.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.
- package/LICENSE +21 -0
- package/README.md +278 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +77 -0
- package/dist/index.mjs +48 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aryan Tiwari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# safe-timeout
|
|
2
|
+
|
|
3
|
+
**Deadline-based timeouts for async Node.js code with AbortSignal support.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/safe-timeout)
|
|
6
|
+
[](https://www.npmjs.com/package/safe-timeout)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://www.npmjs.com/package/safe-timeout)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
|
|
11
|
+
Promise-based deadline enforcement for async code in Node.js. `safe-timeout` helps you apply a **single execution deadline** across async functions, services, and external calls using standard `AbortSignal` semantics.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why this exists
|
|
16
|
+
|
|
17
|
+
In real backend systems, timeouts are **end-to-end**, not per-function:
|
|
18
|
+
|
|
19
|
+
* An HTTP request has a deadline
|
|
20
|
+
* That deadline must apply across DB calls, service logic, and external APIs
|
|
21
|
+
* Nested functions should **not accidentally extend** the available time
|
|
22
|
+
|
|
23
|
+
Most timeout utilities fail here because they:
|
|
24
|
+
|
|
25
|
+
* don’t propagate context
|
|
26
|
+
* don’t compose across nested calls
|
|
27
|
+
* don’t integrate with `AbortSignal`
|
|
28
|
+
|
|
29
|
+
`safe-timeout` solves this correctly.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install safe-timeout
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Node.js >= 16 is required.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Basic usage
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { withTimeout, TimeoutError } from "safe-timeout";
|
|
47
|
+
import axios from "axios";
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const result = await withTimeout(2000, async (signal) => {
|
|
51
|
+
const res = await axios.get("https://api.example.com/users", { signal });
|
|
52
|
+
return res.data;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(result);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err instanceof TimeoutError) {
|
|
58
|
+
console.error("Request timed out");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
What happens:
|
|
64
|
+
|
|
65
|
+
* A 2s deadline is created
|
|
66
|
+
* An `AbortController` is started internally
|
|
67
|
+
* If the deadline is exceeded:
|
|
68
|
+
|
|
69
|
+
* the promise rejects with `TimeoutError`
|
|
70
|
+
* the `AbortSignal` is aborted
|
|
71
|
+
* Axios cancels the HTTP request
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Nested timeouts (key feature)
|
|
76
|
+
|
|
77
|
+
Deadlines **propagate and compose automatically**.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await withTimeout(3000, async () => {
|
|
81
|
+
await serviceA(); // uses part of the budget
|
|
82
|
+
|
|
83
|
+
await withTimeout(5000, async () => {
|
|
84
|
+
await serviceB(); // still limited by the original 3s
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The inner timeout **cannot extend** the outer deadline.
|
|
90
|
+
|
|
91
|
+
This makes time budgets safe and deterministic.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Using with services (multiple layers)
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import axios from "axios";
|
|
99
|
+
|
|
100
|
+
await withTimeout(2000, async (signal) => {
|
|
101
|
+
await controller(signal);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
async function controller(signal) {
|
|
105
|
+
await serviceA(signal);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function serviceA(signal) {
|
|
109
|
+
await serviceB(signal);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function serviceB(signal) {
|
|
113
|
+
const res = await axios.get("/users", { signal });
|
|
114
|
+
return res.data;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
All functions share the same deadline by passing the same `AbortSignal` down the call chain.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Abort-aware vs non-abort-aware operations
|
|
123
|
+
|
|
124
|
+
### Abort-aware APIs (cancel immediately)
|
|
125
|
+
|
|
126
|
+
These stop execution as soon as the deadline is exceeded:
|
|
127
|
+
|
|
128
|
+
* `fetch` (Node 18+)
|
|
129
|
+
* `axios` (with `{ signal }`)
|
|
130
|
+
* `fs/promises` (partial)
|
|
131
|
+
* `stream.pipeline`
|
|
132
|
+
* `timers/promises`
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// GET
|
|
138
|
+
await axios.get(url, { signal }); // 👈 AbortSignal goes here
|
|
139
|
+
// POST
|
|
140
|
+
await axios.post(
|
|
141
|
+
url,
|
|
142
|
+
{ name: "Aryan", role: "admin" },
|
|
143
|
+
{
|
|
144
|
+
signal, // 👈 AbortSignal goes here
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
Authorization: "Bearer YOUR_TOKEN",
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Non-abort-aware operations (cooperative)
|
|
154
|
+
|
|
155
|
+
These **cannot be forcibly stopped**:
|
|
156
|
+
|
|
157
|
+
* `setTimeout` / sleep
|
|
158
|
+
* Sequelize queries
|
|
159
|
+
* CPU-bound loops
|
|
160
|
+
* legacy libraries
|
|
161
|
+
|
|
162
|
+
For these, `safe-timeout`:
|
|
163
|
+
|
|
164
|
+
* stops waiting
|
|
165
|
+
* rejects the outer promise
|
|
166
|
+
* allows you to guard further logic
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Non-abort-aware operations and control flow
|
|
171
|
+
|
|
172
|
+
JavaScript cannot forcibly stop non-abort-aware operations (like `setTimeout`, Sequelize queries, or CPU-bound work).
|
|
173
|
+
|
|
174
|
+
When such operations exceed the deadline:
|
|
175
|
+
|
|
176
|
+
* `safe-timeout` rejects the outer promise
|
|
177
|
+
* abort-aware APIs are cancelled automatically
|
|
178
|
+
* JavaScript execution resumes only when the pending operation completes
|
|
179
|
+
|
|
180
|
+
To keep control flow predictable:
|
|
181
|
+
|
|
182
|
+
* prefer calling abort-aware APIs (Axios, fetch, streams) after non-abort-aware work
|
|
183
|
+
* abort-aware APIs will throw immediately if the deadline has already been exceeded
|
|
184
|
+
|
|
185
|
+
This design avoids hidden global checks while remaining honest about JavaScript limitations.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Axios integration
|
|
190
|
+
|
|
191
|
+
`safe-timeout` works with Axios by passing the provided `AbortSignal` to the request.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import axios from "axios";
|
|
195
|
+
import { withTimeout } from "safe-timeout";
|
|
196
|
+
|
|
197
|
+
await withTimeout(2000, async (signal) => {
|
|
198
|
+
const res = await axios.get("/users", { signal });
|
|
199
|
+
return res.data;
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Axios is abort-aware:
|
|
204
|
+
|
|
205
|
+
* if the deadline is exceeded before the request starts, Axios throws immediately
|
|
206
|
+
* if the deadline is exceeded while the request is in flight, Axios cancels the request
|
|
207
|
+
|
|
208
|
+
This explicit integration keeps cancellation predictable and avoids hidden behavior.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## What `safe-timeout` does NOT do
|
|
213
|
+
|
|
214
|
+
It is important to be explicit about limitations:
|
|
215
|
+
|
|
216
|
+
* ❌ It cannot forcibly stop JavaScript execution
|
|
217
|
+
* ❌ It cannot cancel non-abort-aware libraries
|
|
218
|
+
* ❌ It cannot stop CPU-bound loops
|
|
219
|
+
* ❌ It does not replace DB-level timeouts
|
|
220
|
+
|
|
221
|
+
This matches the realities of Node.js and modern async runtimes.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## How this differs from `setTimeout`
|
|
226
|
+
|
|
227
|
+
| Feature | setTimeout | safe-timeout |
|
|
228
|
+
| ------------------- | ---------- | ------------ |
|
|
229
|
+
| End-to-end deadline | ❌ | ✅ |
|
|
230
|
+
| Nested composition | ❌ | ✅ |
|
|
231
|
+
| AbortSignal support | ❌ | ✅ |
|
|
232
|
+
| Context propagation | ❌ | ✅ |
|
|
233
|
+
| Concurrency-safe | ❌ | ✅ |
|
|
234
|
+
|
|
235
|
+
`setTimeout` works locally. `safe-timeout` works across your entire async call graph.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## API
|
|
240
|
+
|
|
241
|
+
### `withTimeout(ms, fn)`
|
|
242
|
+
|
|
243
|
+
Runs an async function with a deadline.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
withTimeout<T>(ms: number, fn: (signal: AbortSignal) => Promise<T>): Promise<T>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Rejects with `TimeoutError` when the deadline is exceeded.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### `TimeoutError`
|
|
254
|
+
|
|
255
|
+
Error thrown when the deadline is exceeded.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
instanceof TimeoutError === true
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## When to use this
|
|
264
|
+
|
|
265
|
+
Use `safe-timeout` when:
|
|
266
|
+
|
|
267
|
+
* you want request-level deadlines
|
|
268
|
+
* you call multiple async services
|
|
269
|
+
* you rely on Axios, fetch, or streams
|
|
270
|
+
* you want correct nested timeout behavior
|
|
271
|
+
|
|
272
|
+
Do **not** use it as a replacement for DB-level query timeouts.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
|
|
3
|
+
declare function withTimeout<T>(ms: number, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
4
|
+
|
|
5
|
+
type TimeoutContext = {
|
|
6
|
+
deadline: number;
|
|
7
|
+
controller: AbortController;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
declare const timeoutContext: AsyncLocalStorage<TimeoutContext>;
|
|
11
|
+
|
|
12
|
+
declare class TimeoutError extends Error {
|
|
13
|
+
constructor(message?: string);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { TimeoutError, timeoutContext, withTimeout };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
|
|
3
|
+
declare function withTimeout<T>(ms: number, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
4
|
+
|
|
5
|
+
type TimeoutContext = {
|
|
6
|
+
deadline: number;
|
|
7
|
+
controller: AbortController;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
declare const timeoutContext: AsyncLocalStorage<TimeoutContext>;
|
|
11
|
+
|
|
12
|
+
declare class TimeoutError extends Error {
|
|
13
|
+
constructor(message?: string);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { TimeoutError, timeoutContext, withTimeout };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
TimeoutError: () => TimeoutError,
|
|
24
|
+
timeoutContext: () => timeoutContext,
|
|
25
|
+
withTimeout: () => withTimeout
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/context.ts
|
|
30
|
+
var import_node_async_hooks = require("async_hooks");
|
|
31
|
+
var timeoutContext = new import_node_async_hooks.AsyncLocalStorage();
|
|
32
|
+
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
var TimeoutError = class extends Error {
|
|
35
|
+
constructor(message = "Operation timed out") {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "TimeoutError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/withTimeout.ts
|
|
42
|
+
async function withTimeout(ms, fn) {
|
|
43
|
+
const parent = timeoutContext.getStore();
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const deadline = parent ? Math.min(parent.deadline, now + ms) : now + ms;
|
|
46
|
+
if (deadline <= now) {
|
|
47
|
+
throw new TimeoutError();
|
|
48
|
+
}
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const remaining = deadline - now;
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
controller.abort();
|
|
53
|
+
}, remaining);
|
|
54
|
+
try {
|
|
55
|
+
return await timeoutContext.run(
|
|
56
|
+
{ deadline, controller },
|
|
57
|
+
() => Promise.race([
|
|
58
|
+
fn(controller.signal),
|
|
59
|
+
new Promise((_, reject) => {
|
|
60
|
+
controller.signal.addEventListener(
|
|
61
|
+
"abort",
|
|
62
|
+
() => reject(new TimeoutError()),
|
|
63
|
+
{ once: true }
|
|
64
|
+
);
|
|
65
|
+
})
|
|
66
|
+
])
|
|
67
|
+
);
|
|
68
|
+
} finally {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
73
|
+
0 && (module.exports = {
|
|
74
|
+
TimeoutError,
|
|
75
|
+
timeoutContext,
|
|
76
|
+
withTimeout
|
|
77
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
var timeoutContext = new AsyncLocalStorage();
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var TimeoutError = class extends Error {
|
|
7
|
+
constructor(message = "Operation timed out") {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "TimeoutError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/withTimeout.ts
|
|
14
|
+
async function withTimeout(ms, fn) {
|
|
15
|
+
const parent = timeoutContext.getStore();
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
const deadline = parent ? Math.min(parent.deadline, now + ms) : now + ms;
|
|
18
|
+
if (deadline <= now) {
|
|
19
|
+
throw new TimeoutError();
|
|
20
|
+
}
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const remaining = deadline - now;
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
controller.abort();
|
|
25
|
+
}, remaining);
|
|
26
|
+
try {
|
|
27
|
+
return await timeoutContext.run(
|
|
28
|
+
{ deadline, controller },
|
|
29
|
+
() => Promise.race([
|
|
30
|
+
fn(controller.signal),
|
|
31
|
+
new Promise((_, reject) => {
|
|
32
|
+
controller.signal.addEventListener(
|
|
33
|
+
"abort",
|
|
34
|
+
() => reject(new TimeoutError()),
|
|
35
|
+
{ once: true }
|
|
36
|
+
);
|
|
37
|
+
})
|
|
38
|
+
])
|
|
39
|
+
);
|
|
40
|
+
} finally {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
TimeoutError,
|
|
46
|
+
timeoutContext,
|
|
47
|
+
withTimeout
|
|
48
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "safe-timeouts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deadline-based timeouts for async code in Node.js. Enforce end-to-end execution deadlines with automatic propagation and AbortSignal support.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/yetanotheraryan/safe-timeout.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"timeout",
|
|
17
|
+
"deadline",
|
|
18
|
+
"abortcontroller",
|
|
19
|
+
"abortsignal",
|
|
20
|
+
"async",
|
|
21
|
+
"nodejs",
|
|
22
|
+
"axios",
|
|
23
|
+
"fetch",
|
|
24
|
+
"structured-concurrency",
|
|
25
|
+
"async-hooks"
|
|
26
|
+
],
|
|
27
|
+
"author": "Aryan Tiwari",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/yetanotheraryan/safe-timeout/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/yetanotheraryan/safe-timeout#readme",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.0.9",
|
|
35
|
+
"tsup": "^8.5.1",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
}
|
|
38
|
+
}
|