Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/smogon/pokemon-showdown-client/llms.txt

Use this file to discover all available pages before exploring further.

PSStreamModel<T> solves a specific problem that plain PSModel cannot: what happens to events that fire before any listener is attached? In the PS client, many events — server messages, preference changes, background loads — can arrive during the JavaScript boot sequence, before Preact panels have mounted and called subscribe. PSStreamModel holds those events in a backlog array and replays them the moment the first subscriber registers, so nothing is silently dropped. Once at least one subscriber exists the backlog is cleared and events flow directly to listeners as they would in a normal observable.
Nullish values are never added to the backlog. Passing null or undefined to update() while there are no subscribers is a deliberate signal (e.g. “bulk reload”) — it is forwarded to live subscribers but is intentionally not buffered.

Class Signature

class PSStreamModel<T = string>
The default type parameter is string, reflecting its primary use case: streaming string keys or identifiers (like preference keys) to late-binding subscribers.

Properties

subscriptions
PSSubscription<T>[]
required
The live list of all active subscriptions. Managed automatically by subscribe() and PSSubscription.unsubscribe(). Before the first subscriber registers, this array is empty and events are buffered in backlog instead.
backlog
NonNullable<T>[] | null
required
Stores events that were fired before any subscriber registered.
  • Starts as an empty array [].
  • Appended to (with non-nullish values) whenever update(value) is called while subscriptions is empty.
  • Set to null permanently after the first subscriber registers and the backlog is replayed — null is the sentinel meaning “backlog phase is over”.
Inspect this value to determine whether the stream has ever had a subscriber:
  • backlog !== null → still in pre-subscriber phase; backlog may contain queued events.
  • backlog === null → at least one subscriber has existed; replay is complete.

Methods

subscribe(listener)
PSSubscription<T>
Registers a listener. If events are sitting in the backlog they are replayed synchronously in order before this call returns, and then the backlog is set to null. Future events flow directly to all registered listeners.
// Any events that fired before this line are replayed immediately
const sub = PS.prefs.subscribe((changedKey) => {
  if (changedKey === 'theme' || changedKey === null) applyTheme();
});
subscribeAndRun(listener, value?)
PSSubscription<T>
Like subscribe, but additionally fires the listener once synchronously with value after the backlog has been replayed. Defaults to calling the listener with null! if value is omitted.
PS.prefs.subscribeAndRun((changedKey) => {
  // called immediately with null, then on every pref change
  renderOptionsPanel();
});
update(value)
void
Fires an event with the given value.
  • If subscriptions is non-empty: calls every listener with value.
  • If subscriptions is empty and value is non-nullish: appends value to backlog for later replay.
  • If subscriptions is empty and value is null or undefined: the event is discarded (not added to backlog).
// Fires to all live subscribers; also buffered if no one is listening yet
myStream.update('volume');

// Fires to all live subscribers; NOT buffered if no one is listening yet
myStream.update(null);

PSPrefs — The canonical PSStreamModel

PSPrefs (accessed as PS.prefs) is the built-in PSStreamModel<string | null> that tracks every user preference. It extends PSStreamModel rather than PSModel to ensure preference updates that arrive during boot (before panels mount) are not lost.
class PSPrefs extends PSStreamModel<string | null> { … }
The update value convention:
ValueMeaning
"theme"Only the theme preference changed.
"volume"Only the volume preference changed.
nullAll preferences were reloaded (bulk change).
When writing a subscriber for PS.prefs, always handle null as a signal to re-read all preferences, not just one. The null case fires after PSStorage delivers preferences from the cross-origin iframe on page load.

Usage Examples

import { PSStreamModel } from './client-core';

// Create a stream of chat room IDs as they receive messages
const activeRooms = new PSStreamModel<string>();

// Somewhere during boot, before any panel is ready:
activeRooms.update('lobby');
activeRooms.update('help');

// Later, when the sidebar panel mounts:
const sub = activeRooms.subscribe((roomId) => {
  // Called immediately with 'lobby', then 'help' (backlog replay),
  // then called again for every future update.
  highlightTab(roomId);
});

Backlog Lifecycle

When a PSStreamModel is first created, backlog is an empty array and subscriptions is empty. Every call to update(nonNullValue) appends to backlog. Nullish values are dropped silently in this phase.
new PSStreamModel()
  backlog: []          ← collecting events
  subscriptions: []

.update('lobby')
  backlog: ['lobby']
  subscriptions: []

.update(null)          ← nullish: not buffered
  backlog: ['lobby']
  subscriptions: []
The moment subscribe(listener) is called, all buffered events are replayed to the new listener in order, then backlog is set to null to signal that the pre-subscriber phase is over.
.subscribe(handler)
  → handler('lobby')   ← replay
  backlog: null        ← phase over; future subscribers do NOT replay
  subscriptions: [sub]
Only the first subscriber triggers backlog replay. Subscribers added later receive only future events — they do not see the replayed backlog. Design your streams so that a single authoritative subscriber handles the backlog (e.g. a top-level store).
Once backlog is null, every update(value) call goes directly to all live subscribers. New subscribers joining at this stage receive only future events.
.update('help')
  → handler('help')    ← direct delivery
  backlog: null

PSStreamModel vs PSModel

PSStreamModel

Use when events can arrive before listeners are ready. Keeps a backlog of non-nullish events and replays them to the first subscriber. Best for boot-time data streams: preferences, backgrounds, early server messages.

PSModel

Use when late subscribers simply don’t need history. Notifies all current listeners immediately and discards the value afterwards. Best for UI-driven state like connection status, panel focus, and render triggers.

Build docs developers (and LLMs) love