Skip to content

Commit cf95154

Browse files
authored
Merge pull request #9 from TENSIILE/release/v1.0.1
release/v1.0.1
2 parents 1d30732 + 952ef57 commit cf95154

12 files changed

Lines changed: 42 additions & 64 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<img src="https://github.com/TENSIILE/saborter-server/actions/workflows/publish.yml/badge.svg" /></a>
1010
<a href="https://github.com/TENSIILE/saborter-server/actions/workflows/ci.yml" alt="CI">
1111
<img src="https://github.com/TENSIILE/saborter-server/actions/workflows/ci.yml/badge.svg" /></a>
12-
<a href="https://github.com/TENSIILE/saborter-server/blob/develop/LICENSE" alt="License">
12+
<a href="https://github.com/TENSIILE/saborter-server/blob/develop/LICENSE.md" alt="License">
1313
<img src="https://img.shields.io/badge/license-MIT-blue" /></a>
1414
<a href="https://github.com/TENSIILE/saborter-server" alt="Github">
1515
<img src="https://img.shields.io/badge/repository-github-color" /></a>
@@ -46,7 +46,7 @@ import { initRequestInterrupts, getAbortSignal } from '@saborter/server/express'
4646
const app = express();
4747
const port = process.env.PORT || 3000;
4848

49-
initRequestInterrupts(app, { endpointName: '/api/abort' });
49+
initRequestInterrupts(app);
5050
app.use(express.json());
5151

