1import * as paymentcryptography from "@distilled.cloud/aws/payment-cryptography";
2import * as Effect from "effect/Effect";
3import * as Stream from "effect/Stream";
4import { isResolved } from "../../Diff.ts";
5import { createPhysicalName } from "../../PhysicalName.ts";
6import * as Provider from "../../Provider.ts";
7import { Resource } from "../../Resource.ts";
8import type { Providers } from "../Providers.ts";
9
10export interface AliasProps {
11 /**
12 * The alias name. Must begin with `alias/` and may contain alphanumeric
13 * characters, forward slashes, underscores and hyphens. Changing it
14 * replaces the alias.
15 * @default alias/${app}-${stage}-${id}
16 */
17 aliasName?: string;
18 /**
19 * The ARN of the key the alias points to. Omit to create an unattached
20 * alias; set later to point it at a key. Updated in place.
21 */
22 keyArn?: string;
23}
24
25export interface Alias extends Resource<
26 "AWS.PaymentCryptography.Alias",
27 AliasProps,
28 {
29 /**
30 * Name of the alias (starts with `alias/`).
31 */
32 aliasName: string;
33 /**
34 * ARN of the key the alias points to, if attached.
35 */
36 keyArn: string | undefined;
37 },
38 never,
39 Providers
40> {}
41
42/**
43 * A friendly name for an AWS Payment Cryptography {@link Key}. Aliases give
44 * keys a stable, human-readable identifier that survives key rotation — the
45 * alias can be repointed to a new key without touching consumers.
46 * ### Creating Aliases
47 * **Example:** Alias attached to a key
48 * ```typescript
49 * import * as PaymentCryptography from "alchemy/AWS/PaymentCryptography";
50 *
51 * const key = yield* PaymentCryptography.Key("DataKey", { keyAttributes: { ... } });
52 * const alias = yield* PaymentCryptography.Alias("DataKeyAlias", {
53 * keyArn: key.keyArn,
54 * });
55 * ```
56 *
57 * **Example:** Alias with an explicit name
58 * ```typescript
59 * const alias = yield* PaymentCryptography.Alias("DataKeyAlias", {
60 * aliasName: "alias/payments/data-encryption",
61 * keyArn: key.keyArn,
62 * });
63 * ```
64 *
65 * @resource
66 */
67export const Alias = Resource<Alias>("AWS.PaymentCryptography.Alias");
68
69const toAttrs = (alias: paymentcryptography.Alias): Alias["Attributes"] => ({
70 aliasName: alias.AliasName,
71 keyArn: alias.KeyArn,
72});
73
74export const AliasProvider = () =>
75 Provider.effect(
76 Alias,
77 Effect.gen(function* () {
78 const createAliasName = Effect.fn(function* (
79 id: string,
80 props: AliasProps,
81 ) {
82 if (props.aliasName) {
83 return props.aliasName;
84 }
85 const baseName = yield* createPhysicalName({
86 id,
87 maxLength: 256 - "alias/".length,
88 });
89 return `alias/${baseName}`;
90 });
91
92 const observeAlias = Effect.fn(function* (aliasName: string) {
93 return yield* paymentcryptography
94 .getAlias({ AliasName: aliasName })
95 .pipe(
96 Effect.map((r) => r.Alias),
97 Effect.catchTag("ResourceNotFoundException", () =>
98 Effect.succeed(undefined),
99 ),
100 );
101 });
102
103 return Alias.Provider.of({
104 stables: ["aliasName"],
105 list: () =>
106 paymentcryptography.listAliases.items({}).pipe(
107 Stream.runCollect,
108 Effect.map((chunk) => Array.from(chunk).map(toAttrs)),
109 ),
110 read: Effect.fn(function* ({ id, olds, output }) {
111 const aliasName =
112 output?.aliasName ?? (yield* createAliasName(id, olds ?? {}));
113 const alias = yield* observeAlias(aliasName);
114 if (alias === undefined) return undefined;
115 return toAttrs(alias);
116 }),
117 diff: Effect.fn(function* ({ id, news, olds }) {
118 if (!isResolved(news)) return undefined;
119 const oldName = yield* createAliasName(id, olds);
120 const newName = yield* createAliasName(id, news);
121 if (oldName !== newName) {
122 return { action: "replace" } as const;
123 }
124 // fall through: undefined → default update (keyArn repoint)
125 }),
126 reconcile: Effect.fn(function* ({ id, news, output, session }) {
127 const aliasName =
128 output?.aliasName ?? (yield* createAliasName(id, news));
129
130 // 1. Observe — cloud state is authoritative.
131 let alias = yield* observeAlias(aliasName);
132
133 // 2. Ensure — create when missing; a concurrent create surfaces as
134 // ConflictException, which resolves by re-observing.
135 if (alias === undefined) {
136 alias = yield* paymentcryptography
137 .createAlias({ AliasName: aliasName, KeyArn: news.keyArn })
138 .pipe(
139 Effect.map((r) => r.Alias),
140 Effect.catchTag("ConflictException", () =>
141 paymentcryptography
142 .getAlias({ AliasName: aliasName })
143 .pipe(Effect.map((r) => r.Alias)),
144 ),
145 );
146 }
147
148 // 3. Sync — repoint the alias when the observed target differs
149 // from the desired one (including detaching).
150 if (alias.KeyArn !== news.keyArn) {
151 alias = yield* paymentcryptography
152 .updateAlias({ AliasName: aliasName, KeyArn: news.keyArn })
153 .pipe(Effect.map((r) => r.Alias));
154 }
155
156 yield* session.note(aliasName);
157 return toAttrs(alias);
158 }),
159 delete: Effect.fn(function* ({ output }) {
160 yield* paymentcryptography
161 .deleteAlias({ AliasName: output.aliasName })
162 .pipe(
163 Effect.catchTag("ResourceNotFoundException", () => Effect.void),
164 );
165 }),
166 });
167 }),
168 );