@effect/platform-node 4.0.0-beta.99 → 4.0.0-rc.109

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 (144) hide show
  1. package/AGENTS.md +395 -0
  2. package/CLAUDE.md +395 -0
  3. package/README.md +10 -3
  4. package/ai-docs/README.md +44 -0
  5. package/ai-docs/package.json +36 -0
  6. package/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +30 -0
  7. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +39 -0
  8. package/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +74 -0
  9. package/ai-docs/src/01_effect/01_basics/index.md +5 -0
  10. package/ai-docs/src/01_effect/02_schema/10_schema-basics.ts +43 -0
  11. package/ai-docs/src/01_effect/02_schema/index.md +7 -0
  12. package/ai-docs/src/01_effect/03_services/01_service.ts +45 -0
  13. package/ai-docs/src/01_effect/03_services/10_reference.ts +10 -0
  14. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +70 -0
  15. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +66 -0
  16. package/ai-docs/src/01_effect/03_services/index.md +5 -0
  17. package/ai-docs/src/01_effect/04_errors/01_error-handling.ts +30 -0
  18. package/ai-docs/src/01_effect/04_errors/10_catch-tags.ts +24 -0
  19. package/ai-docs/src/01_effect/04_errors/20_reason-errors.ts +64 -0
  20. package/ai-docs/src/01_effect/04_errors/index.md +1 -0
  21. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +105 -0
  22. package/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts +31 -0
  23. package/ai-docs/src/01_effect/05_resources/30_layer-map.ts +86 -0
  24. package/ai-docs/src/01_effect/05_resources/index.md +3 -0
  25. package/ai-docs/src/01_effect/06_running/10_run-main.ts +30 -0
  26. package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +27 -0
  27. package/ai-docs/src/01_effect/06_running/index.md +1 -0
  28. package/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts +56 -0
  29. package/ai-docs/src/01_effect/07_pubsub/index.md +3 -0
  30. package/ai-docs/src/03_stream/10_creating-streams.ts +103 -0
  31. package/ai-docs/src/03_stream/20_consuming-streams.ts +137 -0
  32. package/ai-docs/src/03_stream/30_encoding.ts +165 -0
  33. package/ai-docs/src/03_stream/index.md +4 -0
  34. package/ai-docs/src/04_integration/10_managed-runtime.ts +129 -0
  35. package/ai-docs/src/04_integration/index.md +5 -0
  36. package/ai-docs/src/05_batching/10_request-resolver.ts +89 -0
  37. package/ai-docs/src/05_batching/index.md +3 -0
  38. package/ai-docs/src/06_schedule/10_schedules.ts +110 -0
  39. package/ai-docs/src/06_schedule/index.md +3 -0
  40. package/ai-docs/src/07_datetime/10_creating-and-formatting.ts +30 -0
  41. package/ai-docs/src/07_datetime/20_time-zones.ts +44 -0
  42. package/ai-docs/src/07_datetime/index.md +5 -0
  43. package/ai-docs/src/08_observability/10_logging.ts +66 -0
  44. package/ai-docs/src/08_observability/20_otlp-tracing.ts +95 -0
  45. package/ai-docs/src/08_observability/index.md +7 -0
  46. package/ai-docs/src/09_testing/10_effect-tests.ts +55 -0
  47. package/ai-docs/src/09_testing/20_layer-tests.ts +138 -0
  48. package/ai-docs/src/09_testing/index.md +1 -0
  49. package/ai-docs/src/10_predicate/01_basics.ts +14 -0
  50. package/ai-docs/src/10_predicate/index.md +9 -0
  51. package/ai-docs/src/40_sql/10_basics.ts +175 -0
  52. package/ai-docs/src/40_sql/index.md +6 -0
  53. package/ai-docs/src/50_http-client/10_basics.ts +102 -0
  54. package/ai-docs/src/50_http-client/index.md +3 -0
  55. package/ai-docs/src/51_http-server/10_basics.ts +118 -0
  56. package/ai-docs/src/51_http-server/20_testing.ts +100 -0
  57. package/ai-docs/src/51_http-server/fixtures/api/Api.ts +14 -0
  58. package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +36 -0
  59. package/ai-docs/src/51_http-server/fixtures/api/System.ts +10 -0
  60. package/ai-docs/src/51_http-server/fixtures/api/Users.ts +107 -0
  61. package/ai-docs/src/51_http-server/fixtures/domain/User.ts +21 -0
  62. package/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +22 -0
  63. package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +37 -0
  64. package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +80 -0
  65. package/ai-docs/src/51_http-server/fixtures/server/Users.ts +183 -0
  66. package/ai-docs/src/51_http-server/index.md +3 -0
  67. package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +117 -0
  68. package/ai-docs/src/60_child-process/index.md +3 -0
  69. package/ai-docs/src/70_cli/10_basics.ts +160 -0
  70. package/ai-docs/src/70_cli/index.md +5 -0
  71. package/ai-docs/src/71_ai/10_language-model.ts +156 -0
  72. package/ai-docs/src/71_ai/20_tools.ts +226 -0
  73. package/ai-docs/src/71_ai/30_chat.ts +158 -0
  74. package/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts +9 -0
  75. package/ai-docs/src/71_ai/index.md +5 -0
  76. package/ai-docs/src/80_cluster/10_entities.ts +97 -0
  77. package/ai-docs/src/80_cluster/index.md +4 -0
  78. package/ai-docs/src/index.md +10 -0
  79. package/ai-docs/tsconfig.json +24 -0
  80. package/dist/Mime.js.map +1 -1
  81. package/dist/NodeClusterHttp.d.ts +1 -0
  82. package/dist/NodeClusterHttp.d.ts.map +1 -1
  83. package/dist/NodeClusterHttp.js +6 -1
  84. package/dist/NodeClusterHttp.js.map +1 -1
  85. package/dist/NodeClusterSocket.d.ts +1 -0
  86. package/dist/NodeClusterSocket.d.ts.map +1 -1
  87. package/dist/NodeClusterSocket.js +10 -2
  88. package/dist/NodeClusterSocket.js.map +1 -1
  89. package/dist/NodeCrypto.js.map +1 -1
  90. package/dist/NodeFileSystem.js.map +1 -1
  91. package/dist/NodeHttpClient.d.ts +18 -18
  92. package/dist/NodeHttpClient.d.ts.map +1 -1
  93. package/dist/NodeHttpClient.js +26 -20
  94. package/dist/NodeHttpClient.js.map +1 -1
  95. package/dist/NodeHttpIncomingMessage.js.map +1 -1
  96. package/dist/NodeHttpPlatform.d.ts +8 -4
  97. package/dist/NodeHttpPlatform.d.ts.map +1 -1
  98. package/dist/NodeHttpPlatform.js +38 -0
  99. package/dist/NodeHttpPlatform.js.map +1 -1
  100. package/dist/NodeHttpServer.d.ts +21 -16
  101. package/dist/NodeHttpServer.d.ts.map +1 -1
  102. package/dist/NodeHttpServer.js +29 -21
  103. package/dist/NodeHttpServer.js.map +1 -1
  104. package/dist/NodeHttpServerRequest.js.map +1 -1
  105. package/dist/NodeMultipart.d.ts.map +1 -1
  106. package/dist/NodeMultipart.js +3 -2
  107. package/dist/NodeMultipart.js.map +1 -1
  108. package/dist/NodeMultipartParser.d.ts +97 -0
  109. package/dist/NodeMultipartParser.d.ts.map +1 -0
  110. package/dist/NodeMultipartParser.js +108 -0
  111. package/dist/NodeMultipartParser.js.map +1 -0
  112. package/dist/NodePath.js.map +1 -1
  113. package/dist/NodeRedis.d.ts +40 -15
  114. package/dist/NodeRedis.d.ts.map +1 -1
  115. package/dist/NodeRedis.js +66 -16
  116. package/dist/NodeRedis.js.map +1 -1
  117. package/dist/NodeRuntime.js.map +1 -1
  118. package/dist/NodeServices.js.map +1 -1
  119. package/dist/NodeSocket.js.map +1 -1
  120. package/dist/NodeStdio.js.map +1 -1
  121. package/dist/NodeTerminal.js.map +1 -1
  122. package/dist/NodeWorker.js.map +1 -1
  123. package/dist/NodeWorkerRunner.d.ts.map +1 -1
  124. package/dist/NodeWorkerRunner.js +30 -18
  125. package/dist/NodeWorkerRunner.js.map +1 -1
  126. package/dist/Undici.d.ts +2 -2
  127. package/dist/Undici.js +2 -2
  128. package/dist/Undici.js.map +1 -1
  129. package/dist/index.d.ts +4 -0
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +4 -0
  132. package/dist/index.js.map +1 -1
  133. package/package.json +17 -14
  134. package/src/NodeClusterHttp.ts +6 -2
  135. package/src/NodeClusterSocket.ts +8 -2
  136. package/src/NodeHttpClient.ts +27 -20
  137. package/src/NodeHttpPlatform.ts +49 -0
  138. package/src/NodeHttpServer.ts +61 -40
  139. package/src/NodeMultipart.ts +5 -4
  140. package/src/NodeMultipartParser.ts +184 -0
  141. package/src/NodeRedis.ts +86 -26
  142. package/src/NodeWorkerRunner.ts +39 -23
  143. package/src/Undici.ts +2 -2
  144. package/src/index.ts +5 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Node.js streams adapter for the low-level multipart parser.
