1import { afterEach, describe, it } from "@effect/vitest"
2import { assertTrue, deepStrictEqual, strictEqual } from "@effect/vitest/utils"
3import { Effect, type Layer, Option, Schema } from "effect"
4import * as KeyValueStore from "effect/persistence/KeyValueStore"
5import * as Persistence from "effect/persistence/Persistence"
6
7export const testLayer = <E>(layer: Layer.Layer<KeyValueStore.KeyValueStore, E>) => {
8 describe("store operations", { concurrent: false }, () => {
9 const run = <E, A>(effect: Effect.Effect<A, E, KeyValueStore.KeyValueStore>) =>
10 Effect.runPromise(Effect.provide(effect, layer))
11
12 afterEach(() =>
13 run(Effect.gen(function*() {
14 const kv = yield* (KeyValueStore.KeyValueStore)
15 yield* kv.clear
16 }))
17 )
18
19 it("set", () =>
20 run(Effect.gen(function*() {
21 const kv = yield* (KeyValueStore.KeyValueStore)
22 yield* (kv.set("/foo/bar", "bar"))
23
24 const value = yield* (kv.get("/foo/bar"))
25 const length = yield* (kv.size)
26
27 strictEqual(value, "bar")
28 strictEqual(length, 1)
29 })))
30
31 it("get/ missing", () =>
32 run(Effect.gen(function*() {
33 const kv = yield* (KeyValueStore.KeyValueStore)
34 yield* (kv.clear)
35 const value = yield* (kv.get("foo"))
36
37 strictEqual(value, undefined)
38 })))
39
40 it("remove", () =>
41 run(Effect.gen(function*() {
42 const kv = yield* (KeyValueStore.KeyValueStore)
43 yield* (kv.set("foo", "bar"))
44 yield* (kv.remove("foo"))
45
46 const value = yield* (kv.get("foo"))
47 const length = yield* (kv.size)
48
49 strictEqual(value, undefined)
50 strictEqual(length, 0)
51 })))
52
53 it("clear", () =>
54 run(Effect.gen(function*() {
55 const kv = yield* (KeyValueStore.KeyValueStore)
56 yield* (kv.set("foo", "bar"))
57 yield* (kv.clear)
58
59 const value = yield* (kv.get("foo"))
60 const length = yield* (kv.size)
61
62 strictEqual(value, undefined)
63 strictEqual(length, 0)
64 })))
65
66 it("modify", () =>
67 run(Effect.gen(function*() {
68 const kv = yield* (KeyValueStore.KeyValueStore)
69 yield* (kv.set("foo", "bar"))
70
71 const value = yield* (kv.modify("foo", (v) => v + "bar"))
72 const length = yield* (kv.size)
73
74 strictEqual(value, "barbar")
75 strictEqual(length, 1)
76 })))
77
78 it("modify - none", () =>
79 run(Effect.gen(function*() {
80 const kv = yield* (KeyValueStore.KeyValueStore)
81
82 const value = yield* (kv.modify("foo", (v) => v + "bar"))
83 const length = yield* (kv.size)
84
85 strictEqual(value, undefined)
86 strictEqual(length, 0)
87 })))
88
89 it("setMany stores entries without a TTL", () =>
90 run(
91 Effect.gen(function*() {
92 const backing = yield* Persistence.BackingPersistence
93 const store = yield* backing.make("store")
94
95 yield* store.setMany([["key", { value: 1 }, undefined]])
96
97 deepStrictEqual(yield* store.get("key"), { value: 1 })
98 }).pipe(
99 Effect.scoped,
100 Effect.provide(Persistence.layerBackingKvs)
101 )
102 ))
103 })
104}
105
106describe("KeyValueStore / layerMemory", () => testLayer(KeyValueStore.layerMemory))
107
108describe("KeyValueStore / prefix", () => {
109 it.effect("prefixes the keys", () =>
110 Effect.gen(function*() {
111 const store = yield* (KeyValueStore.KeyValueStore)
112 const prefixed = KeyValueStore.prefix(store, "prefix/")
113
114 yield* (prefixed.set("foo", "bar"))
115 yield* (prefixed.modify("foo", (v) => v + "bar"))
116
117 strictEqual(yield* (prefixed.get("foo")), "barbar")
118 assertTrue(yield* (prefixed.has("foo")))
119
120 strictEqual(yield* (store.get("prefix/foo")), "barbar")
121 assertTrue(yield* (store.has("prefix/foo")))
122 }).pipe(
123 Effect.provide(KeyValueStore.layerMemory)
124 ))
125})
126
127describe("toSchemaStore", () => {
128 class User extends Schema.Class<User>("User")({
129 name: Schema.String,
130 age: Schema.Number
131 }) {}
132
133 it.effect("encodes & decodes", () =>
134 Effect.gen(function*() {
135 const store = yield* KeyValueStore.KeyValueStore
136 const schemaStore = KeyValueStore.toSchemaStore(store, User)
137 yield* (schemaStore.set("foo", new User({ name: "foo", age: 42 })))
138 yield* (schemaStore.modify("foo", (user) => new User({ ...user, age: 43 })))
139 const value = yield* schemaStore.get("foo")
140
141 assertTrue(Option.isSome(value))
142 strictEqual(value.value.name, "foo")
143 strictEqual(value.value.age, 43)
144 }).pipe(
145 Effect.provide(KeyValueStore.layerMemory)
146 ))
147
148 it.effect("prefix", () =>
149 Effect.gen(function*() {
150 const store = yield* KeyValueStore.KeyValueStore
151 const schemaStore = KeyValueStore.toSchemaStore(store, User)
152 yield* (schemaStore.set("prefix/foo", new User({ name: "foo", age: 42 })))
153 const value = yield* schemaStore.get("prefix/foo")
154
155 if (Option.isSome(value)) {
156 strictEqual(value.value.name, "foo")
157 strictEqual(value.value.age, 42)
158 }
159 }).pipe(
160 Effect.provide(KeyValueStore.layerMemory)
161 ))
162
163 it.effect("json compliant", () =>
164 Effect.gen(function*() {
165 const store = yield* KeyValueStore.KeyValueStore
166 const schema = Schema.Struct({
167 a: Schema.Date
168 })
169 const schemaStore = KeyValueStore.toSchemaStore(store, schema)
170 yield* (schemaStore.set("foo", { a: new Date(0) }))
171 const value = yield* schemaStore.get("foo")
172 assertTrue(Option.isSome(value))
173 deepStrictEqual(value.value.a, new Date(0))
174 }).pipe(
175 Effect.provide(KeyValueStore.layerMemory)
176 ))
177})