Skip to content
Source
Sign inSign up
Open History to see the last commit

ProviderSettingsPanel.logic.ts

132 lines4.7 KB

1import type { EnvironmentConnectionPhase } from "@t3tools/client-runtime/connection";
2import {
3 AuthProvidersManageScope,
4 type EnvironmentId,
5 type ProjectId,
6 type ProjectSettingsOverrides,
7 type ProviderInstanceId,
8 sessionGrantsScope,
9 type ServerSettings,
10 type SessionGrantInput,
11} from "@t3tools/contracts";
12​
13export interface ProviderEnvironmentOptionLike {
14 readonly environmentId: EnvironmentId;
15 readonly label: string;
17​
18export function isProviderSettingsEnvironmentAvailable(input: {
19 readonly connectionPhase: EnvironmentConnectionPhase;
20 readonly hasServerConfig: boolean;
21}): boolean {
22 return input.connectionPhase === "connected" && input.hasServerConfig;
24​
25export function buildProviderEnvironmentOptions<T extends ProviderEnvironmentOptionLike>(
26 environments: ReadonlyArray<T>,
27 primaryEnvironmentId: EnvironmentId | null,
28 environmentIds?: readonly EnvironmentId[],
29): ReadonlyArray<T> {
30 const allowed = environmentIds ? new Set(environmentIds) : null;
31 return environments
32 .filter((environment) => !allowed || allowed.has(environment.environmentId))
33 .toSorted((left, right) => {
34 const leftIsPrimary = left.environmentId === primaryEnvironmentId;
35 const rightIsPrimary = right.environmentId === primaryEnvironmentId;
36 if (leftIsPrimary !== rightIsPrimary) {
37 return leftIsPrimary ? -1 : 1;
38 }
39 return (
40 left.label.localeCompare(right.label) ||
41 String(left.environmentId).localeCompare(String(right.environmentId))
42 );
43 });
45​
46export function resolveSelectedProviderEnvironmentId(
47 environments: ReadonlyArray<ProviderEnvironmentOptionLike>,
48 selectedEnvironmentId: EnvironmentId | null,
49 primaryEnvironmentId: EnvironmentId | null,
50): EnvironmentId | null {
51 if (
52 selectedEnvironmentId !== null &&
53 environments.some((environment) => environment.environmentId === selectedEnvironmentId)
54 ) {
55 return selectedEnvironmentId;
56 }
57 if (
58 primaryEnvironmentId !== null &&
59 environments.some((environment) => environment.environmentId === primaryEnvironmentId)
60 ) {
61 return primaryEnvironmentId;
62 }
63 return environments[0]?.environmentId ?? null;
65​
66export type ProviderEnvironmentAccess =
67 | { readonly kind: "editable" }
68 | { readonly kind: "loading"; readonly reason: "config" | "permissions" }
69 | { readonly kind: "read-only" }
70 | { readonly kind: "unavailable" }
71 | { readonly kind: "error" };
72​
73export type ProviderOperateAccess = "granted" | "denied" | "pending";
74​
75export function resolveRemoteOperateAccess(input: {
76 readonly session: SessionGrantInput | null;
77 readonly isPending: boolean;
78 readonly hasError: boolean;
79}): ProviderOperateAccess {
80 if (input.hasError) return "denied";
81 if (input.session === null) return input.isPending ? "pending" : "denied";
82 return sessionGrantsScope(input.session, AuthProvidersManageScope) ? "granted" : "denied";
84​
85/** Send providerInstances even when empty; omission preserves the stored map for older clients. */
86​
87export function buildProviderInstanceOverridePatch(
88 settings: Pick<ServerSettings, "projectSettingsOverrides">,
89 projectIds: readonly ProjectId[],
90 instanceId: ProviderInstanceId,
91 value: boolean | undefined,
92): Record<ProjectId, ProjectSettingsOverrides | null> {
93 const patch: Record<ProjectId, ProjectSettingsOverrides | null> = {};
94 for (const projectId of projectIds) {
95 const current = settings.projectSettingsOverrides[projectId];
96 const instances = { ...current?.providerInstances };
97 const instance = { ...instances[instanceId] };
98 if (value === undefined) delete instance.enabled;
99 else instance.enabled = value;
100 if (Object.keys(instance).length > 0) instances[instanceId] = instance;
101 else delete instances[instanceId];
102​
103 const { providerInstances: _omit, ...rest } = current ?? {};
104 const nextEntry: ProjectSettingsOverrides = { ...rest, providerInstances: instances };
105 patch[projectId] =
106 Object.keys(rest).length === 0 && Object.keys(instances).length === 0 ? null : nextEntry;
107 }
108 return patch;
110​
111export function classifyProviderEnvironmentAccess(input: {
112 readonly connectionPhase: EnvironmentConnectionPhase;
113 readonly hasServerConfig: boolean;
114 readonly operateAccess: ProviderOperateAccess;
115}): ProviderEnvironmentAccess {
116 if (input.connectionPhase === "error") {
117 return { kind: "error" };
118 }
119 if (input.connectionPhase !== "connected") {
120 return { kind: "unavailable" };
121 }
122 if (!input.hasServerConfig) {
123 return { kind: "loading", reason: "config" };
124 }
125 if (input.operateAccess === "pending") {
126 return { kind: "loading", reason: "permissions" };
127 }
128 if (input.operateAccess === "denied") {
129 return { kind: "read-only" };
130 }
131 return { kind: "editable" };