5252
app.get('/', async (req, res) => {

demo/express/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/express/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"scripts": {
66
"start": "node dist/index.js",
77
"build": "tsc",
8-
"dev": "nodemon src/index.ts"
8+
"dev": "nodemon src/index.ts -watch"
99
},
1010
"keywords": [],
1111
"author": "",
@@ -14,7 +14,7 @@
1414
"dependencies": {
1515
"cors": "^2.8.6",
1616
"express": "^5.2.1",
17-
"@saborter/server": "file:./saborter-server-1.0.0.tgz"
17+
"@saborter/server": "file:./saborter-server-1.0.1.tgz"
1818
},
1919
"devDependencies": {
2020
"@types/express": "^5.0.6",
-5.22 KB
Binary file not shown.
4.84 KB
Binary file not shown.

demo/express/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { initRequestInterrupts, getAbortSignal } from '@saborter/server/express'
66
const app = express();
77
const port = process.env['PORT'] || 3000;
88

9-
initRequestInterrupts(app, { endpointName: '/api/cancel' });
9+
initRequestInterrupts(app);
1010

1111
app.use(cors());
1212
app.use(express.json());

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@saborter/server",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"description": "Lightweight, tree‑shakeable utility to cancel server tasks on client abort",
55
"main": "dist/express.cjs.js",
66
"module": "dist/express.es.js",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const ENDPOINT_WAS_INTERRUPTED_MESSAGE = 'The endpoint was interrupted';

src/packages/frameworks/express/express.service.ts

Lines changed: 5 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Request, Response, NextFunction, Express } from 'express';
22
import { AbortError } from 'saborter/errors';
33
import { setSignalInExpressRequest } from './express.utils';
4+
import * as Constants from './express.constants';
45
import * as Shared from '../../../shared';
56

67
/**
@@ -76,7 +77,7 @@ class ExpressRequestInterruptionService {
7677
setSignalInExpressRequest(req, controller);
7778

7879
this.registerAbortableFunction(requestId, () => {
79-
controller.abort(new AbortError('The endpoint was interrupted', { initiator: 'server' }));
80+
controller.abort(new AbortError(Constants.ENDPOINT_WAS_INTERRUPTED_MESSAGE, { initiator: 'server' }));
8081
});
8182

8283
req.on('close', () => {
@@ -85,6 +86,7 @@ class ExpressRequestInterruptionService {
8586
}
8687
});
8788

89+
// Triggers clearing of request id when request completes or fails, excluding the case of abort.
8890
res.on('finish', () => {
8991
if (!controller.signal.aborted) {
9092
this.abortRegistries.delete(requestId);
@@ -100,65 +102,14 @@ class ExpressRequestInterruptionService {
100102
*
101103
* This function:
102104
* - Adds a middleware that attaches an `AbortSignal` to every request.
103-
* - Adds a POST endpoint (default `/api/cancel`) that can be called to abort a
104-
* specific request by sending its request ID in the request body.
105105
*
106106
* @param app - Express application instance.
107-
* @param options - Configuration options.
108-
* @param options.endpointName - The path where the abort endpoint will be mounted.
109-
* Defaults to `/api/cancel`.
107+
110108
* @returns An `ExpressRequestInterruptionService` instance, which can be used
111109
* to manually abort requests if needed.
112110
*/
113-
export const initRequestInterrupts = (
114-
app: Express,
115-
{ endpointName = '/api/cancel' }: { endpointName?: string } = {}
116-
) => {
111+
export const initRequestInterrupts = (app: Express): void => {
117112
const requestInterruptionService = new ExpressRequestInterruptionService();
118113

119114
app.use(requestInterruptionService.expressMiddleware);
120-
121-
app.post(`${endpointName}`, async (req, res) => {
122-
const requestId = await new Promise<string | undefined>((resolve) => {
123-
let rawBody = '';
124-
125-
req.on('data', (chunk) => {
126-
rawBody += chunk;
127-
});
128-
req.on('end', () => {
129-
resolve(rawBody);
130-
});
131-
});
132-
133-
if (requestId && requestInterruptionService.abort(requestId)) {
134-
res.status(200).json({ aborted: true });
135-
} else {
136-
res.status(404).json({ error: 'Request not found' });
137-
}
138-
});
139115
};
140-
141-
/**
142-
* Retrieves the `signal` property from an Express Request object.
143-
*
144-
* @param {import('express').Request} req - The Express request object.
145-
* @returns {AbortSignal | undefined} The abort signal attached to the request,
146-
* or `undefined` if the property does not exist.
147-
*
148-
* @example
149-
* // In a route handler
150-
* app.get('/data', (req, res) => {
151-
* const signal = getAbortSignal(req);
152-
* fetch('https://api.example.com/data', { signal })
153-
* .then(response => response.json())
154-
* .then(data => res.json(data))
155-
* .catch(err => {
156-
* if (err.name === 'AbortError') {
157-
* res.status(499).end();
158-
* } else {
159-
* res.status(500).end();
160-
* }
161-
* });
162-
* });
163-
*/
164-
export const getAbortSignal = (req: Request): AbortSignal | undefined => (req as any).signal;

src/packages/frameworks/express/express.utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,28 @@ import { Request } from 'express';
2121
export const setSignalInExpressRequest = (req: Request, controller: AbortController): void => {
2222
(req as any).signal = controller.signal;
2323
};
24+
25+
/**
26+
* Retrieves the `signal` property from an Express Request object.
27+
*
28+
* @param {import('express').Request} req - The Express request object.
29+
* @returns {AbortSignal | undefined} The abort signal attached to the request,
30+
* or `undefined` if the property does not exist.
31+
*
32+
* @example
33+
* // In a route handler
34+
* app.get('/data', (req, res) => {
35+
* const signal = getAbortSignal(req);
36+
* fetch('https://api.example.com/data', { signal })
37+
* .then(response => response.json())
38+
* .then(data => res.json(data))
39+
* .catch(err => {
40+
* if (err.name === 'AbortError') {
41+
* res.status(499).end();
42+
* } else {
43+
* res.status(500).end();
44+
* }
45+
* });
46+
* });
47+
*/
48+
export const getAbortSignal = (req: Request): AbortSignal | undefined => (req as any).signal;

0 commit comments

Comments
 (0)