@luckystack/sync 0.9.2 → 0.10.1
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.
- package/CHANGELOG.md +16 -1
- package/CLAUDE.md +6 -3
- package/dist/chunk-AJ36D2PQ.js +35 -0
- package/dist/chunk-AJ36D2PQ.js.map +1 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +5 -0
- package/dist/client.js.map +1 -1
- package/dist/errorCodes-B8GoTenr.d.ts +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +73 -16
- package/dist/index.js.map +1 -1
- package/docs/error-states.md +9 -4
- package/docs/room-fanout.md +48 -24
- package/docs/server-vs-client-handlers.md +8 -6
- package/package.json +3 -3
package/docs/error-states.md
CHANGED
|
@@ -20,7 +20,7 @@ Every sync error — whether produced client-side before sending or server-side
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
`message` is **always** the localized string for `errorCode` (with `errorParams` interpolated). The
|
|
23
|
+
`message` is **always** the localized string for `errorCode` (with `errorParams` interpolated). The framework itself ships no message catalog: the `sync.*` keys live in the scaffold's `src/_locales/*.json`, and `translate()` returns the raw key when a key is missing — so a code that is not in your locale files surfaces as `sync.someCode` in the UI rather than as a sentence. The canonical list of every `sync.*` code the framework can emit (both transports plus the server's HTTP sync route, e.g. `sync.disabled`, `sync.methodNotAllowed`, `sync.requestTimeout`) is exported as `SYNC_ERROR_CODES` (with the type `SyncErrorCode`) from both `@luckystack/sync` and `@luckystack/sync/client`. Write a parity test of your locale files against that array instead of retyping the tables below — the framework's own test keeps the scaffold locales in parity the same way. Project-defined codes use whatever the project catalog defines.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -56,12 +56,14 @@ Emitted by `handleSyncRequest` / `handleHttpSyncRequest` to the originator's ack
|
|
|
56
56
|
| `sync.notFound` | Neither `_server_v{N}` nor `_client_v{N}` exists for this route | `404` | — |
|
|
57
57
|
| `auth.required` | `auth.login === true` on `_server` and no session resolved | `401` | — |
|
|
58
58
|
| `auth.forbidden` (or route-specific code from `validateRequest`) | `validateRequest(auth.additional)` rejected | `403` (default; some predicates override) | predicate-defined |
|
|
59
|
+
| `sync.receiverNotAllowed` | `receiver: 'all'` requested while `sync.allowClientReceiverAll` is `false` | `403` | — |
|
|
60
|
+
| `sync.notRoomMember` | `sync.requireRoomMembership` is on and the requester is not a member of `receiver` — socket: physical membership of the originating socket; HTTP: logical membership from the session's `roomCodes` (+ its own token-room); an anonymous HTTP caller fails closed. See `/docs/ARCHITECTURE_SYNC.md` → "Room membership is transport-specific" | `403` | — |
|
|
59
61
|
| `sync.rateLimitExceeded` | Per-route or per-IP rate-limit bucket rejected | `429` | `[{ key: 'seconds', value: <retry in> }]` |
|
|
60
62
|
| `sync.invalidInputType` | `validateInputByType` rejected (Zod schema mismatch) | `400` | `[{ key: 'message', value: <readable reason> }]` |
|
|
61
63
|
| `sync.serverExecutionFailed` | `_server`'s `main(...)` threw (caught by `tryCatch`) | `500` | — |
|
|
62
64
|
| `<route-supplied>` (e.g. `board.cardNotFound`) | `_server` returned `{ status: 'error', errorCode }` | `defaultHttpStatusForResponse` or as-provided | as-provided |
|
|
63
65
|
| `sync.invalidServerResponse` | `_server` returned a value with `status` other than `'success'` / `'error'` | `500` | — |
|
|
64
|
-
| `sync.noReceiversFound` |
|
|
66
|
+
| `sync.noReceiversFound` | The cross-instance recipient list for `receiver` is empty. Both transports since 0.10.0 (the HTTP path used to answer `success` with zero recipients). Recipients are resolved BEFORE `_server` runs, so nothing has been persisted — a retry is safe | `404` | — |
|
|
65
67
|
| `<hook-supplied>` | `preSyncAuthorize` / `preSyncFanout` stop signal | from hook | from hook |
|
|
66
68
|
|
|
67
69
|
The `preSyncAuthorize` and `preSyncFanout` hooks can stop with any `errorCode` they choose; the framework normalizes the message via `normalizeErrorResponse` using the originator's locale.
|
|
@@ -88,7 +90,7 @@ Recipients see these on their own `socketEventNames.sync` channel as if they wer
|
|
|
88
90
|
`httpStatus` defaults via `defaultHttpStatusForResponse(errorCode)` in `@luckystack/core`. Default rules (paraphrased — see core for exact source):
|
|
89
91
|
|
|
90
92
|
- `auth.required` -> `401`
|
|
91
|
-
- `auth.forbidden` and similar role/policy codes -> `403`
|
|
93
|
+
- `auth.forbidden` and similar role/policy codes, plus the receiver-policy codes `sync.receiverNotAllowed` / `sync.notRoomMember` -> `403`
|
|
92
94
|
- Validation failures (`*.invalidRequest`, `*.invalidInputType`, `*.invalidName`, `routing.invalidServiceRouteName`) -> `400`
|
|
93
95
|
- Not-found (`*.notFound`, `sync.noReceiversFound`) -> `404`
|
|
94
96
|
- Rate limit (`*.rateLimitExceeded`) -> `429`
|
|
@@ -237,7 +239,9 @@ These are dev hints, not production behavior. Toggle both to `false` for product
|
|
|
237
239
|
| Server-side schema fail | `sync.invalidInputType` | `_server`'s `SyncParams.clientInput` interface + generated Zod schema |
|
|
238
240
|
| Handler threw | `sync.serverExecutionFailed` / `sync.clientExecutionFailed` | Registered error-tracker capture |
|
|
239
241
|
| Domain logic rejected | `<route-supplied errorCode>` | The `_server` or `_client` file's return path |
|
|
240
|
-
|
|
|
242
|
+
| Receiver rejected before `_server` ran (403) | `sync.receiverNotAllowed`, `sync.notRoomMember` | `projectConfig.sync.allowClientReceiverAll` / `requireRoomMembership`; on `routed-http` membership is the session's `roomCodes`, not the socket — `/docs/ARCHITECTURE_SYNC.md` |
|
|
243
|
+
| Empty room (both transports; before `_server` ran, nothing persisted) | `sync.noReceiversFound` | `receiver` argument; whether any socket actually (re)joined the room — [`./room-fanout.md`](./room-fanout.md) §8 |
|
|
244
|
+
| Recipient lookup failed (both transports; before `_server` ran, nothing persisted) | `sync.serverExecutionFailed` | The Redis adapter's `fetchSockets()` timed out: an instance on the same adapter key + Redis did not answer — `/docs/ARCHITECTURE_MULTI_INSTANCE.md` pitfalls (shared Redis across environments) |
|
|
241
245
|
| Offline + caller didn't await | `offline.queueFull` | Bump `offlineQueue.maxSize` or switch `dropPolicy` |
|
|
242
246
|
|
|
243
247
|
---
|
|
@@ -249,5 +253,6 @@ These are dev hints, not production behavior. Toggle both to `false` for product
|
|
|
249
253
|
- Rate limiting hook + buckets: [`./room-fanout.md`](./room-fanout.md) §5
|
|
250
254
|
- Hook payload shapes: `@luckystack/core` `HookPayloads`
|
|
251
255
|
- Status-code mapping source: `@luckystack/core` `defaultHttpStatusForResponse`
|
|
256
|
+
- Canonical code list for locale parity tests: `SYNC_ERROR_CODES` / `SyncErrorCode` (`@luckystack/sync` and `@luckystack/sync/client`)
|
|
252
257
|
- Locale extraction: `@luckystack/core` `extractLanguageFromHeader`, `normalizeErrorResponse`
|
|
253
258
|
- Project config: `projectConfig.rateLimiting`, `projectConfig.offlineQueue`, `projectConfig.logging`
|
package/docs/room-fanout.md
CHANGED
|
@@ -4,20 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
For the originator's `receiver` argument see [`./sync-request.md`](./sync-request.md). For per-recipient handler authoring see [`./server-vs-client-handlers.md`](./server-vs-client-handlers.md).
|
|
6
6
|
|
|
7
|
+
> **Applies from `@luckystack/sync` 0.10.0.** Earlier versions resolved recipients from this process's own adapter maps, and only the socket transport rejected an empty room. Everything below describes the cross-instance model that replaced that.
|
|
8
|
+
|
|
7
9
|
---
|
|
8
10
|
|
|
9
11
|
## 1. How `receiver` resolves to sockets
|
|
10
12
|
|
|
11
|
-
`handleSyncRequest`
|
|
13
|
+
Both transports — `handleSyncRequest` (socket) and `handleHttpSyncRequest` (HTTP/SSE) — resolve recipients the same way, in the same order:
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```
|
|
15
|
+
1. **Format the room.** The `receiver` string goes through the room-name formatter from `@luckystack/core` under the canonical `'broadcast'` purpose, so a non-identity `registerRoomNameFormatter` targets the same physical room the sockets joined (join formats the name too). `'all'` is a sentinel and is never formatted.
|
|
16
|
+
2. **Enumerate across instances — before `_server` runs.** The framework asks Socket.io for the members of that physical room on **every** instance sharing the Redis adapter (`io.in(room).fetchSockets()`), or for every socket everywhere when the receiver is `'all'`. The result is a list of `RemoteSocket`s — never a lookup in this process's local room map. This happens after auth, rate-limit and the authorize hooks but **before** input validation and the `_server` handler, so nothing has been persisted yet when it fails.
|
|
17
|
+
3. **Empty → `sync.noReceiversFound`.** An empty list ends the request with that error on **both** transports, before `_server` runs, before `preSyncFanout` fires and before any recipient is touched. See §8.
|
|
18
|
+
4. `_server` (validate input, execute, persist), then `preSyncFanout`, then the per-recipient loop (§3).
|
|
18
19
|
|
|
19
|
-
-
|
|
20
|
-
|
|
20
|
+
Three consequences worth internalising: a recipient on another instance is a first-class recipient (its `_client` runs, see §7); "does the room exist locally" is never the question — the only question is whether the cross-instance query returned members; and an error from this step is a **pre-commit** rejection — the cross-instance query can fail (an adapter request timeout when an instance on the channel does not answer) and when that happened after `_server` had persisted its mutation the caller was told `error` about a change that had been saved, so a retry applied it twice. The cost of resolving first is a recipient snapshot taken one handler-execution earlier: a socket that joins the room while `_server` runs misses that one fan-out.
|
|
21
|
+
|
|
22
|
+
Whether the sender is *allowed* to target the room at all is a separate, earlier check (`sync.requireRoomMembership`) whose semantics deliberately differ per transport — see [`/docs/ARCHITECTURE_SYNC.md`](../../../docs/ARCHITECTURE_SYNC.md) → "Room membership is transport-specific".
|
|
21
23
|
|
|
22
24
|
There is no "broadcast to all but yourself" sentinel — pass `ignoreSelf: true` to skip the originator's own sockets. See [`./ignore-self.md`](./ignore-self.md).
|
|
23
25
|
|
|
@@ -52,6 +54,8 @@ preSyncFanout (stop signal aborts before any recipient is touched)
|
|
|
52
54
|
for each socket in <resolved sockets>:
|
|
53
55
|
yield every N (configurable)
|
|
54
56
|
skip if ignoreSelf && token === recipientToken
|
|
57
|
+
preSyncRecipient (stop without overrideOutput skips this recipient, uncounted;
|
|
58
|
+
stop WITH overrideOutput replaces serverOutput for this recipient)
|
|
55
59
|
recipientCount++
|
|
56
60
|
if _client exists:
|
|
57
61
|
run clientHandler
|
|
@@ -89,7 +93,7 @@ Tuning:
|
|
|
89
93
|
- **Lower `fanoutYieldEvery` + higher `fanoutYieldMs`** = smoother for concurrent traffic but slower fanout.
|
|
90
94
|
- For typical room sizes (<100 recipients), the yield never triggers — defaults are tuned for `receiver: 'all'` worst cases.
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
Both transports yield on the same schedule — the HTTP handler's loop reads the same two config keys. An HTTP-invoked fanout still runs on the event loop of the instance that received the request, so a giant `'all'` fanout would starve it just the same.
|
|
93
97
|
|
|
94
98
|
---
|
|
95
99
|
|
|
@@ -113,6 +117,18 @@ Fires **after** `_server` runs successfully and the recipient set is resolved, *
|
|
|
113
117
|
- "Throttle fanout-heavy routes during peak load."
|
|
114
118
|
- "Inject a cross-room replication hop before the room receives the payload."
|
|
115
119
|
|
|
120
|
+
### `preSyncRecipient`
|
|
121
|
+
|
|
122
|
+
Fires once per resolved recipient, after the `ignoreSelf` skip and before that one socket receives anything. It carries the route name, the receiver, the recipient's socket id, the recipient's session token (`recipientToken`, `null` for an anonymous socket) and the `serverOutput` about to be sent. Exact shape: `HookPayloads` in `@luckystack/core`.
|
|
123
|
+
|
|
124
|
+
Semantics that differ from the other fanout hooks:
|
|
125
|
+
|
|
126
|
+
- A stop signal **without** `overrideOutput` skips just this recipient — the loop continues, and the skipped socket is not counted in `recipientCount`.
|
|
127
|
+
- A stop signal **with** `overrideOutput` still delivers to this recipient, but with the override in place of `serverOutput` — per-recipient redaction without a `_client` file.
|
|
128
|
+
- `recipientUserId` is **always `null`**. There is no resolver option: resolving a user per recipient would cost a session read per socket on the hot path. A handler that needs the user reads the session itself by `recipientToken` (the token is already known in the loop, so it costs nothing to expose).
|
|
129
|
+
|
|
130
|
+
Fires on both transports and for remote recipients too (§7).
|
|
131
|
+
|
|
116
132
|
### `postSyncFanout`
|
|
117
133
|
|
|
118
134
|
```ts
|
|
@@ -163,41 +179,49 @@ Fires before fanout begins, when either bucket rejects. Used to surface abusive
|
|
|
163
179
|
|
|
164
180
|
## 6. `recipientCount` vs raw room size
|
|
165
181
|
|
|
166
|
-
`recipientCount` differs from
|
|
182
|
+
`recipientCount` differs from the size of the resolved recipient list in two cases, identically on both transports:
|
|
167
183
|
|
|
168
184
|
1. **`ignoreSelf: true`** — every socket whose extracted token matches the sender's is skipped. If a user has 3 sockets in the room and triggered the sync themselves, `recipientCount` is `size - 3`.
|
|
169
|
-
2.
|
|
170
|
-
3. **HTTP transport** — the HTTP path's `_client` execution `continue`s without bumping `recipientCount` only for per-recipient failures it could not recover from; the socket path always bumps for handled (success or error) recipients and only skips disappeared sockets.
|
|
185
|
+
2. **`preSyncRecipient` stop without `overrideOutput`** — the recipient is skipped and not counted (§5).
|
|
171
186
|
|
|
172
|
-
|
|
187
|
+
What does **not** reduce it:
|
|
188
|
+
|
|
189
|
+
- **Sockets disappearing mid-fanout.** The recipient list is a snapshot of `RemoteSocket`s taken once, before the loop. A socket that disconnects between the snapshot and its turn is still iterated: the loop emits to the remote handle, the adapter drops the emit because the target is gone, and the recipient is still counted. There is no per-iteration "does this socket still exist" re-check against a local map — that check would only be answerable for local sockets anyway.
|
|
190
|
+
- **A failing `_client`.** The recipient is counted before `_client` runs, so a recipient that received an error frame counts the same as one that received a success frame.
|
|
191
|
+
|
|
192
|
+
So `recipientCount` is "how many recipients the loop emitted to", not "how many received the payload" — the second number does not exist on the server side of an adapter. Observers wanting delivery confirmation need a client-side ack of their own.
|
|
173
193
|
|
|
174
194
|
---
|
|
175
195
|
|
|
176
196
|
## 7. Cross-instance fanout via Redis adapter
|
|
177
197
|
|
|
178
|
-
Single-instance fanout is built into Socket.io. **Cross-instance fanout requires the Redis adapter
|
|
179
|
-
|
|
180
|
-
`@luckystack/server` wires the Redis adapter when `REDIS_URL` is set and the Socket.io Redis adapter peer is installed. With it:
|
|
198
|
+
Single-instance fanout is built into Socket.io. **Cross-instance fanout requires the Redis adapter**, which `@luckystack/server` attaches on every backend (see [`/docs/ARCHITECTURE_SOCKET.md`](../../../docs/ARCHITECTURE_SOCKET.md)). Two mechanisms ride on it:
|
|
181
199
|
|
|
182
|
-
- `io.to(
|
|
183
|
-
- `
|
|
200
|
+
- `io.to(room).emit(...)` — used by the streaming emitters — publishes to Redis; every instance delivers to its own members of that room.
|
|
201
|
+
- `io.in(room).fetchSockets()` + `RemoteSocket.emit()` — used by the regular sync fanout — enumerates the room across all instances and delivers to each member individually.
|
|
184
202
|
|
|
185
|
-
|
|
203
|
+
**The per-recipient `_client` handler runs for remote recipients too.** The fanout loop iterates the cross-instance list, runs `_client` on the instance that is handling the request (it has the route code, the `serverOutput`, and the recipient's handshake headers and token), and delivers the result through the recipient's `RemoteSocket`. No sticky sessions are needed for correctness; spreading one room's members across instances is fine. The cost model (one Redis round-trip per fanout plus one emit per remote recipient) is in [`/docs/ARCHITECTURE_MULTI_INSTANCE.md`](../../../docs/ARCHITECTURE_MULTI_INSTANCE.md).
|
|
186
204
|
|
|
187
|
-
|
|
188
|
-
|
|
205
|
+
> **Warning — `io.sockets.adapter.rooms`, `io.sockets.adapter.sids` and enumerating `io.sockets.sockets` are per-instance maps.** They answer "who is connected to *this process*", which under the Redis adapter is a partial view that looks complete: no error, just missing members. A helper that builds a room snapshot, a presence list or a broadcast target from them works on one instance and silently drops the rest of the cluster on two. Use instead:
|
|
206
|
+
>
|
|
207
|
+
> - `getRoomSockets(room, { userId? })` from `@luckystack/core` — routes the room through the formatter under `'broadcast'` and returns the cross-instance `RemoteSocket[]` (`'all'` = every socket everywhere). It throws when no Socket.io server is registered, because a silent empty list is exactly the failure this exists to prevent.
|
|
208
|
+
> - `io.in(room).fetchSockets()` / `io.to(room).emit()` directly when you already hold the physical room name.
|
|
209
|
+
>
|
|
210
|
+
> Three guards catch the mistake before production does: the ESLint rule `luckystack/no-local-socket-enumeration` in `@luckystack/core/eslint` flags the patterns statically; outside production `getIoInstance()` returns a guarded view that **throws** on those accessors (`sockets.sockets.get(id)` stays allowed); and `getIoInstance({ raw: true })` is the explicit opt-in for deliberate per-instance work — a sweep cleaning up its own connections, or sampling this process's backpressure. In production the raw instance is always returned, so the guard costs nothing on the hot path.
|
|
189
211
|
|
|
190
|
-
|
|
212
|
+
`recipientCount` in `postSyncFanout` counts recipients on every instance, not just local ones.
|
|
191
213
|
|
|
192
214
|
---
|
|
193
215
|
|
|
194
216
|
## 8. `sync.noReceiversFound`
|
|
195
217
|
|
|
196
|
-
Triggered when the
|
|
218
|
+
Triggered on **both transports** when the cross-instance recipient list (§1) comes back empty. Before 0.10.0 only the socket transport rejected this; the HTTP/SSE path returned `success` with zero recipients, so an HTTP caller could not tell "delivered" from "nobody there". Typical causes:
|
|
197
219
|
|
|
198
220
|
- The room name was misspelled.
|
|
199
221
|
- Every member already disconnected before the request reached the fanout step.
|
|
200
222
|
- A bug had the client `joinRoom`-ing under a different name than the sender's `receiver` argument.
|
|
223
|
+
- A room-name formatter that folds the caller's `userId` into the physical name — join and fanout then land in different physical rooms (see the formatter contract in `packages/core/docs/socket-bootstrap.md`).
|
|
224
|
+
- On `routed-http`: the caller's session still lists the room in `roomCodes` (so the membership check passed) but no socket has re-joined it — see the membership section in [`/docs/ARCHITECTURE_SYNC.md`](../../../docs/ARCHITECTURE_SYNC.md).
|
|
201
225
|
- `receiver: 'all'` while no sockets are connected at all (development edge case).
|
|
202
226
|
|
|
203
227
|
Surfaced to the originator as:
|
|
@@ -208,7 +232,7 @@ Surfaced to the originator as:
|
|
|
208
232
|
|
|
209
233
|
Default `httpStatus` mapping for this code comes from `defaultHttpStatusForResponse` in `@luckystack/core`.
|
|
210
234
|
|
|
211
|
-
This is not necessarily a bug — sending to an empty room is legal if the sender doesn't yet know the room is empty.
|
|
235
|
+
This is not necessarily a bug — sending to an empty room is legal if the sender doesn't yet know the room is empty. Recipients are resolved **before** `_server` runs (§1), so on this error nothing has been persisted: the mutation did NOT happen, and a retry once someone is listening is safe. (Earlier versions ran the check after `_server`, so the UI had to read this error as "saved, but nobody was listening"; that reading is now wrong.)
|
|
212
236
|
|
|
213
237
|
---
|
|
214
238
|
|
|
@@ -156,16 +156,17 @@ incoming msg (Socket.io 'sync' event)
|
|
|
156
156
|
validateRequest(auth.additional) -> auth.forbidden
|
|
157
157
|
8. dispatchHook('preSyncAuthorize') -> stop signal becomes error envelope
|
|
158
158
|
9. applySyncRateLimits() -> sync.rateLimitExceeded
|
|
159
|
-
10.
|
|
159
|
+
10. Resolve recipients (cross-instance, both transports, BEFORE anything is persisted — see ./room-fanout.md §1):
|
|
160
|
+
receiver === 'all' -> every socket on every instance (fetchSockets())
|
|
161
|
+
otherwise -> formatRoomName(receiver, 'broadcast') -> io.in(room).fetchSockets()
|
|
162
|
+
fetchSockets throws -> sync.serverExecutionFailed (adapter request timeout; nothing persisted)
|
|
163
|
+
empty list -> sync.noReceiversFound (nothing persisted)
|
|
164
|
+
11. If _server exists:
|
|
160
165
|
validateInputByType(clientInput) -> sync.invalidInputType
|
|
161
166
|
tryCatch(serverMain(...)) -> sync.serverExecutionFailed
|
|
162
167
|
status !== 'success' | 'error' -> sync.invalidServerResponse
|
|
163
168
|
status === 'error' -> server's errorCode (normalized)
|
|
164
169
|
status === 'success' -> serverOutput = result (minus status)
|
|
165
|
-
11. Resolve recipients:
|
|
166
|
-
receiver === 'all' -> io.sockets.sockets (Map of every connected socket)
|
|
167
|
-
otherwise -> io.sockets.adapter.rooms.get(receiver) (Set of IDs)
|
|
168
|
-
no sockets found -> sync.noReceiversFound
|
|
169
170
|
12. dispatchHook('preSyncFanout') -> stop signal becomes error envelope
|
|
170
171
|
13. Per-recipient loop (with periodic event-loop yield):
|
|
171
172
|
if (ignoreSelf && token === recipientToken) continue
|
|
@@ -201,7 +202,8 @@ Per-recipient failures inside step 13 do **not** abort the fanout — every othe
|
|
|
201
202
|
| Server execution | Thrown | `sync.serverExecutionFailed` | originator ack |
|
|
202
203
|
| Server return | `status: 'error'` from `_server` | route-supplied `errorCode` | originator ack |
|
|
203
204
|
| Server return | Anything other than `success`/`error` | `sync.invalidServerResponse` | originator ack |
|
|
204
|
-
|
|
|
205
|
+
| Recipients (before `_server`) | No sockets in room | `sync.noReceiversFound` | originator ack |
|
|
206
|
+
| Recipients (before `_server`) | `fetchSockets()` threw (adapter request timeout) | `sync.serverExecutionFailed` | originator ack |
|
|
205
207
|
| Fanout | `preSyncFanout` stop | hook's `errorCode` | originator ack |
|
|
206
208
|
| Per-recipient | `_client` thrown | `sync.clientExecutionFailed` | that recipient only |
|
|
207
209
|
| Per-recipient | `_client` returned `status: 'error'` | route code or `sync.clientRejected` | that recipient only |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luckystack/sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
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.
|
|
64
|
-
"@luckystack/error-tracking": "^0.
|
|
63
|
+
"@luckystack/core": "^0.10.1",
|
|
64
|
+
"@luckystack/error-tracking": "^0.10.1"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"@prisma/client": "^6.19.0",
|