import type { IndexedDbRuntimeDependencies, } from "../../src/adapters/storage/indexeddb/indexeddb-types.ts"; import type { LocalDraft } from "../../src/features/local-draft-feature/domain/local-draft.ts"; import { expect, test, } from "../support/browser/strict-browser-test.ts"; test("composes the local-draft feature over the real browser IndexedDB runtime", async ({ page, }) => { await page.route("**/favicon.ico", (route) => route.fulfill({ status: 204 }), ); await page.goto("/config.json"); const result = await page.evaluate(async () => { const indexedDbModulePath = "/src/adapters/storage/indexeddb/index.ts"; const contributionModulePath = "/src/features/feature-adapter-contribution.ts"; const localDraftModulePath = "/src/features/local-draft-feature/adapters/create-local-draft-feature-input.ts"; const { createIndexedDbRuntime, indexedDbPhysicalDatabaseName, } = (await import( /* @vite-ignore */ indexedDbModulePath )) as typeof import("../../src/adapters/storage/indexeddb/index.ts"); const { composeFeatureAdapterInputs, createIndexedDbRepositoryProvider, } = (await import( /* @vite-ignore */ contributionModulePath )) as typeof import("../../src/features/feature-adapter-contribution.ts"); const { LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION, } = (await import( /* @vite-ignore */ localDraftModulePath )) as typeof import("../../src/features/local-draft-feature/adapters/create-local-draft-feature-input.ts"); const suffix = crypto.randomUUID().replaceAll("-", ""); const scope = Object.freeze({ authorityToken: `a${suffix.slice(0, 31)}`, namespaceToken: `n${suffix.slice(1, 32)}`, partitionToken: `p${[...suffix].reverse().join("").slice(0, 31)}`, accountScope: "ORIGIN_SHARED" as const, }); const storagePolicy = Object.freeze({ owner: "local-draft-feature", namespace: "local-draft", classification: "INTERNAL" as const, authority: "LOCAL_FIRST" as const, accountScope: "ORIGIN_SHARED" as const, retention: Object.freeze({ kind: "EXPLICIT_DELETE" as const }), softBudgetBytes: 1_000_000, hardBudgetBytes: 2_000_000, evictionPriority: "USER_AUTHORED" as const, logoutAction: "KEEP_ORIGIN_SHARED" as const, accountDeletionAction: "KEEP_ORIGIN_SHARED" as const, pressureAction: "RETAIN" as const, unavailableFallback: "READ_ONLY" as const, }); const config: IndexedDbRuntimeDependencies< LocalDraft, LocalDraft, never > = { scope, storagePolicy, schemaVersion: 1, recordStore: "records", governanceStore: "governance", retentionStore: "retention", retentionEligibilityIndex: "by-eligibility", lifecycleMetadataStores: [], idempotencyStore: "receipts", idempotencyExpiryIndex: "by-expiry", receiptRetentionMs: 60_000, maxIdempotencyReceipts: 100, migrations: [ { id: "local-draft-schema-v1", fromVersion: 0, toVersion: 1, operations: [ { kind: "CREATE_STORE", name: "governance", keyPath: "bindingKey", }, { kind: "CREATE_STORE", name: "retention", keyPath: "recordKey", indexes: [ { name: "by-eligibility", keyPath: "eligibleAtEpochMs", }, ], }, { kind: "CREATE_STORE", name: "records", keyPath: "key", }, { kind: "CREATE_STORE", name: "receipts", keyPath: "idempotencyKey", indexes: [ { name: "by-expiry", keyPath: "expiresAtEpochMs", }, ], }, ], }, ], codec: { currentVersion: 1, encode: (value) => ({ ok: true, value }), measureStoredBytes: (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength, decode: (version, value) => { if ( version !== 1 || value === null || typeof value !== "object" ) { return { ok: false }; } const candidate = value as Partial; return typeof candidate.draftId === "string" && typeof candidate.title === "string" && typeof candidate.body === "string" ? { ok: true, value: candidate as LocalDraft } : { ok: false }; }, fingerprint: async (value) => { const bytes = new TextEncoder().encode( JSON.stringify([value.draftId, value.title, value.body]), ); const digest = await crypto.subtle.digest("SHA-256", bytes); return Array.from( new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0"), ).join(""); }, }, queryPolicy: { plan: () => ({ limit: 1 }), }, authorizeLifecycle: () => ({ authorized: true, proofToken: "localdraftauthorityproof_001", }), }; const runtime = createIndexedDbRuntime(config); const databaseName = indexedDbPhysicalDatabaseName(scope); const opened = await runtime.open(); try { const indexedDb = createIndexedDbRepositoryProvider( Object.freeze({ "local-draft": runtime }), ); const composed = composeFeatureAdapterInputs( Object.freeze([LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION] as const), Object.freeze(["local-draft"]), Object.freeze({ indexedDb }), ); const localDraft = composed["local-draft"]; if (!localDraft) { throw new Error("local-draft feature input was not composed"); } const draft = Object.freeze({ draftId: "draft-1", title: "Architecture notes", body: "Stored through the reusable IndexedDB capability.", }); const saved = await localDraft.saveDraft({ draft, expectedRevision: null, idempotencyKey: "local-draft-create-1", }); const found = await localDraft.findDraft(draft.draftId); const removed = await localDraft.removeDraft({ draftId: draft.draftId, expectedRevision: saved.ok ? saved.value.revision : 1, idempotencyKey: "local-draft-remove-1", }); const afterRemove = await localDraft.findDraft(draft.draftId); return { opened, saved, found, removed, afterRemove, composedFeatureIds: Object.keys(composed), runtimeStatus: runtime.getStatus(), }; } finally { runtime.close(); await new Promise((resolve, reject) => { const request = indexedDB.deleteDatabase(databaseName); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error ?? new Error("IndexedDB cleanup failed")); request.onblocked = () => reject(new Error("IndexedDB cleanup was blocked")); }); } }); expect(result.opened).toEqual({ ok: true, value: undefined }); expect(result.composedFeatureIds).toEqual(["local-draft"]); expect(result.saved).toEqual({ ok: true, value: { revision: 1 } }); expect(result.found).toMatchObject({ ok: true, value: { draft: { draftId: "draft-1", title: "Architecture notes", body: "Stored through the reusable IndexedDB capability.", }, revision: 1, }, }); expect(result.removed).toEqual({ ok: true, value: undefined }); expect(result.afterRemove).toEqual({ ok: true, value: null }); expect(result.runtimeStatus).toMatchObject({ kind: "READY", schemaVersion: 1, }); });