Skip to content

package: wheels-websockets — opt-in realtime WS transport (RustCFML native, Lucee emulated) #3292

Description

@bpamiri

What

New first-party package wheels-websockets: an opt-in realtime WebSocket transport for Wheels channels. Core stays SSE-only (the AdonisJS/Transmit position, unchanged); this package adds WS as an additive transport for the same publish()/subscribeToChannel() semantics where the engine can serve it.

Decision record: #3154 (GO as package, 2026-07-06). Parent roadmap: #2962. Demand probe (now prioritizing engine backends + features): #3286.

Engine strategy

Engine Backend Status
RustCFML Native (wsPublish BIF + channel CFCs, shipped v0.299–v0.303) Day one — bridge seam proven live 2026-07-06: HTTP request → wsPublish() → connected WS client, on stock v0.423
Lucee 6/7 Emulation over the official lucee/extension-websocket (active, updated 2026-06) — fallback candidate: isapir/lucee-websocket (JSR-356, updated 2026-07) Day one — API spike first (extension surface → emulation shape)
Adobe / BoxLang None (cfwebsocket deprecated / absent) Demand-gated via #3286; package degrades gracefully — SSE channels keep working

Programming model — adopt the RustCFML surface

Rather than inventing a third CFML realtime API, the package adopts the channel-CFC shape RustCFML shipped (one CFC per channel, convention lifecycle onConnect/onMessage/onDisconnect/onError, connect reject gate, rooms, io() emitter, ack-by-return):

  • On RustCFML: channel CFCs are served natively by the engine; the package contributes only the channels bridge, config, and the JS client.
  • On Lucee: the package hosts the same CFC contract over the WS extension — instantiates the app's channel CFCs and provides socket/io() shims.
  • Everywhere: publish() on the Wheels channel bus fans out to connected WS clients when a backend is live.

Architecture

  • Channel-bus hook (zero core changes): Global.cfc::$getChannelEngine() lazily creates application.wheels.channelEngine; the package (loaded at app start) pre-installs a decorator that forwards every publish() to the wrapped engine AND the WS transport. Same for the database adapter singleton.
  • Engine detection at package load: feature-detect (wsPublish BIF → RustCFML; WS extension present → Lucee; else inactive with a one-time wheels.log notice). Per-package error isolation means a broken backend can never take the app down.
  • JS client: WheelsRealtime — attempts WS, falls back to the existing WheelsSSE transparently, one subscription API.
  • Package manifest: provides.mixins: global for the app-facing helpers; wheelsVersion >= 4.0.

Phasing

  • P1 — RustCFML backend + channels bridge: decorator hook, engine detection, JS client, docs, specs; verify against a Wheels app served by RustCFML (ladder rig)
  • P2 — Lucee emulation: spike lucee/extension-websocket's API; implement the channel-CFC host + socket/io() shims; CommandBox/undertow compatibility check
  • P3 — registry publish: standalone repo wheels-dev/wheels-websockets, two-PR registry flow, wheels packages add websockets
  • P4 — demand-gated: Adobe/BoxLang backends if Do you need WebSockets in Wheels? Real-world demand check for channels #3286 shows demand; requires.engines manifest field proposal if gating should become declarative
  • Upstream ask (RustCFML): configurable channel-CFC directory (Wheels apps would prefer app/websockets/ over webroot public/websockets/)

Evidence

  • RustCFML spike (2026-07-06): WS handshake 101 → welcome frame; then plain HTTP .cfm calling wsPublish(channel="/chat", event="bridged", ...) delivered the frame to the connected client. The bridge is a thin adapter, not an integration project.
  • Full surface reference: docs/websockets.md in the RustCFML repo (rooms, presence, acks, lastEventId resumability, multi-node fan-out via memberlist, raw-WS + socket.io wires, socket.io-lucee compat).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions