Skip to content

Repository files navigation

ws-edn in calcit-js

Usages

The server and browser connection snippets require their Node/WebSocket hosts, so they are marked cirru.no-check. CI validates both generated entries and runs the executable client generation/lifecycle smoke; host-independent data snippets below remain checked by calcit docs check-md.

Server side:

ws-edn.server/wss-serve! 5001
  {}
    :on-open $ fn (sid socket)
      println |opened sid
    :on-data $ fn (sid data)
      println |data sid data
    :on-close $ fn (sid event)
      println |close sid
    :on-listening $ fn ()
      println |listening
    :on-error $ fn (error)
      println error

ws-edn.server/wss-send! |demo-sid $ {} (:data "|some data")

ws-edn.server/wss-each! $ fn (sid socket)
  println sid
  ; "or send"
  ws-edn.server/wss-send! sid $ {} (:data "|some data")

; "use in onreload! to bind a new listener"
ws-edn.server/wss-set-on-data! $ fn (sid data)
  println "|received data" sid data

Client side (legacy singleton helpers remain compatible):

ws-edn.client/ws-connect! "|ws://localhost:5001"
  {}
    :on-open $ fn (event)
      println |open
    :on-data $ fn (data)
      println |data data
    :on-close $ fn (event)
      println |close
    :on-error $ fn (error)
      println error

ws-edn.client/ws-send! |demo-data

ws-edn.client/ws-connected?
; => true or false

; "use in onreload! to bind a new listener"
ws-edn.client/ws-set-on-data! $ fn (data)
  println data

ws-connect! now returns a nominal WsClient. Prefer lifecycle methods when the caller owns the connection:

let
    client $ ws-edn.client/ws-connect! |ws://localhost:5001 $ {}
  client .connected?
  match (client .send $ {} (:kind :ping))
    (:sent) nil
    (:not-open phase) (println |send-skipped phase)
  client .reconnect
  client .close

Each reconnect advances an internal generation before replacing the host socket. Late open, message, close, and error events from an older socket are ignored, so a stale callback cannot close or feed data into the active connection. Browser clients now reuse Cumulo/cumulo-util.calcit 0.0.19: visibility and online recovery signals reconnect only after the active client has reached :closed. This preserves single-flight connection attempts, and .close removes the lifecycle listeners and timers. Protocol-specific heartbeat messages and resync hooks still belong to the caller.

ws-connect! 现在返回名义类型 WsClient。连接所有者可优先使用 .connected?、.send、.reconnect 与 .close 方法;旧的单例函数继续兼容。 每次重连会先提升 generation,再替换宿主 WebSocket,因此旧连接迟到的 open/message/close/error 事件不会污染当前连接。浏览器端现复用 Cumulo/cumulo-util.calcit 0.0.19:可见性和 online 恢复信号只会在 client 已经进入 :closed 后触发连接,保持 single-flight;.close 会清理 lifecycle listener 与 timer。

Unexpected close now enters an explicit :backoff phase and schedules one retry. The immutable Cumulo backoff state is configurable through :retry-base-ms (default 500), :retry-max-ms (default 30000), and :retry-jitter (default 0.2). A successful open resets the attempt counter. Manual reconnect and visible/online recovery cancel a pending timer before connecting immediately; explicit close cancels it without reconnecting. Protocol resync hooks remain follow-up work.

意外 close 现在进入显式 :backoff,并且最多只保留一个重连 timer。可通过 :retry-base-ms(默认 500)、:retry-max-ms(默认 30000)和 :retry-jitter(默认 0.2)配置 Cumulo 的不可变 backoff 状态。连接成功会 重置 attempt;手动重连和 visible/online 恢复会先取消 timer 再立即连接;显式 close 只清理而不会再次连接。协议 resync hook 留在后续阶段。

Browser clients may opt into a generation-safe heartbeat deadline with :heartbeat-timeout-ms. The deadline starts after open and is renewed by every inbound message. When it expires, ws-edn closes only the active socket and lets the normal bounded-backoff path reconnect it. The option is disabled by default, so protocols that legitimately stay silent retain their existing behavior. Explicit close and generation replacement both cancel the pending heartbeat timer and lease.

浏览器 client 可通过 :heartbeat-timeout-ms 选择启用 generation-safe 的心跳 deadline。连接 open 后开始计时,每条入站消息都会续租;超时后 ws-edn 只关闭 当前 generation 的 socket,再由已有的有界 backoff 流程重连。该选项默认关闭, 因此允许长时间静默的旧协议不会改变行为。显式 close 与 generation 替换都会清理 待执行的 heartbeat timer 和 lease;协议层的 ping/pong 与 resync hook 仍由调用方 定义。

Typed decoding for application data:

ws-edn.schema/Track is a nominal Struct. Use the built-in decode-map-as boundary when a received map has a known shape; missing required fields and wrong field types fail before application code consumes the value.

ws-edn.schema/decode-track $ {} (:message |hello) (:time |now)

Keep open protocol payloads as Dynamic only at the WebSocket/JS boundary and decode them into a named Struct or Enum as soon as their shape is known. Do not reintroduce the removed calcit-test, lilac, or memof modules.

The module snapshot targets Calcit 0.13.77. Its local when-let macro declares an explicit syntax/expansion contract. ws-send! always returns Unit; the method-oriented .send returns WsSendOutcome instead of leaking JavaScript WebSocket.send results.

模块 Snapshot 已迁移到 Calcit 0.13.77;本地 when-let 明确声明 syntax 与 expansion。ws-send! 始终返回 Unit,方法形式 .send 返回 WsSendOutcome,不会把 JavaScript WebSocket.send 的返回值泄漏到公开 API。

Legacy class mapper (for compatibility with older payloads):

  :class-mapper $ {}
    :Person Person

Deps:

yarn add nanoid

WSS

add in options to enabled WSS mode:

{}
  :key "./path-to.pem"
  :cert "./path-to.pem"

License

MIT

About

ws-edn in calcit-js

Topics

Resources

Stars

0 stars

Watchers

2 watching

Forks

Releases

Used by

Contributors

Languages