Skip to content
← Public packages

@cameronpak/box

Full Box Public API v1 client: lifecycle, prompts, files, commands, snapshots, environments, webhooks, desktop, hosting, and account.

src/wait-for-box.ts

63 lines · 2.1 KB · TypeScript
import { assertBoxId, failure, success, type Result } from './client.ts'
import getBox from './get-box.ts'
import type { Box, BoxState } from './types.ts'

const DEFAULT_TARGETS: Array<BoxState> = ['ready', 'idle']
const TERMINAL: Array<BoxState> = ['archived', 'error']

export type WaitForBoxInput = {
	boxId: string
	/** States that end the wait. Defaults to ready and idle. */
	targetStates?: Array<BoxState>
	/** Seconds to keep polling. Defaults to 60. Kody execute stops near 90 seconds. */
	timeoutSeconds?: number
	/** Milliseconds between polls. Defaults to 3000. */
	pollIntervalMs?: number
}

export type WaitForBoxOutput = {
	box: Box
	/** True when the box reached one of the target states. */
	reached: boolean
	waitedMs: number
}

/**
 * Poll a box until it reaches a target state, goes terminal, or the wait times out.
 * A timeout is not an error: reached is false and the last box snapshot comes back.
 */
export default async function waitForBox(
	input: WaitForBoxInput,
): Promise<Result<WaitForBoxOutput>> {
	const invalid = assertBoxId(input?.boxId)
	if (invalid) return failure<WaitForBoxOutput>(invalid)

	const targets = input.targetStates ?? DEFAULT_TARGETS
	const timeoutMs = (input.timeoutSeconds ?? 60) * 1000
	const intervalMs = input.pollIntervalMs ?? 3000
	const startedAt = Date.now()

	let last: Box | null = null
	while (Date.now() - startedAt < timeoutMs) {
		const read = await getBox({ boxId: input.boxId })
		if (!read.ok) return failure<WaitForBoxOutput>(read.error)

		last = read.data.box
		if (targets.includes(last.state)) {
			return success({ box: last, reached: true, waitedMs: Date.now() - startedAt })
		}
		if (TERMINAL.includes(last.state) && !targets.includes(last.state)) {
			return success({ box: last, reached: false, waitedMs: Date.now() - startedAt })
		}
		await new Promise((resolve) => setTimeout(resolve, intervalMs))
	}

	if (last) {
		return success({ box: last, reached: false, waitedMs: Date.now() - startedAt })
	}
	return failure<WaitForBoxOutput>({
		code: 'wait_timeout',
		message: 'The box was never read before the wait timed out.',
		status: 0,
	})
}