@effect/platform-node 0.11.5 → 0.12.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/Http/NodeClient.d.ts +10 -0
- package/Http/NodeClient.d.ts.map +1 -1
- package/Http/NodeClient.js +14 -2
- package/Http/NodeClient.js.map +1 -1
- package/Http/Server.d.ts +34 -0
- package/Http/Server.d.ts.map +1 -0
- package/Http/Server.js +49 -0
- package/Http/Server.js.map +1 -0
- package/HttpServer.d.ts +9 -0
- package/HttpServer.d.ts.map +1 -0
- package/HttpServer.js +26 -0
- package/HttpServer.js.map +1 -0
- package/Stream.d.ts +14 -6
- package/Stream.d.ts.map +1 -1
- package/Stream.js.map +1 -1
- package/internal/fileSystem.js +9 -9
- package/internal/fileSystem.js.map +1 -1
- package/internal/http/formData.d.ts +5 -0
- package/internal/http/formData.d.ts.map +1 -0
- package/internal/http/formData.js +106 -0
- package/internal/http/formData.js.map +1 -0
- package/internal/http/incomingMessage.d.ts +2 -0
- package/internal/http/incomingMessage.d.ts.map +1 -0
- package/internal/http/incomingMessage.js +62 -0
- package/internal/http/incomingMessage.js.map +1 -0
- package/internal/http/nodeClient.js +28 -65
- package/internal/http/nodeClient.js.map +1 -1
- package/internal/http/server.d.ts +2 -0
- package/internal/http/server.d.ts.map +1 -0
- package/internal/http/server.js +218 -0
- package/internal/http/server.js.map +1 -0
- package/internal/stream.js +54 -35
- package/internal/stream.js.map +1 -1
- package/mjs/Http/NodeClient.mjs +10 -0
- package/mjs/Http/NodeClient.mjs.map +1 -1
- package/mjs/Http/Server.mjs +21 -0
- package/mjs/Http/Server.mjs.map +1 -0
- package/mjs/HttpServer.mjs +7 -0
- package/mjs/HttpServer.mjs.map +1 -0
- package/mjs/Stream.mjs.map +1 -1
- package/mjs/internal/fileSystem.mjs +9 -9
- package/mjs/internal/fileSystem.mjs.map +1 -1
- package/mjs/internal/http/formData.mjs +95 -0
- package/mjs/internal/http/formData.mjs.map +1 -0
- package/mjs/internal/http/incomingMessage.mjs +53 -0
- package/mjs/internal/http/incomingMessage.mjs.map +1 -0
- package/mjs/internal/http/nodeClient.mjs +25 -64
- package/mjs/internal/http/nodeClient.mjs.map +1 -1
- package/mjs/internal/http/server.mjs +207 -0
- package/mjs/internal/http/server.mjs.map +1 -0
- package/mjs/internal/stream.mjs +54 -35
- package/mjs/internal/stream.mjs.map +1 -1
- package/package.json +5 -2
- package/src/Http/NodeClient.ts +13 -0
- package/src/Http/Server.ts +45 -0
- package/src/HttpServer.ts +10 -0
- package/src/Stream.ts +14 -9
- package/src/internal/fileSystem.ts +16 -11
- package/src/internal/http/formData.ts +158 -0
- package/src/internal/http/incomingMessage.ts +78 -0
- package/src/internal/http/nodeClient.ts +30 -78
- package/src/internal/http/server.ts +323 -0
- package/src/internal/stream.ts +81 -46
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import type { LazyArg } from "@effect/data/Function"
|
|
5
|
+
import type * as Config from "@effect/io/Config"
|
|
6
|
+
import type * as ConfigError from "@effect/io/Config/Error"
|
|
7
|
+
import type * as Effect from "@effect/io/Effect"
|
|
8
|
+
import type * as Layer from "@effect/io/Layer"
|
|
9
|
+
import type * as Scope from "@effect/io/Scope"
|
|
10
|
+
import * as internal from "@effect/platform-node/internal/http/server"
|
|
11
|
+
import type * as Server from "@effect/platform/Http/Server"
|
|
12
|
+
import type * as Http from "node:http"
|
|
13
|
+
import type * as Net from "node:net"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @since 1.0.0
|
|
17
|
+
*/
|
|
18
|
+
export * from "@effect/platform/Http/Server"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @since 1.0.0
|
|
22
|
+
* @category constructors
|
|
23
|
+
*/
|
|
24
|
+
export const make: (
|
|
25
|
+
evaluate: LazyArg<Http.Server>,
|
|
26
|
+
options: Net.ListenOptions
|
|
27
|
+
) => Effect.Effect<Scope.Scope, never, Server.Server> = internal.make
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @since 1.0.0
|
|
31
|
+
* @category layers
|
|
32
|
+
*/
|
|
33
|
+
export const layer: (
|
|
34
|
+
evaluate: LazyArg<Http.Server>,
|
|
35
|
+
options: Net.ListenOptions
|
|
36
|
+
) => Layer.Layer<never, never, Server.Server> = internal.layer
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @since 1.0.0
|
|
40
|
+
* @category layers
|
|
41
|
+
*/
|
|
42
|
+
export const layerConfig: (
|
|
43
|
+
evaluate: LazyArg<Http.Server>,
|
|
44
|
+
options: Config.Config.Wrap<Net.ListenOptions>
|
|
45
|
+
) => Layer.Layer<never, ConfigError.ConfigError, Server.Server> = internal.layerConfig
|
package/src/Stream.ts
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
4
|
import type { LazyArg } from "@effect/data/Function"
|
|
5
|
-
import type { Option } from "@effect/data/Option"
|
|
6
5
|
import type { Effect } from "@effect/io/Effect"
|
|
7
6
|
import * as internal from "@effect/platform-node/internal/stream"
|
|
8
|
-
import type {
|
|
7
|
+
import type { SizeInput } from "@effect/platform/FileSystem"
|
|
9
8
|
import type { Stream } from "@effect/stream/Stream"
|
|
10
9
|
import type { Readable } from "stream"
|
|
11
10
|
|
|
@@ -14,8 +13,8 @@ import type { Readable } from "stream"
|
|
|
14
13
|
* @since 1.0.0
|
|
15
14
|
*/
|
|
16
15
|
export interface FromReadableOptions {
|
|
17
|
-
/** Defaults to
|
|
18
|
-
readonly chunkSize?:
|
|
16
|
+
/** Defaults to undefined, which lets Node.js decide the chunk size */
|
|
17
|
+
readonly chunkSize?: SizeInput
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
/**
|
|
@@ -33,9 +32,12 @@ export const fromReadable: <E, A>(
|
|
|
33
32
|
* @category conversions
|
|
34
33
|
*/
|
|
35
34
|
export const toString: <E>(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
options: {
|
|
36
|
+
readable: LazyArg<Readable>
|
|
37
|
+
onFailure: (error: unknown) => E
|
|
38
|
+
encoding?: BufferEncoding
|
|
39
|
+
maxBytes?: SizeInput
|
|
40
|
+
}
|
|
39
41
|
) => Effect<never, E, string> = internal.toString
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -43,6 +45,9 @@ export const toString: <E>(
|
|
|
43
45
|
* @category conversions
|
|
44
46
|
*/
|
|
45
47
|
export const toUint8Array: <E>(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
options: {
|
|
49
|
+
readable: LazyArg<Readable>
|
|
50
|
+
onFailure: (error: unknown) => E
|
|
51
|
+
maxBytes?: SizeInput
|
|
52
|
+
}
|
|
48
53
|
) => Effect<never, E, Uint8Array> = internal.toUint8Array
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pipe } from "@effect/data/Function"
|
|
2
2
|
import * as Option from "@effect/data/Option"
|
|
3
3
|
import * as Effect from "@effect/io/Effect"
|
|
4
4
|
import * as Layer from "@effect/io/Layer"
|
|
@@ -218,7 +218,7 @@ const makeFile = (() => {
|
|
|
218
218
|
const nodeWriteAll = nodeWriteFactory("writeAll")
|
|
219
219
|
|
|
220
220
|
class FileImpl implements FileSystem.File {
|
|
221
|
-
readonly [FileSystem.FileTypeId]
|
|
221
|
+
readonly [FileSystem.FileTypeId]: FileSystem.FileTypeId
|
|
222
222
|
|
|
223
223
|
private readonly semaphore = Effect.unsafeMakeSemaphore(1)
|
|
224
224
|
private position: bigint = 0n
|
|
@@ -226,19 +226,22 @@ const makeFile = (() => {
|
|
|
226
226
|
constructor(
|
|
227
227
|
readonly fd: FileSystem.File.Descriptor,
|
|
228
228
|
private readonly append: boolean
|
|
229
|
-
) {
|
|
229
|
+
) {
|
|
230
|
+
this[FileSystem.FileTypeId] = FileSystem.FileTypeId
|
|
231
|
+
}
|
|
230
232
|
|
|
231
233
|
get stat() {
|
|
232
234
|
return Effect.map(nodeStat(this.fd), makeFileInfo)
|
|
233
235
|
}
|
|
234
236
|
|
|
235
|
-
seek(offset: FileSystem.
|
|
237
|
+
seek(offset: FileSystem.SizeInput, from: FileSystem.SeekMode) {
|
|
238
|
+
const offsetSize = FileSystem.Size(offset)
|
|
236
239
|
return this.semaphore.withPermits(1)(
|
|
237
240
|
Effect.sync(() => {
|
|
238
241
|
if (from === "start") {
|
|
239
|
-
this.position =
|
|
242
|
+
this.position = offsetSize
|
|
240
243
|
} else if (from === "current") {
|
|
241
|
-
this.position = this.position +
|
|
244
|
+
this.position = this.position + offsetSize
|
|
242
245
|
}
|
|
243
246
|
|
|
244
247
|
return this.position
|
|
@@ -264,9 +267,10 @@ const makeFile = (() => {
|
|
|
264
267
|
)
|
|
265
268
|
}
|
|
266
269
|
|
|
267
|
-
readAlloc(size: FileSystem.
|
|
270
|
+
readAlloc(size: FileSystem.SizeInput) {
|
|
271
|
+
const sizeNumber = Number(size)
|
|
268
272
|
return this.semaphore.withPermits(1)(Effect.flatMap(
|
|
269
|
-
Effect.sync(() => Buffer.allocUnsafeSlow(
|
|
273
|
+
Effect.sync(() => Buffer.allocUnsafeSlow(sizeNumber)),
|
|
270
274
|
(buffer) =>
|
|
271
275
|
Effect.map(
|
|
272
276
|
nodeReadAlloc(this.fd, {
|
|
@@ -279,7 +283,7 @@ const makeFile = (() => {
|
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
this.position = this.position + BigInt(bytesRead)
|
|
282
|
-
if (bytesRead ===
|
|
286
|
+
if (bytesRead === sizeNumber) {
|
|
283
287
|
return Option.some(buffer)
|
|
284
288
|
}
|
|
285
289
|
|
|
@@ -291,7 +295,7 @@ const makeFile = (() => {
|
|
|
291
295
|
))
|
|
292
296
|
}
|
|
293
297
|
|
|
294
|
-
truncate(length?: FileSystem.
|
|
298
|
+
truncate(length?: FileSystem.SizeInput) {
|
|
295
299
|
return this.semaphore.withPermits(1)(
|
|
296
300
|
Effect.map(nodeTruncate(this.fd, length ? Number(length) : undefined), () => {
|
|
297
301
|
if (!this.append) {
|
|
@@ -505,7 +509,8 @@ const truncate = (() => {
|
|
|
505
509
|
handleErrnoException("FileSystem", "truncate"),
|
|
506
510
|
handleBadArgument("truncate")
|
|
507
511
|
)
|
|
508
|
-
return (path: string, length?: FileSystem.
|
|
512
|
+
return (path: string, length?: FileSystem.SizeInput) =>
|
|
513
|
+
nodeTruncate(path, length !== undefined ? Number(length) : undefined)
|
|
509
514
|
})()
|
|
510
515
|
|
|
511
516
|
// == utimes
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as Chunk from "@effect/data/Chunk"
|
|
2
|
+
import { pipe } from "@effect/data/Function"
|
|
3
|
+
import * as Option from "@effect/data/Option"
|
|
4
|
+
import * as Effect from "@effect/io/Effect"
|
|
5
|
+
import * as FiberRef from "@effect/io/FiberRef"
|
|
6
|
+
import * as NodeStream from "@effect/platform-node/Stream"
|
|
7
|
+
import * as FileSystem from "@effect/platform/FileSystem"
|
|
8
|
+
import * as FormData from "@effect/platform/Http/FormData"
|
|
9
|
+
import * as Path from "@effect/platform/Path"
|
|
10
|
+
import * as Stream from "@effect/stream/Stream"
|
|
11
|
+
import Busboy from "busboy"
|
|
12
|
+
import * as NodeFs from "node:fs"
|
|
13
|
+
import type * as Http from "node:http"
|
|
14
|
+
import type { Readable } from "node:stream"
|
|
15
|
+
import * as NodeStreamP from "node:stream/promises"
|
|
16
|
+
|
|
17
|
+
export const fromRequest = (
|
|
18
|
+
source: Http.IncomingMessage
|
|
19
|
+
): Stream.Stream<never, FormData.FormDataError, FormData.Part> =>
|
|
20
|
+
pipe(
|
|
21
|
+
Effect.Do,
|
|
22
|
+
Effect.bind("fieldMimeTypes", () => FiberRef.get(FormData.fieldMimeTypes)),
|
|
23
|
+
Effect.bind("maxFieldSize", () => FiberRef.get(FormData.maxFieldSize)),
|
|
24
|
+
Effect.bind("maxFileSize", () => FiberRef.get(FormData.maxFileSize)),
|
|
25
|
+
Effect.bind("busboy", ({ maxFieldSize, maxFileSize }) =>
|
|
26
|
+
Effect.acquireRelease(
|
|
27
|
+
Effect.sync(
|
|
28
|
+
() =>
|
|
29
|
+
Busboy({
|
|
30
|
+
headers: source.headers,
|
|
31
|
+
limits: {
|
|
32
|
+
fieldSize: Number(maxFieldSize),
|
|
33
|
+
fileSize: Option.getOrUndefined(Option.map(maxFileSize, Number))
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
),
|
|
37
|
+
(busboy) =>
|
|
38
|
+
Effect.sync(() => {
|
|
39
|
+
busboy.removeAllListeners()
|
|
40
|
+
if (!busboy.closed) {
|
|
41
|
+
busboy.destroy()
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
)),
|
|
45
|
+
Effect.map(({ busboy, fieldMimeTypes }) =>
|
|
46
|
+
Stream.mapEffect(
|
|
47
|
+
Stream.async<never, FormData.FormDataError, FieldImpl | FileImpl>((emit) => {
|
|
48
|
+
busboy.on("field", (name, value, info) => {
|
|
49
|
+
if (info.valueTruncated) {
|
|
50
|
+
emit.fail(FormData.FormDataError("FieldTooLarge", new Error("maxFieldSize exceeded")))
|
|
51
|
+
} else {
|
|
52
|
+
emit.single(new FieldImpl(name, info.mimeType, value))
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
busboy.on("file", (name, stream, info) => {
|
|
57
|
+
stream.once("limit", () => {
|
|
58
|
+
emit.fail(FormData.FormDataError("FileTooLarge", new Error("maxFileSize exceeded")))
|
|
59
|
+
})
|
|
60
|
+
emit.single(
|
|
61
|
+
new FileImpl(
|
|
62
|
+
name,
|
|
63
|
+
info.filename,
|
|
64
|
+
info.mimeType,
|
|
65
|
+
NodeStream.fromReadable(() => stream, (error) => FormData.FormDataError("InternalError", error)),
|
|
66
|
+
stream
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
busboy.on("error", (_) => {
|
|
72
|
+
emit.fail(FormData.FormDataError("InternalError", _))
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
busboy.on("finish", () => {
|
|
76
|
+
emit.end()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
source.pipe(busboy)
|
|
80
|
+
}),
|
|
81
|
+
(part) =>
|
|
82
|
+
part._tag === "File" && Chunk.some(fieldMimeTypes, (_) => part.contentType.includes(_)) ?
|
|
83
|
+
Effect.map(
|
|
84
|
+
NodeStream.toString({
|
|
85
|
+
readable: () => part.source,
|
|
86
|
+
onFailure: (error) => FormData.FormDataError("InternalError", error)
|
|
87
|
+
}),
|
|
88
|
+
(content) => new FieldImpl(part.key, part.contentType, content)
|
|
89
|
+
)
|
|
90
|
+
: Effect.succeed(part)
|
|
91
|
+
)
|
|
92
|
+
),
|
|
93
|
+
Stream.unwrapScoped
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
class FieldImpl implements FormData.Field {
|
|
97
|
+
readonly [FormData.TypeId]: FormData.TypeId
|
|
98
|
+
readonly _tag = "Field"
|
|
99
|
+
constructor(
|
|
100
|
+
readonly key: string,
|
|
101
|
+
readonly contentType: string,
|
|
102
|
+
readonly value: string
|
|
103
|
+
) {
|
|
104
|
+
this[FormData.TypeId] = FormData.TypeId
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class FileImpl implements FormData.File {
|
|
109
|
+
readonly [FormData.TypeId]: FormData.TypeId
|
|
110
|
+
readonly _tag = "File"
|
|
111
|
+
constructor(
|
|
112
|
+
readonly key: string,
|
|
113
|
+
readonly name: string,
|
|
114
|
+
readonly contentType: string,
|
|
115
|
+
readonly content: Stream.Stream<never, FormData.FormDataError, Uint8Array>,
|
|
116
|
+
readonly source: Readable
|
|
117
|
+
) {
|
|
118
|
+
this[FormData.TypeId] = FormData.TypeId
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @internal */
|
|
123
|
+
export const formData = (
|
|
124
|
+
source: Http.IncomingMessage
|
|
125
|
+
) =>
|
|
126
|
+
Effect.flatMap(
|
|
127
|
+
Effect.all([
|
|
128
|
+
Effect.mapError(
|
|
129
|
+
Effect.flatMap(FileSystem.FileSystem, (_) => _.makeTempDirectoryScoped()),
|
|
130
|
+
(error) => FormData.FormDataError("InternalError", error)
|
|
131
|
+
),
|
|
132
|
+
Path.Path
|
|
133
|
+
]),
|
|
134
|
+
([dir, path_]) =>
|
|
135
|
+
Stream.runFoldEffect(
|
|
136
|
+
fromRequest(source),
|
|
137
|
+
new globalThis.FormData(),
|
|
138
|
+
(formData, part) => {
|
|
139
|
+
if (part._tag === "Field") {
|
|
140
|
+
formData.append(part.key, part.value)
|
|
141
|
+
return Effect.succeed(formData)
|
|
142
|
+
}
|
|
143
|
+
const file = part as FileImpl
|
|
144
|
+
const path = path_.join(dir, file.name)
|
|
145
|
+
formData.append(part.key, new Blob([], { type: file.contentType }), path)
|
|
146
|
+
return Effect.as(
|
|
147
|
+
Effect.tryPromise({
|
|
148
|
+
try: (signal) =>
|
|
149
|
+
NodeStreamP.pipeline(file.source, NodeFs.createWriteStream(path), {
|
|
150
|
+
signal
|
|
151
|
+
}),
|
|
152
|
+
catch: (error) => FormData.FormDataError("InternalError", error)
|
|
153
|
+
}),
|
|
154
|
+
formData
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as Option from "@effect/data/Option"
|
|
2
|
+
import * as Effect from "@effect/io/Effect"
|
|
3
|
+
import * as FiberRef from "@effect/io/FiberRef"
|
|
4
|
+
import * as NodeStream from "@effect/platform-node/Stream"
|
|
5
|
+
import * as Headers from "@effect/platform/Http/Headers"
|
|
6
|
+
import * as IncomingMessage from "@effect/platform/Http/IncomingMessage"
|
|
7
|
+
import * as UrlParams from "@effect/platform/Http/UrlParams"
|
|
8
|
+
import type * as Stream from "@effect/stream/Stream"
|
|
9
|
+
import type * as Http from "node:http"
|
|
10
|
+
|
|
11
|
+
/** @internal */
|
|
12
|
+
export class IncomingMessageImpl<E> implements IncomingMessage.IncomingMessage<E> {
|
|
13
|
+
readonly [IncomingMessage.TypeId]: IncomingMessage.TypeId
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
readonly source: Http.IncomingMessage,
|
|
17
|
+
readonly onError: (error: unknown) => E
|
|
18
|
+
) {
|
|
19
|
+
this[IncomingMessage.TypeId] = IncomingMessage.TypeId
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get headers() {
|
|
23
|
+
return Headers.fromInput(this.source.headers as any)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private textEffect: Effect.Effect<never, E, string> | undefined
|
|
27
|
+
get text(): Effect.Effect<never, E, string> {
|
|
28
|
+
if (this.textEffect) {
|
|
29
|
+
return this.textEffect
|
|
30
|
+
}
|
|
31
|
+
this.textEffect = Effect.runSync(Effect.cached(
|
|
32
|
+
Effect.flatMap(
|
|
33
|
+
FiberRef.get(IncomingMessage.maxBodySize),
|
|
34
|
+
(maxBodySize) =>
|
|
35
|
+
NodeStream.toString({
|
|
36
|
+
readable: () => this.source,
|
|
37
|
+
onFailure: this.onError,
|
|
38
|
+
maxBytes: Option.getOrUndefined(maxBodySize)
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
))
|
|
42
|
+
return this.textEffect
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get json(): Effect.Effect<never, E, unknown> {
|
|
46
|
+
return Effect.tryMap(this.text, {
|
|
47
|
+
try: (_) => JSON.parse(_) as unknown,
|
|
48
|
+
catch: this.onError
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get urlParams(): Effect.Effect<never, E, UrlParams.UrlParams> {
|
|
53
|
+
return Effect.flatMap(this.text, (_) =>
|
|
54
|
+
Effect.try({
|
|
55
|
+
try: () => UrlParams.fromInput(new URLSearchParams(_)),
|
|
56
|
+
catch: this.onError
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get stream(): Stream.Stream<never, E, Uint8Array> {
|
|
61
|
+
return NodeStream.fromReadable<E, Uint8Array>(
|
|
62
|
+
() => this.source,
|
|
63
|
+
this.onError
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get arrayBuffer(): Effect.Effect<never, E, ArrayBuffer> {
|
|
68
|
+
return Effect.flatMap(
|
|
69
|
+
FiberRef.get(IncomingMessage.maxBodySize),
|
|
70
|
+
(maxBodySize) =>
|
|
71
|
+
NodeStream.toUint8Array({
|
|
72
|
+
readable: () => this.source,
|
|
73
|
+
onFailure: this.onError,
|
|
74
|
+
maxBytes: Option.getOrUndefined(maxBodySize)
|
|
75
|
+
})
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -4,15 +4,13 @@ import * as Effect from "@effect/io/Effect"
|
|
|
4
4
|
import * as Layer from "@effect/io/Layer"
|
|
5
5
|
import type * as Scope from "@effect/io/Scope"
|
|
6
6
|
import type * as NodeClient from "@effect/platform-node/Http/NodeClient"
|
|
7
|
+
import { IncomingMessageImpl } from "@effect/platform-node/internal/http/incomingMessage"
|
|
7
8
|
import * as NodeSink from "@effect/platform-node/Sink"
|
|
8
|
-
import * as NodeStream from "@effect/platform-node/Stream"
|
|
9
9
|
import type * as Body from "@effect/platform/Http/Body"
|
|
10
10
|
import * as Client from "@effect/platform/Http/Client"
|
|
11
11
|
import * as Error from "@effect/platform/Http/ClientError"
|
|
12
12
|
import type * as ClientRequest from "@effect/platform/Http/ClientRequest"
|
|
13
13
|
import * as ClientResponse from "@effect/platform/Http/ClientResponse"
|
|
14
|
-
import * as Headers from "@effect/platform/Http/Headers"
|
|
15
|
-
import * as IncomingMessage from "@effect/platform/Http/IncomingMessage"
|
|
16
14
|
import * as UrlParams from "@effect/platform/Http/UrlParams"
|
|
17
15
|
import * as Stream from "@effect/stream/Stream"
|
|
18
16
|
import * as Http from "node:http"
|
|
@@ -49,7 +47,11 @@ export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<Scope.Sco
|
|
|
49
47
|
)
|
|
50
48
|
|
|
51
49
|
/** @internal */
|
|
52
|
-
export const
|
|
50
|
+
export const makeAgentLayer = (options?: Https.AgentOptions): Layer.Layer<never, never, NodeClient.HttpAgent> =>
|
|
51
|
+
Layer.scoped(HttpAgent, makeAgent(options))
|
|
52
|
+
|
|
53
|
+
/** @internal */
|
|
54
|
+
export const agentLayer = makeAgentLayer()
|
|
53
55
|
|
|
54
56
|
const fromAgent = (agent: NodeClient.HttpAgent): Client.Client.Default =>
|
|
55
57
|
Client.make((request) =>
|
|
@@ -90,7 +92,7 @@ const fromAgent = (agent: NodeClient.HttpAgent): Client.Client.Default =>
|
|
|
90
92
|
const sendBody = (
|
|
91
93
|
nodeRequest: Http.ClientRequest,
|
|
92
94
|
request: ClientRequest.ClientRequest,
|
|
93
|
-
body: Body.
|
|
95
|
+
body: Body.NonEffect
|
|
94
96
|
): Effect.Effect<never, Error.RequestError, void> =>
|
|
95
97
|
Effect.suspend((): Effect.Effect<never, Error.RequestError, void> => {
|
|
96
98
|
switch (body._tag) {
|
|
@@ -98,7 +100,7 @@ const sendBody = (
|
|
|
98
100
|
nodeRequest.end()
|
|
99
101
|
return waitForFinish(nodeRequest, request)
|
|
100
102
|
}
|
|
101
|
-
case "
|
|
103
|
+
case "Uint8Array":
|
|
102
104
|
case "Raw": {
|
|
103
105
|
nodeRequest.end(body.body)
|
|
104
106
|
return waitForFinish(nodeRequest, request)
|
|
@@ -120,20 +122,6 @@ const sendBody = (
|
|
|
120
122
|
})
|
|
121
123
|
})
|
|
122
124
|
}
|
|
123
|
-
case "BytesEffect": {
|
|
124
|
-
return Effect.flatMap(
|
|
125
|
-
Effect.mapError(body.body, (_) =>
|
|
126
|
-
Error.RequestError({
|
|
127
|
-
request,
|
|
128
|
-
reason: "Encode",
|
|
129
|
-
error: _
|
|
130
|
-
})),
|
|
131
|
-
(bytes) => {
|
|
132
|
-
nodeRequest.end(bytes)
|
|
133
|
-
return waitForFinish(nodeRequest, request)
|
|
134
|
-
}
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
125
|
case "Stream": {
|
|
138
126
|
return Stream.run(
|
|
139
127
|
Stream.mapError(body.stream, (_) =>
|
|
@@ -183,44 +171,25 @@ const waitForFinish = (nodeRequest: Http.ClientRequest, request: ClientRequest.C
|
|
|
183
171
|
})
|
|
184
172
|
})
|
|
185
173
|
|
|
186
|
-
class ClientResponseImpl implements ClientResponse.ClientResponse {
|
|
187
|
-
readonly [
|
|
188
|
-
readonly [ClientResponse.TypeId]: ClientResponse.TypeId = ClientResponse.TypeId
|
|
174
|
+
class ClientResponseImpl extends IncomingMessageImpl<Error.ResponseError> implements ClientResponse.ClientResponse {
|
|
175
|
+
readonly [ClientResponse.TypeId]: ClientResponse.TypeId
|
|
189
176
|
|
|
190
177
|
constructor(
|
|
191
178
|
readonly request: ClientRequest.ClientRequest,
|
|
192
|
-
|
|
193
|
-
) {
|
|
194
|
-
|
|
195
|
-
get status() {
|
|
196
|
-
return this.source.statusCode!
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
get headers() {
|
|
200
|
-
return Headers.fromInput(this.source.headers as any)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
get text(): Effect.Effect<never, Error.ResponseError, string> {
|
|
204
|
-
return NodeStream.toString(() => this.source, (_) =>
|
|
179
|
+
source: Http.IncomingMessage
|
|
180
|
+
) {
|
|
181
|
+
super(source, (_) =>
|
|
205
182
|
Error.ResponseError({
|
|
206
|
-
request
|
|
183
|
+
request,
|
|
207
184
|
response: this,
|
|
208
185
|
reason: "Decode",
|
|
209
186
|
error: _
|
|
210
187
|
}))
|
|
188
|
+
this[ClientResponse.TypeId] = ClientResponse.TypeId
|
|
211
189
|
}
|
|
212
190
|
|
|
213
|
-
get
|
|
214
|
-
return
|
|
215
|
-
try: (_) => JSON.parse(_) as unknown,
|
|
216
|
-
catch: (_) =>
|
|
217
|
-
Error.ResponseError({
|
|
218
|
-
request: this.request,
|
|
219
|
-
response: this,
|
|
220
|
-
reason: "Decode",
|
|
221
|
-
error: _
|
|
222
|
-
})
|
|
223
|
-
})
|
|
191
|
+
get status() {
|
|
192
|
+
return this.source.statusCode!
|
|
224
193
|
}
|
|
225
194
|
|
|
226
195
|
get formData(): Effect.Effect<never, Error.ResponseError, FormData> {
|
|
@@ -231,37 +200,20 @@ class ClientResponseImpl implements ClientResponse.ClientResponse {
|
|
|
231
200
|
status: this.source.statusCode,
|
|
232
201
|
statusText: this.source.statusMessage
|
|
233
202
|
}).formData(),
|
|
234
|
-
catch:
|
|
235
|
-
Error.ResponseError({
|
|
236
|
-
request: this.request,
|
|
237
|
-
response: this,
|
|
238
|
-
reason: "Decode",
|
|
239
|
-
error: _
|
|
240
|
-
})
|
|
203
|
+
catch: this.onError
|
|
241
204
|
})
|
|
242
205
|
}
|
|
243
206
|
|
|
244
|
-
|
|
245
|
-
return
|
|
246
|
-
() => this.source,
|
|
247
|
-
(_) =>
|
|
248
|
-
Error.ResponseError({
|
|
249
|
-
request: this.request,
|
|
250
|
-
response: this,
|
|
251
|
-
reason: "Decode",
|
|
252
|
-
error: _
|
|
253
|
-
})
|
|
254
|
-
)
|
|
207
|
+
toString(): string {
|
|
208
|
+
return `ClientResponse(${this.status})`
|
|
255
209
|
}
|
|
256
210
|
|
|
257
|
-
|
|
258
|
-
return
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
error: _
|
|
264
|
-
}))
|
|
211
|
+
toJSON(): unknown {
|
|
212
|
+
return {
|
|
213
|
+
_tag: "ClientResponse",
|
|
214
|
+
status: this.status,
|
|
215
|
+
headers: Object.fromEntries(this.headers)
|
|
216
|
+
}
|
|
265
217
|
}
|
|
266
218
|
}
|
|
267
219
|
|
|
@@ -269,7 +221,7 @@ class ClientResponseImpl implements ClientResponse.ClientResponse {
|
|
|
269
221
|
export const make = Effect.map(HttpAgent, fromAgent)
|
|
270
222
|
|
|
271
223
|
/** @internal */
|
|
272
|
-
export const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
224
|
+
export const layerWithoutAgent = Layer.effect(Client.Client, make)
|
|
225
|
+
|
|
226
|
+
/** @internal */
|
|
227
|
+
export const layer = Layer.provide(agentLayer, layerWithoutAgent)
|