Stone.jsDocs
Paradigm

Extensions

Realtime

One API to push live updates, on the server and in the browser alike. @stone-js/realtime gives you a single Broadcaster: publish an event on a channel with to(channel).emit(event, payload), subscribe with on(channel, listener), and read members(channel) for presence. It runs over an in-process broadcaster (zero-config) or Redis pub/sub, and the exact same API backs the isomorphic client.

#Install

terminalbash
npm i @stone-js/realtime
npm i ioredis   # only for the Redis (multi-node) fan-out
npm i ws        # only for the Node client (the browser uses the global WebSocket)

#Enable it

The principle

Emitting a live update is part of the domain. Which socket carries it, and whether it fans out across one node or a fleet, is context. The domain should write the intent once and let the runtime resolve the transport.

In Stone.js

One agnostic Broadcaster contract backs every driver. The provider binds the manager as realtimeManager and the default connection as realtime. Gateways are routed by the light key-router from @stone-js/router (a WS adapter runs each socket event through the kernel). The core is never touched.

app/Application.tsdeclarativeimperative
import { StoneApp } from '@stone-js/core'
import { Realtime } from '@stone-js/realtime'

@Realtime({ driver: 'redis', url: 'redis://localhost:6379' })
@StoneApp({ name: 'app' })
export class Application {}
import { defineConfig } from '@stone-js/core'
import { defineRealtime } from '@stone-js/realtime'

export const AppConfig = defineConfig(defineRealtime({
  default: 'redis',
  connections: [
    { name: 'redis', driver: 'redis', url: 'redis://localhost:6379', prefix: 'rt' },
    { name: 'local', driver: 'memory' }
  ]
}))

#Broadcast

app/OrderService.tsts
export class OrderService {
  constructor (private readonly realtime) {}          // the default broadcaster

  async ship (order) {
    // every subscriber of the channel receives it, on this node or across the Redis fleet
    await this.realtime.to(`order:${order.id}`).emit('shipped', { at: Date.now() })
  }
}

#Listen with a gateway

A gateway is a plain, dependency-injected class. Its methods react to connection lifecycle events and to specific channel events, one method each. Like every keyed handler they receive (payload, event); reach the originating connection with connectionOf(event).

app/Chat.tsts
import { RealtimeGateway, OnConnect, OnDisconnect, OnEvent, connectionOf } from '@stone-js/realtime'

@RealtimeGateway()
export class Chat {
  constructor (private readonly realtime) {}

  @OnConnect()    onConnect (_, event) { const connection = connectionOf(event) /* greet, authorize… */ }
  @OnDisconnect() onLeave (_, event)   { /* clean up presence… */ }

  @OnEvent('room:1', 'message')
  async onMessage (payload, event) {
    await this.realtime.to('room:1').emit('message', payload)
  }
}

The full set of method decorators: @OnConnect, @OnDisconnect, @OnMessage, @OnError, @OnSubscribe, @OnUnsubscribe and @OnEvent(channel, event). Each is a thin alias of the light router's @OnKey; @RealtimeGateway is @KeyHandler.

#Presence

app/Room.tsts
export class Room {
  constructor (private readonly realtime) {}
  async who (channel: string) {
    return await this.realtime.members(channel)   // [{ connectionId, info }]
  }
}

#On the frontend

The isomorphic client speaks the same Broadcaster API, so the code that emits and listens is written once. The browser uses the global WebSocket; on Node it loads ws lazily.

app/live.tsts
import { RealtimeClient } from '@stone-js/realtime'

const realtime = RealtimeClient.create({ url: 'wss://api.example.com/ws' })

realtime.on('room:1', (message) => render(message.payload))
await realtime.to('room:1').emit('message', { text: 'hi' })

#Drivers

driverTypeDescription
memorybuilt-inIn-process fan-out to local listeners and held connections. Zero-config default; single node.
redisioredisPub/sub fan-out across every node: publish on a channel, each instance delivers to its local sockets.
providercoming nextCloud fan-out (EventBridge, Momento, Ably) wired as a driver, never grafted onto core.

Stone.js

Your app exists in every runtime. Until you run it.

An open-source project by Stone Foundation
Created by Mr. Stone (Evens Pierre)