* Shared client_hello/subscribe session sync: * 1. register the subscription FIRST (live events flow immediately; * the client dedups overlap by seq), * 2. replay durable events past the client's cursor, or emit * `resync_required` when the cursor cannot be served, * 3
(
sessionIds: readonly string[],
cursors: CursorsBySession | undefined,
)
| 299 | * session so the client can adopt the current epoch. |
| 300 | */ |
| 301 | private async syncSessions( |
| 302 | sessionIds: readonly string[], |
| 303 | cursors: CursorsBySession | undefined, |
| 304 | ): Promise<{ |
| 305 | accepted: string[]; |
| 306 | resyncRequired: string[]; |
| 307 | serverCursors: CursorsBySession; |
| 308 | }> { |
| 309 | const accepted: string[] = []; |
| 310 | const resyncRequired: string[] = []; |
| 311 | const serverCursors: CursorsBySession = {}; |
| 312 | |
| 313 | for (const sid of sessionIds) { |
| 314 | if (!this.subscriptions.has(sid)) { |
| 315 | this.subscribe(sid); |
| 316 | } |
| 317 | if (!accepted.includes(sid)) accepted.push(sid); |
| 318 | } |
| 319 | |
| 320 | if (cursors) { |
| 321 | for (const [sid, cursor] of Object.entries(cursors)) { |
| 322 | this.cursorsBySession.set(sid, cursor); |
| 323 | if (!this.subscriptions.has(sid)) { |
| 324 | this.subscribe(sid); |
| 325 | } |
| 326 | if (!accepted.includes(sid)) accepted.push(sid); |
| 327 | |
| 328 | const result = await this.wsBroadcast.getBufferedSince(sid, cursor); |
| 329 | if (result.resyncRequired !== false) { |
| 330 | this.send( |
| 331 | buildResyncRequired(sid, result.resyncRequired, result.currentSeq, result.epoch), |
| 332 | ); |
| 333 | resyncRequired.push(sid); |
| 334 | } else { |
| 335 | for (const entry of result.events) { |
| 336 | this.send(entry.envelope); |
| 337 | } |
| 338 | } |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | for (const sid of accepted) { |
| 343 | try { |
| 344 | serverCursors[sid] = await this.wsBroadcast.getCursor(sid); |
| 345 | } catch (err) { |
| 346 | this.logger.warn({ sid, err: String(err) }, 'getCursor failed for ack'); |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | return { accepted, resyncRequired, serverCursors }; |
| 351 | } |
| 352 | |
| 353 | private async onSubscribe(msg: SubscribeMessage): Promise<void> { |
| 354 | const { session_ids, cursors, watch_fs } = msg.payload; |
no test coverage detected