1import * as r53r from "@distilled.cloud/aws/route53resolver";
2import * as Effect from "effect/Effect";
3import * as Stream from "effect/Stream";
4import { diffTags } from "../../Tags.ts";
5
6/**
7 * Fetch the observed tags on a Route 53 Resolver resource as a plain record.
8 * Any failure (e.g. the resource vanished between observation and the tag
9 * read, or the resource is an AWS-managed rule we cannot read tags for)
10 * degrades to an empty record so callers can still converge.
11 *
12 * @internal
13 */
14export const fetchResolverTags = (arn: string) =>
15 r53r.listTagsForResource.items({ ResourceArn: arn }).pipe(
16 Stream.runCollect,
17 Effect.map(
18 (chunk) =>
19 Object.fromEntries(
20 Array.from(chunk).map((tag) => [tag.Key, tag.Value]),
21 ) as Record<string, string>,
22 ),
23 Effect.catch(() => Effect.succeed({} as Record<string, string>)),
24 );
25
26/**
27 * Converge the tags on a Route 53 Resolver resource to `desired`, diffing
28 * against the OBSERVED cloud tags (never olds/output) so adoption converges.
29 *
30 * @internal
31 */
32export const syncResolverTags = Effect.fn(function* (
33 arn: string,
34 desired: Record<string, string>,
35) {
36 const observed = yield* fetchResolverTags(arn);
37 const { upsert, removed } = diffTags(observed, desired);
38 if (upsert.length > 0) {
39 yield* r53r.tagResource({ ResourceArn: arn, Tags: upsert });
40 }
41 if (removed.length > 0) {
42 yield* r53r.untagResource({ ResourceArn: arn, TagKeys: removed });
43 }
44});
45
46/**
47 * Convert a tag record into the wire `Tag[]` shape for create calls.
48 *
49 * @internal
50 */
51export const toResolverTagList = (tags: Record<string, string>): r53r.Tag[] =>
52 Object.entries(tags).map(([Key, Value]) => ({ Key, Value }));
53
54/**
55 * Order-insensitive string set equality.
56 *
57 * @internal
58 */
59export const sameStringSet = (
60 a: readonly string[],
61 b: readonly string[],
62): boolean => {
63 if (a.length !== b.length) return false;
64 const sortedA = [...a].sort();
65 const sortedB = [...b].sort();
66 return sortedA.every((value, i) => value === sortedB[i]);
67};