@effect/platform-node 0.11.5 → 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 +28 -65
  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 +25 -64
  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 +30 -78
  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,7 +47,11 @@ 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))
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.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 "Bytes":
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 [IncomingMessage.TypeId]: IncomingMessage.TypeId = IncomingMessage.TypeId
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
- readonly source: Http.IncomingMessage
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: this.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 json(): Effect.Effect<never, Error.ResponseError, unknown> {
214
- return Effect.tryMap(this.text, {
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
- get stream(): Stream.Stream<never, Error.ResponseError, Uint8Array> {
245
- return NodeStream.fromReadable<Error.ResponseError, Uint8Array>(
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
- get arrayBuffer(): Effect.Effect<never, Error.ResponseError, ArrayBuffer> {
258
- return NodeStream.toUint8Array(() => this.source, (_) =>
259
- Error.ResponseError({
260
- request: this.request,
261
- response: this,
262
- reason: "Decode",
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 layer = Layer.provide(
273
- agentLayer,
274
- Layer.effect(Client.Client, make)
275
- )
224
+ export const layerWithoutAgent = Layer.effect(Client.Client, make)
225
+
226
+ /** @internal */
227
+ export const layer = Layer.provide(agentLayer, layerWithoutAgent)