From 04807b44f284212b225563f90e46160d5e4dea4a Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sat, 13 Apr 2024 22:15:04 -0600 Subject: [PATCH 01/14] added Stream.fromEventListener --- .changeset/green-lions-jog.md | 6 ++++ packages/effect/src/Stream.ts | 10 +++++++ packages/effect/src/internal/stream.ts | 30 +++++++++++++++++++ .../test/Stream/fromEventListener.test.ts | 27 +++++++++++++++++ .../platform-browser/src/BrowserStream.ts | 28 +++++++++++++++++ .../platform-browser/src/internal/stream.ts | 17 +++++++++++ 6 files changed, 118 insertions(+) create mode 100644 .changeset/green-lions-jog.md create mode 100644 packages/effect/test/Stream/fromEventListener.test.ts create mode 100644 packages/platform-browser/src/BrowserStream.ts create mode 100644 packages/platform-browser/src/internal/stream.ts diff --git a/.changeset/green-lions-jog.md b/.changeset/green-lions-jog.md new file mode 100644 index 0000000000..ef67c499eb --- /dev/null +++ b/.changeset/green-lions-jog.md @@ -0,0 +1,6 @@ +--- +"@effect/platform": patch +"@effect/platform-browser": patch +--- + +added Stream.fromEventListener, and BrowserStream.{fromWindowEventListener, fromDocumentEventListener} for constructing a stream from addEventListener diff --git a/packages/effect/src/Stream.ts b/packages/effect/src/Stream.ts index 1ccd80b98f..7a756bc55e 100644 --- a/packages/effect/src/Stream.ts +++ b/packages/effect/src/Stream.ts @@ -4555,3 +4555,13 @@ export const decodeText: { * @category encoding */ export const encodeText: (self: Stream) => Stream = internal.encodeText + +/** + * Creates a `Stream` using addEventListener. + * @since 1.0.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 2ae65838db..b649c560f9 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8045,3 +8045,33 @@ 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 => { + return asyncScoped((emit) => + Effect.gen(function*(_) { + const controller = new AbortController() + + function cb(event: Event) { + emit(Effect.succeed(Chunk.of(event))) + } + + const options_ = typeof options === "boolean" + ? { signal: controller.signal, capture: options } + : { ...options, signal: controller.signal } + + yield* _( + Effect.sync(() => target.addEventListener(type, cb, options_)), + Effect.onInterrupt(() => Effect.sync(() => controller.abort())) + ) + + yield* _( + Effect.addFinalizer(() => Effect.sync(() => target.removeEventListener(type, cb, 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..f0f92c6c85 --- /dev/null +++ b/packages/effect/test/Stream/fromEventListener.test.ts @@ -0,0 +1,27 @@ +/** + * @since 1.0.0 + */ + +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..b10547c508 --- /dev/null +++ b/packages/platform-browser/src/BrowserStream.ts @@ -0,0 +1,28 @@ +/** + * @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 + * @example + * + * import { BrowserStream } from "@effect/platform-browser"; + * BrowserStream.fromWindowEventListener("keypress") // Stream + */ +export const fromWindowEventListener: ( + type: K, + options?: boolean | Omit +) => Stream.Stream = internal.fromWindowEventListener + +/** + * Creates a `Stream` from document.addEventListener. + * @since 1.0.0 + */ +export const fromDocumentEventListener: ( + type: K, + options?: boolean | Omit +) => Stream.Stream = internal.fromDocumentEventListener diff --git a/packages/platform-browser/src/internal/stream.ts b/packages/platform-browser/src/internal/stream.ts new file mode 100644 index 0000000000..0c52b25782 --- /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 fromWindowEventListener = ( + type: K, + options?: boolean | Omit +): Stream.Stream => Stream.fromEventListener(window, type, options) as any + +/** @internal */ +export const fromDocumentEventListener = ( + type: K, + options?: boolean | Omit +): Stream.Stream => Stream.fromEventListener(document, type, options) as any From 7e551911350fab1f43287566bea2d7208189bd5f Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sun, 14 Apr 2024 01:06:44 -0600 Subject: [PATCH 02/14] refactored --- packages/effect/src/Effect.ts | 11 +++++++++ packages/effect/src/internal/stream.ts | 33 ++++++++++---------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index 3c6051b9db..e26b280c4a 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -5403,3 +5403,14 @@ export const Tag: (id: Id) => () => }) return done } + +/** + * @since 2.0.0 + */ +export const makeAbortSignal: Effect = map( + acquireRelease( + sync(() => new AbortController()), + (controller) => sync(() => controller.abort()) + ), + (_) => _.signal +) diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index b649c560f9..4d40eb9c34 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8053,25 +8053,18 @@ export const fromEventListener = ( options?: boolean | Omit ): Stream.Stream => { return asyncScoped((emit) => - Effect.gen(function*(_) { - const controller = new AbortController() - - function cb(event: Event) { - emit(Effect.succeed(Chunk.of(event))) - } - - const options_ = typeof options === "boolean" - ? { signal: controller.signal, capture: options } - : { ...options, signal: controller.signal } - - yield* _( - Effect.sync(() => target.addEventListener(type, cb, options_)), - Effect.onInterrupt(() => Effect.sync(() => controller.abort())) - ) - - yield* _( - Effect.addFinalizer(() => Effect.sync(() => target.removeEventListener(type, cb, options_))) - ) - }) + Effect.flatMap( + Effect.makeAbortSignal, + (signal) => + Effect.sync(() => + target.addEventListener( + type, + (event) => emit(Effect.succeed(Chunk.of(event))), + typeof options === "boolean" + ? { signal, capture: options } + : { ...options, signal } + ) + ) + ) ) } From 261f7dc555c0b1871bc285c65c8c0ea912dc84e5 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sun, 14 Apr 2024 01:10:37 -0600 Subject: [PATCH 03/14] added changeset --- .changeset/orange-years-sort.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/orange-years-sort.md diff --git a/.changeset/orange-years-sort.md b/.changeset/orange-years-sort.md new file mode 100644 index 0000000000..eb67763c09 --- /dev/null +++ b/.changeset/orange-years-sort.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +added Effect.makeAbortSignal From 8c7d2997113bec9501926e15229d77c43b3203d6 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sun, 14 Apr 2024 01:52:51 -0600 Subject: [PATCH 04/14] use emit.single --- packages/effect/src/internal/stream.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index 4d40eb9c34..dcc5e4f6d9 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8059,7 +8059,7 @@ export const fromEventListener = ( Effect.sync(() => target.addEventListener( type, - (event) => emit(Effect.succeed(Chunk.of(event))), + (event) => emit.single(event), typeof options === "boolean" ? { signal, capture: options } : { ...options, signal } From fb6a893c821e2ec56784ed2bac2df58cdeb65d11 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sun, 14 Apr 2024 02:02:52 -0600 Subject: [PATCH 05/14] fix --- packages/effect/src/internal/stream.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index dcc5e4f6d9..92d7f7f45f 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8051,8 +8051,8 @@ export const fromEventListener = ( target: EventTarget, type: string, options?: boolean | Omit -): Stream.Stream => { - return asyncScoped((emit) => +): Stream.Stream => + asyncScoped((emit) => Effect.flatMap( Effect.makeAbortSignal, (signal) => @@ -8067,4 +8067,3 @@ export const fromEventListener = ( ) ) ) -} From 632d0dc6f0b71b169adf642f290941ca45995940 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sun, 14 Apr 2024 02:37:37 -0600 Subject: [PATCH 06/14] simplified --- .changeset/orange-years-sort.md | 5 ----- packages/effect/src/Effect.ts | 11 ----------- packages/effect/src/internal/stream.ts | 22 +++++++--------------- 3 files changed, 7 insertions(+), 31 deletions(-) delete mode 100644 .changeset/orange-years-sort.md diff --git a/.changeset/orange-years-sort.md b/.changeset/orange-years-sort.md deleted file mode 100644 index eb67763c09..0000000000 --- a/.changeset/orange-years-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"effect": patch ---- - -added Effect.makeAbortSignal diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index e26b280c4a..3c6051b9db 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -5403,14 +5403,3 @@ export const Tag: (id: Id) => () => }) return done } - -/** - * @since 2.0.0 - */ -export const makeAbortSignal: Effect = map( - acquireRelease( - sync(() => new AbortController()), - (controller) => sync(() => controller.abort()) - ), - (_) => _.signal -) diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index 92d7f7f45f..f4a22e6189 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8052,18 +8052,10 @@ export const fromEventListener = ( type: string, options?: boolean | Omit ): Stream.Stream => - asyncScoped((emit) => - Effect.flatMap( - Effect.makeAbortSignal, - (signal) => - Effect.sync(() => - target.addEventListener( - type, - (event) => emit.single(event), - typeof options === "boolean" - ? { signal, capture: options } - : { ...options, signal } - ) - ) - ) - ) + _async((emit) => { + function cb(e: Event) { + emit.single(e) + } + target.addEventListener(type, cb, options) + return Effect.sync(() => target.removeEventListener(type, cb, options)) + }) From 167e88fa30a94d84223684edf17cd75293b40af1 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sun, 14 Apr 2024 14:54:58 -0600 Subject: [PATCH 07/14] renamed platform fns --- .changeset/green-lions-jog.md | 2 +- packages/platform-browser/src/BrowserStream.ts | 10 +++++----- packages/platform-browser/src/internal/stream.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.changeset/green-lions-jog.md b/.changeset/green-lions-jog.md index ef67c499eb..60bd9a10cc 100644 --- a/.changeset/green-lions-jog.md +++ b/.changeset/green-lions-jog.md @@ -3,4 +3,4 @@ "@effect/platform-browser": patch --- -added Stream.fromEventListener, and BrowserStream.{fromWindowEventListener, fromDocumentEventListener} for constructing a stream from addEventListener +added Stream.fromEventListener, and BrowserStream.{fromEventListenerWindow, fromEventListenerDocument} for constructing a stream from addEventListener diff --git a/packages/platform-browser/src/BrowserStream.ts b/packages/platform-browser/src/BrowserStream.ts index b10547c508..943f9d07ce 100644 --- a/packages/platform-browser/src/BrowserStream.ts +++ b/packages/platform-browser/src/BrowserStream.ts @@ -11,18 +11,18 @@ import * as internal from "./internal/stream.js" * @example * * import { BrowserStream } from "@effect/platform-browser"; - * BrowserStream.fromWindowEventListener("keypress") // Stream + * BrowserStream.fromEventListenerWindow("keypress") // Stream */ -export const fromWindowEventListener: ( +export const fromEventListenerWindow: ( type: K, options?: boolean | Omit -) => Stream.Stream = internal.fromWindowEventListener +) => Stream.Stream = internal.fromEventListenerWindow /** * Creates a `Stream` from document.addEventListener. * @since 1.0.0 */ -export const fromDocumentEventListener: ( +export const fromEventListenerDocument: ( type: K, options?: boolean | Omit -) => Stream.Stream = internal.fromDocumentEventListener +) => Stream.Stream = internal.fromEventListenerDocument diff --git a/packages/platform-browser/src/internal/stream.ts b/packages/platform-browser/src/internal/stream.ts index 0c52b25782..be827e8816 100644 --- a/packages/platform-browser/src/internal/stream.ts +++ b/packages/platform-browser/src/internal/stream.ts @@ -5,13 +5,13 @@ import * as Stream from "effect/Stream" /** @internal */ -export const fromWindowEventListener = ( +export const fromEventListenerWindow = ( type: K, options?: boolean | Omit ): Stream.Stream => Stream.fromEventListener(window, type, options) as any /** @internal */ -export const fromDocumentEventListener = ( +export const fromEventListenerDocument = ( type: K, options?: boolean | Omit ): Stream.Stream => Stream.fromEventListener(document, type, options) as any From fd89cf27aa2cca2e25b424b8f850d8b6db032185 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sun, 14 Apr 2024 17:00:31 -0600 Subject: [PATCH 08/14] batch events Co-authored-by: Tim --- packages/effect/src/internal/stream.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index f4a22e6189..d62d8cbb31 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8053,8 +8053,19 @@ export const fromEventListener = ( options?: boolean | Omit ): Stream.Stream => _async((emit) => { + let batch: Array = [] + let taskRunning = false function cb(e: Event) { - emit.single(e) + batch.push(e) + if (!taskRunning) { + taskRunning = true + queueMicrotask(() => { + const events = batch + batch = [] + taskRunning = false + emit.chunk(Chunk.unsafeFromArray(events)) + }) + } } target.addEventListener(type, cb, options) return Effect.sync(() => target.removeEventListener(type, cb, options)) From e3d7019e8658849b85a3c760d3a35d3f67ca0083 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Tue, 16 Apr 2024 15:32:55 -0600 Subject: [PATCH 09/14] added type param --- packages/effect/src/Stream.ts | 6 +++--- packages/effect/src/internal/stream.ts | 14 +++++++------- packages/platform-browser/src/internal/stream.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/effect/src/Stream.ts b/packages/effect/src/Stream.ts index 7a756bc55e..86c72298a6 100644 --- a/packages/effect/src/Stream.ts +++ b/packages/effect/src/Stream.ts @@ -4558,10 +4558,10 @@ export const encodeText: (self: Stream) => Stream( target: EventTarget, type: string, options?: boolean | Omit -) => Stream = internal.fromEventListener +) => Stream = internal.fromEventListener diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index d62d8cbb31..592658a96e 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8047,15 +8047,15 @@ export const encodeText = (self: Stream.Stream): Stream.Stre }) /** @internal */ -export const fromEventListener = ( +export const fromEventListener = ( target: EventTarget, type: string, options?: boolean | Omit -): Stream.Stream => - _async((emit) => { - let batch: Array = [] +): Stream.Stream => + _async((emit) => { + let batch: Array = [] let taskRunning = false - function cb(e: Event) { + function cb(e: A) { batch.push(e) if (!taskRunning) { taskRunning = true @@ -8067,6 +8067,6 @@ export const fromEventListener = ( }) } } - target.addEventListener(type, cb, options) - return Effect.sync(() => target.removeEventListener(type, cb, options)) + target.addEventListener(type, cb as any, options) + return Effect.sync(() => target.removeEventListener(type, cb as any, options)) }) diff --git a/packages/platform-browser/src/internal/stream.ts b/packages/platform-browser/src/internal/stream.ts index be827e8816..a37cdca36c 100644 --- a/packages/platform-browser/src/internal/stream.ts +++ b/packages/platform-browser/src/internal/stream.ts @@ -8,10 +8,10 @@ import * as Stream from "effect/Stream" export const fromEventListenerWindow = ( type: K, options?: boolean | Omit -): Stream.Stream => Stream.fromEventListener(window, type, options) as any +) => Stream.fromEventListener(window, type, options) /** @internal */ export const fromEventListenerDocument = ( type: K, options?: boolean | Omit -): Stream.Stream => Stream.fromEventListener(document, type, options) as any +) => Stream.fromEventListener(document, type, options) From da90a802800a7d5e6d3ad97f07ae2d9ba9b8f801 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Tue, 16 Apr 2024 15:34:04 -0600 Subject: [PATCH 10/14] updated changeset --- .changeset/green-lions-jog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/green-lions-jog.md b/.changeset/green-lions-jog.md index 60bd9a10cc..e19afe54b5 100644 --- a/.changeset/green-lions-jog.md +++ b/.changeset/green-lions-jog.md @@ -1,5 +1,5 @@ --- -"@effect/platform": patch +"effect": minor "@effect/platform-browser": patch --- From 157d92cc636500ef584716897009a1ff9811d189 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Tue, 16 Apr 2024 15:36:06 -0600 Subject: [PATCH 11/14] remove comment --- packages/effect/test/Stream/fromEventListener.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/effect/test/Stream/fromEventListener.test.ts b/packages/effect/test/Stream/fromEventListener.test.ts index f0f92c6c85..c800dac2dd 100644 --- a/packages/effect/test/Stream/fromEventListener.test.ts +++ b/packages/effect/test/Stream/fromEventListener.test.ts @@ -1,7 +1,3 @@ -/** - * @since 1.0.0 - */ - import { Effect, Stream } from "effect" import * as it from "effect-test/utils/extend" import { describe } from "vitest" From a1806146db5f02bc01110231d11dbc63714d52b1 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Tue, 16 Apr 2024 17:14:20 -0600 Subject: [PATCH 12/14] Update packages/effect/src/Stream.ts Co-authored-by: Tim --- packages/effect/src/Stream.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/effect/src/Stream.ts b/packages/effect/src/Stream.ts index 86c72298a6..53db5a7ef5 100644 --- a/packages/effect/src/Stream.ts +++ b/packages/effect/src/Stream.ts @@ -4560,7 +4560,7 @@ export const encodeText: (self: Stream) => Stream( +export const fromEventListener: ( target: EventTarget, type: string, options?: boolean | Omit From 9dff6f71c8a34f2e14a60b314fb83a2f9baf4fb4 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Tue, 16 Apr 2024 17:15:24 -0600 Subject: [PATCH 13/14] ran codegen --- packages/platform-browser/src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) 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 */ From 586ec660fa1a751c10a3f3be9dbcc38692f846ea Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Tue, 16 Apr 2024 17:55:28 -0600 Subject: [PATCH 14/14] fixed docgen --- packages/effect/src/internal/stream.ts | 2 +- packages/platform-browser/src/BrowserStream.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index 592658a96e..4810bc10ed 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -8047,7 +8047,7 @@ export const encodeText = (self: Stream.Stream): Stream.Stre }) /** @internal */ -export const fromEventListener = ( +export const fromEventListener = ( target: EventTarget, type: string, options?: boolean | Omit diff --git a/packages/platform-browser/src/BrowserStream.ts b/packages/platform-browser/src/BrowserStream.ts index 943f9d07ce..345372b7ba 100644 --- a/packages/platform-browser/src/BrowserStream.ts +++ b/packages/platform-browser/src/BrowserStream.ts @@ -8,10 +8,6 @@ import * as internal from "./internal/stream.js" /** * Creates a `Stream` from window.addEventListener. * @since 1.0.0 - * @example - * - * import { BrowserStream } from "@effect/platform-browser"; - * BrowserStream.fromEventListenerWindow("keypress") // Stream */ export const fromEventListenerWindow: ( type: K,