effect-cf 0.20.1 → 0.22.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 (53) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/{Hyperdrive-RTjgirMV.d.mts → Hyperdrive-COyg7R26.d.mts} +40 -13
  4. package/dist/HyperdrivePg.d.mts +13 -5
  5. package/dist/HyperdrivePg.mjs +12 -7
  6. package/dist/index.d.mts +708 -549
  7. package/dist/index.mjs +924 -587
  8. package/package.json +10 -5
  9. package/src/AiGateway.ts +82 -47
  10. package/src/AnalyticsEngine.ts +74 -58
  11. package/src/Binding.ts +66 -13
  12. package/src/BrowserRendering.ts +30 -7
  13. package/src/Cache.ts +32 -8
  14. package/src/CloudflareOtlp.ts +32 -9
  15. package/src/ContainerNamespace.ts +87 -55
  16. package/src/D1.ts +13 -3
  17. package/src/DurableObject.ts +27 -164
  18. package/src/DurableObjectAlarm.ts +30 -9
  19. package/src/DurableObjectDefinition.ts +58 -54
  20. package/src/DurableObjectNamespace.ts +99 -96
  21. package/src/DurableObjectRpcWebSocket.ts +14 -6
  22. package/src/DurableObjectSqlite.ts +6 -3
  23. package/src/DurableObjectState.ts +6 -9
  24. package/src/DurableObjectStorage.ts +7 -1
  25. package/src/DurableObjectWebSocket.ts +51 -41
  26. package/src/Email.ts +40 -13
  27. package/src/Environment.ts +26 -6
  28. package/src/Hyperdrive.ts +2 -2
  29. package/src/HyperdrivePg.ts +25 -13
  30. package/src/Images.ts +77 -82
  31. package/src/Kv.ts +37 -27
  32. package/src/Queue.ts +6 -1
  33. package/src/QueueBinding.ts +18 -21
  34. package/src/QueueDefinition.ts +15 -14
  35. package/src/R2.ts +107 -29
  36. package/src/Rpc.ts +3 -1
  37. package/src/RpcDefinition.ts +177 -133
  38. package/src/ServiceBinding.ts +112 -108
  39. package/src/Vectorize.ts +59 -14
  40. package/src/Worker.ts +172 -192
  41. package/src/WorkerDefinition.ts +5 -10
  42. package/src/WorkersAi.ts +27 -17
  43. package/src/Workflow.ts +54 -49
  44. package/src/WorkflowBinding.ts +19 -11
  45. package/src/WorkflowDefinition.ts +24 -27
  46. package/src/index.ts +4 -3
  47. package/src/internal/Entrypoint.ts +3 -1
  48. package/src/internal/ErrorMessage.ts +24 -0
  49. package/src/internal/RpcInvocation.ts +11 -8
  50. package/src/internal/Runtime.ts +52 -0
  51. package/src/internal/WorkerContext.ts +14 -2
  52. package/src/Entry.ts +0 -259
  53. package/src/internal/Clock.ts +0 -37
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # effect-cf
2
2
 
