1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Schema from "effect/Schema";
5import * as SecureStore from "expo-secure-store";
6
7const MobileSecureStorageOperation = Schema.Literals(["read", "write", "delete"]);
8
9export class MobileSecureStorageError extends Schema.TaggedError<MobileSecureStorageError>()(
10 "MobileSecureStorageError",
11 {
12 operation: MobileSecureStorageOperation,
13 key: Schema.String,
14 cause: Schema.Defect(),
15 },
16) {
17 override get message(): string {
18 return `Mobile secure storage operation ${this.operation} failed for key ${this.key}.`;
19 }
20}
21
22export class MobileSecureStorage extends Context.Service<
23 MobileSecureStorage,
24 {
25 readonly getItem: (key: string) => Effect.Effect<string | null, MobileSecureStorageError>;
26 readonly setItem: (key: string, value: string) => Effect.Effect<void, MobileSecureStorageError>;
27 readonly removeItem: (key: string) => Effect.Effect<void, MobileSecureStorageError>;
28 }
29>()("@t3tools/mobile/persistence/MobileSecureStorage") {}
30
31export const make = MobileSecureStorage.of({
32 getItem: Effect.fn("MobileSecureStorage.getItem")((key) =>
33 Effect.tryPromise({
34 try: () => SecureStore.getItemAsync(key),
35 catch: (cause) => new MobileSecureStorageError({ operation: "read", key, cause }),
36 }),
37 ),
38 setItem: Effect.fn("MobileSecureStorage.setItem")((key, value) =>
39 Effect.tryPromise({
40 try: () => SecureStore.setItemAsync(key, value),
41 catch: (cause) => new MobileSecureStorageError({ operation: "write", key, cause }),
42 }),
43 ),
44 removeItem: Effect.fn("MobileSecureStorage.removeItem")((key) =>
45 Effect.tryPromise({
46 try: () => SecureStore.deleteItemAsync(key),
47 catch: (cause) => new MobileSecureStorageError({ operation: "delete", key, cause }),
48 }),
49 ),
50});
51
52export const layer = Layer.succeed(MobileSecureStorage, make);