Skip to content

broadcast

Syncs a store's entire state across browser tabs using BroadcastChannel. Unlike persist, it never touches storage: every change is broadcast to other tabs directly, and applying one back travels through the reducer pipeline via an internal _apply reducer so middlewares can observe it. A tab opened after others requests the current state on activation, so it doesn't have to wait for the next change to catch up.

Basic usage

ts
import { withPlugins } from "@kin-store/core";
import { broadcast } from "@kin-store/plugins";

const store = withPlugins({ items: [] as string[] })
  .use({
    reducers: {
      add: (state, item: string) => ({ items: [...state.items, item] }),
    },
  })
  .use("broadcast", broadcast({ name: "todos" }));

store.dispatch.add("hello"); // seen by other tabs sharing the "todos" channel

Plugin methods

MethodDescription
close()Closes the underlying BroadcastChannel. Also called on destroy().

Options

OptionTypeDescription
namestringThe BroadcastChannel name. Only stores sharing the same name sync.
ts
.use("broadcast", broadcast({ name: "todos" }))

Conflict resolution

Conflicts are resolved last-write-wins by wall-clock time: if two tabs change state within the same millisecond, one of the changes is silently dropped. For state that genuinely needs conflict resolution (concurrent edits merged rather than one replacing the other), broadcast the specific operations instead of the whole state, or reach for a CRDT library.

Composing with persist

broadcast and persist solve different problems and compose cleanly: persist survives a page reload, broadcast reaches other open tabs immediately without waiting on a storage write.

ts
const store = withPlugins({ items: [] as string[] })
  .use("persist", persist({ key: "todos" }))
  .use("broadcast", broadcast({ name: "todos" }));

Inside middleware

The plugin uses an internal _apply reducer to change state, so every incoming update from another tab travels through the middleware pipeline. A logging middleware will see it:

ts
middleware: () => (ctx, next) => {
  // Includes "broadcast._apply", assuming the plugin is registered under
  // the "broadcast" namespace.
  console.log(ctx.reducer.name);
  return next();
},

MIT License. Comparison