@iam4x/reconnecting-websocket 1.1.0 → 1.3.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/README.md +38 -18
- package/dist/index.d.ts +16 -0
- package/dist/index.js +175 -25
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -5,7 +5,9 @@ A robust, TypeScript-first WebSocket client with automatic reconnection, exponen
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- ✅ **Automatic Reconnection** - Automatically reconnects on connection loss with exponential backoff
|
|
8
|
+
- ✅ **Message Queueing** - Messages sent while disconnected are queued and delivered on reconnection
|
|
8
9
|
- ✅ **Connection Timeout** - Configurable timeout to detect stalled connections
|
|
10
|
+
- ✅ **Inactivity Detection** - Optionally reconnect when no messages are received within a timeout
|
|
9
11
|
- ✅ **Event-Driven API** - Familiar event listener pattern matching WebSocket API
|
|
10
12
|
- ✅ **TypeScript Support** - Full TypeScript definitions included
|
|
11
13
|
- ✅ **Customizable** - Configurable retry delays, backoff factors, and WebSocket implementations
|
|
@@ -76,6 +78,8 @@ interface ReconnectOptions {
|
|
|
76
78
|
maxRetryDelay?: number; // Maximum retry delay in ms (default: 30000)
|
|
77
79
|
connectionTimeout?: number; // Connection timeout in ms (default: 10000)
|
|
78
80
|
backoffFactor?: number; // Exponential backoff multiplier (default: 2)
|
|
81
|
+
healthCheckInterval?: number; // Health check interval in ms (default: 30000)
|
|
82
|
+
watchingInactivityTimeout?: number; // Inactivity timeout in ms (default: 0, disabled)
|
|
79
83
|
WebSocketConstructor?: typeof WebSocket; // Custom WebSocket implementation
|
|
80
84
|
}
|
|
81
85
|
```
|
|
@@ -86,6 +90,8 @@ interface ReconnectOptions {
|
|
|
86
90
|
- **maxRetryDelay**: The maximum delay between reconnection attempts. The delay will grow exponentially but won't exceed this value
|
|
87
91
|
- **connectionTimeout**: If a connection doesn't establish within this time, it will be aborted and retried
|
|
88
92
|
- **backoffFactor**: The multiplier for exponential backoff. Each retry delay is multiplied by this factor
|
|
93
|
+
- **healthCheckInterval**: Interval for checking if the socket is still healthy. Set to `0` to disable (default: 30000ms)
|
|
94
|
+
- **watchingInactivityTimeout**: If no message is received within this timeout, the connection will be closed and a reconnection attempt will be made. Useful for detecting silent connection failures or keeping connections alive on servers that expect regular activity. Set to `0` to disable (default: 0, disabled). A common value is `300000` (5 minutes)
|
|
89
95
|
- **WebSocketConstructor**: Allows you to provide a custom WebSocket implementation (useful for Node.js environments using libraries like `ws`)
|
|
90
96
|
|
|
91
97
|
### Methods
|
|
@@ -119,14 +125,14 @@ ws.removeEventListener("open", handler);
|
|
|
119
125
|
|
|
120
126
|
#### `send(data)`
|
|
121
127
|
|
|
122
|
-
Sends data through the WebSocket connection.
|
|
128
|
+
Sends data through the WebSocket connection. If the socket is not open, messages are automatically queued and sent once the connection is established.
|
|
123
129
|
|
|
124
130
|
```typescript
|
|
125
131
|
ws.send("Hello, Server!");
|
|
126
132
|
ws.send(JSON.stringify({ type: "ping" }));
|
|
127
133
|
```
|
|
128
134
|
|
|
129
|
-
**Note:**
|
|
135
|
+
**Note:** Messages sent while disconnected are queued and delivered in order when the socket opens. The queue is cleared if `close()` is called.
|
|
130
136
|
|
|
131
137
|
#### `close(code?, reason?)`
|
|
132
138
|
|
|
@@ -181,6 +187,26 @@ const ws = new ReconnectingWebSocket("wss://api.example.com", {
|
|
|
181
187
|
});
|
|
182
188
|
```
|
|
183
189
|
|
|
190
|
+
### Inactivity Timeout
|
|
191
|
+
|
|
192
|
+
Use `watchingInactivityTimeout` to automatically reconnect when no messages are received for a period of time. This is useful for detecting silent connection failures or when the server expects regular activity:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const ws = new ReconnectingWebSocket("wss://api.example.com", {
|
|
196
|
+
watchingInactivityTimeout: 300000, // Reconnect if no message received for 5 minutes
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
ws.addEventListener("message", (event: MessageEvent) => {
|
|
200
|
+
// Each message received resets the inactivity timer
|
|
201
|
+
console.log("Received:", event.data);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
ws.addEventListener("close", (event) => {
|
|
205
|
+
// This will fire when inactivity timeout triggers a reconnect
|
|
206
|
+
console.log("Connection closed:", event.code, event.reason);
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
184
210
|
### Using with Node.js
|
|
185
211
|
|
|
186
212
|
```typescript
|
|
@@ -194,30 +220,24 @@ const ws = new ReconnectingWebSocket("wss://api.example.com", {
|
|
|
194
220
|
|
|
195
221
|
### Handling Reconnections
|
|
196
222
|
|
|
197
|
-
|
|
198
|
-
let messageQueue: string[] = [];
|
|
223
|
+
Messages sent while disconnected are automatically queued and delivered when the connection is restored:
|
|
199
224
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
while (messageQueue.length > 0) {
|
|
203
|
-
ws.send(messageQueue.shift()!);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
225
|
+
```typescript
|
|
226
|
+
const ws = new ReconnectingWebSocket("wss://api.example.com");
|
|
206
227
|
|
|
207
228
|
ws.addEventListener("reconnect", () => {
|
|
208
229
|
console.log("Reconnected! Resuming operations...");
|
|
209
230
|
});
|
|
210
231
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
messageQueue.push(data);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
232
|
+
// Safe to call anytime - messages are queued if disconnected
|
|
233
|
+
ws.send("message1");
|
|
234
|
+
ws.send("message2");
|
|
235
|
+
|
|
236
|
+
// When socket opens/reconnects, queued messages are sent automatically
|
|
219
237
|
```
|
|
220
238
|
|
|
239
|
+
**Note:** Calling `close()` clears the message queue. Messages queued before a forced close are discarded.
|
|
240
|
+
|
|
221
241
|
### Error Handling
|
|
222
242
|
|
|
223
243
|
```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ interface ReconnectOptions {
|
|
|
6
6
|
connectionTimeout?: number;
|
|
7
7
|
backoffFactor?: number;
|
|
8
8
|
WebSocketConstructor?: typeof WebSocket;
|
|
9
|
+
healthCheckInterval?: number;
|
|
10
|
+
watchingInactivityTimeout?: number;
|
|
9
11
|
}
|
|
10
12
|
export declare class ReconnectingWebSocket {
|
|
11
13
|
options: Required<ReconnectOptions & {
|
|
@@ -15,20 +17,34 @@ export declare class ReconnectingWebSocket {
|
|
|
15
17
|
abortController?: AbortController;
|
|
16
18
|
connectTimeout?: ReturnType<typeof setTimeout>;
|
|
17
19
|
reconnectTimeout?: ReturnType<typeof setTimeout>;
|
|
20
|
+
healthCheckInterval?: ReturnType<typeof setInterval>;
|
|
21
|
+
inactivityTimeout?: ReturnType<typeof setTimeout>;
|
|
18
22
|
retryCount: number;
|
|
19
23
|
forcedClose: boolean;
|
|
20
24
|
wasConnected: boolean;
|
|
25
|
+
private openFn?;
|
|
26
|
+
private msgFn?;
|
|
27
|
+
private closeFn?;
|
|
28
|
+
private errorFn?;
|
|
29
|
+
private abortHandler?;
|
|
21
30
|
listeners: Record<EventType, Listener[]>;
|
|
31
|
+
private messageQueue;
|
|
22
32
|
get readyState(): number;
|
|
23
33
|
get bufferedAmount(): number;
|
|
24
34
|
constructor(url: string, options?: ReconnectOptions);
|
|
25
35
|
connect(): void;
|
|
26
36
|
emit(event: EventType, payload: any): void;
|
|
27
37
|
scheduleReconnect(): void;
|
|
38
|
+
startHealthCheck(): void;
|
|
39
|
+
stopHealthCheck(): void;
|
|
40
|
+
startInactivityTimer(): void;
|
|
41
|
+
stopInactivityTimer(): void;
|
|
42
|
+
resetInactivityTimer(): void;
|
|
28
43
|
clearTimers(): void;
|
|
29
44
|
addEventListener(event: EventType, listener: Listener): void;
|
|
30
45
|
removeEventListener(event: EventType, listener: Listener): void;
|
|
31
46
|
send(...args: Parameters<WebSocket["send"]>): void;
|
|
47
|
+
private flushMessageQueue;
|
|
32
48
|
close(...args: Parameters<WebSocket["close"]>): void;
|
|
33
49
|
}
|
|
34
50
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,17 @@ export class ReconnectingWebSocket {
|
|
|
4
4
|
abortController;
|
|
5
5
|
connectTimeout;
|
|
6
6
|
reconnectTimeout;
|
|
7
|
+
healthCheckInterval;
|
|
8
|
+
inactivityTimeout;
|
|
7
9
|
retryCount = 0;
|
|
8
10
|
forcedClose = false;
|
|
9
11
|
wasConnected = false;
|
|
12
|
+
// Store event handlers so we can remove them when cleaning up
|
|
13
|
+
openFn;
|
|
14
|
+
msgFn;
|
|
15
|
+
closeFn;
|
|
16
|
+
errorFn;
|
|
17
|
+
abortHandler;
|
|
10
18
|
listeners = {
|
|
11
19
|
open: [],
|
|
12
20
|
message: [],
|
|
@@ -14,6 +22,8 @@ export class ReconnectingWebSocket {
|
|
|
14
22
|
reconnect: [],
|
|
15
23
|
error: [],
|
|
16
24
|
};
|
|
25
|
+
// Queue for messages sent when socket is not open
|
|
26
|
+
messageQueue = [];
|
|
17
27
|
get readyState() {
|
|
18
28
|
return this.ws?.readyState ?? WebSocket.CLOSED;
|
|
19
29
|
}
|
|
@@ -28,43 +38,93 @@ export class ReconnectingWebSocket {
|
|
|
28
38
|
connectionTimeout: options.connectionTimeout ?? 10_000,
|
|
29
39
|
backoffFactor: options.backoffFactor ?? 2,
|
|
30
40
|
WebSocketConstructor: options.WebSocketConstructor ?? WebSocket,
|
|
41
|
+
healthCheckInterval: options.healthCheckInterval ?? 30_000,
|
|
42
|
+
watchingInactivityTimeout: options.watchingInactivityTimeout ?? 0, // disabled by default, set to 300_000 for 5 minutes
|
|
31
43
|
};
|
|
32
44
|
this.connect();
|
|
33
45
|
}
|
|
34
46
|
connect() {
|
|
47
|
+
// Reset forcedClose flag to allow reconnection for new connection attempts
|
|
48
|
+
// This ensures that manual reconnections (via connect()) can auto-reconnect
|
|
49
|
+
this.forcedClose = false;
|
|
50
|
+
// Remove event listeners from old socket
|
|
51
|
+
if (this.openFn)
|
|
52
|
+
this.ws?.removeEventListener("open", this.openFn);
|
|
53
|
+
if (this.msgFn)
|
|
54
|
+
this.ws?.removeEventListener("message", this.msgFn);
|
|
55
|
+
if (this.closeFn)
|
|
56
|
+
this.ws?.removeEventListener("close", this.closeFn);
|
|
57
|
+
if (this.errorFn)
|
|
58
|
+
this.ws?.removeEventListener("error", this.errorFn);
|
|
59
|
+
// Close old socket if still connecting or open
|
|
60
|
+
if (this.ws?.readyState === WebSocket.CONNECTING ||
|
|
61
|
+
this.ws?.readyState === WebSocket.OPEN) {
|
|
62
|
+
this.ws?.close();
|
|
63
|
+
}
|
|
64
|
+
// Clear any pending timers (this also removes abort listener from old controller)
|
|
65
|
+
this.clearTimers();
|
|
66
|
+
// Create new abort controller
|
|
35
67
|
this.abortController = new AbortController();
|
|
36
|
-
this.
|
|
68
|
+
this.abortHandler = () => {
|
|
37
69
|
if (this.ws?.readyState === WebSocket.CONNECTING) {
|
|
38
70
|
this.ws.close();
|
|
39
71
|
}
|
|
40
|
-
}
|
|
72
|
+
};
|
|
73
|
+
this.abortController.signal.addEventListener("abort", this.abortHandler);
|
|
74
|
+
// Create new socket
|
|
41
75
|
this.ws = new this.options.WebSocketConstructor(this.options.url);
|
|
42
76
|
this.connectTimeout = setTimeout(() => {
|
|
43
77
|
this.abortController?.abort();
|
|
44
78
|
}, this.options.connectionTimeout);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
if (this.
|
|
51
|
-
this.
|
|
79
|
+
// Create and store new event handlers
|
|
80
|
+
// Capture the new socket reference to check against in handlers
|
|
81
|
+
const currentWs = this.ws;
|
|
82
|
+
this.openFn = (event) => {
|
|
83
|
+
// Only process if event is from the current socket
|
|
84
|
+
if (event.target === currentWs && this.ws === currentWs) {
|
|
85
|
+
this.clearTimers();
|
|
86
|
+
this.retryCount = 0;
|
|
87
|
+
this.emit("open", event);
|
|
88
|
+
// Emit reconnect event if this was a reconnection after a disconnection
|
|
89
|
+
if (this.wasConnected) {
|
|
90
|
+
this.emit("reconnect", event);
|
|
91
|
+
}
|
|
92
|
+
this.wasConnected = true;
|
|
93
|
+
this.startHealthCheck();
|
|
94
|
+
this.startInactivityTimer();
|
|
95
|
+
// Flush any queued messages now that socket is open
|
|
96
|
+
this.flushMessageQueue();
|
|
52
97
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
};
|
|
99
|
+
this.msgFn = (event) => {
|
|
100
|
+
// Only process if event is from the current socket
|
|
101
|
+
if (event.target === currentWs && this.ws === currentWs) {
|
|
102
|
+
this.resetInactivityTimer();
|
|
103
|
+
this.emit("message", event);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
this.closeFn = (event) => {
|
|
107
|
+
// Only process if event is from the current socket
|
|
108
|
+
if (event.target === currentWs && this.ws === currentWs) {
|
|
109
|
+
this.stopHealthCheck();
|
|
110
|
+
this.stopInactivityTimer();
|
|
111
|
+
this.emit("close", { code: event.code, reason: event.reason });
|
|
112
|
+
if (!this.forcedClose) {
|
|
113
|
+
this.scheduleReconnect();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
this.errorFn = (event) => {
|
|
118
|
+
// Only process if event is from the current socket
|
|
119
|
+
if (event.target === currentWs && this.ws === currentWs) {
|
|
120
|
+
this.emit("error", event);
|
|
62
121
|
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
122
|
+
};
|
|
123
|
+
// Add event listeners to new socket
|
|
124
|
+
currentWs.addEventListener("open", this.openFn);
|
|
125
|
+
currentWs.addEventListener("message", this.msgFn);
|
|
126
|
+
currentWs.addEventListener("close", this.closeFn);
|
|
127
|
+
currentWs.addEventListener("error", this.errorFn);
|
|
68
128
|
}
|
|
69
129
|
emit(event, payload) {
|
|
70
130
|
for (const listener of this.listeners[event]) {
|
|
@@ -72,11 +132,71 @@ export class ReconnectingWebSocket {
|
|
|
72
132
|
}
|
|
73
133
|
}
|
|
74
134
|
scheduleReconnect() {
|
|
135
|
+
// Clear any existing reconnect timeout first to prevent multiple reconnects
|
|
136
|
+
if (this.reconnectTimeout) {
|
|
137
|
+
clearTimeout(this.reconnectTimeout);
|
|
138
|
+
this.reconnectTimeout = undefined;
|
|
139
|
+
}
|
|
75
140
|
const { retryDelay, backoffFactor, maxRetryDelay } = this.options;
|
|
76
141
|
const delay = Math.min(retryDelay * Math.pow(backoffFactor, this.retryCount), maxRetryDelay);
|
|
77
142
|
this.retryCount += 1;
|
|
78
143
|
this.reconnectTimeout = setTimeout(() => this.connect(), delay);
|
|
79
144
|
}
|
|
145
|
+
startHealthCheck() {
|
|
146
|
+
this.stopHealthCheck();
|
|
147
|
+
if (this.options.healthCheckInterval <= 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.healthCheckInterval = setInterval(() => {
|
|
151
|
+
// Only check if we're not forcing a close and we expect to be connected
|
|
152
|
+
if (this.forcedClose) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// If we've been connected before and the socket is not OPEN, trigger reconnection
|
|
156
|
+
if (this.wasConnected && this.readyState !== WebSocket.OPEN) {
|
|
157
|
+
// Clear the existing socket reference since it's in a bad state
|
|
158
|
+
if (this.ws) {
|
|
159
|
+
// Don't emit close event since we didn't receive one - this is a silent failure
|
|
160
|
+
this.ws = undefined;
|
|
161
|
+
}
|
|
162
|
+
this.stopHealthCheck();
|
|
163
|
+
this.scheduleReconnect();
|
|
164
|
+
}
|
|
165
|
+
}, this.options.healthCheckInterval);
|
|
166
|
+
}
|
|
167
|
+
stopHealthCheck() {
|
|
168
|
+
if (this.healthCheckInterval) {
|
|
169
|
+
clearInterval(this.healthCheckInterval);
|
|
170
|
+
this.healthCheckInterval = undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
startInactivityTimer() {
|
|
174
|
+
this.stopInactivityTimer();
|
|
175
|
+
if (this.options.watchingInactivityTimeout <= 0) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.inactivityTimeout = setTimeout(() => {
|
|
179
|
+
// Only trigger if we're not forcing a close and we expect to be connected
|
|
180
|
+
if (this.forcedClose) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Trigger reconnection due to inactivity
|
|
184
|
+
if (this.ws) {
|
|
185
|
+
this.ws.close();
|
|
186
|
+
}
|
|
187
|
+
}, this.options.watchingInactivityTimeout);
|
|
188
|
+
}
|
|
189
|
+
stopInactivityTimer() {
|
|
190
|
+
if (this.inactivityTimeout) {
|
|
191
|
+
clearTimeout(this.inactivityTimeout);
|
|
192
|
+
this.inactivityTimeout = undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
resetInactivityTimer() {
|
|
196
|
+
if (this.options.watchingInactivityTimeout > 0) {
|
|
197
|
+
this.startInactivityTimer();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
80
200
|
clearTimers() {
|
|
81
201
|
if (this.connectTimeout) {
|
|
82
202
|
clearTimeout(this.connectTimeout);
|
|
@@ -86,9 +206,16 @@ export class ReconnectingWebSocket {
|
|
|
86
206
|
clearTimeout(this.reconnectTimeout);
|
|
87
207
|
this.reconnectTimeout = undefined;
|
|
88
208
|
}
|
|
89
|
-
if (this.abortController) {
|
|
209
|
+
if (this.abortController && this.abortHandler) {
|
|
210
|
+
this.abortController.signal.removeEventListener("abort", this.abortHandler);
|
|
211
|
+
this.abortController = undefined;
|
|
212
|
+
this.abortHandler = undefined;
|
|
213
|
+
}
|
|
214
|
+
else if (this.abortController) {
|
|
90
215
|
this.abortController = undefined;
|
|
91
216
|
}
|
|
217
|
+
this.stopHealthCheck();
|
|
218
|
+
this.stopInactivityTimer();
|
|
92
219
|
}
|
|
93
220
|
addEventListener(event, listener) {
|
|
94
221
|
this.listeners[event].push(listener);
|
|
@@ -97,12 +224,35 @@ export class ReconnectingWebSocket {
|
|
|
97
224
|
this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
|
|
98
225
|
}
|
|
99
226
|
send(...args) {
|
|
100
|
-
this.ws?.
|
|
227
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
228
|
+
this.ws.send(...args);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
this.messageQueue.push(args);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
flushMessageQueue() {
|
|
235
|
+
while (this.messageQueue.length > 0 &&
|
|
236
|
+
this.ws?.readyState === WebSocket.OPEN) {
|
|
237
|
+
const args = this.messageQueue.shift();
|
|
238
|
+
this.ws.send(...args);
|
|
239
|
+
}
|
|
101
240
|
}
|
|
102
241
|
close(...args) {
|
|
103
242
|
this.forcedClose = true;
|
|
104
243
|
this.clearTimers();
|
|
244
|
+
// Clear the message queue on forced close
|
|
245
|
+
this.messageQueue = [];
|
|
105
246
|
if (this.ws) {
|
|
247
|
+
// Remove event listeners before closing to prevent memory leaks
|
|
248
|
+
if (this.openFn)
|
|
249
|
+
this.ws.removeEventListener("open", this.openFn);
|
|
250
|
+
if (this.msgFn)
|
|
251
|
+
this.ws.removeEventListener("message", this.msgFn);
|
|
252
|
+
if (this.closeFn)
|
|
253
|
+
this.ws.removeEventListener("close", this.closeFn);
|
|
254
|
+
if (this.errorFn)
|
|
255
|
+
this.ws.removeEventListener("error", this.errorFn);
|
|
106
256
|
this.ws.close(...args);
|
|
107
257
|
this.ws = undefined;
|
|
108
258
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"module": "src/index.ts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.3.0",
|
|
7
7
|
"private": false,
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
@@ -24,17 +24,18 @@
|
|
|
24
24
|
"prepare": "husky"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@eslint/js": "^9.
|
|
28
|
-
"@
|
|
29
|
-
"eslint": "^
|
|
27
|
+
"@eslint/js": "^9.39.2",
|
|
28
|
+
"@types/bun": "^1.3.6",
|
|
29
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
30
|
+
"eslint": "^9.39.2",
|
|
30
31
|
"eslint-config-prettier": "10.1.1",
|
|
31
32
|
"eslint-import-resolver-typescript": "4.3.1",
|
|
32
33
|
"eslint-plugin-import": "^2.32.0",
|
|
33
|
-
"eslint-plugin-prettier": "^5.5.
|
|
34
|
-
"globals": "^16.
|
|
34
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
35
|
+
"globals": "^16.5.0",
|
|
35
36
|
"husky": "^9.1.7",
|
|
36
|
-
"prettier": "^3.
|
|
37
|
-
"typescript-eslint": "^8.
|
|
37
|
+
"prettier": "^3.8.1",
|
|
38
|
+
"typescript-eslint": "^8.54.0"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"typescript": "^5.8.3"
|