nest-abort-controller 1.2.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 +375 -0
- package/dist/src/abort-controller.middleware.d.ts +9 -0
- package/dist/src/abort-controller.middleware.js +58 -0
- package/dist/src/abort-signal.decorator.d.ts +1 -0
- package/dist/src/abort-signal.decorator.js +8 -0
- package/dist/src/abort-utilities.d.ts +4 -0
- package/dist/src/abort-utilities.js +11 -0
- package/dist/src/abort.module.d.ts +8 -0
- package/dist/src/abort.module.js +45 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +21 -0
- package/dist/src/throw-if-aborted.d.ts +4 -0
- package/dist/src/throw-if-aborted.js +16 -0
- package/dist/src/types.d.ts +9 -0
- package/dist/src/types.js +2 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ferhat Yalçın
|
|
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,375 @@
|
|
|
1
|
+
# nestjs-abort-controller
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/nestjs-abort-controller)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Graceful request cancellation support for NestJS using native `AbortController`. Automatically handles client disconnections and provides easy-to-use decorators for request-scoped cancellation.
|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- 🚀 **Automatic AbortController Creation**: Creates `AbortSignal` for each incoming HTTP request
|
|
11
|
+
- 🔄 **Client Disconnection Handling**: Automatically aborts long-running operations when client disconnects
|
|
12
|
+
- 🎯 **Easy Integration**: Simple decorator-based approach with `@NestAbortSignal()`
|
|
13
|
+
- ⚡ **Lightweight**: Zero additional dependencies (only Node >= 16)
|
|
14
|
+
- 🔧 **TypeScript Support**: Full TypeScript support with proper type definitions
|
|
15
|
+
- 🛡️ **Error Handling**: Built-in utilities for checking abort status
|
|
16
|
+
- ⏰ **Configurable Timeout**: Set request timeout in **milliseconds** to automatically abort long-running operations
|
|
17
|
+
- 📝 **Logging Support**: Optional logging for debugging abort scenarios
|
|
18
|
+
|
|
19
|
+
## 📦 Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install nestjs-abort-controller
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🚀 Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Import the Module
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { Module } from '@nestjs/common';
|
|
31
|
+
import { AbortControllerModule } from 'nestjs-abort-controller';
|
|
32
|
+
|
|
33
|
+
@Module({
|
|
34
|
+
imports: [
|
|
35
|
+
AbortControllerModule.forRoot({
|
|
36
|
+
timeout: 30000, // 30000ms = 30 seconds timeout
|
|
37
|
+
enableLogging: true,
|
|
38
|
+
}),
|
|
39
|
+
],
|
|
40
|
+
})
|
|
41
|
+
export class AppModule {}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Use the Decorator
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { Controller, Get } from '@nestjs/common';
|
|
48
|
+
import { NestAbortSignal, throwIfAborted } from 'nestjs-abort-controller';
|
|
49
|
+
|
|
50
|
+
@Controller('api')
|
|
51
|
+
export class ApiController {
|
|
52
|
+
@Get('long-running-operation')
|
|
53
|
+
async longRunningOperation(@NestAbortSignal() signal: AbortSignal): Promise<string> {
|
|
54
|
+
for (let i = 0; i < 100; i++) {
|
|
55
|
+
// Check if operation was aborted
|
|
56
|
+
throwIfAborted(signal);
|
|
57
|
+
|
|
58
|
+
// Your long-running work here
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return 'Operation completed successfully!';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 📚 API Reference
|
|
68
|
+
|
|
69
|
+
### Decorators
|
|
70
|
+
|
|
71
|
+
#### `@NestAbortSignal()`
|
|
72
|
+
|
|
73
|
+
A parameter decorator that injects the `AbortSignal` for the current request.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
@Get()
|
|
77
|
+
async handler(@NestAbortSignal() signal: AbortSignal) {
|
|
78
|
+
// signal is automatically aborted when client disconnects
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Utility Functions
|
|
83
|
+
|
|
84
|
+
#### `throwIfAborted(signal: AbortSignal, message?: string): void`
|
|
85
|
+
|
|
86
|
+
Throws an `AbortError` if the signal is aborted.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { throwIfAborted } from 'nestjs-abort-controller';
|
|
90
|
+
|
|
91
|
+
async function longRunningOperation(signal: AbortSignal) {
|
|
92
|
+
for (let i = 0; i < 1000; i++) {
|
|
93
|
+
throwIfAborted(signal); // Will throw if aborted
|
|
94
|
+
|
|
95
|
+
// Your work here
|
|
96
|
+
await someAsyncWork();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### `AbortError`
|
|
102
|
+
|
|
103
|
+
Custom error class for abort operations.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { AbortError } from 'nestjs-abort-controller';
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Some operation
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (error instanceof AbortError) {
|
|
112
|
+
console.log('Operation was aborted');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `AbortControllerModule`
|
|
118
|
+
|
|
119
|
+
The main module that sets up the middleware for all routes.
|
|
120
|
+
|
|
121
|
+
#### `forRoot(options?: AbortControllerOptions)`
|
|
122
|
+
|
|
123
|
+
Configure the module for all routes.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { AbortControllerModule } from 'nestjs-abort-controller';
|
|
127
|
+
|
|
128
|
+
@Module({
|
|
129
|
+
imports: [
|
|
130
|
+
AbortControllerModule.forRoot({
|
|
131
|
+
timeout: 30000, // 30000ms = 30 seconds
|
|
132
|
+
enableLogging: true,
|
|
133
|
+
}),
|
|
134
|
+
],
|
|
135
|
+
})
|
|
136
|
+
export class AppModule {}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### `forRoutes(routes: string | string[], options?: AbortControllerOptions)`
|
|
140
|
+
|
|
141
|
+
Configure the module for specific routes.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { AbortControllerModule } from 'nestjs-abort-controller';
|
|
145
|
+
|
|
146
|
+
@Module({
|
|
147
|
+
imports: [
|
|
148
|
+
AbortControllerModule.forRoutes(['/api/*'], {
|
|
149
|
+
timeout: 60000, // 60000ms = 60 seconds
|
|
150
|
+
enableLogging: false,
|
|
151
|
+
}),
|
|
152
|
+
],
|
|
153
|
+
})
|
|
154
|
+
export class AppModule {}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Configuration Options
|
|
158
|
+
|
|
159
|
+
#### `AbortControllerOptions`
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
interface AbortControllerOptions {
|
|
163
|
+
timeout?: number; // Timeout duration in milliseconds (default: 30000ms = 30 seconds)
|
|
164
|
+
enableLogging?: boolean; // Enable debug logging (default: false)
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 🔧 Advanced Usage
|
|
169
|
+
|
|
170
|
+
### Using with Services
|
|
171
|
+
|
|
172
|
+
The recommended approach is to pass the `AbortSignal` from controller to service methods.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { Injectable } from '@nestjs/common';
|
|
176
|
+
import { throwIfAborted } from 'nestjs-abort-controller';
|
|
177
|
+
|
|
178
|
+
@Injectable()
|
|
179
|
+
export class UserService {
|
|
180
|
+
async getUsers(signal: AbortSignal) {
|
|
181
|
+
for (let i = 0; i < 100; i++) {
|
|
182
|
+
throwIfAborted(signal);
|
|
183
|
+
await someAsyncWork();
|
|
184
|
+
}
|
|
185
|
+
return users;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
import { Controller, Get } from '@nestjs/common';
|
|
190
|
+
import { NestAbortSignal } from 'nestjs-abort-controller';
|
|
191
|
+
|
|
192
|
+
@Controller('users')
|
|
193
|
+
export class UserController {
|
|
194
|
+
constructor(private userService: UserService) {}
|
|
195
|
+
|
|
196
|
+
@Get()
|
|
197
|
+
getUsers(@NestAbortSignal() signal: AbortSignal) {
|
|
198
|
+
// Pass signal to service
|
|
199
|
+
return this.userService.getUsers(signal);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Integration with External APIs
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { Injectable } from '@nestjs/common';
|
|
208
|
+
|
|
209
|
+
@Injectable()
|
|
210
|
+
export class ExternalApiService {
|
|
211
|
+
async fetchFromExternalApi(signal: AbortSignal) {
|
|
212
|
+
const response = await fetch('https://api.example.com/data', {
|
|
213
|
+
signal, // Pass the signal to fetch
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return response.json();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
import { Controller, Get } from '@nestjs/common';
|
|
221
|
+
import { NestAbortSignal } from 'nestjs-abort-controller';
|
|
222
|
+
|
|
223
|
+
@Controller('api')
|
|
224
|
+
export class ApiController {
|
|
225
|
+
constructor(private externalApiService: ExternalApiService) {}
|
|
226
|
+
|
|
227
|
+
@Get('external-data')
|
|
228
|
+
async getExternalData(@NestAbortSignal() signal: AbortSignal) {
|
|
229
|
+
return this.externalApiService.fetchFromExternalApi(signal);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Manual Abort Signal Checking
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { Controller, Get } from '@nestjs/common';
|
|
238
|
+
import { NestAbortSignal, throwIfAborted } from 'nestjs-abort-controller';
|
|
239
|
+
|
|
240
|
+
@Controller('api')
|
|
241
|
+
export class ApiController {
|
|
242
|
+
@Get('manual-check')
|
|
243
|
+
async manualCheck(@NestAbortSignal() signal: AbortSignal) {
|
|
244
|
+
for (let i = 0; i < 1000; i++) {
|
|
245
|
+
// Manual check
|
|
246
|
+
if (signal.aborted) {
|
|
247
|
+
throw new Error('Operation was cancelled');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Or use utility function
|
|
251
|
+
throwIfAborted(signal);
|
|
252
|
+
|
|
253
|
+
await someAsyncWork();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Error Handling
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { Controller, Get } from '@nestjs/common';
|
|
263
|
+
import { NestAbortSignal, throwIfAborted, AbortError } from 'nestjs-abort-controller';
|
|
264
|
+
|
|
265
|
+
@Controller('api')
|
|
266
|
+
export class ApiController {
|
|
267
|
+
@Get('with-error-handling')
|
|
268
|
+
async withErrorHandling(@NestAbortSignal() signal: AbortSignal) {
|
|
269
|
+
try {
|
|
270
|
+
for (let i = 0; i < 100; i++) {
|
|
271
|
+
throwIfAborted(signal);
|
|
272
|
+
await someAsyncWork();
|
|
273
|
+
}
|
|
274
|
+
return 'Success';
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (error instanceof AbortError) {
|
|
277
|
+
// Handle abort specifically
|
|
278
|
+
return 'Operation was cancelled';
|
|
279
|
+
}
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Multiple Service Calls
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import { Injectable } from '@nestjs/common';
|
|
290
|
+
import { throwIfAborted } from 'nestjs-abort-controller';
|
|
291
|
+
|
|
292
|
+
@Injectable()
|
|
293
|
+
export class DataService {
|
|
294
|
+
async processData(signal: AbortSignal) {
|
|
295
|
+
throwIfAborted(signal);
|
|
296
|
+
// Process data
|
|
297
|
+
return processedData;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@Injectable()
|
|
302
|
+
export class CacheService {
|
|
303
|
+
async getFromCache(signal: AbortSignal) {
|
|
304
|
+
throwIfAborted(signal);
|
|
305
|
+
// Get from cache
|
|
306
|
+
return cachedData;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
import { Controller, Get } from '@nestjs/common';
|
|
311
|
+
import { NestAbortSignal } from 'nestjs-abort-controller';
|
|
312
|
+
|
|
313
|
+
@Controller('data')
|
|
314
|
+
export class DataController {
|
|
315
|
+
constructor(
|
|
316
|
+
private dataService: DataService,
|
|
317
|
+
private cacheService: CacheService
|
|
318
|
+
) {}
|
|
319
|
+
|
|
320
|
+
@Get('process')
|
|
321
|
+
async processData(@NestAbortSignal() signal: AbortSignal) {
|
|
322
|
+
// Use same signal for multiple service calls
|
|
323
|
+
const cached = await this.cacheService.getFromCache(signal);
|
|
324
|
+
const processed = await this.dataService.processData(signal);
|
|
325
|
+
|
|
326
|
+
return { cached, processed };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## 🧪 Testing
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
335
|
+
import { AbortControllerModule } from 'nestjs-abort-controller';
|
|
336
|
+
|
|
337
|
+
describe('AbortController', () => {
|
|
338
|
+
let module: TestingModule;
|
|
339
|
+
|
|
340
|
+
beforeEach(async () => {
|
|
341
|
+
module = await Test.createTestingModule({
|
|
342
|
+
imports: [AbortControllerModule.forRoot()],
|
|
343
|
+
}).compile();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Your tests here
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## 📋 Requirements
|
|
351
|
+
|
|
352
|
+
- Node.js >= 16.0.0
|
|
353
|
+
- NestJS >= 8.0.0
|
|
354
|
+
|
|
355
|
+
## 🤝 Contributing
|
|
356
|
+
|
|
357
|
+
1. Fork the repository
|
|
358
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
359
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
360
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
361
|
+
5. Open a Pull Request
|
|
362
|
+
|
|
363
|
+
## 📄 License
|
|
364
|
+
|
|
365
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
366
|
+
|
|
367
|
+
## 🙏 Acknowledgments
|
|
368
|
+
|
|
369
|
+
- Built for the NestJS community
|
|
370
|
+
- Inspired by the need for better request cancellation handling
|
|
371
|
+
- Uses native Node.js AbortController for maximum compatibility
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
**Made with ❤️ for the NestJS community**
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NestMiddleware } from '@nestjs/common';
|
|
2
|
+
import { Response } from 'express';
|
|
3
|
+
import { AbortControllerOptions, AbortControllerRequest } from './types';
|
|
4
|
+
export declare class AbortControllerMiddleware implements NestMiddleware {
|
|
5
|
+
private readonly logger;
|
|
6
|
+
private static options;
|
|
7
|
+
static setOptions(options: AbortControllerOptions): void;
|
|
8
|
+
use(req: AbortControllerRequest, res: Response, next: () => void): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var AbortControllerMiddleware_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.AbortControllerMiddleware = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const LOG_MESSAGES = {
|
|
13
|
+
CLIENT_DISCONNECTED: 'Client disconnected, aborting request',
|
|
14
|
+
REQUEST_TIMEOUT: 'Request timeout after {timeout}ms',
|
|
15
|
+
REQUEST_COMPLETED: 'Request completed successfully',
|
|
16
|
+
};
|
|
17
|
+
let AbortControllerMiddleware = class AbortControllerMiddleware {
|
|
18
|
+
static { AbortControllerMiddleware_1 = this; }
|
|
19
|
+
logger = new common_1.Logger(AbortControllerMiddleware_1.name);
|
|
20
|
+
static options = {};
|
|
21
|
+
static setOptions(options) {
|
|
22
|
+
AbortControllerMiddleware_1.options = options;
|
|
23
|
+
}
|
|
24
|
+
use(req, res, next) {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
// Timeout in milliseconds (default: 30000ms = 30 seconds)
|
|
27
|
+
const timeout = AbortControllerMiddleware_1.options.timeout ?? 30000;
|
|
28
|
+
const enableLogging = AbortControllerMiddleware_1.options.enableLogging ?? false;
|
|
29
|
+
req.abortController = controller;
|
|
30
|
+
req.abortSignal = controller.signal;
|
|
31
|
+
req.on('close', () => {
|
|
32
|
+
if (enableLogging) {
|
|
33
|
+
this.logger.debug(LOG_MESSAGES.CLIENT_DISCONNECTED);
|
|
34
|
+
}
|
|
35
|
+
controller.abort();
|
|
36
|
+
});
|
|
37
|
+
if (timeout > 0) {
|
|
38
|
+
const timeoutId = setTimeout(() => {
|
|
39
|
+
if (enableLogging) {
|
|
40
|
+
this.logger.warn(LOG_MESSAGES.REQUEST_TIMEOUT.replace('{timeout}', timeout.toString()));
|
|
41
|
+
}
|
|
42
|
+
controller.abort();
|
|
43
|
+
}, timeout);
|
|
44
|
+
req.on('close', () => clearTimeout(timeoutId));
|
|
45
|
+
res.on('finish', () => clearTimeout(timeoutId));
|
|
46
|
+
}
|
|
47
|
+
res.on('finish', () => {
|
|
48
|
+
if (enableLogging) {
|
|
49
|
+
this.logger.debug(LOG_MESSAGES.REQUEST_COMPLETED);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
next();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
exports.AbortControllerMiddleware = AbortControllerMiddleware;
|
|
56
|
+
exports.AbortControllerMiddleware = AbortControllerMiddleware = AbortControllerMiddleware_1 = __decorate([
|
|
57
|
+
(0, common_1.Injectable)()
|
|
58
|
+
], AbortControllerMiddleware);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const NestAbortSignal: (...dataOrPipes: any[]) => ParameterDecorator;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NestAbortSignal = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
exports.NestAbortSignal = (0, common_1.createParamDecorator)((_, ctx) => {
|
|
6
|
+
const req = ctx.switchToHttp().getRequest();
|
|
7
|
+
return req.abortSignal;
|
|
8
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.throwIfAborted = throwIfAborted;
|
|
4
|
+
/**
|
|
5
|
+
* Utility function to throw if aborted with custom error
|
|
6
|
+
*/
|
|
7
|
+
function throwIfAborted(signal, message) {
|
|
8
|
+
if (signal.aborted) {
|
|
9
|
+
throw new Error(message || 'Operation was aborted');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MiddlewareConsumer, NestModule, DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { AbortControllerOptions } from './types';
|
|
3
|
+
export declare class AbortControllerModule implements NestModule {
|
|
4
|
+
private static routes;
|
|
5
|
+
configure(consumer: MiddlewareConsumer): void;
|
|
6
|
+
static forRoot(options?: AbortControllerOptions): DynamicModule;
|
|
7
|
+
static forRoutes(routes?: string | string[], options?: AbortControllerOptions): DynamicModule;
|
|
8
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var AbortControllerModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.AbortControllerModule = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const abort_controller_middleware_1 = require("./abort-controller.middleware");
|
|
13
|
+
let AbortControllerModule = class AbortControllerModule {
|
|
14
|
+
static { AbortControllerModule_1 = this; }
|
|
15
|
+
static routes = '*';
|
|
16
|
+
configure(consumer) {
|
|
17
|
+
const routes = AbortControllerModule_1.routes;
|
|
18
|
+
if (Array.isArray(routes)) {
|
|
19
|
+
consumer.apply(abort_controller_middleware_1.AbortControllerMiddleware).forRoutes(...routes);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
consumer.apply(abort_controller_middleware_1.AbortControllerMiddleware).forRoutes(routes);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
static forRoot(options) {
|
|
26
|
+
AbortControllerModule_1.routes = '*';
|
|
27
|
+
abort_controller_middleware_1.AbortControllerMiddleware.setOptions(options || {});
|
|
28
|
+
return {
|
|
29
|
+
module: AbortControllerModule_1,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
static forRoutes(routes = '*', options) {
|
|
33
|
+
AbortControllerModule_1.routes = routes;
|
|
34
|
+
abort_controller_middleware_1.AbortControllerMiddleware.setOptions(options || {});
|
|
35
|
+
return {
|
|
36
|
+
module: AbortControllerModule_1,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.AbortControllerModule = AbortControllerModule;
|
|
41
|
+
exports.AbortControllerModule = AbortControllerModule = AbortControllerModule_1 = __decorate([
|
|
42
|
+
(0, common_1.Module)({
|
|
43
|
+
providers: [abort_controller_middleware_1.AbortControllerMiddleware],
|
|
44
|
+
})
|
|
45
|
+
], AbortControllerModule);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./abort-controller.middleware"), exports);
|
|
18
|
+
__exportStar(require("./abort-signal.decorator"), exports);
|
|
19
|
+
__exportStar(require("./abort.module"), exports);
|
|
20
|
+
__exportStar(require("./types"), exports);
|
|
21
|
+
__exportStar(require("./throw-if-aborted"), exports);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AbortError = void 0;
|
|
4
|
+
exports.throwIfAborted = throwIfAborted;
|
|
5
|
+
class AbortError extends Error {
|
|
6
|
+
constructor(message = 'Operation was aborted') {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'AbortError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.AbortError = AbortError;
|
|
12
|
+
function throwIfAborted(signal, message) {
|
|
13
|
+
if (signal.aborted) {
|
|
14
|
+
throw new AbortError(message);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nest-abort-controller",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "AbortController integration for NestJS: request-scoped cancellation using AbortSignal.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"abort",
|
|
7
|
+
"abort-signal",
|
|
8
|
+
"abortcontroller",
|
|
9
|
+
"cancellation",
|
|
10
|
+
"middleware",
|
|
11
|
+
"nestjs",
|
|
12
|
+
"request-cancellation",
|
|
13
|
+
"typescript"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/ylcnfrht/nestjs-abort-controller#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/ylcnfrht/nestjs-abort-controller/issues"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Ferhat Yalçın",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist/**/*",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"main": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"require": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"reflect-metadata": "^0.2.2",
|
|
37
|
+
"rxjs": "^7.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@nestjs/common": "^12.0.1",
|
|
41
|
+
"@nestjs/core": "^12.0.1",
|
|
42
|
+
"@nestjs/testing": "^12.0.1",
|
|
43
|
+
"@types/express": "^5.0.3",
|
|
44
|
+
"@types/jest": "^30.0.0",
|
|
45
|
+
"@types/node": "^22.20.2",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^8.70.0",
|
|
47
|
+
"@typescript-eslint/parser": "^8.70.0",
|
|
48
|
+
"eslint": "^10.10.0",
|
|
49
|
+
"express": "^5.1.0",
|
|
50
|
+
"jest": "^30.5.1",
|
|
51
|
+
"prettier": "^3.0.0",
|
|
52
|
+
"ts-jest": "^29.0.0",
|
|
53
|
+
"ts-node": "^10.0.0",
|
|
54
|
+
"typescript": "^6.0.3"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@nestjs/common": "^11.0.0",
|
|
58
|
+
"@nestjs/core": "^11.0.0"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=16.0.0"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsc",
|
|
65
|
+
"dev": "tsc --watch",
|
|
66
|
+
"clean": "rm -rf dist",
|
|
67
|
+
"prebuild": "npm run clean",
|
|
68
|
+
"test": "jest",
|
|
69
|
+
"test:watch": "jest --watch",
|
|
70
|
+
"test:coverage": "jest --coverage",
|
|
71
|
+
"lint": "eslint src/**/*.ts",
|
|
72
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
73
|
+
"format": "prettier --write src/**/*.ts",
|
|
74
|
+
"start:example": "ts-node usage/src/main.ts"
|
|
75
|
+
}
|
|
76
|
+
}
|