@@ -80,7 +80,10 @@ export interface IdempotencyExecutionOptions {
8080 */
8181export type IdempotentExecution < T > = { outcome : 'resolved' ; result : T } | { outcome : 'in-progress' }
8282
83- type InProgressPolicy = 'wait' | 'skip'
83+ /** An in-progress outcome carries the wait on the live holder, so each caller decides whether to take it. */
84+ type ClaimedExecution < T > =
85+ | { outcome : 'resolved' ; result : T }
86+ | { outcome : 'in-progress' ; wait : ( ) => Promise < T > }
8487
8588export interface AtomicClaimResult {
8689 claimed : boolean
@@ -578,18 +581,17 @@ export class IdempotencyService {
578581 provider : string ,
579582 identifier : string ,
580583 operation : ( ) => Promise < T > ,
581- additionalContext ?: Record < string , any > ,
584+ additionalContext ?: Record < string , unknown > ,
582585 options ?: IdempotencyExecutionOptions
583586 ) : Promise < T > {
584587 const execution = await this . execute (
585588 provider ,
586589 identifier ,
587590 operation ,
588591 additionalContext ,
589- options ,
590- 'wait'
592+ options
591593 )
592- return execution . result
594+ return execution . outcome === 'resolved' ? execution . result : execution . wait ( )
593595 }
594596
595597 /**
@@ -605,33 +607,25 @@ export class IdempotencyService {
605607 additionalContext ?: Record < string , unknown > ,
606608 options ?: IdempotencyExecutionOptions
607609 ) : Promise < IdempotentExecution < T > > {
608- return this . execute ( provider , identifier , operation , additionalContext , options , 'skip' )
610+ const execution = await this . execute (
611+ provider ,
612+ identifier ,
613+ operation ,
614+ additionalContext ,
615+ options
616+ )
617+ if ( execution . outcome === 'resolved' ) return execution
618+ logger . info ( `Skipping duplicate of in-progress operation: ${ provider } :${ identifier } ` )
619+ return { outcome : 'in-progress' }
609620 }
610621
611- private execute < T > (
612- provider : string ,
613- identifier : string ,
614- operation : ( ) => Promise < T > ,
615- additionalContext : Record < string , unknown > | undefined ,
616- options : IdempotencyExecutionOptions | undefined ,
617- inProgressPolicy : 'wait'
618- ) : Promise < { outcome : 'resolved' ; result : T } >
619- private execute < T > (
620- provider : string ,
621- identifier : string ,
622- operation : ( ) => Promise < T > ,
623- additionalContext : Record < string , unknown > | undefined ,
624- options : IdempotencyExecutionOptions | undefined ,
625- inProgressPolicy : InProgressPolicy
626- ) : Promise < IdempotentExecution < T > >
627622 private async execute < T > (
628623 provider : string ,
629624 identifier : string ,
630625 operation : ( ) => Promise < T > ,
631626 additionalContext : Record < string , unknown > | undefined ,
632- options : IdempotencyExecutionOptions | undefined ,
633- inProgressPolicy : InProgressPolicy
634- ) : Promise < IdempotentExecution < T > > {
627+ options : IdempotencyExecutionOptions | undefined
628+ ) : Promise < ClaimedExecution < T > > {
635629 const claimResult = await this . atomicallyClaim ( provider , identifier , additionalContext , options )
636630
637631 if ( ! claimResult . claimed ) {
@@ -651,32 +645,24 @@ export class IdempotencyService {
651645 observedResult : existingResult ,
652646 observedValue : claimResult . observedValue ,
653647 } )
654- return this . execute (
655- provider ,
656- identifier ,
657- operation ,
658- additionalContext ,
659- options ,
660- inProgressPolicy
661- )
648+ return this . execute ( provider , identifier , operation , additionalContext , options )
662649 }
663650 logger . info ( `Previous operation failed for: ${ claimResult . normalizedKey } ` )
664651 throw new Error ( existingResult . error || 'Previous operation failed' )
665652 }
666653
667654 if ( existingResult ?. status === 'in-progress' ) {
668- if ( inProgressPolicy === 'skip' ) {
669- logger . info ( `Skipping duplicate of in-progress operation: ${ claimResult . normalizedKey } ` )
670- return { outcome : 'in-progress' }
671- }
672- logger . info ( `Waiting for in-progress operation: ${ claimResult . normalizedKey } ` )
673- const result = await this . waitForResult < T > (
674- claimResult . normalizedKey ,
675- claimResult . storageMethod ,
655+ const { normalizedKey, storageMethod } = claimResult
656+ const deadline =
676657 existingResult . inProgressExpiresAt ??
677- ( existingResult . startedAt ?? Date . now ( ) ) + this . config . inProgressTtlSeconds * 1000
678- )
679- return { outcome : 'resolved' , result }
658+ ( existingResult . startedAt ?? Date . now ( ) ) + this . config . inProgressTtlSeconds * 1000
659+ return {
660+ outcome : 'in-progress' ,
661+ wait : ( ) => {
662+ logger . info ( `Waiting for in-progress operation: ${ normalizedKey } ` )
663+ return this . waitForResult < T > ( normalizedKey , storageMethod , deadline )
664+ } ,
665+ }
680666 }
681667
682668 if ( existingResult ) {
0 commit comments