1import type { EnvironmentId } from "@t3tools/contracts";
2import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4import * as Option from "effect/Option";
5import * as Schema from "effect/Schema";
6
7/** Platforms opt in to storing selected query responses beside their other offline data. */
8export class EnvironmentQueryStore extends Context.Reference<{
9 readonly load: (
10 environmentId: EnvironmentId,
11 key: string,
12 ) => Effect.Effect<Option.Option<string>>;
13 readonly save: (
14 environmentId: EnvironmentId,
15 key: string,
16 payload: string,
17 ) => Effect.Effect<void>;
18} | null>("@t3tools/client-runtime/EnvironmentQueryStore", {
19 defaultValue: () => null,
20}) {}
21
22export interface QueryPersistence<A> {
23 readonly load: (environmentId: EnvironmentId, key: string) => Effect.Effect<Option.Option<A>>;
24 readonly save: (environmentId: EnvironmentId, key: string, value: A) => Effect.Effect<void>;
25}
26
27export function persistQuery<A, I>(schema: Schema.Codec<A, I>): QueryPersistence<A> {
28 const codec = Schema.fromJsonString(Schema.toCodecJson(schema));
29 return {
30 load: (environmentId, key) =>
31 Effect.gen(function* () {
32 const store = yield* EnvironmentQueryStore;
33 if (store === null) return Option.none<A>();
34 const stored = yield* store.load(environmentId, key);
35 if (Option.isNone(stored)) return Option.none<A>();
36 return yield* Schema.decodeUnknownEffect(codec)(stored.value).pipe(
37 Effect.map(Option.some),
38 Effect.catch(() => Effect.succeedNone),
39 );
40 }),
41 save: (environmentId, key, value) =>
42 Effect.gen(function* () {
43 const store = yield* EnvironmentQueryStore;
44 if (store === null) return;
45 const payload = yield* Schema.encodeEffect(codec)(value);
46 yield* store.save(environmentId, key, payload);
47 }).pipe(Effect.catch(() => Effect.logWarning("Offline query cache save failed"))),
48 };
49}