1import * as mediaconvert from "@distilled.cloud/aws/mediaconvert";
2import * as Effect from "effect/Effect";
3import { diffTags } from "../../Tags.ts";
4
5/**
6 * Coerce a MediaConvert wire tag map (values are `string | undefined`) into a
7 * plain `Record<string, string>`, dropping any 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 a MediaConvert resource by ARN. Tag reads are
20 * best-effort — a failure (e.g. a race with deletion) reports no tags.
21 */
22export const readMcTags = Effect.fn(function* (arn: string) {
23 const response = yield* mediaconvert
24 .listTagsForResource({ Arn: arn })
25 .pipe(Effect.catch(() => Effect.succeed(undefined)));
26 return toTagRecord(response?.ResourceTags?.Tags);
27});
28
29/**
30 * Sync tags on a MediaConvert resource: diff the OBSERVED cloud tags against
31 * the desired set and apply only the delta. MediaConvert's `tagResource`
32 * takes a tag map keyed by ARN in the body; `untagResource` takes an ARN path
33 * label plus the keys to remove.
34 */
35export const syncMcTags = Effect.fn(function* (
36 arn: string,
37 desiredTags: Record<string, string>,
38) {
39 const observedTags = yield* readMcTags(arn);
40 const { removed, upsert } = diffTags(observedTags, desiredTags);
41 if (upsert.length > 0) {
42 yield* mediaconvert.tagResource({
43 Arn: arn,
44 Tags: Object.fromEntries(upsert.map((t) => [t.Key, t.Value])),
45 });
46 }
47 if (removed.length > 0) {
48 yield* mediaconvert.untagResource({ Arn: arn, TagKeys: removed });
49 }
50});