@effect/platform-node 0.11.4 → 0.12.0

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.
Files changed (63) hide show
  1. package/Http/NodeClient.d.ts +10 -0
  2. package/Http/NodeClient.d.ts.map +1 -1
  3. package/Http/NodeClient.js +14 -2
  4. package/Http/NodeClient.js.map +1 -1
  5. package/Http/Server.d.ts +34 -0
  6. package/Http/Server.d.ts.map +1 -0
  7. package/Http/Server.js +49 -0
  8. package/Http/Server.js.map +1 -0
  9. package/HttpServer.d.ts +9 -0
  10. package/HttpServer.d.ts.map +1 -0
  11. package/HttpServer.js +26 -0
  12. package/HttpServer.js.map +1 -0
  13. package/Stream.d.ts +14 -6
  14. package/Stream.d.ts.map +1 -1
  15. package/Stream.js.map +1 -1
  16. package/internal/fileSystem.js +9 -9
  17. package/internal/fileSystem.js.map +1 -1
  18. package/internal/http/formData.d.ts +5 -0
  19. package/internal/http/formData.d.ts.map +1 -0
  20. package/internal/http/formData.js +106 -0
  21. package/internal/http/formData.js.map +1 -0
  22. package/internal/http/incomingMessage.d.ts +2 -0
  23. package/internal/http/incomingMessage.d.ts.map +1 -0
  24. package/internal/http/incomingMessage.js +62 -0
  25. package/internal/http/incomingMessage.js.map +1 -0
  26. package/internal/http/nodeClient.js +30 -67
  27. package/internal/http/nodeClient.js.map +1 -1
  28. package/internal/http/server.d.ts +2 -0
  29. package/internal/http/server.d.ts.map +1 -0
  30. package/internal/http/server.js +218 -0
  31. package/internal/http/server.js.map +1 -0
  32. package/internal/stream.js +54 -35
  33. package/internal/stream.js.map +1 -1
  34. package/mjs/Http/NodeClient.mjs +10 -0
  35. package/mjs/Http/NodeClient.mjs.map +1 -1
  36. package/mjs/Http/Server.mjs +21 -0
  37. package/mjs/Http/Server.mjs.map +1 -0
  38. package/mjs/HttpServer.mjs +7 -0
  39. package/mjs/HttpServer.mjs.map +1 -0
  40. package/mjs/Stream.mjs.map +1 -1
  41. package/mjs/internal/fileSystem.mjs +9 -9
  42. package/mjs/internal/fileSystem.mjs.map +1 -1
  43. package/mjs/internal/http/formData.mjs +95 -0
  44. package/mjs/internal/http/formData.mjs.map +1 -0
  45. package/mjs/internal/http/incomingMessage.mjs +53 -0
  46. package/mjs/internal/http/incomingMessage.mjs.map +1 -0
  47. package/mjs/internal/http/nodeClient.mjs +27 -66
  48. package/mjs/internal/http/nodeClient.mjs.map +1 -1
  49. package/mjs/internal/http/server.mjs +207 -0
  50. package/mjs/internal/http/server.mjs.map +1 -0
  51. package/mjs/internal/stream.mjs +54 -35
  52. package/mjs/internal/stream.mjs.map +1 -1
  53. package/package.json +5 -2
  54. package/src/Http/NodeClient.ts +13 -0
  55. package/src/Http/Server.ts +45 -0
  56. package/src/HttpServer.ts +10 -0
  57. package/src/Stream.ts +14 -9
  58. package/src/internal/fileSystem.ts +16 -11
  59. package/src/internal/http/formData.ts +158 -0
  60. package/src/internal/http/incomingMessage.ts +78 -0
  61. package/src/internal/http/nodeClient.ts +64 -110
  62. package/src/internal/http/server.ts +323 -0
  63. 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
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+
5
+ export * from "@effect/platform/HttpServer"
6
+
7
+ /**
8
+ * @since 1.0.0
9
+ */
10
+ export * as server from "@effect/platform-node/Http/Server"
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 { Size } from "@effect/platform/FileSystem"
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 None, which lets Node.js decide the chunk size */
18
- readonly chunkSize?: Option<Size>
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
- evaluate: LazyArg<Readable>,
37
- onError: (error: unknown) => E,
38
- encoding?: BufferEncoding
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
- evaluate: LazyArg<Readable>,
47
- onError: (error: unknown) => E
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 { identity, pipe } from "@effect/data/Function"
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] = identity
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.Size, from: FileSystem.SeekMode) {
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 = offset
242
+ this.position = offsetSize
240
243
  } else if (from === "current") {
241
- this.position = this.position + offset
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.Size) {
270
+ readAlloc(size: FileSystem.SizeInput) {
271
+ const sizeNumber = Number(size)
268
272
  return this.semaphore.withPermits(1)(Effect.flatMap(
269
- Effect.sync(() => Buffer.allocUnsafeSlow(Number(size))),
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 === Number(size)) {
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.Size) {
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.Size) => nodeTruncate(path, length ? Number(length) : undefined)
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,46 +47,52 @@ export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<Scope.Sco
49
47
  )
50
48
 
51
49
  /** @internal */
52
- export const agentLayer = Layer.scoped(HttpAgent, makeAgent())
50
+ export const makeAgentLayer = (options?: Https.AgentOptions): Layer.Layer<never, never, NodeClient.HttpAgent> =>
51
+ Layer.scoped(HttpAgent, makeAgent(options))
53
52
 
54
- const fromAgent = (agent: NodeClient.HttpAgent): Client.Client.Default => (request) =>
55
- Effect.flatMap(
56
- UrlParams.makeUrl(request.url, request.urlParams, (_) =>
57
- Error.RequestError({
58
- request,
59
- reason: "InvalidUrl",
60
- error: _
61
- })),
62
- (url) =>
63
- Effect.suspend(() => {
64
- const controller = new AbortController()
65
- const nodeRequest = url.protocol === "https:" ?
66
- Https.request(url, {
67
- agent: agent.https,
68
- method: request.method,
69
- headers: Object.fromEntries(request.headers),
70
- signal: controller.signal
71
- }) :
72
- Http.request(url, {
73
- agent: agent.http,
74
- method: request.method,
75
- headers: Object.fromEntries(request.headers),
76
- signal: controller.signal
77
- })
78
- return pipe(
79
- Effect.zipRight(sendBody(nodeRequest, request, request.body), waitForResponse(nodeRequest), {
80
- concurrent: true
81
- }),
82
- Effect.onInterrupt(() => Effect.sync(() => controller.abort())),
83
- Effect.map((_) => new ClientResponseImpl(request, _))
84
- )
85
- })
53
+ /** @internal */
54
+ export const agentLayer = makeAgentLayer()
55
+
56
+ const fromAgent = (agent: NodeClient.HttpAgent): Client.Client.Default =>
57
+ Client.make((request) =>
58
+ Effect.flatMap(
59
+ UrlParams.makeUrl(request.url, request.urlParams, (_) =>
60
+ Error.RequestError({
61
+ request,
62
+ reason: "InvalidUrl",
63
+ error: _
64
+ })),
65
+ (url) =>
66
+ Effect.suspend(() => {
67
+ const controller = new AbortController()
68
+ const nodeRequest = url.protocol === "https:" ?
69
+ Https.request(url, {
70
+ agent: agent.https,
71
+ method: request.method,
72
+ headers: Object.fromEntries(request.headers),
73
+ signal: controller.signal
74
+ }) :
75
+ Http.request(url, {
76
+ agent: agent.http,
77
+ method: request.method,
78
+ headers: Object.fromEntries(request.headers),
79
+ signal: controller.signal
80
+ })
81
+ return pipe(
82
+ Effect.zipRight(sendBody(nodeRequest, request, request.body), waitForResponse(nodeRequest), {
83
+ concurrent: true
84
+ }),
85
+ Effect.onInterrupt(() => Effect.sync(() => controller.abort())),
86
+ Effect.map((_) => new ClientResponseImpl(request, _))
87
+ )
88
+ })
89
+ )
86
90
  )
87
91
 
88
92
  const sendBody = (
89
93
  nodeRequest: Http.ClientRequest,
90
94
  request: ClientRequest.ClientRequest,
91
- body: Body.Body
95
+ body: Body.NonEffect
92
96
  ): Effect.Effect<never, Error.RequestError, void> =>
93
97
  Effect.suspend((): Effect.Effect<never, Error.RequestError, void> => {
94
98
  switch (body._tag) {
@@ -96,7 +100,7 @@ const sendBody = (
96
100
  nodeRequest.end()
97
101
  return waitForFinish(nodeRequest, request)
98
102
  }
99
- case "Bytes":
103
+ case "Uint8Array":
100
104
  case "Raw": {
101
105
  nodeRequest.end(body.body)
102
106
  return waitForFinish(nodeRequest, request)
@@ -118,20 +122,6 @@ const sendBody = (
118
122
  })
119
123
  })
120
124
  }
121
- case "BytesEffect": {
122
- return Effect.flatMap(
123
- Effect.mapError(body.body, (_) =>
124
- Error.RequestError({
125
- request,
126
- reason: "Encode",
127
- error: _
128
- })),
129
- (bytes) => {
130
- nodeRequest.end(bytes)
131
- return waitForFinish(nodeRequest, request)
132
- }
133
- )
134
- }
135
125
  case "Stream": {
136
126
  return Stream.run(
137
127
  Stream.mapError(body.stream, (_) =>
@@ -181,44 +171,25 @@ const waitForFinish = (nodeRequest: Http.ClientRequest, request: ClientRequest.C
181
171
  })
182
172
  })
183
173
 
184
- class ClientResponseImpl implements ClientResponse.ClientResponse {
185
- readonly [IncomingMessage.TypeId]: IncomingMessage.TypeId = IncomingMessage.TypeId
186
- readonly [ClientResponse.TypeId]: ClientResponse.TypeId = ClientResponse.TypeId
174
+ class ClientResponseImpl extends IncomingMessageImpl<Error.ResponseError> implements ClientResponse.ClientResponse {
175
+ readonly [ClientResponse.TypeId]: ClientResponse.TypeId
187
176
 
188
177
  constructor(
189
178
  readonly request: ClientRequest.ClientRequest,
190
- readonly source: Http.IncomingMessage
191
- ) {}
192
-
193
- get status() {
194
- return this.source.statusCode!
195
- }
196
-
197
- get headers() {
198
- return Headers.fromInput(this.source.headers as any)
199
- }
200
-
201
- get text(): Effect.Effect<never, Error.ResponseError, string> {
202
- return NodeStream.toString(() => this.source, (_) =>
179
+ source: Http.IncomingMessage
180
+ ) {
181
+ super(source, (_) =>
203
182
  Error.ResponseError({
204
- request: this.request,
183
+ request,
205
184
  response: this,
206
185
  reason: "Decode",
207
186
  error: _
208
187
  }))
188
+ this[ClientResponse.TypeId] = ClientResponse.TypeId
209
189
  }
210
190
 
211
- get json(): Effect.Effect<never, Error.ResponseError, unknown> {
212
- return Effect.tryMap(this.text, {
213
- try: (_) => JSON.parse(_) as unknown,
214
- catch: (_) =>
215
- Error.ResponseError({
216
- request: this.request,
217
- response: this,
218
- reason: "Decode",
219
- error: _
220
- })
221
- })
191
+ get status() {
192
+ return this.source.statusCode!
222
193
  }
223
194
 
224
195
  get formData(): Effect.Effect<never, Error.ResponseError, FormData> {
@@ -229,37 +200,20 @@ class ClientResponseImpl implements ClientResponse.ClientResponse {
229
200
  status: this.source.statusCode,
230
201
  statusText: this.source.statusMessage
231
202
  }).formData(),
232
- catch: (_) =>
233
- Error.ResponseError({
234
- request: this.request,
235
- response: this,
236
- reason: "Decode",
237
- error: _
238
- })
203
+ catch: this.onError
239
204
  })
240
205
  }
241
206
 
242
- get stream(): Stream.Stream<never, Error.ResponseError, Uint8Array> {
243
- return NodeStream.fromReadable<Error.ResponseError, Uint8Array>(
244
- () => this.source,
245
- (_) =>
246
- Error.ResponseError({
247
- request: this.request,
248
- response: this,
249
- reason: "Decode",
250
- error: _
251
- })
252
- )
207
+ toString(): string {
208
+ return `ClientResponse(${this.status})`
253
209
  }
254
210
 
255
- get arrayBuffer(): Effect.Effect<never, Error.ResponseError, ArrayBuffer> {
256
- return NodeStream.toUint8Array(() => this.source, (_) =>
257
- Error.ResponseError({
258
- request: this.request,
259
- response: this,
260
- reason: "Decode",
261
- error: _
262
- }))
211
+ toJSON(): unknown {
212
+ return {
213
+ _tag: "ClientResponse",
214
+ status: this.status,
215
+ headers: Object.fromEntries(this.headers)
216
+ }
263
217
  }
264
218
  }
265
219
 
@@ -267,7 +221,7 @@ class ClientResponseImpl implements ClientResponse.ClientResponse {
267
221
  export const make = Effect.map(HttpAgent, fromAgent)
268
222
 
269
223
  /** @internal */
270
- export const layer = Layer.provide(
271
- agentLayer,
272
- Layer.effect(Client.Client, make)
273
- )
224
+ export const layerWithoutAgent = Layer.effect(Client.Client, make)
225
+
226
+ /** @internal */
227
+ export const layer = Layer.provide(agentLayer, layerWithoutAgent)