1import type * as s3files from "@distilled.cloud/aws/s3files";
2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";
4import * as Schedule from "effect/Schedule";
5
6/**
7 * Raised when an S3 Files file system or access point fails to settle into
8 * `available` within the bounded polling budget, or when the API returns a
9 * structurally incomplete description.
10 */
11export class S3FilesNotConverged extends Data.TaggedError(
12 "S3FilesNotConverged",
13)<{
14 readonly resource: string;
15 readonly status: string | undefined;
16}> {}
17
18/**
19 * Bounded retry through transient `ConflictException` states — e.g. deleting
20 * a file system or access point whose previous lifecycle transition is still
21 * settling.
22 *
23 * Expressed as an explicitly-typed module-scope helper: inlining
24 * `Effect.retry` in lifecycle code leaves its conditional return type
25 * unresolved in the provider's declaration emit, which widens the
26 * `AWS.providers()` layer type for every downstream consumer.
27 */
28export const retryWhileConflict = <A, E extends { readonly _tag: string }, R>(
29 self: Effect.Effect<A, E, R>,
30): Effect.Effect<A, E, R> =>
31 Effect.retry(self, {
32 while: (e) => e._tag === "ConflictException",
33 schedule: Schedule.max([Schedule.fixed("3 seconds"), Schedule.recurs(10)]),
34 });
35
36/**
37 * Repeat an observe poll until `done` holds (bounded — file systems and
38 * access points typically become `available` within seconds). Explicitly
39 * typed for the declaration-emit reason above.
40 */
41export const untilSettled = <A, E, R>(
42 self: Effect.Effect<A, E, R>,
43 done: (a: A) => boolean,
44): Effect.Effect<A, E, R> =>
45 Effect.repeat(self, {
46 schedule: Schedule.spaced("3 seconds"),
47 until: done,
48 times: 30,
49 });
50
51/**
52 * Convert the wire tag list (`[{ key, value }]`) into a plain record for
53 * diffing with `diffTags`.
54 */
55export const toTagRecord = (
56 tags: readonly s3files.Tag[] | undefined,
57): Record<string, string> => {
58 const out: Record<string, string> = {};
59 for (const tag of tags ?? []) {
60 out[tag.key] = tag.value;
61 }
62 return out;
63};
64
65/**
66 * Convert a plain tag record into the wire tag list shape.
67 */
68export const toTagList = (tags: Record<string, string>): s3files.Tag[] =>
69 Object.entries(tags).map(([key, value]) => ({ key, value }));