fix: stabilize worker console refresh

This commit is contained in:
Keisuke Hirata 2026-06-27 03:18:26 +09:00
parent c3fed59109
commit a1083908b6
No known key found for this signature in database
2 changed files with 49 additions and 16 deletions

View File

@ -57,7 +57,7 @@ Deno.test("Worker Console page is routed by runtime_id and worker_id through bac
); );
assert( assert(
consolePage.includes( consolePage.includes(
"/api/runtimes/${encodeURIComponent(runtimeId)}/workers/${encodeURIComponent(workerId)}", "/api/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(target.workerId)}",
), ),
"Worker detail should use the backend Worker detail API", "Worker detail should use the backend Worker detail API",
); );
@ -75,4 +75,17 @@ Deno.test("Worker Console page is routed by runtime_id and worker_id through bac
consolePage.includes("Streaming observation is not available"), consolePage.includes("Streaming observation is not available"),
"Console should show an explicit non-streaming degradation path", "Console should show an explicit non-streaming degradation path",
); );
assert(
consolePage.includes("function advanceReloadToken()") &&
consolePage.includes("nextReloadToken += 1") &&
!consolePage.includes("reloadToken += 1"),
"reload token advancement should not synchronously read and write the rune state",
);
assert(
consolePage.includes(
"advanceReloadToken();\n void loadConsoleData(target);",
) &&
!consolePage.includes("void refreshConsole();\n });\n\n $effect"),
"target-change effect should load data without depending on manual refresh state reads",
);
}); });

View File

@ -39,7 +39,15 @@
let streamState = $state<'connecting' | 'open' | 'unsupported' | 'closed' | 'error'>('connecting'); let streamState = $state<'connecting' | 'open' | 'unsupported' | 'closed' | 'error'>('connecting');
let streamDiagnostics = $state<Diagnostic[]>([]); let streamDiagnostics = $state<Diagnostic[]>([]);
let observedEvents = $state<Array<{ cursor: string; event: ClientWorkerEventWsFrame & { kind: 'event' } }>>([]); let observedEvents = $state<Array<{ cursor: string; event: ClientWorkerEventWsFrame & { kind: 'event' } }>>([]);
let reloadToken = 0; let nextReloadToken = 0;
let reloadToken = $state(0);
type ConsoleTarget = {
runtimeId: string;
workerId: string;
};
const consoleTarget = $derived({ runtimeId, workerId });
const projection = $derived( const projection = $derived(
projectConsole( projectConsole(
@ -101,11 +109,11 @@
} }
} }
async function loadWorker() { async function loadWorker(target: ConsoleTarget) {
workerError = null; workerError = null;
try { try {
worker = await getJson<Worker>( worker = await getJson<Worker>(
`/api/runtimes/${encodeURIComponent(runtimeId)}/workers/${encodeURIComponent(workerId)}` `/api/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(target.workerId)}`
); );
} catch (error) { } catch (error) {
workerError = error instanceof Error ? error.message : String(error); workerError = error instanceof Error ? error.message : String(error);
@ -113,11 +121,11 @@
} }
} }
async function loadTranscript() { async function loadTranscript(target: ConsoleTarget) {
transcriptError = null; transcriptError = null;
try { try {
transcript = await getJson<WorkerTranscriptProjection>( transcript = await getJson<WorkerTranscriptProjection>(
`/api/runtimes/${encodeURIComponent(runtimeId)}/workers/${encodeURIComponent(workerId)}/transcript?limit=200` `/api/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(target.workerId)}/transcript?limit=200`
); );
} catch (error) { } catch (error) {
transcriptError = error instanceof Error ? error.message : String(error); transcriptError = error instanceof Error ? error.message : String(error);
@ -125,9 +133,19 @@
} }
} }
async function loadConsoleData(target: ConsoleTarget) {
await Promise.all([loadWorker(target), loadTranscript(target)]);
}
function advanceReloadToken(): number {
nextReloadToken += 1;
reloadToken = nextReloadToken;
return nextReloadToken;
}
async function refreshConsole() { async function refreshConsole() {
reloadToken += 1; advanceReloadToken();
await Promise.all([loadWorker(), loadTranscript()]); await loadConsoleData(consoleTarget);
} }
async function sendMessage(event: SubmitEvent) { async function sendMessage(event: SubmitEvent) {
@ -149,7 +167,7 @@
} else { } else {
sendError = diagnosticsToText(result.diagnostics) || `Input was ${result.state}.`; sendError = diagnosticsToText(result.diagnostics) || `Input was ${result.state}.`;
} }
await loadTranscript(); await loadTranscript(consoleTarget);
} catch (error) { } catch (error) {
sendError = error instanceof Error ? error.message : String(error); sendError = error instanceof Error ? error.message : String(error);
} finally { } finally {
@ -157,12 +175,12 @@
} }
} }
function connectObservation(target: Worker | null, token: number) { function connectObservation(targetWorker: Worker | null, token: number, target: ConsoleTarget) {
if (!target) { if (!targetWorker) {
streamState = 'closed'; streamState = 'closed';
return; return;
} }
if (!target.capabilities.can_stream_events) { if (!targetWorker.capabilities.can_stream_events) {
streamState = 'unsupported'; streamState = 'unsupported';
streamDiagnostics = [ streamDiagnostics = [
{ {
@ -177,8 +195,8 @@
streamState = 'connecting'; streamState = 'connecting';
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket( const ws = new WebSocket(
`${protocol}//${window.location.host}/api/runtimes/${encodeURIComponent(runtimeId)}/workers/${encodeURIComponent( `${protocol}//${window.location.host}/api/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(
workerId target.workerId
)}/events/ws` )}/events/ws`
); );
@ -261,12 +279,14 @@
}); });
$effect(() => { $effect(() => {
const target = consoleTarget;
observedEvents = []; observedEvents = [];
streamDiagnostics = []; streamDiagnostics = [];
void refreshConsole(); advanceReloadToken();
void loadConsoleData(target);
}); });
$effect(() => connectObservation(worker, reloadToken)); $effect(() => connectObservation(worker, reloadToken, consoleTarget));
</script> </script>
<svelte:head> <svelte:head>