import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as S from "effect/Schema"; import { type AnyBinding, type Bind } from "./binding.ts"; import type { Capability } from "./capability.ts"; import type { IRuntime, Runtime } from "./runtime.ts"; /** * A Policy binds a set of Capbilities (e.g SQS.SendMessage, SQS.Consume, etc.) to a * specific Runtime (e.g. AWS Lambda Function, Cloudflare Worker, etc.). * * It brings with it a set of upstream Tags containing the required Provider services * to deploy the infrastructure, e.g. (BindingTag>) * * A Policy is invariant over the set of Capabilities to ensure least-privilege. */ export interface Policy< F extends IRuntime, in out Capabilities, Tags = unknown, > { readonly kind: "alchemy/Policy"; readonly runtime: F; readonly tags: Tags[]; readonly capabilities: Capabilities[]; // phantom property (exists at runtime but not in types) // readonly bindings: AnyBinding[]; /** Add more Capabilities to a Policy */ and( ...bindings: B ): Policy< F, B[number]["capability"] | Capabilities, BindingTags | Exclude >; } type BindingTags = B extends any ? Bind> : never; export function Policy(): Policy; export function Policy( ...capabilities: B ): Policy< B[number]["runtime"], B[number]["capability"], BindingTags >; export function Policy(...bindings: AnyBinding[]): any { return { runtime: bindings[0]?.["runtime"], capabilities: bindings.map((b) => b.capability), tags: bindings.map((b) => Context.Tag(b.tag as any)()), bindings, and: (...b2: AnyBinding[]) => Policy(...bindings, ...b2), } as Policy & { // add the phantom property bindings: AnyBinding[]; }; } export namespace Policy { export interface AnyOf { readonly anyOf: T[]; } type Generalize = T extends S.Schema ? U : T; export const anyOf = (...anyOf: T[]): AnyOf> => ({ anyOf: anyOf as Generalize[], }); export const join = < const Strings extends readonly string[], const Delimiter extends string, >( strings: Strings, delimiter: Delimiter, ) => strings.join(delimiter) as Join; type ___ = Join; type Join< T extends readonly string[], Delimiter extends string, > = T extends readonly [infer First extends string] ? First : T extends readonly [ infer First extends string, ...infer Rest extends readonly string[], ] ? `${First}${Delimiter}${Join}` : T extends string[] ? string : ""; export type Constraint = Pick< T, { [k in keyof T]: T[k] extends never ? never : T[k] extends AnyOf ? never : k; }[keyof T] >; // TODO(sam): one day we might infer policies using a compiler plugin, this is a placeholder export const infer = (): T => undefined!; } /** declare a Policy requiring Capabilities in some context */ export const declare = () => Effect.gen(function* () {}) as Effect.Effect; export type Instance = T extends { id: string } ? string extends T["id"] ? T : T extends new (...args: any) => infer I ? I : never : never; // syntactic sugar for mapping `typeof Messages` -> Messages, e.g. so it's SQS.SendMessage instead of SQS.SendMessage // e.g. (queue: Q) => SQS.SendMessage> export type From = Instance; export type To = Instance; export type In = Instance; export type Into = Instance; export type On = Instance;