effect-cf 0.28.0 → 0.29.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.
- package/CHANGELOG.md +18 -0
- package/README.md +6 -0
- package/dist/ComputerWorkspace.d.mts +1 -1
- package/dist/ComputerWorkspace.mjs +1 -1
- package/dist/{DurableObjectState-BjkK2kwH.mjs → DurableObjectState-DnnLoHV-.mjs} +6 -2
- package/dist/{DurableObjectState-B3uFY1eM.d.mts → DurableObjectState-OupNMMUl.d.mts} +4 -2
- package/dist/Vitest.d.mts +2 -2
- package/dist/Vitest.mjs +1 -1
- package/dist/{WorkflowBinding-wZ-yGHow.d.mts → WorkflowBinding-DUZtKCVM.d.mts} +1 -1
- package/dist/index.d.mts +8 -4
- package/dist/index.mjs +24 -12
- package/package.json +6 -6
- package/src/CloudflareOtlp.ts +13 -6
- package/src/DurableObject.ts +7 -2
- package/src/DurableObjectStorage.ts +16 -13
- package/src/Worker.ts +7 -3
- package/src/Workflow.ts +1 -0
- package/src/internal/Telemetry.ts +17 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# effect-cf
|
|
2
2
|
|
|
3
|
+
## 0.29.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#108](https://github.com/danieljvdm/effect-cf/pull/108) [`735b6d9`](https://github.com/danieljvdm/effect-cf/commit/735b6d95e4b62c7ade598a7f18ba2d8b4ee60f87) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Require the Effect 4.0.0-rc.110 family — dependency ranges move from `^4.0.0-beta.107 <4.0.0-rc.0` to `^4.0.0-rc.110`.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#66](https://github.com/danieljvdm/effect-cf/pull/66) [`fe34f95`](https://github.com/danieljvdm/effect-cf/commit/fe34f95abc92f4637968186aeebbb8033157c041) Thanks [@danieljvdm](https://github.com/danieljvdm)! - `DurableObjectStorage.transaction` and `transactionSync` now recover the aborting `Exit` by identity instead of re-deriving its type from the untyped `catch` binding. Failures raised inside a transaction keep the same typed error channel, and a failure thrown by the platform itself is still reported as `StorageOperationError`. No public API change.
|
|
12
|
+
|
|
13
|
+
## 0.28.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [#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.
|
|
18
|
+
|
|
19
|
+
- [#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.
|
|
20
|
+
|
|
3
21
|
## 0.28.0
|
|
4
22
|
|
|
5
23
|
### 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-
|
|
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";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as DurableObjectState } from "./DurableObjectState-
|
|
1
|
+
import { t as DurableObjectState } from "./DurableObjectState-DnnLoHV-.mjs";
|
|
2
2
|
import { fromClient } from "./ComputerArtifacts.mjs";
|
|
3
3
|
import { t as lookup } from "./ComputerWorkspaceHostRegistry-CY5Fv4Ng.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Predicate, Schema, Stream } from "effect";
|
|
@@ -99,24 +99,28 @@ const fromDurableObjectStorage = (storage) => ({
|
|
|
99
99
|
setAlarm: (scheduledTime, options) => tryStoragePromise("setAlarm", () => storage.setAlarm(scheduledTime, options)),
|
|
100
100
|
deleteAlarm: (options) => tryStoragePromise("deleteAlarm", () => storage.deleteAlarm(options)),
|
|
101
101
|
transactionSync: (closure) => Effect.context().pipe(Effect.flatMap((context) => Effect.suspend(() => {
|
|
102
|
+
let aborted;
|
|
102
103
|
try {
|
|
103
104
|
return Effect.succeed(storage.transactionSync(() => {
|
|
104
105
|
const exit = Effect.runSyncExitWith(context)(closure());
|
|
105
106
|
if (Exit.isSuccess(exit)) return exit.value;
|
|
107
|
+
aborted = exit;
|
|
106
108
|
throw exit;
|
|
107
109
|
}));
|
|
108
110
|
} catch (cause) {
|
|
109
|
-
if (
|
|
111
|
+
if (aborted !== void 0 && cause === aborted && Exit.isFailure(aborted)) return Effect.failCause(aborted.cause);
|
|
110
112
|
return Effect.fail(storageError("transactionSync", cause));
|
|
111
113
|
}
|
|
112
114
|
}))),
|
|
113
115
|
transaction: (closure) => Effect.context().pipe(Effect.flatMap((context) => Effect.callback((resume) => {
|
|
116
|
+
let aborted;
|
|
114
117
|
storage.transaction(async (txn) => {
|
|
115
118
|
const exit = await Effect.runPromiseExitWith(context)(closure(fromDurableObjectTransaction(txn)));
|
|
116
119
|
if (Exit.isSuccess(exit)) return exit.value;
|
|
120
|
+
aborted = exit;
|
|
117
121
|
throw exit;
|
|
118
122
|
}).then((value) => resume(Effect.succeed(value)), (cause) => {
|
|
119
|
-
if (
|
|
123
|
+
if (aborted !== void 0 && cause === aborted && Exit.isFailure(aborted)) resume(Effect.failCause(aborted.cause));
|
|
120
124
|
else resume(Effect.fail(storageError("transaction", cause)));
|
|
121
125
|
});
|
|
122
126
|
}))),
|
|
@@ -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
|
|
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-
|
|
3
|
-
import { o as WorkflowInstanceStatusName } from "./WorkflowBinding-
|
|
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";
|
package/dist/Vitest.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as WorkerEnvironment, i as WorkerConfig } from "./ErrorMessage-BM0K-Nik.mjs";
|
|
2
|
-
import { r as fromDurableObjectState, t as DurableObjectState } from "./DurableObjectState-
|
|
2
|
+
import { r as fromDurableObjectState, t as DurableObjectState } from "./DurableObjectState-DnnLoHV-.mjs";
|
|
3
3
|
import { ConfigProvider, Effect, Exit, Layer } from "effect";
|
|
4
4
|
import { env } from "cloudflare:workers";
|
|
5
5
|
import { abortAllDurableObjects as abortAllDurableObjects$1, adminSecretsStore as adminSecretsStore$1, applyD1Migrations as applyD1Migrations$1, createExecutionContext, createMessageBatch, createPagesEventContext, createScheduledController, evictAllDurableObjects as evictAllDurableObjects$1, evictDurableObject as evictDurableObject$1, getQueueResult, introspectWorkflow as introspectWorkflow$1, introspectWorkflowInstance as introspectWorkflowInstance$1, listDurableObjectIds as listDurableObjectIds$1, reset as reset$1, runDurableObjectAlarm as runDurableObjectAlarm$1, runInDurableObject as runInDurableObject$1, waitOnExecutionContext } 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-
|
|
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-
|
|
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-
|
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { a as WorkerEnvironment, i as WorkerConfig, n as violationsMessage, r as Environment_exports, t as causeMessage } from "./ErrorMessage-BM0K-Nik.mjs";
|
|
3
3
|
import { a as layer$19, i as Service$2, n as Artifacts_exports, r as Binding_exports } from "./Artifacts-rq3hUKQE.mjs";
|
|
4
|
-
import { a as DurableObjectWebSocket_exports, i as fromExecutionContext, n as DurableObjectState_exports, o as fromWebSocket, r as fromDurableObjectState, s as DurableObjectStorage_exports, t as DurableObjectState } from "./DurableObjectState-
|
|
4
|
+
import { a as DurableObjectWebSocket_exports, i as fromExecutionContext, n as DurableObjectState_exports, o as fromWebSocket, r as fromDurableObjectState, s as DurableObjectStorage_exports, t as DurableObjectState } from "./DurableObjectState-DnnLoHV-.mjs";
|
|
5
5
|
import { Cause, Clock, Config, ConfigProvider, Context, Data, DateTime, Duration, Effect, Exit, Function as Function$1, Layer, ManagedRuntime, Option, Predicate, Queue, Result, Schema } from "effect";
|
|
6
6
|
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
7
7
|
import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
|
|
@@ -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
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
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
|
-
|
|
1346
|
-
const
|
|
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.
|
|
1354
|
-
}).pipe(Effect.
|
|
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.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Effect-native primitives for Cloudflare Workers and bindings.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@cloudflare/containers": "catalog:",
|
|
65
65
|
"@cloudflare/vitest-pool-workers": "catalog:",
|
|
66
66
|
"@cloudflare/workers-types": "catalog:",
|
|
67
|
-
"@effect/platform-node": "^4.0.0-
|
|
67
|
+
"@effect/platform-node": "^4.0.0-rc.110",
|
|
68
68
|
"@effect/sql-d1": "catalog:",
|
|
69
69
|
"@effect/sql-pg": "catalog:",
|
|
70
70
|
"@effect/sql-sqlite-do": "catalog:",
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"@cloudflare/computer": "^0.2.0",
|
|
80
80
|
"@cloudflare/vitest-pool-workers": "^0.21.3",
|
|
81
81
|
"@cloudflare/workers-types": "^4.20260611.1",
|
|
82
|
-
"@effect/sql-d1": "^4.0.0-
|
|
83
|
-
"@effect/sql-pg": "^4.0.0-
|
|
84
|
-
"@effect/sql-sqlite-do": "^4.0.0-
|
|
85
|
-
"effect": "^4.0.0-
|
|
82
|
+
"@effect/sql-d1": "^4.0.0-rc.110",
|
|
83
|
+
"@effect/sql-pg": "^4.0.0-rc.110",
|
|
84
|
+
"@effect/sql-sqlite-do": "^4.0.0-rc.110",
|
|
85
|
+
"effect": "^4.0.0-rc.110"
|
|
86
86
|
},
|
|
87
87
|
"peerDependenciesMeta": {
|
|
88
88
|
"@cloudflare/computer": {
|
package/src/CloudflareOtlp.ts
CHANGED
|
@@ -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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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";
|
package/src/DurableObject.ts
CHANGED
|
@@ -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,
|
|
@@ -196,6 +200,7 @@ export const make = <
|
|
|
196
200
|
// SAFETY: event-layer bypass is used only for initialize; otherwise an absent layer means REvent is never.
|
|
197
201
|
return Runtime.runEventPromise(
|
|
198
202
|
this.runtime,
|
|
203
|
+
// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
|
|
199
204
|
effect as Effect.Effect<A, E, HandlerContext<ROut>>,
|
|
200
205
|
);
|
|
201
206
|
}
|
|
@@ -320,7 +320,12 @@ export const fromDurableObjectStorage = (
|
|
|
320
320
|
transactionSync: <A, E, R>(closure: () => Effect.Effect<A, E, R>) =>
|
|
321
321
|
Effect.context<R>().pipe(
|
|
322
322
|
Effect.flatMap((context) =>
|
|
323
|
-
Effect.suspend(() => {
|
|
323
|
+
Effect.suspend((): Effect.Effect<A, E | StorageOperationError> => {
|
|
324
|
+
// Cloudflare rolls the transaction back when the callback throws, so a
|
|
325
|
+
// failed Exit is thrown to abort it. Keep a typed reference to that
|
|
326
|
+
// Exit rather than recovering its type from the untyped catch binding.
|
|
327
|
+
let aborted: Exit.Exit<A, E> | undefined;
|
|
328
|
+
|
|
324
329
|
try {
|
|
325
330
|
return Effect.succeed(
|
|
326
331
|
storage.transactionSync(() => {
|
|
@@ -329,18 +334,14 @@ export const fromDurableObjectStorage = (
|
|
|
329
334
|
if (Exit.isSuccess(exit)) {
|
|
330
335
|
return exit.value;
|
|
331
336
|
}
|
|
337
|
+
aborted = exit;
|
|
332
338
|
|
|
333
339
|
throw exit;
|
|
334
340
|
}),
|
|
335
341
|
);
|
|
336
342
|
} catch (cause) {
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
return Effect.failCause(cause.cause) as Effect.Effect<
|
|
340
|
-
A,
|
|
341
|
-
E | StorageOperationError,
|
|
342
|
-
R
|
|
343
|
-
>;
|
|
343
|
+
if (aborted !== undefined && cause === aborted && Exit.isFailure(aborted)) {
|
|
344
|
+
return Effect.failCause(aborted.cause);
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
return Effect.fail(storageError("transactionSync", cause));
|
|
@@ -352,6 +353,10 @@ export const fromDurableObjectStorage = (
|
|
|
352
353
|
Effect.context<R>().pipe(
|
|
353
354
|
Effect.flatMap((context) =>
|
|
354
355
|
Effect.callback<A, E | StorageOperationError>((resume) => {
|
|
356
|
+
// See `transactionSync`: the thrown Exit aborts Cloudflare's
|
|
357
|
+
// transaction, and holding it here keeps its error type.
|
|
358
|
+
let aborted: Exit.Exit<A, E> | undefined;
|
|
359
|
+
|
|
355
360
|
void storage
|
|
356
361
|
.transaction(async (txn) => {
|
|
357
362
|
const exit = await Effect.runPromiseExitWith(context)(
|
|
@@ -361,17 +366,15 @@ export const fromDurableObjectStorage = (
|
|
|
361
366
|
if (Exit.isSuccess(exit)) {
|
|
362
367
|
return exit.value;
|
|
363
368
|
}
|
|
369
|
+
aborted = exit;
|
|
364
370
|
|
|
365
371
|
throw exit;
|
|
366
372
|
})
|
|
367
373
|
.then(
|
|
368
374
|
(value) => resume(Effect.succeed(value)),
|
|
369
375
|
(cause) => {
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
resume(
|
|
373
|
-
Effect.failCause(cause.cause) as Effect.Effect<A, E | StorageOperationError>,
|
|
374
|
-
);
|
|
376
|
+
if (aborted !== undefined && cause === aborted && Exit.isFailure(aborted)) {
|
|
377
|
+
resume(Effect.failCause(aborted.cause));
|
|
375
378
|
} else {
|
|
376
379
|
resume(Effect.fail(storageError("transaction", cause)));
|
|
377
380
|
}
|
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
|
|
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) =>
|
|
@@ -389,6 +392,7 @@ export function make<
|
|
|
389
392
|
// SAFETY: the public make overloads permit an absent event layer only when REvent is never.
|
|
390
393
|
return Runtime.runEventPromise(
|
|
391
394
|
this.runtime,
|
|
395
|
+
// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
|
|
392
396
|
effect as Effect.Effect<A, E, RuntimeContext<ROut> | Scope.Scope>,
|
|
393
397
|
);
|
|
394
398
|
}
|
package/src/Workflow.ts
CHANGED
|
@@ -157,6 +157,7 @@ const fromWorkflowStep = (
|
|
|
157
157
|
Effect.provideService(
|
|
158
158
|
Effect.provideContext(
|
|
159
159
|
// SAFETY: WorkflowStepContext is provided immediately below; context supplies every other R service.
|
|
160
|
+
// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
|
|
160
161
|
effect as Effect.Effect<A, E, Exclude<R, WorkflowStepContext>>,
|
|
161
162
|
context,
|
|
162
163
|
),
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Effect, Option } from "effect";
|
|
2
2
|
import { OtlpExporter } from "effect/unstable/observability";
|
|
3
3
|
|
|
4
|
-
|
|
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.
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
yield* waitUntil(
|
|
27
|
-
|
|
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);
|