From a531d3447e4a461dda5e6cf2d29084c32b89cfd0 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Sat, 13 Jun 2026 19:45:51 -0700 Subject: [PATCH] fix(sdk): fs adapter flush() tracks in-flight writes; add to T13 contract suite --- packages/sdk/src/adapters/fs.ts | 19 +++++++++++++++++-- .../adapters/persistAdapter.contract.test.ts | 9 +++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/adapters/fs.ts b/packages/sdk/src/adapters/fs.ts index dd1947c0e..ccbde84c7 100644 --- a/packages/sdk/src/adapters/fs.ts +++ b/packages/sdk/src/adapters/fs.ts @@ -16,6 +16,8 @@ class FsAdapter implements PersistAdapter { private readonly root: string; private readonly maxVersions: number; private errorHandlers: Array<(e: PersistErrorEvent) => void> = []; + private readonly inflightWrites = new Set>(); + private versionCounter = 0; constructor(opts: FsAdapterOptions) { this.root = opts.root; @@ -32,6 +34,16 @@ class FsAdapter implements PersistAdapter { } async write(path: string, content: string): Promise { + const p = this.doWrite(path, content); + this.inflightWrites.add(p); + try { + await p; + } finally { + this.inflightWrites.delete(p); + } + } + + private async doWrite(path: string, content: string): Promise { try { const abs = this.abs(path); await mkdir(dirname(abs), { recursive: true }); @@ -42,7 +54,9 @@ class FsAdapter implements PersistAdapter { } } - async flush(): Promise {} + async flush(): Promise { + await Promise.all([...this.inflightWrites]); + } async listVersions(path: string): Promise { const dir = this.versionsDir(path); @@ -92,7 +106,8 @@ class FsAdapter implements PersistAdapter { private async appendVersion(path: string, content: string): Promise { const dir = this.versionsDir(path); await mkdir(dir, { recursive: true }); - const key = String(Date.now()); + // Pad counter to 6 digits so lexicographic sort = insertion order within same ms. + const key = `${Date.now()}_${String(this.versionCounter++).padStart(6, "0")}`; await writeFile(join(dir, `${key}.html`), content, "utf8"); // prune oldest beyond maxVersions const all = (await readdir(dir)).filter((f) => f.endsWith(".html")).sort(); diff --git a/packages/sdk/src/adapters/persistAdapter.contract.test.ts b/packages/sdk/src/adapters/persistAdapter.contract.test.ts index d03e68930..4d0bb06c9 100644 --- a/packages/sdk/src/adapters/persistAdapter.contract.test.ts +++ b/packages/sdk/src/adapters/persistAdapter.contract.test.ts @@ -9,8 +9,12 @@ * runPersistAdapterContract("s3", () => createS3Adapter({ bucket, prefix })) */ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { describe, it, expect, vi } from "vitest"; import { createMemoryAdapter } from "./memory.js"; +import { createFsAdapter } from "./fs.js"; import type { PersistAdapter } from "./types.js"; export function runPersistAdapterContract( @@ -126,3 +130,8 @@ export function runPersistAdapterContract( // Run the suite against the memory adapter immediately runPersistAdapterContract("memory", createMemoryAdapter); + +// Run against the fs adapter — each test gets an isolated tmpdir +runPersistAdapterContract("fs", () => + createFsAdapter({ root: mkdtempSync(join(tmpdir(), "hf-fs-test-")) }), +);