1import { EnvironmentId, type OrchestrationV2ArchivedShellSnapshot } from "@t3tools/contracts";
2import * as Arr from "effect/Array";
3import { pipe } from "effect/Function";
4import * as Option from "effect/Option";
5import * as Order from "effect/Order";
6import { AsyncResult, Atom } from "effect/reactivity";
7
8export interface ArchivedSnapshotEntry {
9 readonly environmentId: EnvironmentId;
10 readonly snapshot: OrchestrationV2ArchivedShellSnapshot;
11}
12
13export interface ArchivedThreadSnapshotsState {
14 readonly snapshots: ReadonlyArray<ArchivedSnapshotEntry>;
15 readonly error: string | null;
16 readonly isLoading: boolean;
17}
18
19const ARCHIVED_THREADS_ENVIRONMENT_KEY_SEPARATOR = "\u001f";
20const environmentIdOrder = Order.String as Order.Order<EnvironmentId>;
21
22export function makeArchivedThreadsEnvironmentKey(
23 environmentIds: ReadonlyArray<EnvironmentId>,
24): string {
25 return pipe(environmentIds, Arr.sort(environmentIdOrder), (sortedEnvironmentIds) =>
26 sortedEnvironmentIds.join(ARCHIVED_THREADS_ENVIRONMENT_KEY_SEPARATOR),
27 );
28}
29
30export function parseArchivedThreadsEnvironmentKey(key: string): ReadonlyArray<EnvironmentId> {
31 if (key.length === 0) {
32 return [];
33 }
34 return pipe(
35 key.split(ARCHIVED_THREADS_ENVIRONMENT_KEY_SEPARATOR),
36 Arr.map((environmentId) => EnvironmentId.make(environmentId)),
37 );
38}
39
40export function createArchivedThreadSnapshotsAtomFamily<E>(options: {
41 readonly getSnapshotAtom: (
42 environmentId: EnvironmentId,
43 ) => Atom.Atom<AsyncResult.AsyncResult<OrchestrationV2ArchivedShellSnapshot, E>>;
44 readonly labelPrefix: string;
45}) {
46 return Atom.family((environmentKey: string) =>
47 Atom.make((get): ArchivedThreadSnapshotsState => {
48 const snapshots: ArchivedSnapshotEntry[] = [];
49 let error: string | null = null;
50 let isLoading = false;
51
52 for (const environmentId of parseArchivedThreadsEnvironmentKey(environmentKey)) {
53 const result = get(options.getSnapshotAtom(environmentId));
54 isLoading ||= result.waiting;
55
56 const snapshot = Option.getOrNull(AsyncResult.value(result));
57 if (snapshot !== null) {
58 snapshots.push({ environmentId, snapshot });
59 }
60
61 if (error === null && result._tag === "Failure") {
62 error = "Failed to load archived threads.";
63 }
64 }
65
66 return { snapshots, error, isLoading };
67 }).pipe(Atom.withLabel(`${options.labelPrefix}:${environmentKey}`)),
68 );
69}