@effect/platform-node 0.44.11 → 0.45.1
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/NodeSocket/package.json +6 -0
- package/dist/cjs/NodeSocket.js +17 -0
- package/dist/cjs/NodeSocket.js.map +1 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/http/client.js +7 -7
- package/dist/cjs/internal/http/client.js.map +1 -1
- package/dist/cjs/internal/http/server.js +71 -12
- package/dist/cjs/internal/http/server.js.map +1 -1
- package/dist/dts/Http/Server.d.ts +4 -4
- package/dist/dts/Http/Server.d.ts.map +1 -1
- package/dist/dts/NodeSocket.d.ts +8 -0
- package/dist/dts/NodeSocket.d.ts.map +1 -0
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/NodeSocket.js +8 -0
- package/dist/esm/NodeSocket.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/http/client.js +7 -7
- package/dist/esm/internal/http/client.js.map +1 -1
- package/dist/esm/internal/http/server.js +69 -11
- package/dist/esm/internal/http/server.js.map +1 -1
- package/package.json +13 -4
- package/src/Http/Server.ts +7 -7
- package/src/NodeSocket.ts +8 -0
- package/src/index.ts +5 -0
- package/src/internal/http/client.ts +19 -15
- package/src/internal/http/server.ts +120 -18
|
@@ -12,6 +12,7 @@ import * as Error from "@effect/platform/Http/ServerError"
|
|
|
12
12
|
import * as ServerRequest from "@effect/platform/Http/ServerRequest"
|
|
13
13
|
import type * as ServerResponse from "@effect/platform/Http/ServerResponse"
|
|
14
14
|
import type * as Path from "@effect/platform/Path"
|
|
15
|
+
import * as Socket from "@effect/platform/Socket"
|
|
15
16
|
import * as Cause from "effect/Cause"
|
|
16
17
|
import * as Config from "effect/Config"
|
|
17
18
|
import * as Effect from "effect/Effect"
|
|
@@ -19,12 +20,14 @@ import { type LazyArg } from "effect/Function"
|
|
|
19
20
|
import * as Layer from "effect/Layer"
|
|
20
21
|
import * as Option from "effect/Option"
|
|
21
22
|
import * as Runtime from "effect/Runtime"
|
|
22
|
-
import
|
|
23
|
+
import * as Scope from "effect/Scope"
|
|
23
24
|
import * as Stream from "effect/Stream"
|
|
24
|
-
import
|
|
25
|
+
import * as Http from "node:http"
|
|
25
26
|
import type * as Net from "node:net"
|
|
27
|
+
import type { Duplex } from "node:stream"
|
|
26
28
|
import { Readable } from "node:stream"
|
|
27
29
|
import { pipeline } from "node:stream/promises"
|
|
30
|
+
import * as WS from "ws"
|
|
28
31
|
import * as NodeContext from "../../NodeContext.js"
|
|
29
32
|
import * as NodeSink from "../../NodeSink.js"
|
|
30
33
|
import { IncomingMessageImpl } from "./incomingMessage.js"
|
|
@@ -36,6 +39,8 @@ export const make = (
|
|
|
36
39
|
options: Net.ListenOptions
|
|
37
40
|
): Effect.Effect<Server.Server, Error.ServeError, Scope.Scope> =>
|
|
38
41
|
Effect.gen(function*(_) {
|
|
42
|
+
const scope = yield* _(Effect.scope)
|
|
43
|
+
|
|
39
44
|
const server = yield* _(Effect.acquireRelease(
|
|
40
45
|
Effect.sync(evaluate),
|
|
41
46
|
(server) =>
|
|
@@ -52,7 +57,7 @@ export const make = (
|
|
|
52
57
|
|
|
53
58
|
yield* _(Effect.async<void, Error.ServeError>((resume) => {
|
|
54
59
|
server.on("error", (error) => {
|
|
55
|
-
resume(Effect.fail(Error.ServeError({ error })))
|
|
60
|
+
resume(Effect.fail(new Error.ServeError({ error })))
|
|
56
61
|
})
|
|
57
62
|
server.listen(options, () => {
|
|
58
63
|
resume(Effect.unit)
|
|
@@ -61,6 +66,18 @@ export const make = (
|
|
|
61
66
|
|
|
62
67
|
const address = server.address()!
|
|
63
68
|
|
|
69
|
+
const wss = yield* _(
|
|
70
|
+
Effect.acquireRelease(
|
|
71
|
+
Effect.sync(() => new WS.WebSocketServer({ noServer: true })),
|
|
72
|
+
(wss) =>
|
|
73
|
+
Effect.async<void>((resume) => {
|
|
74
|
+
wss.close(() => resume(Effect.unit))
|
|
75
|
+
})
|
|
76
|
+
),
|
|
77
|
+
Scope.extend(scope),
|
|
78
|
+
Effect.cached
|
|
79
|
+
)
|
|
80
|
+
|
|
64
81
|
return Server.make({
|
|
65
82
|
address: typeof address === "string" ?
|
|
66
83
|
{
|
|
@@ -75,12 +92,15 @@ export const make = (
|
|
|
75
92
|
serve: (httpApp, middleware) =>
|
|
76
93
|
Effect.gen(function*(_) {
|
|
77
94
|
const handler = yield* _(makeHandler(httpApp, middleware!))
|
|
95
|
+
const upgradeHandler = yield* _(makeUpgradeHandler(wss, httpApp, middleware!))
|
|
78
96
|
yield* _(Effect.addFinalizer(() =>
|
|
79
97
|
Effect.sync(() => {
|
|
80
98
|
server.off("request", handler)
|
|
99
|
+
server.off("upgrade", upgradeHandler)
|
|
81
100
|
})
|
|
82
101
|
))
|
|
83
102
|
server.on("request", handler)
|
|
103
|
+
server.on("upgrade", upgradeHandler)
|
|
84
104
|
})
|
|
85
105
|
})
|
|
86
106
|
}).pipe(
|
|
@@ -113,7 +133,10 @@ export const makeHandler: {
|
|
|
113
133
|
)
|
|
114
134
|
return Effect.map(Effect.runtime<R>(), (runtime) => {
|
|
115
135
|
const runFork = Runtime.runFork(runtime)
|
|
116
|
-
return function handler(
|
|
136
|
+
return function handler(
|
|
137
|
+
nodeRequest: Http.IncomingMessage,
|
|
138
|
+
nodeResponse: Http.ServerResponse
|
|
139
|
+
) {
|
|
117
140
|
const fiber = runFork(
|
|
118
141
|
Effect.provideService(
|
|
119
142
|
handledApp,
|
|
@@ -134,6 +157,65 @@ export const makeHandler: {
|
|
|
134
157
|
})
|
|
135
158
|
}
|
|
136
159
|
|
|
160
|
+
/** @internal */
|
|
161
|
+
export const makeUpgradeHandler = <R, E>(
|
|
162
|
+
lazyWss: Effect.Effect<WS.WebSocketServer>,
|
|
163
|
+
httpApp: App.Default<R, E>,
|
|
164
|
+
middleware?: Middleware.Middleware
|
|
165
|
+
) => {
|
|
166
|
+
const handledApp = Effect.scoped(
|
|
167
|
+
middleware
|
|
168
|
+
? middleware(App.withDefaultMiddleware(respond(httpApp)))
|
|
169
|
+
: App.withDefaultMiddleware(respond(httpApp))
|
|
170
|
+
)
|
|
171
|
+
return Effect.map(Effect.runtime<R>(), (runtime) => {
|
|
172
|
+
const runFork = Runtime.runFork(runtime)
|
|
173
|
+
return function handler(
|
|
174
|
+
nodeRequest: Http.IncomingMessage,
|
|
175
|
+
socket: Duplex,
|
|
176
|
+
head: Buffer
|
|
177
|
+
) {
|
|
178
|
+
let nodeResponse_: Http.ServerResponse | undefined = undefined
|
|
179
|
+
const nodeResponse = () => {
|
|
180
|
+
if (nodeResponse_ === undefined) {
|
|
181
|
+
nodeResponse_ = new Http.ServerResponse(nodeRequest)
|
|
182
|
+
nodeResponse_.assignSocket(socket as any)
|
|
183
|
+
}
|
|
184
|
+
return nodeResponse_
|
|
185
|
+
}
|
|
186
|
+
const upgradeEffect = Socket.fromWebSocket(Effect.flatMap(
|
|
187
|
+
lazyWss,
|
|
188
|
+
(wss) =>
|
|
189
|
+
Effect.acquireRelease(
|
|
190
|
+
Effect.async<globalThis.WebSocket>((resume) =>
|
|
191
|
+
wss.handleUpgrade(nodeRequest, socket, head, (ws) => {
|
|
192
|
+
resume(Effect.succeed(ws as any))
|
|
193
|
+
})
|
|
194
|
+
),
|
|
195
|
+
(ws) => Effect.sync(() => ws.close())
|
|
196
|
+
)
|
|
197
|
+
))
|
|
198
|
+
const fiber = runFork(
|
|
199
|
+
Effect.provideService(
|
|
200
|
+
handledApp,
|
|
201
|
+
ServerRequest.ServerRequest,
|
|
202
|
+
new ServerRequestImpl(nodeRequest, nodeResponse, upgradeEffect)
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
socket.on("close", () => {
|
|
206
|
+
const res = nodeResponse()
|
|
207
|
+
if (!socket.writableEnded) {
|
|
208
|
+
if (!res.headersSent) {
|
|
209
|
+
res.writeHead(499)
|
|
210
|
+
}
|
|
211
|
+
res.end()
|
|
212
|
+
runFork(fiber.interruptAsFork(Error.clientAbortFiberId))
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
137
219
|
const respond = Middleware.make((httpApp) =>
|
|
138
220
|
Effect.uninterruptibleMask((restore) =>
|
|
139
221
|
Effect.flatMap(ServerRequest.ServerRequest, (request) =>
|
|
@@ -149,7 +231,7 @@ const respond = Middleware.make((httpApp) =>
|
|
|
149
231
|
),
|
|
150
232
|
(cause) =>
|
|
151
233
|
Effect.sync(() => {
|
|
152
|
-
const nodeResponse = (request as ServerRequestImpl).
|
|
234
|
+
const nodeResponse = (request as ServerRequestImpl).resolvedResponse
|
|
153
235
|
if (!nodeResponse.headersSent) {
|
|
154
236
|
nodeResponse.writeHead(Cause.isInterruptedOnly(cause) ? 503 : 500)
|
|
155
237
|
}
|
|
@@ -166,13 +248,14 @@ class ServerRequestImpl extends IncomingMessageImpl<Error.RequestError> implemen
|
|
|
166
248
|
|
|
167
249
|
constructor(
|
|
168
250
|
readonly source: Http.IncomingMessage,
|
|
169
|
-
readonly response: Http.ServerResponse
|
|
251
|
+
readonly response: Http.ServerResponse | LazyArg<Http.ServerResponse>,
|
|
252
|
+
private upgradeEffect?: Effect.Effect<Socket.Socket, Error.RequestError>,
|
|
170
253
|
readonly url = source.url!,
|
|
171
254
|
private headersOverride?: Headers.Headers,
|
|
172
255
|
remoteAddressOverride?: string
|
|
173
256
|
) {
|
|
174
257
|
super(source, (_) =>
|
|
175
|
-
Error.RequestError({
|
|
258
|
+
new Error.RequestError({
|
|
176
259
|
request: this,
|
|
177
260
|
reason: "Decode",
|
|
178
261
|
error: _
|
|
@@ -180,6 +263,10 @@ class ServerRequestImpl extends IncomingMessageImpl<Error.RequestError> implemen
|
|
|
180
263
|
this[ServerRequest.TypeId] = ServerRequest.TypeId
|
|
181
264
|
}
|
|
182
265
|
|
|
266
|
+
get resolvedResponse(): Http.ServerResponse {
|
|
267
|
+
return typeof this.response === "function" ? this.response() : this.response
|
|
268
|
+
}
|
|
269
|
+
|
|
183
270
|
modify(
|
|
184
271
|
options: {
|
|
185
272
|
readonly url?: string | undefined
|
|
@@ -190,6 +277,7 @@ class ServerRequestImpl extends IncomingMessageImpl<Error.RequestError> implemen
|
|
|
190
277
|
return new ServerRequestImpl(
|
|
191
278
|
this.source,
|
|
192
279
|
this.response,
|
|
280
|
+
this.upgradeEffect,
|
|
193
281
|
options.url ?? this.url,
|
|
194
282
|
options.headers ?? this.headersOverride,
|
|
195
283
|
options.remoteAddress ?? this.remoteAddressOverride
|
|
@@ -234,6 +322,16 @@ class ServerRequestImpl extends IncomingMessageImpl<Error.RequestError> implemen
|
|
|
234
322
|
return MultipartNode.stream(this.source, this.source.headers)
|
|
235
323
|
}
|
|
236
324
|
|
|
325
|
+
get upgrade(): Effect.Effect<Socket.Socket, Error.RequestError> {
|
|
326
|
+
return this.upgradeEffect ?? Effect.fail(
|
|
327
|
+
new Error.RequestError({
|
|
328
|
+
request: this,
|
|
329
|
+
reason: "Decode",
|
|
330
|
+
error: "not an upgradeable ServerRequest"
|
|
331
|
+
})
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
237
335
|
toString(): string {
|
|
238
336
|
return `ServerRequest(${this.method} ${this.url})`
|
|
239
337
|
}
|
|
@@ -284,8 +382,10 @@ export const layerConfig = (
|
|
|
284
382
|
|
|
285
383
|
const handleResponse = (request: ServerRequest.ServerRequest, response: ServerResponse.ServerResponse) =>
|
|
286
384
|
Effect.suspend((): Effect.Effect<void, Error.ResponseError> => {
|
|
287
|
-
const nodeResponse = (request as ServerRequestImpl).
|
|
288
|
-
if (
|
|
385
|
+
const nodeResponse = (request as ServerRequestImpl).resolvedResponse
|
|
386
|
+
if (nodeResponse.writableEnded) {
|
|
387
|
+
return Effect.unit
|
|
388
|
+
} else if (request.method === "HEAD") {
|
|
289
389
|
nodeResponse.writeHead(response.status, response.headers)
|
|
290
390
|
nodeResponse.end()
|
|
291
391
|
return Effect.unit
|
|
@@ -306,7 +406,7 @@ const handleResponse = (request: ServerRequest.ServerRequest, response: ServerRe
|
|
|
306
406
|
return Effect.tryPromise({
|
|
307
407
|
try: (signal) => pipeline(body.body as any, nodeResponse, { signal, end: true }),
|
|
308
408
|
catch: (error) =>
|
|
309
|
-
Error.ResponseError({
|
|
409
|
+
new Error.ResponseError({
|
|
310
410
|
request,
|
|
311
411
|
response,
|
|
312
412
|
reason: "Decode",
|
|
@@ -333,12 +433,14 @@ const handleResponse = (request: ServerRequest.ServerRequest, response: ServerRe
|
|
|
333
433
|
Readable.fromWeb(r.body as any)
|
|
334
434
|
.pipe(nodeResponse)
|
|
335
435
|
.on("error", (error) => {
|
|
336
|
-
resume(Effect.fail(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
436
|
+
resume(Effect.fail(
|
|
437
|
+
new Error.ResponseError({
|
|
438
|
+
request,
|
|
439
|
+
response,
|
|
440
|
+
reason: "Decode",
|
|
441
|
+
error
|
|
442
|
+
})
|
|
443
|
+
))
|
|
342
444
|
})
|
|
343
445
|
.once("finish", () => {
|
|
344
446
|
resume(Effect.unit)
|
|
@@ -351,7 +453,7 @@ const handleResponse = (request: ServerRequest.ServerRequest, response: ServerRe
|
|
|
351
453
|
Stream.mapError(
|
|
352
454
|
body.stream,
|
|
353
455
|
(error) =>
|
|
354
|
-
Error.ResponseError({
|
|
456
|
+
new Error.ResponseError({
|
|
355
457
|
request,
|
|
356
458
|
response,
|
|
357
459
|
reason: "Decode",
|
|
@@ -359,7 +461,7 @@ const handleResponse = (request: ServerRequest.ServerRequest, response: ServerRe
|
|
|
359
461
|
})
|
|
360
462
|
),
|
|
361
463
|
NodeSink.fromWritable(() => nodeResponse, (error) =>
|
|
362
|
-
Error.ResponseError({
|
|
464
|
+
new Error.ResponseError({
|
|
363
465
|
request,
|
|
364
466
|
response,
|
|
365
467
|
reason: "Decode",
|