From 3a6112f64121f8d1dffa1e372a692d8990154839 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Tue, 11 Aug 2026 12:49:27 +0800 Subject: [PATCH] fix(web): satisfy contracts-ready lint and dedup the stable-file read Rescope nothing new: drop the needless async from the inherited readRaw default (reject explicitly on abort), fix the void arrow shorthand in downloadBlob, and share one revision-stable file-read loop between readRaw and readPrefix in the JSONL backend. --- .../ui-trajectory/src/client/export-log.ts | 2 +- .../session-persistence-jsonl/src/index.ts | 48 +++++++++---------- .../session/session-persistence/src/index.ts | 6 +-- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/client/ui-trajectory/src/client/export-log.ts b/packages/client/ui-trajectory/src/client/export-log.ts index ba3d1ca8ff..3a25794d4d 100644 --- a/packages/client/ui-trajectory/src/client/export-log.ts +++ b/packages/client/ui-trajectory/src/client/export-log.ts @@ -38,5 +38,5 @@ export function downloadBlob(blob: Blob, filename: string): void { anchor.download = filename anchor.click() // Revoke one tick later: some browsers read the blob URL after click(). - setTimeout(() => URL.revokeObjectURL(url), 0) + setTimeout(() => { URL.revokeObjectURL(url) }, 0) } diff --git a/packages/session/session-persistence-jsonl/src/index.ts b/packages/session/session-persistence-jsonl/src/index.ts index 88739b81ee..42a3c431ce 100644 --- a/packages/session/session-persistence-jsonl/src/index.ts +++ b/packages/session/session-persistence-jsonl/src/index.ts @@ -253,17 +253,7 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi signal?.throwIfAborted() const path = await this.findLog(id, signal) if (path === undefined) return undefined - let buffer: Buffer - // Revision-stable read: a writer appending between stat and readFile - // would yield a torn physical file (see readPrefix). - for (;;) { - signal?.throwIfAborted() - const before = fileRevision(await stat(path, { bigint: true })) - buffer = await readFile(path, { signal }) - signal?.throwIfAborted() - const after = fileRevision(await stat(path, { bigint: true })) - if (before === after) break - } + const { buffer } = await this.readStableFile(path, signal) let content: string if (this.compression === 'zstd') { const { frames } = scanZstdFrames(buffer) @@ -289,6 +279,28 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi return { meta, filename: 'session.jsonl', content } } + /** + * Read a file's bytes under a revision-stable loop: a writer appending + * between stat and readFile would yield a torn physical file, so retry + * while the stat revision changes. + * @param path - the artifact file to read. + * @param signal - optional cancellation for the stat/read work. + * @returns the stable bytes and the revision that matched both stats. + */ + private async readStableFile( + path: string, + signal?: AbortSignal, + ): Promise<{ buffer: Buffer; revision: PersistenceRevision }> { + for (;;) { + signal?.throwIfAborted() + const before = fileRevision(await stat(path, { bigint: true })) + const buffer = await readFile(path, { signal }) + signal?.throwIfAborted() + const after = fileRevision(await stat(path, { bigint: true })) + if (before === after) return { buffer, revision: after } + } + } + /** * Read a stored prefix and convert torn-tail state to the opaque marker the * coordinator can round-trip without knowing the physical encoding. @@ -298,19 +310,7 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi expectedId?: SessionId, signal?: AbortSignal, ): Promise> { - let buffer: Buffer - let revision: PersistenceRevision - for (;;) { - signal?.throwIfAborted() - const before = fileRevision(await stat(path, { bigint: true })) - buffer = await readFile(path, { signal }) - signal?.throwIfAborted() - const after = fileRevision(await stat(path, { bigint: true })) - if (before === after) { - revision = after - break - } - } + const { buffer, revision } = await this.readStableFile(path, signal) let prefix: Omit, 'revision'> try { if (this.compression === 'zstd') { diff --git a/packages/session/session-persistence/src/index.ts b/packages/session/session-persistence/src/index.ts index 80f67c92ea..e30b5adaa1 100644 --- a/packages/session/session-persistence/src/index.ts +++ b/packages/session/session-persistence/src/index.ts @@ -108,9 +108,9 @@ export abstract class SessionPersistence extends Service { * @returns the raw artifact plus its parsed header, or `undefined` when the * session is absent or the backend owns no per-session artifact. */ - async readRaw(_id: SessionId, signal?: AbortSignal): Promise { - signal?.throwIfAborted() - return undefined + readRaw(_id: SessionId, signal?: AbortSignal): Promise { + if (signal?.aborted === true) return Promise.reject(signal.reason) + return Promise.resolve(undefined) } /**