1import * as securitylake from "@distilled.cloud/aws/securitylake";
2import * as Effect from "effect/Effect";
3import * as Schedule from "effect/Schedule";
4
5/**
6 * Security Lake tags use lowercase `{ key, value }` wire members (unlike most
7 * AWS services' `{ Key, Value }`), so the generic Tags.ts converters don't
8 * apply directly.
9 */
10export const toTagList = (tags: Record<string, string>): securitylake.Tag[] =>
11 Object.entries(tags).map(([key, value]) => ({ key, value }));
12
13export const fromTagList = (
14 tags: readonly securitylake.Tag[] | undefined,
15): Record<string, string> =>
16 Object.fromEntries((tags ?? []).map((tag) => [tag.key, tag.value]));
17
18/** Observed cloud tags for a Security Lake resource ARN ({} on any failure). */
19export const readSecurityLakeTags = (resourceArn: string) =>
20 securitylake.listTagsForResource({ resourceArn }).pipe(
21 Effect.map((response) => fromTagList(response.tags)),
22 Effect.catch(() => Effect.succeed<Record<string, string>>({})),
23 );
24
25/**
26 * Bounded retry through transient `ConflictException`s (e.g. a subscriber or
27 * data-lake mutation racing an in-flight update). Explicitly annotated so the
28 * `Retry.Return` conditional never leaks into declaration emit.
29 */
30export const retryWhileConflict = <A, E extends { readonly _tag: string }, R>(
31 self: Effect.Effect<A, E, R>,
32): Effect.Effect<A, E, R> =>
33 Effect.retry(self, {
34 while: (error) => error._tag === "ConflictException",
35 schedule: Schedule.max([Schedule.fixed("5 seconds"), Schedule.recurs(8)]),
36 });