1import * as b2bi from "@distilled.cloud/aws/b2bi";
2import * as Effect from "effect/Effect";
3import { diffTags } from "../../Tags.ts";
4
5/**
6 * Convert a B2BI wire tag list (`{ Key, Value }[]`) into a plain record,
7 * 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 B2BI wire tag list for create calls.
23 */
24export const toWireTags = (tags: Record<string, string>): b2bi.Tag[] =>
25 Object.entries(tags).map(([Key, Value]) => ({ Key, Value }));
26
27/**
28 * Read the observed tags of a B2BI resource. Tag reads are best-effort —
29 * a failure (e.g. a race with deletion) reports no tags.
30 */
31export const readB2biTags = Effect.fn(function* (arn: string) {
32 const response = yield* b2bi
33 .listTagsForResource({ ResourceARN: arn })
34 .pipe(Effect.catch(() => Effect.succeed(undefined)));
35 return toTagRecord(response?.Tags);
36});
37
38/**
39 * Sync tags on a B2BI resource: diff the OBSERVED cloud tags against the
40 * desired set and apply only the delta.
41 */
42export const syncB2biTags = Effect.fn(function* (
43 arn: string,
44 desiredTags: Record<string, string>,
45) {
46 const observedTags = yield* readB2biTags(arn);
47 const { removed, upsert } = diffTags(observedTags, desiredTags);
48 if (upsert.length > 0) {
49 yield* b2bi.tagResource({ ResourceARN: arn, Tags: upsert });
50 }
51 if (removed.length > 0) {
52 yield* b2bi.untagResource({ ResourceARN: arn, TagKeys: removed });
53 }
54});