diff --git a/bench/browser/opfs.spec.ts b/bench/browser/opfs.spec.ts index 285fbe3..ce803f7 100644 --- a/bench/browser/opfs.spec.ts +++ b/bench/browser/opfs.spec.ts @@ -31,7 +31,7 @@ function report( test("reports raw native OPFS, direct adapter, and facade overhead", async ({ browserName, page }, testInfo) => { await ready(page); - const result = await page.evaluate(async () => await window.opfsTest.benchmark(25, 64 * 1024)); + const result = await page.evaluate(async () => await globalThis.opfsTest.benchmark(25, 64 * 1024)); test.skip(result === null, "OPFS is unavailable in this browser context."); expect(result!.rawMs).toBeGreaterThan(0); expect(result!.adapterMs).toBeGreaterThan(0); @@ -48,7 +48,7 @@ for (const backend of ["localstorage", "indexeddb", "cache"] as const) { test(`reports raw ${backend}, direct adapter, and facade overhead`, async ({ browserName, page }, testInfo) => { await ready(page); const result = await page.evaluate( - async ({ backend }) => await window.opfsTest.benchmarkAdapter(backend, 20, 16 * 1024), + async ({ backend }) => await globalThis.opfsTest.benchmarkAdapter(backend, 20, 16 * 1024), { backend }, ); test.skip(result === null, `${backend} is unavailable in this browser context.`); diff --git a/src/adapter/object.ts b/src/adapter/object.ts index 2d46e7b..499054d 100644 --- a/src/adapter/object.ts +++ b/src/adapter/object.ts @@ -14,13 +14,7 @@ import type { } from "../driver/file.ts"; import type { - ObjectCopyOptionsType, ObjectDriverType, - ObjectEntryType, - ObjectGetOptionsType, - ObjectListOptionsType, - ObjectListType, - ObjectPutOptionsType, ObjectStatType, } from "../driver/object.ts"; diff --git a/src/adapter/record.ts b/src/adapter/record.ts index 0adcca8..acc428f 100644 --- a/src/adapter/record.ts +++ b/src/adapter/record.ts @@ -14,14 +14,10 @@ import { basename, dirname, type PathType, ROOT_PATH } from "../path.ts"; import { type AdapterLimitsType, type AdapterPartitionType, - type DirectoryRecordType, - type FileRecordType, RecordSchema, - type RecordType, - type WriteModeType, } from "../schema.ts"; -import type { RecordDriverType, RecordListType } from "../driver/record.ts"; +import type { RecordDriverType } from "../driver/record.ts"; /** Options for a filesystem adapter created from a record driver. */ export interface RecordAdapterOptionsType { diff --git a/src/driver/object.ts b/src/driver/object.ts index c2b11d2..4dca7c9 100644 --- a/src/driver/object.ts +++ b/src/driver/object.ts @@ -234,8 +234,7 @@ export function defineObjectDriver( versions: backend.capabilities.versions ?? false, }); - let base!: DriverType; - base = defineDriver({ + const base = defineDriver({ ...options, name: options.name || backend.name, kind: "object", diff --git a/src/driver/record.ts b/src/driver/record.ts index 63795e8..bbff60f 100644 --- a/src/driver/record.ts +++ b/src/driver/record.ts @@ -204,8 +204,7 @@ export function defineRecordDriver( write: !(options.readOnly ?? false), }); - let base!: DriverType; - base = defineDriver({ + const base = defineDriver({ ...options, kind: "record", provides: options.provides ?? [ diff --git a/src/filesystem.ts b/src/filesystem.ts index f88acda..51fb2f8 100644 --- a/src/filesystem.ts +++ b/src/filesystem.ts @@ -353,8 +353,7 @@ async function settleConcurrent(active: Set>, failures: unknown[], /** Tracks one bounded child mutation and records its first failure without an unhandled rejection. */ function trackConcurrent(active: Set>, failures: unknown[], operation: Promise): void { - let tracked!: Promise; - tracked = operation.catch((error) => { + const tracked = operation.catch((error) => { failures.push(error); throw error; }).finally(() => active.delete(tracked)); diff --git a/src/s3.ts b/src/s3.ts index 6fa9753..6b345b0 100644 --- a/src/s3.ts +++ b/src/s3.ts @@ -933,6 +933,19 @@ class S3Client implements S3ClientType { const partSize = this.#getPartSize(options.size); let chunks = getChunks(body, partSize); + // Preserve the already-consumed chunks before replacing the iterator. + // A single chunk larger than PutObject's hard limit still enters multipart + // instead of failing only because delayed multipart is enabled. + async function* retained( + _first: IteratorResult, + _second: IteratorResult, + _chunks: AsyncGenerator + ): AsyncGenerator { + yield _first.value; + if (!_second.done) yield _second.value; + for await (const chunk of _chunks) yield chunk; + } + if (this.optimizations.delayedMultipart) { const first = await chunks.next(); if (first.done) return await this.#putBytes(key, new Uint8Array(), options); @@ -947,17 +960,7 @@ class S3Client implements S3ClientType { return await this.#putBytes(key, first.value.bytes, options); } - // Preserve the already-consumed chunks before replacing the iterator. - // A single chunk larger than PutObject's hard limit still enters multipart - // instead of failing only because delayed multipart is enabled. - const rest = chunks; - async function* retained(): AsyncGenerator { - yield first.value; - if (!second.done) yield second.value; - for await (const chunk of rest) yield chunk; - } - - chunks = retained(); + chunks = retained(first, second, chunks); } const upload = await this.createUpload(key, options); diff --git a/tests/browser/adapters.spec.ts b/tests/browser/adapters.spec.ts index 28deff3..0969a59 100644 --- a/tests/browser/adapters.spec.ts +++ b/tests/browser/adapters.spec.ts @@ -6,18 +6,18 @@ const APP_URL = "http://127.0.0.1:4173/tests/browser/fixtures/index.html"; /** Opens the fixture page and waits until its module API is ready for Playwright calls. */ async function ready(page: import("@playwright/test").Page): Promise { await page.goto(APP_URL); - await page.waitForFunction(() => Boolean((window as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); + await page.waitForFunction(() => Boolean((globalThis as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); } for (const kind of ["localstorage", "indexeddb", "cache"] as const) { test(`${kind} adapter executes against the real browser backend`, async ({ page }) => { await ready(page); - expect(await page.evaluate(async ({ kind }) => await window.opfsTest.adapter(kind), { kind })).toBe(kind); + expect(await page.evaluate(async ({ kind }) => await globalThis.opfsTest.adapter(kind), { kind })).toBe(kind); }); } test("IndexedDB append preserves both independent writers", async ({ page }) => { await ready(page); - const value = await page.evaluate(async () => await window.opfsTest.indexedDbAppend()); + const value = await page.evaluate(async () => await globalThis.opfsTest.indexedDbAppend()); expect(["baseAB", "baseBA"]).toContain(value); }); diff --git a/tests/browser/fixtures/app.ts b/tests/browser/fixtures/app.ts index 8dbe0ef..43ce05c 100644 --- a/tests/browser/fixtures/app.ts +++ b/tests/browser/fixtures/app.ts @@ -566,7 +566,7 @@ async function roundTripAdapter(kind: BrowserAdapterType): Promise { } } -window.opfsTest = { +globalThis.opfsTest = { ready: true, probe: probeOpfs, roundTrip: roundTripOpfs, diff --git a/tests/browser/iframe.spec.ts b/tests/browser/iframe.spec.ts index 70dd45a..dd02c13 100644 --- a/tests/browser/iframe.spec.ts +++ b/tests/browser/iframe.spec.ts @@ -7,7 +7,7 @@ const CROSS_URL = "http://127.0.0.1:4174/tests/browser/fixtures/frame.html"; /** Waits until an iframe has installed the shared browser test API. */ async function waitForApi(frame: import("@playwright/test").Frame): Promise { - await frame.waitForFunction(() => Boolean((window as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); + await frame.waitForFunction(() => Boolean((globalThis as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); } test("same-origin iframe observes its real OPFS placement", async ({ page }) => { @@ -23,7 +23,7 @@ test("same-origin iframe observes its real OPFS placement", async ({ page }) => const frame = await framePromise; await waitForApi(frame); const result = await frame.evaluate(async () => - await window.opfsTest.roundTrip(`/frames/${crypto.randomUUID()}.txt`, "same") + await globalThis.opfsTest.roundTrip(`/frames/${crypto.randomUUID()}.txt`, "same") ); expect(result.probe?.embedded).toBe(true); expect(result.probe?.sameOriginTop).toBe(true); @@ -43,7 +43,7 @@ test("cross-origin iframe reports partition/policy behavior instead of guessing }, CROSS_URL); const frame = await framePromise; await waitForApi(frame); - const probe = await frame.evaluate(async () => await window.opfsTest.probe()); + const probe = await frame.evaluate(async () => await globalThis.opfsTest.probe()); expect(probe.embedded).toBe(true); expect(probe.sameOriginTop).toBe(false); if (!probe.rootAvailable) expect(probe.rootError).toBeDefined(); @@ -54,7 +54,7 @@ test("opaque sandbox reports the platform result without browser-name assumption await page.evaluate(() => { const frame = document.createElement("iframe"); frame.sandbox.add("allow-scripts"); - frame.srcdoc = ""; + frame.srcdoc = ""; document.body.append(frame); }); const frame = page.frames().find((candidate) => candidate !== page.mainFrame())!; diff --git a/tests/browser/opfs.spec.ts b/tests/browser/opfs.spec.ts index a1a1494..548d599 100644 --- a/tests/browser/opfs.spec.ts +++ b/tests/browser/opfs.spec.ts @@ -6,13 +6,13 @@ const APP_URL = "http://127.0.0.1:4173/tests/browser/fixtures/index.html"; /** Opens the fixture page and waits for its OPFS test API. */ async function ready(page: import("@playwright/test").Page): Promise { await page.goto(APP_URL); - await page.waitForFunction(() => Boolean((window as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); + await page.waitForFunction(() => Boolean((globalThis as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); } test("window probes the actual capability and round-trips when OPFS is available", async ({ page }) => { await ready(page); const result = await page.evaluate(async () => - await window.opfsTest.roundTrip(`/window/${crypto.randomUUID()}.txt`, "window") + await globalThis.opfsTest.roundTrip(`/window/${crypto.randomUUID()}.txt`, "window") ); expect(result.supported).toBe(true); expect(result.probe?.context).toBe("window"); @@ -22,14 +22,14 @@ test("window probes the actual capability and round-trips when OPFS is available test("an aborted write cannot commit", async ({ page }) => { await ready(page); - const result = await page.evaluate(async () => await window.opfsTest.abort(`/abort/${crypto.randomUUID()}.txt`)); + const result = await page.evaluate(async () => await globalThis.opfsTest.abort(`/abort/${crypto.randomUUID()}.txt`)); test.skip(!result.supported, "OPFS is unavailable in this browser context."); expect(result).toEqual({ supported: true, name: "FileSystemError", code: "aborted" }); }); test("queued Web Locks cancellation is normalized to the package error", async ({ page }) => { await ready(page); - const result = await page.evaluate(async () => await window.opfsTest.queuedAbort()); + const result = await page.evaluate(async () => await globalThis.opfsTest.queuedAbort()); test.skip(!result.supported, "This browser does not expose the Web Locks API."); expect(result).toEqual({ supported: true, name: "FileSystemError", code: "aborted" }); }); @@ -39,7 +39,7 @@ test("fresh browser contexts do not inherit another context's OPFS file", async const first = await browser.newContext(); const firstPage = await first.newPage(); await ready(firstPage); - const written = await firstPage.evaluate(async ({ path }) => await window.opfsTest.roundTrip(path, "private"), { + const written = await firstPage.evaluate(async ({ path }) => await globalThis.opfsTest.roundTrip(path, "private"), { path, }); await first.close(); @@ -51,7 +51,7 @@ test("fresh browser contexts do not inherit another context's OPFS file", async const second = await browser.newContext(); const secondPage = await second.newPage(); await ready(secondPage); - expect(await secondPage.evaluate(async ({ path }) => await window.opfsTest.read(path), { path })).toBeNull(); + expect(await secondPage.evaluate(async ({ path }) => await globalThis.opfsTest.read(path), { path })).toBeNull(); await second.close(); }); @@ -63,7 +63,7 @@ test("a persistent profile reopens the same OPFS data", async ({ browserName }, const first = await browserType.launchPersistentContext(profile); const firstPage = await first.newPage(); await ready(firstPage); - const written = await firstPage.evaluate(async ({ path }) => await window.opfsTest.roundTrip(path, "persisted"), { + const written = await firstPage.evaluate(async ({ path }) => await globalThis.opfsTest.roundTrip(path, "persisted"), { path, }); await first.close(); @@ -75,6 +75,6 @@ test("a persistent profile reopens the same OPFS data", async ({ browserName }, const second = await browserType.launchPersistentContext(profile); const secondPage = await second.newPage(); await ready(secondPage); - expect(await secondPage.evaluate(async ({ path }) => await window.opfsTest.read(path), { path })).toBe("persisted"); + expect(await secondPage.evaluate(async ({ path }) => await globalThis.opfsTest.read(path), { path })).toBe("persisted"); await second.close(); }); diff --git a/tests/browser/service-worker.spec.ts b/tests/browser/service-worker.spec.ts index 553a58f..82098e5 100644 --- a/tests/browser/service-worker.spec.ts +++ b/tests/browser/service-worker.spec.ts @@ -6,13 +6,13 @@ const APP_URL = "http://127.0.0.1:4173/tests/browser/fixtures/index.html"; /** Opens the fixture page and waits for its OPFS test API. */ async function ready(page: import("@playwright/test").Page): Promise { await page.goto(APP_URL); - await page.waitForFunction(() => Boolean((window as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); + await page.waitForFunction(() => Boolean((globalThis as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); } test("ServiceWorker behavior is verified through page messaging in every browser", async ({ page }) => { await ready(page); const result = await page.evaluate(async () => - await window.opfsTest.service(`/service/${crypto.randomUUID()}.txt`, "service") + await globalThis.opfsTest.service(`/service/${crypto.randomUUID()}.txt`, "service") ); test.skip(!result.supported, "ServiceWorker is not exposed in this browser context."); expect(result.probe?.context).toBe("service-worker"); @@ -24,7 +24,7 @@ test("Chromium exposes the registered service worker to Playwright instrumentati test.skip(browserName !== "chromium", "Playwright serviceWorkers() inspection is Chromium-only."); await ready(page); const result = await page.evaluate(async () => - await window.opfsTest.service(`/service/${crypto.randomUUID()}.txt`, "instrumented") + await globalThis.opfsTest.service(`/service/${crypto.randomUUID()}.txt`, "instrumented") ); test.skip(!result.supported, "ServiceWorker is not exposed in this Chromium context."); expect(context.serviceWorkers().length).toBeGreaterThan(0); diff --git a/tests/browser/worker.spec.ts b/tests/browser/worker.spec.ts index 8113d1d..67f8934 100644 --- a/tests/browser/worker.spec.ts +++ b/tests/browser/worker.spec.ts @@ -6,13 +6,13 @@ const APP_URL = "http://127.0.0.1:4173/tests/browser/fixtures/index.html"; /** Opens the fixture page and waits for its OPFS test API. */ async function ready(page: import("@playwright/test").Page): Promise { await page.goto(APP_URL); - await page.waitForFunction(() => Boolean((window as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); + await page.waitForFunction(() => Boolean((globalThis as unknown as { opfsTest?: { ready?: boolean } }).opfsTest?.ready)); } test("DedicatedWorker uses real OPFS and probes synchronous access", async ({ page }) => { await ready(page); const result = await page.evaluate(async () => - await window.opfsTest.dedicated(`/dedicated/${crypto.randomUUID()}.txt`, "dedicated") + await globalThis.opfsTest.dedicated(`/dedicated/${crypto.randomUUID()}.txt`, "dedicated") ); test.skip(!result.supported, "DedicatedWorker is not exposed in this browser context."); expect(result.probe?.context).toBe("dedicated-worker"); @@ -27,7 +27,7 @@ test("DedicatedWorker uses real OPFS and probes synchronous access", async ({ pa test("SharedWorker uses the browser's actual storage capability", async ({ page }) => { await ready(page); const result = await page.evaluate(async () => - await window.opfsTest.shared(`/shared/${crypto.randomUUID()}.txt`, "shared") + await globalThis.opfsTest.shared(`/shared/${crypto.randomUUID()}.txt`, "shared") ); test.skip(!result.supported, "SharedWorker is not exposed in this browser context."); expect(["shared-worker", "worker"]).toContain(result.probe?.context); diff --git a/tests/ecosystems.test.ts b/tests/ecosystems.test.ts index d50401c..2c114fe 100644 --- a/tests/ecosystems.test.ts +++ b/tests/ecosystems.test.ts @@ -8,45 +8,11 @@ import { createDb0Adapter } from "../src/adapter/db0.ts"; import { createDrizzleAdapter } from "../src/adapter/drizzle.ts"; import { createMemoryAdapter } from "../src/adapter/memory.ts"; import { createRxDbAdapter, RxDbRecordJsonSchema } from "../src/adapter/rxdb.ts"; -import { createUnstorageAdapter } from "../src/adapter/unstorage.ts"; import { createKeyValueBridge } from "../src/bridge/kv.ts"; import { createUnstorageBridge } from "../src/bridge/unstorage.ts"; import { Db0Integration, DrizzleIntegration, RxDbIntegration, UnstorageIntegration } from "../src/integration.ts"; import { defineIntegration } from "../src/integration/definition.ts"; -/** In-memory unstorage-shaped resource used to verify driver-to-adapter semantics and disposal ownership. */ -class MemoryUnstorage { - /** Stored unstorage values keyed exactly as the adapter writes them. */ - #values = new Map(); - /** Records whether explicit database ownership was disposed. */ - disposed = false; - - /** Returns one cloned value so tests cannot pass through shared object identity. */ - async getItem(key: string): Promise { - return structuredClone(this.#values.get(key) ?? null); - } - - /** Replaces one key with a cloned value. */ - async setItem(key: string, value: unknown): Promise { - this.#values.set(key, structuredClone(value)); - } - - /** Removes one exact unstorage key. */ - async removeItem(key: string): Promise { - this.#values.delete(key); - } - - /** Lists keys under the requested unstorage prefix. */ - async getKeys(base = ""): Promise { - return [...this.#values.keys()].filter((key) => key.startsWith(base)); - } - - /** Marks the database disposed for adapter-ownership assertions. */ - async dispose(): Promise { - this.disposed = true; - } -} - /** Minimal RxDocument-shaped value that preserves JSON reads and incremental removal. */ class FakeRxDocument { /** Current stored record represented by this document. */