@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.
@@ -0,0 +1,47 @@
1
+ import { ContextConfigDefault, FastifyBaseLogger, FastifyInstance, FastifyRequest, FastifyReply, FastifySchema, FastifyServerFactory, FastifyTypeProvider, FastifyTypeProviderDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestGenericInterface } from 'fastify';
2
+ import { SSEContext } from './plugin-sse';
3
+ import { Server } from './server';
4
+ import { Request } from './request';
5
+ import { Response } from './response';
6
+ import { WebSocket as UwsWebSocket, WebSocketServer, WebSocketStream } from './websocket-server';
7
+ export type UwsServer = RawServerDefault & Server;
8
+ export type UwsRequest = Request;
9
+ export type UwsResponse = Response;
10
+ export type FastifyUwsInstance<TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger> = FastifyInstance<any, any, any, Logger, TypeProvider>;
11
+ export type FastifyUwsRequest<RequestGeneric extends RequestGenericInterface = RequestGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ContextConfig = ContextConfigDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger> = FastifyRequest<RequestGeneric, any, any, SchemaCompiler, TypeProvider, ContextConfig, Logger>;
12
+ export type FastifyUwsReply<RequestGeneric extends RequestGenericInterface = RequestGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ContextConfig = ContextConfigDefault> = FastifyReply<RequestGeneric, any, any, any, ContextConfig, SchemaCompiler, TypeProvider>;
13
+ export declare const serverFactory: FastifyServerFactory<any>;
14
+ export type { SSEContext } from './plugin-sse';
15
+ export { default as sse } from './plugin-sse';
16
+ export { default as websocket } from './plugin-websocket';
17
+ export { UwsWebSocket as WebSocket, WebSocketServer, WebSocketStream, Server, Request, Response };
18
+ declare module 'fastify' {
19
+ interface FastifyReply {
20
+ sse: SSEContext;
21
+ }
22
+ interface RouteOptions {
23
+ websocket?: boolean;
24
+ sse?: boolean | {
25
+ heartbeat?: boolean;
26
+ serializer?: (data: any) => string;
27
+ };
28
+ ws?: {
29
+ topics?: string[];
30
+ };
31
+ }
32
+ interface RouteShorthandOptions<RawServer extends RawServerBase = RawServerDefault> {
33
+ websocket?: boolean;
34
+ sse?: boolean | {
35
+ heartbeat?: boolean;
36
+ serializer?: (data: any) => string;
37
+ };
38
+ ws?: {
39
+ topics?: string[];
40
+ };
41
+ }
42
+ interface RouteShorthandMethod<RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>, RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger> {
43
+ <RequestGeneric extends RequestGenericInterface = RequestGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger>(path: string, opts: RouteShorthandOptions<RawServer, RawRequest, RawReply, RequestGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger> & {
44
+ websocket: true;
45
+ }, handler?: (socket: UwsWebSocket, req: FastifyRequest<RequestGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider, ContextConfig, Logger>) => void | Promise<any>): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
46
+ }
47
+ }
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import { Server as o } from "./server.js";
2
+ import { Request as p } from "./request.js";
3
+ import { Response as c } from "./response.js";
4
+ import { WebSocket as S, WebSocketServer as b, WebSocketStream as k } from "./websocket-server.js";
5
+ import { default as u } from "./plugin-sse.js";
6
+ import { default as W } from "./plugin-websocket.js";
7
+ const s = (e, r) => new o(e, r);
8
+ export {
9
+ p as Request,
10
+ c as Response,
11
+ o as Server,
12
+ S as WebSocket,
13
+ b as WebSocketServer,
14
+ k as WebSocketStream,
15
+ s as serverFactory,
16
+ u as sse,
17
+ W as websocket
18
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const E=require("node:stream"),y=require("fastify-plugin"),c=require("./symbols.cjs"),k=3e4,p=32,H=1024*1024;function d(i){return String(i).replace(/[\r\n]/g,"")}const S=/\r\n|\r|\n/;function f(i,t){let e="";if(typeof i=="string")for(const s of i.split(S))e+=`data: ${s}
2
+ `;else if(Buffer.isBuffer(i)){const s=i.toString("utf-8");for(const r of s.split(S))e+=`data: ${r}
3
+ `}else{if(i.id&&(e+=`id: ${d(i.id)}
4
+ `),i.event&&(e+=`event: ${d(i.event)}
5
+ `),i.data!==void 0){const s=t(i.data);for(const r of s.split(S))e+=`data: ${r}
6
+ `}i.retry&&(e+=`retry: ${d(String(i.retry))}
7
+ `)}return e&&(e+=`
8
+ `),e}const T=[{name:"Content-Type",value:"text/event-stream",isMultiValue:!1},{name:"Cache-Control",value:"no-cache",isMultiValue:!1},{name:"Connection",value:"keep-alive",isMultiValue:!1},{name:"X-Accel-Buffering",value:"no",isMultiValue:!1}];function m(i){return typeof i=="object"&&i!==null&&"data"in i}function C(i){return i!=null&&typeof i[Symbol.asyncIterator]=="function"}function I(i,t){if(t.status&&i.writeStatus(t.status),t.headers)for(const e of t.headers.values())if(e.isMultiValue)for(const s of e.value)i.writeHeader(e.name,s);else i.writeHeader(e.name,e.value)}class v{reply;serializer;heartbeatTimer=null;closeCallbacks=[];#e;#a;#t=!0;#c=!1;#f=!1;#o;#r=null;#i="";#l=0;#h=!1;#n=null;#u;#d=!0;constructor(t){this.reply=t.reply,this.#e=t.socket,this.#a=t.socket[c.kRes],this.#o=t.lastEventId||null,this.serializer=t.serializer,this.#u=this._corkFlush.bind(this);const e=()=>{if(!this.#t)return;this.#t=!1,this.#n=null;const s=this.#r;this.#r=null,s&&s(),this.cleanup()};t.socket.once("close",e),t.socket.once("aborted",e),t.heartbeatInterval>0&&this.startHeartbeat(t.heartbeatInterval)}get lastEventId(){return this.#o}get isConnected(){return this.#t}get shouldKeepAlive(){return this.#c}keepAlive(){this.#c=!0}flush(){this.#i&&this.#t&&this.#s()}close(){if(this.#t&&(this.#i&&this.#s(),this.#t=!1,this.cleanup(),!this.#e.destroyed&&!this.#e.writableEnded))try{this.#e.end(void 0,void 0)}catch{}}onClose(t){this.closeCallbacks.push(t)}async replay(t){this.#o&&await t(this.#o)}sendHeaders(){if(this.#f)return;this.#f=!0;const t=new Map;for(const e of T)t.set(e.name.toLowerCase(),e);try{const e=this.reply.getHeaders();for(const[s,r]of Object.entries(e)){const n=s.toLowerCase();t.has(n)||t.set(n,{name:s,value:String(r),isMultiValue:!1})}}catch{}this.#e[c.kHead]={status:"200 OK",headers:t}}async send(t){if(!this.#t)throw new Error("SSE connection is closed");if(typeof t=="string"||Buffer.isBuffer(t)||m(t)){const e=f(t,this.serializer);return this.writeToSocket(e)}if(t instanceof E.Readable){try{for await(const e of t){if(!this.#t){t.destroy();break}const s=f(e,this.serializer);await this.writeToSocket(s)}this.#i&&this.#t&&this.#s()}catch(e){this.#i&&this.#t&&this.#s(),this.#t=!1,this.cleanup(),e?.code!=="ECONNRESET"&&e?.code!=="EPIPE"&&this.reply.log?.error?.({err:e},"Unexpected error in SSE stream")}return}if(C(t)){try{for await(const e of t){if(!this.#t)break;const s=f(e,this.serializer);await this.writeToSocket(s)}this.#i&&this.#t&&this.#s()}catch(e){this.#i&&this.#t&&this.#s(),this.#t=!1,this.cleanup(),e?.code!=="ECONNRESET"&&e?.code!=="EPIPE"&&this.reply.log?.error?.({err:e},"Unexpected error in SSE async iterable")}return}throw new TypeError("Invalid SSE source type")}stream(){if(!this.#t)throw new Error("SSE connection is closed");const t=this;return new E.Writable({objectMode:!0,write(e,s,r){if(!t.#t){r(new Error("SSE connection is closed"));return}try{const n=f(e,t.serializer),o=t.writeToSocket(n);o===void 0?r():o.then(()=>r(),r)}catch(n){r(n)}}})}writeToSocket(t){if(!this.#t)throw new Error("SSE connection is closed");if(this.sendHeaders(),this.#h&&this.#i.length+t.length>H)throw this.close(),new Error("SSE buffer overflow: client too slow");if(this.#i+=t,this.#l++,this.#h)return this.#n;if(this.#l>=p)return this.#s()}#s(){if(!(!this.#i||!this.#t)&&(this.#a.cork(this.#u),this.#i="",this.#l=0,!this.#d))return this.#h=!0,this.#n=new Promise(t=>{this.#r=t,this.#e.writableNeedDrain=!0,this.#e._drainCb=()=>{this.#e.writableNeedDrain=!1,this.#h=!1,this.#n=null;const e=this.#r;this.#r=null,e&&e()},this.#e._drainTimer=setTimeout(this.#e._boundDrainTimeout,k)}),this.#n}_corkFlush(){const t=this.#e[c.kHead];t&&(I(this.#a,t),this.#e[c.kHead]=null),this.#d=this.#a.write(this.#i)}startHeartbeat(t){this.heartbeatTimer=setInterval(()=>{this.#t?(this.sendHeaders(),this.#i+=`: heartbeat
9
+
10
+ `,this.#l++,this.#s()):this.stopHeartbeat()},t),this.heartbeatTimer.unref()}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}cleanup(){this.stopHeartbeat();for(const t of this.closeCallbacks)try{t()}catch{}this.closeCallbacks=[]}}function _(i,t,e){const{heartbeatInterval:s=3e4,serializer:r=JSON.stringify}=t;i.addHook("onRoute",n=>{if(!n.sse)return;const o=n.handler,b=typeof n.sse=="object"?n.sse:{};n.handler=async function(l,h){if(!(l.headers.accept||"").includes("text/event-stream"))return o.call(this,l,h);h.hijack();const u=l.raw.socket;if(u.aborted||u.destroyed)return;const a=new v({reply:h,socket:u,lastEventId:l.headers["last-event-id"]||null,heartbeatInterval:b.heartbeat!==!1?s:0,serializer:b.serializer||r});h.sse=a;try{await o.call(this,l,h)}catch(w){throw a.shouldKeepAlive||a.close(),w}a.flush(),a.shouldKeepAlive||a.close()}}),e()}const M=y(_,{fastify:"5.x",name:"@fastify/sse"});exports.SSEContext=v;exports.default=M;
@@ -0,0 +1,50 @@
1
+ import { Writable } from 'node:stream';
2
+ import { HTTPSocket } from './http-socket';
3
+ declare class SSEContext {
4
+ #private;
5
+ reply: any;
6
+ serializer: (data: any) => string;
7
+ heartbeatTimer: ReturnType<typeof setInterval> | null;
8
+ closeCallbacks: (() => void)[];
9
+ constructor(opts: {
10
+ reply: any;
11
+ socket: HTTPSocket;
12
+ lastEventId: string | null;
13
+ heartbeatInterval: number;
14
+ serializer: (data: any) => string;
15
+ });
16
+ get lastEventId(): string | null;
17
+ get isConnected(): boolean;
18
+ get shouldKeepAlive(): boolean;
19
+ keepAlive(): void;
20
+ flush(): void;
21
+ close(): void;
22
+ onClose(callback: () => void): void;
23
+ replay(callback: (lastEventId: string) => Promise<void>): Promise<void>;
24
+ sendHeaders(): void;
25
+ send(source: any): Promise<void>;
26
+ stream(): Writable;
27
+ /**
28
+ * Write-combining SSE writer.
29
+ *
30
+ * Events are accumulated in a string buffer. When the buffer reaches
31
+ * FLUSH_BATCH_SIZE events, all events are flushed in a single corked write
32
+ * to the uWS HttpResponse. This reduces C++ boundary crossings from
33
+ * one-per-event to one-per-batch.
34
+ *
35
+ * If there's active backpressure from a previous flush, the send() caller
36
+ * receives a Promise that resolves when the drain completes.
37
+ */
38
+ writeToSocket(data: string): Promise<void> | undefined;
39
+ /**
40
+ * Pre-bound cork callback. Writes headers (first call only) + buffered data.
41
+ */
42
+ _corkFlush(): void;
43
+ startHeartbeat(interval: number): void;
44
+ stopHeartbeat(): void;
45
+ cleanup(): void;
46
+ }
47
+ declare function fastifySSE(fastify: any, opts: any, next: (err?: Error) => void): void;
48
+ export { SSEContext };
49
+ declare const _default: typeof fastifySSE;
50
+ export default _default;
@@ -0,0 +1,285 @@
1
+ import { Readable as w, Writable as v } from "node:stream";
2
+ import y from "fastify-plugin";
3
+ import { kRes as p, kHead as u } from "./symbols.js";
4
+ const k = 3e4, m = 32, T = 1024 * 1024;
5
+ function d(i) {
6
+ return String(i).replace(/[\r\n]/g, "");
7
+ }
8
+ const S = /\r\n|\r|\n/;
9
+ function c(i, t) {
10
+ let e = "";
11
+ if (typeof i == "string")
12
+ for (const s of i.split(S))
13
+ e += `data: ${s}
14
+ `;
15
+ else if (Buffer.isBuffer(i)) {
16
+ const s = i.toString("utf-8");
17
+ for (const r of s.split(S))
18
+ e += `data: ${r}
19
+ `;
20
+ } else {
21
+ if (i.id && (e += `id: ${d(i.id)}
22
+ `), i.event && (e += `event: ${d(i.event)}
23
+ `), i.data !== void 0) {
24
+ const s = t(i.data);
25
+ for (const r of s.split(S))
26
+ e += `data: ${r}
27
+ `;
28
+ }
29
+ i.retry && (e += `retry: ${d(String(i.retry))}
30
+ `);
31
+ }
32
+ return e && (e += `
33
+ `), e;
34
+ }
35
+ const H = [
36
+ { name: "Content-Type", value: "text/event-stream", isMultiValue: !1 },
37
+ { name: "Cache-Control", value: "no-cache", isMultiValue: !1 },
38
+ { name: "Connection", value: "keep-alive", isMultiValue: !1 },
39
+ { name: "X-Accel-Buffering", value: "no", isMultiValue: !1 }
40
+ ];
41
+ function I(i) {
42
+ return typeof i == "object" && i !== null && "data" in i;
43
+ }
44
+ function C(i) {
45
+ return i != null && typeof i[Symbol.asyncIterator] == "function";
46
+ }
47
+ function _(i, t) {
48
+ if (t.status && i.writeStatus(t.status), t.headers)
49
+ for (const e of t.headers.values())
50
+ if (e.isMultiValue)
51
+ for (const s of e.value)
52
+ i.writeHeader(e.name, s);
53
+ else
54
+ i.writeHeader(e.name, e.value);
55
+ }
56
+ class M {
57
+ reply;
58
+ serializer;
59
+ heartbeatTimer = null;
60
+ closeCallbacks = [];
61
+ #e;
62
+ #a;
63
+ #t = !0;
64
+ #c = !1;
65
+ #f = !1;
66
+ #o;
67
+ #r = null;
68
+ // Write-combining buffer: accumulate events, flush when batch is full
69
+ #i = "";
70
+ #h = 0;
71
+ #l = !1;
72
+ #n = null;
73
+ // Pre-bound methods
74
+ #u;
75
+ // Cork state
76
+ #d = !0;
77
+ constructor(t) {
78
+ this.reply = t.reply, this.#e = t.socket, this.#a = t.socket[p], this.#o = t.lastEventId || null, this.serializer = t.serializer, this.#u = this._corkFlush.bind(this);
79
+ const e = () => {
80
+ if (!this.#t) return;
81
+ this.#t = !1, this.#n = null;
82
+ const s = this.#r;
83
+ this.#r = null, s && s(), this.cleanup();
84
+ };
85
+ t.socket.once("close", e), t.socket.once("aborted", e), t.heartbeatInterval > 0 && this.startHeartbeat(t.heartbeatInterval);
86
+ }
87
+ get lastEventId() {
88
+ return this.#o;
89
+ }
90
+ get isConnected() {
91
+ return this.#t;
92
+ }
93
+ get shouldKeepAlive() {
94
+ return this.#c;
95
+ }
96
+ keepAlive() {
97
+ this.#c = !0;
98
+ }
99
+ flush() {
100
+ this.#i && this.#t && this.#s();
101
+ }
102
+ close() {
103
+ if (this.#t && (this.#i && this.#s(), this.#t = !1, this.cleanup(), !this.#e.destroyed && !this.#e.writableEnded))
104
+ try {
105
+ this.#e.end(void 0, void 0);
106
+ } catch {
107
+ }
108
+ }
109
+ onClose(t) {
110
+ this.closeCallbacks.push(t);
111
+ }
112
+ async replay(t) {
113
+ this.#o && await t(this.#o);
114
+ }
115
+ sendHeaders() {
116
+ if (this.#f) return;
117
+ this.#f = !0;
118
+ const t = /* @__PURE__ */ new Map();
119
+ for (const e of H)
120
+ t.set(e.name.toLowerCase(), e);
121
+ try {
122
+ const e = this.reply.getHeaders();
123
+ for (const [s, r] of Object.entries(e)) {
124
+ const n = s.toLowerCase();
125
+ t.has(n) || t.set(n, { name: s, value: String(r), isMultiValue: !1 });
126
+ }
127
+ } catch {
128
+ }
129
+ this.#e[u] = {
130
+ status: "200 OK",
131
+ headers: t
132
+ };
133
+ }
134
+ async send(t) {
135
+ if (!this.#t) throw new Error("SSE connection is closed");
136
+ if (typeof t == "string" || Buffer.isBuffer(t) || I(t)) {
137
+ const e = c(t, this.serializer);
138
+ return this.writeToSocket(e);
139
+ }
140
+ if (t instanceof w) {
141
+ try {
142
+ for await (const e of t) {
143
+ if (!this.#t) {
144
+ t.destroy();
145
+ break;
146
+ }
147
+ const s = c(e, this.serializer);
148
+ await this.writeToSocket(s);
149
+ }
150
+ this.#i && this.#t && this.#s();
151
+ } catch (e) {
152
+ this.#i && this.#t && this.#s(), this.#t = !1, this.cleanup(), e?.code !== "ECONNRESET" && e?.code !== "EPIPE" && this.reply.log?.error?.({ err: e }, "Unexpected error in SSE stream");
153
+ }
154
+ return;
155
+ }
156
+ if (C(t)) {
157
+ try {
158
+ for await (const e of t) {
159
+ if (!this.#t) break;
160
+ const s = c(e, this.serializer);
161
+ await this.writeToSocket(s);
162
+ }
163
+ this.#i && this.#t && this.#s();
164
+ } catch (e) {
165
+ this.#i && this.#t && this.#s(), this.#t = !1, this.cleanup(), e?.code !== "ECONNRESET" && e?.code !== "EPIPE" && this.reply.log?.error?.({ err: e }, "Unexpected error in SSE async iterable");
166
+ }
167
+ return;
168
+ }
169
+ throw new TypeError("Invalid SSE source type");
170
+ }
171
+ stream() {
172
+ if (!this.#t) throw new Error("SSE connection is closed");
173
+ const t = this;
174
+ return new v({
175
+ objectMode: !0,
176
+ write(e, s, r) {
177
+ if (!t.#t) {
178
+ r(new Error("SSE connection is closed"));
179
+ return;
180
+ }
181
+ try {
182
+ const n = c(e, t.serializer), o = t.writeToSocket(n);
183
+ o === void 0 ? r() : o.then(() => r(), r);
184
+ } catch (n) {
185
+ r(n);
186
+ }
187
+ }
188
+ });
189
+ }
190
+ /**
191
+ * Write-combining SSE writer.
192
+ *
193
+ * Events are accumulated in a string buffer. When the buffer reaches
194
+ * FLUSH_BATCH_SIZE events, all events are flushed in a single corked write
195
+ * to the uWS HttpResponse. This reduces C++ boundary crossings from
196
+ * one-per-event to one-per-batch.
197
+ *
198
+ * If there's active backpressure from a previous flush, the send() caller
199
+ * receives a Promise that resolves when the drain completes.
200
+ */
201
+ writeToSocket(t) {
202
+ if (!this.#t) throw new Error("SSE connection is closed");
203
+ if (this.sendHeaders(), this.#l && this.#i.length + t.length > T)
204
+ throw this.close(), new Error("SSE buffer overflow: client too slow");
205
+ if (this.#i += t, this.#h++, this.#l)
206
+ return this.#n;
207
+ if (this.#h >= m)
208
+ return this.#s();
209
+ }
210
+ /**
211
+ * Flush buffered events to the uWS HttpResponse in a single corked write.
212
+ */
213
+ #s() {
214
+ if (!(!this.#i || !this.#t) && (this.#a.cork(this.#u), this.#i = "", this.#h = 0, !this.#d))
215
+ return this.#l = !0, this.#n = new Promise((t) => {
216
+ this.#r = t, this.#e.writableNeedDrain = !0, this.#e._drainCb = () => {
217
+ this.#e.writableNeedDrain = !1, this.#l = !1, this.#n = null;
218
+ const e = this.#r;
219
+ this.#r = null, e && e();
220
+ }, this.#e._drainTimer = setTimeout(this.#e._boundDrainTimeout, k);
221
+ }), this.#n;
222
+ }
223
+ /**
224
+ * Pre-bound cork callback. Writes headers (first call only) + buffered data.
225
+ */
226
+ _corkFlush() {
227
+ const t = this.#e[u];
228
+ t && (_(this.#a, t), this.#e[u] = null), this.#d = this.#a.write(this.#i);
229
+ }
230
+ startHeartbeat(t) {
231
+ this.heartbeatTimer = setInterval(() => {
232
+ this.#t ? (this.sendHeaders(), this.#i += `: heartbeat
233
+
234
+ `, this.#h++, this.#s()) : this.stopHeartbeat();
235
+ }, t), this.heartbeatTimer.unref();
236
+ }
237
+ stopHeartbeat() {
238
+ this.heartbeatTimer && (clearInterval(this.heartbeatTimer), this.heartbeatTimer = null);
239
+ }
240
+ cleanup() {
241
+ this.stopHeartbeat();
242
+ for (const t of this.closeCallbacks)
243
+ try {
244
+ t();
245
+ } catch {
246
+ }
247
+ this.closeCallbacks = [];
248
+ }
249
+ }
250
+ function g(i, t, e) {
251
+ const { heartbeatInterval: s = 3e4, serializer: r = JSON.stringify } = t;
252
+ i.addHook("onRoute", (n) => {
253
+ if (!n.sse) return;
254
+ const o = n.handler, E = typeof n.sse == "object" ? n.sse : {};
255
+ n.handler = async function(h, l) {
256
+ if (!(h.headers.accept || "").includes("text/event-stream"))
257
+ return o.call(this, h, l);
258
+ l.hijack();
259
+ const f = h.raw.socket;
260
+ if (f.aborted || f.destroyed) return;
261
+ const a = new M({
262
+ reply: l,
263
+ socket: f,
264
+ lastEventId: h.headers["last-event-id"] || null,
265
+ heartbeatInterval: E.heartbeat !== !1 ? s : 0,
266
+ serializer: E.serializer || r
267
+ });
268
+ l.sse = a;
269
+ try {
270
+ await o.call(this, h, l);
271
+ } catch (b) {
272
+ throw a.shouldKeepAlive || a.close(), b;
273
+ }
274
+ a.flush(), a.shouldKeepAlive || a.close();
275
+ };
276
+ }), e();
277
+ }
278
+ const B = y(g, {
279
+ fastify: "5.x",
280
+ name: "@fastify/sse"
281
+ });
282
+ export {
283
+ M as SSEContext,
284
+ B as default
285
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const E=require("fastify-plugin"),r=require("./symbols.cjs"),k=require("./websocket-server.cjs"),H=/^[A-Za-z0-9+/]{22}==$/;function y(o){return typeof o!="string"||!H.test(o)?!1:Buffer.from(o,"base64").byteLength===16}function R(o,f,n){n.log.error(o),f.close()}const T=(o,f,n)=>{const{server:W}=o,{errorHandler:a=R,options:m}=f;if(a&&typeof a!="function")return n(new Error("invalid errorHandler function"));const b=new k.WebSocketServer(m);W[r.kWs]=b,o.decorate("websocketServer",b),o.addHook("onRoute",t=>{if(!!!t.websocket||t.method==="HEAD"||t.method==="OPTIONS")return;const u=typeof t.ws=="object"?t.ws:{},h=t.handler,w=Buffer.from(t.url||"/"),S={};if(u.topics)for(const c of u.topics)S[c]=k.WebSocket.allocTopic(w,c);t.handler=function(c,i){const e=c.raw;if(e[r.kWs]){const g=e.headers["sec-websocket-key"];if(!y(g)){i.code(400).send("Invalid sec-websocket-key header");return}i.hijack();const p=e.socket[r.kRes];if(e.socket[r.kWs]=!0,e.socket.aborted||e.socket.destroyed)return;p.upgrade({req:e,handler:v=>{const s=new k.WebSocket(w,v,S);let l;try{e.once("error",()=>{s.close()}),e.once("close",()=>{s.end(1e3,"Normal Closure")}),l=h.call(this,s,c,i)}catch(d){return a.call(this,d,s,c)}l&&typeof l.catch=="function"&&l.catch(d=>a.call(this,d,s,c))}},e.headers["sec-websocket-key"],e.headers["sec-websocket-protocol"],e.headers["sec-websocket-extensions"],e[r.kWs])}else return h.call(this,c,i)}}),n()},K=E(T,{fastify:"5.x",name:"@fastify/websocket"});exports.default=K;exports.isValidWebSocketKey=y;
@@ -0,0 +1,9 @@
1
+ import { FastifyPluginCallback, FastifyRequest } from 'fastify';
2
+ import { WebSocket } from './websocket-server';
3
+ export declare function isValidWebSocketKey(value: unknown): value is string;
4
+ declare function defaultErrorHandler(err: Error, conn: WebSocket, request: FastifyRequest): void;
5
+ declare const _default: FastifyPluginCallback<{
6
+ errorHandler?: typeof defaultErrorHandler;
7
+ options?: any;
8
+ }, any>;
9
+ export default _default;
@@ -0,0 +1,67 @@
1
+ import E from "fastify-plugin";
2
+ import { kWs as i, kRes as H } from "./symbols.js";
3
+ import { WebSocketServer as R, WebSocket as m } from "./websocket-server.js";
4
+ const v = /^[A-Za-z0-9+/]{22}==$/;
5
+ function T(o) {
6
+ return typeof o != "string" || !v.test(o) ? !1 : Buffer.from(o, "base64").byteLength === 16;
7
+ }
8
+ function A(o, l, s) {
9
+ s.log.error(o), l.close();
10
+ }
11
+ const K = (o, l, s) => {
12
+ const { server: p } = o, { errorHandler: n = A, options: y } = l;
13
+ if (n && typeof n != "function")
14
+ return s(new Error("invalid errorHandler function"));
15
+ const k = new R(y);
16
+ p[i] = k, o.decorate("websocketServer", k), o.addHook("onRoute", (c) => {
17
+ if (!!!c.websocket || c.method === "HEAD" || c.method === "OPTIONS") return;
18
+ const h = typeof c.ws == "object" ? c.ws : {}, b = c.handler, w = Buffer.from(c.url || "/"), u = {};
19
+ if (h.topics)
20
+ for (const t of h.topics)
21
+ u[t] = m.allocTopic(w, t);
22
+ c.handler = function(t, a) {
23
+ const e = t.raw;
24
+ if (e[i]) {
25
+ const S = e.headers["sec-websocket-key"];
26
+ if (!T(S)) {
27
+ a.code(400).send("Invalid sec-websocket-key header");
28
+ return;
29
+ }
30
+ a.hijack();
31
+ const W = e.socket[H];
32
+ if (e.socket[i] = !0, e.socket.aborted || e.socket.destroyed) return;
33
+ W.upgrade(
34
+ {
35
+ req: e,
36
+ handler: (g) => {
37
+ const r = new m(w, g, u);
38
+ let f;
39
+ try {
40
+ e.once("error", () => {
41
+ r.close();
42
+ }), e.once("close", () => {
43
+ r.end(1e3, "Normal Closure");
44
+ }), f = b.call(this, r, t, a);
45
+ } catch (d) {
46
+ return n.call(this, d, r, t);
47
+ }
48
+ f && typeof f.catch == "function" && f.catch((d) => n.call(this, d, r, t));
49
+ }
50
+ },
51
+ e.headers["sec-websocket-key"],
52
+ e.headers["sec-websocket-protocol"],
53
+ e.headers["sec-websocket-extensions"],
54
+ e[i]
55
+ );
56
+ } else
57
+ return b.call(this, t, a);
58
+ };
59
+ }), s();
60
+ }, I = E(K, {
61
+ fastify: "5.x",
62
+ name: "@fastify/websocket"
63
+ });
64
+ export {
65
+ I as default,
66
+ T as isValidWebSocketKey
67
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("streamx"),s=require("./symbols.cjs"),c=()=>{};class a extends u.Readable{socket;method;httpVersion="1.1";httpVersionMajor=1;httpVersionMinor=1;readableEnded;complete=!1;headersDistinct=Object.create(null);trailersDistinct=Object.create(null);trailers=Object.create(null);[s.kUrl];[s.kHeaders];[s.kWs];constructor(e,t,r){super(),this.socket=t,this.method=r,this.readableEnded=!1;const i=e.getQuery();this[s.kUrl]=e.getUrl()+(i&&i.length>0?`?${i}`:"");const o=Object.create(null);e.forEach((d,h)=>{o[d]=h,this.headersDistinct[d]=[h]}),this[s.kHeaders]=o,this.once("error",c);const n=super.destroy.bind(this);t.once("error",n),t.once("close",n),t.once("aborted",()=>{this.emit("aborted")})}get aborted(){return this.socket.aborted}get url(){return this[s.kUrl]}set url(e){this[s.kUrl]=e}get headers(){return this[s.kHeaders]}setEncoding(e){return this.socket.setEncoding(e),this}setTimeout(e,t){return this.socket.setTimeout(e),t&&this.once("timeout",t),this}destroy(e){return this.destroyed||this.destroying?this:(this.socket.destroy(e),this)}unpipe(e){return e.destroy&&e.destroy(),this}_read(e){if(this.destroyed||this.destroying||this.socket.destroyed)return e();this.socket.onRead((t,r)=>{if(t)return e(t);if(this.destroyed||this.destroying)return e();this.push(r),r||(this.complete=!0,this.readableEnded=!0,e())})}}exports.Request=a;
@@ -0,0 +1,29 @@
1
+ import { default as uws } from 'uWebSockets.js';
2
+ import { Readable } from 'streamx';
3
+ import { HTTPSocket } from './http-socket';
4
+ import { kHeaders, kUrl, kWs } from './symbols';
5
+ export declare class Request extends Readable {
6
+ socket: HTTPSocket;
7
+ method: string;
8
+ httpVersion: string;
9
+ httpVersionMajor: number;
10
+ httpVersionMinor: number;
11
+ readableEnded: boolean;
12
+ complete: boolean;
13
+ headersDistinct: Record<string, string[]>;
14
+ trailersDistinct: Record<string, string[]>;
15
+ trailers: Record<string, string>;
16
+ [kUrl]: string;
17
+ [kHeaders]: Record<string, string>;
18
+ [kWs]?: uws.us_socket_context_t;
19
+ constructor(req: uws.HttpRequest, socket: HTTPSocket, method: string);
20
+ get aborted(): boolean;
21
+ get url(): string;
22
+ set url(url: string);
23
+ get headers(): Record<string, string>;
24
+ setEncoding(encoding: string): this;
25
+ setTimeout(timeout: number, cb?: () => void): this;
26
+ destroy(err?: Error): this;
27
+ unpipe(writable: any): this;
28
+ _read(cb: (err?: Error | null) => void): void;
29
+ }
@@ -0,0 +1,67 @@
1
+ import { Readable as u } from "streamx";
2
+ import { kUrl as r, kHeaders as o, kWs as a } from "./symbols.js";
3
+ const l = () => {
4
+ };
5
+ class m extends u {
6
+ socket;
7
+ method;
8
+ httpVersion = "1.1";
9
+ httpVersionMajor = 1;
10
+ httpVersionMinor = 1;
11
+ readableEnded;
12
+ complete = !1;
13
+ headersDistinct = /* @__PURE__ */ Object.create(null);
14
+ trailersDistinct = /* @__PURE__ */ Object.create(null);
15
+ trailers = /* @__PURE__ */ Object.create(null);
16
+ [r];
17
+ [o];
18
+ [a];
19
+ constructor(t, e, s) {
20
+ super(), this.socket = e, this.method = s, this.readableEnded = !1;
21
+ const i = t.getQuery();
22
+ this[r] = t.getUrl() + (i && i.length > 0 ? `?${i}` : "");
23
+ const n = /* @__PURE__ */ Object.create(null);
24
+ t.forEach((d, c) => {
25
+ n[d] = c, this.headersDistinct[d] = [c];
26
+ }), this[o] = n, this.once("error", l);
27
+ const h = super.destroy.bind(this);
28
+ e.once("error", h), e.once("close", h), e.once("aborted", () => {
29
+ this.emit("aborted");
30
+ });
31
+ }
32
+ get aborted() {
33
+ return this.socket.aborted;
34
+ }
35
+ get url() {
36
+ return this[r];
37
+ }
38
+ set url(t) {
39
+ this[r] = t;
40
+ }
41
+ get headers() {
42
+ return this[o];
43
+ }
44
+ setEncoding(t) {
45
+ return this.socket.setEncoding(t), this;
46
+ }
47
+ setTimeout(t, e) {
48
+ return this.socket.setTimeout(t), e && this.once("timeout", e), this;
49
+ }
50
+ destroy(t) {
51
+ return this.destroyed || this.destroying ? this : (this.socket.destroy(t), this);
52
+ }
53
+ unpipe(t) {
54
+ return t.destroy && t.destroy(), this;
55
+ }
56
+ _read(t) {
57
+ if (this.destroyed || this.destroying || this.socket.destroyed) return t();
58
+ this.socket.onRead((e, s) => {
59
+ if (e) return t(e);
60
+ if (this.destroyed || this.destroying) return t();
61
+ this.push(s), s || (this.complete = !0, this.readableEnded = !0, t());
62
+ });
63
+ }
64
+ }
65
+ export {
66
+ m as Request
67
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("node:http"),u=require("streamx"),o=require("./errors.cjs"),i=require("./symbols.cjs");class f{isMultiValue;name;value;constructor(e,t){this.isMultiValue=Array.isArray(t),this.name=e,this.value=this.isMultiValue?t:String(t)}}const a=Buffer.alloc(0);function h(n,e=!1){return n?{chunk:n,empty:!1,end:e,byteLength:Buffer.isBuffer(n)?n.byteLength:Buffer.byteLength(n)}:{chunk:a,empty:!0,end:e,byteLength:0}}const c=()=>{},y={byteLength(n){return n.byteLength}};class l extends u.Writable{socket;statusCode=200;statusMessage;headersSent=!1;chunked=!1;contentLength=null;writableEnded=!1;firstChunk=!0;_boundEmitDrain;[i.kHeaders];constructor(e){super(y),this.socket=e,this._boundEmitDrain=this._emitDrain.bind(this),this[i.kHeaders]=new Map;const t=this.destroy.bind(this);this.once("error",c),e.once("error",t),e.once("close",t),e.once("aborted",()=>{this.emit("aborted")})}get aborted(){return this.socket.aborted}get finished(){return this.socket.writableEnded&&!this.socket.aborted}get status(){return`${this.statusCode} ${this.statusMessage||d.STATUS_CODES[this.statusCode]}`}get bytesWritten(){return this.socket.bytesWritten}hasHeader(e){return this[i.kHeaders].has(e.toLowerCase())}getHeader(e){return this[i.kHeaders].get(e.toLowerCase())?.value}getHeaders(){const e={};return this[i.kHeaders].forEach((t,s)=>{e[s]=t.value}),e}setHeader(e,t){if(this.headersSent)throw new o.ERR_HEAD_SET;e=e.replace(/[\r\n]/g,"");const s=e.toLowerCase();return s==="content-length"?(this.contentLength=Number(t),this):s==="transfer-encoding"?(this.chunked=String(t).includes("chunked"),this):(typeof t=="string"?t=t.replace(/[\r\n]/g,""):Array.isArray(t)&&(t=t.map(r=>typeof r=="string"?r.replace(/[\r\n]/g,""):r)),this[i.kHeaders].set(s,new f(e,t)),this)}removeHeader(e){if(this.headersSent)throw new o.ERR_HEAD_SET;this[i.kHeaders].delete(e.toLowerCase())}writeHead(e,t,s){if(this.headersSent)throw new o.ERR_HEAD_SET;if(this.statusCode=e,typeof t=="object"?s=t:t&&(this.statusMessage=typeof t=="string"?t.replace(/[\r\n]/g,""):t),s)for(const r of Object.keys(s))this.setHeader(r,s[r]);return this}end(e,t,s){if(typeof e=="function"?(s=e,e=void 0):typeof t=="function"&&(s=t),this.aborted)return s&&process.nextTick(s),this;if(this.destroyed)throw new o.ERR_STREAM_DESTROYED;return this.writableEnded=!0,s&&this.once("finish",s),super.end(h(e,!0)),this}addTrailers(){}destroy(e){return this.destroyed||this.destroying||this.aborted?this:(this.socket.destroy(e),this)}write(e,t,s){if(typeof t=="function"&&(s=t),this.aborted)return s&&process.nextTick(s),!1;if(this.destroyed)throw new o.ERR_STREAM_DESTROYED;const r=h(e);return this.firstChunk&&this.contentLength!==null&&this.contentLength===r.byteLength?(r.end=!0,this.writableEnded=!0,super.end(r),s&&process.nextTick(s),!0):(this.firstChunk=!1,this.headersSent||(this.headersSent=!0,this.socket[i.kHead]={headers:this[i.kHeaders],status:this.status}),this.socket.write(r,null,()=>{this._boundEmitDrain(),s&&s()}),!this.socket.writableNeedDrain)}_emitDrain(){this.emit("drain")}_write(e,t){if(this.aborted)return t();if(this.headersSent||(this.headersSent=!0,this.socket[i.kHead]={headers:this[i.kHeaders],status:this.status}),e.end){this.socket.end(e,null,t);return}this.socket.write(e,null,t)}_destroy(e){if(this.socket.destroyed)return e();this.socket.once("close",e)}}exports.Response=l;
@@ -0,0 +1,40 @@
1
+ import { Writable } from 'streamx';
2
+ import { HTTPSocket } from './http-socket';
3
+ import { kHeaders } from './symbols';
4
+ declare class Header {
5
+ isMultiValue: boolean;
6
+ name: string;
7
+ value: unknown;
8
+ constructor(name: string, value: unknown);
9
+ }
10
+ export declare class Response extends Writable {
11
+ socket: HTTPSocket;
12
+ statusCode: number;
13
+ statusMessage?: string;
14
+ headersSent: boolean;
15
+ chunked: boolean;
16
+ contentLength: number | null;
17
+ writableEnded: boolean;
18
+ firstChunk: boolean;
19
+ _boundEmitDrain: () => void;
20
+ [kHeaders]: Map<string, Header>;
21
+ constructor(socket: HTTPSocket);
22
+ get aborted(): boolean;
23
+ get finished(): boolean;
24
+ get status(): string;
25
+ get bytesWritten(): number;
26
+ hasHeader(name: string): boolean;
27
+ getHeader(name: string): unknown;
28
+ getHeaders(): Record<string, string>;
29
+ setHeader(name: string, value: string | string[] | number): this;
30
+ removeHeader(name: string): void;
31
+ writeHead(statusCode: number, statusMessage?: string | Record<string, any>, headers?: Record<string, any>): this;
32
+ end(data?: any, encoding?: any, callback?: () => void): this;
33
+ addTrailers(): void;
34
+ destroy(err?: Error): this;
35
+ write(data: any, encoding?: any, cb?: (err?: Error | null) => void): boolean;
36
+ _emitDrain(): void;
37
+ _write(data: any, cb: (err?: Error | null) => void): void;
38
+ _destroy(cb: (err?: Error | null) => void): void;
39
+ }
40
+ export {};