1import * as BrowserKeyValueStore from "@effect/platform-browser/BrowserKeyValueStore"
2import * as IndexedDb from "@effect/platform-browser/IndexedDb"
3import { assert, describe, it } from "@effect/vitest"
4import { Layer } from "effect"
5import { testLayer } from "effect-test/persistence/KeyValueStore.test"
6import * as Effect from "effect/Effect"
7import * as KeyValueStore from "effect/persistence/KeyValueStore"
8import * as Result from "effect/Result"
9import { IDBKeyRange, indexedDB } from "fake-indexeddb"
10
11describe("KeyValueStore / layerLocalStorage", () => testLayer(BrowserKeyValueStore.layerLocalStorage))
12
13describe("KeyValueStore / layerSessionStorage", () => testLayer(BrowserKeyValueStore.layerSessionStorage))
14
15describe("KeyValueStore / layerIndexedDb", () => {
16 const layerFakeIndexedDb = Layer.succeed(
17 IndexedDb.IndexedDb,
18 IndexedDb.make({ indexedDB, IDBKeyRange })
19 )
20
21 testLayer(
22 BrowserKeyValueStore.layerIndexedDb({ database: "kvs_test_db" }).pipe(
23 Layer.provide(layerFakeIndexedDb)
24 )
25 )
26
27 it.effect("reports a key containing bytes as present", () => {
28 const layer = BrowserKeyValueStore.layerIndexedDb({
29 database: `kvs_binary_has_${crypto.randomUUID()}`
30 }).pipe(Layer.provide(layerFakeIndexedDb))
31
32 return Effect.gen(function*() {
33 const store = yield* KeyValueStore.KeyValueStore
34 yield* store.set("binary", new Uint8Array([0, 127, 255]))
35 assert.isTrue(yield* store.has("binary"))
36 }).pipe(Effect.provide(layer))
37 })
38
39 it.effect("does not report a write before its transaction commits", () => {
40 const db = {
41 objectStoreNames: { contains: () => true },
42 close() {},
43 transaction() {
44 const transaction = {
45 error: null as unknown,
46 onabort: null as null | (() => void),
47 objectStore() {
48 return {
49 put() {
50 const request = {
51 readyState: "pending",
52 result: undefined,
53 error: null,
54 onsuccess: null as null | (() => void),
55 onerror: null as null | (() => void)
56 }
57 queueMicrotask(() => {
58 request.readyState = "done"
59 request.onsuccess?.()
60 transaction.error = new DOMException("Commit failed", "AbortError")
61 transaction.onabort?.()
62 })
63 return request
64 }
65 }
66 }
67 }
68 return transaction
69 }
70 }
71 const failingIndexedDb = {
72 open() {
73 const request = {
74 readyState: "pending",
75 result: undefined as unknown,
76 error: null,
77 onsuccess: null as null | (() => void),
78 onerror: null as null | (() => void),
79 onupgradeneeded: null as null | (() => void)
80 }
81 queueMicrotask(() => {
82 request.readyState = "done"
83 request.result = db
84 request.onsuccess?.()
85 })
86 return request
87 }
88 }
89 const layer = BrowserKeyValueStore.layerIndexedDb({ database: "transaction_repro" }).pipe(
90 Layer.provide(Layer.succeed(
91 IndexedDb.IndexedDb,
92 IndexedDb.make({ indexedDB: failingIndexedDb as unknown as IDBFactory, IDBKeyRange })
93 ))
94 )
95
96 return Effect.gen(function*() {
97 const store = yield* KeyValueStore.KeyValueStore
98 const result = yield* Effect.result(store.set("key", "value"))
99 yield* Effect.yieldNow
100 assert.isTrue(Result.isFailure(result), "the aborted transaction was reported as successful")
101 }).pipe(Effect.provide(layer))
102 })
103})