@effect/platform-node 0.22.0 → 0.23.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 (68) hide show
  1. package/CommandExecutor/dist/effect-platform-node-CommandExecutor.cjs.dev.js +8 -2
  2. package/CommandExecutor/dist/effect-platform-node-CommandExecutor.cjs.prod.js +8 -2
  3. package/CommandExecutor/dist/effect-platform-node-CommandExecutor.esm.js +8 -2
  4. package/Http/FormData/dist/effect-platform-node-Http-FormData.cjs.dev.js +8 -3
  5. package/Http/FormData/dist/effect-platform-node-Http-FormData.cjs.prod.js +8 -3
  6. package/Http/FormData/dist/effect-platform-node-Http-FormData.esm.js +8 -3
  7. package/Http/NodeClient/dist/effect-platform-node-Http-NodeClient.cjs.dev.js +11 -4
  8. package/Http/NodeClient/dist/effect-platform-node-Http-NodeClient.cjs.prod.js +11 -4
  9. package/Http/NodeClient/dist/effect-platform-node-Http-NodeClient.esm.js +11 -4
  10. package/Http/Server/dist/effect-platform-node-Http-Server.cjs.dev.js +13 -8
  11. package/Http/Server/dist/effect-platform-node-Http-Server.cjs.prod.js +13 -8
  12. package/Http/Server/dist/effect-platform-node-Http-Server.esm.js +13 -8
  13. package/HttpClient/dist/effect-platform-node-HttpClient.cjs.dev.js +11 -4
  14. package/HttpClient/dist/effect-platform-node-HttpClient.cjs.prod.js +11 -4
  15. package/HttpClient/dist/effect-platform-node-HttpClient.esm.js +11 -4
  16. package/HttpServer/dist/effect-platform-node-HttpServer.cjs.dev.js +12 -7
  17. package/HttpServer/dist/effect-platform-node-HttpServer.cjs.prod.js +12 -7
  18. package/HttpServer/dist/effect-platform-node-HttpServer.esm.js +12 -7
  19. package/NodeContext/dist/effect-platform-node-NodeContext.cjs.dev.js +8 -2
  20. package/NodeContext/dist/effect-platform-node-NodeContext.cjs.prod.js +8 -2
  21. package/NodeContext/dist/effect-platform-node-NodeContext.esm.js +8 -2
  22. package/Sink/dist/effect-platform-node-Sink.cjs.dev.js +11 -8
  23. package/Sink/dist/effect-platform-node-Sink.cjs.prod.js +11 -8
  24. package/Sink/dist/effect-platform-node-Sink.esm.js +11 -8
  25. package/Stream/dist/effect-platform-node-Stream.cjs.dev.js +33 -2
  26. package/Stream/dist/effect-platform-node-Stream.cjs.mjs +3 -0
  27. package/Stream/dist/effect-platform-node-Stream.cjs.prod.js +33 -2
  28. package/Stream/dist/effect-platform-node-Stream.esm.js +31 -3
  29. package/dist/{FormData-15af6672.cjs.dev.js → FormData-1b197f9f.cjs.dev.js} +1 -1
  30. package/dist/{FormData-5ea8a8b6.esm.js → FormData-9c8077af.esm.js} +1 -1
  31. package/dist/{FormData-d91e8016.cjs.prod.js → FormData-b4b59ecb.cjs.prod.js} +1 -1
  32. package/dist/{NodeClient-cd56cae8.esm.js → NodeClient-1b5f7152.esm.js} +2 -2
  33. package/dist/{NodeClient-ebd71893.cjs.dev.js → NodeClient-433f41ed.cjs.dev.js} +2 -2
  34. package/dist/{NodeClient-3432a6a8.cjs.prod.js → NodeClient-8d8ff956.cjs.prod.js} +2 -2
  35. package/dist/{Server-5dbcee7b.cjs.prod.js → Server-2dd836bc.cjs.prod.js} +3 -3
  36. package/dist/{Server-5f055bfd.cjs.dev.js → Server-66eb964d.cjs.dev.js} +3 -3
  37. package/dist/{Server-c08c825c.esm.js → Server-913d7080.esm.js} +3 -3
  38. package/dist/declarations/src/Sink.d.ts +2 -9
  39. package/dist/declarations/src/Sink.d.ts.map +1 -1
  40. package/dist/declarations/src/Stream.d.ts +42 -11
  41. package/dist/declarations/src/Stream.d.ts.map +1 -1
  42. package/dist/{formData-ecf6742b.cjs.prod.js → formData-2d4168a3.cjs.prod.js} +1 -2
  43. package/dist/{formData-632b1146.cjs.dev.js → formData-5d873a90.cjs.dev.js} +1 -2
  44. package/dist/{formData-dd75bbe1.esm.js → formData-b50a3c9f.esm.js} +1 -2
  45. package/dist/{incomingMessage-f56be93e.cjs.prod.js → incomingMessage-4526b216.cjs.prod.js} +2 -4
  46. package/dist/{incomingMessage-11c9bea6.esm.js → incomingMessage-a56317f6.esm.js} +2 -4
  47. package/dist/{incomingMessage-86bcf94d.cjs.dev.js → incomingMessage-ac1817d4.cjs.dev.js} +2 -4
  48. package/dist/sink-3a150604.cjs.dev.js +48 -0
  49. package/dist/sink-570c8582.cjs.prod.js +48 -0
  50. package/dist/sink-5dfcc09e.esm.js +23 -0
  51. package/dist/stream-1667e8bf.cjs.prod.js +209 -0
  52. package/dist/stream-8bbecb96.cjs.dev.js +209 -0
  53. package/dist/stream-faaffb40.esm.js +175 -0
  54. package/package.json +5 -3
  55. package/src/Sink.ts +3 -11
  56. package/src/Stream.ts +59 -13
  57. package/src/internal/http/formData.ts +1 -2
  58. package/src/internal/http/incomingMessage.ts +2 -4
  59. package/src/internal/http/nodeClient.ts +1 -1
  60. package/src/internal/http/server.ts +3 -1
  61. package/src/internal/sink.ts +42 -47
  62. package/src/internal/stream.ts +242 -61
  63. package/dist/sink-bd7ef408.esm.js +0 -34
  64. package/dist/sink-da49e187.cjs.prod.js +0 -57
  65. package/dist/sink-daf9e0e5.cjs.dev.js +0 -57
  66. package/dist/stream-1456ece0.cjs.dev.js +0 -120
  67. package/dist/stream-860139d3.esm.js +0 -94
  68. package/dist/stream-ef8b6a66.cjs.prod.js +0 -120
