diff --git a/.changeset/green-lions-jog.md b/.changeset/green-lions-jog.md new file mode 100644 index 0000000000..e19afe54b5 --- /dev/null +++ b/.changeset/green-lions-jog.md @@ -0,0 +1,6 @@ +--- +"effect": minor +"@effect/platform-browser": patch +--- + +added Stream.fromEventListener, and BrowserStream.{fromEventListenerWindow, fromEventListenerDocument} for constructing a stream from addEventListener diff --git a/packages/effect/src/Stream.ts b/packages/effect/src/Stream.ts index cdf1cbb596..a156323017 100644 --- a/packages/effect/src/Stream.ts +++ b/packages/effect/src/Stream.ts @@ -4558,3 +4558,13 @@ export const decodeText: { * @category encoding */ export const encodeText: (self: Stream) => Stream = internal.encodeText + +/** + * Creates a `Stream` using addEventListener. + * @since 3.1.0 + */ +export const fromEventListener: ( + target: EventTarget, + type: string, + options?: boolean | Omit +) => Stream = internal.fromEventListener diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index 66c3e3c0d7..8d4ddde987 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8048,3 +8048,28 @@ export const encodeText = (self: Stream.Stream): Stream.Stre const encoder = new TextEncoder() return map(self, (s) => encoder.encode(s)) }) + +/** @internal */ +export const fromEventListener = ( + target: EventTarget, + type: string, + options?: boolean | Omit +): Stream.Stream => + _async((emit) => { + let batch: Array = [] + let taskRunning = false + function cb(e: A) { + batch.push(e) + if (!taskRunning) { + taskRunning = true + queueMicrotask(() => { + const events = batch + batch = [] + taskRunning = false + emit.chunk(Chunk.unsafeFromArray(events)) + }) + } + } + target.addEventListener(type, cb as any, options) + return Effect.sync(() => target.removeEventListener(type, cb as any, options)) + }) diff --git a/packages/effect/test/Stream/fromEventListener.test.ts b/packages/effect/test/Stream/fromEventListener.test.ts new file mode 100644 index 0000000000..c800dac2dd --- /dev/null +++ b/packages/effect/test/Stream/fromEventListener.test.ts @@ -0,0 +1,23 @@ +import { Effect, Stream } from "effect" +import * as it from "effect-test/utils/extend" +import { describe } from "vitest" + +class TestTarget extends EventTarget { + emit() { + this.dispatchEvent(new Event("test-event")) + } +} + +describe("Stream.fromEventListener", () => { + it.effect("emitted count", (ctx) => + Effect.gen(function*(_) { + const target = new TestTarget() + + const count = yield* _( + Stream.fromEventListener(target, "test-event"), + Stream.interruptWhen(Effect.sync(() => target.emit()).pipe(Effect.repeatN(2))), + Stream.runCount + ) + ctx.expect(count).toEqual(3) + })) +}) diff --git a/packages/platform-browser/src/BrowserStream.ts b/packages/platform-browser/src/BrowserStream.ts new file mode 100644 index 0000000000..345372b7ba --- /dev/null +++ b/packages/platform-browser/src/BrowserStream.ts @@ -0,0 +1,24 @@ +/** + * @since 1.0.0 + */ + +import type * as Stream from "effect/Stream" +import * as internal from "./internal/stream.js" + +/** + * Creates a `Stream` from window.addEventListener. + * @since 1.0.0 + */ +export const fromEventListenerWindow: ( + type: K, + options?: boolean | Omit +) => Stream.Stream = internal.fromEventListenerWindow + +/** + * Creates a `Stream` from document.addEventListener. + * @since 1.0.0 + */ +export const fromEventListenerDocument: ( + type: K, + options?: boolean | Omit +) => Stream.Stream = internal.fromEventListenerDocument diff --git a/packages/platform-browser/src/index.ts b/packages/platform-browser/src/index.ts index 96d0578620..d60dafb5bc 100644 --- a/packages/platform-browser/src/index.ts +++ b/packages/platform-browser/src/index.ts @@ -13,6 +13,11 @@ export * as BrowserKeyValueStore from "./BrowserKeyValueStore.js" */ export * as BrowserRuntime from "./BrowserRuntime.js" +/** + * @since 1.0.0 + */ +export * as BrowserStream from "./BrowserStream.js" + /** * @since 1.0.0 */ diff --git a/packages/platform-browser/src/internal/stream.ts b/packages/platform-browser/src/internal/stream.ts new file mode 100644 index 0000000000..a37cdca36c --- /dev/null +++ b/packages/platform-browser/src/internal/stream.ts @@ -0,0 +1,17 @@ +/** + * @since 1.0.0 + */ + +import * as Stream from "effect/Stream" + +/** @internal */ +export const fromEventListenerWindow = ( + type: K, + options?: boolean | Omit +) => Stream.fromEventListener(window, type, options) + +/** @internal */ +export const fromEventListenerDocument = ( + type: K, + options?: boolean | Omit +) => Stream.fromEventListener(document, type, options)