3
+ ## 0.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#73](https://github.com/danieljvdm/effect-cf/pull/73) [`9096e1e`](https://github.com/danieljvdm/effect-cf/commit/9096e1e6801bb9ac29ce8de0fa30902d9e210983) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Encode RPC method schemas through their canonical JSON codec at the Workers RPC boundary. Declaration schemas such as `Schema.Result` keep their container instance in their encoded form, so Durable Object, Worker entrypoint, and service binding RPC methods declared with them crashed in production with `DataCloneError` even though local same-isolate calls worked. Wire values are now plain JSON in both directions: clients encode arguments and decode results back into real instances, servers decode arguments and encode results, and codec failures still surface as tagged errors naming the definition and method. The `Method.EncodedArgs` and `Method.EncodedSuccess` utility types now report the JSON wire form.
8
+
9
+ ## 0.21.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#69](https://github.com/danieljvdm/effect-cf/pull/69) [`16d6190`](https://github.com/danieljvdm/effect-cf/commit/16d6190bc022f040b6e9ddb1f583a973357598c3) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Align effect-cf with effect 4.0.0-beta.105 canonical APIs. This release contains breaking renames alongside behavior fixes surfaced by a full audit of the beta.65 → beta.105 upgrade.
14
+
15
+ Breaking changes:
16
+
17
+ - `unsafeRaw` is now `rawUnsafe` on every binding client and definition tag, following the effect v4 `Unsafe`-suffix convention. Definition tag statics are now Effect properties (matching the client shape) rather than zero-arg functions.
18
+ - `Kv` clients: `remove` is now `delete`.
19
+ - Layer variants renamed: `AnalyticsEngine.queryFetchLayer`/`queryFetchLayerConfig` → `layerFetch`/`layerFetchConfig` (tag fields `fetchLayer`/`fetchLayerConfig` likewise); `CloudflareOtlp.workerLayer`/`durableObjectLayer` → `layerWorker`/`layerDurableObject`.
20
+ - Definition tags (Durable Objects, Queues, Workflows, Containers, Bindings) are keyed as `effect-cf/<Module>/<id>` instead of the bare user id, so a definition named `"X"` no longer silently collides with an unrelated service keyed `"X"`. `definition.id` is unchanged.
21
+ - Queue producers must provide `sendBatch`; the silent sequential-send fallback (which lost batch atomicity) was removed and now fails with `QueueOperationError`.
22
+ - `Worker.renderHttpResponse` removed — return `HttpServerResponse` values directly from fetch handlers to get canonical response handling, including streaming-scope transfer.
23
+ - `RpcDefinition`: the plain-Error `ReservedMethodNameError` and `assertNoReservedMethodNames` were removed (use `assertNoReservedMethods`, which now throws the tagged `RpcReservedMethodNameError`); the `index` field was removed from argument encode/decode errors (positional info lives in the schema issue path).
24
+ - `Worker.TagFactory` / `DurableObject.TagFactory` types removed; the definition type surface is now re-exported from `WorkerDefinition`/`DurableObjectDefinition`.
25
+ - The unexported legacy `Entry` module and the internal Cloudflare clock were deleted; the effect default clock is used (its epoch time is monotonic-anchored, so it may skew from `Date.now()` by microseconds).
26
+ - effect peer dependencies are pinned exactly to `4.0.0-beta.105` while the package depends on `effect/unstable/*` modules.
27
+
28
+ Fixes:
29
+
30
+ - `DurableObjectSqlite.layer` now passes Durable Object storage to the SQL client, so `sql.withTransaction` works — previously every transaction failed at runtime.
31
+ - `CloudflareOtlp` layers now expose `OtlpExporter.Flusher`, and Worker fetch handlers automatically flush buffered telemetry via `ctx.waitUntil` after each response — telemetry is no longer lost when the isolate freezes after responding.
32
+ - Worker fetch handlers built with `makeFetchHandler` cache the runtime per environment instead of rebuilding every layer on each request.
33
+ - RPC method errors keep their tags across the Workers RPC wire: `RpcArgumentCountError`, `RpcArgumentDecodeError`, and `RpcSuccessEncodeError` are schema-serialized at the entrypoint and rehydrated as tagged instances on the caller side.
34
+ - The Durable Object RPC WebSocket transport closes the socket with code 1009 when a frame exceeds the serialization buffer limit instead of leaving the connection in a poisoned state.
35
+ - `DurableObjectWebSocket.acceptUpgrade` fails with a typed `DurableWebSocketAttachmentError` for non-serializable attachments instead of an untyped defect.
36
+ - Cloudflare Workflow steps fail with a typed `WorkflowStepError` carrying the step name, operation, and cause instead of leaking raw `unknown` rejections.
37
+ - `AiGateway` HTTP requests abort in-flight fetches when the running fiber is interrupted, and merge a caller-supplied `AbortSignal`.
38
+
39
+ Additions:
40
+
41
+ - Named tracing spans across the public client surface (`Kv.put`, `R2.get`, `DurableObject.call`, `QueueBinding.send`, `Email.send`, ...) with binding/operation attributes, so operations show up in the OTLP traces the package exports.
42
+ - Error classes render actionable messages by default: `Cause.pretty` and logs now show the binding, operation, error code when present, and the underlying cause instead of an empty message with a stack trace.
43
+ - `WorkerConfig.providerFromEnv` accepts `{ preserveEmptyStrings: true }` for environments where an empty-string var must decode as `""` rather than as absent.
44
+ - `Email` error `code` extraction is documented as best-effort (dev-time remote-proxy bindings can strip the property).
45
+ - `package.json` declares `sideEffects: []` for better tree-shaking of Workers bundles, and `@cloudflare/workers-types` is an optional peer for consumers type-checking against the public declarations.
46
+
3
47
  ## 0.20.1
