diff --git a/README.md b/README.md index 9537d05..0665985 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - + @@ -46,7 +46,7 @@ import { initRequestInterrupts, getAbortSignal } from '@saborter/server/express' const app = express(); const port = process.env.PORT || 3000; -initRequestInterrupts(app, { endpointName: '/api/abort' }); +initRequestInterrupts(app); app.use(express.json()); app.get('/', async (req, res) => { diff --git a/demo/express/package-lock.json b/demo/express/package-lock.json index 401d2e3..171fd00 100644 --- a/demo/express/package-lock.json +++ b/demo/express/package-lock.json @@ -63,9 +63,9 @@ } }, "node_modules/@saborter/server": { - "version": "1.0.0", + "version": "1.0.1", "resolved": "file:saborter-server-1.0.1.tgz", - "integrity": "sha512-B4WuIeOlR3DEqcO/VsKAj2dm4gGrxeGcSI/32W8qmQKkJd9a9inoXl5Zq1XYf32mDDcGjbjl4EgKDbvw4Aznmw==", + "integrity": "sha512-Wc0GR9zyUYXZFxhOeyQiQfgaoBIRXwdLZRsf+2VE1tscOSdSlDJLTxRY/FnD3LSVpKJbU3vBRrOkBODNZi7yOQ==", "license": "MIT", "peerDependencies": { "express": "^4.17.0 || ^5.0.0", diff --git a/demo/express/package.json b/demo/express/package.json index dc747f5..80da26e 100644 --- a/demo/express/package.json +++ b/demo/express/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "node dist/index.js", "build": "tsc", - "dev": "nodemon src/index.ts" + "dev": "nodemon src/index.ts -watch" }, "keywords": [], "author": "", @@ -14,7 +14,7 @@ "dependencies": { "cors": "^2.8.6", "express": "^5.2.1", - "@saborter/server": "file:./saborter-server-1.0.0.tgz" + "@saborter/server": "file:./saborter-server-1.0.1.tgz" }, "devDependencies": { "@types/express": "^5.0.6", diff --git a/demo/express/saborter-server-1.0.0.tgz b/demo/express/saborter-server-1.0.0.tgz deleted file mode 100644 index 0f5d000..0000000 Binary files a/demo/express/saborter-server-1.0.0.tgz and /dev/null differ diff --git a/demo/express/saborter-server-1.0.1.tgz b/demo/express/saborter-server-1.0.1.tgz new file mode 100644 index 0000000..f1a25e3 Binary files /dev/null and b/demo/express/saborter-server-1.0.1.tgz differ diff --git a/demo/express/src/index.ts b/demo/express/src/index.ts index 814e094..081e13e 100644 --- a/demo/express/src/index.ts +++ b/demo/express/src/index.ts @@ -6,7 +6,7 @@ import { initRequestInterrupts, getAbortSignal } from '@saborter/server/express' const app = express(); const port = process.env['PORT'] || 3000; -initRequestInterrupts(app, { endpointName: '/api/cancel' }); +initRequestInterrupts(app); app.use(cors()); app.use(express.json()); diff --git a/package.json b/package.json index 5b37fbf..7634383 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@saborter/server", - "version": "1.0.0", + "version": "1.0.1", "description": "Lightweight, tree‑shakeable utility to cancel server tasks on client abort", "main": "dist/express.cjs.js", "module": "dist/express.es.js", diff --git a/src/packages/frameworks/express/express.constants.ts b/src/packages/frameworks/express/express.constants.ts new file mode 100644 index 0000000..568f03d --- /dev/null +++ b/src/packages/frameworks/express/express.constants.ts @@ -0,0 +1 @@ +export const ENDPOINT_WAS_INTERRUPTED_MESSAGE = 'The endpoint was interrupted'; diff --git a/src/packages/frameworks/express/express.service.ts b/src/packages/frameworks/express/express.service.ts index 7a4d0db..26dc9a0 100644 --- a/src/packages/frameworks/express/express.service.ts +++ b/src/packages/frameworks/express/express.service.ts @@ -1,6 +1,7 @@ import { Request, Response, NextFunction, Express } from 'express'; import { AbortError } from 'saborter/errors'; import { setSignalInExpressRequest } from './express.utils'; +import * as Constants from './express.constants'; import * as Shared from '../../../shared'; /** @@ -76,7 +77,7 @@ class ExpressRequestInterruptionService { setSignalInExpressRequest(req, controller); this.registerAbortableFunction(requestId, () => { - controller.abort(new AbortError('The endpoint was interrupted', { initiator: 'server' })); + controller.abort(new AbortError(Constants.ENDPOINT_WAS_INTERRUPTED_MESSAGE, { initiator: 'server' })); }); req.on('close', () => { @@ -85,6 +86,7 @@ class ExpressRequestInterruptionService { } }); + // Triggers clearing of request id when request completes or fails, excluding the case of abort. res.on('finish', () => { if (!controller.signal.aborted) { this.abortRegistries.delete(requestId); @@ -100,65 +102,14 @@ class ExpressRequestInterruptionService { * * This function: * - Adds a middleware that attaches an `AbortSignal` to every request. - * - Adds a POST endpoint (default `/api/cancel`) that can be called to abort a - * specific request by sending its request ID in the request body. * * @param app - Express application instance. - * @param options - Configuration options. - * @param options.endpointName - The path where the abort endpoint will be mounted. - * Defaults to `/api/cancel`. + * @returns An `ExpressRequestInterruptionService` instance, which can be used * to manually abort requests if needed. */ -export const initRequestInterrupts = ( - app: Express, - { endpointName = '/api/cancel' }: { endpointName?: string } = {} -) => { +export const initRequestInterrupts = (app: Express): void => { const requestInterruptionService = new ExpressRequestInterruptionService(); app.use(requestInterruptionService.expressMiddleware); - - app.post(`${endpointName}`, async (req, res) => { - const requestId = await new Promise((resolve) => { - let rawBody = ''; - - req.on('data', (chunk) => { - rawBody += chunk; - }); - req.on('end', () => { - resolve(rawBody); - }); - }); - - if (requestId && requestInterruptionService.abort(requestId)) { - res.status(200).json({ aborted: true }); - } else { - res.status(404).json({ error: 'Request not found' }); - } - }); }; - -/** - * Retrieves the `signal` property from an Express Request object. - * - * @param {import('express').Request} req - The Express request object. - * @returns {AbortSignal | undefined} The abort signal attached to the request, - * or `undefined` if the property does not exist. - * - * @example - * // In a route handler - * app.get('/data', (req, res) => { - * const signal = getAbortSignal(req); - * fetch('https://api.example.com/data', { signal }) - * .then(response => response.json()) - * .then(data => res.json(data)) - * .catch(err => { - * if (err.name === 'AbortError') { - * res.status(499).end(); - * } else { - * res.status(500).end(); - * } - * }); - * }); - */ -export const getAbortSignal = (req: Request): AbortSignal | undefined => (req as any).signal; diff --git a/src/packages/frameworks/express/express.utils.ts b/src/packages/frameworks/express/express.utils.ts index 02991d8..977f570 100644 --- a/src/packages/frameworks/express/express.utils.ts +++ b/src/packages/frameworks/express/express.utils.ts @@ -21,3 +21,28 @@ import { Request } from 'express'; export const setSignalInExpressRequest = (req: Request, controller: AbortController): void => { (req as any).signal = controller.signal; }; + +/** + * Retrieves the `signal` property from an Express Request object. + * + * @param {import('express').Request} req - The Express request object. + * @returns {AbortSignal | undefined} The abort signal attached to the request, + * or `undefined` if the property does not exist. + * + * @example + * // In a route handler + * app.get('/data', (req, res) => { + * const signal = getAbortSignal(req); + * fetch('https://api.example.com/data', { signal }) + * .then(response => response.json()) + * .then(data => res.json(data)) + * .catch(err => { + * if (err.name === 'AbortError') { + * res.status(499).end(); + * } else { + * res.status(500).end(); + * } + * }); + * }); + */ +export const getAbortSignal = (req: Request): AbortSignal | undefined => (req as any).signal; diff --git a/src/packages/frameworks/express/index.ts b/src/packages/frameworks/express/index.ts index c4fc744..30f6ee8 100644 --- a/src/packages/frameworks/express/index.ts +++ b/src/packages/frameworks/express/index.ts @@ -1 +1,2 @@ export * from './express.service'; +export { getAbortSignal } from './express.utils'; diff --git a/src/shared/utils/get-request-id/get-request-id.util.ts b/src/shared/utils/get-request-id/get-request-id.util.ts index f86f9f4..4ca87fc 100644 --- a/src/shared/utils/get-request-id/get-request-id.util.ts +++ b/src/shared/utils/get-request-id/get-request-id.util.ts @@ -1,7 +1,7 @@ import * as Constants from './get-request-id.constants'; export const getRequestId = (req: T): string => { - const requestId = req.headers[Constants.X_REQUEST_ID_HEADER]?.toString().split(','); + const requestId = req.headers[Constants.X_REQUEST_ID_HEADER]?.toString(); - return requestId?.[0] ?? ''; + return requestId ?? ''; };