1import type { EnvironmentId } from "@t3tools/contracts";
2import * as Effect from "effect/Effect";
3import { Atom } from "effect/reactivity";
4
5import * as MobileDatabase from "../persistence/mobile-database";
6import { projectFaviconDatabaseCache } from "../lib/projectFaviconDatabaseCache";
7import * as Runtime from "../lib/runtime";
8
9export interface EnvironmentClientCacheSummary {
10 readonly environmentId: EnvironmentId;
11 readonly recordCount: number;
12 readonly payloadBytes: number;
13 readonly kinds: Readonly<Partial<Record<MobileDatabase.ClientCacheKind, number>>>;
14}
15
16export interface ClientCacheSummary {
17 readonly recordCount: number;
18 readonly payloadBytes: number;
19 readonly environments: ReadonlyArray<EnvironmentClientCacheSummary>;
20}
21
22export type ClientCacheClearScope =
23 | { readonly type: "all" }
24 | { readonly type: "environment"; readonly environmentId: EnvironmentId };
25
26function aggregateCacheSummary(
27 rows: ReadonlyArray<{
28 readonly environmentId: EnvironmentId;
29 readonly kind: MobileDatabase.ClientCacheKind;
30 readonly recordCount: number;
31 readonly payloadBytes: number;
32 }>,
33): ClientCacheSummary {
34 const environments = new Map<EnvironmentId, EnvironmentClientCacheSummary>();
35 let recordCount = 0;
36 let payloadBytes = 0;
37
38 for (const row of rows) {
39 recordCount += row.recordCount;
40 payloadBytes += row.payloadBytes;
41 const current = environments.get(row.environmentId) ?? {
42 environmentId: row.environmentId,
43 recordCount: 0,
44 payloadBytes: 0,
45 kinds: {},
46 };
47 environments.set(row.environmentId, {
48 environmentId: row.environmentId,
49 recordCount: current.recordCount + row.recordCount,
50 payloadBytes: current.payloadBytes + row.payloadBytes,
51 kinds: { ...current.kinds, [row.kind]: row.recordCount },
52 });
53 }
54
55 return {
56 recordCount,
57 payloadBytes,
58 environments: [...environments.values()],
59 };
60}
61
62const clientCacheRuntime = Atom.runtime(Runtime.layer);
63
64export const clientCacheSummaryAtom = clientCacheRuntime
65 .atom(
66 MobileDatabase.MobileDatabase.pipe(
67 Effect.flatMap((database) => database.inspectCaches),
68 Effect.map(aggregateCacheSummary),
69 ),
70 )
71 .pipe(Atom.withLabel("mobile:client-cache:summary"));
72
73export const clearClientCacheAtom = clientCacheRuntime
74 .fn((scope: ClientCacheClearScope, get) =>
75 Effect.promise(() =>
76 scope.type === "all"
77 ? projectFaviconDatabaseCache.clearAll()
78 : projectFaviconDatabaseCache.clearEnvironment(scope.environmentId),
79 ).pipe(
80 Effect.andThen(MobileDatabase.MobileDatabase),
81 Effect.flatMap((database) =>
82 scope.type === "all"
83 ? database.clearAllCaches
84 : database.clearEnvironmentCache(scope.environmentId),
85 ),
86 Effect.tap(() =>
87 Effect.sync(() => {
88 get.refresh(clientCacheSummaryAtom);
89 }),
90 ),
91 ),
92 )
93 .pipe(Atom.withLabel("mobile:client-cache:clear"));