effect-cf 0.25.1 → 0.25.3
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 +12 -0
- package/dist/Vitest.d.mts +1 -1
- package/dist/{WorkflowBinding-Bzs5kmoE.d.mts → WorkflowBinding-D5iWzCUa.d.mts} +6 -1
- package/dist/index.d.mts +13 -2
- package/dist/index.mjs +30 -15
- package/package.json +1 -1
- package/src/CloudflareOtlp.ts +7 -2
- package/src/DurableObject.ts +29 -17
- package/src/Worker.ts +13 -19
- package/src/internal/Entrypoint.ts +6 -3
- package/src/internal/Telemetry.ts +27 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# effect-cf
|
|
2
2
|
|
|
3
|
+
## 0.25.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#92](https://github.com/danieljvdm/effect-cf/pull/92) [`f174800`](https://github.com/danieljvdm/effect-cf/commit/f1748004e463d4e54c22e7c77fdea68ff8de7f82) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Flush configured OTLP telemetry after Durable Object alarm handlers, including failed alarms, so buffered telemetry is exported consistently with fetch and native RPC handlers.
|
|
8
|
+
|
|
9
|
+
## 0.25.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#90](https://github.com/danieljvdm/effect-cf/pull/90) [`56c1b2d`](https://github.com/danieljvdm/effect-cf/commit/56c1b2dfa30cbcc2336f28a8d8f7a48032f565df) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Flush configured OTLP telemetry after native Worker and Durable Object RPC handlers complete. Flush and scheduling failures keep the handler outcome unchanged and emit only bounded framework diagnostics.
|
|
14
|
+
|
|
3
15
|
## 0.25.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/Vitest.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as WorkerEnvironment, s as WorkerEnv } from "./Binding-COCtoJXa.mjs";
|
|
2
|
-
import { c as DurableObjectState, l as DurableObjectStateService, o as WorkflowInstanceStatusName } from "./WorkflowBinding-
|
|
2
|
+
import { c as DurableObjectState, l as DurableObjectStateService, o as WorkflowInstanceStatusName } from "./WorkflowBinding-D5iWzCUa.mjs";
|
|
3
3
|
import { Effect, Layer } from "effect";
|
|
4
4
|
import { DurableObject } from "cloudflare:workers";
|
|
5
5
|
import { adminSecretsStore as adminSecretsStore$1, getQueueResult, introspectWorkflow as introspectWorkflow$1, introspectWorkflowInstance as introspectWorkflowInstance$1 } from "cloudflare:test";
|
|
@@ -881,7 +881,12 @@ interface WorkerOptions<RRuntime, REvent = never, EventLayerError = never, Rpc e
|
|
|
881
881
|
readonly fetch?: Effect.Effect<WorkerFetchSuccess, unknown, WorkerFetchContext<RRuntime | REvent>>;
|
|
882
882
|
/** Queue consumer handler invoked per message batch. */
|
|
883
883
|
readonly queue?: QueueHandler<RRuntime | REvent>;
|
|
884
|
-
/**
|
|
884
|
+
/**
|
|
885
|
+
* Optional RPC methods exposed as class instance methods.
|
|
886
|
+
*
|
|
887
|
+
* After every invocation, the configured OTLP flusher is scheduled through
|
|
888
|
+
* `WorkerContext.waitUntil`, including when the handler fails.
|
|
889
|
+
*/
|
|
885
890
|
readonly rpc?: Rpc;
|
|
886
891
|
}
|
|
887
892
|
type FetchWorkerOptions<RRuntime, REvent = never, EventLayerError = never> = Omit<WorkerOptions<RRuntime, REvent, EventLayerError, Record<never, never>>, "rpc"> & {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-COCtoJXa.mjs";
|
|
2
2
|
import { t as Hyperdrive_d_exports } from "./Hyperdrive-6p59yyVk.mjs";
|
|
3
|
-
import { A as Rpc_d_exports, C as QueueBinding_d_exports, D as RpcDefinition_d_exports, E as ReservedMethodName$2, M as DurableWebSocket, N as DurableObjectStorage_d_exports, O as ServiceFreeSchema$1, P as StorageOperationError, S as QueueDefinition_d_exports, T as Method$2, _ as AsyncMethodArgs, a as WorkflowInstanceCreateOptions, b as AsyncMethodSuccess, c as DurableObjectState, d as ExecutionContext, f as NativeRequest, g as ServiceBinding_d_exports, h as WorkerDefinition_d_exports, i as WorkflowInstanceCreateBatchOptions, j as DurableObjectWebSocket_d_exports, k as Provider, m as Worker_d_exports, n as WorkflowBinding_d_exports, p as WorkerContext, r as WorkflowInstance, s as WorkflowOperationError, t as WorkflowBindingClient, u as DurableObjectState_d_exports, v as AsyncMethodCloudflareReturn, w as Definition$4, x as Queue_d_exports, y as AsyncMethodKey } from "./WorkflowBinding-
|
|
3
|
+
import { A as Rpc_d_exports, C as QueueBinding_d_exports, D as RpcDefinition_d_exports, E as ReservedMethodName$2, M as DurableWebSocket, N as DurableObjectStorage_d_exports, O as ServiceFreeSchema$1, P as StorageOperationError, S as QueueDefinition_d_exports, T as Method$2, _ as AsyncMethodArgs, a as WorkflowInstanceCreateOptions, b as AsyncMethodSuccess, c as DurableObjectState, d as ExecutionContext, f as NativeRequest, g as ServiceBinding_d_exports, h as WorkerDefinition_d_exports, i as WorkflowInstanceCreateBatchOptions, j as DurableObjectWebSocket_d_exports, k as Provider, m as Worker_d_exports, n as WorkflowBinding_d_exports, p as WorkerContext, r as WorkflowInstance, s as WorkflowOperationError, t as WorkflowBindingClient, u as DurableObjectState_d_exports, v as AsyncMethodCloudflareReturn, w as Definition$4, x as Queue_d_exports, y as AsyncMethodKey } from "./WorkflowBinding-D5iWzCUa.mjs";
|
|
4
4
|
import { Config, Context, Data, DateTime, Duration, Effect, Layer, Option, Redacted, Schema, Scope } from "effect";
|
|
5
5
|
import { Headers as Headers$1, HttpClient, HttpClientResponse } from "effect/unstable/http";
|
|
6
6
|
import { OtlpExporter } from "effect/unstable/observability";
|
|
@@ -1099,7 +1099,12 @@ interface DurableObjectOptions<RRuntime, REvent = never, EventLayerError = never
|
|
|
1099
1099
|
* Object storage if work must happen only once per id.
|
|
1100
1100
|
*/
|
|
1101
1101
|
readonly initialize?: Effect.Effect<void, unknown, HandlerContext<RRuntime>>;
|
|
1102
|
-
/**
|
|
1102
|
+
/**
|
|
1103
|
+
* Optional RPC methods exposed as Durable Object instance methods.
|
|
1104
|
+
*
|
|
1105
|
+
* After every invocation, the configured OTLP flusher is scheduled through
|
|
1106
|
+
* `DurableObjectState.waitUntil`, including when the handler fails.
|
|
1107
|
+
*/
|
|
1103
1108
|
readonly rpc?: Rpc;
|
|
1104
1109
|
/** Optional fetch handler for HTTP/WebSocket requests. */
|
|
1105
1110
|
readonly fetch?: Effect.Effect<Response, unknown, FetchContext<RRuntime | REvent>>;
|
|
@@ -1111,6 +1116,12 @@ interface DurableObjectOptions<RRuntime, REvent = never, EventLayerError = never
|
|
|
1111
1116
|
* the Durable Object's single managed runtime boundary.
|
|
1112
1117
|
*/
|
|
1113
1118
|
readonly alarms?: Effect.Effect<unknown, unknown, HandlerContext<RRuntime | REvent>>;
|
|
1119
|
+
/**
|
|
1120
|
+
* Optional raw alarm handler.
|
|
1121
|
+
*
|
|
1122
|
+
* After every invocation, the configured OTLP flusher is scheduled through
|
|
1123
|
+
* `DurableObjectState.waitUntil`, including when the handler fails.
|
|
1124
|
+
*/
|
|
1114
1125
|
readonly alarm?: (alarmInfo?: globalThis.AlarmInvocationInfo) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
|
|
1115
1126
|
readonly webSocketMessage?: (socket: DurableWebSocket, message: string | ArrayBuffer) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
|
|
1116
1127
|
readonly webSocketClose?: (socket: DurableWebSocket, code: number, reason: string, wasClean: boolean) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
|
package/dist/index.mjs
CHANGED
|
@@ -683,8 +683,13 @@ const layer$14 = Layer.sync(CacheStorage, () => makeClient$11(caches));
|
|
|
683
683
|
*
|
|
684
684
|
* Every layer exposes {@link OtlpExporter.Flusher}, which drains buffered
|
|
685
685
|
* telemetry on demand. Cloudflare isolates can freeze before the periodic
|
|
686
|
-
* export interval fires
|
|
687
|
-
*
|
|
686
|
+
* export interval fires. Effect-backed Worker fetch handlers, Worker or
|
|
687
|
+
* Durable Object native RPC handlers, and Durable Object alarm handlers
|
|
688
|
+
* schedule this flusher automatically.
|
|
689
|
+
* Flush or scheduling failures do not replace the handler outcome and emit
|
|
690
|
+
* only bounded framework diagnostics without attaching the foreign cause.
|
|
691
|
+
* Other entrypoint lifecycles can flush explicitly or hand the flush to their
|
|
692
|
+
* `waitUntil` mechanism:
|
|
688
693
|
*
|
|
689
694
|
* ```ts
|
|
690
695
|
* const flusher = yield* OtlpExporter.Flusher;
|
|
@@ -1347,13 +1352,14 @@ const make$11 = (id, methods) => {
|
|
|
1347
1352
|
//#endregion
|
|
1348
1353
|
//#region src/internal/Entrypoint.ts
|
|
1349
1354
|
const provideEntrypointServices = (layer, services) => layer.pipe(Layer.provideMerge(services));
|
|
1350
|
-
const defineEntrypointRpcMethods = (target, prototype, rpc, reservedMethodNames, run) => {
|
|
1355
|
+
const defineEntrypointRpcMethods = (target, prototype, rpc, reservedMethodNames, run, onExit) => {
|
|
1351
1356
|
const methods = rpc ?? {};
|
|
1352
1357
|
assertNoReservedMethods(target, methods, reservedMethodNames);
|
|
1353
1358
|
for (const [key, method] of Object.entries(methods)) Object.defineProperty(prototype, key, {
|
|
1354
1359
|
enumerable: true,
|
|
1355
1360
|
value(...args) {
|
|
1356
|
-
|
|
1361
|
+
const handler = Effect.suspend(() => method(...args)).pipe(Effect.mapError(encodeWireError));
|
|
1362
|
+
return run(this, onExit === void 0 ? handler : handler.pipe(Effect.onExit(() => onExit(this))));
|
|
1357
1363
|
}
|
|
1358
1364
|
});
|
|
1359
1365
|
};
|
|
@@ -1386,6 +1392,18 @@ const runEventPromise = (runtime, effect, eventLayer) => {
|
|
|
1386
1392
|
return runtime.runPromise(Effect.scoped(withEventLayer));
|
|
1387
1393
|
};
|
|
1388
1394
|
//#endregion
|
|
1395
|
+
//#region src/internal/Telemetry.ts
|
|
1396
|
+
const logFlushFailure = Effect.logError("Telemetry flush failed").pipe(Effect.ignoreCause);
|
|
1397
|
+
const logFlushSchedulingFailure = Effect.logError("Telemetry flush scheduling failed").pipe(Effect.ignoreCause);
|
|
1398
|
+
/**
|
|
1399
|
+
* Schedules a configured OTLP flusher through the current Cloudflare event.
|
|
1400
|
+
*/
|
|
1401
|
+
const scheduleTelemetryFlush$2 = (waitUntil) => Effect.gen(function* () {
|
|
1402
|
+
const flusher = yield* Effect.serviceOption(OtlpExporter.Flusher);
|
|
1403
|
+
if (Option.isNone(flusher)) return;
|
|
1404
|
+
yield* waitUntil(flusher.value.flush.pipe(Effect.catchCause(() => logFlushFailure)));
|
|
1405
|
+
}).pipe(Effect.catchCause(() => logFlushSchedulingFailure));
|
|
1406
|
+
//#endregion
|
|
1389
1407
|
//#region src/Rpc.ts
|
|
1390
1408
|
var Rpc_exports = /* @__PURE__ */ __exportAll({
|
|
1391
1409
|
RpcStub: () => RpcStub,
|
|
@@ -1755,10 +1773,7 @@ const isWorkerOptions = (options) => !Effect.isEffect(options);
|
|
|
1755
1773
|
* (including streaming bodies still being consumed). It is a no-op when the
|
|
1756
1774
|
* context does not contain an `OtlpExporter.Flusher`.
|
|
1757
1775
|
*/
|
|
1758
|
-
const scheduleTelemetryFlush = Effect.flatMap(
|
|
1759
|
-
onNone: () => Effect.void,
|
|
1760
|
-
onSome: (flusher) => Effect.flatMap(WorkerContext, (workerContext) => workerContext.waitUntil(flusher.flush))
|
|
1761
|
-
}));
|
|
1776
|
+
const scheduleTelemetryFlush$1 = scheduleTelemetryFlush$2((flush) => Effect.flatMap(WorkerContext, (workerContext) => workerContext.waitUntil(flush)));
|
|
1762
1777
|
function make$9(layer, optionsOrFetch) {
|
|
1763
1778
|
const options = isWorkerOptions(optionsOrFetch) ? optionsOrFetch : { fetch: optionsOrFetch };
|
|
1764
1779
|
class EffectWorker extends WorkerEntrypoint {
|
|
@@ -1778,7 +1793,7 @@ function make$9(layer, optionsOrFetch) {
|
|
|
1778
1793
|
if (fetchHandler === void 0) return Promise.resolve(new Response("Not Found", { status: 404 }));
|
|
1779
1794
|
const ctx = requestContext ?? this.ctx;
|
|
1780
1795
|
const requestServices = Layer.mergeAll(Layer.succeed(NativeRequest, request), Layer.succeed(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromWeb(request)), Layer.succeed(ExecutionContext, ctx), Layer.succeed(WorkerContext, fromExecutionContext(ctx)));
|
|
1781
|
-
return this[RunSymbol$1](renderFetchSuccess(fetchHandler).pipe(Effect.tap(() => scheduleTelemetryFlush), Effect.provide(requestServices)));
|
|
1796
|
+
return this[RunSymbol$1](renderFetchSuccess(fetchHandler).pipe(Effect.tap(() => scheduleTelemetryFlush$1), Effect.provide(requestServices)));
|
|
1782
1797
|
}
|
|
1783
1798
|
queue(batch) {
|
|
1784
1799
|
const queueHandler = options.queue;
|
|
@@ -1787,7 +1802,7 @@ function make$9(layer, optionsOrFetch) {
|
|
|
1787
1802
|
return this[RunSymbol$1](queueHandler(fromMessageBatch(batch, messages)));
|
|
1788
1803
|
}
|
|
1789
1804
|
}
|
|
1790
|
-
defineEntrypointRpcMethods("Worker", EffectWorker.prototype, options.rpc, reservedMethodNames$2, (self, effect) => self[RunSymbol$1](effect));
|
|
1805
|
+
defineEntrypointRpcMethods("Worker", EffectWorker.prototype, options.rpc, reservedMethodNames$2, (self, effect) => self[RunSymbol$1](effect), () => scheduleTelemetryFlush$1);
|
|
1791
1806
|
return assumeEntrypointClass(EffectWorker);
|
|
1792
1807
|
}
|
|
1793
1808
|
/**
|
|
@@ -2080,6 +2095,7 @@ const reservedMethodNames = /* @__PURE__ */ new Set([
|
|
|
2080
2095
|
"webSocketError"
|
|
2081
2096
|
]);
|
|
2082
2097
|
const RunSymbol = Symbol.for("effect-cf/DurableObject/run");
|
|
2098
|
+
const scheduleTelemetryFlush = scheduleTelemetryFlush$2((flush) => Effect.flatMap(DurableObjectState, (state) => state.waitUntil(flush)));
|
|
2083
2099
|
/**
|
|
2084
2100
|
* Creates a Durable Object class backed by a single managed Effect runtime.
|
|
2085
2101
|
*/
|
|
@@ -2103,12 +2119,11 @@ const make$7 = (layer, options = {}) => {
|
|
|
2103
2119
|
alarm(alarmInfo) {
|
|
2104
2120
|
const logicalAlarms = options.alarms?.pipe(Effect.asVoid);
|
|
2105
2121
|
const rawAlarm = options.alarm?.(alarmInfo);
|
|
2106
|
-
|
|
2122
|
+
const alarmEffect = logicalAlarms !== void 0 && rawAlarm !== void 0 ? Effect.gen(function* () {
|
|
2107
2123
|
yield* logicalAlarms;
|
|
2108
2124
|
yield* rawAlarm;
|
|
2109
|
-
})
|
|
2110
|
-
if (
|
|
2111
|
-
if (rawAlarm !== void 0) return this[RunSymbol](rawAlarm);
|
|
2125
|
+
}) : logicalAlarms ?? rawAlarm;
|
|
2126
|
+
if (alarmEffect !== void 0) return this[RunSymbol](alarmEffect.pipe(Effect.onExit(() => scheduleTelemetryFlush)));
|
|
2112
2127
|
}
|
|
2113
2128
|
webSocketMessage(socket, message) {
|
|
2114
2129
|
if (options.webSocketMessage !== void 0) return this[RunSymbol](options.webSocketMessage(fromWebSocket(socket), message));
|
|
@@ -2120,7 +2135,7 @@ const make$7 = (layer, options = {}) => {
|
|
|
2120
2135
|
if (options.webSocketError !== void 0) return this[RunSymbol](options.webSocketError(fromWebSocket(socket), error));
|
|
2121
2136
|
}
|
|
2122
2137
|
}
|
|
2123
|
-
defineEntrypointRpcMethods("Durable Object", EffectDurableObject.prototype, options.rpc, reservedMethodNames, (self, effect) => self[RunSymbol](effect));
|
|
2138
|
+
defineEntrypointRpcMethods("Durable Object", EffectDurableObject.prototype, options.rpc, reservedMethodNames, (self, effect) => self[RunSymbol](effect), () => scheduleTelemetryFlush);
|
|
2124
2139
|
return assumeEntrypointClass(EffectDurableObject);
|
|
2125
2140
|
};
|
|
2126
2141
|
//#endregion
|
package/package.json
CHANGED
package/src/CloudflareOtlp.ts
CHANGED
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Every layer exposes {@link OtlpExporter.Flusher}, which drains buffered
|
|
5
5
|
* telemetry on demand. Cloudflare isolates can freeze before the periodic
|
|
6
|
-
* export interval fires
|
|
7
|
-
*
|
|
6
|
+
* export interval fires. Effect-backed Worker fetch handlers, Worker or
|
|
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
13
|
*
|
|
9
14
|
* ```ts
|
|
10
15
|
* const flusher = yield* OtlpExporter.Flusher;
|
package/src/DurableObject.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { DurableObjectState, fromDurableObjectState } from "./DurableObjectState
|
|
|
7
7
|
import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
|
|
8
8
|
import * as Entrypoint from "./internal/Entrypoint";
|
|
9
9
|
import * as Runtime from "./internal/Runtime";
|
|
10
|
+
import * as Telemetry from "./internal/Telemetry";
|
|
10
11
|
|
|
11
12
|
const reservedMethodNames = new Set<string>([
|
|
12
13
|
"constructor",
|
|
@@ -29,6 +30,11 @@ type RunOptions = {
|
|
|
29
30
|
|
|
30
31
|
const RunSymbol = Symbol.for("effect-cf/DurableObject/run");
|
|
31
32
|
|
|
33
|
+
const scheduleTelemetryFlush: Effect.Effect<void, never, DurableObjectState> =
|
|
34
|
+
Telemetry.scheduleTelemetryFlush((flush) =>
|
|
35
|
+
Effect.flatMap(DurableObjectState, (state) => state.waitUntil(flush)),
|
|
36
|
+
);
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* Effect type for Durable Object lifecycle and RPC handlers.
|
|
34
40
|
*/
|
|
@@ -98,7 +104,12 @@ export interface DurableObjectOptions<
|
|
|
98
104
|
* Object storage if work must happen only once per id.
|
|
99
105
|
*/
|
|
100
106
|
readonly initialize?: Effect.Effect<void, unknown, HandlerContext<RRuntime>>;
|
|
101
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Optional RPC methods exposed as Durable Object instance methods.
|
|
109
|
+
*
|
|
110
|
+
* After every invocation, the configured OTLP flusher is scheduled through
|
|
111
|
+
* `DurableObjectState.waitUntil`, including when the handler fails.
|
|
112
|
+
*/
|
|
102
113
|
readonly rpc?: Rpc;
|
|
103
114
|
/** Optional fetch handler for HTTP/WebSocket requests. */
|
|
104
115
|
readonly fetch?: Effect.Effect<Response, unknown, FetchContext<RRuntime | REvent>>;
|
|
@@ -110,6 +121,12 @@ export interface DurableObjectOptions<
|
|
|
110
121
|
* the Durable Object's single managed runtime boundary.
|
|
111
122
|
*/
|
|
112
123
|
readonly alarms?: Effect.Effect<unknown, unknown, HandlerContext<RRuntime | REvent>>;
|
|
124
|
+
/**
|
|
125
|
+
* Optional raw alarm handler.
|
|
126
|
+
*
|
|
127
|
+
* After every invocation, the configured OTLP flusher is scheduled through
|
|
128
|
+
* `DurableObjectState.waitUntil`, including when the handler fails.
|
|
129
|
+
*/
|
|
113
130
|
readonly alarm?: (
|
|
114
131
|
alarmInfo?: globalThis.AlarmInvocationInfo,
|
|
115
132
|
) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
|
|
@@ -193,22 +210,16 @@ export const make = <
|
|
|
193
210
|
alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> | void {
|
|
194
211
|
const logicalAlarms = options.alarms?.pipe(Effect.asVoid);
|
|
195
212
|
const rawAlarm = options.alarm?.(alarmInfo);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (logicalAlarms !== undefined) {
|
|
207
|
-
return this[RunSymbol](logicalAlarms);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (rawAlarm !== undefined) {
|
|
211
|
-
return this[RunSymbol](rawAlarm);
|
|
213
|
+
const alarmEffect =
|
|
214
|
+
logicalAlarms !== undefined && rawAlarm !== undefined
|
|
215
|
+
? Effect.gen(function* () {
|
|
216
|
+
yield* logicalAlarms;
|
|
217
|
+
yield* rawAlarm;
|
|
218
|
+
})
|
|
219
|
+
: (logicalAlarms ?? rawAlarm);
|
|
220
|
+
|
|
221
|
+
if (alarmEffect !== undefined) {
|
|
222
|
+
return this[RunSymbol](alarmEffect.pipe(Effect.onExit(() => scheduleTelemetryFlush)));
|
|
212
223
|
}
|
|
213
224
|
}
|
|
214
225
|
|
|
@@ -244,6 +255,7 @@ export const make = <
|
|
|
244
255
|
options.rpc,
|
|
245
256
|
reservedMethodNames,
|
|
246
257
|
(self, effect) => self[RunSymbol](effect),
|
|
258
|
+
() => scheduleTelemetryFlush,
|
|
247
259
|
);
|
|
248
260
|
|
|
249
261
|
return Entrypoint.assumeEntrypointClass<DurableObjectClass<Rpc, ROut | REvent>>(
|
package/src/Worker.ts
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import { WorkerEntrypoint as CloudflareWorkerEntrypoint } from "cloudflare:workers";
|
|
2
|
-
import {
|
|
3
|
-
type Cause,
|
|
4
|
-
Context,
|
|
5
|
-
Effect,
|
|
6
|
-
Layer,
|
|
7
|
-
type ManagedRuntime,
|
|
8
|
-
Option,
|
|
9
|
-
type Scope,
|
|
10
|
-
} from "effect";
|
|
2
|
+
import { type Cause, Context, Effect, Layer, type ManagedRuntime, type Scope } from "effect";
|
|
11
3
|
import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
12
|
-
import { OtlpExporter } from "effect/unstable/observability";
|
|
13
4
|
|
|
14
5
|
import { WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
15
6
|
import { fromMessage, fromMessageBatch, type QueueHandler } from "./Queue";
|
|
16
7
|
import type * as RpcDefinition from "./RpcDefinition";
|
|
17
8
|
import * as Entrypoint from "./internal/Entrypoint";
|
|
18
9
|
import * as Runtime from "./internal/Runtime";
|
|
10
|
+
import * as Telemetry from "./internal/Telemetry";
|
|
19
11
|
import { fromExecutionContext } from "./internal/WorkerContext";
|
|
20
12
|
|
|
21
13
|
/**
|
|
@@ -208,7 +200,12 @@ export interface WorkerOptions<
|
|
|
208
200
|
>;
|
|
209
201
|
/** Queue consumer handler invoked per message batch. */
|
|
210
202
|
readonly queue?: QueueHandler<RRuntime | REvent>;
|
|
211
|
-
/**
|
|
203
|
+
/**
|
|
204
|
+
* Optional RPC methods exposed as class instance methods.
|
|
205
|
+
*
|
|
206
|
+
* After every invocation, the configured OTLP flusher is scheduled through
|
|
207
|
+
* `WorkerContext.waitUntil`, including when the handler fails.
|
|
208
|
+
*/
|
|
212
209
|
readonly rpc?: Rpc;
|
|
213
210
|
}
|
|
214
211
|
|
|
@@ -292,14 +289,10 @@ const isWorkerOptions = <ROut, REvent, EventLayerError, Rpc extends WorkerRpc<RO
|
|
|
292
289
|
* (including streaming bodies still being consumed). It is a no-op when the
|
|
293
290
|
* context does not contain an `OtlpExporter.Flusher`.
|
|
294
291
|
*/
|
|
295
|
-
const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> =
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
onSome: (flusher) =>
|
|
300
|
-
Effect.flatMap(WorkerContext, (workerContext) => workerContext.waitUntil(flusher.flush)),
|
|
301
|
-
}),
|
|
302
|
-
);
|
|
292
|
+
const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> =
|
|
293
|
+
Telemetry.scheduleTelemetryFlush((flush) =>
|
|
294
|
+
Effect.flatMap(WorkerContext, (workerContext) => workerContext.waitUntil(flush)),
|
|
295
|
+
);
|
|
303
296
|
|
|
304
297
|
/**
|
|
305
298
|
* Creates a Cloudflare worker class backed by a single managed Effect runtime.
|
|
@@ -424,6 +417,7 @@ export function make<
|
|
|
424
417
|
options.rpc,
|
|
425
418
|
reservedMethodNames,
|
|
426
419
|
(self, effect) => self[RunSymbol](effect),
|
|
420
|
+
() => scheduleTelemetryFlush,
|
|
427
421
|
);
|
|
428
422
|
|
|
429
423
|
return Entrypoint.assumeEntrypointClass<WorkerClass<Rpc, ROut | REvent>>(EffectWorker);
|
|
@@ -19,6 +19,7 @@ export const defineEntrypointRpcMethods = <Self>(
|
|
|
19
19
|
rpc: EntrypointRpc | undefined,
|
|
20
20
|
reservedMethodNames: ReadonlySet<string>,
|
|
21
21
|
run: (self: Self, effect: Effect.Effect<any, any, any>) => Promise<unknown>,
|
|
22
|
+
onExit?: (self: Self) => Effect.Effect<void, never, any>,
|
|
22
23
|
): void => {
|
|
23
24
|
const methods = rpc ?? {};
|
|
24
25
|
|
|
@@ -28,11 +29,13 @@ export const defineEntrypointRpcMethods = <Self>(
|
|
|
28
29
|
Object.defineProperty(prototype, key, {
|
|
29
30
|
enumerable: true,
|
|
30
31
|
value(this: Self, ...args: AnyArgs) {
|
|
32
|
+
const handler = Effect.suspend(() => method(...args)).pipe(
|
|
33
|
+
Effect.mapError(RpcDefinition.encodeWireError),
|
|
34
|
+
);
|
|
35
|
+
|
|
31
36
|
return run(
|
|
32
37
|
this,
|
|
33
|
-
Effect.
|
|
34
|
-
Effect.mapError(RpcDefinition.encodeWireError),
|
|
35
|
-
),
|
|
38
|
+
onExit === undefined ? handler : handler.pipe(Effect.onExit(() => onExit(this))),
|
|
36
39
|
);
|
|
37
40
|
},
|
|
38
41
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import { OtlpExporter } from "effect/unstable/observability";
|
|
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
|
+
);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Schedules a configured OTLP flusher through the current Cloudflare event.
|
|
12
|
+
*/
|
|
13
|
+
export const scheduleTelemetryFlush = <R>(
|
|
14
|
+
waitUntil: (flush: Effect.Effect<void>) => Effect.Effect<void, never, R>,
|
|
15
|
+
): Effect.Effect<void, never, R> =>
|
|
16
|
+
Effect.gen(function* () {
|
|
17
|
+
const flusher = yield* Effect.serviceOption(OtlpExporter.Flusher);
|
|
18
|
+
|
|
19
|
+
if (Option.isNone(flusher)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
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));
|