@@ -0,0 +1,175 @@
1
+ import * as Channel from 'effect/Channel';
2
+ import * as Chunk from 'effect/Chunk';
3
+ import * as Effect from 'effect/Effect';
4
+ import * as Either from 'effect/Either';
5
+ import * as Exit from 'effect/Exit';
6
+ import { dual, pipe } from 'effect/Function';
7
+ import * as Queue from 'effect/Queue';
8
+ import * as Stream from 'effect/Stream';
9
+ import { SystemError } from '@effect/platform/Error';
10
+
11
+ /** @internal */
12
+ const fromReadable = (evaluate, onError, {
13
+ chunkSize
14
+ } = {}) => Stream.fromChannel(readChannel(evaluate, onError, chunkSize ? Number(chunkSize) : undefined));
15
+
16
+ /** @internal */
17
+ const toString = (readable, options) => {
18
+ const maxBytesNumber = options.maxBytes ? Number(options.maxBytes) : undefined;
19
+ return Effect.acquireUseRelease(Effect.sync(() => {
20
+ const stream = readable();
21
+ stream.setEncoding(options.encoding ?? "utf8");
22
+ return stream;
23
+ }), stream => Effect.async(resume => {
24
+ let string = "";
25
+ let bytes = 0;
26
+ stream.once("error", err => {
27
+ resume(Effect.fail(options.onFailure(err)));
28
+ });
29
+ stream.once("end", () => {
30
+ resume(Effect.succeed(string));
31
+ });
32
+ stream.on("data", chunk => {
33
+ string += chunk;
34
+ bytes += Buffer.byteLength(chunk);
35
+ if (maxBytesNumber && bytes > maxBytesNumber) {
36
+ resume(Effect.fail(options.onFailure(new Error("maxBytes exceeded"))));
37
+ }
38
+ });
39
+ }), stream => Effect.sync(() => {
40
+ stream.removeAllListeners();
41
+ if ("closed" in stream && !stream.closed) {
42
+ stream.destroy();
43
+ }
44
+ }));
45
+ };
46
+
47
+ /** @internal */
48
+ const toUint8Array = (readable, options) => {
49
+ const maxBytesNumber = options.maxBytes ? Number(options.maxBytes) : undefined;
50
+ return Effect.acquireUseRelease(Effect.sync(readable), stream => Effect.async(resume => {
51
+ let buffer = Buffer.alloc(0);
52
+ let bytes = 0;
53
+ stream.once("error", err => {
54
+ resume(Effect.fail(options.onFailure(err)));
55
+ });
56
+ stream.once("end", () => {
57
+ resume(Effect.succeed(buffer));
58
+ });
59
+ stream.on("data", chunk => {
60
+ buffer = Buffer.concat([buffer, chunk]);
61
+ bytes += chunk.length;
62
+ if (maxBytesNumber && bytes > maxBytesNumber) {
63
+ resume(Effect.fail(options.onFailure(new Error("maxBytes exceeded"))));
64
+ }
65
+ });
66
+ }), stream => Effect.sync(() => {
67
+ stream.removeAllListeners();
68
+ if ("closed" in stream && !stream.closed) {
69
+ stream.destroy();
70
+ }
71
+ }));
72
+ };
73
+
74
+ /** @internal */
75
+ const fromDuplex = (evaluate, onError, options = {}) => Channel.acquireUseRelease(Effect.tap(Effect.zip(Effect.sync(evaluate), Queue.unbounded()), ([duplex, queue]) => readableOffer(duplex, queue, onError)), ([duplex, queue]) => Channel.embedInput(readableTake(duplex, queue, options.chunkSize ? Number(options.chunkSize) : undefined), writeInput(duplex, cause => Queue.offer(queue, Either.left(Exit.failCause(cause))), options)), ([duplex, queue]) => Effect.zipRight(Effect.sync(() => {
76
+ duplex.removeAllListeners();
77
+ if (!duplex.closed) {
78
+ duplex.destroy();
79
+ }
80
+ }), Queue.shutdown(queue)));
81
+
82
+ /** @internal */
83
+ const pipeThroughDuplex = /*#__PURE__*/dual(args => Stream.StreamTypeId in args[0], (self, duplex, onError, options) => Stream.pipeThroughChannelOrFail(self, fromDuplex(duplex, onError, options)));
84
+
85
+ /** @internal */
86
+ const pipeThroughSimple = /*#__PURE__*/dual(2, (self, duplex) => Stream.pipeThroughChannelOrFail(self, fromDuplex(duplex, error => SystemError({
87
+ module: "Stream",
88
+ method: "pipeThroughSimple",
89
+ pathOrDescriptor: "",
90
+ reason: "Unknown",
91
+ message: String(error)
92
+ }))));
93
+ const readChannel = (evaluate, onError, chunkSize) => Channel.acquireUseRelease(Effect.tap(Effect.zip(Effect.sync(evaluate), Queue.unbounded()), ([readable, queue]) => readableOffer(readable, queue, onError)), ([readable, queue]) => readableTake(readable, queue, chunkSize), ([readable, queue]) => Effect.zipRight(Effect.sync(() => {
94
+ readable.removeAllListeners();
95
+ if ("closed" in readable && !readable.closed) {
96
+ readable.destroy();
97
+ }
98
+ }), Queue.shutdown(queue)));
99
+
100
+ /** @internal */
101
+ const writeInput = (writable, onFailure, {
102
+ encoding,
103
+ endOnDone = true
104
+ }, onDone = Effect.unit) => {
105
+ const write = writeEffect(writable, encoding);
106
+ const close = endOnDone ? Effect.async(resume => {
107
+ if ("closed" in writable && writable.closed) {
108
+ resume(Effect.unit);
109
+ } else {
110
+ writable.once("finish", () => resume(Effect.unit));
111
+ writable.end();
112
+ }
113
+ }) : Effect.unit;
114
+ return {
115
+ awaitRead: () => Effect.unit,
116
+ emit: write,
117
+ error: cause => Effect.zipRight(close, onFailure(cause)),
118
+ done: _ => Effect.zipRight(close, onDone)
119
+ };
120
+ };
121
+
122
+ /** @internal */
123
+ const writeEffect = (writable, encoding) => chunk => chunk.length === 0 ? Effect.unit : Effect.async(resume => {
124
+ const iterator = chunk[Symbol.iterator]();
125
+ let next = iterator.next();
126
+ function loop() {
127
+ const item = next;
128
+ next = iterator.next();
129
+ const success = writable.write(item.value, encoding);
130
+ if (next.done) {
131
+ resume(Effect.unit);
132
+ } else if (success) {
133
+ loop();
134
+ } else {
135
+ writable.once("drain", loop);
136
+ }
137
+ }
138
+ loop();
139
+ });
140
+ const readableOffer = (readable, queue, onError) => Effect.sync(() => {
141
+ readable.on("readable", () => {
142
+ const size = queue.unsafeSize();
143
+ if (size._tag === "Some" && size.value <= 0) {
144
+ queue.unsafeOffer(Either.right(void 0));
145
+ }
146
+ });
147
+ readable.on("error", err => {
148
+ queue.unsafeOffer(Either.left(Exit.fail(onError(err))));
149
+ });
150
+ readable.on("end", () => {
151
+ queue.unsafeOffer(Either.left(Exit.unit));
152
+ });
153
+ if (readable.readable) {
154
+ queue.unsafeOffer(Either.right(void 0));
155
+ }
156
+ });
157
+ const readableTake = (readable, queue, chunkSize) => {
158
+ const read = readChunkChannel(readable, chunkSize);
159
+ const loop = pipe(Channel.fromEffect(Queue.take(queue)), Channel.flatMap(Either.match({
160
+ onLeft: Channel.fromEffect,
161
+ onRight: _ => Channel.flatMap(read, () => loop)
162
+ })));
163
+ return loop;
164
+ };
165
+ const readChunkChannel = (readable, chunkSize) => Channel.flatMap(Channel.sync(() => {
166
+ const arr = [];
167
+ let chunk = readable.read(chunkSize);
168
+ while (chunk !== null) {
169
+ arr.push(chunk);
170
+ chunk = readable.read(chunkSize);
171
+ }
172
+ return Chunk.unsafeFromArray(arr);
173
+ }), Channel.write);
174
+
175
+ export { fromDuplex as a, pipeThroughSimple as b, toUint8Array as c, fromReadable as f, pipeThroughDuplex as p, toString as t, writeInput as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/platform-node",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Unified interfaces for common platform-specific services",
5
5
  "main": "dist/effect-platform-node.cjs.js",
6
6
  "module": "dist/effect-platform-node.esm.js",
@@ -36,7 +36,9 @@
36
36
  "@effect/schema": "^0.43.0",
37
37
  "@types/busboy": "^1.5.1",
38
38
  "@types/mime": "^3.0.2",
39
- "effect": "2.0.0-next.48"
39
+ "@types/tar": "^6.1.6",
40
+ "effect": "2.0.0-next.48",
41
+ "tar": "^6.2.0"
40
42
  },
