Skip to content

Commit cd20b67

Browse files
authored
Merge pull request #2 from TENSIILE/feature/abortable-service
feat: adds an abortable service
2 parents bfba741 + c4d25af commit cd20b67

File tree

8 files changed

+139
-2
lines changed

8 files changed

+139
-2
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,8 @@ jobs:
2727
- name: Install dependencies
2828
run: npm ci
2929

30+
- name: Run checks
31+
run: npm run verify
32+
3033
- name: Build project
3134
run: npm run build

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules/
22
npm-debug.log*
3-
.DS_Store
3+
.DS_Store
4+
.env

CODE_OF_CONDUCT.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Contributor Covenant Code of Conduct
2+
3+
## Our Pledge
4+
5+
As contributors and maintainers of this project, and in order to keep Aborter community open and welcoming, we ask to respect all community members.
6+
7+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8+
9+
## Our Standards
10+
11+
Examples of behavior that contributes to a positive environment for our community include:
12+
13+
- Using welcoming and inclusive language
14+
- Being respectful of differing viewpoints and experiences
15+
- Gracefully accepting constructive criticism
16+
- Focusing on what is best for the community
17+
- Showing empathy towards other community members
18+
19+
Examples of unacceptable behavior by participants include:
20+
21+
- The use of sexualized language or imagery and unwelcome sexual attention or advances
22+
- Personal attacks
23+
- Trolling or insulting/derogatory comments, and personal or political attacks
24+
- Public or private harassment
25+
- Publishing other's private information, such as physical or electronic addresses, without explicit permission
26+
- Other conduct which could reasonably be considered inappropriate in a professional setting
27+
28+
## Enforcement Responsibilities
29+
30+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
31+
32+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
33+
34+
## Scope
35+
36+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
37+
38+
## Enforcement
39+
40+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting core team members. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
41+
42+
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
43+
44+
## Attribution
45+
46+
This Code of Conduct is adapted from the , version 2.1, available at
47+
48+
[Code of conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html)

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
![Logo](./assets/logo.png)
22

33
<p align='center'>
4+
<a href="https://www.npmjs.com/package/@saborter/server" alt="Npm package">
5+
<img src="https://img.shields.io/npm/v/@saborter/server?color=red&label=npm%20package" /></a>
6+
<a href="https://www.npmjs.com/package/@saborter/server" alt="Npm downloads">
7+
<img src="https://img.shields.io/npm/dm/@saborter/server.svg" /></a>
8+
<a href="https://github.com/TENSIILE/saborter-server/actions/workflows/ci.yml" alt="CI">
9+
<img src="https://github.com/TENSIILE/saborter-server/actions/workflows/ci.yml/badge.svg" /></a>
410
<a href="https://github.com/TENSIILE/saborter-server/blob/develop/LICENSE" alt="License">
511
<img src="https://img.shields.io/badge/license-MIT-blue" /></a>
612
<a href="https://github.com/TENSIILE/saborter-server" alt="Github">
713
<img src="https://img.shields.io/badge/repository-github-color" /></a>
814
</p>
915

16+
**Saborter Server** – A lightweight [Node.js](https://nodejs.org/en) library that automatically cancels server-side operations when the client aborts a request. Built on the standard [AbortController API](https://developer.mozilla.org/en-US/docs/Web/API/AbortController), it seamlessly with [Express](https://expressjs.com/) framework and allows you to stop unnecessary database queries, file writes, or external API calls, saving server resources and improving scalability. Fully tree‑shakeable – only the code you actually use ends up in your bundle.
17+
1018
## 📚 Documentation
1119

1220
The documentation is divided into several sections:

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"scripts": {
66
"start": "node dist/index.js",
77
"build": "tsc",
8-
"dev": "nodemon src/index.ts"
8+
"dev": "nodemon src/index.ts",
9+
"typecheck": "tsc --pretty --noEmit --skipLibCheck",
10+
"verify": "npm run typecheck"
911
},
1012
"keywords": [],
1113
"author": "",

src/packages/index.ts

Whitespace-only changes.

src/service/abortable-service.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Request, Response, NextFunction } from 'express';
2+
3+
type AbortFunction = () => void;
4+
5+
export const getRequestId = (req: Request): string => {
6+
const requestId = req.headers['x-request-id']?.toString().split(',');
7+
return requestId?.[0] ?? '';
8+
};
9+
10+
export class AbortableService {
11+
private abortRegistries = new Map<string, AbortFunction | null>();
12+
13+
private registerAbortableFunction = (
14+
requestId: string,
15+
abortFn: AbortFunction
16+
): void => {
17+
this.abortRegistries.set(requestId, abortFn);
18+
};
19+
20+
public abort = (
21+
requestId: string,
22+
{ deletable = true }: { deletable?: boolean } = {}
23+
): boolean => {
24+
const abortFn = this.abortRegistries.get(requestId) ?? null;
25+
26+
if (abortFn) {
27+
abortFn();
28+
29+
if (deletable) {
30+
this.abortRegistries.delete(requestId);
31+
}
32+
33+
return true;
34+
}
35+
36+
return false;
37+
};
38+
39+
public expressMiddleware = (
40+
req: Request,
41+
res: Response,
42+
next: NextFunction
43+
) => {
44+
const requestId = getRequestId(req);
45+
46+
console.log('MIDDLEWARE', this.abortRegistries);
47+
48+
if (!requestId) {
49+
return next();
50+
}
51+
52+
if (this.abortRegistries.has(requestId)) {
53+
this.abort(requestId, { deletable: false });
54+
}
55+
56+
const controller = new AbortController();
57+
58+
(req as any).controller = controller;
59+
(req as any).signal = controller.signal;
60+
61+
this.registerAbortableFunction(requestId, () => {
62+
controller.abort(new Error('AbortError'));
63+
});
64+
65+
req.on('close', () => {
66+
if (!res.writableEnded) {
67+
console.log('requestId', requestId);
68+
this.abort(requestId);
69+
}
70+
});
71+
72+
next();
73+
};
74+
}

src/service/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './abortable-service';

0 commit comments

Comments
 (0)