1import * as avp from "@distilled.cloud/aws/verifiedpermissions";
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 PolicyStoreAliasProps {
11 /**
12 * The ID of the policy store the alias points at. There is no update API
13 * for aliases — changing the store replaces the alias.
14 */
15 policyStoreId: string;
16 /**
17 * Name of the alias. If omitted, a unique name is generated from the app,
18 * stage, and logical ID. Changing the name replaces the alias.
19 */
20 aliasName?: string;
21 /**
22 * How the alias is deleted when the resource is destroyed. `SoftDelete`
23 * keeps the alias name reserved in `PendingDeletion` state for a recovery
24 * window; `HardDelete` frees the name immediately.
25 * @default "HardDelete"
26 */
27 deletionMode?: "SoftDelete" | "HardDelete";
28}
29
30export interface PolicyStoreAlias extends Resource<
31 "AWS.VerifiedPermissions.PolicyStoreAlias",
32 PolicyStoreAliasProps,
33 {
34 /**
35 * Name of the alias — usable in place of a policy store ID in
36 * authorization requests.
37 */
38 aliasName: string;
39 /**
40 * ID of the policy store the alias points at.
41 */
42 policyStoreId: string;
43 /**
44 * ARN of the alias.
45 */
46 aliasArn: string;
47 },
48 {},
49 Providers
50> {}
51
52/**
53 * A named alias for a Verified Permissions policy store. Aliases let callers
54 * reference a policy store by a stable name (e.g. in `IsAuthorized`
55 * requests) so the underlying store can be swapped without reconfiguring
56 * clients.
57 * ### Creating an Alias
58 * **Example:** Alias with a Generated Name
59 * ```typescript
60 * import * as AWS from "alchemy/AWS";
61 *
62 * const store = yield* AWS.VerifiedPermissions.PolicyStore("Store", {});
63 *
64 * const alias = yield* AWS.VerifiedPermissions.PolicyStoreAlias("Alias", {
65 * policyStoreId: store.policyStoreId,
66 * });
67 * ```
68 *
69 * **Example:** Named Alias with Hard Delete
70 * ```typescript
71 * yield* AWS.VerifiedPermissions.PolicyStoreAlias("Alias", {
72 * policyStoreId: store.policyStoreId,
73 * aliasName: "photo-app-prod",
74 * deletionMode: "HardDelete",
75 * });
76 * ```
77 *
78 * @resource
79 */
80export const PolicyStoreAlias = Resource<PolicyStoreAlias>(
81 "AWS.VerifiedPermissions.PolicyStoreAlias",
82);
83
84const toAliasName = (id: string, props: { aliasName?: string } = {}) =>
85 props.aliasName
86 ? Effect.succeed(props.aliasName)
87 : createPhysicalName({ id, maxLength: 64, lowercase: true });
88
89export const PolicyStoreAliasProvider = () =>
90 Provider.effect(
91 PolicyStoreAlias,
92 Effect.gen(function* () {
93 const observe = Effect.fn(function* (aliasName: string) {
94 return yield* avp
95 .getPolicyStoreAlias({ aliasName })
96 .pipe(
97 Effect.catchTag("ResourceNotFoundException", () =>
98 Effect.succeed(undefined),
99 ),
100 );
101 });
102
103 return PolicyStoreAlias.Provider.of({
104 stables: ["aliasName", "policyStoreId", "aliasArn"],
105
106 list: () =>
107 Effect.gen(function* () {
108 const pages = yield* avp.listPolicyStoreAliases
109 .pages({})
110 .pipe(Stream.runCollect);
111 const items = Array.from(pages).flatMap(
112 (page) => page.policyStoreAliases ?? [],
113 );
114 return items.map((item) => ({
115 aliasName: item.aliasName,
116 policyStoreId: item.policyStoreId,
117 aliasArn: item.aliasArn,
118 }));
119 }),
120
121 read: Effect.fn(function* ({ id, olds, output }) {
122 const aliasName =
123 output?.aliasName ??
124 (olds !== undefined ? yield* toAliasName(id, olds) : undefined);
125 if (aliasName === undefined) return undefined;
126 const alias = yield* observe(aliasName);
127 if (alias === undefined || alias.state === "PendingDeletion") {
128 return undefined;
129 }
130 return {
131 aliasName: alias.aliasName,
132 policyStoreId: alias.policyStoreId,
133 aliasArn: alias.aliasArn,
134 };
135 }),
136
137 diff: Effect.fn(function* ({ news, olds }) {
138 if (!isResolved(news)) return undefined;
139 // no UpdatePolicyStoreAlias API — name and target changes replace;
140 // deletionMode only affects delete-time behavior
141 if (
142 olds.policyStoreId !== news.policyStoreId ||
143 (news.aliasName !== undefined && olds.aliasName !== news.aliasName)
144 ) {
145 return { action: "replace" } as const;
146 }
147 }),
148
149 reconcile: Effect.fn(function* ({ id, news, output, session }) {
150 const aliasName = output?.aliasName ?? (yield* toAliasName(id, news));
151
152 // 1. OBSERVE — cloud state is authoritative
153 const existing = yield* observe(aliasName);
154
155 // 2. ENSURE — tolerate the AlreadyExists race
156 if (existing === undefined || existing.state === "PendingDeletion") {
157 const created = yield* avp
158 .createPolicyStoreAlias({
159 aliasName,
160 policyStoreId: news.policyStoreId,
161 })
162 .pipe(
163 Effect.catchTag("ConflictException", () =>
164 Effect.succeed(undefined),
165 ),
166 );
167 if (created !== undefined) {
168 yield* session.note(created.aliasName);
169 return {
170 aliasName: created.aliasName,
171 policyStoreId: created.policyStoreId,
172 aliasArn: created.aliasArn,
173 };
174 }
175 }
176
177 const alias = existing ?? (yield* observe(aliasName));
178 if (alias === undefined) {
179 return yield* Effect.fail(
180 new Error(
181 `policy store alias '${aliasName}' not found after create`,
182 ),
183 );
184 }
185 yield* session.note(alias.aliasName);
186 return {
187 aliasName: alias.aliasName,
188 policyStoreId: alias.policyStoreId,
189 aliasArn: alias.aliasArn,
190 };
191 }),
192
193 delete: Effect.fn(function* ({ olds, output }) {
194 yield* avp
195 .deletePolicyStoreAlias({
196 aliasName: output.aliasName,
197 deletionMode: olds.deletionMode ?? "HardDelete",
198 })
199 .pipe(
200 Effect.catchTag("ResourceNotFoundException", () => Effect.void),
201 );
202 }),
203 });
204 }),
205 );