← Back to Docs
Recipe

State Management Patterns

Predictable state containers for Meridian loaders, licensing flows, and dashboard widgets.

1. Atomic License State

Wrap KeyAuth session tokens in a single immutable snapshot. Every mutation produces a new object — no partial updates leak between the loader heartbeat and the UI.

type LicenseState = Readonly<{
  status: 'idle' | 'validating' | 'active' | 'expired'
  token: string | null
  expiry: number
}>

function reduce(state: LicenseState, event: LicenseEvent): LicenseState {
  switch (event.type) {
    case 'VALIDATE_START':
      return { ...state, status: 'validating' }
    case 'VALIDATE_OK':
      return { status: 'active', token: event.token, expiry: event.expiry }
    case 'VALIDATE_FAIL':
      return { ...state, status: 'expired', token: null }
    default:
      return state
  }
}

2. Offline Grace Cache

When the license server is unreachable, fall back to an HMAC-signed local blob. The cache is valid for 72 hours and self-destructs if the machine fingerprint changes.

interface GraceEntry {
  fingerprint: string
  issued: number
  ttl: number
  signature: Uint8Array
}

function verifyGrace(entry: GraceEntry, key: Uint8Array): boolean {
  const payload = entry.fingerprint + ':' + entry.issued + ':' + entry.ttl
  return nacl.sign.detached.verify(
    new TextEncoder().encode(payload),
    entry.signature,
    key
  )
}

3. Dashboard Widget Bus

Each widget subscribes to a typed event stream. The bus enforces at-most-once delivery and backpressure via a ring buffer. No global store — every widget owns its slice.

type WidgetEvent =
  | { type: 'HW_FINGERPRINT_READY'; id: string }
  | { type: 'LICENSE_SYNC'; status: LicenseState['status'] }
  | { type: 'HEARTBEAT_TICK'; latency: number }

class WidgetBus {
  private listeners = new Map<string, Set<(e: WidgetEvent) => void>>()

  on(widgetId: string, fn: (e: WidgetEvent) => void) {
    if (!this.listeners.has(widgetId)) this.listeners.set(widgetId, new Set())
    this.listeners.get(widgetId)!.add(fn)
  }

  emit(e: WidgetEvent) {
    for (const [, fns] of this.listeners) {
      for (const fn of fns) fn(e)
    }
  }
}