import type { ActionAttempt } from '@seamapi/types/connect' import type { SeamHttpActionAttempts } from './routes/index.js' export interface ResolveActionAttemptOptions { timeout?: number pollingInterval?: number } export const resolveActionAttempt = async ( actionAttempt: T, actionAttempts: SeamHttpActionAttempts, { timeout = 10_000, pollingInterval = 1_000 }: ResolveActionAttemptOptions, ): Promise> => { let timeoutRef const timeoutPromise = new Promise>( (_resolve, reject) => { timeoutRef = globalThis.setTimeout(() => { reject(new SeamActionAttemptTimeoutError(actionAttempt, timeout)) }, timeout) }, ) try { return await Promise.race([ pollActionAttempt(actionAttempt, actionAttempts, { pollingInterval }), timeoutPromise, ]) } finally { if (timeoutRef != null) globalThis.clearTimeout(timeoutRef) } } const pollActionAttempt = async ( actionAttempt: T, actionAttempts: SeamHttpActionAttempts, options: Pick, ): Promise> => { if (isSuccessfulActionAttempt(actionAttempt)) { return actionAttempt } if (isFailedActionAttempt(actionAttempt)) { throw new SeamActionAttemptFailedError(actionAttempt) } await new Promise((resolve) => setTimeout(resolve, options.pollingInterval)) const nextActionAttempt = await actionAttempts.get({ action_attempt_id: actionAttempt.action_attempt_id, }) return await pollActionAttempt( nextActionAttempt as unknown as T, actionAttempts, options, ) } export const isSeamActionAttemptError = ( error: unknown, ): error is SeamActionAttemptError => { return error instanceof SeamActionAttemptError } export class SeamActionAttemptError extends Error { actionAttempt: T constructor(message: string, actionAttempt: T) { super(message) this.name = this.constructor.name this.actionAttempt = actionAttempt } } export const isSeamActionAttemptFailedError = ( error: unknown, ): error is SeamActionAttemptFailedError => { return error instanceof SeamActionAttemptFailedError } export class SeamActionAttemptFailedError< T extends ActionAttempt, > extends SeamActionAttemptError { code: string constructor(actionAttempt: FailedActionAttempt) { super(actionAttempt.error.message, actionAttempt) this.name = this.constructor.name this.code = actionAttempt.error.type } } export const isSeamActionAttemptTimeoutError = ( error: unknown, ): error is SeamActionAttemptTimeoutError => { return error instanceof SeamActionAttemptTimeoutError } export class SeamActionAttemptTimeoutError< T extends ActionAttempt, > extends SeamActionAttemptError { constructor(actionAttempt: T, timeout: number) { super( `Timed out waiting for action action attempt after ${timeout}ms`, actionAttempt, ) this.name = this.constructor.name } } const isSuccessfulActionAttempt = ( actionAttempt: T, ): actionAttempt is SucceededActionAttempt => actionAttempt.status === 'success' const isFailedActionAttempt = ( actionAttempt: T, ): actionAttempt is FailedActionAttempt => actionAttempt.status === 'error' export type SucceededActionAttempt = Extract< T, { status: 'success' } > export type FailedActionAttempt = Extract< T, { status: 'error' } >