import type { EmailAddress as CloudflareEmailAddress, EmailAttachment as CloudflareEmailAttachment, EmailMessage as CloudflareEmailMessage, EmailSendResult as CloudflareEmailSendResult, SendEmail as CloudflareSendEmail, } from "@cloudflare/workers-types"; import { Context, Data, Effect, type Layer } from "effect"; import * as Binding from "./Binding"; import type { WorkerEnvironment } from "./Environment"; const expectedSendEmailBinding = "Send Email binding with send()"; /** Error raised when a Cloudflare Send Email operation fails. */ export class EmailOperationError extends Data.TaggedError("EmailOperationError")<{ readonly binding: string; readonly operation: string; readonly cause: unknown; }> {} /** Typed Cloudflare Send Email binding definition. */ export interface EmailDefinition { /** Binding name as configured in `wrangler.jsonc`. */ readonly binding: string; } export type EmailAddress = CloudflareEmailAddress; export type EmailAttachment = CloudflareEmailAttachment; export type EmailMessage = CloudflareEmailMessage; export type EmailMessageBuilder = Parameters[0]; export type EmailSendInput = EmailMessage | EmailMessageBuilder; export type EmailSendResult = CloudflareEmailSendResult; export type EmailBinding = CloudflareSendEmail; interface EmailRuntimeBinding { readonly send: (message: EmailSendInput) => Promise; } export interface EmailClient { readonly send: { (message: EmailMessage): Effect.Effect; (builder: EmailMessageBuilder): Effect.Effect; }; readonly unsafeRaw: Effect.Effect; readonly definition: EmailDefinition; } declare const EmailServiceTypeId: unique symbol; /** Nominal service marker for Send Email services created with {@link make}. */ export interface EmailService { readonly [EmailServiceTypeId]: { readonly id: Id; }; } export type LayerOptions = { readonly binding: string; }; export interface TagClass extends Context.ServiceClass< Self, Id, EmailClient > { readonly id: Id; readonly layer: ( options: LayerOptions, ) => Layer.Layer< Self, Binding.BindingNotFoundError | Binding.BindingValidationError, WorkerEnvironment >; } const emailError = (binding: string, operation: string, cause: unknown) => new EmailOperationError({ binding, operation, cause }); const tryEmailPromise = ( binding: string, operation: string, evaluate: () => Promise, ): Effect.Effect => Effect.tryPromise({ try: evaluate, catch: (cause) => emailError(binding, operation, cause), }); export const isEmailBinding = (value: unknown): value is EmailBinding => { if (typeof value !== "object" || value === null) { return false; } const resource = value as Record; return typeof resource.send === "function"; }; export const makeClient = (definition: EmailDefinition) => (email: EmailBinding): EmailClient => { const runtime = email as EmailRuntimeBinding; const send = ((message: EmailSendInput) => tryEmailPromise(definition.binding, "send", () => runtime.send(message), )) as EmailClient["send"]; return { definition, send, unsafeRaw: Effect.succeed(email), }; }; export const layer = (tag: Context.Service, definition: EmailDefinition) => Binding.layer(tag, definition.binding, isEmailBinding, makeClient(definition), { expected: expectedSendEmailBinding, }); export const make = (id: Id) => Tag>()(id); export const Tag = () => (id: Id) => { const tag = Context.Service()(id); const makeLayer = (definition: LayerOptions) => layer(tag, definition); return Object.assign(tag, { id, layer: makeLayer, }) as TagClass; };