@@ -13,6 +13,10 @@ import { isCommandRecord, meteredBytes } from "../utils.js";
1313import { FifoQueue } from "./queue.js" ;
1414import type { AppendResult , CloseResult } from "./result.js" ;
1515import { err , errClose , ok , okClose } from "./result.js" ;
16+ import {
17+ type CaughtUpBoundary ,
18+ CaughtUpTracker ,
19+ } from "./stream/caught-up-tracker.js" ;
1620import type {
1721 AcksStream ,
1822 AppendRecord ,
@@ -229,25 +233,10 @@ export async function withRetries<T>(
229233
230234 throw lastError ;
231235}
232- /** Error a pending `caughtUp()` settles with when the session ends first. */
233- function sessionClosedError ( ) : S2Error {
234- return new S2Error ( {
235- message : "Read session ended before catching up" ,
236- code : "SESSION_CLOSED" ,
237- status : 0 ,
238- origin : "sdk" ,
239- } ) ;
240- }
241-
242- type CaughtUpWaiter = {
243- resolve : ( tail : Types . StreamPosition ) => void ;
244- reject : ( error : S2Error ) => void ;
245- } ;
246-
247236type ReadDelivery < Format extends "string" | "bytes" > = {
248237 records : Types . ReadRecord < Format > [ ] ;
249238 next : number ;
250- caughtUpTail ?: API . StreamPosition ;
239+ boundary ?: CaughtUpBoundary ;
251240 onDrained ?: ( ) => void ;
252241} ;
253242
@@ -262,41 +251,7 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
262251 private _recordsRead : number = 0 ;
263252 private _bytesRead : number = 0 ;
264253
265- private _isCaughtUp = false ;
266- private _caughtUpTail : API . StreamPosition | undefined = undefined ;
267- private caughtUpWaiters : CaughtUpWaiter [ ] = [ ] ;
268- private terminal : { error ?: S2Error } | undefined = undefined ;
269-
270- /** Mark caught up and resolve pending waits with this tail. */
271- private markCaughtUp ( tail : API . StreamPosition ) : void {
272- this . _isCaughtUp = true ;
273- this . _caughtUpTail = tail ;
274- const waiters = this . caughtUpWaiters . splice ( 0 ) ;
275- const position = toSDKStreamPosition ( tail ) ;
276- for ( const waiter of waiters ) {
277- waiter . resolve ( position ) ;
278- }
279- }
280-
281- private markBehind ( ) : void {
282- this . _isCaughtUp = false ;
283- }
284-
285- /** End the session and reject pending waits. Clean ends preserve caught-up state. */
286- private settleTerminal ( error ?: S2Error ) : void {
287- if ( this . terminal ) {
288- return ;
289- }
290- this . terminal = { error } ;
291- if ( error ) {
292- this . _isCaughtUp = false ;
293- }
294- const waiters = this . caughtUpWaiters . splice ( 0 ) ;
295- const rejection = error ?? sessionClosedError ( ) ;
296- for ( const waiter of waiters ) {
297- waiter . reject ( rejection ) ;
298- }
299- }
254+ private readonly caughtUpTracker : CaughtUpTracker ;
300255
301256 static async create < Format extends "string" | "bytes" = "string" > (
302257 generator : (
@@ -361,6 +316,7 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
361316 ...config ,
362317 } ;
363318 const format = ( args ?. as ?? "string" ) as Format ;
319+ const caughtUpTracker = new CaughtUpTracker ( ) ;
364320 let session : TransportReadSession < Format > | undefined = initialSession ;
365321 let currentReader :
366322 | ReadableStreamDefaultReader < TransportReadEvent < Format > >
@@ -379,9 +335,9 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
379335 } ;
380336 const enqueueBatch = async (
381337 records : Types . ReadRecord < Format > [ ] ,
382- caughtUpTail ?: API . StreamPosition ,
338+ boundary ?: CaughtUpBoundary ,
383339 ) => {
384- if ( ! caughtUpTail ) {
340+ if ( ! boundary ) {
385341 if ( records . length > 0 ) {
386342 deliveries . push ( { records, next : 0 } ) ;
387343 wake ( ) ;
@@ -391,22 +347,22 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
391347 await new Promise < void > ( ( onDrained ) => {
392348 resolvePendingDelivery = onDrained ;
393349 if ( records . length === 0 && deliveries . length === 0 ) {
394- this . markCaughtUp ( caughtUpTail ) ;
350+ boundary . markDelivered ( ) ;
395351 onDrained ( ) ;
396352 return ;
397353 }
398354 deliveries . push ( {
399355 records,
400356 next : 0 ,
401- caughtUpTail ,
357+ boundary ,
402358 onDrained,
403359 } ) ;
404360 wake ( ) ;
405361 } ) ;
406362 resolvePendingDelivery = undefined ;
407363 } ;
408364 const failPump = ( error : S2Error ) => {
409- this . settleTerminal ( error ) ;
365+ caughtUpTracker . end ( error ) ;
410366 pumpError = error ;
411367 deliveries . clear ( ) ;
412368 wake ( ) ;
@@ -504,7 +460,7 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
504460 if ( done || cancelled ) {
505461 reader . releaseLock ( ) ;
506462 currentReader = undefined ;
507- this . settleTerminal ( ) ;
463+ caughtUpTracker . end ( ) ;
508464 pumpDone = true ;
509465 wake ( ) ;
510466 return ;
@@ -549,7 +505,7 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
549505 nextArgs . wait = baselineWait ;
550506 }
551507 }
552- this . markBehind ( ) ;
508+ caughtUpTracker . reconnect ( ) ;
553509
554510 try {
555511 await session . cancel ?.( "retry" ) ;
@@ -580,15 +536,13 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
580536 return ;
581537 }
582538
583- this . markBehind ( ) ;
584539 const { records, tail } = result . batch ;
585540 const lastRecord = records . at ( - 1 ) ;
586- const caughtUpTail =
587- tail &&
588- ( lastRecord === undefined ||
589- lastRecord . seq_num + 1 === tail . seq_num )
590- ? tail
591- : undefined ;
541+ const boundary = caughtUpTracker . observeBatch ( {
542+ recordCount : records . length ,
543+ lastSeqNum : lastRecord ?. seq_num ,
544+ tail : tail ? toSDKStreamPosition ( tail ) : undefined ,
545+ } ) ;
592546 const visibleRecords : Types . ReadRecord < Format > [ ] = [ ] ;
593547 for ( const record of records ) {
594548 this . _nextReadPosition = {
@@ -602,14 +556,12 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
602556 visibleRecords . push ( toSDKReadRecord ( record , format ) ) ;
603557 }
604558 }
605- await enqueueBatch ( visibleRecords , caughtUpTail ) ;
559+ await enqueueBatch ( visibleRecords , boundary ) ;
606560 }
607561 }
608562 } ;
609563 const finishDelivery = ( delivery : ReadDelivery < Format > ) => {
610- if ( delivery . caughtUpTail ) {
611- this . markCaughtUp ( delivery . caughtUpTail ) ;
612- }
564+ delivery . boundary ?. markDelivered ( ) ;
613565 delivery . onDrained ?.( ) ;
614566 } ;
615567
@@ -654,7 +606,7 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
654606 } ,
655607 cancel : async ( reason ) => {
656608 cancelled = true ;
657- this . settleTerminal ( ) ;
609+ caughtUpTracker . end ( ) ;
658610 pumpDone = true ;
659611 deliveries . clear ( ) ;
660612 resolvePendingDelivery ?.( ) ;
@@ -676,6 +628,7 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
676628 } ,
677629 { highWaterMark : 0 } ,
678630 ) ;
631+ this . caughtUpTracker = caughtUpTracker ;
679632 }
680633
681634 async [ Symbol . asyncDispose ] ( ) {
@@ -743,22 +696,11 @@ export class RetryReadSession<Format extends "string" | "bytes" = "string">
743696 }
744697
745698 isCaughtUp ( ) : boolean {
746- return this . _isCaughtUp ;
699+ return this . caughtUpTracker . isCaughtUp ( ) ;
747700 }
748701
749702 caughtUp ( ) : Promise < Types . StreamPosition > {
750- if ( this . _isCaughtUp && this . _caughtUpTail ) {
751- return Promise . resolve ( toSDKStreamPosition ( this . _caughtUpTail ) ) ;
752- }
753- if ( this . terminal ) {
754- return Promise . reject ( this . terminal . error ?? sessionClosedError ( ) ) ;
755- }
756- const promise = new Promise < Types . StreamPosition > ( ( resolve , reject ) => {
757- this . caughtUpWaiters . push ( { resolve, reject } ) ;
758- } ) ;
759- // Prevent an unhandled rejection when the promise is ignored.
760- promise . catch ( ( ) => { } ) ;
761- return promise ;
703+ return this . caughtUpTracker . caughtUp ( ) ;
762704 }
763705}
764706
0 commit comments