41
43
  "peerDependencies": {
42
44
  "effect": "2.0.0-next.48"
@@ -44,7 +46,7 @@
44
46
  "dependencies": {
45
47
  "busboy": "^1.6.0",
46
48
  "mime": "^3.0.0",
47
- "@effect/platform": "^0.21.0"
49
+ "@effect/platform": "^0.22.0"
48
50
  },
49
51
  "files": [
50
52
  "src",
package/src/Sink.ts CHANGED
@@ -6,22 +6,14 @@ import type { LazyArg } from "effect/Function"
6
6
  import type { Sink } from "effect/Sink"
7
7
  import type { Writable } from "stream"
8
8
  import * as internal from "./internal/sink"
9
-
10
- /**
11
- * @category model
12
- * @since 1.0.0
13
- */
14
- export interface FromWritableOptions {
15
- readonly endOnClose?: boolean
16
- readonly encoding?: BufferEncoding
17
- }
9
+ import type { FromWritableOptions } from "./Stream"
18
10
 
19
11
  /**
20
12
  * @category constructor
21
13
  * @since 1.0.0
22
14
  */
23
- export const fromWritable: <E, A>(
24
- evaluate: LazyArg<Writable>,
15
+ export const fromWritable: <E, A = string | Uint8Array>(
16
+ evaluate: LazyArg<Writable | NodeJS.WritableStream>,
25
17
  onError: (error: unknown) => E,
26
18
  options?: FromWritableOptions
27
19
  ) => Sink<never, E, A, never, void> = internal.fromWritable
package/src/Stream.ts CHANGED
@@ -2,10 +2,13 @@
2
2
  * @since 1.0.0
3
3
  */
4
4
  import type { SizeInput } from "@effect/platform/FileSystem"
5
+ import type { Channel } from "effect/Channel"
6
+ import type { Chunk } from "effect/Chunk"
5
7
  import type { Effect } from "effect/Effect"
6
8
  import type { LazyArg } from "effect/Function"
7
9
  import type { Stream } from "effect/Stream"
8
- import type { Readable } from "stream"
10
+ import type { Duplex, Readable } from "stream"
11
+ import type { PlatformError } from "./Error"
9
12
  import * as internal from "./internal/stream"
10
13
 
11
14
  /**
@@ -17,26 +20,72 @@ export interface FromReadableOptions {
17
20
  readonly chunkSize?: SizeInput
18
21
  }
19
22
 
23
+ /**
24
+ * @category model
25
+ * @since 1.0.0
26
+ */
27
+ export interface FromWritableOptions {
28
+ readonly endOnDone?: boolean
29
+ readonly encoding?: BufferEncoding
30
+ }
31
+
20
32
  /**
21
33
  * @category constructors
22
34
  * @since 1.0.0
23
35
  */
24
- export const fromReadable: <E, A>(
25
- evaluate: LazyArg<Readable>,
36
+ export const fromReadable: <E, A = Uint8Array>(
37
+ evaluate: LazyArg<Readable | NodeJS.ReadableStream>,
26
38
  onError: (error: unknown) => E,
27
- options?: FromReadableOptions
39
+ { chunkSize }?: FromReadableOptions
28
40
  ) => Stream<never, E, A> = internal.fromReadable
29
41
 
42
+ /**
43
+ * @category constructors
44
+ * @since 1.0.0
45
+ */
46
+ export const fromDuplex: <IE, E, I = Uint8Array, O = Uint8Array>(
47
+ evaluate: LazyArg<Duplex>,
48
+ onError: (error: unknown) => E,
49
+ options?: FromReadableOptions & FromWritableOptions
50
+ ) => Channel<never, IE, Chunk<I>, unknown, IE | E, Chunk<O>, void> = internal.fromDuplex
51
+
52
+ /**
53
+ * @category combinators
54
+ * @since 1.0.0
55
+ */
56
+ export const pipeThroughDuplex: {
57
+ <E2, B = Uint8Array>(
58
+ duplex: LazyArg<Duplex>,
59
+ onError: (error: unknown) => E2,
60
+ options?: FromReadableOptions & FromWritableOptions
61
+ ): <R, E, A>(self: Stream<R, E, A>) => Stream<R, E2 | E, B>
62
+ <R, E, A, E2, B = Uint8Array>(
63
+ self: Stream<R, E, A>,
64
+ duplex: LazyArg<Duplex>,
65
+ onError: (error: unknown) => E2,
66
+ options?: FromReadableOptions & FromWritableOptions
67
+ ): Stream<R, E | E2, B>
68
+ } = internal.pipeThroughDuplex
69
+
70
+ /**
71
+ * @category combinators
72
+ * @since 1.0.0
73
+ */
74
+ export const pipeThroughSimple: {
75
+ (duplex: LazyArg<Duplex>): <R, E>(self: Stream<R, E, string | Uint8Array>) => Stream<R, E | PlatformError, Uint8Array>
76
+ <R, E>(self: Stream<R, E, string | Uint8Array>, duplex: LazyArg<Duplex>): Stream<R, PlatformError | E, Uint8Array>
77
+ } = internal.pipeThroughSimple
78
+
30
79
  /**
31
80
  * @since 1.0.0
32
81
  * @category conversions
33
82
  */
34
83
  export const toString: <E>(
84
+ readable: LazyArg<Readable | NodeJS.ReadableStream>,
35
85
  options: {
36
- readable: LazyArg<Readable>
37
- onFailure: (error: unknown) => E
38
- encoding?: BufferEncoding
39
- maxBytes?: SizeInput
86
+ readonly onFailure: (error: unknown) => E
87
+ readonly encoding?: BufferEncoding | undefined
88
+ readonly maxBytes?: SizeInput | undefined
40
89
  }
41
90
  ) => Effect<never, E, string> = internal.toString
42
91
 
@@ -45,9 +94,6 @@ export const toString: <E>(
45
94
  * @category conversions
46
95
  */
47
96
  export const toUint8Array: <E>(
48
- options: {
49
- readable: LazyArg<Readable>
50
- onFailure: (error: unknown) => E
51
- maxBytes?: SizeInput
52
- }
97
+ readable: LazyArg<Readable | NodeJS.ReadableStream>,
98
+ options: { readonly onFailure: (error: unknown) => E; readonly maxBytes?: SizeInput | undefined }
53
99
  ) => Effect<never, E, Uint8Array> = internal.toUint8Array
@@ -88,8 +88,7 @@ export const stream = (
88
88
  (part) =>
89
89
  part._tag === "File" && Chunk.some(fieldMimeTypes, (_) => part.contentType.includes(_)) ?
90
90
  Effect.map(
91
- NodeStream.toString({
92
- readable: () => part.source,
91
+ NodeStream.toString(() => part.source, {
93
92
  onFailure: (error) => FormData.FormDataError("InternalError", error)
94
93
  }),
95
94
  (content) => new FieldImpl(part.key, part.contentType, content)
@@ -37,8 +37,7 @@ export class IncomingMessageImpl<E> implements IncomingMessage.IncomingMessage<E
37
37
  Effect.flatMap(
38
38
  FiberRef.get(IncomingMessage.maxBodySize),
39
39
  (maxBodySize) =>
40
- NodeStream.toString({
41
- readable: () => this.source,
40
+ NodeStream.toString(() => this.source, {
42
41
  onFailure: this.onError,
43
42
  maxBytes: Option.getOrUndefined(maxBodySize)
44
43
  })
@@ -73,8 +72,7 @@ export class IncomingMessageImpl<E> implements IncomingMessage.IncomingMessage<E
73
72
  return Effect.flatMap(
74
73
  FiberRef.get(IncomingMessage.maxBodySize),
75
74
  (maxBodySize) =>
76
- NodeStream.toUint8Array({
77
- readable: () => this.source,
75
+ NodeStream.toUint8Array(() => this.source, {
78
76
  onFailure: this.onError,
79
77
  maxBytes: Option.getOrUndefined(maxBodySize)
80
78
  })
@@ -54,7 +54,7 @@ export const makeAgentLayer = (options?: Https.AgentOptions): Layer.Layer<never,
54
54
  export const agentLayer = makeAgentLayer()
55
55
 
56
56
  const fromAgent = (agent: NodeClient.HttpAgent): Client.Client.Default =>
57
- Client.make((request) =>
57
+ Client.makeDefault((request) =>
58
58
  Effect.flatMap(
59
59
  UrlParams.makeUrl(request.url, request.urlParams, (_) =>
60
60
  Error.RequestError({
@@ -79,7 +79,9 @@ export const make = (
79
79
  port: address.port
80
80
  },
81
81
  serve: (httpApp, middleware) => {
82
- const handledApp = middleware ? middleware(respond(httpApp)) : respond(httpApp)
82
+ const handledApp = middleware
83
+ ? middleware(App.withDefaultMiddleware(respond(httpApp)))
84
+ : App.withDefaultMiddleware(respond(httpApp))
83
85
  return Effect.flatMap(Effect.all([Effect.runtime(), Effect.fiberId]), ([runtime, fiberId]) => {
84
86
  const runFork = Runtime.runFork(runtime)
85
87
  function handler(nodeRequest: Http.IncomingMessage, nodeResponse: Http.ServerResponse) {
@@ -1,61 +1,56 @@
1
+ import * as Channel from "effect/Channel"
2
+ import type * as Chunk from "effect/Chunk"
3
+ import * as Deferred from "effect/Deferred"
1
4
  import * as Effect from "effect/Effect"
2
5
  import type { LazyArg } from "effect/Function"
3
- import { pipe } from "effect/Function"
4
6
  import * as Sink from "effect/Sink"
5
7
  import type { Writable } from "node:stream"
6
- import type { FromWritableOptions } from "../Sink"
8
+ import type { FromWritableOptions } from "../Stream"
9
+ import { writeInput } from "./stream"
7
10
 
8
11
  /** @internal */
9
- export const fromWritable = <E, A>(
10
- evaluate: LazyArg<Writable>,
12
+ export const fromWritable = <E, A = Uint8Array | string>(
13
+ evaluate: LazyArg<Writable | NodeJS.WritableStream>,
11
14
  onError: (error: unknown) => E,
12
- { encoding, endOnClose = true }: FromWritableOptions = {}
15
+ options: FromWritableOptions = {}
13
16
  ): Sink.Sink<never, E, A, never, void> =>
14
- endOnClose ?
15
- makeSinkWithRelease<E, A>(evaluate, onError, encoding) :
16
- makeSink<E, A>(evaluate, onError, encoding)
17
+ Sink.suspend(() => Sink.fromChannel(writeChannel(evaluate(), onError, options)))
17
18
 
18
- const makeSink = <E, A>(stream: LazyArg<Writable>, onError: (error: unknown) => E, encoding?: BufferEncoding) =>
19
- pipe(
20
- Effect.sync(stream),
21
- Effect.map((stream) => Sink.forEach(write<E, A>(stream, onError, encoding))),
22
- Sink.unwrap
19
+ const writeChannel = <IE, OE, A>(
20
+ writable: Writable | NodeJS.WritableStream,
21
+ onError: (error: unknown) => OE,
22
+ options: FromWritableOptions = {}
23
+ ): Channel.Channel<never, IE, Chunk.Chunk<A>, unknown, IE | OE, Chunk.Chunk<never>, void> =>
24
+ Channel.flatMap(
25
+ Deferred.make<IE, void>(),
26
+ (deferred) => {
27
+ const input = writeInput<IE, A>(
28
+ writable,
29
+ (_) => Deferred.failCause(deferred, _),
30
+ options,
31
+ Deferred.complete(deferred, Effect.unit)
32
+ )
33
+ return Channel.embedInput(
34
+ writableOutput(writable, deferred, onError),
35
+ input
36
+ )
37
+ }
23
38
  )
24
39
 
25
- const makeSinkWithRelease = <E, A>(
26
- stream: LazyArg<Writable>,
27
- onError: (error: unknown) => E,
28
- encoding?: BufferEncoding
40
+ const writableOutput = <IE, E>(
41
+ writable: Writable | NodeJS.WritableStream,
42
+ deferred: Deferred.Deferred<IE, void>,
43
+ onError: (error: unknown) => E
29
44
  ) =>
30
- pipe(
31
- Effect.acquireRelease(Effect.sync(stream), endWritable),
32
- Effect.map((stream) => Sink.forEach(write<E, A>(stream, onError, encoding))),
33
- Sink.unwrapScoped
34
- )
35
-
36
- const endWritable = (stream: Writable) =>
37
- Effect.async<never, never, void>((resume) => {
38
- if (stream.closed) {
39
- resume(Effect.unit)
40
- return
41
- }
42
-
43
- stream.end(() => resume(Effect.unit))
44
- })
45
-
46
- const write = <E, A>(stream: Writable, onError: (error: unknown) => E, encoding?: BufferEncoding) => (_: A) =>
47
- Effect.async<never, E, void>((resume) => {
48
- const cb = (err?: Error | null) => {
49
- if (err) {
45
+ Effect.raceFirst(
46
+ Effect.async<never, E, never>((resume) => {
47
+ function handleError(err: unknown) {
50
48
  resume(Effect.fail(onError(err)))
51
- } else {
52
- resume(Effect.unit)
53
49
  }
54
- }
55
-
56
- if (encoding) {
57
- stream.write(_, encoding, cb)
58
- } else {
59
- stream.write(_, cb)
60
- }
61
- })
50
+ writable.on("error", handleError)
51
+ return Effect.sync(() => {
52
+ writable.off("error", handleError)
53
+ })
54
+ }),
55
+ Deferred.await(deferred)
56
+ )