effect-cf 0.20.1 → 0.23.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 +52 -0
  2. package/README.md +27 -3
  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 +1113 -922
  7. package/dist/index.mjs +980 -604
  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 +251 -80
  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 +63 -39
  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 +39 -17
  52. package/src/Entry.ts +0 -259
  53. package/src/internal/Clock.ts +0 -37
@@ -2,6 +2,7 @@ import { Context, Data, Effect, type Layer } from "effect";
2
2
 
3
3
  import * as Binding from "./Binding";
4
4
  import type { WorkerEnvironment } from "./Environment";
5
+ import * as ErrorMessage from "./internal/ErrorMessage";
5
6
 
6
7
  const expectedBrowserRenderingBinding = "Browser Rendering binding resource";
7
8
 
@@ -12,7 +13,11 @@ export class BrowserRenderingOperationError extends Data.TaggedError(
12
13
  readonly binding: string;
13
14
  readonly operation: string;
14
15
  readonly cause: unknown;
15
- }> {}
16
+ }> {
17
+ override get message(): string {
18
+ return `Browser Rendering ${this.operation} failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
19
+ }
20
+ }
16
21
 
17
22
  /** Typed Browser Rendering binding definition. */
18
23
  export interface BrowserRenderingDefinition {
@@ -106,7 +111,7 @@ export interface BrowserRenderingClient<
106
111
  connect: BrowserRenderingConnect<RawBinding, Browser, ConnectOptions>,
107
112
  options?: ConnectOptions,
108
113
  ) => Effect.Effect<BrowserRenderingBrowserClient<Browser, Page>, BrowserRenderingOperationError>;
109
- readonly unsafeRaw: Effect.Effect<RawBinding>;
114
+ readonly rawUnsafe: Effect.Effect<RawBinding>;
110
115
  readonly definition: BrowserRenderingDefinition;
111
116
  }
112
117
 
@@ -251,15 +256,33 @@ export const makeClient =
251
256
  ) =>
252
257
  (binding: RawBinding): BrowserRenderingClient<RawBinding> => ({
253
258
  definition,
254
- launchWith: (launch, options) =>
259
+ launchWith: <
260
+ Browser extends BrowserRenderingBrowserLike<Page>,
261
+ Page extends BrowserRenderingPageLike = BrowserRenderingPageLike,
262
+ LaunchOptions = unknown,
263
+ >(
264
+ launch: BrowserRenderingLaunch<RawBinding, Browser, LaunchOptions>,
265
+ options?: LaunchOptions,
266
+ ) =>
255
267
  tryBrowserRenderingPromise(definition.binding, "launch", () => launch(binding, options)).pipe(
256
- Effect.map((browser) => wrapBrowser(definition.binding, browser)),
268
+ Effect.map((browser) => wrapBrowser<Browser, Page>(definition.binding, browser)),
269
+ Effect.withSpan("BrowserRendering.launchWith"),
257
270
  ),
258
- connectWith: (connect, options) =>
271
+ connectWith: <
272
+ Browser extends BrowserRenderingBrowserLike<Page>,
273
+ Page extends BrowserRenderingPageLike = BrowserRenderingPageLike,
274
+ ConnectOptions = unknown,
275
+ >(
276
+ connect: BrowserRenderingConnect<RawBinding, Browser, ConnectOptions>,
277
+ options?: ConnectOptions,
278
+ ) =>
259
279
  tryBrowserRenderingPromise(definition.binding, "connect", () =>
260
280
  connect(binding, options),
261
- ).pipe(Effect.map((browser) => wrapBrowser(definition.binding, browser))),
262
- unsafeRaw: Effect.succeed(binding),
281
+ ).pipe(
282
+ Effect.map((browser) => wrapBrowser<Browser, Page>(definition.binding, browser)),
283
+ Effect.withSpan("BrowserRendering.connectWith"),
284
+ ),
285
+ rawUnsafe: Effect.succeed(binding),
263
286
  });
264
287
 
265
288
  export const layer = <Self>(
package/src/Cache.ts CHANGED
@@ -6,6 +6,8 @@ import type {
6
6
  } from "@cloudflare/workers-types";
7
7
  import { Context, Data, Effect, Layer, Option } from "effect";
8
8
 
9
+ import * as ErrorMessage from "./internal/ErrorMessage";
10
+
9
11
  /** Request key accepted by Cloudflare's Cache API. */
10
12
  export type CacheRequest = CloudflareRequestInfo | URL;
11
13
 
@@ -20,7 +22,11 @@ export class CacheOperationError extends Data.TaggedError("CacheOperationError")
20
22
  readonly cache: string;
21
23
  readonly operation: CacheOperation;
22
24
  readonly cause: unknown;
23
- }> {}
25
+ }> {
26
+ override get message(): string {
27
+ return `Cache ${this.operation} failed for cache "${this.cache}": ${ErrorMessage.causeMessage(this.cause)}`;
28
+ }
29
+ }
24
30
 
25
31
  /** Effect wrapper around one Cloudflare `Cache` instance. */
26
32
  export interface CacheClient {
@@ -42,7 +48,7 @@ export interface CacheClient {
42
48
  options?: CacheQueryOptions,
43
49
  ) => Effect.Effect<boolean, CacheOperationError>;
44
50
  /** Access to the native Cloudflare `Cache` instance. */
45
- readonly unsafeRaw: Effect.Effect<CloudflareCache>;
51
+ readonly rawUnsafe: Effect.Effect<CloudflareCache>;
46
52
  }
47
53
 
48
54
  /** Effect wrapper around Cloudflare's global `caches` object. */
@@ -52,7 +58,7 @@ export interface CacheStorageClient {
52
58
  /** Opens a named cache. */
53
59
  readonly open: (name: string) => Effect.Effect<CacheClient, CacheOperationError>;
54
60
  /** Access to the native Cloudflare `CacheStorage` instance. */
55
- readonly unsafeRaw: Effect.Effect<CloudflareCacheStorage>;
61
+ readonly rawUnsafe: Effect.Effect<CloudflareCacheStorage>;
56
62
  }
57
63
 
58
64
  /** Service for Cloudflare's global Cache API. */
@@ -73,17 +79,34 @@ const tryCachePromise = <A>(
73
79
  catch: (cause) => cacheError(cache, operation, cause),
74
80
  });
75
81
 
82
+ const spanOptions = (cache: string, operation: CacheOperation) => ({
83
+ attributes: { cache, operation },
84
+ });
85
+
76
86
  /** Wraps a native Cloudflare `Cache` instance. */
77
87
  export const makeCacheClient = (cache: CloudflareCache, name = "default"): CacheClient => ({
78
88
  name,
79
- match: (request, options) =>
89
+ match: Effect.fn(
90
+ "Cache.match",
91
+ spanOptions(name, "match"),
92
+ )((request: CacheRequest, options?: CacheQueryOptions) =>
80
93
  tryCachePromise(name, "match", () => cache.match(request, options)).pipe(
81
94
  Effect.map(Option.fromUndefinedOr),
82
95
  ),
83
- put: (request, response) => tryCachePromise(name, "put", () => cache.put(request, response)),
84
- delete: (request, options) =>
96
+ ),
97
+ put: Effect.fn(
98
+ "Cache.put",
99
+ spanOptions(name, "put"),
100
+ )((request: CacheRequest, response: Response) =>
101
+ tryCachePromise(name, "put", () => cache.put(request, response)),
102
+ ),
103
+ delete: Effect.fn(
104
+ "Cache.delete",
105
+ spanOptions(name, "delete"),
106
+ )((request: CacheRequest, options?: CacheQueryOptions) =>
85
107
  tryCachePromise(name, "delete", () => cache.delete(request, options)),
86
- unsafeRaw: Effect.succeed(cache),
108
+ ),
109
+ rawUnsafe: Effect.succeed(cache),
87
110
  });
88
111
 
89
112
  /** Wraps a native Cloudflare `CacheStorage` instance. */
@@ -92,8 +115,9 @@ export const makeClient = (storage: CloudflareCacheStorage): CacheStorageClient
92
115
  open: (name) =>
93
116
  tryCachePromise(name, "open", () => storage.open(name)).pipe(
94
117
  Effect.map((cache) => makeCacheClient(cache, name)),
118
+ Effect.withSpan("Cache.open", spanOptions(name, "open")),
95
119
  ),
96
- unsafeRaw: Effect.succeed(storage),
120
+ rawUnsafe: Effect.succeed(storage),
97
121
  });
98
122
 
99
123
  /** Provides Cache API services from an explicit native `CacheStorage` instance. */
@@ -1,7 +1,22 @@
1
+ /**
2
+ * OTLP telemetry layers for Cloudflare Workers and Durable Objects.
3
+ *
4
+ * Every layer exposes {@link OtlpExporter.Flusher}, which drains buffered
5
+ * telemetry on demand. Cloudflare isolates can freeze before the periodic
6
+ * export interval fires, so flush explicitly before returning a response, or
7
+ * hand the flush to `ctx.waitUntil`:
8
+ *
9
+ * ```ts
10
+ * const flusher = yield* OtlpExporter.Flusher;
11
+ *
12
+ * yield* flusher.flush;
13
+ * ```
14
+ */
1
15
  import { ConfigProvider, Effect, Layer, Option } from "effect";
2
16
  import type * as Tracer from "effect/Tracer";
3
17
  import { FetchHttpClient, type Headers } from "effect/unstable/http";
4
18
  import {
19
+ OtlpExporter,
5
20
  OtlpLogger,
6
21
  OtlpMetrics,
7
22
  OtlpSerialization,
@@ -138,7 +153,7 @@ const mergeSignalLayers = (layers: ReadonlyArray<SignalLayer>): SignalLayer => {
138
153
  const makeLayer = (
139
154
  options: LayerOptions = {},
140
155
  runtimeAttributes: Record<string, unknown> = {},
141
- ): Layer.Layer<never, never, never> => {
156
+ ): Layer.Layer<OtlpExporter.Flusher> => {
142
157
  const signals = selectedSignals(options.signals);
143
158
  const resource = makeResource(options, runtimeAttributes);
144
159
  const layers: Array<SignalLayer> = [];
@@ -174,7 +189,7 @@ const makeLayer = (
174
189
  }
175
190
 
176
191
  if (layers.length === 0) {
177
- return Layer.empty;
192
+ return OtlpExporter.layerFlusher;
178
193
  }
179
194
 
180
195
  return mergeSignalLayers(layers).pipe(
@@ -191,16 +206,22 @@ const makeLayer = (
191
206
  * layers. In Cloudflare Workers and Durable Objects, the current `env` object is
192
207
  * installed as the primary `ConfigProvider`; outside Cloudflare, the ambient
193
208
  * Effect `ConfigProvider` remains the fallback.
209
+ *
210
+ * The layer provides {@link OtlpExporter.Flusher} for draining buffered
211
+ * telemetry before the isolate freezes.
194
212
  */
195
- export const layer = (options: LayerOptions = {}) => makeLayer(options);
213
+ export const layer = (options: LayerOptions = {}): Layer.Layer<OtlpExporter.Flusher> =>
214
+ makeLayer(options);
196
215
 
197
216
  /** Base OTLP telemetry layer forced to JSON serialization. */
198
- export const layerJson = (options: Omit<LayerOptions, "serialization"> = {}) =>
199
- layer({ ...options, serialization: "json" });
217
+ export const layerJson = (
218
+ options: Omit<LayerOptions, "serialization"> = {},
219
+ ): Layer.Layer<OtlpExporter.Flusher> => layer({ ...options, serialization: "json" });
200
220
 
201
221
  /** Base OTLP telemetry layer forced to protobuf serialization. */
202
- export const layerProtobuf = (options: Omit<LayerOptions, "serialization"> = {}) =>
203
- layer({ ...options, serialization: "protobuf" });
222
+ export const layerProtobuf = (
223
+ options: Omit<LayerOptions, "serialization"> = {},
224
+ ): Layer.Layer<OtlpExporter.Flusher> => layer({ ...options, serialization: "protobuf" });
204
225
 
205
226
  /**
206
227
  * OTLP layer with Cloudflare Worker resource attributes.
@@ -208,7 +229,7 @@ export const layerProtobuf = (options: Omit<LayerOptions, "serialization"> = {})
208
229
  * Provide this layer at runtime scope for long-lived metrics aggregation, or at
209
230
  * handler scope when traces/logs should flush as the Cloudflare event finishes.
210
231
  */
211
- export const workerLayer = (options: WorkerLayerOptions = {}) =>
232
+ export const layerWorker = (options: WorkerLayerOptions = {}): Layer.Layer<OtlpExporter.Flusher> =>
212
233
  makeLayer(options, {
213
234
  "cloudflare.resource_type": "worker",
214
235
  "cloudflare.worker.name": options.workerName,
@@ -221,7 +242,9 @@ export const workerLayer = (options: WorkerLayerOptions = {}) =>
221
242
  * Durable Object id. Prefer leaving `includeObjectId` disabled unless the
222
243
  * backend can tolerate high-cardinality resource attributes.
223
244
  */
224
- export const durableObjectLayer = (options: DurableObjectLayerOptions = {}) =>
245
+ export const layerDurableObject = (
246
+ options: DurableObjectLayerOptions = {},
247
+ ): Layer.Layer<OtlpExporter.Flusher, never, DurableObjectState> =>
225
248
  Layer.unwrap(
226
249
  Effect.map(DurableObjectState, (state) =>
227
250
  makeLayer(options, {