4
48
 
5
49
  ### Patch Changes
package/README.md CHANGED
@@ -119,7 +119,7 @@ export default Worker.make(RenderersLive, {
119
119
 
120
120
  The instance client exposes `state`, `fetch`, `start`,
121
121
  `startAndWaitForPorts`, `stop`, `destroy`, and the Effect-valued native
122
- `unsafeRaw` stub.
122
+ `rawUnsafe` stub.
123
123
  Responses are returned unchanged, including non-2xx and WebSocket upgrade
124
124
  responses. If the Container uses outbound interception, also export
125
125
  `ContainerProxy` from `@cloudflare/containers`.
@@ -366,7 +366,7 @@ export const recordPageView = (request: Request) =>
366
366
 
367
367
  Define the dataset binding in the consuming Worker's `wrangler.jsonc` with `analytics_engine_datasets`, then provide `RequestAnalytics.layer({ binding: "REQUEST_ANALYTICS" })` from the Worker layer. Invalid writes fail with `AnalyticsEngineWriteValidationError` by default. Use `write: { onInvalid: "drop" }` on the layer or `{ onInvalid: "drop" }` per call when dropping invalid points is preferable.
368
368
 
369
- Analytics Engine query tags wrap Cloudflare's HTTP SQL API. Configuration can stay in Effect `Config`, including a redacted API token, the outbound transport is an Effect `HttpClient` dependency, and rows can be decoded with Effect schemas at the query boundary. Use `fetchLayerConfig(...)` as shorthand when the platform fetch-backed client is enough.
369
+ Analytics Engine query tags wrap Cloudflare's HTTP SQL API. Configuration can stay in Effect `Config`, including a redacted API token, the outbound transport is an Effect `HttpClient` dependency, and rows can be decoded with Effect schemas at the query boundary. Use `layerFetchConfig(...)` as shorthand when the platform fetch-backed client is enough.
370
370
 
371
371
  ```ts
372
372
  import { Effect, Layer, Schema as S } from "effect";
@@ -1,5 +1,7 @@
1
1
  import { Config, ConfigProvider, Context, Effect, Layer } from "effect";
2
- //#region src/Environment.d.ts
2
+ declare namespace Environment_d_exports {
3
+ export { WorkerConfig, WorkerEnv, WorkerEnvironment };
4
+ }
3
5
  /** Cloudflare worker environment object (`env`). */
4
6
  type WorkerEnv = Cloudflare.Env;
5
7
  declare const WorkerEnvironment_base: Context.ServiceClass<WorkerEnvironment, "effect-cf/WorkerEnvironment", Cloudflare.Env>;
@@ -51,8 +53,23 @@ declare namespace WorkerConfig {
51
53
  const integer: <const Name extends Key>(name: Name) => Config.Config<number>;
52
54
  /** Read a scalar Cloudflare var or secret as a boolean. */
53
55
  const boolean: <const Name extends Key>(name: Name) => Config.Config<boolean>;
54
- /** Build a `ConfigProvider` from a Cloudflare worker `env` object. */
55
- const providerFromEnv: (env: WorkerEnv) => ConfigProvider.ConfigProvider;
56
+ /** Options for building a `ConfigProvider` from a Cloudflare worker `env` object. */
57
+ interface ProviderOptions {
58
+ /**
59
+ * Keep empty-string env values as explicit `""` config values.
60
+ *
61
+ * By default (absent or `false`), empty strings are treated as missing
62
+ * config, matching `ConfigProvider.fromUnknown`.
63
+ */
64
+ readonly preserveEmptyStrings?: boolean;
65
+ }
66
+ /**
67
+ * Build a `ConfigProvider` from a Cloudflare worker `env` object.
68
+ *
69
+ * Empty-string values are treated as missing config by default; pass
70
+ * `{ preserveEmptyStrings: true }` to keep them as explicit values.
71
+ */
72
+ const providerFromEnv: (env: WorkerEnv, options?: ProviderOptions) => ConfigProvider.ConfigProvider;
56
73
  /**
57
74
  * Build a `ConfigProvider` from the current `WorkerEnvironment` with a custom
58
75
  * conversion function.
@@ -64,17 +81,19 @@ declare namespace WorkerConfig {
64
81
  * Replace the active Effect `ConfigProvider` with one backed by the current
65
82
  * Cloudflare worker `env` object.
66
83
  */
67
- const providerLayer: import("effect/Layer").Layer<never, never, WorkerEnvironment>;
84
+ const providerLayer: Layer.Layer<never, never, WorkerEnvironment>;
68
85
  /** Alias for `providerLayer` for concise use in worker layers. */
69
- const layer: import("effect/Layer").Layer<never, never, WorkerEnvironment>;
86
+ const layer: Layer.Layer<never, never, WorkerEnvironment>;
70
87
  /** Build a `ConfigProvider` layer with a custom `env` conversion function. */
71
- const layerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => import("effect/Layer").Layer<never, never, WorkerEnvironment>;
88
+ const layerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => Layer.Layer<never, never, WorkerEnvironment>;
72
89
  }
73
90
  declare namespace Binding_d_exports {
74
91
  export { BindingNotFoundError, BindingService, BindingValidationError, Service, TypeId, ValidationOptions, layer$1 as layer };
75
92
  }
76
93
  /** Internal type id marker used by binding helper services. */
77
- declare const TypeId: "effect-cf/Binding";
94
+ declare const TypeId: "~effect-cf/Binding";
95
+ /** Internal type id marker used by binding helper services. */
96
+ type TypeId = typeof TypeId;
78
97
  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 & {
79
98
  readonly _tag: "BindingNotFoundError";
80
99
  } & Readonly<A>;
@@ -98,14 +117,22 @@ interface ValidationOptions {
98
117
  }
99
118
  /**
100
119
  * Creates a Context tag + layer for reading and validating a Cloudflare binding.
120
+ *
121
+ * The Context key is namespaced as `effect-cf/Binding/<id>`; `id` stays the
122
+ * bare user-supplied identifier.
101
123
  */
102
- interface BindingService<Self, Id extends string, Service> extends Context.ServiceClass<Self, Id, Service> {
103
- readonly [TypeId]: typeof TypeId;
124
+ interface BindingService<Self, Id extends string, Service> extends Context.ServiceClass<Self, `effect-cf/Binding/${Id}`, Service> {
125
+ readonly [TypeId]: TypeId;
126
+ readonly id: Id;
104
127
  readonly binding: string;
105
128
  readonly layer: Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
106
129
  }
107
- declare const layer$1: <Self, Resource, Service = Resource>(tag: Context.Service<Self, Service>, binding: string, isResource: (value: unknown) => value is Resource, wrap?: (resource: Resource) => Service, options?: ValidationOptions) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
108
- declare const Service: <Self>() => <Id extends string, Resource, Service = Resource>(id: Id, binding: string, isResource: (value: unknown) => value is Resource, wrap?: (resource: Resource) => Service, options?: ValidationOptions) => BindingService<Self, Id, Service>;
130
+ declare function layer$1<Self, Resource>(tag: Context.Service<Self, Resource>, binding: string, isResource: (value: unknown) => value is Resource, wrap?: undefined, options?: ValidationOptions): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
131
+ declare function layer$1<Self, Resource, Service>(tag: Context.Service<Self, Service>, binding: string, isResource: (value: unknown) => value is Resource, wrap: (resource: Resource) => Service, options?: ValidationOptions): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
132
+ declare const Service: <Self>() => {
133
+ <Id extends string, Resource>(id: Id, binding: string, isResource: (value: unknown) => value is Resource, wrap?: undefined, options?: ValidationOptions): BindingService<Self, Id, Resource>;
134
+ <Id extends string, Resource, Service>(id: Id, binding: string, isResource: (value: unknown) => value is Resource, wrap: (resource: Resource) => Service, options?: ValidationOptions): BindingService<Self, Id, Service>;
135
+ };
109
136
  declare namespace Hyperdrive_d_exports {
110
137
  export { HyperdriveClient, HyperdriveDefinition, HyperdriveService, LayerOptions, Tag, TagClass, isHyperdrive, layer, make, makeClient };
111
138
  }
@@ -116,7 +143,7 @@ interface HyperdriveDefinition {
116
143
  }
117
144
  interface HyperdriveClient {
118
145
  readonly connectionString: string;
119
- readonly unsafeRaw: Effect.Effect<Hyperdrive>;
146
+ readonly rawUnsafe: Effect.Effect<Hyperdrive>;
120
147
  readonly definition: HyperdriveDefinition;
121
148
  }
122
149
  declare const HyperdriveServiceTypeId: unique symbol;
@@ -139,4 +166,4 @@ declare const layer: <Self>(tag: Context.Service<Self, HyperdriveClient>, defini
139
166
  declare const make: <Id extends string>(id: Id) => TagClass<HyperdriveService<Id>, Id>;
140
167
  declare const Tag: <Self>() => <Id extends string>(id: Id) => TagClass<Self, Id>;
141
168
  //#endregion
142
- export { BindingService as a, WorkerConfig as c, BindingNotFoundError as i, WorkerEnv as l, LayerOptions as n, BindingValidationError as o, TagClass as r, Binding_d_exports as s, Hyperdrive_d_exports as t, WorkerEnvironment as u };
169
+ export { BindingService as a, Environment_d_exports as c, WorkerEnvironment as d, BindingNotFoundError as i, WorkerConfig as l, LayerOptions as n, BindingValidationError as o, TagClass as r, Binding_d_exports as s, Hyperdrive_d_exports as t, WorkerEnv as u };
@@ -1,10 +1,18 @@
1
- import { i as BindingNotFoundError, n as LayerOptions, o as BindingValidationError, r as TagClass, u as WorkerEnvironment } from "./Hyperdrive-RTjgirMV.mjs";
1
+ import { d as WorkerEnvironment, i as BindingNotFoundError, n as LayerOptions, o as BindingValidationError, r as TagClass } from "./Hyperdrive-COyg7R26.mjs";
2
2
  import { Layer } from "effect";
3
3
  import { PgClient } from "@effect/sql-pg";
4
+ import { SqlClient, SqlError } from "effect/unstable/sql";
4
5
  //#region src/HyperdrivePg.d.ts
5
- type PgLayerOptions = Omit<PgClient.PgClientConfig & {
6
- readonly acquireForStream?: boolean;
7
- }, "url" | "host" | "port" | "path" | "ssl" | "database" | "username" | "password" | "stream">;
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>>;
6
+ /** Hyperdrive owns database pooling, so app-side pool and connection options are not exposed. */
7
+ type PgLayerOptions = Omit<Parameters<typeof PgClient.makeClient>[0], "url" | "host" | "port" | "path" | "ssl" | "database" | "username" | "password" | "stream">;
8
+ /**
9
+ * Provides `PgClient` and `SqlClient` backed by a Hyperdrive binding.
10
+ *
11
+ * Note: since Effect `4.0.0-beta.99`, `PgClient.makeClient` connects eagerly,
12
+ * so the TCP connection to Hyperdrive is opened when this layer is built.
13
+ * In per-request Workers runtimes, build the layer within the request
14
+ * lifecycle rather than sharing it across requests.
15
+ */
16
+ declare const layer: <Self, Id extends string>(tag: TagClass<Self, Id>, binding: LayerOptions, options?: PgLayerOptions) => Layer.Layer<PgClient.PgClient | SqlClient.SqlClient, BindingNotFoundError | BindingValidationError | SqlError.SqlError, WorkerEnvironment>;
9
17
  //#endregion
10
18
  export { PgLayerOptions, layer };
@@ -1,12 +1,17 @@
1
1
  import { Effect, Layer, Redacted } from "effect";
2
2
  import { PgClient } from "@effect/sql-pg";
3
3
  //#region src/HyperdrivePg.ts
4
- const layer = (tag, binding, options) => Layer.unwrap(Effect.gen(function* () {
5
- const hyperdrive = yield* tag;
6
- return PgClient.layerFrom(PgClient.makeClient({
7
- ...options,
8
- url: Redacted.make(hyperdrive.connectionString)
9
- }));
10
- })).pipe(Layer.provide(tag.layer(binding)));
4
+ /**
5
+ * Provides `PgClient` and `SqlClient` backed by a Hyperdrive binding.
6
+ *
7
+ * Note: since Effect `4.0.0-beta.99`, `PgClient.makeClient` connects eagerly,
8
+ * so the TCP connection to Hyperdrive is opened when this layer is built.
9
+ * In per-request Workers runtimes, build the layer within the request
10
+ * lifecycle rather than sharing it across requests.
11
+ */
12
+ const layer = (tag, binding, options) => PgClient.layerFrom(Effect.flatMap(tag, (hyperdrive) => PgClient.makeClient({
13
+ ...options,
14
+ url: Redacted.make(hyperdrive.connectionString)
15
+ }))).pipe(Layer.provide(tag.layer(binding)));
11
16
  //#endregion
12
17
  export { layer };