Skip to content

Commit 7d43b67

Browse files
committed
🎉 feat: 1.4.4
1 parent 2cd1c5e commit 7d43b67

6 files changed

Lines changed: 122 additions & 76 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 1.4.4 - 9 Feb 2025
2+
Bug fix:
3+
- [#55](https://github.com/elysiajs/node/pull/55) allow node cluster mode as default
4+
- createResponseHandler with more precision headers merging
5+
- simplify ws hook
6+
17
# 1.4.3 - 5 Jan 2025
28
Bug fix:
39
- [#54](https://github.com/elysiajs/node/issues/54) update srvx to 0.10.0

bun.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@elysiajs/node",
3-
"version": "1.4.3",
3+
"version": "1.4.4",
44
"description": "Elysia adapter to run Elysia on Node.js",
55
"license": "MIT",
66
"scripts": {
@@ -13,7 +13,7 @@
1313
},
1414
"dependencies": {
1515
"crossws": "^0.4.1",
16-
"srvx": "^0.10.0"
16+
"srvx": "^0.11.2"
1717
},
1818
"peerDependencies": {
1919
"elysia": ">= 1.4.0"

src/index.ts

Lines changed: 13 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ import { WebStandardAdapter } from 'elysia/adapter/web-standard'
1515
import type { Server } from 'elysia/universal'
1616
import { isNumericString, randomId } from 'elysia/utils'
1717

18-
import { defineHooks } from 'crossws'
1918
import { serve } from 'crossws/server'
2019

21-
import type { ServerWebSocket } from 'elysia/ws/bun'
2220
import { createWebSocketAdapter, type NodeWebSocketContext } from './ws'
2321

2422
import {
@@ -50,71 +48,20 @@ export const node = () => {
5048
options = parseInt(options)
5149
}
5250

53-
const websocket = defineHooks({
54-
async upgrade(request) {
55-
// @ts-ignore
56-
const id = (request.wsId = randomId())
57-
58-
const response = await app.handle(request)
59-
60-
const context = ws.context[id]
61-
if (!context) return response
62-
63-
return {
64-
context: context as any,
65-
headers: context.data.set.headers as any
66-
}
67-
},
68-
open(ws) {
69-
const context =
70-
ws.context as any as NodeWebSocketContext
71-
72-
context.open(ws)
73-
},
74-
message(ws, message) {
75-
const context =
76-
ws.context as any as NodeWebSocketContext
77-
78-
// ws is parsed in context.open
79-
context.message(
80-
ws as any as ServerWebSocket,
81-
message.text()
82-
)
83-
},
84-
close(ws, detail) {
85-
const context =
86-
ws.context as any as NodeWebSocketContext
87-
88-
context.close(
89-
// ws is parsed in context.open
90-
ws as any as ServerWebSocket,
91-
detail.code!,
92-
detail.reason!
93-
)
94-
},
95-
error(ws, error) {
96-
const context =
97-
ws.context as any as NodeWebSocketContext
98-
99-
// ws is parsed in context.open
100-
context.error?.(ws as any as ServerWebSocket, error)
101-
}
102-
})
103-
10451
const serverOptions: Parameters<typeof serve>[0] =
10552
typeof options === 'number'
106-
? {
53+
? ({
10754
port: options,
10855
silent: true,
109-
websocket,
56+
websocket: ws.createConfig(app),
11057
fetch: app.fetch,
11158
reusePort: true
112-
}
59+
} as any)
11360
: {
11461
reusePort: true,
11562
...options,
11663
silent: true,
117-
websocket,
64+
websocket: ws.createConfig(app),
11865
fetch: app.fetch
11966
}
12067

@@ -149,13 +96,15 @@ export const node = () => {
14996
resolve(total)
15097
})
15198

152-
return promise
99+
return promise as any
153100
},
154101
get pendingWebSockets() {
155102
return 0
156103
},
157-
port,
158-
publish() {},
104+
port: port as any,
105+
publish() {
106+
return 0
107+
},
159108
ref() {
160109
nodeServer?.ref()
161110
},
@@ -165,14 +114,16 @@ export const node = () => {
165114
reload() {
166115
nodeServer?.close()
167116
server = serve(serverOptions)
117+
118+
return serverInfo
168119
},
169120
requestIP() {
170121
throw new Error(
171122
"This adapter doesn't support Bun requestIP method"
172123
)
173124
},
174125
stop() {
175-
server.close()
126+
return server.close()
176127
},
177128
upgrade() {
178129
throw new Error(
@@ -185,6 +136,7 @@ export const node = () => {
185136
[Symbol.dispose]() {
186137
server.close()
187138
},
139+
// @ts-ignore
188140
raw: server
189141
} satisfies Server
190142

src/utils.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FastResponse as Response } from 'srvx'
22
import type { ReadStream } from 'fs'
33

4-
import { isNotEmpty } from 'elysia/utils'
4+
import { isNotEmpty, StatusMap } from 'elysia/utils'
55
import type { Context } from 'elysia/context'
66
import {
77
createStreamHandler,
@@ -84,17 +84,57 @@ interface CreateHandlerParameter {
8484
mapCompactResponse(response: unknown, request?: Request): Response
8585
}
8686

87+
// Merge header by allocating a new one
88+
// In Bun, response.headers can be mutable
89+
// while in Node and Cloudflare Worker is not
90+
// default to creating a new one instead
91+
export function mergeHeaders(
92+
responseHeaders: Headers,
93+
setHeaders: Context['set']['headers']
94+
) {
95+
// @ts-ignore
96+
const headers = new Headers(Object.fromEntries(responseHeaders.entries()))
97+
98+
// Merge headers: Response headers take precedence, set.headers fill in non-conflicting ones
99+
if (setHeaders instanceof Headers)
100+
// @ts-ignore
101+
for (const key of setHeaders.keys()) {
102+
if (key === 'set-cookie') {
103+
if (headers.has('set-cookie')) continue
104+
105+
for (const cookie of setHeaders.getSetCookie())
106+
headers.append('set-cookie', cookie)
107+
} else if (!responseHeaders.has(key))
108+
headers.set(key, setHeaders?.get(key) ?? '')
109+
}
110+
else
111+
for (const key in setHeaders)
112+
if (key === 'set-cookie')
113+
headers.append(key, setHeaders[key] as any)
114+
else if (!responseHeaders.has(key))
115+
headers.set(key, setHeaders[key] as any)
116+
117+
return headers
118+
}
119+
120+
export function mergeStatus(
121+
responseStatus: number,
122+
setStatus: Context['set']['status']
123+
) {
124+
if (typeof setStatus === 'string') setStatus = StatusMap[setStatus]
125+
126+
if (responseStatus === 200) return setStatus
127+
128+
return responseStatus
129+
}
130+
87131
export const createResponseHandler = (handler: CreateHandlerParameter) => {
88132
const handleStream = createStreamHandler(handler)
89133

90134
return (response: Response, set: Context['set'], request?: Request) => {
91135
const newResponse = new Response(response.body, {
92-
headers: Object.assign(
93-
// @ts-ignore
94-
Object.fromEntries(response.headers.entries()),
95-
set.headers
96-
),
97-
status: response.status ?? set.status
136+
headers: mergeHeaders(response.headers, set.headers),
137+
status: mergeStatus(response.status, set.status)
98138
})
99139

100140
if (
@@ -105,7 +145,9 @@ export const createResponseHandler = (handler: CreateHandlerParameter) => {
105145
return handleStream(
106146
streamResponse(newResponse as Response),
107147
responseToSetHeaders(newResponse as Response, set),
108-
request
148+
request,
149+
// @ts-ignore
150+
true // don't auto-format SSE for pre-formatted Response
109151
) as any
110152

111153
return newResponse

src/ws.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Peer, WSError } from 'crossws'
1+
import { defineHooks, Peer, WSError } from 'crossws'
22
import {
33
Context,
44
getSchemaValidator,
@@ -249,5 +249,51 @@ export function createWebSocketAdapter() {
249249
)
250250
}
251251

252-
return { handler, context: store }
252+
function createConfig(app: AnyElysia) {
253+
return defineHooks({
254+
async upgrade(request) {
255+
// @ts-ignore
256+
const id = (request.wsId = randomId())
257+
258+
const response = await app.handle(request)
259+
260+
const context = store[id]
261+
if (!context) return response
262+
263+
return {
264+
context: context as any,
265+
headers: context.data.set.headers as any
266+
}
267+
},
268+
open(ws) {
269+
const context = ws.context as any as NodeWebSocketContext
270+
271+
context.open(ws)
272+
},
273+
message(ws, message) {
274+
const context = ws.context as any as NodeWebSocketContext
275+
276+
// ws is parsed in context.open
277+
context.message(ws as any as ServerWebSocket, message.text())
278+
},
279+
close(ws, detail) {
280+
const context = ws.context as any as NodeWebSocketContext
281+
282+
context.close(
283+
// ws is parsed in context.open
284+
ws as any as ServerWebSocket,
285+
detail.code!,
286+
detail.reason!
287+
)
288+
},
289+
error(ws, error) {
290+
const context = ws.context as any as NodeWebSocketContext
291+
292+
// ws is parsed in context.open
293+
context.error?.(ws as any as ServerWebSocket, error)
294+
}
295+
})
296+
}
297+
298+
return { handler, createConfig, context: store }
253299
}

0 commit comments

Comments
 (0)