Skip to content
Source
Sign inSign up

previewWebviewConfigState.ts

94 lines3.3 KB

1import { useAtomValue } from "@effect/atom-react";
2import type {
3 DesktopPreviewBridge,
4 DesktopPreviewWebviewConfig,
5 EnvironmentId,
6} from "@t3tools/contracts";
7import * as Effect from "effect/Effect";
8import * as Schema from "effect/Schema";
9import { AsyncResult, Atom } from "effect/reactivity";
10​
11import { previewBridge } from "~/components/preview/previewBridge";
12​
13const PREVIEW_CONFIG_STALE_TIME_MS = 5 * 60_000;
14const PREVIEW_CONFIG_IDLE_TTL_MS = 10 * 60_000;
15​
16export class PreviewWebviewBridgeUnavailableError extends Schema.TaggedError<PreviewWebviewBridgeUnavailableError>()(
17 "PreviewWebviewBridgeUnavailableError",
18 { environmentId: Schema.String },
19) {
20 override get message(): string {
21 return `Desktop preview configuration is unavailable for environment "${this.environmentId}".`;
22 }
24​
25export class PreviewWebviewConfigLoadError extends Schema.TaggedError<PreviewWebviewConfigLoadError>()(
26 "PreviewWebviewConfigLoadError",
27 {
28 environmentId: Schema.String,
29 cause: Schema.Defect(),
30 },
31) {
32 override get message(): string {
33 return `Failed to load desktop preview configuration for environment "${this.environmentId}".`;
34 }
36​
37export const PreviewWebviewConfigError = Schema.Union([
38 PreviewWebviewBridgeUnavailableError,
39 PreviewWebviewConfigLoadError,
40]);
41export type PreviewWebviewConfigError = typeof PreviewWebviewConfigError.Type;
42​
43type PreviewConfigBridge = Pick<DesktopPreviewBridge, "getPreviewConfig">;
44​
45const loadPreviewWebviewConfig = (
46 environmentId: EnvironmentId,
47 profileId?: string,
48 bridge: PreviewConfigBridge | null = previewBridge,
49): Effect.Effect<DesktopPreviewWebviewConfig, PreviewWebviewConfigError> => {
50 if (bridge === null) {
51 return Effect.fail(new PreviewWebviewBridgeUnavailableError({ environmentId }));
52 }
53​
54 return Effect.tryPromise({
55 try: () => bridge.getPreviewConfig(environmentId, profileId),
56 catch: (cause) => new PreviewWebviewConfigLoadError({ environmentId, cause }),
57 });
58};
59​
60// Atom.family needs a primitive key; keep profile last so delimiter-containing ids round-trip.
61const CONFIG_KEY_DELIMITER = "\u0000";
62​
63const configKey = (environmentId: EnvironmentId, profileId: string | undefined): string =>
64 `${environmentId}${CONFIG_KEY_DELIMITER}${profileId ?? ""}`;
65​
66const parseConfigKey = (key: string): { environmentId: EnvironmentId; profileId?: string } => {
67 const delimiter = key.indexOf(CONFIG_KEY_DELIMITER);
68 const environmentId = (delimiter === -1 ? key : key.slice(0, delimiter)) as EnvironmentId;
69 const profileId = delimiter === -1 ? "" : key.slice(delimiter + CONFIG_KEY_DELIMITER.length);
70 return {
71 environmentId,
72 ...(profileId === "" ? {} : { profileId }),
73 };
74};
75​
76const previewWebviewConfigAtom = Atom.family((key: string) => {
77 const { environmentId, profileId } = parseConfigKey(key);
78 return Atom.make(loadPreviewWebviewConfig(environmentId, profileId)).pipe(
79 Atom.swr({
80 staleTime: PREVIEW_CONFIG_STALE_TIME_MS,
81 revalidateOnMount: true,
82 }),
83 Atom.setIdleTTL(PREVIEW_CONFIG_IDLE_TTL_MS),
84 Atom.withLabel(`preview:webview-config:${key}`),
85 );
86});
87​
88export function usePreviewWebviewConfig(
89 environmentId: EnvironmentId,
90 profileId?: string,
91): DesktopPreviewWebviewConfig | null {
92 const result = useAtomValue(previewWebviewConfigAtom(configKey(environmentId, profileId)));
93 return AsyncResult.getOrElse(result, () => null);