@jmealo/fastify-uws 1.0.4

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 ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) Shyam Chen
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,315 @@
1
+ ![fastify-uws](./.github/assets/logo.png)
2
+
3
+ # fastify-uws
4
+
5
+ A performant HTTP and WebSocket server for Fastify with [uWebSockets](https://github.com/uNetworking/uWebSockets.js).
6
+
7
+ ## Installation
8
+
9
+ Install `@jmealo/fastify-uws` with your favorite package manager:
10
+
11
+ ```sh
12
+ $ npm i @jmealo/fastify-uws
13
+ # or
14
+ $ yarn add @jmealo/fastify-uws
15
+ # or
16
+ $ pnpm i @jmealo/fastify-uws
17
+ # or
18
+ $ bun add @jmealo/fastify-uws
19
+ ```
20
+
21
+ ## Supported
22
+
23
+ `fastify-uws` v1.x supports the following versions:
24
+
25
+ - Node.js: v20, v22, and v24
26
+ - `fastify`: v5.x
27
+ - `@fastify/websocket`: v11.x
28
+
29
+ ## Release (CD)
30
+
31
+ GitHub Actions publishes to npm on semver tags (`v*.*.*`) via [cd.yml](./.github/workflows/cd.yml) using npm Trusted Publishing (OIDC).
32
+
33
+ One-time npm setup (no `NPM_TOKEN` secret needed):
34
+
35
+ - npm package: `@jmealo/fastify-uws`
36
+ - npm trusted publisher:
37
+ - Owner: `jmealo`
38
+ - Repository: `jmealo/fastify-uws`
39
+ - Workflow file: `.github/workflows/cd.yml`
40
+ - Environment: leave empty unless you deliberately scope to a GitHub Environment
41
+
42
+ ## Usage
43
+
44
+ Just two lines are needed to speed up your Fastify application.
45
+
46
+ ```ts
47
+ // app.ts
48
+ import fastify from 'fastify';
49
+ import { serverFactory } from '@jmealo/fastify-uws'; // Import here
50
+
51
+ import router from '~/plugins/router';
52
+
53
+ export default () => {
54
+ const app = fastify({
55
+ logger: {
56
+ transport: {
57
+ target: '@fastify/one-line-logger',
58
+ },
59
+ },
60
+ serverFactory, // And use here
61
+ });
62
+
63
+ app.register(router);
64
+
65
+ return app;
66
+ };
67
+ ```
68
+
69
+ ```ts
70
+ // server.ts
71
+ import app from './app';
72
+
73
+ const server = app();
74
+
75
+ const start = async () => {
76
+ try {
77
+ await server.listen({
78
+ host: '127.0.0.1',
79
+ port: 3000,
80
+ });
81
+ } catch (err) {
82
+ server.log.error(err);
83
+ process.exit(1);
84
+ }
85
+ };
86
+
87
+ start();
88
+ ```
89
+
90
+ ### Use [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
91
+
92
+ ```ts
93
+ // src/routes/hello-http/+handler.ts
94
+ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox';
95
+ import { Type } from 'typebox';
96
+
97
+ export default (async (app) => {
98
+ app.get(
99
+ '',
100
+ {
101
+ schema: {
102
+ response: {
103
+ 200: Type.Object({
104
+ message: Type.String(),
105
+ }),
106
+ },
107
+ },
108
+ },
109
+ async (req, reply) => {
110
+ return reply.send({
111
+ message: 'Hello, World!',
112
+ });
113
+ },
114
+ );
115
+ }) as FastifyPluginAsyncTypebox;
116
+ ```
117
+
118
+ #### With [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
119
+
120
+ ```ts
121
+ // app.ts
122
+ import multipart from '@fastify/multipart';
123
+
124
+ app.register(multipart);
125
+ ```
126
+
127
+ ```ts
128
+ // src/routes/hello-fd/+handler.ts
129
+ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox';
130
+ import fs from 'node:fs';
131
+ import { pipeline } from 'node:stream/promises';
132
+
133
+ export default (async (app) => {
134
+ app.post('', async (req, reply) => {
135
+ const data = await req.file();
136
+
137
+ data.file; // stream
138
+ data.fields; // other parsed parts
139
+ data.fieldname;
140
+ data.filename;
141
+ data.encoding;
142
+ data.mimetype;
143
+
144
+ await pipeline(data.file, fs.createWriteStream(data.filename));
145
+ // or
146
+ // await data.toBuffer(); // Buffer
147
+
148
+ return reply.send({ message: 'ok' });
149
+ });
150
+ }) as FastifyPluginAsyncTypebox;
151
+ ```
152
+
153
+ ### Use [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
154
+
155
+ Just a single line of change can speed up your WebSocket application in Fastify.
156
+
157
+ ```diff
158
+ - import websocket from '@fastify/websocket';
159
+ + import { websocket } from '@jmealo/fastify-uws';
160
+ ```
161
+
162
+ ```ts
163
+ // app.ts
164
+ import { websocket } from '@jmealo/fastify-uws';
165
+
166
+ app.register(websocket);
167
+ ```
168
+
169
+ ```ts
170
+ // src/routes/hello-ws/+handler.ts
171
+ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox';
172
+
173
+ export default (async (app) => {
174
+ // $ node client-ws.mjs
175
+ app.get('', { websocket: true }, (socket) => {
176
+ app.log.info('Client connected');
177
+
178
+ socket.on('message', (message: MessageEvent) => {
179
+ console.log(`Client message: ${message}`);
180
+ socket.send('Hello from Fastify!');
181
+ });
182
+
183
+ socket.on('close', () => {
184
+ app.log.info('Client disconnected');
185
+ });
186
+ });
187
+ }) as FastifyPluginAsyncTypebox;
188
+ ```
189
+
190
+ ### Use [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
191
+
192
+ ```ts
193
+ // app.ts
194
+ import sse from '@fastify/sse';
195
+
196
+ app.register(sse);
197
+ ```
198
+
199
+ ```ts
200
+ // src/routes/hello-sse/+handler.ts
201
+ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox';
202
+
203
+ export default (async (app) => {
204
+ // $ node client-es.mjs
205
+ app.get('', { sse: true }, async (request, reply) => {
206
+ app.log.info('Client connected');
207
+
208
+ await reply.sse.send({ data: 'Hello from Fastify!' });
209
+
210
+ request.raw.on('close', () => {
211
+ app.log.info('Client disconnected');
212
+ });
213
+ });
214
+ }) as FastifyPluginAsyncTypebox;
215
+ ```
216
+
217
+ ### TLS/SSL: `http2` and `https`
218
+
219
+ ```ts
220
+ import fs from 'node:fs';
221
+ import fastify from 'fastify';
222
+ import { serverFactory, websocket } from '@jmealo/fastify-uws';
223
+ // [...]
224
+ export default () => {
225
+ const app = fastify({
226
+ http2: true,
227
+ https: {
228
+ key: fs.readFileSync(process.env.HTTPS_KEY),
229
+ cert: fs.readFileSync(process.env.HTTPS_CERT),
230
+ },
231
+ logger: {
232
+ transport: {
233
+ target: '@fastify/one-line-logger',
234
+ },
235
+ },
236
+ serverFactory,
237
+ });
238
+
239
+ app.register(websocket);
240
+ // [...]
241
+ };
242
+ // [...]
243
+ ```
244
+
245
+ ## Benchmarks
246
+
247
+ ### Quick Comparison
248
+
249
+ > Measured with `pnpm bench` — autocannon, 100 connections, 10s duration, pipelining 10
250
+
251
+ #### HTTP — `GET /ping` → `{ ok: true }`
252
+
253
+ | | Req/sec | Latency (mean) | Latency (p99) |
254
+ | :--- | ---: | ---: | ---: |
255
+ | fastify | 93,000 | 10.3 ms | 26 ms |
256
+ | **fastify-uws** | **200,000** | **4.6 ms** | **9 ms** |
257
+ | | **+115%** | **-55%** | **-65%** |
258
+
259
+ #### WebSocket — 100 clients, 64B echo flood, 10s
260
+
261
+ | | Messages/sec |
262
+ | :--- | ---: |
263
+ | @fastify/websocket (ws) | 91,000 |
264
+ | **fastify-uws** | **94,000** |
265
+ | | **+3%** |
266
+
267
+ #### SSE — 100 clients, 64B events, 10s
268
+
269
+ | | Events/sec |
270
+ | :--- | ---: |
271
+ | @fastify/sse (default) | 420,000 |
272
+ | **@fastify/sse + fastify-uws** | **197,000** |
273
+ | | **-53%** |
274
+
275
+ > Results vary by machine. Run `pnpm bench` to reproduce.
276
+
277
+ ### [oha v1.4.5](https://github.com/hatoo/oha)
278
+
279
+ ```sh
280
+ $ oha -c 500 -z 10s --no-tui http://0.0.0.0:3000/api/hello-world
281
+ ```
282
+
283
+ | | Version | Language | Router | Requests/sec |
284
+ | :------------ | ------------: | :-------------- | -----: | -----------: |
285
+ | hyper | 1.4.1 | Rust | ✗ | 56,175.6102 |
286
+ | warp | 0.3.7 | Rust | ✓ | 55,868.5861 |
287
+ | axum | 0.7.7 | Rust | ✓ | 54,588.2828 |
288
+ | bun | 1.1.30 | TypeScript/Bun | ✗ | 54,098.4841 |
289
+ | graphul | 1.0.1 | Rust | ✓ | 53,949.4400 |
290
+ | poem | 3.1.0 | Rust | ✓ | 53,849.0781 |
291
+ | uws | 20.48.0 | JavaScript/Node | ✗ | 52,802.8029 |
292
+ | elysia | 1.1.17 | TypeScript/Bun | ✓ | 52,257.3305 |
293
+ | | | | | ~ 5.5k ~ |
294
+ | hyper-express | 6.17.2 | JavaScript/Node | ✓ | 46,745.4887 |
295
+ | hono | 4.6.3 | TypeScript/Bun | ✓ | 46,685.6014 |
296
+ | nhttp | 2.0.2 | TypeScript/Deno | ✓ | 44,874.2535 |
297
+ | deno | 2.0.0 | TypeScript/Deno | ✗ | 44,753.8552 |
298
+ | hono | 4.6.3 | TypeScript/Deno | ✓ | 43,280.7544 |
299
+ | | | | | ~ 9.2k ~ |
300
+ | h3 | 1.12.0 | TypeScript/Bun | ✓ | 34,043.4693 |
301
+ | fastify-uws | 1.0.0 | JavaScript/Node | ✓ | 31,295.8715 |
302
+ | polka | 1.0.0-next.28 | JavaScript/Node | ✓ | 31,086.5543 |
303
+ | oak | 17.0.0 | TypeScript/Deno | ✓ | 30,730.7971 |
304
+ | node | 20.18.0 | JavaScript/Node | ✗ | 29,230.1719 |
305
+ | oak | 17.0.0 | TypeScript/Bun | ✓ | 27,449.3417 |
306
+ | fastify | 5.0.0 | JavaScript/Node | ✓ | 27,408.6679 |
307
+ | hono | 4.6.3 | JavaScript/Node | ✓ | 25,138.5659 |
308
+ | | | | | ~ 4.9k ~ |
309
+ | h3 | 1.12.0 | JavaScript/Node | ✓ | 20,193.2311 |
310
+ | | | | | ~ 9.2k ~ |
311
+ | express | 4.21.0 | JavaScript/Node | ✓ | 10,949.1532 |
312
+
313
+ ## Credits
314
+
315
+ This project is based on [`@geut/fastify-uws`](https://github.com/geut/fastify-uws) and is licensed under the MIT License.
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("@fastify/error"),R=E("HPE_INVALID_METHOD","Invalid method encountered"),e=E("ERR_HEAD_SET","Cannot set headers after they are sent to the client"),_=E("EADDRINUSE","listen EADDRINUSE: address already in use %s:%s");E("ERR_UPGRADE","Cannot upgrade to WebSocket protocol %o");const D=E("ERR_STREAM_DESTROYED","Stream destroyed");E("ERR_UWS_APP_NOT_FOUND","uWebSockets app not found");const t=E("ERR_ENOTFOUND","getaddrinfo ENOTFOUND %s"),o=E("ERR_SOCKET_BAD_PORT","RangeError [ERR_SOCKET_BAD_PORT]: options.port should be >= 0 and < 65536. Received (%s)"),S=E("ERR_SERVER_DESTROYED","Server destroyed");exports.ERR_ADDRINUSE=_;exports.ERR_ENOTFOUND=t;exports.ERR_HEAD_SET=e;exports.ERR_INVALID_METHOD=R;exports.ERR_SERVER_DESTROYED=S;exports.ERR_SOCKET_BAD_PORT=o;exports.ERR_STREAM_DESTROYED=D;
@@ -0,0 +1,27 @@
1
+ export declare const ERR_INVALID_METHOD: import('@fastify/error').FastifyErrorConstructor<{
2
+ code: "HPE_INVALID_METHOD";
3
+ }, [any?, any?, any?]>;
4
+ export declare const ERR_HEAD_SET: import('@fastify/error').FastifyErrorConstructor<{
5
+ code: "ERR_HEAD_SET";
6
+ }, [any?, any?, any?]>;
7
+ export declare const ERR_ADDRINUSE: import('@fastify/error').FastifyErrorConstructor<{
8
+ code: "EADDRINUSE";
9
+ }, [any?, any?, any?]>;
10
+ export declare const ERR_UPGRADE: import('@fastify/error').FastifyErrorConstructor<{
11
+ code: "ERR_UPGRADE";
12
+ }, [any?, any?, any?]>;
13
+ export declare const ERR_STREAM_DESTROYED: import('@fastify/error').FastifyErrorConstructor<{
14
+ code: "ERR_STREAM_DESTROYED";
15
+ }, [any?, any?, any?]>;
16
+ export declare const ERR_UWS_APP_NOT_FOUND: import('@fastify/error').FastifyErrorConstructor<{
17
+ code: "ERR_UWS_APP_NOT_FOUND";
18
+ }, [any?, any?, any?]>;
19
+ export declare const ERR_ENOTFOUND: import('@fastify/error').FastifyErrorConstructor<{
20
+ code: "ERR_ENOTFOUND";
21
+ }, [any?, any?, any?]>;
22
+ export declare const ERR_SOCKET_BAD_PORT: import('@fastify/error').FastifyErrorConstructor<{
23
+ code: "ERR_SOCKET_BAD_PORT";
24
+ }, [any?, any?, any?]>;
25
+ export declare const ERR_SERVER_DESTROYED: import('@fastify/error').FastifyErrorConstructor<{
26
+ code: "ERR_SERVER_DESTROYED";
27
+ }, [any?, any?, any?]>;
package/dist/errors.js ADDED
@@ -0,0 +1,27 @@
1
+ import E from "@fastify/error";
2
+ const e = E("HPE_INVALID_METHOD", "Invalid method encountered"), o = E(
3
+ "ERR_HEAD_SET",
4
+ "Cannot set headers after they are sent to the client"
5
+ ), t = E(
6
+ "EADDRINUSE",
7
+ "listen EADDRINUSE: address already in use %s:%s"
8
+ );
9
+ E("ERR_UPGRADE", "Cannot upgrade to WebSocket protocol %o");
10
+ const _ = E("ERR_STREAM_DESTROYED", "Stream destroyed");
11
+ E(
12
+ "ERR_UWS_APP_NOT_FOUND",
13
+ "uWebSockets app not found"
14
+ );
15
+ const D = E("ERR_ENOTFOUND", "getaddrinfo ENOTFOUND %s"), r = E(
16
+ "ERR_SOCKET_BAD_PORT",
17
+ "RangeError [ERR_SOCKET_BAD_PORT]: options.port should be >= 0 and < 65536. Received (%s)"
18
+ ), n = E("ERR_SERVER_DESTROYED", "Server destroyed");
19
+ export {
20
+ t as ERR_ADDRINUSE,
21
+ D as ERR_ENOTFOUND,
22
+ o as ERR_HEAD_SET,
23
+ e as ERR_INVALID_METHOD,
24
+ n as ERR_SERVER_DESTROYED,
25
+ r as ERR_SOCKET_BAD_PORT,
26
+ _ as ERR_STREAM_DESTROYED
27
+ };
@@ -0,0 +1,4 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("node:events"),f=require("./errors.cjs"),t=require("./symbols.cjs"),E=Buffer.from([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]),o=(s,e,i)=>s.subarray(e,i).toString("hex"),D=3e4,a=()=>{};function A(){this._clearTimeout(),this.aborted=!0,this._resolveDrain(),this.emit("aborted"),this.errored&&this.emit("error",this.errored),this.emit("close")}function C(s){return this._resolveDrain(),!0}function c(){this.destroyed||(this.emit("timeout"),this.abort())}function m(s,e){if(e.status&&s.writeStatus(e.status),e.headers)for(const i of e.headers.values())if(i.isMultiValue)for(const r of i.value)s.writeHeader(i.name,r);else s.writeHeader(i.name,i.value)}function b(s){return s.empty?0:s.byteLength!==void 0?s.byteLength:Buffer.byteLength(s)}function k(s){return s?.chunk?s.chunk:s}class S extends p.EventEmitter{aborted=!1;writableNeedDrain=!1;bytesRead=0;bytesWritten=0;writableEnded=!1;errored=null;drainTimeout=D;_drainCb=null;_drainTimer=null;_pendingData=null;_pendingCb=a;_boundCorkWrite;_boundCorkEnd;_boundDrainTimeout;[t.kServer];[t.kRes];[t.kWriteOnly];[t.kReadyState]={read:!1,write:!1};[t.kEncoding]=null;[t.kRemoteAddress]=null;[t.kUwsRemoteAddress]=null;[t.kHead]=null;[t.kClientError]=!1;[t.kTimeoutRef];[t.kWs];constructor(e,i,r){super(),this[t.kServer]=e,this[t.kRes]=i,this[t.kWriteOnly]=r,e.drainTimeout&&(this.drainTimeout=e.drainTimeout),this._boundCorkWrite=this._corkWrite.bind(this),this._boundCorkEnd=this._corkEnd.bind(this),this._boundDrainTimeout=this._onDrainTimeout.bind(this),this.once("error",a),i.onAborted(A.bind(this)),i.onWritable(C.bind(this)),e.timeout&&(this[t.kTimeoutRef]=setTimeout(c.bind(this),e.timeout))}get readyState(){const e=this[t.kReadyState];return e.read&&!e.write?"readOnly":!e.read&&e.write?"writeOnly":e.read?"open":"opening"}get writable(){return!0}get readable(){return!0}get encrypted(){return!!this[t.kServer][t.kHttps]}get remoteAddress(){if(this.aborted)return;let e=this[t.kRemoteAddress];if(e)return e;let i=this[t.kUwsRemoteAddress];return i||(i=this[t.kUwsRemoteAddress]=Buffer.copyBytesFrom(new Uint8Array(this[t.kRes].getRemoteAddress()))),i.length===4?e=`${i.readUInt8(0)}.${i.readUInt8(1)}.${i.readUInt8(2)}.${i.readUInt8(3)}`:i.equals(E)?e="::1":e=`${o(i,0,2)}:${o(i,2,4)}:${o(i,4,6)}:${o(i,6,8)}:${o(i,8,10)}:${o(i,10,12)}:${o(i,12,14)}:${o(i,14,i.length)}`,this[t.kRemoteAddress]=e,e}get remoteFamily(){if(!this.aborted)return this[t.kUwsRemoteAddress]||(this[t.kUwsRemoteAddress]=Buffer.copyBytesFrom(new Uint8Array(this[t.kRes].getRemoteAddress()))),this[t.kUwsRemoteAddress].length===4?"IPv4":"IPv6"}get destroyed(){return this.writableEnded||this.aborted}address(){return{...this[t.kServer][t.kAddress]}}abort(){this.aborted||(this.aborted=!0,!this[t.kWs]&&!this.writableEnded&&this[t.kRes].close())}setEncoding(e){this[t.kEncoding]=e}setTimeout(e){this._clearTimeout(),e&&(this[t.kTimeoutRef]=setTimeout(c.bind(this),e))}destroy(e){this.aborted||(this._clearTimeout(),this.errored=e||null,this.abort())}onRead(e){if(this[t.kWriteOnly]||this.aborted)return e(null,null);let i=!1;this[t.kReadyState].read=!0;const r=this[t.kEncoding];try{this[t.kRes].onData((n,h)=>{if(i)return;this.bytesRead+=n.byteLength;const d=r?Buffer.copyBytesFrom(new Uint8Array(n)).toString(r):Buffer.copyBytesFrom(new Uint8Array(n));this.emit("data",d),e(null,d),h&&(i=!0,e(null,null))})}catch(n){i=!0,this.destroy(n),e(n,null)}}end(e,i,r=a){if(this.aborted)throw new f.ERR_STREAM_DESTROYED;this.writableEnded=!0,this._clearTimeout(),this._pendingData=e,this._pendingCb=r,this[t.kRes].cork(this._boundCorkEnd)}write(e,i,r=a){if(this.destroyed)throw new f.ERR_STREAM_DESTROYED;if(this[t.kClientError]&&typeof e=="string"&&e.startsWith("HTTP/")){const[n,h]=e.split(`\r
2
+ \r
3
+ `),[d,..._]=n.split(`\r
4
+ `),[,y,T]=d.split(" "),l=new Map;for(const R of _){const[u,...g]=R.split(": "),w=g.join(": ").trim();u.toLowerCase()!=="content-length"&&l.set(u.toLowerCase(),{name:u,value:w,isMultiValue:!1})}return this[t.kHead]={headers:l,status:`${y} ${T}`},e=h,this.end(e,i,r)}return this[t.kReadyState].write=!0,this._pendingData=e,this._pendingCb=r,this[t.kRes].cork(this._boundCorkWrite),!this.writableNeedDrain}_corkWrite(){const e=this._pendingCb;if(this.aborted)return e();const i=this[t.kRes];this[t.kHead]&&(m(i,this[t.kHead]),this[t.kHead]=null);const r=i.write(k(this._pendingData));if(this.bytesWritten+=b(this._pendingData),r)return e();this.writableNeedDrain=!0,this._drainCb=e,this._drainTimer=setTimeout(this._boundDrainTimeout,this.drainTimeout)}_corkEnd(){const e=this._pendingCb;if(this.aborted)return e();const i=this[t.kRes];this[t.kHead]&&(m(i,this[t.kHead]),this[t.kHead]=null);const r=this._pendingData;i.end(r?k(r):void 0),r&&(this.bytesWritten+=b(r)),this.emit("close"),this.emit("finish"),e()}_onDrainTimeout(){this._drainTimer=null;const e=this._drainCb;this._drainCb=null,this.destroy(new Error("drain timeout")),e&&e()}_resolveDrain(){this._drainTimer&&(clearTimeout(this._drainTimer),this._drainTimer=null);const e=this._drainCb;e&&(this._drainCb=null,this.writableNeedDrain=!1,e())}_clearTimeout(){this[t.kTimeoutRef]&&clearTimeout(this[t.kTimeoutRef])}}exports.HTTPSocket=S;
@@ -0,0 +1,67 @@
1
+ import { default as uws } from 'uWebSockets.js';
2
+ import { EventEmitter } from 'node:events';
3
+ import { Server } from './server';
4
+ import { kClientError, kEncoding, kHead, kReadyState, kRemoteAddress, kRes, kServer, kTimeoutRef, kUwsRemoteAddress, kWriteOnly, kWs } from './symbols';
5
+ export interface UwsHead {
6
+ status?: string;
7
+ headers?: Map<string, {
8
+ name: string;
9
+ value: any;
10
+ isMultiValue: boolean;
11
+ }>;
12
+ }
13
+ export declare class HTTPSocket extends EventEmitter {
14
+ aborted: boolean;
15
+ writableNeedDrain: boolean;
16
+ bytesRead: number;
17
+ bytesWritten: number;
18
+ writableEnded: boolean;
19
+ errored: Error | null;
20
+ drainTimeout: number;
21
+ _drainCb: (() => void) | null;
22
+ _drainTimer: NodeJS.Timeout | null;
23
+ _pendingData: any;
24
+ _pendingCb: () => void;
25
+ _boundCorkWrite: () => void;
26
+ _boundCorkEnd: () => void;
27
+ _boundDrainTimeout: () => void;
28
+ [kServer]: Server;
29
+ [kRes]: uws.HttpResponse;
30
+ [kWriteOnly]: boolean;
31
+ [kReadyState]: {
32
+ read: boolean;
33
+ write: boolean;
34
+ };
35
+ [kEncoding]: string | null;
36
+ [kRemoteAddress]: string | null;
37
+ [kUwsRemoteAddress]: Buffer | null;
38
+ [kHead]: UwsHead | null;
39
+ [kClientError]: boolean;
40
+ [kTimeoutRef]?: NodeJS.Timeout;
41
+ [kWs]?: boolean;
42
+ constructor(server: Server, res: uws.HttpResponse, writeOnly: boolean);
43
+ get readyState(): "open" | "readOnly" | "writeOnly" | "opening";
44
+ get writable(): boolean;
45
+ get readable(): boolean;
46
+ get encrypted(): boolean;
47
+ get remoteAddress(): string | undefined;
48
+ get remoteFamily(): "IPv6" | "IPv4" | undefined;
49
+ get destroyed(): boolean;
50
+ address(): {
51
+ address?: string | undefined;
52
+ family?: string | undefined;
53
+ port?: number | undefined;
54
+ };
55
+ abort(): void;
56
+ setEncoding(encoding: string): void;
57
+ setTimeout(timeout: number): void;
58
+ destroy(err?: Error): void;
59
+ onRead(cb: (err: Error | null, chunk: Buffer | string | null) => void): void;
60
+ end(data: any, _?: any, cb?: () => void): void;
61
+ write(data: any, _?: any, cb?: () => void): boolean | void;
62
+ _corkWrite(): void;
63
+ _corkEnd(): void;
64
+ _onDrainTimeout(): void;
65
+ _resolveDrain(): void;
66
+ _clearTimeout(): void;
67
+ }
@@ -0,0 +1,174 @@
1
+ import { EventEmitter as S } from "node:events";
2
+ import { ERR_STREAM_DESTROYED as p } from "./errors.js";
3
+ import { kServer as l, kRes as n, kWriteOnly as _, kReadyState as f, kEncoding as y, kRemoteAddress as T, kUwsRemoteAddress as d, kHead as o, kClientError as w, kTimeoutRef as a, kWs as k, kHttps as I, kAddress as L } from "./symbols.js";
4
+ const H = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]), h = (r, t, e) => r.subarray(t, e).toString("hex"), M = 3e4, b = () => {
5
+ };
6
+ function F() {
7
+ this._clearTimeout(), this.aborted = !0, this._resolveDrain(), this.emit("aborted"), this.errored && this.emit("error", this.errored), this.emit("close");
8
+ }
9
+ function x(r) {
10
+ return this._resolveDrain(), !0;
11
+ }
12
+ function D() {
13
+ this.destroyed || (this.emit("timeout"), this.abort());
14
+ }
15
+ function C(r, t) {
16
+ if (t.status && r.writeStatus(t.status), t.headers)
17
+ for (const e of t.headers.values())
18
+ if (e.isMultiValue)
19
+ for (const i of e.value)
20
+ r.writeHeader(e.name, i);
21
+ else
22
+ r.writeHeader(e.name, e.value);
23
+ }
24
+ function E(r) {
25
+ return r.empty ? 0 : r.byteLength !== void 0 ? r.byteLength : Buffer.byteLength(r);
26
+ }
27
+ function A(r) {
28
+ return r?.chunk ? r.chunk : r;
29
+ }
30
+ class V extends S {
31
+ aborted = !1;
32
+ writableNeedDrain = !1;
33
+ bytesRead = 0;
34
+ bytesWritten = 0;
35
+ writableEnded = !1;
36
+ errored = null;
37
+ drainTimeout = M;
38
+ _drainCb = null;
39
+ _drainTimer = null;
40
+ _pendingData = null;
41
+ _pendingCb = b;
42
+ _boundCorkWrite;
43
+ _boundCorkEnd;
44
+ _boundDrainTimeout;
45
+ [l];
46
+ [n];
47
+ [_];
48
+ [f] = { read: !1, write: !1 };
49
+ [y] = null;
50
+ [T] = null;
51
+ [d] = null;
52
+ [o] = null;
53
+ [w] = !1;
54
+ [a];
55
+ [k];
56
+ constructor(t, e, i) {
57
+ super(), this[l] = t, this[n] = e, this[_] = i, t.drainTimeout && (this.drainTimeout = t.drainTimeout), this._boundCorkWrite = this._corkWrite.bind(this), this._boundCorkEnd = this._corkEnd.bind(this), this._boundDrainTimeout = this._onDrainTimeout.bind(this), this.once("error", b), e.onAborted(F.bind(this)), e.onWritable(x.bind(this)), t.timeout && (this[a] = setTimeout(D.bind(this), t.timeout));
58
+ }
59
+ get readyState() {
60
+ const t = this[f];
61
+ return t.read && !t.write ? "readOnly" : !t.read && t.write ? "writeOnly" : t.read ? "open" : "opening";
62
+ }
63
+ get writable() {
64
+ return !0;
65
+ }
66
+ get readable() {
67
+ return !0;
68
+ }
69
+ get encrypted() {
70
+ return !!this[l][I];
71
+ }
72
+ get remoteAddress() {
73
+ if (this.aborted) return;
74
+ let t = this[T];
75
+ if (t) return t;
76
+ let e = this[d];
77
+ return e || (e = this[d] = Buffer.copyBytesFrom(
78
+ new Uint8Array(this[n].getRemoteAddress())
79
+ )), e.length === 4 ? t = `${e.readUInt8(0)}.${e.readUInt8(1)}.${e.readUInt8(2)}.${e.readUInt8(3)}` : e.equals(H) ? t = "::1" : t = `${h(e, 0, 2)}:${h(e, 2, 4)}:${h(e, 4, 6)}:${h(e, 6, 8)}:${h(e, 8, 10)}:${h(e, 10, 12)}:${h(e, 12, 14)}:${h(e, 14, e.length)}`, this[T] = t, t;
80
+ }
81
+ get remoteFamily() {
82
+ if (!this.aborted)
83
+ return this[d] || (this[d] = Buffer.copyBytesFrom(new Uint8Array(this[n].getRemoteAddress()))), this[d].length === 4 ? "IPv4" : "IPv6";
84
+ }
85
+ get destroyed() {
86
+ return this.writableEnded || this.aborted;
87
+ }
88
+ address() {
89
+ return { ...this[l][L] };
90
+ }
91
+ abort() {
92
+ this.aborted || (this.aborted = !0, !this[k] && !this.writableEnded && this[n].close());
93
+ }
94
+ setEncoding(t) {
95
+ this[y] = t;
96
+ }
97
+ setTimeout(t) {
98
+ this._clearTimeout(), t && (this[a] = setTimeout(D.bind(this), t));
99
+ }
100
+ destroy(t) {
101
+ this.aborted || (this._clearTimeout(), this.errored = t || null, this.abort());
102
+ }
103
+ onRead(t) {
104
+ if (this[_] || this.aborted) return t(null, null);
105
+ let e = !1;
106
+ this[f].read = !0;
107
+ const i = this[y];
108
+ try {
109
+ this[n].onData((s, c) => {
110
+ if (e) return;
111
+ this.bytesRead += s.byteLength;
112
+ const u = i ? Buffer.copyBytesFrom(new Uint8Array(s)).toString(i) : Buffer.copyBytesFrom(new Uint8Array(s));
113
+ this.emit("data", u), t(null, u), c && (e = !0, t(null, null));
114
+ });
115
+ } catch (s) {
116
+ e = !0, this.destroy(s), t(s, null);
117
+ }
118
+ }
119
+ end(t, e, i = b) {
120
+ if (this.aborted) throw new p();
121
+ this.writableEnded = !0, this._clearTimeout(), this._pendingData = t, this._pendingCb = i, this[n].cork(this._boundCorkEnd);
122
+ }
123
+ write(t, e, i = b) {
124
+ if (this.destroyed) throw new p();
125
+ if (this[w] && typeof t == "string" && t.startsWith("HTTP/")) {
126
+ const [s, c] = t.split(`\r
127
+ \r
128
+ `), [u, ...R] = s.split(`\r
129
+ `), [, v, $] = u.split(" "), g = /* @__PURE__ */ new Map();
130
+ for (const W of R) {
131
+ const [m, ...U] = W.split(": "), B = U.join(": ").trim();
132
+ m.toLowerCase() !== "content-length" && g.set(m.toLowerCase(), { name: m, value: B, isMultiValue: !1 });
133
+ }
134
+ return this[o] = {
135
+ headers: g,
136
+ status: `${v} ${$}`
137
+ }, t = c, this.end(t, e, i);
138
+ }
139
+ return this[f].write = !0, this._pendingData = t, this._pendingCb = i, this[n].cork(this._boundCorkWrite), !this.writableNeedDrain;
140
+ }
141
+ _corkWrite() {
142
+ const t = this._pendingCb;
143
+ if (this.aborted) return t();
144
+ const e = this[n];
145
+ this[o] && (C(e, this[o]), this[o] = null);
146
+ const i = e.write(A(this._pendingData));
147
+ if (this.bytesWritten += E(this._pendingData), i) return t();
148
+ this.writableNeedDrain = !0, this._drainCb = t, this._drainTimer = setTimeout(this._boundDrainTimeout, this.drainTimeout);
149
+ }
150
+ _corkEnd() {
151
+ const t = this._pendingCb;
152
+ if (this.aborted) return t();
153
+ const e = this[n];
154
+ this[o] && (C(e, this[o]), this[o] = null);
155
+ const i = this._pendingData;
156
+ e.end(i ? A(i) : void 0), i && (this.bytesWritten += E(i)), this.emit("close"), this.emit("finish"), t();
157
+ }
158
+ _onDrainTimeout() {
159
+ this._drainTimer = null;
160
+ const t = this._drainCb;
161
+ this._drainCb = null, this.destroy(new Error("drain timeout")), t && t();
162
+ }
163
+ _resolveDrain() {
164
+ this._drainTimer && (clearTimeout(this._drainTimer), this._drainTimer = null);
165
+ const t = this._drainCb;
166
+ t && (this._drainCb = null, this.writableNeedDrain = !1, t());
167
+ }
168
+ _clearTimeout() {
169
+ this[a] && clearTimeout(this[a]);
170
+ }
171
+ }
172
+ export {
173
+ V as HTTPSocket
174
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./server.cjs"),o=require("./request.cjs"),c=require("./response.cjs"),e=require("./websocket-server.cjs"),u=require("./plugin-sse.cjs"),S=require("./plugin-websocket.cjs"),n=(t,s)=>new r.Server(t,s);exports.Server=r.Server;exports.Request=o.Request;exports.Response=c.Response;exports.WebSocket=e.WebSocket;exports.WebSocketServer=e.WebSocketServer;exports.WebSocketStream=e.WebSocketStream;exports.sse=u.default;exports.websocket=S.default;exports.serverFactory=n;