1import { createHubEnvironmentCapabilitiesAtom } from "@t3tools/client-runtime/state/hub";
2import { hubConnection } from "../connection/hub";
3import {
4 DEFAULT_SERVER_SETTINGS,
5 type EditorId,
6 type EnvironmentTheme,
7 type ServerConfig,
8 type ServerConfigStreamEvent,
9 type ServerLifecycleLegacyThreadMigrationPayload,
10 type ServerLifecycleWelcomePayload,
11 type ServerProvider,
12 type ServerSettings,
13} from "@t3tools/contracts";
14import { createServerEnvironmentAtoms } from "@t3tools/client-runtime/state/server";
15import { createOutdatedServerUpdateCommand } from "@t3tools/client-runtime/state/outdatedServerUpdate";
16import { createEnvironmentServerConfigsAtom } from "@t3tools/client-runtime/state/shell";
17import { mergeWithDefaultKeybindings } from "@t3tools/shared/keybindings";
18import * as Option from "effect/Option";
19import { AsyncResult, Atom } from "effect/reactivity";
20
21import { environmentCatalog } from "../connection/catalog";
22import { connectionAtomRuntime } from "../connection/runtime";
23import { primaryEnvironmentIdAtom } from "./primaryEnvironment";
24import { environmentSession } from "./session";
25
26// Opted in for every environment, not just the primary one. Only the primary
27// environment's themes are rendered, but which environment is primary changes
28// at runtime and the subscription payload is fixed when it is established --
29// gating on "primary right now" would leave a newly promoted environment with
30// no themes until it reconnected. The set is capped server-side and stripped
31// before caching, so following all of them costs a few KB per environment.
32export const serverEnvironment = createServerEnvironmentAtoms(connectionAtomRuntime, {
33 initialConfigValueAtom: environmentSession.initialConfigValueAtom,
34 environmentThemes: true,
35 usageLimitSources: true,
36 usageLimitsCommand: true,
37});
38/** Updates a host whose protocol is too old for this client to connect to. */
39export const updateOutdatedServer = createOutdatedServerUpdateCommand(connectionAtomRuntime);
40export const environmentServerConfigsAtom = createEnvironmentServerConfigsAtom({
41 catalogValueAtom: environmentCatalog.catalogValueAtom,
42 serverConfigValueAtom: serverEnvironment.configValueAtom,
43});
44export const environmentCapabilitiesAtom = createHubEnvironmentCapabilitiesAtom(
45 environmentServerConfigsAtom,
46 hubConnection,
47);
48
49interface PrimaryServerState {
50 readonly config: ServerConfig | null;
51 readonly latestEvent: ServerConfigStreamEvent | null;
52 readonly welcome: ServerLifecycleWelcomePayload | null;
53}
54
55const EMPTY_AVAILABLE_EDITORS: ReadonlyArray<EditorId> = [];
56export const EMPTY_SERVER_PROVIDERS: ReadonlyArray<ServerProvider> = [];
57const EMPTY_PRIMARY_SERVER_STATE: PrimaryServerState = {
58 config: null,
59 latestEvent: null,
60 welcome: null,
61};
62
63const primaryServerStateAtom = Atom.make((get): PrimaryServerState => {
64 const environmentId = get(primaryEnvironmentIdAtom);
65 if (environmentId === null) {
66 return EMPTY_PRIMARY_SERVER_STATE;
67 }
68
69 const target = { environmentId, input: {} };
70 const configProjection = Option.getOrNull(
71 AsyncResult.value(get(serverEnvironment.configProjection(target))),
72 );
73 const welcome = Option.getOrNull(AsyncResult.value(get(serverEnvironment.welcome(target))));
74
75 return {
76 config: get(serverEnvironment.configValueAtom(environmentId)),
77 latestEvent: configProjection?.latestEvent ?? null,
78 welcome,
79 };
80}).pipe(Atom.withLabel("web-primary-server-state"));
81
82export const primaryServerConfigAtom = Atom.make(
83 (get): ServerConfig | null => get(primaryServerStateAtom).config,
84).pipe(Atom.withLabel("web-primary-server-config"));
85
86export const primaryServerConfigEventAtom = Atom.make(
87 (get): ServerConfigStreamEvent | null => get(primaryServerStateAtom).latestEvent,
88).pipe(Atom.withLabel("web-primary-server-config-event"));
89
90export const primaryServerWelcomeAtom = Atom.make(
91 (get): ServerLifecycleWelcomePayload | null => get(primaryServerStateAtom).welcome,
92).pipe(Atom.withLabel("web-primary-server-welcome"));
93
94export const primaryServerLegacyThreadMigrationAtom = Atom.make(
95 (get): ServerLifecycleLegacyThreadMigrationPayload | null => {
96 const environmentId = get(primaryEnvironmentIdAtom);
97 if (environmentId === null) {
98 return null;
99 }
100 return Option.getOrNull(
101 AsyncResult.value(get(serverEnvironment.legacyThreadMigration({ environmentId, input: {} }))),
102 );
103 },
104).pipe(Atom.withLabel("web-primary-server-legacy-thread-migration"));
105
106export const primaryServerSettingsAtom = Atom.make(
107 (get): ServerSettings => get(primaryServerConfigAtom)?.settings ?? DEFAULT_SERVER_SETTINGS,
108).pipe(Atom.withLabel("web-primary-server-settings"));
109
110export const primaryServerProvidersAtom = Atom.make(
111 (get): ReadonlyArray<ServerProvider> =>
112 get(primaryServerConfigAtom)?.providers ?? EMPTY_SERVER_PROVIDERS,
113).pipe(Atom.withLabel("web-primary-server-providers"));
114
115export const primaryServerKeybindingsAtom = Atom.make((get): ServerConfig["keybindings"] =>
116 mergeWithDefaultKeybindings(get(primaryServerConfigAtom)?.keybindings ?? []),
117).pipe(Atom.withLabel("web-primary-server-keybindings"));
118
119export const primaryServerAvailableEditorsAtom = Atom.make(
120 (get): ReadonlyArray<EditorId> =>
121 get(primaryServerConfigAtom)?.availableEditors ?? EMPTY_AVAILABLE_EDITORS,
122).pipe(Atom.withLabel("web-primary-server-available-editors"));
123
124const EMPTY_ENVIRONMENT_THEMES: ReadonlyArray<EnvironmentTheme> = [];
125
126/**
127 * Palettes published by the primary environment's machine. Only the primary
128 * environment: a client follows the machine it is anchored to, not every
129 * environment it happens to be connected to.
130 */
131export const primaryServerEnvironmentThemesAtom = Atom.make(
132 (get): ReadonlyArray<EnvironmentTheme> =>
133 get(primaryServerConfigAtom)?.environmentThemes ?? EMPTY_ENVIRONMENT_THEMES,
134).pipe(Atom.withLabel("web-primary-server-environment-themes"));