From 37b1bab45250d8916b848f22b134dbf983a9a8a3 Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Sun, 3 May 2026 13:54:48 -0400 Subject: [PATCH] Ported to Solid 2.0 beta 10 --- .changeset/event-bus-solid2-migration.md | 25 ++++++++++ packages/event-bus/README.md | 16 ++----- packages/event-bus/package.json | 6 +-- packages/event-bus/src/eventStack.ts | 15 +++--- packages/event-bus/src/utils.ts | 18 +++---- packages/event-bus/test/eventHub.test.ts | 3 +- packages/event-bus/test/eventStack.test.ts | 21 ++++---- packages/event-bus/test/utils.test.ts | 56 ++++++++++++---------- pnpm-lock.yaml | 4 +- 9 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 .changeset/event-bus-solid2-migration.md diff --git a/.changeset/event-bus-solid2-migration.md b/.changeset/event-bus-solid2-migration.md new file mode 100644 index 000000000..b2e6eb2e4 --- /dev/null +++ b/.changeset/event-bus-solid2-migration.md @@ -0,0 +1,25 @@ +--- +"@solid-primitives/event-bus": major +--- + +Migrate to Solid.js v2.0 (beta.10) + +## Breaking Changes + +**Peer dependency**: `solid-js@^2.0.0-beta.10` is now required. + +### `batchEmits` + +In Solid 2.0, all signal writes are automatically batched via microtasks. `batchEmits` is now a no-op that returns the bus unchanged. The `batch` wrapper has been removed. + +### `toEffect` + +Updated to use the Solid 2.0 split `createEffect(compute, apply)` signature. The `on()` helper (removed in Solid 2.0) has been replaced with the split effect pattern. The reactive owner is now explicitly captured at creation time and forwarded via `runWithOwner` in the apply phase, since the apply phase is unowned in Solid 2.0. + +### `createEventStack` + +The internal stack signal is created with `{ ownedWrite: true }` to allow writes from event listeners called within reactive contexts (e.g. `createRoot`, effects). Signal reads are now deferred in Solid 2.0, so the stack value emitted in event payloads is now computed inside the setter callback to ensure it reflects the updated state. + +### Tests + +Tests that read signal values after `emit()` now require `flush()` to commit pending signal updates before assertions. diff --git a/packages/event-bus/README.md b/packages/event-bus/README.md index f71bf899f..a07a00f98 100644 --- a/packages/event-bus/README.md +++ b/packages/event-bus/README.md @@ -25,6 +25,8 @@ pnpm add @solid-primitives/event-bus yarn add @solid-primitives/event-bus ``` +Requires `solid-js` `^2.0.0-beta.10`. + ## `createEventBus` Provides all the base functions of an event-emitter, plus additional functions for managing listeners, it's behavior could be customized with an config object. Good for advanced usage. @@ -276,26 +278,14 @@ emitInEffect(); // listener will log an owner object ### `batchEmits` -Wraps `emit` calls inside a `batch` call. It causes that listeners execute in a single batch, so they are not executed in sepatate queue ticks. +In Solid 2.0, all signal writes are automatically batched via microtasks, so this function is now a no-op that returns the bus unchanged. It is kept for backwards compatibility. ```ts import { createEventBus, batchEmits } from "@solid-primitives/event-bus"; const bus = batchEmits(createEventBus()); - -const [a, setA] = createSignal(0); -const [b, setB] = createSignal(0); - -bus.listen(setA); -bus.listen(setB); - -bus.emit(1); // will set both a and b to 1 in a single batch ``` -## Demo - -https://codesandbox.io/s/solid-primitives-event-bus-6fp4h?file=/index.tsx - ## Changelog See [CHANGELOG.md](./CHANGELOG.md) diff --git a/packages/event-bus/package.json b/packages/event-bus/package.json index c53f7cf6b..fba87420f 100644 --- a/packages/event-bus/package.json +++ b/packages/event-bus/package.json @@ -1,6 +1,6 @@ { "name": "@solid-primitives/event-bus", - "version": "1.1.3", + "version": "2.0.0", "description": "A collection of SolidJS primitives providing various features of a pubsub/event-emitter/event-bus.", "author": "Damian Tarnawski @thetarnav ", "license": "MIT", @@ -56,10 +56,10 @@ "@solid-primitives/utils": "workspace:^" }, "peerDependencies": { - "solid-js": "^1.6.12" + "solid-js": "^2.0.0-beta.10" }, "typesVersions": {}, "devDependencies": { - "solid-js": "^1.9.7" + "solid-js": "2.0.0-beta.10" } } diff --git a/packages/event-bus/src/eventStack.ts b/packages/event-bus/src/eventStack.ts index 80552f46a..f518eb050 100644 --- a/packages/event-bus/src/eventStack.ts +++ b/packages/event-bus/src/eventStack.ts @@ -25,11 +25,11 @@ export type EventStackConfig = { /** * Provides all the base functions of an event-emitter, functions for managing listeners, it's behavior could be customized with an config object. * Additionally it provides the emitted events in a list/history form, with tools to manage it. - * + * * @returns event stack: `{listen, emit, remove, clear, value, setValue}` - * + * * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/event-bus#createEventStack - * + * * @example const bus = createEventStack<{ message: string }>(); // can be destructured: @@ -61,19 +61,22 @@ export function createEventStack( ): EventStack { const { toValue = (e: any) => e as V, length = 0 } = config; - const [stack, setValue] = /*#__PURE__*/ createSignal([]); + const [stack, setValue] = /*#__PURE__*/ createSignal([], { ownedWrite: true }); const eventEventBus = createEventBus(); const valueEventBus = createEventBus>(); eventEventBus.listen(event => { const value = toValue(event, stack()); + // Capture the new stack inside the setter because signal reads are deferred + // in Solid — stack() after setValue() still returns the old committed value. + let newStack: V[]; setValue(prev => { const list = push(prev, value); - return length && list.length > length ? drop(list) : list; + return (newStack = length && list.length > length ? drop(list) : list); }); valueEventBus.emit({ event: value, - stack: stack(), + stack: newStack!, remove: () => remove(value), }); }); diff --git a/packages/event-bus/src/utils.ts b/packages/event-bus/src/utils.ts index bab5c7c31..32030a033 100644 --- a/packages/event-bus/src/utils.ts +++ b/packages/event-bus/src/utils.ts @@ -1,6 +1,6 @@ import { push } from "@solid-primitives/utils/immutable"; import { type AnyFunction } from "@solid-primitives/utils"; -import { batch, createEffect, createSignal, on } from "solid-js"; +import { createEffect, createSignal, getOwner, runWithOwner } from "solid-js"; import type { Listen, Listener, Emit } from "./eventBus.js"; /** @@ -62,25 +62,25 @@ export function once(subscribe: Listen, listener: Listener): VoidFuncti * emitInEffect() // listener will log an owner object */ export function toEffect(emit: Emit): Emit { + const owner = getOwner(); const [stack, setStack] = createSignal([]); createEffect( - on(stack, stack => { + () => stack(), + stack => { if (!stack.length) return; setStack([]); - stack.forEach(emit as Emit); - }), + runWithOwner(owner, () => stack.forEach(emit as Emit)); + }, ); return (payload?: any) => void setStack(p => push(p, payload)); } /** - * Wraps `emit` calls inside a `batch` call. It causes that listeners execute in a single batch, so they are not executed in sepatate queue ticks. + * In Solid 2.0 all signal writes are automatically batched via microtask. This function + * is kept for backwards compatibility but is now a no-op — it simply returns the bus unchanged. * * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/event-bus#batchEmits */ export function batchEmits(bus: T): T { - return { - ...bus, - emit: (...args) => batch(() => bus.emit(...args)), - }; + return bus; } diff --git a/packages/event-bus/test/eventHub.test.ts b/packages/event-bus/test/eventHub.test.ts index c8fc3dc01..3c71f0941 100644 --- a/packages/event-bus/test/eventHub.test.ts +++ b/packages/event-bus/test/eventHub.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect, vi } from "vitest"; -import { createRoot } from "solid-js"; +import { createRoot, flush } from "solid-js"; import { createEventBus, createEventHub, createEventStack } from "../src/index.js"; const syncTest = (name: string, fn: (dispose: () => void) => void) => @@ -57,6 +57,7 @@ describe("createEventHub", () => { hub.emit("busA"); hub.emit("busB", { text: "bar" }); + flush(); expect(hub.value.busA).toBe(undefined); expect(hub.value.busB).toEqual([{ text: "bar" }]); diff --git a/packages/event-bus/test/eventStack.test.ts b/packages/event-bus/test/eventStack.test.ts index cc5e272f1..137164be5 100644 --- a/packages/event-bus/test/eventStack.test.ts +++ b/packages/event-bus/test/eventStack.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from "vitest"; -import { createComputed, createRoot } from "solid-js"; +import { createRoot, flush } from "solid-js"; import { createEventStack } from "../src/index.js"; describe("createEventStack", () => { @@ -18,17 +18,20 @@ describe("createEventStack", () => { }); emit(["foo"]); + flush(); expect(captured[0]).toBe("foo"); expect(capturedStack.length).toBe(1); expect(value().length).toBe(1); emit(["bar"]); + flush(); expect(captured[1]).toBe("bar"); expect(capturedStack.length).toBe(2); expect(value().length).toBe(2); allowRemove = true; emit(["baz"]); + flush(); expect(captured[2]).toBe("baz"); expect(capturedStack.length).toBe(3); expect(value().length).toBe(2); @@ -70,37 +73,39 @@ describe("createEventStack", () => { expect(value()).toEqual([]); emit(["foo"]); + flush(); expect(value()).toEqual([["foo"]]); const x: [string] = ["bar"]; emit(x); + flush(); expect(value()).toEqual([["foo"], ["bar"]]); expect(remove(x)).toBe(true); + flush(); expect(remove(["hello"])).toBe(false); expect(value().length).toBe(1); const y: [string][] = [["0"], ["1"]]; setValue(y); + flush(); expect(value()).toEqual(y); }); test("stack is reactive", () => createRoot(dispose => { const { emit, value } = createEventStack<{ t: string }>(); - let captured: any; - createComputed(() => { - captured = value(); - }); - expect(captured).toEqual([]); + expect(value()).toEqual([]); emit({ t: "foo" }); - expect(captured).toEqual([{ t: "foo" }]); + flush(); + expect(value()).toEqual([{ t: "foo" }]); emit({ t: "bar" }); - expect(captured).toEqual([{ t: "foo" }, { t: "bar" }]); + flush(); + expect(value()).toEqual([{ t: "foo" }, { t: "bar" }]); dispose(); })); diff --git a/packages/event-bus/test/utils.test.ts b/packages/event-bus/test/utils.test.ts index 51d8cf3a3..0e3a6a2dd 100644 --- a/packages/event-bus/test/utils.test.ts +++ b/packages/event-bus/test/utils.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from "vitest"; -import { createRoot, getOwner } from "solid-js"; +import { createRoot, flush, getOwner } from "solid-js"; import { createEventBus, once, toEffect, toPromise } from "../src/index.js"; describe("toPromise", () => { @@ -38,31 +38,35 @@ describe("once", () => { describe("toEffect", () => { test("toEffect()", () => - createRoot(dispose => { - const captured: any[] = []; - let capturedOwner: any; - const { listen, emit } = createEventBus(); - const emitInEffect = toEffect(emit); - listen(e => { - captured.push(e); - capturedOwner = getOwner(); - }); + new Promise(resolve => + createRoot(dispose => { + const captured: any[] = []; + let capturedOwner: any; + const { listen, emit } = createEventBus(); + const emitInEffect = toEffect(emit); + listen(e => { + captured.push(e); + capturedOwner = getOwner(); + }); - // owner gets set to null synchronously after root executes - setTimeout(() => { - emit("foo"); - expect( - capturedOwner, - "owner will should not be available inside listener after using normal emit", - ).toBe(null); + // owner is null after the synchronous root callback returns + setTimeout(() => { + emit("foo"); + expect( + capturedOwner, + "owner should not be available inside listener after using normal emit", + ).toBe(null); - emitInEffect("bar"); - expect(captured).toEqual(["foo", "bar"]); - expect( - capturedOwner, - "owner will should be available inside listener after using emitInEffect", - ).not.toBe(null); - dispose(); - }, 0); - })); + emitInEffect("bar"); + flush(); + expect(captured).toEqual(["foo", "bar"]); + expect( + capturedOwner, + "owner should be available inside listener after using emitInEffect", + ).not.toBe(null); + dispose(); + resolve(); + }, 0); + }), + )); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f59c63a9c..a04f56b3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -283,8 +283,8 @@ importers: version: link:../utils devDependencies: solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.10 + version: 2.0.0-beta.10 packages/event-dispatcher: devDependencies: