Current section

19 Versions

Jump to

Compare versions

9 files changed
+546 additions
-1034 deletions
  @@ -11,6 +11,45 @@ not in lockstep yet; entries note which surface they affect.
11 11
12 12 ## [Unreleased]
13 13
14 + ## [0.12.0] — 2026-06-27
15 +
16 + ### Fixed
17 +
18 + - **`musubi` / `@musubi/client`** — A WebSocket reconnect no longer leaves a
19 + root store permanently unrecoverable (`Command "…" failed: Store is not
20 + connected` until a full page reload). The whole connection multiplexed every
21 + root over one `musubi:connection` topic/channel; on a drop, Phoenix's
22 + automatic rejoin of the (never-`leave()`d) channel collided on that single
23 + topic with the new channel the recovery path opened, Phoenix closed the
24 + duplicate join, the state machine derailed, `mount` was never re-sent, and the
25 + server's `terminate/2` `stop_root` was never reversed. Each root store now
26 + owns its own channel, so recovery rides Phoenix's per-channel rejoin with no
27 + collision (see Changed).
28 +
29 + ### Changed
30 +
31 + - **`musubi` / `@musubi/client`** — **Breaking wire protocol.** Each root store
32 + now gets its own channel on topic `musubi:connection:<root_id>` instead of one
33 + shared `musubi:connection` channel multiplexing all roots by `root_id`. Join
34 + carries `{module, id, params}` and **is** the mount (the join reply returns the
35 + canonical `root_id`); leaving the channel (client `leave()` or a transport
36 + drop) **is** the unmount and stops that one root via `terminate/2`. Reconnect
37 + recovery now reuses Phoenix's built-in per-channel rejoin: the
38 + `join().receive("ok")` hook re-fires on every rejoin and rebuilds the root,
39 + keeping the last-good snapshot until the server's fresh initial patch swaps it
40 + in. The `command` / `allow_upload` / `cancel_upload` / `upload_progress` /
41 + `upload_error` payloads no longer carry a `root_id` (one root per channel).
42 + Client and server must be upgraded together.
43 +
44 + ### Removed
45 +
46 + - **`@musubi/client`** — `SocketLike.onOpen` is gone (added in 0.11.0 to drive
47 + the socket-level reopen handler). Recovery is now per-channel, so the
48 + socket-level `onOpen`/`handleSocketReopen`/`reestablishConnectionRoots`
49 + machinery — and the `mount` / `unmount` / `already_mounted` channel messages —
50 + were removed. Custom `SocketLike` implementations need only `connect` and
51 + `channel`.
52 +
14 53 ## [0.11.1] — 2026-06-25
15 54
16 55 ### Fixed
  @@ -388,7 +427,8 @@ Initial public release of the Musubi runtime (then `Arbor`):
388 427 - TypeScript client and React adapter that materialize the diff stream
389 428 into immutable snapshots.
390 429
391 - [Unreleased]: https://github.com/fahchen/musubi/compare/v0.11.1...HEAD
430 + [Unreleased]: https://github.com/fahchen/musubi/compare/v0.12.0...HEAD
431 + [0.12.0]: https://github.com/fahchen/musubi/compare/v0.11.1...v0.12.0
392 432 [0.11.1]: https://github.com/fahchen/musubi/compare/v0.11.0...v0.11.1
393 433 [0.11.0]: https://github.com/fahchen/musubi/compare/v0.10.0...v0.11.0
394 434 [0.10.0]: https://github.com/fahchen/musubi/compare/v0.9.2...v0.10.0
  @@ -34,7 +34,7 @@ Add Musubi to your Phoenix application:
34 34 ```elixir
35 35 def deps do
36 36 [
37 - {:musubi, "~> 0.11.1"}
37 + {:musubi, "~> 0.12.0"}
38 38 ]
39 39 end
40 40 ```
  @@ -10,7 +10,7 @@ In the Phoenix app:
10 10 ```elixir
11 11 def deps do
12 12 [
13 - {:musubi, "~> 0.11.1"}
13 + {:musubi, "~> 0.12.0"}
14 14 ]
15 15 end
16 16 ```
  @@ -71,15 +71,17 @@ transport params from the client. Use it for:
71 71 - long-lived assigns shared by every Musubi connection and mounted root
72 72 - rejecting the socket with `:error` or `{:error, :unauthorized}`
73 73
74 - `handle_join/2` runs once when the Musubi connection channel joins. It receives
75 - the connection join params, not root mount params. Use it for:
74 + `handle_join/2` runs once per root channel (each root store joins its own
75 + channel). It receives that root's mount params (`module` / `id` / `params`) and
76 + runs just before the root's `mount/2`. Use it for:
76 77
77 - - workspace or tenant scope checks
78 - - connection-level authorization
79 - - setting assigns shared by every root mounted on that Musubi connection
78 + - per-root authorization (reject the join with `:error` / `{:error, :unauthorized}`)
79 + - workspace or tenant scope checks keyed off the mount params
80 + - setting assigns for the root mounted on that channel
80 81
81 - Root store `mount/2` runs once for each mounted root. It receives the root's
82 - own mount params:
82 + For assigns shared by *every* root, prefer `handle_connect/2` (socket-level,
83 + runs once per socket). Root store `mount/2` then runs for the root, receiving the
84 + same mount params:
83 85
84 86 ```elixir
85 87 @impl Musubi.Store
  @@ -102,7 +104,7 @@ Use these scopes consistently:
102 104 | connect params | `new Socket("/socket", params: ...)` | Phoenix socket | auth tokens and transport credentials |
103 105 | connect info | Phoenix endpoint `connect_info` | Phoenix socket | session, peer data, headers configured by the endpoint |
104 106 | session | `connect_info[:session]` | Musubi socket | browser session data shared by mounted roots |
105 - | join params | `connect(socket, { topic: ... })` channel join payload | Musubi connection | connection-wide scopes |
107 + | join params | per-root channel join payload (`{ module, id, params }`, sent by `mountStore`) | one root store | seen by `handle_join/2`; identical to that root's mount params |
106 108 | mount params | `connection.mountStore({ module, id, params })` | one root store | business identity such as `poll_id` or `cart_id` |
107 109
108 110 Child stores do not receive `mount/2`. They run `init/1` and can still read
  @@ -1,6 +1,6 @@
1 1 {<<"links">>,[{<<"GitHub">>,<<"https://github.com/fahchen/musubi">>}]}.
2 2 {<<"name">>,<<"musubi">>}.
3 - {<<"version">>,<<"0.11.1">>}.
3 + {<<"version">>,<<"0.12.0">>}.
4 4 {<<"description">>,
5 5 <<"Server-authoritative, page-scoped runtime library for Elixir/Phoenix applications.">>}.
6 6 {<<"elixir">>,<<"~> 1.18">>}.
Loading more files…