effect-cf 0.28.0 → 0.28.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,13 @@
1
1
  # effect-cf
2
2
 
3
+ ## 0.28.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#105](https://github.com/danieljvdm/effect-cf/pull/105) [`f5dfd5e`](https://github.com/danieljvdm/effect-cf/commit/f5dfd5e2c79b257cc760dc0b2cdf5a2901b0b295) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Bound internally scheduled telemetry flushes so exporter retries cannot extend Worker or Durable Object event lifetimes indefinitely.
8
+
9
+ - [#106](https://github.com/danieljvdm/effect-cf/pull/106) [`c0fbb85`](https://github.com/danieljvdm/effect-cf/commit/c0fbb8535479262741e6844935e9ff9d508f6b56) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Silently absorb failures raised while scheduling automatic OTLP telemetry flushes so platform boundary defects cannot feed back into the configured exporter. Document the two-second best-effort boundary and keep explicit queue flush policy application-owned.
10
+
3
11
  ## 0.28.0
4
12
 
5
13
  ### Minor Changes
package/README.md CHANGED
@@ -357,6 +357,12 @@ Producers should usually use `const queue = yield* AvatarQueue` and then call `q
357
357
 
358
358
  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.
359
359
 
360
+ effect-cf automatically schedules best-effort OTLP flushes only for Worker
361
+ fetch/RPC and Durable Object alarm/RPC handlers. Those internal flushes are
362
+ capped at two seconds. Queue handlers do not flush telemetry automatically;
363
+ applications that explicitly flush in a queue handler own that flush's timeout
364
+ and failure policy.
365
+
360
366
  ## Cache Example
361
367
 
362
368
  `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(...)`.
@@ -1,5 +1,5 @@
1
1
  import { ComputerArtifactsClient } from "./ComputerArtifacts.mjs";
2
- import { t as DurableObjectState } from "./DurableObjectState-B3uFY1eM.mjs";
2
+ import { t as DurableObjectState } from "./DurableObjectState-OupNMMUl.mjs";
3
3
  import { Context, Effect, Layer, Schema, Scope, Stream } from "effect";
4
4
  import { CommitView, GitClient, GitCloneOptions, GitCommitOptions, GitDiffOptions, GitFetchOptions, GitLogOptions, GitUpdateRefOptions } from "@cloudflare/computer/git";
5
5
  import { ExecSyncResult, RuntimeExecOptions, RuntimeGetOptions, RuntimeKillOptions, ShellValue, SkippedEntry, ThinkWorkspaceCompatibility, WorkspaceClient, WorkspaceRuntimeResult, WorkspaceRuntimeValue as WorkspaceRuntimeValue$1 } from "@cloudflare/computer";
@@ -888,7 +888,8 @@ interface WorkerOptions<RRuntime, REvent = never, EventLayerError = never, Rpc e
888
888
  *
889
889
  * The layer is built inside the event's Effect scope and finalized when the
890
890
  * event effect completes. Use this for event-scoped resources such as OTLP
891
- * trace/log exporters that should flush at event completion.
891
+ * trace/log exporters. Worker fetch and RPC handlers schedule a best-effort
892
+ * flush capped at two seconds; queue handlers do not flush automatically.
892
893
  */
893
894
  readonly eventLayer?: Layer.Layer<REvent, EventLayerError, WorkerBaseContext<RRuntime>>;
894
895
  /** Main request handler. */
@@ -899,7 +900,8 @@ interface WorkerOptions<RRuntime, REvent = never, EventLayerError = never, Rpc e
899
900
  * Optional RPC methods exposed as class instance methods.
900
901
  *
901
902
  * After every invocation, the configured OTLP flusher is scheduled through
902
- * `WorkerContext.waitUntil`, including when the handler fails.
903
+ * `WorkerContext.waitUntil`, including when the handler fails. The scheduled
904
+ * flush silently settles within two seconds even if the exporter does not.
903
905
  */
904
906
  readonly rpc?: Rpc;
905
907
  }
package/dist/Vitest.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { c as WorkerEnvironment, s as WorkerEnv } from "./Binding-xJJJPmiD.mjs";
2
- import { n as DurableObjectStateService, t as DurableObjectState } from "./DurableObjectState-B3uFY1eM.mjs";
3
- import { o as WorkflowInstanceStatusName } from "./WorkflowBinding-wZ-yGHow.mjs";
2
+ import { n as DurableObjectStateService, t as DurableObjectState } from "./DurableObjectState-OupNMMUl.mjs";
3
+ import { o as WorkflowInstanceStatusName } from "./WorkflowBinding-DUZtKCVM.mjs";
4
4
  import { Effect, Layer, Schema } from "effect";
5
5
  import { DurableObject } from "cloudflare:workers";
6
6
  import { adminSecretsStore as adminSecretsStore$1, getQueueResult, introspectWorkflow as introspectWorkflow$1, introspectWorkflowInstance as introspectWorkflowInstance$1 } from "cloudflare:test";
@@ -1,5 +1,5 @@
1
1
  import { c as WorkerEnvironment, r as BindingValidationError, t as BindingNotFoundError } from "./Binding-xJJJPmiD.mjs";
2
- import { x as ServiceFreeSchema } from "./DurableObjectState-B3uFY1eM.mjs";
2
+ import { x as ServiceFreeSchema } from "./DurableObjectState-OupNMMUl.mjs";
3
3
  import { Context, Effect, Option, Schema } from "effect";
4
4
  declare namespace WorkflowBinding_d_exports {
5
5
  export { WorkflowBindingClient, WorkflowBindingDefinition, WorkflowInstance, WorkflowInstanceCreateBatchOptions, WorkflowInstanceCreateOptions, WorkflowInstanceEvent, WorkflowInstanceRestartOptions, WorkflowInstanceStatus, WorkflowInstanceStatusName, WorkflowOperationError, WorkflowResultDecodeError, isWorkflow, layer, makeClient };
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { a as Environment_d_exports, c as WorkerEnvironment, i as Binding_d_exports, n as BindingService, o as WorkerConfig, r as BindingValidationError, s as WorkerEnv, t as BindingNotFoundError } from "./Binding-xJJJPmiD.mjs";
2
2
  import { l as Artifacts_d_exports } from "./Artifacts-DnHQA-Ax.mjs";
3
- import { C as Rpc_d_exports, D as StorageOperationError, E as DurableObjectStorage_d_exports, S as Provider, T as DurableWebSocket, _ as Definition$4, a as NativeRequest, b as RpcDefinition_d_exports, c as WorkerDefinition_d_exports, d as AsyncMethodCloudflareReturn, f as AsyncMethodKey, g as QueueBinding_d_exports, h as QueueDefinition_d_exports, i as ExecutionContext, l as ServiceBinding_d_exports, m as Queue_d_exports, o as WorkerContext, p as AsyncMethodSuccess, r as DurableObjectState_d_exports, s as Worker_d_exports, t as DurableObjectState, u as AsyncMethodArgs, v as Method$2, w as DurableObjectWebSocket_d_exports, x as ServiceFreeSchema$1, y as ReservedMethodName$2 } from "./DurableObjectState-B3uFY1eM.mjs";
3
+ import { C as Rpc_d_exports, D as StorageOperationError, E as DurableObjectStorage_d_exports, S as Provider, T as DurableWebSocket, _ as Definition$4, a as NativeRequest, b as RpcDefinition_d_exports, c as WorkerDefinition_d_exports, d as AsyncMethodCloudflareReturn, f as AsyncMethodKey, g as QueueBinding_d_exports, h as QueueDefinition_d_exports, i as ExecutionContext, l as ServiceBinding_d_exports, m as Queue_d_exports, o as WorkerContext, p as AsyncMethodSuccess, r as DurableObjectState_d_exports, s as Worker_d_exports, t as DurableObjectState, u as AsyncMethodArgs, v as Method$2, w as DurableObjectWebSocket_d_exports, x as ServiceFreeSchema$1, y as ReservedMethodName$2 } from "./DurableObjectState-OupNMMUl.mjs";
4
4
  import { t as Hyperdrive_d_exports } from "./Hyperdrive-z-9fFz15.mjs";
5
- import { a as WorkflowInstanceCreateOptions, i as WorkflowInstanceCreateBatchOptions, n as WorkflowBinding_d_exports, r as WorkflowInstance, s as WorkflowOperationError, t as WorkflowBindingClient } from "./WorkflowBinding-wZ-yGHow.mjs";
5
+ import { a as WorkflowInstanceCreateOptions, i as WorkflowInstanceCreateBatchOptions, n as WorkflowBinding_d_exports, r as WorkflowInstance, s as WorkflowOperationError, t as WorkflowBindingClient } from "./WorkflowBinding-DUZtKCVM.mjs";
6
6
  import { Cause, Config, Context, Data, DateTime, Duration, Effect, Layer, Option, Predicate, Redacted, Schema, Scope } from "effect";
7
7
  import { Headers as Headers$1, HttpClient, HttpClientResponse } from "effect/unstable/http";
8
8
  import { OtlpExporter, OtlpResource } from "effect/unstable/observability";
@@ -1111,7 +1111,9 @@ interface DurableObjectOptions<RRuntime, REvent = never, EventLayerError = never
1111
1111
  * Optional RPC methods exposed as Durable Object instance methods.
1112
1112
  *
1113
1113
  * After every invocation, the configured OTLP flusher is scheduled through
1114
- * `DurableObjectState.waitUntil`, including when the handler fails.
1114
+ * `DurableObjectState.waitUntil`, including when the handler fails. The
1115
+ * scheduled flush silently settles within two seconds even if the exporter
1116
+ * does not.
1115
1117
  */
1116
1118
  readonly rpc?: Rpc;
1117
1119
  /** Optional fetch handler for HTTP/WebSocket requests. */
@@ -1128,7 +1130,9 @@ interface DurableObjectOptions<RRuntime, REvent = never, EventLayerError = never
1128
1130
  * Optional raw alarm handler.
1129
1131
  *
1130
1132
  * After every invocation, the configured OTLP flusher is scheduled through
1131
- * `DurableObjectState.waitUntil`, including when the handler fails.
1133
+ * `DurableObjectState.waitUntil`, including when the handler fails. The
1134
+ * scheduled flush silently settles within two seconds even if the exporter
1135
+ * does not.
1132
1136
  */
1133
1137
  readonly alarm?: (alarmInfo?: globalThis.AlarmInvocationInfo) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
1134
1138
  readonly webSocketMessage?: (socket: DurableWebSocket, message: string | ArrayBuffer) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
package/dist/index.mjs CHANGED
@@ -643,16 +643,23 @@ const layer$14 = Layer.sync(CacheStorage, () => makeClient$11(caches));
643
643
  * telemetry on demand. Cloudflare isolates can freeze before the periodic
644
644
  * export interval fires. Effect-backed Worker fetch handlers, Worker or
645
645
  * Durable Object native RPC handlers, and Durable Object alarm handlers
646
- * schedule this flusher automatically.
647
- * Flush or scheduling failures do not replace the handler outcome and emit
648
- * only bounded framework diagnostics without attaching the foreign cause.
649
- * Other entrypoint lifecycles can flush explicitly or hand the flush to their
650
- * `waitUntil` mechanism:
646
+ * schedule this flusher automatically. These automatic flushes are capped at
647
+ * two seconds and silently discard timeouts and failures so telemetry cannot
648
+ * extend or fail the user event, or recursively log through an unhealthy
649
+ * exporter.
650
+ *
651
+ * Other entrypoint lifecycles, including queue handlers, do not flush
652
+ * automatically. Applications can flush explicitly or hand the flush to their
653
+ * `waitUntil` mechanism, and own the timeout and failure policy at that call
654
+ * site:
651
655
  *
652
656
  * ```ts
653
657
  * const flusher = yield* OtlpExporter.Flusher;
654
658
  *
655
- * yield* flusher.flush;
659
+ * yield* flusher.flush.pipe(
660
+ * Effect.timeoutOption("2 seconds"),
661
+ * Effect.ignoreCause,
662
+ * );
656
663
  * ```
657
664
  */
658
665
  var CloudflareOtlp_exports = /* @__PURE__ */ __exportAll({
@@ -1342,16 +1349,20 @@ function runEventPromise(...args) {
1342
1349
  }
1343
1350
  //#endregion
1344
1351
  //#region src/internal/Telemetry.ts
1345
- const logFlushFailure = Effect.logError("Telemetry flush failed").pipe(Effect.ignoreCause);
1346
- const logFlushSchedulingFailure = Effect.logError("Telemetry flush scheduling failed").pipe(Effect.ignoreCause);
1352
+ /** Maximum event lifetime spent on an internally scheduled telemetry flush. */
1353
+ const scheduledFlushTimeout = "2 seconds";
1347
1354
  /**
1348
1355
  * Schedules a configured OTLP flusher through the current Cloudflare event.
1356
+ *
1357
+ * The handed-off effect is best-effort: it settles within two seconds and
1358
+ * silently absorbs every failure cause so telemetry cannot extend or fail the
1359
+ * user event, or recursively log through an unhealthy exporter.
1349
1360
  */
1350
1361
  const scheduleTelemetryFlush$2 = (waitUntil) => Effect.gen(function* () {
1351
1362
  const flusher = yield* Effect.serviceOption(OtlpExporter.Flusher);
1352
1363
  if (Option.isNone(flusher)) return;
1353
- yield* waitUntil(flusher.value.flush.pipe(Effect.catchCause(() => logFlushFailure)));
1354
- }).pipe(Effect.catchCause(() => logFlushSchedulingFailure));
1364
+ yield* waitUntil(flusher.value.flush.pipe(Effect.timeoutOption(scheduledFlushTimeout), Effect.ignoreCause, Effect.asVoid));
1365
+ }).pipe(Effect.ignoreCause);
1355
1366
  //#endregion
1356
1367
  //#region src/Rpc.ts
1357
1368
  var Rpc_exports = /* @__PURE__ */ __exportAll({
@@ -1737,7 +1748,8 @@ const isWorkerOptions = (options) => !Effect.isEffect(options);
1737
1748
  *
1738
1749
  * The flush is handed to `ctx.waitUntil` so it never delays the response
1739
1750
  * (including streaming bodies still being consumed). It is a no-op when the
1740
- * context does not contain an `OtlpExporter.Flusher`.
1751
+ * context does not contain an `OtlpExporter.Flusher`. The handed-off effect is
1752
+ * best-effort and silently settles within two seconds.
1741
1753
  */
1742
1754
  const scheduleTelemetryFlush$1 = scheduleTelemetryFlush$2((flush) => Effect.flatMap(WorkerContext, (workerContext) => workerContext.waitUntil(flush)));
1743
1755
  function make$9(layer, optionsOrFetch) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.28.0",
3
+ "version": "0.28.1",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -5,16 +5,23 @@
5
5
  * telemetry on demand. Cloudflare isolates can freeze before the periodic
6
6
  * export interval fires. Effect-backed Worker fetch handlers, Worker or
7
7
  * Durable Object native RPC handlers, and Durable Object alarm handlers
8
- * schedule this flusher automatically.
9
- * Flush or scheduling failures do not replace the handler outcome and emit
10
- * only bounded framework diagnostics without attaching the foreign cause.
11
- * Other entrypoint lifecycles can flush explicitly or hand the flush to their
12
- * `waitUntil` mechanism:
8
+ * schedule this flusher automatically. These automatic flushes are capped at
9
+ * two seconds and silently discard timeouts and failures so telemetry cannot
10
+ * extend or fail the user event, or recursively log through an unhealthy
11
+ * exporter.
12
+ *
13
+ * Other entrypoint lifecycles, including queue handlers, do not flush
14
+ * automatically. Applications can flush explicitly or hand the flush to their
15
+ * `waitUntil` mechanism, and own the timeout and failure policy at that call
16
+ * site:
13
17
  *
14
18
  * ```ts
15
19
  * const flusher = yield* OtlpExporter.Flusher;
16
20
  *
17
- * yield* flusher.flush;
21
+ * yield* flusher.flush.pipe(
22
+ * Effect.timeoutOption("2 seconds"),
23
+ * Effect.ignoreCause,
24
+ * );
18
25
  * ```
19
26
  */
20
27
  import { ConfigProvider, Effect, Layer, Option } from "effect";
@@ -108,7 +108,9 @@ export interface DurableObjectOptions<
108
108
  * Optional RPC methods exposed as Durable Object instance methods.
109
109
  *
110
110
  * After every invocation, the configured OTLP flusher is scheduled through
111
- * `DurableObjectState.waitUntil`, including when the handler fails.
111
+ * `DurableObjectState.waitUntil`, including when the handler fails. The
112
+ * scheduled flush silently settles within two seconds even if the exporter
113
+ * does not.
112
114
  */
113
115
  readonly rpc?: Rpc;
114
116
  /** Optional fetch handler for HTTP/WebSocket requests. */
@@ -125,7 +127,9 @@ export interface DurableObjectOptions<
125
127
  * Optional raw alarm handler.
126
128
  *
127
129
  * After every invocation, the configured OTLP flusher is scheduled through
128
- * `DurableObjectState.waitUntil`, including when the handler fails.
130
+ * `DurableObjectState.waitUntil`, including when the handler fails. The
131
+ * scheduled flush silently settles within two seconds even if the exporter
132
+ * does not.
129
133
  */
130
134
  readonly alarm?: (
131
135
  alarmInfo?: globalThis.AlarmInvocationInfo,
package/src/Worker.ts CHANGED
@@ -185,7 +185,8 @@ export interface WorkerOptions<
185
185
  *
186
186
  * The layer is built inside the event's Effect scope and finalized when the
187
187
  * event effect completes. Use this for event-scoped resources such as OTLP
188
- * trace/log exporters that should flush at event completion.
188
+ * trace/log exporters. Worker fetch and RPC handlers schedule a best-effort
189
+ * flush capped at two seconds; queue handlers do not flush automatically.
189
190
  */
190
191
  readonly eventLayer?: Layer.Layer<REvent, EventLayerError, WorkerBaseContext<RRuntime>>;
191
192
  /** Main request handler. */
@@ -200,7 +201,8 @@ export interface WorkerOptions<
200
201
  * Optional RPC methods exposed as class instance methods.
201
202
  *
202
203
  * After every invocation, the configured OTLP flusher is scheduled through
203
- * `WorkerContext.waitUntil`, including when the handler fails.
204
+ * `WorkerContext.waitUntil`, including when the handler fails. The scheduled
205
+ * flush silently settles within two seconds even if the exporter does not.
204
206
  */
205
207
  readonly rpc?: Rpc;
206
208
  }
@@ -300,7 +302,8 @@ const isWorkerOptions = <ROut, REvent, EventLayerError, Rpc extends WorkerRpc<RO
300
302
  *
301
303
  * The flush is handed to `ctx.waitUntil` so it never delays the response
302
304
  * (including streaming bodies still being consumed). It is a no-op when the
303
- * context does not contain an `OtlpExporter.Flusher`.
305
+ * context does not contain an `OtlpExporter.Flusher`. The handed-off effect is
306
+ * best-effort and silently settles within two seconds.
304
307
  */
305
308
  const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> =
306
309
  Telemetry.scheduleTelemetryFlush((flush) =>
@@ -1,14 +1,15 @@
1
1
  import { Effect, Option } from "effect";
2
2
  import { OtlpExporter } from "effect/unstable/observability";
3
3
 
4
- const logFlushFailure = Effect.logError("Telemetry flush failed").pipe(Effect.ignoreCause);
5
-
6
- const logFlushSchedulingFailure = Effect.logError("Telemetry flush scheduling failed").pipe(
7
- Effect.ignoreCause,
8
- );
4
+ /** Maximum event lifetime spent on an internally scheduled telemetry flush. */
5
+ const scheduledFlushTimeout = "2 seconds";
9
6
 
10
7
  /**
11
8
  * Schedules a configured OTLP flusher through the current Cloudflare event.
9
+ *
10
+ * The handed-off effect is best-effort: it settles within two seconds and
11
+ * silently absorbs every failure cause so telemetry cannot extend or fail the
12
+ * user event, or recursively log through an unhealthy exporter.
12
13
  */
13
14
  export const scheduleTelemetryFlush = <R>(
14
15
  waitUntil: (flush: Effect.Effect<void>) => Effect.Effect<void, never, R>,
@@ -20,8 +21,14 @@ export const scheduleTelemetryFlush = <R>(
20
21
  return;
21
22
  }
22
23
 
23
- // Exporters and platform schedulers are foreign boundaries. Preserve a
24
- // useful diagnostic without retaining their arbitrary failure causes in
25
- // logs, where payloads, headers, or credentials could otherwise escape.
26
- yield* waitUntil(flusher.value.flush.pipe(Effect.catchCause(() => logFlushFailure)));
27
- }).pipe(Effect.catchCause(() => logFlushSchedulingFailure));
24
+ // Exporters and platform schedulers are foreign boundaries. Do not log
25
+ // their arbitrary failure causes: the configured logger may be the same
26
+ // unhealthy OTLP exporter and recursively increase its backlog.
27
+ yield* waitUntil(
28
+ flusher.value.flush.pipe(
29
+ Effect.timeoutOption(scheduledFlushTimeout),
30
+ Effect.ignoreCause,
31
+ Effect.asVoid,
32
+ ),
33
+ );
34
+ }).pipe(Effect.ignoreCause);