• v4.0.1 8cdf78dcd5

    v4.0.1
    All checks were successful
    ci / ci (push) Successful in 1m29s
    release / release (push) Successful in 2s
    conformance / conformance (push) Successful in 2m37s
    Stable

    yuris released this 2026-08-22 11:41:54 +00:00 | 3 commits to main since this release

    A single-fix patch release: a call issued on the line after stdb.connect no
    longer raises. No API, option, or protocol-visible behaviour changed. The one
    observable difference is that a too-early call's error now arrives on the handle
    or callback it already returns instead of raising at the callsite; the outcome
    still lands.

    Bug fixes

    • Frames issued before the socket opens are held
      (#113) -
      stdb.connect returns before the websocket handshake completes, so a call on
      the next line was too early: it reached extension-websocket's own LuaSend
      guard (websocket.cpp:562), which raises for any state that is not
      STATE_CONNECTED, and the caller saw Connection isn't connected - an error
      from a native module they never wrote, naming neither this SDK nor readiness.
      Every other mistake here explains itself: a missing websocket dependency, a
      URL with no scheme, a stale generated module all produce messages that name
      the actual problem. Those frames are now held in a per-socket outbox and
      flushed, in order, when EVENT_CONNECTED arrives. The flush is guarded
      because HTML5 delivers that event twice for one socket - once from the
      emscripten onopen handler, once from the shared update loop. Reproduced
      in-engine against a live 2.x server before and after: the old raise also
      killed the socket, so the run reconnected and then timed out. Four assertions
      in dev/test/test_init.lua pin the hold, the flush, the order, and the
      double-EVENT_CONNECTED guard.

      The hold covers that one window and no more. After a drop, the gap until the
      replacement socket exists still raises, deliberately: set_send installs a
      new holder only when open() runs, which is after the reconnect backoff, and
      holding that gap would strand the caller whenever no socket ever replaces this
      one (reconnect = false, or after close()) - worse than a raise. A socket
      that dies without ever opening still settles every held call through the same
      "connection lost" sweep as any other drop, because stdb/connection.lua
      parks each request in pending before calling send. The READINESS note in
      stdb/init.lua now states that boundary, and says to gate on the
      "disconnected" and "socket_error" events rather than db:state(), which
      still reads its pre-drop value inside the very handler being told about the
      drop.

    Known in this release, filed as
    #115: a closed
    connection still decodes frames, which resurrects its state, re-persists
    credentials and re-emits "connected". Not reachable from a native build's
    close path. Fixed in v4.0.2.

    What's Changed

    • release: v4.0.1 in #114
    • stdb: hold frames issued before the socket opens in #113

    Full Changelog: v4.0.0...v4.0.1

    Use as a Defold library

    Add to game.project dependencies:

    https://git.nokopia.com/yuris/spacetimedb-defold/archive/v4.0.1.zip
    https://github.com/defold/extension-websocket/archive/refs/tags/4.2.4.zip
    

    extension-websocket is required: Defold does not resolve dependencies
    transitively, so without it connect fails at runtime.

    Private repo? Append ?token=<access-token> to the URL, the same
    shape Defold documents for GitLab. Basic auth in the URL
    (https://user:token@...) is not a substitute: the archive endpoint
    ignores URL credentials - a real account with a wrong password still
    gets 200 on a public repo - while ?token= is validated and a bad
    one gets a plain 401.

    Downloads