1import * as redshiftserverless from "@distilled.cloud/aws/redshift-serverless";
2import * as Effect from "effect/Effect";
3import { diffTags } from "../../Tags.ts";
4
5/**
6 * Convert a Redshift Serverless wire tag list (`{ key, value }`) into a plain
7 * record, dropping malformed entries.
8 */
9export const toTagRecord = (
10 tags: ReadonlyArray<{ key?: string; value?: string }> | undefined,
11): Record<string, string> =>
12 Object.fromEntries(
13 (tags ?? [])
14 .filter(
15 (tag): tag is { key: string; value: string } =>
16 typeof tag.key === "string" && typeof tag.value === "string",
17 )
18 .map((tag) => [tag.key, tag.value]),
19 );
20
21/**
22 * Convert a desired tag record into the wire tag list (`{ key, value }`) used
23 * by create/tag calls.
24 */
25export const toWireTags = (
26 tags: Record<string, string>,
27): redshiftserverless.Tag[] =>
28 Object.entries(tags).map(([key, value]) => ({ key, value }));
29
30/**
31 * Read the observed tags of a Redshift Serverless resource. Tag reads are
32 * best-effort — a failure (e.g. a race with deletion) reports no tags.
33 */
34export const readTags = Effect.fn(function* (arn: string) {
35 const response = yield* redshiftserverless
36 .listTagsForResource({ resourceArn: arn })
37 .pipe(Effect.catch(() => Effect.succeed(undefined)));
38 return toTagRecord(response?.tags);
39});
40
41/**
42 * Sync tags on a Redshift Serverless resource: diff the OBSERVED cloud tags
43 * against the desired set and apply only the delta.
44 */
45export const syncTags = Effect.fn(function* (
46 arn: string,
47 desiredTags: Record<string, string>,
48) {
49 const observedTags = yield* readTags(arn);
50 const { removed, upsert } = diffTags(observedTags, desiredTags);
51 if (upsert.length > 0) {
52 yield* redshiftserverless.tagResource({
53 resourceArn: arn,
54 tags: upsert.map(({ Key, Value }) => ({ key: Key, value: Value })),
55 });
56 }
57 if (removed.length > 0) {
58 yield* redshiftserverless.untagResource({
59 resourceArn: arn,
60 tagKeys: removed,
61 });
62 }
63});