* Poll the filesystem until the restored overlay actually serves reads, or * time out. Uses `fetch` (not the exec WebSocket) so each attempt is reliably * bounded by its abort signal — during a restart the socket can stall in * CONNECTING without opening or closing. * * Probes a write
(
name: string,
timeoutMs: number,
probePath: string,
signal?: AbortSignal,
)
| 436 | * is removed and the Sprite is declared ready. |
| 437 | */ |
| 438 | private async waitUntilReady( |
| 439 | name: string, |
| 440 | timeoutMs: number, |
| 441 | probePath: string, |
| 442 | signal?: AbortSignal, |
| 443 | ): Promise<void> { |
| 444 | const deadline = Date.now() + timeoutMs |
| 445 | const sentinel = `${probePath.replace(/\/$/, '')}/.tanstack-restore-probe` |
| 446 | const marker = `ready-${Date.now()}` |
| 447 | let lastError: unknown |
| 448 | let consecutive = 0 |
| 449 | while (Date.now() < deadline) { |
| 450 | signal?.throwIfAborted() |
| 451 | try { |
| 452 | await this.probeReadWrite(name, sentinel, marker, signal) |
| 453 | consecutive += 1 |
| 454 | if (consecutive >= 2) { |
| 455 | // Best-effort cleanup; ignore failures. |
| 456 | await this.deleteSentinel(name, sentinel).catch(() => undefined) |
| 457 | return |
| 458 | } |
| 459 | } catch (error) { |
| 460 | if (error instanceof Error && error.name === 'AbortError') throw error |
| 461 | consecutive = 0 |
| 462 | lastError = error |
| 463 | } |
| 464 | await delay(2000, signal) |
| 465 | } |
| 466 | throw new Error( |
| 467 | `Sprites: "${name}" did not become ready within ${timeoutMs}ms after restore${ |
| 468 | lastError instanceof Error ? ` (last error: ${lastError.message})` : '' |
| 469 | }.`, |
| 470 | ) |
| 471 | } |
| 472 | |
| 473 | /** Write a sentinel and read it back; throws unless the round-trip matches. */ |
| 474 | private async probeReadWrite( |
no test coverage detected