@luckystack/sync 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,12 +4,16 @@ A receiver supplied by a client must be authorized before delivery. Server-side
4
4
 
5
5
  The two membership sources are deliberate (ADR 0059). The socket check tests the room the fanout will actually deliver to. HTTP has no originator socket, and resolving the caller's sockets across instances before authorization would add a Redis round-trip to every HTTP sync. Consequence: on routed HTTP, a successful sync does not prove that the caller is in the room or will receive the fanout. A session can still list a room that its socket has not re-joined since a reconnect. Re-joining after a reconnect is the consumer's job.
6
6
 
7
- Each authenticated socket's private token room is for session-targeted messages; never expose token room names to unrelated callers. Tenant room formatting must be consistent when joining and fanning out (`../../core/src/roomNameFormatterRegistry.ts`). The `userId` a formatter receives is context only. It is the joiner on join and the sender on fanout. A formatter that builds it into a content room's physical name therefore splits join and send into different rooms, and every broadcast reaches nobody (ADR 0060). Put per-user isolation in the room code itself.
7
+ Each authenticated socket's private token room is for session-targeted messages; never expose token room names to unrelated callers. The token room is joined without the room-name formatter, so a sync to the caller's own token is addressed raw as well (`../src/_shared/physicalReceiver.ts`). Tenant room formatting must be consistent when joining and fanning out (`../../core/src/roomNameFormatterRegistry.ts`). The `userId` a formatter receives is context only. It is the joiner on join and the sender on fanout. A formatter that builds it into a content room's physical name therefore splits join and send into different rooms, and every broadcast reaches nobody (ADR 0060). Put per-user isolation in the room code itself.
8
8
 
9
9
  ## Recipients are resolved across instances, before the handler commits
10
10
 
11
11
  Both transports enumerate the room across every instance that shares the adapter, after authorization and before the `_server` handler (ADR 0063). An empty list or a failed lookup is therefore reported before anything is persisted (see `./error-states.md`). The accepted cost: the recipient snapshot is taken one handler execution earlier, so a socket that joins while the handler runs misses that one fanout.
12
12
 
13
+ The originator is answered once the `_server` handler has saved the change and `preSyncFanout` has allowed delivery, before the per-recipient loop runs, on both transports. The loop is serial and awaits each recipient handler, so on a large room it can outlast the client's request timeout; answering after it told the caller "timed out" about a saved change, and a retry applied it twice. A failure inside the loop is logged and tracked, never reported to the originator (ADR 0063).
14
+
15
+ Frames that reach other sockets are keyed on the server-resolved route only. Recipients fire callbacks for both the route name and the callback name, so a client-chosen callback name would let a room member trigger another route's callbacks with data that never passed that route's server checks.
16
+
13
17
  A recipient on another instance is a full recipient. Its per-recipient handler runs on the instance handling the request, and the result is delivered through the remote socket handle. Correctness does not need sticky sessions.
14
18
 
15
19
  Never build a fanout target, presence list or room snapshot from the per-instance socket maps (`io.sockets.adapter.rooms`, `adapter.sids` or enumerating `io.sockets.sockets`). Under the Redis adapter they return a partial view that looks complete, with no error, and silently drop the rest of the cluster (ADR 0061). Use `getRoomSockets` (`../../core/src/roomSockets.ts`) or `io.in(room).fetchSockets()`. A lint rule, `no-local-socket-enumeration`, and a guard on `getIoInstance()` that throws outside production both catch this mistake. Deliberate per-instance work opts out explicitly with a stated reason. See `../../core/src/localSocketEnumerationGuard.ts`, `../../core/docs/redis-adapter.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luckystack/sync",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -60,8 +60,8 @@
60
60
  "test": "vitest run"
61
61
  },
62
62
  "dependencies": {
63
- "@luckystack/core": "^0.13.0",
64
- "@luckystack/error-tracking": "^0.13.0"
63
+ "@luckystack/core": "^0.15.0",
64
+ "@luckystack/error-tracking": "^0.15.0"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "@prisma/client": "^6.19.0",