1/**
2 * Bun stream interoperability for Effect streams.
3 *
4 * This module is the Bun entry point for adapting runtime streams into Effect's
5 * streaming model. It re-exports the shared Node stream adapters for Bun's
6 * Node-compatible stream APIs and adds {@link fromReadableStream}, a Web
7 * `ReadableStream` adapter that uses Bun's `readMany` reader method to pull
8 * batches of values into an Effect `Stream`.
9 *
10 * @since 4.0.0
11 */
12import * as Arr from "effect/Array"
13import * as Cause from "effect/Cause"
14import * as Channel from "effect/Channel"
15import * as Effect from "effect/Effect"
16import { constVoid, type LazyArg } from "effect/Function"
17import type * as Pull from "effect/Pull"
18import * as Scope from "effect/Scope"
19import * as Stream from "effect/Stream"
20
21/**
22 * @since 4.0.0
23 */
24export * from "@effect/platform-node-shared/NodeStream"
25
26/**
27 * Creates a stream from a `ReadableStream` using Bun's optimized `.readMany`
28 * API.
29 *
30 * @category constructors
31 * @since 4.0.0
32 */
33export const fromReadableStream = <A, E>(
34 options: {
35 readonly evaluate: LazyArg<ReadableStream<A>>
36 readonly onError: (error: unknown) => E
37 readonly releaseLockOnEnd?: boolean | undefined
38 }
39): Stream.Stream<A, E> =>
40 Stream.fromChannel(Channel.fromTransform(Effect.fnUntraced(function*(_, scope) {
41 const reader = options.evaluate().getReader()
42 yield* Scope.addFinalizer(
43 scope,
44 options.releaseLockOnEnd
45 ? Effect.sync(() => reader.releaseLock())
46 : Effect.promise(() => reader.cancel().catch(constVoid))
47 )
48 function readMany(): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E> {
49 let result:
50 | Bun.ReadableStreamDefaultReadManyResult<A>
51 | Promise<Bun.ReadableStreamDefaultReadManyResult<A>>
52 try {
53 result = reader.readMany()
54 } catch (error) {
55 return Effect.fail(options.onError(error))
56 }
57 if ("then" in result) {
58 return Effect.callback<Arr.NonEmptyReadonlyArray<A>, E | Cause.Done>((resume) => {
59 result.then((_) => resume(handleResult(_)), (e) => resume(Effect.fail(options.onError(e))))
60 })
61 }
62 return handleResult(result)
63 }
64 function handleResult(
65 result: Bun.ReadableStreamDefaultReadManyResult<A>
66 ): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E> {
67 if (result.done) {
68 return Cause.done()
69 } else if (!Arr.isReadonlyArrayNonEmpty(result.value)) {
70 return readMany()
71 }
72 return Effect.succeed(result.value)
73 }
74 // @effect-diagnostics-next-line returnEffectInGen:off
75 return Effect.suspend(readMany)
76 })))