3
+ *
4
+ * @since 4.0.0
5
+ */
6
+ // oxlint-disable typescript/no-unsafe-declaration-merging
7
+ /// <reference types="node" />
8
+ import type { BaseConfig, MultipartError, Parser, PartInfo } from "effect/unstable/http/MultipartParser"
9
+ import { make as makeParser } from "effect/unstable/http/MultipartParser"
10
+ import type { IncomingHttpHeaders } from "node:http"
11
+ import { Duplex, Readable } from "node:stream"
12
+
13
+ /**
14
+ * A part emitted by the Node.js multipart parser.
15
+ *
16
+ * @category models
17
+ * @since 4.0.0
18
+ */
19
+ export type Part = Field | FileStream
20
+
21
+ /**
22
+ * A parsed multipart field.
23
+ *
24
+ * @category models
25
+ * @since 4.0.0
26
+ */
27
+ export interface Field {
28
+ readonly _tag: "Field"
29
+ readonly info: PartInfo
30
+ readonly value: Uint8Array
31
+ }
32
+
33
+ /**
34
+ * A Node.js duplex stream that emits parsed multipart parts.
35
+ *
36
+ * @category models
37
+ * @since 4.0.0
38
+ */
39
+ export interface MultipartStream extends Duplex {
40
+ [Symbol.asyncIterator](): NodeJS.AsyncIterator<Part>
41
+
42
+ on(event: "field", listener: (field: Field) => void): this
43
+ on(event: "file", listener: (file: FileStream) => void): this
44
+ on(event: "close", listener: () => void): this
45
+ on(event: "data", listener: (part: Part) => void): this
46
+ on(event: "drain", listener: () => void): this
47
+ on(event: "end", listener: () => void): this
48
+ on(event: "error", listener: (err: MultipartError) => void): this
49
+ on(event: "finish", listener: () => void): this
50
+ on(event: "pause", listener: () => void): this
51
+ on(event: "pipe", listener: (src: Readable) => void): this
52
+ on(event: "readable", listener: () => void): this
53
+ on(event: "resume", listener: () => void): this
54
+ on(event: "unpipe", listener: (src: Readable) => void): this
55
+ on(event: string | symbol, listener: (...args: Array<any>) => void): this
56
+
57
+ read(size?: number): Part | null
58
+ }
59
+
60
+ /**
61
+ * Configuration for the Node.js multipart parser.
62
+ *
63
+ * @category models
64
+ * @since 4.0.0
65
+ */
66
+ export type NodeConfig = Omit<BaseConfig, "headers"> & {
67
+ readonly headers: IncomingHttpHeaders
68
+ }
69
+
70
+ /**
71
+ * A Node.js duplex stream that parses multipart input.
72
+ *
73
+ * @category models
74
+ * @since 4.0.0
75
+ */
76
+ export class MultipartStream extends Duplex {
77
+ private _parser: Parser
78
+ _canWrite = true
79
+ private _writeCallback: (() => void) | undefined
80
+
81
+ constructor(config: NodeConfig) {
82
+ super({ readableObjectMode: true })
83
+ let currentError: MultipartError | undefined
84
+ let currentFile: FileStream | undefined
85
+ this._parser = makeParser({
86
+ ...(config as any),
87
+ onField: (info, value) => {
88
+ if (currentError !== undefined) return
89
+ const field: Field = { _tag: "Field", info, value }
90
+ this.push(field)
91
+ this.emit("field", field)
92
+ },
93
+ onFile: (info) => {
94
+ if (currentError !== undefined) return (_) => {}
95
+ const file = new FileStream(info, this)
96
+ currentFile = file
97
+ this.push(file)
98
+ this.emit("file", file)
99
+ return (chunk) => {
100
+ if (currentError !== undefined) return
101
+ this._canWrite = file.push(chunk)
102
+ if (chunk === null && !this._canWrite) {
103
+ currentFile = undefined
104
+ this._resume()
105
+ }
106
+ }
107
+ },
108
+ onError: (error) => {
109
+ this.emit("error", error)
110
+ currentFile?.emit("error", error)
111
+ currentError = error
112
+ },
113
+ onDone: () => {
114
+ this.push(null)
115
+ }
116
+ })
117
+ }
118
+
119
+ _resume() {
120
+ this._canWrite = true
121
+ if (this._writeCallback !== undefined) {
122
+ const callback = this._writeCallback
123
+ this._writeCallback = undefined
124
+ callback()
125
+ }
126
+ }
127
+
128
+ override _read(_size: number) {}
129
+
130
+ override _write(
131
+ chunk: any,
132
+ encoding: BufferEncoding,
133
+ callback: (error?: Error | null | undefined) => void
134
+ ): void {
135
+ this._parser.write(
136
+ chunk instanceof Uint8Array ? chunk : Buffer.from(chunk, encoding)
137
+ )
138
+ if (this._canWrite) {
139
+ callback()
140
+ } else {
141
+ this._writeCallback = callback
142
+ }
143
+ }
144
+
145
+ override _final(callback: (error?: Error | null | undefined) => void): void {
146
+ this._parser.end()
147
+ callback()
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Creates a Node.js multipart parser stream.
153
+ *
154
+ * @category constructors
155
+ * @since 4.0.0
156
+ */
157
+ export const make = (config: NodeConfig): MultipartStream => new MultipartStream(config)
158
+
159
+ /**
160
+ * A readable stream containing a parsed multipart file.
161
+ *
162
+ * @category models
163
+ * @since 4.0.0
164
+ */
165
+ export class FileStream extends Readable {
166
+ readonly _tag = "File"
167
+ readonly filename: string | undefined
168
+ readonly info: PartInfo
169
+ private _parent: MultipartStream
170
+ constructor(
171
+ info: PartInfo,
172
+ parent: MultipartStream
173
+ ) {
174
+ super()
175
+ this.info = info
176
+ this._parent = parent
177
+ this.filename = info.filename
178
+ }
179
+ override _read(_size: number) {
180
+ if (this._parent._canWrite === false) {
181
+ this._parent._resume()
182
+ }
183
+ }
184
+ }
package/src/NodeRedis.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  /**
2
- * Node.js Redis integration backed by `ioredis`.
2
+ * Node.js Redis integration backed by `redis` (node-redis).
3
3
  *
4
- * This module creates a scoped `ioredis` client and exposes it in two forms:
4
+ * This module creates a scoped `node-redis` client and exposes it in two forms:
5
5
  * the generic `Redis` service and the {@link NodeRedis} service for direct
6
- * access to the underlying client. `layer` accepts ioredis options directly,
7
- * while `layerConfig` reads them from Effect config. Both layers close the
8
- * client when the layer scope ends.
6
+ * access to the underlying client. `layer` accepts node-redis client options
7
+ * directly, while `layerConfig` reads them from Effect config. `node-redis`
8
+ * connects explicitly, so layer construction can fail with a `RedisError`.
9
+ * Both layers close the client when the layer scope ends.
9
10
  *
10
11
  * @since 4.0.0
11
12
  */
@@ -14,31 +15,68 @@ import * as Context from "effect/Context"
14
15
  import * as Effect from "effect/Effect"
15
16
  import * as Fn from "effect/Function"
16
17
  import * as Layer from "effect/Layer"
17
- import * as Scope from "effect/Scope"
18
18
  import * as Redis from "effect/unstable/persistence/Redis"
19
- import * as IoRedis from "ioredis"
19
+ import { createClient, SocketTimeoutError } from "redis"
20
+
21
+ type NodeRedisClient = ReturnType<typeof createClient>
22
+ type NodeRedisClientOptions = NonNullable<Parameters<typeof createClient>[0]>
20
23
 
21
24
  /**
22
25
  * Service tag for the Node Redis integration, exposing the underlying
23
- * `ioredis` client and a `use` helper that maps client failures to
26
+ * `node-redis` client and a `use` helper that maps client failures to
24
27
  * `RedisError`.
25
28
  *
26
29
  * @category services
27
30
  * @since 4.0.0
28
31
  */
29
32
  export class NodeRedis extends Context.Service<NodeRedis, {
30
- readonly client: IoRedis.Redis
31
- readonly use: <A>(f: (client: IoRedis.Redis) => Promise<A>) => Effect.Effect<A, Redis.RedisError>
33
+ readonly client: NodeRedisClient
34
+ readonly use: <A>(f: (client: NodeRedisClient) => Promise<A>) => Effect.Effect<A, Redis.RedisError>
32
35
  }>()("@effect/platform-node/NodeRedis") {}
33
36
 
34
37
  const make = Effect.fnUntraced(function*(
35
- options?: IoRedis.RedisOptions
38
+ options?: NodeRedisClientOptions
36
39
  ) {
37
- const scope = yield* Effect.scope
38
- yield* Scope.addFinalizer(scope, Effect.promise(() => client.quit()))
39
- const client = new IoRedis.Redis(options ?? {})
40
+ let ready = false
41
+ const socket = options?.socket
42
+ const client = yield* Effect.acquireRelease(
43
+ Effect.sync((): NodeRedisClient =>
44
+ createClient({
45
+ ...options,
46
+ socket: socket?.reconnectStrategy === undefined
47
+ ? {
48
+ ...socket,
49
+ reconnectStrategy: (retries, cause) => {
50
+ if (!ready) return cause
51
+ if (cause instanceof SocketTimeoutError) return false
52
+ const jitter = Math.floor(Math.random() * 200)
53
+ const delay = Math.min(2 ** retries * 50, 2000)
54
+ return delay + jitter
55
+ }
56
+ }
57
+ : socket
58
+ })
59
+ ),
60
+ (client) => Effect.ignoreCause(Effect.promise(() => client.close()))
61
+ )
62
+ client.once("ready", () => {
63
+ ready = true
64
+ })
65
+
66
+ // node-redis rethrows `error` events that have no listener, which would crash
67
+ // the process on a transient socket failure. Command failures are still
68
+ // reported as `RedisError`.
69
+ const runSync = Effect.runSyncWith(yield* Effect.context<never>())
70
+ client.on("error", (cause) => {
71
+ runSync(Effect.logWarning("NodeRedis client error", cause))
72
+ })
73
+
74
+ yield* Effect.tryPromise({
75
+ try: () => client.connect(),
76
+ catch: (cause) => new Redis.RedisError({ cause })
77
+ })
40
78
 
41
- const use = <A>(f: (client: IoRedis.Redis) => Promise<A>) =>
79
+ const use = <A>(f: (client: NodeRedisClient) => Promise<A>) =>
42
80
  Effect.tryPromise({
43
81
  try: () => f(client),
44
82
  catch: (cause) => new Redis.RedisError({ cause })
@@ -47,7 +85,7 @@ const make = Effect.fnUntraced(function*(
47
85
  const redis = yield* Redis.make({
48
86
  send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) =>
49
87
  Effect.tryPromise({
50
- try: () => client.call(command, ...args) as Promise<A>,
88
+ try: () => client.sendCommand([command, ...args]) as Promise<A>,
51
89
  catch: (cause) => new Redis.RedisError({ cause })
52
90
  })
53
91
  })
@@ -63,28 +101,50 @@ const make = Effect.fnUntraced(function*(
63
101
  })
64
102
 
65
103
  /**
66
- * Provides `Redis` and `NodeRedis` services backed by an `ioredis` client
67
- * created with the supplied options and closed when the layer scope ends.
104
+ * Provides `Redis` and `NodeRedis` services backed by a `node-redis` client
105
+ * created with the supplied options, connected when the layer is built and
106
+ * closed when the layer scope ends.
107
+ *
108
+ * **Details**
109
+ *
110
+ * By default, the initial connection fails on its first connection error. A
111
+ * caller-supplied `socket.reconnectStrategy` is used instead when present. Once
112
+ * the client has emitted `ready`, the default reconnect strategy uses
113
+ * node-redis' exponential backoff and stops on socket timeouts.
114
+ *
115
+ * Scope finalization calls `close()`, which waits for in-flight commands,
116
+ * including blocking commands, and can therefore delay scope closure.
68
117
  *
69
118
  * @category layers
70
119
  * @since 4.0.0
71
120
  */
72
121
  export const layer = (
73
- options?: IoRedis.RedisOptions | undefined
74
- ): Layer.Layer<Redis.Redis | NodeRedis> => Layer.effectContext(make(options))
122
+ options?: NodeRedisClientOptions | undefined
123
+ ): Layer.Layer<Redis.Redis | NodeRedis, Redis.RedisError> => Layer.effectContext(make(options))
75
124
 
76
125
  /**
77
- * Provides `Redis` and `NodeRedis` services from `Config`-backed ioredis
78
- * options, closing the client when the layer scope ends.
126
+ * Provides `Redis` and `NodeRedis` services from `Config`-backed node-redis
127
+ * client options, connecting the client when the layer is built and closing it
128
+ * when the layer scope ends.
129
+ *
130
+ * **Details**
131
+ *
132
+ * By default, the initial connection fails on its first connection error. A
133
+ * caller-supplied `socket.reconnectStrategy` is used instead when present. Once
134
+ * the client has emitted `ready`, the default reconnect strategy uses
135
+ * node-redis' exponential backoff and stops on socket timeouts.
136
+ *
137
+ * Scope finalization calls `close()`, which waits for in-flight commands,
138
+ * including blocking commands, and can therefore delay scope closure.
79
139
  *
80
140
  * @category layers
81
141
  * @since 4.0.0
82
142
  */
83
143
  export const layerConfig: (
84
- options: Config.Wrap<IoRedis.RedisOptions>
85
- ) => Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> = (
86
- options: Config.Wrap<IoRedis.RedisOptions>
87
- ): Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> =>
144
+ options: Config.Wrap<NodeRedisClientOptions>
145
+ ) => Layer.Layer<Redis.Redis | NodeRedis, Redis.RedisError | Config.ConfigError> = (
146
+ options: Config.Wrap<NodeRedisClientOptions>
147
+ ): Layer.Layer<Redis.Redis | NodeRedis, Redis.RedisError | Config.ConfigError> =>
88
148
  Layer.effectContext(
89
149
  Config.unwrap(options).pipe(
90
150
  Effect.flatMap(make)
@@ -16,6 +16,7 @@ import * as Effect from "effect/Effect"
16
16
  import * as Exit from "effect/Exit"
17
17
  import * as Fiber from "effect/Fiber"
18
18
  import * as Layer from "effect/Layer"
19
+ import * as Scope from "effect/Scope"
19
20
  import { WorkerError, WorkerReceiveError, WorkerSpawnError } from "effect/unstable/workers/WorkerError"
20
21
  import * as WorkerRunner from "effect/unstable/workers/WorkerRunner"
21
22
  import * as WorkerThreads from "node:worker_threads"
@@ -56,7 +57,8 @@ export const layer: Layer.Layer<WorkerRunner.WorkerRunnerPlatform> = Layer.succe
56
57
  runFork(Effect.logError("unhandled error in worker", exit.cause))
57
58
  }
58
59
  }
59
- ;(WorkerThreads.parentPort ?? process).on("message", (message: WorkerRunner.PlatformMessage<I>) => {
60
+ const port = WorkerThreads.parentPort ?? process
61
+ function onMessage(message: WorkerRunner.PlatformMessage<I>) {
60
62
  if (message[0] === 0) {
61
63
  const result = handler(0, message[1])
62
64
  if (Effect.isEffect(result)) {
@@ -72,32 +74,46 @@ export const layer: Layer.Layer<WorkerRunner.WorkerRunnerPlatform> = Layer.succe
72
74
  }
73
75
  Deferred.doneUnsafe(closeLatch, Exit.void)
74
76
  }
75
- })
77
+ }
78
+ port.on("message", onMessage)
76
79
 
77
- if (WorkerThreads.parentPort) {
78
- WorkerThreads.parentPort.on("messageerror", (cause) => {
79
- Deferred.doneUnsafe(
80
- closeLatch,
81
- new WorkerError({
82
- reason: new WorkerReceiveError({
83
- message: "received messageerror event",
84
- cause
85
- })
80
+ function onMessageError(cause: unknown) {
81
+ Deferred.doneUnsafe(
82
+ closeLatch,
83
+ new WorkerError({
84
+ reason: new WorkerReceiveError({
85
+ message: "received messageerror event",
86
+ cause
86
87
  })
87
- )
88
- })
89
- WorkerThreads.parentPort.on("error", (cause) => {
90
- Deferred.doneUnsafe(
91
- closeLatch,
92
- new WorkerError({
93
- reason: new WorkerReceiveError({
94
- message: "received messageerror event",
95
- cause
96
- })
88
+ })
89
+ )
90
+ }
91
+ function onError(cause: unknown) {
92
+ Deferred.doneUnsafe(
93
+ closeLatch,
94
+ new WorkerError({
95
+ reason: new WorkerReceiveError({
96
+ message: "received error event",
97
+ cause
97
98
  })
98
- )
99
- })
99
+ })
100
+ )
100
101
  }
102
+ if (WorkerThreads.parentPort) {
103
+ WorkerThreads.parentPort.on("messageerror", onMessageError)
104
+ WorkerThreads.parentPort.on("error", onError)
105
+ }
106
+
107
+ yield* Scope.addFinalizer(
108
+ scope,
109
+ Effect.sync(() => {
110
+ port.off("message", onMessage)
111
+ if (WorkerThreads.parentPort) {
112
+ WorkerThreads.parentPort.off("messageerror", onMessageError)
113
+ WorkerThreads.parentPort.off("error", onError)
114
+ }
115
+ })
116
+ )
101
117
 
102
118
  sendUnsafe(0, [0])
103
119
 
package/src/Undici.ts CHANGED
@@ -17,13 +17,13 @@
17
17
  import Undici from "undici"
18
18
 
19
19
  /**
20
- * @category Undici
20
+ * @category re-exports
21
21
  * @since 4.0.0
22
22
  */
23
23
  export * from "undici"
24
24
 
25
25
  /**
26
- * @category Undici
26
+ * @category re-exports
27
27
  * @since 4.0.0
28
28
  */
29
29
  export default Undici
package/src/index.ts CHANGED
@@ -64,6 +64,11 @@ export * as NodeHttpServerRequest from "./NodeHttpServerRequest.ts"
64
64
  */
65
65
  export * as NodeMultipart from "./NodeMultipart.ts"
66
66
 
67
+ /**
68
+ * @since 4.0.0
69
+ */
70
+ export * as NodeMultipartParser from "./NodeMultipartParser.ts"
71
+
67
72
  /**
68
73
  * @since 4.0.0
69
74
  */