effect-cf 0.18.0 → 0.20.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # effect-cf
2
2
 
3
+ ## 0.20.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#67](https://github.com/danieljvdm/effect-cf/pull/67) [`d8e1d64`](https://github.com/danieljvdm/effect-cf/commit/d8e1d64e8117fd8b86fabef6a61de01251557d2b) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Route `Worker.make` fetch handlers through `HttpEffect.toHandled`, matching the first-party Effect HTTP adapters.
8
+
9
+ - Pre-response handlers (`HttpEffect.appendPreResponseHandler`, and things built on it such as `HttpApiBuilder.securitySetCookie`) now run before `HttpServerResponse` results are rendered. Previously they were silently dropped, which broke cookie-based auth flows on Workers.
10
+ - Streaming response bodies keep request-scoped resources alive until the stream completes, instead of finalizing them as soon as the `Response` is returned.
11
+ - `HttpServerResponse` bodies are suppressed for `HEAD` requests.
12
+ - Handler failures are now rendered as HTTP error responses (with the cause reported) instead of rejecting the `fetch` promise. Note that exceptions no longer escape `fetch`, so `passThroughOnException` will not trigger origin fallback for Effect-level failures.
13
+ - Native `Response` values returned from a fetch handler continue to bypass all response processing, including pre-response handlers — WebSocket upgrade responses and app-level `HttpEffect.toHandled` wrappers pass through untouched.
14
+
15
+ ## 0.20.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#64](https://github.com/danieljvdm/effect-cf/pull/64) [`d333039`](https://github.com/danieljvdm/effect-cf/commit/d33303949ac38d2a2e942954d0edbfeeebc16b81) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Upgrade Effect to `4.0.0-beta.105`. The `effect`, `@effect/sql-d1`, `@effect/sql-pg`, and `@effect/sql-sqlite-do` peer ranges now require `^4.0.0-beta.105`, so upgrade Effect alongside this release.
20
+
21
+ `CloudflareOtlp` resource precedence has flipped to match Effect's `OtlpResource.fromConfig`. Explicit `resource.serviceName` and `resource.serviceVersion` now take precedence over `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`, and `OTEL_RESOURCE_ATTRIBUTES`; previously the environment won. Omit an option to keep letting operators set it from the environment.
22
+
23
+ ## 0.19.0
24
+
25
+ ### Minor Changes
26
+
27
+ - [#61](https://github.com/danieljvdm/effect-cf/pull/61) [`7aa75de`](https://github.com/danieljvdm/effect-cf/commit/7aa75de93408b1eb345ab530f852db9038923f3d) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Add Cloudflare Email Service sending support to `Email`.
28
+
29
+ Structured messages sent through a `send_email` binding are now validated against documented Email Sending limits — combined `to`/`cc`/`bcc` recipients, attachment count, custom header sizes, and required fields — and fail with `EmailValidationError` before the binding is called. Opt out with `layer({ binding, send: { validate: false } })`. Raw RFC 5322 `EmailMessage` values are still passed through untouched, so the legacy `cloudflare:email` API keeps working.
30
+
31
+ Cloudflare's `E_*` error codes are now reported on `EmailOperationError.code`, with the documented set exported as `Email.emailErrorCodes` and `Email.EmailErrorCode`. Documented limits are exported as `Email.sendLimits`.
32
+
3
33
  ## 0.18.0
4
34
 
5
35
  ### Minor Changes
package/README.md CHANGED
@@ -37,7 +37,7 @@ Runtime creation belongs at Cloudflare entrypoints, not inside binding helpers.
37
37
  - `R2` - typed R2 bucket binding helper with Effect-wrapped object and multipart operations
38
38
  - `Hyperdrive` - typed Hyperdrive binding helper for connection strings and optional Postgres SQL integration
39
39
  - `Images` - typed Cloudflare Images binding helper with transformation APIs and optional hosted image operations
40
- - `Email` - typed Cloudflare Send Email binding helper for `send_email` bindings
40
+ - `Email` - typed Cloudflare Email Service binding helper for `send_email` bindings, with limit validation and typed error codes
41
41
  - `AnalyticsEngine` - typed Cloudflare Analytics Engine write bindings and SQL API query helpers
42
42
  - `Queue` - typed Queue producer/consumer tags plus client and error types
43
43
  - `Workflow` - typed Workflow entrypoints, steps, starter clients, and instance types
@@ -284,7 +284,7 @@ export const resizeAvatar = (image: Images.ImageInputValue) =>
284
284
 
285
285
  ## Email Example
286
286
 
287
- Email tags expose Cloudflare Send Email bindings as Effect-wrapped `send(...)` operations.
287
+ Email tags expose Cloudflare Email Service `send_email` bindings as Effect-wrapped `send(...)` operations. Structured messages are validated against documented Email Sending limits before reaching the binding, and Cloudflare's `E_*` error codes are surfaced on `EmailOperationError.code`.
288
288
 
289
289
  ```ts
290
290
  import { Effect } from "effect";
@@ -308,6 +308,20 @@ export const sendWelcomeEmail = (to: string) =>
308
308
  });
309
309
  ```
310
310
 
311
+ Onboard the sending domain under **Compute > Email Service > Email Sending**, then declare the binding in the consuming Worker's `wrangler.jsonc`:
312
+
313
+ ```jsonc
314
+ {
315
+ "send_email": [{ "name": "EMAIL" }],
316
+ }
317
+ ```
318
+
319
+ Messages that violate a documented limit fail with `EmailValidationError` carrying every violation, without calling the binding. Use `layer({ binding, send: { validate: false } })` to skip validation and let Cloudflare reject the message instead. Raw RFC 5322 `EmailMessage` values are always passed through untouched, so the legacy `cloudflare:email` API keeps working.
320
+
321
+ Cloudflare's total message size limit is exposed as `Email.sendLimits.maxMessageBytes` but is not enforced, because the encoded MIME size is only known once Cloudflare composes the message. Oversized messages fail at the binding with `E_CONTENT_TOO_LARGE`.
322
+
323
+ See [`examples/email/README.md`](../../examples/email/README.md) for domain onboarding, binding restrictions, and typed recovery from `EmailValidationError` and `EmailOperationError`.
324
+
311
325
  ## Analytics Engine Example
312
326
 
313
327
  Analytics Engine tags expose Cloudflare dataset bindings as Effect-wrapped `writeDataPoint(...)` operations. Writes use Cloudflare's native non-blocking runtime API and are validated against Workers Analytics Engine limits before reaching the binding.
@@ -1,9 +1,4 @@
1
1
  import { Config, ConfigProvider, Context, Effect, Layer } from "effect";
2
- import * as _$effect_Types0 from "effect/Types";
3
- import * as _$effect_Cause0 from "effect/Cause";
4
- import * as _$effect_Redacted0 from "effect/Redacted";
5
- import * as _$effect_Layer0 from "effect/Layer";
6
-
7
2
  //#region src/Environment.d.ts
8
3
  /** Cloudflare worker environment object (`env`). */
9
4
  type WorkerEnv = Cloudflare.Env;
@@ -13,7 +8,7 @@ declare const WorkerEnvironment_base: Context.ServiceClass<WorkerEnvironment, "e
13
8
  */
14
9
  declare class WorkerEnvironment extends WorkerEnvironment_base {}
15
10
  type ScalarConfigValue = string | number | boolean;
16
- type ScalarConfigKey = Extract<{ readonly [Key in keyof Cloudflare.Env]-?: NonNullable<Cloudflare.Env[Key]> extends ScalarConfigValue ? Key : never }[keyof Cloudflare.Env], string>;
11
+ type ScalarConfigKey = Extract<{ readonly [Key in keyof Cloudflare.Env]-?: NonNullable<Cloudflare.Env[Key]> extends ScalarConfigValue ? Key : never; }[keyof Cloudflare.Env], string>;
17
12
  /**
18
13
  * Effect `Config` helpers for scalar Cloudflare vars and secrets declared on
19
14
  * `Cloudflare.Env`.
@@ -49,7 +44,7 @@ declare namespace WorkerConfig {
49
44
  /** Read a scalar Cloudflare var or secret as a string. */
50
45
  const string: <const Name extends Key>(name: Name) => Config.Config<string>;
51
46
  /** Read a scalar Cloudflare secret as a redacted string. */
52
- const redacted: <const Name extends Key>(name: Name) => Config.Config<_$effect_Redacted0.Redacted<string>>;
47
+ const redacted: <const Name extends Key>(name: Name) => Config.Config<import("effect/Redacted").Redacted<string>>;
53
48
  /** Read a scalar Cloudflare var or secret as a number. */
54
49
  const number: <const Name extends Key>(name: Name) => Config.Config<number>;
55
50
  /** Read a scalar Cloudflare var or secret as an integer. */
@@ -69,18 +64,18 @@ declare namespace WorkerConfig {
69
64
  * Replace the active Effect `ConfigProvider` with one backed by the current
70
65
  * Cloudflare worker `env` object.
71
66
  */
72
- const providerLayer: _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
67
+ const providerLayer: import("effect/Layer").Layer<never, never, WorkerEnvironment>;
73
68
  /** Alias for `providerLayer` for concise use in worker layers. */
74
- const layer: _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
69
+ const layer: import("effect/Layer").Layer<never, never, WorkerEnvironment>;
75
70
  /** Build a `ConfigProvider` layer with a custom `env` conversion function. */
76
- const layerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
71
+ const layerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => import("effect/Layer").Layer<never, never, WorkerEnvironment>;
77
72
  }
78
73
  declare namespace Binding_d_exports {
79
74
  export { BindingNotFoundError, BindingService, BindingValidationError, Service, TypeId, ValidationOptions, layer$1 as layer };
80
75
  }
81
76
  /** Internal type id marker used by binding helper services. */
82
77
  declare const TypeId: "effect-cf/Binding";
83
- declare const BindingNotFoundError_base: new <A extends Record<string, any> = {}>(args: _$effect_Types0.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => _$effect_Cause0.YieldableError & {
78
+ declare const BindingNotFoundError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
84
79
  readonly _tag: "BindingNotFoundError";
85
80
  } & Readonly<A>;
86
81
  /** Error raised when a configured binding does not exist on `env`. */
@@ -88,7 +83,7 @@ declare class BindingNotFoundError extends BindingNotFoundError_base<{
88
83
  readonly binding: string;
89
84
  readonly message: string;
90
85
  }> {}
91
- declare const BindingValidationError_base: new <A extends Record<string, any> = {}>(args: _$effect_Types0.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => _$effect_Cause0.YieldableError & {
86
+ declare const BindingValidationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
92
87
  readonly _tag: "BindingValidationError";
93
88
  } & Readonly<A>;
94
89
  /** Error raised when a binding exists but does not match the expected shape. */
@@ -1,14 +1,10 @@
1
- import { i as BindingNotFoundError, n as LayerOptions, o as BindingValidationError, r as TagClass, u as WorkerEnvironment } from "./Hyperdrive-C0vfrPeG.mjs";
1
+ import { i as BindingNotFoundError, n as LayerOptions, o as BindingValidationError, r as TagClass, u as WorkerEnvironment } from "./Hyperdrive-RTjgirMV.mjs";
2
2
  import { Layer } from "effect";
3
3
  import { PgClient } from "@effect/sql-pg";
4
- import * as _$effect_unstable_sql_SqlClient0 from "effect/unstable/sql/SqlClient";
5
- import * as _$effect_unstable_sql_SqlError0 from "effect/unstable/sql/SqlError";
6
- import * as _$effect_Scope0 from "effect/Scope";
7
-
8
4
  //#region src/HyperdrivePg.d.ts
9
5
  type PgLayerOptions = Omit<PgClient.PgClientConfig & {
10
6
  readonly acquireForStream?: boolean;
11
7
  }, "url" | "host" | "port" | "path" | "ssl" | "database" | "username" | "password" | "stream">;
12
- declare const layer: <Self, Id extends string>(tag: TagClass<Self, Id>, binding: LayerOptions, options?: PgLayerOptions) => Layer.Layer<PgClient.PgClient | _$effect_unstable_sql_SqlClient0.SqlClient, BindingNotFoundError | BindingValidationError | _$effect_unstable_sql_SqlError0.SqlError, WorkerEnvironment | Exclude<Exclude<Self, _$effect_Scope0.Scope>, Self>>;
8
+ declare const layer: <Self, Id extends string>(tag: TagClass<Self, Id>, binding: LayerOptions, options?: PgLayerOptions) => Layer.Layer<PgClient.PgClient | import("effect/unstable/sql/SqlClient").SqlClient, BindingNotFoundError | BindingValidationError | import("effect/unstable/sql/SqlError").SqlError, WorkerEnvironment | Exclude<Exclude<Self, import("effect/Scope").Scope>, Self>>;
13
9
  //#endregion
14
10
  export { PgLayerOptions, layer };
@@ -1,4 +1,3 @@
1
- import "./Hyperdrive-89hg_Fck.mjs";
2
1
  import { Effect, Layer, Redacted } from "effect";
3
2
  import { PgClient } from "@effect/sql-pg";
4
3
  //#region src/HyperdrivePg.ts