effect-cf 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/Email.ts ADDED
@@ -0,0 +1,134 @@
1
+ import type {
2
+ EmailAddress as CloudflareEmailAddress,
3
+ EmailAttachment as CloudflareEmailAttachment,
4
+ EmailMessage as CloudflareEmailMessage,
5
+ EmailSendResult as CloudflareEmailSendResult,
6
+ SendEmail as CloudflareSendEmail,
7
+ } from "@cloudflare/workers-types";
8
+ import { Context, Data, Effect, type Layer } from "effect";
9
+
10
+ import * as Binding from "./Binding";
11
+ import type { WorkerEnvironment } from "./Environment";
12
+
13
+ const expectedSendEmailBinding = "Send Email binding with send()";
14
+
15
+ /** Error raised when a Cloudflare Send Email operation fails. */
16
+ export class EmailOperationError extends Data.TaggedError("EmailOperationError")<{
17
+ readonly binding: string;
18
+ readonly operation: string;
19
+ readonly cause: unknown;
20
+ }> {}
21
+
22
+ /** Typed Cloudflare Send Email binding definition. */
23
+ export interface EmailDefinition {
24
+ /** Binding name as configured in `wrangler.jsonc`. */
25
+ readonly binding: string;
26
+ }
27
+
28
+ export type EmailAddress = CloudflareEmailAddress;
29
+ export type EmailAttachment = CloudflareEmailAttachment;
30
+ export type EmailMessage = CloudflareEmailMessage;
31
+ export type EmailMessageBuilder = Parameters<CloudflareSendEmail["send"]>[0];
32
+ export type EmailSendInput = EmailMessage | EmailMessageBuilder;
33
+ export type EmailSendResult = CloudflareEmailSendResult;
34
+ export type EmailBinding = CloudflareSendEmail;
35
+
36
+ interface EmailRuntimeBinding {
37
+ readonly send: (message: EmailSendInput) => Promise<EmailSendResult>;
38
+ }
39
+
40
+ export interface EmailClient {
41
+ readonly send: {
42
+ (message: EmailMessage): Effect.Effect<EmailSendResult, EmailOperationError>;
43
+ (builder: EmailMessageBuilder): Effect.Effect<EmailSendResult, EmailOperationError>;
44
+ };
45
+ readonly unsafeRaw: Effect.Effect<EmailBinding>;
46
+ readonly definition: EmailDefinition;
47
+ }
48
+
49
+ declare const EmailServiceTypeId: unique symbol;
50
+
51
+ /** Nominal service marker for Send Email services created with {@link make}. */
52
+ export interface EmailService<Id extends string> {
53
+ readonly [EmailServiceTypeId]: {
54
+ readonly id: Id;
55
+ };
56
+ }
57
+
58
+ export type LayerOptions = {
59
+ readonly binding: string;
60
+ };
61
+
62
+ export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
63
+ Self,
64
+ Id,
65
+ EmailClient
66
+ > {
67
+ readonly id: Id;
68
+ readonly layer: (
69
+ options: LayerOptions,
70
+ ) => Layer.Layer<
71
+ Self,
72
+ Binding.BindingNotFoundError | Binding.BindingValidationError,
73
+ WorkerEnvironment
74
+ >;
75
+ }
76
+
77
+ const emailError = (binding: string, operation: string, cause: unknown) =>
78
+ new EmailOperationError({ binding, operation, cause });
79
+
80
+ const tryEmailPromise = <A>(
81
+ binding: string,
82
+ operation: string,
83
+ evaluate: () => Promise<A>,
84
+ ): Effect.Effect<A, EmailOperationError> =>
85
+ Effect.tryPromise({
86
+ try: evaluate,
87
+ catch: (cause) => emailError(binding, operation, cause),
88
+ });
89
+
90
+ export const isEmailBinding = (value: unknown): value is EmailBinding => {
91
+ if (typeof value !== "object" || value === null) {
92
+ return false;
93
+ }
94
+
95
+ const resource = value as Record<string, unknown>;
96
+
97
+ return typeof resource.send === "function";
98
+ };
99
+
100
+ export const makeClient =
101
+ (definition: EmailDefinition) =>
102
+ (email: EmailBinding): EmailClient => {
103
+ const runtime = email as EmailRuntimeBinding;
104
+ const send = ((message: EmailSendInput) =>
105
+ tryEmailPromise(definition.binding, "send", () =>
106
+ runtime.send(message),
107
+ )) as EmailClient["send"];
108
+
109
+ return {
110
+ definition,
111
+ send,
112
+ unsafeRaw: Effect.succeed(email),
113
+ };
114
+ };
115
+
116
+ export const layer = <Self>(tag: Context.Service<Self, EmailClient>, definition: EmailDefinition) =>
117
+ Binding.layer(tag, definition.binding, isEmailBinding, makeClient(definition), {
118
+ expected: expectedSendEmailBinding,
119
+ });
120
+
121
+ export const make = <Id extends string>(id: Id) => Tag<EmailService<Id>>()<Id>(id);
122
+
123
+ export const Tag =
124
+ <Self>() =>
125
+ <Id extends string>(id: Id) => {
126
+ const tag = Context.Service<Self, EmailClient>()(id);
127
+
128
+ const makeLayer = (definition: LayerOptions) => layer(tag, definition);
129
+
130
+ return Object.assign(tag, {
131
+ id,
132
+ layer: makeLayer,
133
+ }) as TagClass<Self, Id>;
134
+ };
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export * as DurableObjectSqlite from "./DurableObjectSqlite";
12
12
  export * as DurableObjectState from "./DurableObjectState";
13
13
  export * as DurableObjectWebSocket from "./DurableObjectWebSocket";
14
14
  export * as DurableObjectStorage from "./DurableObjectStorage";
15
+ export * as Email from "./Email";
15
16
  export * as Hyperdrive from "./Hyperdrive";
16
17
  export * as Images from "./Images";
17
18
  export * as Kv from "./Kv";