effect-cf 0.17.1 → 0.20.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +50 -3
  3. package/dist/{Hyperdrive-C0vfrPeG.d.mts → Hyperdrive-RTjgirMV.d.mts} +7 -12
  4. package/dist/HyperdrivePg.d.mts +2 -6
  5. package/dist/HyperdrivePg.mjs +0 -1
  6. package/dist/index.d.mts +223 -118
  7. package/dist/index.mjs +587 -164
  8. package/package.json +8 -9
  9. package/src/AiGateway.ts +3 -0
  10. package/src/AnalyticsEngine.ts +6 -1
  11. package/src/Binding.ts +4 -2
  12. package/src/Cache.ts +104 -0
  13. package/src/CloudflareOtlp.ts +20 -7
  14. package/src/ContainerNamespace.ts +3 -0
  15. package/src/D1.ts +1 -0
  16. package/src/DurableObject.ts +10 -2
  17. package/src/DurableObjectAlarm.ts +19 -0
  18. package/src/DurableObjectDefinition.ts +34 -6
  19. package/src/DurableObjectNamespace.ts +4 -1
  20. package/src/DurableObjectRpcWebSocket.ts +14 -0
  21. package/src/DurableObjectSqlite.ts +1 -0
  22. package/src/DurableObjectStorage.ts +2 -0
  23. package/src/DurableObjectWebSocket.ts +4 -1
  24. package/src/Email.ts +354 -10
  25. package/src/HyperdrivePg.ts +1 -1
  26. package/src/Kv.ts +3 -1
  27. package/src/QueueBinding.ts +2 -1
  28. package/src/QueueDefinition.ts +6 -1
  29. package/src/R2.ts +1 -0
  30. package/src/RpcDefinition.ts +2 -0
  31. package/src/ServiceBinding.ts +6 -1
  32. package/src/Worker.ts +10 -2
  33. package/src/WorkerDefinition.ts +28 -6
  34. package/src/WorkflowBinding.ts +5 -2
  35. package/src/WorkflowDefinition.ts +6 -1
  36. package/src/index.ts +1 -0
  37. package/src/internal/Clock.ts +9 -0
  38. package/src/internal/RpcInvocation.ts +1 -0
  39. package/dist/Hyperdrive-89hg_Fck.mjs +0 -156
  40. /package/dist/{chunk-CfYAbeIz.mjs → rolldown-runtime-D7D4PA-g.mjs} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # effect-cf
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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.
8
+
9
+ `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.
10
+
11
+ ## 0.19.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#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`.
16
+
17
+ 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.
18
+
19
+ 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`.
20
+
21
+ ## 0.18.0
22
+
23
+ ### Minor Changes
24
+
25
+ - [#58](https://github.com/danieljvdm/effect-cf/pull/58) [`c5bac36`](https://github.com/danieljvdm/effect-cf/commit/c5bac367587c314a17b32e984faee546a488f1ff) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Add Effect-native support for Cloudflare's default and named Cache API instances, including typed operation errors and `Option`-based cache misses.
26
+
3
27
  ## 0.17.1
4
28
 
5
29
  ### Patch Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # effect-cf
2
2
 
3
- Effect-native Cloudflare primitives for Workers, Durable Objects, Containers, bindings, KV, D1, Queues, Email, Analytics Engine, Workflows, and Durable Object storage.
3
+ Effect-native Cloudflare primitives for Workers, Durable Objects, Containers, bindings, Cache, KV, D1, Queues, Email, Analytics Engine, Workflows, and Durable Object storage.
4
4
 
5
5
  ## Install
6
6
 
@@ -31,12 +31,13 @@ Runtime creation belongs at Cloudflare entrypoints, not inside binding helpers.
31
31
  - `DurableObjectState` / `DurableObjectStorage` - Effect wrappers for state, alarms, SQL, and embedded KV
32
32
  - `DurableObjectWebSocket` - WebSocket upgrade helpers for Durable Objects
33
33
  - `ContainerNamespace` - named Cloudflare Container instances with Effect-wrapped request and lifecycle operations
34
+ - `Cache` - Effect wrapper for Cloudflare's default and named Cache API instances
34
35
  - `Kv` - typed KV namespace helper
35
36
  - `D1` - typed D1 database binding helper with an `@effect/sql-d1` backed SQL layer
36
37
  - `R2` - typed R2 bucket binding helper with Effect-wrapped object and multipart operations
37
38
  - `Hyperdrive` - typed Hyperdrive binding helper for connection strings and optional Postgres SQL integration
38
39
  - `Images` - typed Cloudflare Images binding helper with transformation APIs and optional hosted image operations
39
- - `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
40
41
  - `AnalyticsEngine` - typed Cloudflare Analytics Engine write bindings and SQL API query helpers
41
42
  - `Queue` - typed Queue producer/consumer tags plus client and error types
42
43
  - `Workflow` - typed Workflow entrypoints, steps, starter clients, and instance types
@@ -161,6 +162,38 @@ Producers should usually use `const queue = yield* AvatarQueue` and then call `q
161
162
 
162
163
  Queue handlers run inline failures through Cloudflare's normal retry path. If background work scheduled with `WorkerContext.waitUntil(...)` should also make the batch retry, use `WorkerContext.waitUntilPropagating(...)` or `waitUntil(..., { mode: "propagate" })`; the default `waitUntil` mode observes and logs failures without rejecting the native `waitUntil` promise.
163
164
 
165
+ ## Cache Example
166
+
167
+ `Cache.layer` exposes Cloudflare's global Cache API as an Effect service. Cache misses are represented by `Option.none()`, and named caches are available through `open(...)`.
168
+
169
+ ```ts
170
+ import { Effect, Option } from "effect";
171
+ import { Cache, Worker } from "effect-cf";
172
+
173
+ export default Worker.make(Cache.layer, {
174
+ fetch: Effect.gen(function* () {
175
+ const request = yield* Worker.NativeRequest;
176
+ const storage = yield* Cache.CacheStorage;
177
+ const cached = yield* storage.default.match(request);
178
+
179
+ if (Option.isSome(cached)) {
180
+ return cached.value;
181
+ }
182
+
183
+ const response = new Response("fresh", {
184
+ headers: { "Cache-Control": "public, max-age=300" },
185
+ });
186
+
187
+ const context = yield* Worker.WorkerContext;
188
+ yield* context.waitUntil(storage.default.put(request, response.clone()));
189
+
190
+ return response;
191
+ }),
192
+ });
193
+ ```
194
+
195
+ Use `const cache = yield* storage.open("api-cache")` for a named cache. `match`, `put`, `delete`, and `open` failures are reported as `CacheOperationError` values.
196
+
164
197
  ## R2 Example
165
198
 
166
199
  R2 bucket tags expose Cloudflare object operations as Effects and map nullable reads to `Option`.
@@ -251,7 +284,7 @@ export const resizeAvatar = (image: Images.ImageInputValue) =>
251
284
 
252
285
  ## Email Example
253
286
 
254
- 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`.
255
288
 
256
289
  ```ts
257
290
  import { Effect } from "effect";
@@ -275,6 +308,20 @@ export const sendWelcomeEmail = (to: string) =>
275
308
  });
276
309
  ```
277
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
+
278
325
  ## Analytics Engine Example
279
326
 
280
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