1import * as appflow from "@distilled.cloud/aws/appflow";
2import * as Effect from "effect/Effect";
3import { diffTags } from "../../Tags.ts";
4
5/**
6 * Coerce an AppFlow wire tag map (`Record<string, string | undefined>`) into
7 * a plain `Record<string, string>`, dropping undefined values.
8 */
9export const toTagRecord = (
10 tags: { [key: string]: string | undefined } | undefined,
11): Record<string, string> =>
12 Object.fromEntries(
13 Object.entries(tags ?? {}).filter(
14 (entry): entry is [string, string] => typeof entry[1] === "string",
15 ),
16 );
17
18/**
19 * Read the observed tags of an AppFlow resource. Tag reads are best-effort.
20 */
21export const readAppFlowTags = Effect.fn(function* (arn: string) {
22 const response = yield* appflow
23 .listTagsForResource({ resourceArn: arn })
24 .pipe(Effect.catch(() => Effect.succeed(undefined)));
25 return toTagRecord(response?.tags);
26});
27
28/**
29 * Sync tags on an AppFlow resource: diff the OBSERVED cloud tags against the
30 * desired set and apply only the delta.
31 */
32export const syncAppFlowTags = Effect.fn(function* (
33 arn: string,
34 desiredTags: Record<string, string>,
35) {
36 const observedTags = yield* readAppFlowTags(arn);
37 const { removed, upsert } = diffTags(observedTags, desiredTags);
38 if (upsert.length > 0) {
39 yield* appflow.tagResource({
40 resourceArn: arn,
41 tags: Object.fromEntries(upsert.map((t) => [t.Key, t.Value])),
42 });
43 }
44 if (removed.length > 0) {
45 yield* appflow.untagResource({ resourceArn: arn, tagKeys: removed });
46 }
47});