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