1import * as Redacted from "effect/Redacted";
2import type { DatabaseStatus } from "../Types.ts";
3
4/**
5 * Structural shapes for cloud values that reach shared helpers from more than
6 * one source.
7 *
8 * `Types.ts` narrows discriminants to literals (`type: "database"`) and types
9 * secrets as plain strings, while the generated distilled schemas type those
10 * fields as `string` and `string | Redacted.Redacted<string>`. Helpers used by
11 * both migrated and unmigrated resource files therefore accept the minimum
12 * each one actually reads, which both sides satisfy.
13 */
14
15export interface ObservedEndpoint {
16 readonly host?: string;
17 readonly connectionString?: string | Redacted.Redacted<string> | undefined;
18}
19
20export interface ObservedConnection {
21 readonly endpoints?: {
22 readonly direct?: ObservedEndpoint | undefined;
23 readonly pooled?: ObservedEndpoint | undefined;
24 readonly accelerate?: ObservedEndpoint | undefined;
25 };
26}
27
28export interface ObservedDatabase {
29 readonly id: string;
30 readonly name: string;
31 readonly status: DatabaseStatus;
32 readonly defaultConnectionId: string | null;
33 readonly connections: ReadonlyArray<
34 ObservedConnection & { readonly id: string }
35 >;
36 readonly region?: { readonly id: string; readonly name: string } | null;
37}
38
39export interface ObservedConnectionRecord extends ObservedConnection {
40 readonly id: string;
41 readonly name: string;
42 readonly kind: "postgres" | "accelerate";
43 readonly createdAt: string;
44 readonly database: { readonly id: string };
45}
46
47export interface ObservedSourceRepository {
48 readonly id: string;
49 readonly projectId: string;
50 readonly repoId: number;
51 readonly provider: "github";
52 readonly repoFullName: string;
53 readonly defaultBranch: string;
54 readonly isPrivate: boolean;
55 readonly status: "active" | "archived";
56 readonly installationId: string;
57 readonly createdAt: string;
58 readonly updatedAt: string;
59}
60
61export interface ObservedEnvironmentVariable {
62 readonly id: string;
63 readonly projectId: string;
64 readonly branchId: string | null;
65 readonly class: "production" | "preview";
66 readonly key: string;
67 readonly valueKid: string;
68 readonly isManagedBySystem: boolean;
69 readonly createdAt: string;
70 readonly updatedAt: string;
71}
72
73export interface ObservedBucketKey {
74 readonly id: string;
75 readonly name: string;
76}
77
78export interface ObservedBucket {
79 readonly id: string;
80 readonly name: string;
81 readonly createdAt: string;
82 readonly project: { readonly id: string };
83}
84
85export interface ObservedApp {
86 readonly id: string;
87 readonly name: string;
88 readonly projectId: string;
89 readonly region: { readonly id: string };
90 readonly branchId: string | null;
91 readonly latestDeploymentId: string | null;
92 readonly appEndpointDomain: string;
93 readonly createdAt: string;
94}
95
96export interface ObservedDeployment {
97 readonly id: string;
98 readonly foundryVersionId: string;
99 readonly status: string;
100 readonly previewDomain: string | null;
101 readonly createdAt?: string;
102}
103
104export interface ObservedBranch {
105 readonly id: string;
106 readonly gitName: string;
107 readonly isDefault: boolean;
108 readonly role: "production" | "preview";
109 readonly createdAt: string;
110 readonly updatedAt: string;
111 readonly project: { readonly id: string };
112}
113
114/**
115 * The Management API types a database's `source` discriminant as a plain
116 * string. {@link narrowDatabaseSource} turns an observed value back into the
117 * discriminated union the diffing logic needs, by checking the fields rather
118 * than asserting them.
119 */
120export interface ObservedSource {
121 readonly type: string;
122 readonly databaseId?: string;
123 readonly backupId?: string;
124}
125
126export interface ObservedProjectDatabase extends ObservedDatabase {
127 readonly isDefault: boolean;
128 readonly branchId: string | null;
129 readonly createdAt: string;
130 readonly source: ObservedSource | null;
131 readonly project: { readonly id: string };
132}
133
134export interface ObservedProject {
135 readonly id: string;
136 readonly name: string;
137 readonly createdAt: string;
138 readonly defaultRegion: string | null;
139 readonly workspace: { readonly id: string };
140}
141
142/** Read a required possibly-redacted secret as a plain string. */
143export const requiredSecretValue = (
144 value: string | Redacted.Redacted<string>,
145): string => (typeof value === "string" ? value : Redacted.value(value));
146
147/** Read a possibly-redacted secret as a plain string. */
148export const secretValue = (
149 value: string | Redacted.Redacted<string> | undefined | null,
150): string | undefined =>
151 value === undefined || value === null
152 ? undefined
153 : typeof value === "string"
154 ? value
155 : Redacted.value(value);
156
157/**
158 * Recover the discriminated source union from an observed database's
159 * string-typed `source`. A missing source reads as `empty`, matching how the
160 * diffing logic already treats it. An unrecognized shape reads as `unknown`,
161 * which deliberately matches no desired source, so the immutable-source
162 * refusals fire instead of silently accepting it.
163 */
164export const narrowDatabaseSource = (
165 source: ObservedSource | undefined | null,
166):
167 | { readonly type: "empty" }
168 | { readonly type: "unknown" }
169 | { readonly type: "database"; readonly databaseId: string }
170 | {
171 readonly type: "backup";
172 readonly databaseId: string;
173 readonly backupId: string;
174 } => {
175 if (source == null) return { type: "empty" };
176 if (source.type === "empty") return { type: "empty" };
177 if (source.type === "database" && source.databaseId !== undefined) {
178 return { type: "database", databaseId: source.databaseId };
179 }
180 if (
181 source.type === "backup" &&
182 source.databaseId !== undefined &&
183 source.backupId !== undefined
184 ) {
185 return {
186 type: "backup",
187 databaseId: source.databaseId,
188 backupId: source.backupId,
189 };
190 }
191 return { type: "unknown" };
192};