packeteer 1.0.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 ADDED
@@ -0,0 +1,99 @@
1
+ # packeteer
2
+
3
+ Minimal HTTP server for Node.js with trie-based routing and built-in JSON body parsing.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install packeteer
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { Packeteer } from 'packeteer'
15
+
16
+ const app = await new Packeteer()
17
+ .get('/health', (_req, res, _ctx) => {
18
+ res.writeHead(200, { 'Content-Type': 'application/json' })
19
+ res.end(JSON.stringify({ status: 'ok' }))
20
+ })
21
+ .post('/echo', (_req, res, ctx) => {
22
+ res.writeHead(200, { 'Content-Type': 'application/json' })
23
+ res.end(JSON.stringify(ctx.get('body')))
24
+ })
25
+ .listen(3000)
26
+
27
+ console.log(app.address())
28
+ ```
29
+
30
+ ## API
31
+
32
+ ### `new Packeteer()`
33
+
34
+ Creates a new server instance. CORS headers (`Access-Control-Allow-*: *`) are set on every response automatically.
35
+
36
+ ### `.get(path, handler)` / `.post` / `.put` / `.delete` / `.patch`
37
+
38
+ Registers a route. Returns `this` for chaining.
39
+
40
+ The handler signature is:
41
+
42
+ ```ts
43
+ (request: IncomingMessage, response: ServerResponse, context: Map<string, unknown>) => Promise<void> | void
44
+ ```
45
+
46
+ `context` contains `body` (parsed JSON) when the request has a body.
47
+
48
+ #### Path parameters
49
+
50
+ Prefix a segment with `:` to capture it by name:
51
+
52
+ ```ts
53
+ app.get('/users/:id', (_req, res, ctx) => {
54
+ res.end(ctx.get('id') as string) // e.g. "42" for /users/42
55
+ })
56
+ ```
57
+
58
+ #### Wildcards
59
+
60
+ End a path with `*` to capture all remaining segments as a single slash-joined string under the key `'wildcard'`:
61
+
62
+ ```ts
63
+ app.get('/files/*', (_req, res, ctx) => {
64
+ res.end(ctx.get('wildcard') as string) // e.g. "images/avatar.png" for /files/images/avatar.png
65
+ })
66
+ ```
67
+
68
+ ### `.maxBodySize(bytes)`
69
+
70
+ Overrides the default 8 MB body size limit. Returns `this` for chaining.
71
+
72
+ ```ts
73
+ new Packeteer().maxBodySize(1024 * 1024) // 1 MB
74
+ ```
75
+
76
+ ### `.listen(port): Promise<this>`
77
+
78
+ Starts the server and resolves when it is ready.
79
+
80
+ ### `.address()`
81
+
82
+ Returns the bound address (delegates to `server.address()`).
83
+
84
+ ### `.close(): Promise<void>`
85
+
86
+ Stops the server.
87
+
88
+ ## Behaviour
89
+
90
+ - JSON bodies are parsed automatically and available as `ctx.get('body')`.
91
+ - Bodies over the size limit (default 8 MB) are rejected with `413`.
92
+ - Malformed JSON is rejected with `400`.
93
+ - `TypeError` / `RangeError` thrown from a handler return `400` with the error message.
94
+ - Any other thrown error returns `500` and is logged to stderr.
95
+ - Unmatched routes return `404`.
96
+
97
+ ## Requirements
98
+
99
+ Node.js 18 or later.
@@ -0,0 +1,19 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+ type Handler = (request: IncomingMessage, response: ServerResponse, context: Map<string, unknown>) => Promise<void> | void;
3
+ export declare class Packeteer {
4
+ private router;
5
+ private server;
6
+ private _maxBodySize;
7
+ maxBodySize(bytes: number): this;
8
+ constructor();
9
+ get(path: string, handler: Handler): this;
10
+ post(path: string, handler: Handler): this;
11
+ put(path: string, handler: Handler): this;
12
+ delete(path: string, handler: Handler): this;
13
+ patch(path: string, handler: Handler): this;
14
+ listen(port: number): Promise<this>;
15
+ address(): string | import("node:net").AddressInfo | null;
16
+ close(): Promise<void>;
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEzE,KAAK,OAAO,GAAG,CACX,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAEzB,qBAAa,SAAS;IAClB,OAAO,CAAC,MAAM,CAAoD;IAClE,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,YAAY,CAAkB;IAEtC,WAAW,CAAC,KAAK,EAAE,MAAM;;IAoEzB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAKlC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAKnC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAKlC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAKrC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAKpC,MAAM,CAAC,IAAI,EAAE,MAAM;IAMnB,OAAO;IAIP,KAAK;CAKR"}
package/dist/index.js ADDED
@@ -0,0 +1,105 @@
1
+ import { TrieRouter } from 'cafe-utility';
2
+ import { createServer } from 'node:http';
3
+ export class Packeteer {
4
+ router = new TrieRouter();
5
+ server = createServer();
6
+ _maxBodySize = 1024 * 1024 * 8;
7
+ maxBodySize(bytes) {
8
+ this._maxBodySize = bytes;
9
+ return this;
10
+ }
11
+ constructor() {
12
+ this.server.on('request', (request, response) => {
13
+ response.setHeader('Access-Control-Allow-Origin', '*');
14
+ response.setHeader('Access-Control-Allow-Headers', '*');
15
+ response.setHeader('Access-Control-Allow-Methods', '*');
16
+ if (request.method === 'OPTIONS') {
17
+ response.writeHead(204);
18
+ response.end();
19
+ return;
20
+ }
21
+ let incomingBodySize = 0;
22
+ const chunks = [];
23
+ request.on('data', chunk => {
24
+ incomingBodySize += chunk.length;
25
+ if (incomingBodySize > this._maxBodySize) {
26
+ response.writeHead(413);
27
+ response.end('Request body too large');
28
+ request.destroy();
29
+ return;
30
+ }
31
+ chunks.push(chunk);
32
+ });
33
+ request.on('end', async () => {
34
+ const context = new Map();
35
+ const raw = Buffer.concat(chunks).toString();
36
+ if (raw) {
37
+ try {
38
+ context.set('body', JSON.parse(raw));
39
+ }
40
+ catch {
41
+ response.writeHead(400);
42
+ response.end('Invalid JSON');
43
+ return;
44
+ }
45
+ }
46
+ try {
47
+ const segments = [
48
+ (request.method || 'get').toLowerCase(),
49
+ ...(request.url || '/').slice(1).split('/')
50
+ ];
51
+ const handled = await this.router.handle(segments, request, response, context);
52
+ if (!handled) {
53
+ response.writeHead(404);
54
+ response.end('Not found');
55
+ }
56
+ }
57
+ catch (error) {
58
+ if (error instanceof TypeError || error instanceof RangeError) {
59
+ response.writeHead(400);
60
+ response.end(error.message);
61
+ }
62
+ else {
63
+ console.error(error);
64
+ response.writeHead(500);
65
+ response.end('Internal server error');
66
+ }
67
+ }
68
+ });
69
+ });
70
+ }
71
+ get(path, handler) {
72
+ this.router.insert(['get', ...path.slice(1).split('/')], handler);
73
+ return this;
74
+ }
75
+ post(path, handler) {
76
+ this.router.insert(['post', ...path.slice(1).split('/')], handler);
77
+ return this;
78
+ }
79
+ put(path, handler) {
80
+ this.router.insert(['put', ...path.slice(1).split('/')], handler);
81
+ return this;
82
+ }
83
+ delete(path, handler) {
84
+ this.router.insert(['delete', ...path.slice(1).split('/')], handler);
85
+ return this;
86
+ }
87
+ patch(path, handler) {
88
+ this.router.insert(['patch', ...path.slice(1).split('/')], handler);
89
+ return this;
90
+ }
91
+ listen(port) {
92
+ return new Promise(resolve => {
93
+ this.server.listen(port, () => resolve(this));
94
+ });
95
+ }
96
+ address() {
97
+ return this.server.address();
98
+ }
99
+ close() {
100
+ return new Promise((resolve, reject) => {
101
+ this.server.close(err => (err ? reject(err) : resolve()));
102
+ });
103
+ }
104
+ }
105
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "packeteer",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "keywords": [],
21
+ "author": "",
22
+ "license": "ISC",
23
+ "sideEffects": false,
24
+ "dependencies": {
25
+ "cafe-utility": "^33.9.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^25.7.0",
29
+ "tsx": "^4.21.0",
30
+ "typescript": "^6.0.3"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "test": "node --import tsx --test test/*.test.ts"
35
+ }
36
+ }