1import * as appconfig from "@distilled.cloud/aws/appconfig";
2import * as Effect from "effect/Effect";
3import { isResolved } from "../../Diff.ts";
4import * as Provider from "../../Provider.ts";
5import { Resource } from "../../Resource.ts";
6import type { Providers } from "../Providers.ts";
7
8export interface HostedConfigurationVersionProps {
9 /**
10 * ID of the application. Changing it replaces the version.
11 */
12 applicationId: string;
13 /**
14 * ID of the (hosted) configuration profile. Changing it replaces the
15 * version.
16 */
17 configurationProfileId: string;
18 /**
19 * The configuration content itself — the YAML/JSON/text document served to
20 * clients. Hosted configuration versions are immutable; changing the
21 * content creates a new version (a replacement).
22 */
23 content: string;
24 /**
25 * MIME type of the content, e.g. `application/json`, `application/x-yaml`,
26 * or `text/plain`.
27 */
28 contentType: string;
29 /**
30 * Description of the configuration version.
31 */
32 description?: string;
33 /**
34 * A user-defined label for the version (e.g. a git SHA). Must be unique per
35 * profile.
36 */
37 versionLabel?: string;
38}
39
40export interface HostedConfigurationVersion extends Resource<
41 "AWS.AppConfig.HostedConfigurationVersion",
42 HostedConfigurationVersionProps,
43 {
44 applicationId: string;
45 configurationProfileId: string;
46 versionNumber: number;
47 contentType: string;
48 versionLabel: string | undefined;
49 },
50 never,
51 Providers
52> {}
53
54/**
55 * An AWS AppConfig hosted configuration version — the actual configuration
56 * content stored in the AppConfig hosted store. Versions are immutable: each
57 * change to the content produces a new version (a replacement), and its
58 * `versionNumber` is what you deploy through a {@link Deployment}.
59 *
60 * ### Creating a Hosted Configuration Version
61 * **Example:** JSON Configuration
62 * ```typescript
63 * const version = yield* AppConfig.HostedConfigurationVersion("V1", {
64 * applicationId: app.applicationId,
65 * configurationProfileId: profile.configurationProfileId,
66 * content: JSON.stringify({ featureX: true }),
67 * contentType: "application/json",
68 * });
69 * // version.versionNumber -> 1
70 * ```
71 *
72 * @resource
73 */
74export const HostedConfigurationVersion = Resource<HostedConfigurationVersion>(
75 "AWS.AppConfig.HostedConfigurationVersion",
76);
77
78export const HostedConfigurationVersionProvider = () =>
79 Provider.effect(
80 HostedConfigurationVersion,
81 Effect.gen(function* () {
82 const readVersion = Effect.fn(function* (
83 applicationId: string,
84 configurationProfileId: string,
85 versionNumber: number,
86 ) {
87 return yield* appconfig
88 .getHostedConfigurationVersion({
89 ApplicationId: applicationId,
90 ConfigurationProfileId: configurationProfileId,
91 VersionNumber: versionNumber,
92 })
93 .pipe(
94 Effect.catchTag("ResourceNotFoundException", () =>
95 Effect.succeed(undefined),
96 ),
97 );
98 });
99
100 return {
101 stables: ["applicationId", "configurationProfileId", "versionNumber"],
102
103 // Hosted configuration versions are immutable and content-addressed —
104 // any change produces a fresh version.
105 diff: Effect.fn(function* ({ olds, news }) {
106 if (!isResolved(news)) return undefined;
107 if (
108 olds !== undefined &&
109 (olds.applicationId !== news.applicationId ||
110 olds.configurationProfileId !== news.configurationProfileId ||
111 olds.content !== news.content ||
112 olds.contentType !== news.contentType ||
113 (olds.description ?? undefined) !==
114 (news.description ?? undefined) ||
115 (olds.versionLabel ?? undefined) !==
116 (news.versionLabel ?? undefined))
117 ) {
118 return { action: "replace" } as const;
119 }
120 }),
121
122 read: Effect.fn(function* ({ output }) {
123 if (output?.versionNumber === undefined) return undefined;
124 const version = yield* readVersion(
125 output.applicationId,
126 output.configurationProfileId,
127 output.versionNumber,
128 );
129 if (version?.VersionNumber === undefined) return undefined;
130 return {
131 applicationId: output.applicationId,
132 configurationProfileId: output.configurationProfileId,
133 versionNumber: version.VersionNumber,
134 contentType: version.ContentType ?? output.contentType,
135 versionLabel: version.VersionLabel,
136 };
137 }),
138
139 reconcile: Effect.fn(function* ({ news, output, session }) {
140 // Observe — the version may already exist (crash-resumed create).
141 const existing =
142 output?.versionNumber !== undefined
143 ? yield* readVersion(
144 output.applicationId,
145 output.configurationProfileId,
146 output.versionNumber,
147 )
148 : undefined;
149
150 if (existing?.VersionNumber !== undefined) {
151 yield* session.note(`version-${existing.VersionNumber}`);
152 return {
153 applicationId: news.applicationId,
154 configurationProfileId: news.configurationProfileId,
155 versionNumber: existing.VersionNumber,
156 contentType: existing.ContentType ?? news.contentType,
157 versionLabel: existing.VersionLabel,
158 };
159 }
160
161 const created = yield* appconfig.createHostedConfigurationVersion({
162 ApplicationId: news.applicationId,
163 ConfigurationProfileId: news.configurationProfileId,
164 Content: news.content,
165 ContentType: news.contentType,
166 Description: news.description,
167 VersionLabel: news.versionLabel,
168 });
169
170 yield* session.note(`version-${created.VersionNumber}`);
171 return {
172 applicationId: news.applicationId,
173 configurationProfileId: news.configurationProfileId,
174 versionNumber: created.VersionNumber!,
175 contentType: created.ContentType ?? news.contentType,
176 versionLabel: created.VersionLabel,
177 };
178 }),
179
180 delete: Effect.fn(function* ({ output }) {
181 yield* appconfig
182 .deleteHostedConfigurationVersion({
183 ApplicationId: output.applicationId,
184 ConfigurationProfileId: output.configurationProfileId,
185 VersionNumber: output.versionNumber,
186 })
187 .pipe(
188 Effect.catchTag("ResourceNotFoundException", () => Effect.void),
189 );
190 }),
191
192 // Hosted configuration versions are keyed under their parent profile.
193 list: () => Effect.succeed([]),
194 };
195 }),
196 );