1import * as avp from "@distilled.cloud/aws/verifiedpermissions";
2import * as Effect from "effect/Effect";
3import { isResolved } from "../../Diff.ts";
4import * as Provider from "../../Provider.ts";
5import { Resource } from "../../Resource.ts";
6import type { Providers } from "../Providers.ts";
7
8export interface SchemaProps {
9 /**
10 * The ID of the policy store the schema belongs to. A store has at most one
11 * schema. Changing the store replaces the schema.
12 */
13 policyStoreId: string;
14 /**
15 * The Cedar JSON schema document (as a JSON string). Defines the entity
16 * types, actions, and the relationships between them used to validate
17 * policies when the store's validation mode is `STRICT`. Mutable — updating
18 * re-submits the schema via `PutSchema`.
19 */
20 cedarJson: string;
21}
22
23export interface Schema extends Resource<
24 "AWS.VerifiedPermissions.Schema",
25 SchemaProps,
26 {
27 /**
28 * ID of the policy store the schema belongs to.
29 */
30 policyStoreId: string;
31 },
32 {},
33 Providers
34> {}
35
36/**
37 * The Cedar schema for a Verified Permissions policy store. The schema
38 * declares the entity types and actions your policies reference; with
39 * `validationMode: "STRICT"` on the store, policies and templates are
40 * validated against it at submission time.
41 *
42 * A policy store has at most one schema — `PutSchema` is an upsert that fully
43 * replaces the previous schema.
44 * ### Defining a Schema
45 * **Example:** Photo App Schema
46 * ```typescript
47 * import * as AWS from "alchemy/AWS";
48 *
49 * const store = yield* AWS.VerifiedPermissions.PolicyStore("Store", {
50 * validationMode: "STRICT",
51 * });
52 *
53 * yield* AWS.VerifiedPermissions.Schema("Schema", {
54 * policyStoreId: store.policyStoreId,
55 * cedarJson: JSON.stringify({
56 * PhotoApp: {
57 * entityTypes: {
58 * User: {},
59 * Photo: {},
60 * },
61 * actions: {
62 * viewPhoto: {
63 * appliesTo: {
64 * principalTypes: ["User"],
65 * resourceTypes: ["Photo"],
66 * },
67 * },
68 * },
69 * },
70 * }),
71 * });
72 * ```
73 *
74 * @resource
75 */
76export const Schema = Resource<Schema>("AWS.VerifiedPermissions.Schema");
77
78export const SchemaProvider = () =>
79 Provider.effect(
80 Schema,
81 Effect.gen(function* () {
82 return Schema.Provider.of({
83 stables: ["policyStoreId"],
84
85 // singleton child of a policy store — not enumerable account-wide
86 list: () => Effect.succeed([]),
87
88 read: Effect.fn(function* ({ olds, output }) {
89 const policyStoreId = output?.policyStoreId ?? olds?.policyStoreId;
90 if (policyStoreId === undefined) return undefined;
91 const schema = yield* avp
92 .getSchema({ policyStoreId })
93 .pipe(
94 Effect.catchTag("ResourceNotFoundException", () =>
95 Effect.succeed(undefined),
96 ),
97 );
98 if (schema === undefined) return undefined;
99 return { policyStoreId };
100 }),
101
102 diff: Effect.fn(function* ({ news, olds }) {
103 if (!isResolved(news)) return undefined;
104 if (olds.policyStoreId !== news.policyStoreId) {
105 return { action: "replace" } as const;
106 }
107 // cedarJson is mutable → default update path
108 }),
109
110 reconcile: Effect.fn(function* ({ news, session }) {
111 // PutSchema is a full upsert — idempotent for both create and update
112 yield* avp.putSchema({
113 policyStoreId: news.policyStoreId,
114 definition: { cedarJson: news.cedarJson },
115 });
116 yield* session.note(news.policyStoreId);
117 return { policyStoreId: news.policyStoreId };
118 }),
119
120 delete: Effect.fn(function* () {
121 // Verified Permissions has no DeleteSchema API — the schema is
122 // removed when the policy store is deleted. Nothing to do.
123 yield* Effect.void;
124 }),
125 });
126 }),
127 );