effect-cf 0.32.1 → 0.34.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 +31 -1072
- package/dist/{Artifacts-DnHQA-Ax.d.mts → Artifacts-B3r3RuTB.d.mts} +1 -48
- package/dist/{Artifacts-CSIk1Oml.mjs → Artifacts-CBxWGeZB.mjs} +2 -7
- package/dist/{Binding-pfilNJmP.mjs → Binding-C82jRhdr.mjs} +1 -2
- package/dist/{Binding-xJJJPmiD.d.mts → Binding-DKcLrQCM.d.mts} +0 -45
- package/dist/ComputerArtifacts.d.mts +1 -3
- package/dist/ComputerArtifacts.mjs +1 -2
- package/dist/ComputerWorkspace.d.mts +1 -10
- package/dist/ComputerWorkspace.mjs +1 -2
- package/dist/ComputerWorkspaceHost.d.mts +1 -9
- package/dist/{ContainerNamespace-CFjiLw8b.d.mts → ContainerNamespace-DbLaeFhN.d.mts} +1 -14
- package/dist/{DurableObjectState-B0A0Y1ef.d.mts → DurableObjectState-CrEunfQ-.d.mts} +86 -254
- package/dist/{DurableObjectState-D1Ibr6k0.mjs → DurableObjectState-De4ZojhY.mjs} +2 -40
- package/dist/{ErrorMessage-BM0K-Nik.mjs → ErrorMessage-Byx2OgNd.mjs} +0 -1
- package/dist/{Hyperdrive-z-9fFz15.d.mts → Hyperdrive-HrwFlZ6x.d.mts} +1 -4
- package/dist/HyperdrivePg.d.mts +2 -11
- package/dist/HyperdrivePg.mjs +0 -8
- package/dist/Sandbox.d.mts +2 -32
- package/dist/Sandbox.mjs +2 -11
- package/dist/Vitest.d.mts +3 -31
- package/dist/Vitest.mjs +2 -17
- package/dist/{WorkflowBinding-B9_S8tX2.d.mts → WorkflowBinding-BumVQTuW.d.mts} +2 -5
- package/dist/index.d.mts +34 -480
- package/dist/index.mjs +174 -312
- package/package.json +2 -2
- package/src/AiGateway.ts +0 -6
- package/src/AnalyticsEngine.ts +0 -9
- package/src/Artifacts.ts +0 -47
- package/src/Binding.ts +0 -8
- package/src/BrowserRendering.ts +0 -4
- package/src/Cache.ts +0 -19
- package/src/CloudflareOtlp.ts +3 -39
- package/src/ComputerArtifacts.ts +0 -2
- package/src/ComputerWorkspace.ts +0 -9
- package/src/ComputerWorkspaceHost.ts +0 -8
- package/src/ContainerNamespace.ts +0 -13
- package/src/D1.ts +0 -22
- package/src/DurableObject.ts +45 -25
- package/src/DurableObjectAlarm.ts +6 -210
- package/src/DurableObjectDefinition.ts +8 -20
- package/src/DurableObjectNamespace.ts +57 -112
- package/src/DurableObjectRpcWebSocket.ts +3 -19
- package/src/DurableObjectSqlite.ts +0 -12
- package/src/DurableObjectState.ts +6 -21
- package/src/DurableObjectStorage.ts +2 -35
- package/src/DurableObjectWebSocket.ts +0 -31
- package/src/Email.ts +0 -12
- package/src/Environment.ts +0 -37
- package/src/Hyperdrive.ts +0 -3
- package/src/HyperdrivePg.ts +0 -9
- package/src/Images.ts +0 -5
- package/src/Kv.ts +0 -15
- package/src/QueueBinding.ts +0 -2
- package/src/R2.ts +0 -5
- package/src/RpcDefinition.ts +0 -2
- package/src/RpcTracing.ts +133 -0
- package/src/Sandbox.ts +0 -30
- package/src/ServiceBinding.ts +48 -125
- package/src/Vectorize.ts +0 -5
- package/src/Vitest.ts +0 -28
- package/src/WebTransport.ts +0 -15
- package/src/Worker.ts +36 -57
- package/src/WorkerDefinition.ts +5 -14
- package/src/WorkersAi.ts +0 -4
- package/src/Workflow.ts +0 -5
- package/src/WorkflowBinding.ts +0 -3
- package/src/index.ts +1 -0
- package/src/internal/Entrypoint.ts +23 -4
- package/src/internal/ErrorMessage.ts +0 -1
- package/src/internal/RpcInvocation.ts +18 -1
- package/src/internal/Runtime.ts +12 -7
- package/src/internal/WorkerContext.ts +0 -11
|
@@ -6,13 +6,10 @@ import * as ErrorMessage from "./internal/ErrorMessage";
|
|
|
6
6
|
|
|
7
7
|
const expectedContainerNamespace = "Container namespace binding with getByName()";
|
|
8
8
|
|
|
9
|
-
/** Named signal accepted by Cloudflare Container instances. */
|
|
10
9
|
export type ContainerSignal = "SIGKILL" | "SIGINT" | "SIGTERM";
|
|
11
10
|
|
|
12
|
-
/** Named or numeric signal accepted by Container `stop()`. */
|
|
13
11
|
export type ContainerStopSignal = ContainerSignal | number;
|
|
14
12
|
|
|
15
|
-
/** Per-instance Container startup configuration. */
|
|
16
13
|
export interface ContainerStartOptions {
|
|
17
14
|
readonly envVars?: Record<string, string>;
|
|
18
15
|
readonly entrypoint?: Array<string>;
|
|
@@ -34,7 +31,6 @@ export interface ContainerReadinessOptions {
|
|
|
34
31
|
readonly waitInterval?: number;
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
/** Options for starting a Container and waiting for its ports. */
|
|
38
34
|
export interface ContainerStartAndWaitForPortsOptions {
|
|
39
35
|
readonly startOptions?: ContainerStartOptions;
|
|
40
36
|
readonly ports?: number | Array<number>;
|
|
@@ -77,7 +73,6 @@ export interface ContainerStub {
|
|
|
77
73
|
removeDeniedHost(hostname: string): Promise<void>;
|
|
78
74
|
}
|
|
79
75
|
|
|
80
|
-
/** Native Container namespace binding shape. */
|
|
81
76
|
export interface ContainerNamespaceResource {
|
|
82
77
|
getByName(
|
|
83
78
|
name: string,
|
|
@@ -85,18 +80,14 @@ export interface ContainerNamespaceResource {
|
|
|
85
80
|
): ContainerStub;
|
|
86
81
|
}
|
|
87
82
|
|
|
88
|
-
/** Exact native stub type produced by a Container namespace binding. */
|
|
89
83
|
export type ContainerStubOf<Namespace extends ContainerNamespaceResource> = ReturnType<
|
|
90
84
|
Namespace["getByName"]
|
|
91
85
|
>;
|
|
92
86
|
|
|
93
|
-
/** Container namespace binding metadata. */
|
|
94
87
|
export interface ContainerNamespaceDefinition {
|
|
95
|
-
/** Binding name as configured in `wrangler.jsonc`. */
|
|
96
88
|
readonly binding: string;
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
/** Failure raised when invoking a named Container operation. */
|
|
100
91
|
export class ContainerOperationError extends Data.TaggedError("ContainerOperationError")<{
|
|
101
92
|
readonly binding: string;
|
|
102
93
|
readonly instance: string;
|
|
@@ -108,7 +99,6 @@ export class ContainerOperationError extends Data.TaggedError("ContainerOperatio
|
|
|
108
99
|
}
|
|
109
100
|
}
|
|
110
101
|
|
|
111
|
-
/** Effect-wrapped client for one named Container instance. */
|
|
112
102
|
export interface ContainerInstanceClient<Stub extends ContainerStub = ContainerStub> {
|
|
113
103
|
readonly rawUnsafe: Effect.Effect<Stub, ContainerOperationError>;
|
|
114
104
|
readonly state: Effect.Effect<ContainerState, ContainerOperationError>;
|
|
@@ -140,7 +130,6 @@ export interface ContainerInstanceClient<Stub extends ContainerStub = ContainerS
|
|
|
140
130
|
readonly removeDeniedHost: (hostname: string) => Effect.Effect<void, ContainerOperationError>;
|
|
141
131
|
}
|
|
142
132
|
|
|
143
|
-
/** Effect client for a Container namespace binding. */
|
|
144
133
|
export interface ContainerNamespaceClient<
|
|
145
134
|
Namespace extends ContainerNamespaceResource = ContainerNamespaceResource,
|
|
146
135
|
> {
|
|
@@ -433,14 +422,12 @@ export const make = <Id extends string>(id: Id) => Tag<ContainerNamespaceService
|
|
|
433
422
|
|
|
434
423
|
declare const ContainerNamespaceServiceTypeId: unique symbol;
|
|
435
424
|
|
|
436
|
-
/** Nominal service marker for Container namespaces created with {@link make}. */
|
|
437
425
|
export interface ContainerNamespaceService<Id extends string> {
|
|
438
426
|
readonly [ContainerNamespaceServiceTypeId]: {
|
|
439
427
|
readonly id: Id;
|
|
440
428
|
};
|
|
441
429
|
}
|
|
442
430
|
|
|
443
|
-
/** Creates a typed Effect service for a Cloudflare Container namespace. */
|
|
444
431
|
export const Tag =
|
|
445
432
|
<Self, Namespace extends ContainerNamespaceResource = ContainerNamespaceResource>() =>
|
|
446
433
|
<Id extends string>(id: Id) => {
|
package/src/D1.ts
CHANGED
|
@@ -10,18 +10,14 @@ const TypeId = "~effect-cf/D1" as const;
|
|
|
10
10
|
export type TypeId = typeof TypeId;
|
|
11
11
|
const expectedD1Database = "D1 database binding with prepare(), batch(), and exec()";
|
|
12
12
|
|
|
13
|
-
/** Typed D1 binding definition. */
|
|
14
13
|
export interface D1Definition {
|
|
15
|
-
/** Binding name as configured in `wrangler.jsonc`. */
|
|
16
14
|
readonly binding: string;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
/** Options forwarded to `@effect/sql-d1` when building a SQL client layer. */
|
|
20
17
|
export type D1SqlLayerOptions = Omit<D1Client.D1ClientConfig, "db">;
|
|
21
18
|
|
|
22
19
|
declare const D1ServiceTypeId: unique symbol;
|
|
23
20
|
|
|
24
|
-
/** Nominal service marker for D1 services created with {@link make}. */
|
|
25
21
|
export interface D1Service<Id extends string> {
|
|
26
22
|
readonly [D1ServiceTypeId]: {
|
|
27
23
|
readonly id: Id;
|
|
@@ -36,27 +32,9 @@ const isD1Database = <Candidate>(value: Candidate): value is Candidate & D1Datab
|
|
|
36
32
|
Predicate.hasProperty(value, "exec") &&
|
|
37
33
|
Predicate.isFunction(value.exec);
|
|
38
34
|
|
|
39
|
-
/**
|
|
40
|
-
* Creates a typed D1 service tag plus Effect helpers.
|
|
41
|
-
*/
|
|
42
35
|
export const make = <Id extends string>(id: Id, definition: D1Definition) =>
|
|
43
36
|
Service<D1Service<Id>>()(id, definition);
|
|
44
37
|
|
|
45
|
-
/**
|
|
46
|
-
* Builds a D1 service around a Cloudflare D1 database binding.
|
|
47
|
-
*
|
|
48
|
-
* The returned service exposes the raw `D1Database` binding and `sqlLayer(...)`
|
|
49
|
-
* for providing `effect/unstable/sql` via `@effect/sql-d1`.
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* ```ts
|
|
53
|
-
* class TodoDatabase extends D1.Service<TodoDatabase>()("TodoDatabase", {
|
|
54
|
-
* binding: "TODO_DB",
|
|
55
|
-
* }) {}
|
|
56
|
-
*
|
|
57
|
-
* const SqlLive = TodoDatabase.sqlLayer();
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
38
|
export const Service =
|
|
61
39
|
<Self>() =>
|
|
62
40
|
<Id extends string>(id: Id, definition: D1Definition) => {
|
package/src/DurableObject.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DurableObject as CloudflareDurableObject } from "cloudflare:workers";
|
|
2
|
-
import { Effect, Layer, type ManagedRuntime, type Scope } from "effect";
|
|
2
|
+
import { Effect, Layer, type ManagedRuntime, type Scope, Tracer } from "effect";
|
|
3
3
|
|
|
4
4
|
import { NativeRequest } from "./Worker";
|
|
5
5
|
import { WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
@@ -8,6 +8,7 @@ import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
|
|
|
8
8
|
import * as Entrypoint from "./internal/Entrypoint";
|
|
9
9
|
import * as Runtime from "./internal/Runtime";
|
|
10
10
|
import * as Telemetry from "./internal/Telemetry";
|
|
11
|
+
import type { ReceiverOptions, RpcInvocationInfo } from "./RpcTracing";
|
|
11
12
|
|
|
12
13
|
const reservedMethodNames = new Set<string>([
|
|
13
14
|
"constructor",
|
|
@@ -24,11 +25,21 @@ type RuntimeContext<ROut> = DurableObjectState | WorkerEnvironment | ROut;
|
|
|
24
25
|
type HandlerContext<ROut> = RuntimeContext<ROut> | Scope.Scope;
|
|
25
26
|
|
|
26
27
|
type FetchContext<ROut> = HandlerContext<ROut> | NativeRequest;
|
|
27
|
-
|
|
28
|
+
/** Metadata available before an event effect or its event layer starts. */
|
|
29
|
+
export interface RunOptions {
|
|
28
30
|
readonly eventLayer?: boolean;
|
|
29
|
-
|
|
31
|
+
readonly event?:
|
|
32
|
+
| "fetch"
|
|
33
|
+
| "rpc"
|
|
34
|
+
| "alarm"
|
|
35
|
+
| "webSocketMessage"
|
|
36
|
+
| "webSocketClose"
|
|
37
|
+
| "webSocketError";
|
|
38
|
+
readonly rpc?: RpcInvocationInfo;
|
|
39
|
+
}
|
|
30
40
|
|
|
31
|
-
|
|
41
|
+
/** Override to instrument the whole event, then call super[RunSymbol]. */
|
|
42
|
+
export const RunSymbol = Symbol.for("effect-cf/DurableObject/run");
|
|
32
43
|
|
|
33
44
|
const scheduleTelemetryFlush: Effect.Effect<void, never, DurableObjectState> =
|
|
34
45
|
Telemetry.scheduleTelemetryFlush((flush) =>
|
|
@@ -74,9 +85,6 @@ export type RpcHandlers<ROut, Api> = {
|
|
|
74
85
|
: never;
|
|
75
86
|
};
|
|
76
87
|
|
|
77
|
-
/**
|
|
78
|
-
* Options for creating a Durable Object class backed by Effect handlers.
|
|
79
|
-
*/
|
|
80
88
|
export interface DurableObjectOptions<
|
|
81
89
|
RRuntime,
|
|
82
90
|
REvent = never,
|
|
@@ -113,15 +121,10 @@ export interface DurableObjectOptions<
|
|
|
113
121
|
* does not.
|
|
114
122
|
*/
|
|
115
123
|
readonly rpc?: Rpc;
|
|
116
|
-
/**
|
|
124
|
+
/** Accept live native trace metadata. The client must also enable rpcTracing. */
|
|
125
|
+
readonly rpcTracing?: ReceiverOptions;
|
|
117
126
|
readonly fetch?: Effect.Effect<Response, unknown, FetchContext<RRuntime | REvent>>;
|
|
118
|
-
|
|
119
|
-
* Optional logical alarm processing effect.
|
|
120
|
-
*
|
|
121
|
-
* This runs before `alarm` and should be built with helpers such as
|
|
122
|
-
* `DurableObjectAlarm.processDue(...)` so the reusable scheduler stays inside
|
|
123
|
-
* the Durable Object's single managed runtime boundary.
|
|
124
|
-
*/
|
|
127
|
+
|
|
125
128
|
readonly alarms?: Effect.Effect<unknown, unknown, HandlerContext<RRuntime | REvent>>;
|
|
126
129
|
/**
|
|
127
130
|
* Optional raw alarm handler.
|
|
@@ -156,11 +159,14 @@ export interface DurableObjectOptions<
|
|
|
156
159
|
export type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (
|
|
157
160
|
state: globalThis.DurableObjectState,
|
|
158
161
|
env: WorkerEnv,
|
|
159
|
-
) => CloudflareDurableObject<WorkerEnv> &
|
|
162
|
+
) => CloudflareDurableObject<WorkerEnv> &
|
|
163
|
+
DurableObjectRpcApi<Rpc, ROut> & {
|
|
164
|
+
[RunSymbol]<A, E>(
|
|
165
|
+
effect: Effect.Effect<A, E, HandlerContext<ROut>>,
|
|
166
|
+
options?: RunOptions,
|
|
167
|
+
): Promise<A>;
|
|
168
|
+
};
|
|
160
169
|
|
|
161
|
-
/**
|
|
162
|
-
* Creates a Durable Object class backed by a single managed Effect runtime.
|
|
163
|
-
*/
|
|
164
170
|
export const make = <
|
|
165
171
|
ROut,
|
|
166
172
|
LayerError,
|
|
@@ -195,6 +201,8 @@ export const make = <
|
|
|
195
201
|
runOptions: RunOptions = {},
|
|
196
202
|
): Promise<A> {
|
|
197
203
|
const eventLayer = runOptions.eventLayer === false ? undefined : options.eventLayer;
|
|
204
|
+
const parent = runOptions.rpc?.parent;
|
|
205
|
+
const parentSpan = parent === undefined ? undefined : Tracer.externalSpan(parent);
|
|
198
206
|
|
|
199
207
|
if (eventLayer === undefined) {
|
|
200
208
|
// SAFETY: event-layer bypass is used only for initialize; otherwise an absent layer means REvent is never.
|
|
@@ -202,6 +210,8 @@ export const make = <
|
|
|
202
210
|
this.runtime,
|
|
203
211
|
// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
|
|
204
212
|
effect as Effect.Effect<A, E, HandlerContext<ROut>>,
|
|
213
|
+
undefined,
|
|
214
|
+
parentSpan,
|
|
205
215
|
);
|
|
206
216
|
}
|
|
207
217
|
|
|
@@ -212,7 +222,7 @@ export const make = <
|
|
|
212
222
|
REvent,
|
|
213
223
|
EventLayerError,
|
|
214
224
|
LayerError
|
|
215
|
-
>(this.runtime, effect, eventLayer);
|
|
225
|
+
>(this.runtime, effect, eventLayer, parentSpan);
|
|
216
226
|
}
|
|
217
227
|
|
|
218
228
|
fetch(request: Request): Promise<Response> {
|
|
@@ -222,7 +232,9 @@ export const make = <
|
|
|
222
232
|
return Promise.resolve(new Response("Not Found", { status: 404 }));
|
|
223
233
|
}
|
|
224
234
|
|
|
225
|
-
return this[RunSymbol](Effect.provideService(fetchHandler, NativeRequest, request)
|
|
235
|
+
return this[RunSymbol](Effect.provideService(fetchHandler, NativeRequest, request), {
|
|
236
|
+
event: "fetch",
|
|
237
|
+
});
|
|
226
238
|
}
|
|
227
239
|
|
|
228
240
|
alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> | void {
|
|
@@ -237,13 +249,17 @@ export const make = <
|
|
|
237
249
|
: (logicalAlarms ?? rawAlarm);
|
|
238
250
|
|
|
239
251
|
if (alarmEffect !== undefined) {
|
|
240
|
-
return this[RunSymbol](alarmEffect.pipe(Effect.onExit(() => scheduleTelemetryFlush))
|
|
252
|
+
return this[RunSymbol](alarmEffect.pipe(Effect.onExit(() => scheduleTelemetryFlush)), {
|
|
253
|
+
event: "alarm",
|
|
254
|
+
});
|
|
241
255
|
}
|
|
242
256
|
}
|
|
243
257
|
|
|
244
258
|
webSocketMessage(socket: WebSocket, message: string | ArrayBuffer): Promise<void> | void {
|
|
245
259
|
if (options.webSocketMessage !== undefined) {
|
|
246
|
-
return this[RunSymbol](options.webSocketMessage(fromWebSocket(socket), message)
|
|
260
|
+
return this[RunSymbol](options.webSocketMessage(fromWebSocket(socket), message), {
|
|
261
|
+
event: "webSocketMessage",
|
|
262
|
+
});
|
|
247
263
|
}
|
|
248
264
|
}
|
|
249
265
|
|
|
@@ -256,13 +272,16 @@ export const make = <
|
|
|
256
272
|
if (options.webSocketClose !== undefined) {
|
|
257
273
|
return this[RunSymbol](
|
|
258
274
|
options.webSocketClose(fromWebSocket(socket), code, reason, wasClean),
|
|
275
|
+
{ event: "webSocketClose" },
|
|
259
276
|
);
|
|
260
277
|
}
|
|
261
278
|
}
|
|
262
279
|
|
|
263
280
|
webSocketError(socket: WebSocket, cause: unknown): Promise<void> | void {
|
|
264
281
|
if (options.webSocketError !== undefined) {
|
|
265
|
-
return this[RunSymbol](options.webSocketError(fromWebSocket(socket), cause)
|
|
282
|
+
return this[RunSymbol](options.webSocketError(fromWebSocket(socket), cause), {
|
|
283
|
+
event: "webSocketError",
|
|
284
|
+
});
|
|
266
285
|
}
|
|
267
286
|
}
|
|
268
287
|
}
|
|
@@ -272,8 +291,9 @@ export const make = <
|
|
|
272
291
|
EffectDurableObject.prototype,
|
|
273
292
|
options.rpc,
|
|
274
293
|
reservedMethodNames,
|
|
275
|
-
(self, effect) => self[RunSymbol](effect),
|
|
294
|
+
(self, effect, rpc) => self[RunSymbol](effect, { event: "rpc", rpc }),
|
|
276
295
|
() => scheduleTelemetryFlush,
|
|
296
|
+
options.rpcTracing,
|
|
277
297
|
);
|
|
278
298
|
|
|
279
299
|
return Entrypoint.assumeEntrypointClass<DurableObjectClass<Rpc, ROut | REvent>>(
|
|
@@ -34,13 +34,6 @@ const DEFAULT_PROCESS_DUE_ALARMS_FAILURE_RESCHEDULE_AFTER = "30 seconds" satisfi
|
|
|
34
34
|
const getScheduledEventId = (input: { readonly id: string; readonly tag: string }) =>
|
|
35
35
|
`effect-cf-alarm:${encodeURIComponent(input.tag)}:${encodeURIComponent(input.id)}`;
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* JSON-serializable alarm payload stored with each scheduled alarm.
|
|
39
|
-
*
|
|
40
|
-
* Payloads are intentionally opaque to `DurableObjectAlarm`. Consumers should use
|
|
41
|
-
* stable `tag` values to route due alarms and then decode the payload for that
|
|
42
|
-
* tag in their own domain layer.
|
|
43
|
-
*/
|
|
44
37
|
export type AlarmPayload = S.Json;
|
|
45
38
|
|
|
46
39
|
interface AlarmRow extends Record<string, SqlStorageValue> {
|
|
@@ -107,52 +100,17 @@ export type DurableObjectAlarmError =
|
|
|
107
100
|
| StorageOperationError
|
|
108
101
|
| StoredAlarmDecodeError;
|
|
109
102
|
|
|
110
|
-
/**
|
|
111
|
-
* Events handled by `processDueAlarms`.
|
|
112
|
-
*
|
|
113
|
-
* `AlarmDue` represents one durable alarm whose scheduled time is now due. The
|
|
114
|
-
* `tag` is the consumer-owned routing key, while `id` is the stable alarm id
|
|
115
|
-
* within that tag.
|
|
116
|
-
*
|
|
117
|
-
* `scheduledAt` is the logical due-after timestamp from durable storage. It is
|
|
118
|
-
* not expected to match the current Durable Object alarm invocation time,
|
|
119
|
-
* especially during retries. Retries re-scan durable rows with `run_at <= now`
|
|
120
|
-
* and identify logical events by `{ tag, id }`.
|
|
121
|
-
*
|
|
122
|
-
* @example Handling events by tag
|
|
123
|
-
* ```ts
|
|
124
|
-
* yield* durableObjectAlarm.processDueAlarms((event) =>
|
|
125
|
-
* event.tag === "heartbeat"
|
|
126
|
-
* ? heartbeatManager.handleAlarmEvent(event).pipe(Effect.asVoid)
|
|
127
|
-
* : Effect.void
|
|
128
|
-
* );
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
131
103
|
export const DurableObjectAlarmEvent = S.TaggedUnion({
|
|
132
104
|
AlarmDue: {
|
|
133
105
|
id: S.NonEmptyString,
|
|
134
106
|
payload: S.Json,
|
|
107
|
+
/** Persisted due time, which can differ from the invocation time during retries. */
|
|
135
108
|
scheduledAt: S.DateTimeUtc,
|
|
136
109
|
tag: S.NonEmptyString,
|
|
137
110
|
},
|
|
138
111
|
});
|
|
139
112
|
export type DurableObjectAlarmEvent = typeof DurableObjectAlarmEvent.Type;
|
|
140
113
|
|
|
141
|
-
/**
|
|
142
|
-
* Stable reference for a scheduled alarm.
|
|
143
|
-
*
|
|
144
|
-
* The pair of `tag` and `id` is the durable identity. Scheduling another alarm
|
|
145
|
-
* with the same pair replaces the previous alarm, and cancellation uses the
|
|
146
|
-
* same pair.
|
|
147
|
-
*
|
|
148
|
-
* @example Cancel an alarm
|
|
149
|
-
* ```ts
|
|
150
|
-
* yield* durableObjectAlarm.cancelAlarm({
|
|
151
|
-
* tag: "connection-reconnect-grace",
|
|
152
|
-
* id: "reconnect-grace-check",
|
|
153
|
-
* });
|
|
154
|
-
* ```
|
|
155
|
-
*/
|
|
156
114
|
export type AlarmRef<Tag extends string = string> = {
|
|
157
115
|
readonly id: string;
|
|
158
116
|
readonly tag: Tag;
|
|
@@ -168,38 +126,7 @@ const decodeAlarmRef = (input: AlarmRef) =>
|
|
|
168
126
|
Effect.mapError((cause) => new InvalidAlarmRefError({ cause })),
|
|
169
127
|
);
|
|
170
128
|
|
|
171
|
-
/**
|
|
172
|
-
* Input for scheduling or replacing an alarm.
|
|
173
|
-
*
|
|
174
|
-
* `runAt` is the absolute first fire time. `repeatEvery` can be any Effect
|
|
175
|
-
* `Duration.Input`, including strings like `"5 seconds"`, numbers interpreted
|
|
176
|
-
* as milliseconds, or `Duration` values.
|
|
177
|
-
*
|
|
178
|
-
* @example One-shot alarm
|
|
179
|
-
* ```ts
|
|
180
|
-
* const now = yield* DateTime.now;
|
|
181
|
-
*
|
|
182
|
-
* yield* durableObjectAlarm.scheduleAlarm({
|
|
183
|
-
* tag: "connection-reconnect-grace",
|
|
184
|
-
* id: "reconnect-grace-check",
|
|
185
|
-
* runAt: DateTime.add(now, { seconds: 15 }),
|
|
186
|
-
* payload: { reason: "socket-closed" },
|
|
187
|
-
* });
|
|
188
|
-
* ```
|
|
189
|
-
*
|
|
190
|
-
* @example Repeating alarm
|
|
191
|
-
* ```ts
|
|
192
|
-
* const now = yield* DateTime.now;
|
|
193
|
-
*
|
|
194
|
-
* yield* durableObjectAlarm.scheduleAlarm({
|
|
195
|
-
* tag: "heartbeat",
|
|
196
|
-
* id: "heartbeat-check",
|
|
197
|
-
* runAt: DateTime.add(now, { seconds: 5 }),
|
|
198
|
-
* repeatEvery: "5 seconds",
|
|
199
|
-
* payload: null,
|
|
200
|
-
* });
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
129
|
+
/** Reusing `{tag, id}` replaces an alarm. Repeats run after success, never as fixed-cadence catch-up. */
|
|
203
130
|
export type ScheduleAlarmInput<Tag extends string = string> = AlarmRef<Tag> & {
|
|
204
131
|
readonly payload: AlarmPayload;
|
|
205
132
|
readonly repeatEvery?: Duration.Input;
|
|
@@ -237,107 +164,26 @@ export type ProcessDueAlarmsFailureAction =
|
|
|
237
164
|
};
|
|
238
165
|
|
|
239
166
|
export interface ProcessDueAlarmsOptions<OnFailureR = never, OnFailureE = never> {
|
|
240
|
-
/** Maximum due rows to load and process in one invocation. Defaults to 100. */
|
|
241
167
|
readonly limit?: number;
|
|
242
|
-
/**
|
|
243
|
-
* Failure isolation mode. Defaults to `isolated` so one poison logical alarm
|
|
244
|
-
* does not block unrelated maintenance work.
|
|
245
|
-
*/
|
|
168
|
+
/** `isolated` retries only the failed row; `ordered` stops before later rows. */
|
|
246
169
|
readonly mode?: ProcessDueAlarmsMode;
|
|
247
|
-
/**
|
|
248
|
-
* Callback for logical handler/decode failures. Return a failure action to
|
|
249
|
-
* override the global failure behavior for this row.
|
|
250
|
-
*/
|
|
251
170
|
readonly onFailure?: (
|
|
252
171
|
failure: ProcessDueAlarmsFailure,
|
|
253
172
|
) => Effect.Effect<ProcessDueAlarmsFailureAction | void, OnFailureE, OnFailureR>;
|
|
254
|
-
/**
|
|
255
|
-
* Delay before retrying a failed logical row in isolated mode. Defaults to 30
|
|
256
|
-
* seconds. Ignored by ordered mode unless supplied explicitly.
|
|
257
|
-
*
|
|
258
|
-
* Retrying updates only the selected occurrence, so replacements scheduled by
|
|
259
|
-
* a long-running handler are not clobbered by failure handling.
|
|
260
|
-
*/
|
|
261
173
|
readonly retryFailedAfter?: Duration.Input;
|
|
262
174
|
}
|
|
263
175
|
|
|
264
|
-
/**
|
|
265
|
-
* Handler invoked for each due alarm before that alarm is acknowledged.
|
|
266
|
-
*
|
|
267
|
-
* If the handler fails, `DurableObjectAlarm` leaves the row due and fails the alarm
|
|
268
|
-
* processing effect. Cloudflare can then retry the Durable Object alarm handler,
|
|
269
|
-
* and the scheduler will find the same logical row again because `run_at` is
|
|
270
|
-
* still less than or equal to the retry time.
|
|
271
|
-
*/
|
|
272
176
|
export type ProcessDueAlarmsHandler<R = never, E = never> = (
|
|
273
177
|
event: DurableObjectAlarmEvent,
|
|
274
178
|
) => Effect.Effect<void, E, R>;
|
|
275
179
|
|
|
276
|
-
/**
|
|
277
|
-
* Durable alarm scheduler API.
|
|
278
|
-
*
|
|
279
|
-
* This service only owns scheduling semantics. It does not publish, subscribe,
|
|
280
|
-
* or dispatch events to consumers. Durable Object alarm handlers should pass a
|
|
281
|
-
* domain handler to `processDueAlarms`; rows are acknowledged only after that
|
|
282
|
-
* handler succeeds.
|
|
283
|
-
*
|
|
284
|
-
* This service must be the only code path that owns `storage.setAlarm()` inside
|
|
285
|
-
* a Durable Object instance. Cloudflare exposes one platform alarm timestamp per
|
|
286
|
-
* object, so another scheduler in the same object can clobber this service's
|
|
287
|
-
* reconciled alarm timestamp.
|
|
288
|
-
*
|
|
289
|
-
* @example Durable Object alarm handler
|
|
290
|
-
* ```ts
|
|
291
|
-
* const onAlarm = Effect.gen(function* () {
|
|
292
|
-
* const durableObjectAlarm = yield* DurableObjectAlarm;
|
|
293
|
-
*
|
|
294
|
-
* yield* durableObjectAlarm.processDueAlarms((event) =>
|
|
295
|
-
* Effect.gen(function* () {
|
|
296
|
-
* if (event.tag === "heartbeat") {
|
|
297
|
-
* yield* heartbeatManager.handleAlarmEvent(event);
|
|
298
|
-
* return;
|
|
299
|
-
* }
|
|
300
|
-
*
|
|
301
|
-
* if (event.tag === "connection-reconnect-grace") {
|
|
302
|
-
* yield* connectionManager.expireReconnectGracePeriods();
|
|
303
|
-
* }
|
|
304
|
-
* })
|
|
305
|
-
* );
|
|
306
|
-
* });
|
|
307
|
-
* ```
|
|
308
|
-
*/
|
|
180
|
+
/** Own `storage.setAlarm()` exclusively: a Durable Object has one platform alarm timestamp. */
|
|
309
181
|
export type AlarmScheduler = {
|
|
310
|
-
/**
|
|
311
|
-
* Cancel a scheduled alarm by `{ tag, id }`.
|
|
312
|
-
*
|
|
313
|
-
* This is idempotent. Cancelling a missing alarm is a no-op. The underlying
|
|
314
|
-
* Durable Object alarm is reconciled afterward.
|
|
315
|
-
*/
|
|
316
182
|
readonly cancelAlarm: (
|
|
317
183
|
input: AlarmRef,
|
|
318
184
|
) => Effect.Effect<void, InvalidAlarmRefError | StorageOperationError>;
|
|
319
185
|
|
|
320
|
-
/**
|
|
321
|
-
* Process due alarms according to `Clock.currentTimeMillis`.
|
|
322
|
-
*
|
|
323
|
-
* Each row is acknowledged only after `handle` succeeds. One-shot alarms are
|
|
324
|
-
* then deleted. Repeating alarms are rescheduled to `acknowledgedAt +
|
|
325
|
-
* repeatEvery`, which intentionally behaves as delay-after-success rather
|
|
326
|
-
* than fixed-cadence catch-up. Acknowledgements are conditional on the stored
|
|
327
|
-
* row still matching the selected occurrence, so a handler that reschedules
|
|
328
|
-
* the same `{ tag, id }` will not have its replacement clobbered by the old
|
|
329
|
-
* acknowledgement.
|
|
330
|
-
*
|
|
331
|
-
* Processing is ordered by `runAt` and stable alarm id. By default, logical
|
|
332
|
-
* handler/decode failures are isolated to the failing row: the row is retried
|
|
333
|
-
* after `retryFailedAfter`, later due rows still run, and the result reports
|
|
334
|
-
* both handled and failed rows. Use `mode: "ordered"` when a workflow
|
|
335
|
-
* intentionally requires strict head-of-line blocking.
|
|
336
|
-
*
|
|
337
|
-
* Handlers must still be idempotent: Cloudflare alarms are at-least-once, and
|
|
338
|
-
* if a handler succeeds but the acknowledgement write fails, the logical event
|
|
339
|
-
* can be delivered again.
|
|
340
|
-
*/
|
|
186
|
+
/** Acknowledge after handling. Conditional writes preserve handler replacements; alarms are at-least-once. */
|
|
341
187
|
readonly processDueAlarms: <R = never, E = never, OnFailureR = never, OnFailureE = never>(
|
|
342
188
|
handle: ProcessDueAlarmsHandler<R, E>,
|
|
343
189
|
options?: ProcessDueAlarmsOptions<OnFailureR, OnFailureE>,
|
|
@@ -347,13 +193,7 @@ export type AlarmScheduler = {
|
|
|
347
193
|
R | OnFailureR
|
|
348
194
|
>;
|
|
349
195
|
|
|
350
|
-
/**
|
|
351
|
-
* Schedule or replace an alarm by `{ tag, id }`.
|
|
352
|
-
*
|
|
353
|
-
* The durable row write and underlying platform alarm reconciliation are run
|
|
354
|
-
* in one Durable Object storage transaction, so a failed `setAlarm` rolls back
|
|
355
|
-
* the logical schedule instead of leaving a stored-but-unarmed alarm.
|
|
356
|
-
*/
|
|
196
|
+
/** A failed platform setAlarm rolls back the logical schedule in the same transaction. */
|
|
357
197
|
readonly scheduleAlarm: (
|
|
358
198
|
input: ScheduleAlarmInput,
|
|
359
199
|
) => Effect.Effect<
|
|
@@ -578,50 +418,6 @@ export const define = <const Definitions extends AlarmDefinitions>(definitions:
|
|
|
578
418
|
),
|
|
579
419
|
});
|
|
580
420
|
|
|
581
|
-
/**
|
|
582
|
-
* SQLite-backed Durable Object alarm scheduler.
|
|
583
|
-
*
|
|
584
|
-
* `DurableObjectAlarm` stores alarms in the current Durable Object's SQLite storage
|
|
585
|
-
* and keeps the platform alarm set to the earliest pending scheduled alarm. The
|
|
586
|
-
* platform alarm timestamp is only a wake-up hint; logical event identity comes
|
|
587
|
-
* from persisted `{ tag, id }` rows.
|
|
588
|
-
*
|
|
589
|
-
* Retry safety depends on acknowledgement ordering. `processDueAlarms` runs the
|
|
590
|
-
* caller's handler first and only then conditionally deletes or advances the
|
|
591
|
-
* same selected row. By default, handler failures are isolated: the failed row
|
|
592
|
-
* is retried after a delay, later due rows continue, and the platform alarm is
|
|
593
|
-
* reconciled once at the end. Use `mode: "ordered"` for strict workflows where
|
|
594
|
-
* one failed logical alarm should block later due alarms.
|
|
595
|
-
*
|
|
596
|
-
* Provide `DurableObjectAlarm.layer` anywhere `DurableObjectState` is available.
|
|
597
|
-
*
|
|
598
|
-
* @example Providing the service
|
|
599
|
-
* ```ts
|
|
600
|
-
* const program = Effect.gen(function* () {
|
|
601
|
-
* const durableObjectAlarm = yield* DurableObjectAlarm;
|
|
602
|
-
* const now = yield* DateTime.now;
|
|
603
|
-
*
|
|
604
|
-
* yield* durableObjectAlarm.scheduleAlarm({
|
|
605
|
-
* tag: "heartbeat",
|
|
606
|
-
* id: "heartbeat-check",
|
|
607
|
-
* runAt: DateTime.add(now, { seconds: 5 }),
|
|
608
|
-
* repeatEvery: Duration.seconds(5),
|
|
609
|
-
* payload: null,
|
|
610
|
-
* });
|
|
611
|
-
* }).pipe(Effect.provide(DurableObjectAlarm.layer));
|
|
612
|
-
* ```
|
|
613
|
-
*
|
|
614
|
-
* @example Processing due alarms
|
|
615
|
-
* ```ts
|
|
616
|
-
* const handledEvents = yield* durableObjectAlarm.processDueAlarms((event) =>
|
|
617
|
-
* Effect.gen(function* () {
|
|
618
|
-
* if (event.tag === "heartbeat") {
|
|
619
|
-
* yield* heartbeatManager.handleAlarmEvent(event);
|
|
620
|
-
* }
|
|
621
|
-
* })
|
|
622
|
-
* );
|
|
623
|
-
* ```
|
|
624
|
-
*/
|
|
625
421
|
export class DurableObjectAlarm extends Context.Service<DurableObjectAlarm, AlarmScheduler>()(
|
|
626
422
|
"effect-cf/DurableObjectAlarm",
|
|
627
423
|
) {
|
|
@@ -7,9 +7,9 @@ import * as DurableObjectNamespace from "./DurableObjectNamespace";
|
|
|
7
7
|
import type { DurableObjectState } from "./DurableObjectState";
|
|
8
8
|
import type * as Rpc from "./Rpc";
|
|
9
9
|
import * as RpcDefinition from "./RpcDefinition";
|
|
10
|
+
import { recordDecodedArgs } from "./internal/RpcInvocation";
|
|
10
11
|
import type { WorkerEnvironment } from "./Environment";
|
|
11
12
|
|
|
12
|
-
/** Internal dynamic invocation used only before the public client contract is restored. */
|
|
13
13
|
type ErasedInvoke<E> = (...args: ReadonlyArray<unknown>) => Effect.Effect<unknown, E>;
|
|
14
14
|
|
|
15
15
|
export type ServiceFreeSchema = S.Codec<any, any, never, never>;
|
|
@@ -34,7 +34,6 @@ export namespace Method {
|
|
|
34
34
|
? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
|
|
35
35
|
: Array<S.Schema.Type<Args[number]>>;
|
|
36
36
|
|
|
37
|
-
/** Method schemas cross the wire through their canonical JSON codec. */
|
|
38
37
|
type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = {
|
|
39
38
|
[Index in keyof Args]: S.Json;
|
|
40
39
|
};
|
|
@@ -138,6 +137,7 @@ export interface Options<
|
|
|
138
137
|
|
|
139
138
|
export type LayerOptions = {
|
|
140
139
|
readonly binding: string;
|
|
140
|
+
readonly rpcTracing?: boolean;
|
|
141
141
|
};
|
|
142
142
|
|
|
143
143
|
export type TagClass<
|
|
@@ -192,19 +192,6 @@ export function method(definition: {
|
|
|
192
192
|
return RpcDefinition.method(definition);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
/**
|
|
196
|
-
* Creates a Durable Object RPC definition plus implementation/binding helpers.
|
|
197
|
-
*
|
|
198
|
-
* @example
|
|
199
|
-
* ```ts
|
|
200
|
-
* const ChatRoom = DurableObjectDefinition.make("ChatRoom", {
|
|
201
|
-
* postMessage: DurableObjectDefinition.method({
|
|
202
|
-
* args: [Schema.String],
|
|
203
|
-
* success: Schema.Void,
|
|
204
|
-
* }),
|
|
205
|
-
* });
|
|
206
|
-
* ```
|
|
207
|
-
*/
|
|
208
195
|
const makeDefinition = <Id extends string, const MethodDefinitions extends Methods>(
|
|
209
196
|
id: Id,
|
|
210
197
|
methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
|
|
@@ -319,7 +306,7 @@ export const Tag =
|
|
|
319
306
|
return yield* namespace.fetch(stub, input, init);
|
|
320
307
|
});
|
|
321
308
|
|
|
322
|
-
const rpc = Effect.
|
|
309
|
+
const rpc = Effect.fnUntraced(function* <MethodName extends keyof ClientApi>(
|
|
323
310
|
stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
|
|
324
311
|
methodName: MethodName,
|
|
325
312
|
...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
|
|
@@ -333,7 +320,7 @@ export const Tag =
|
|
|
333
320
|
return yield* invokeRpc(stub, methodName, ...args);
|
|
334
321
|
});
|
|
335
322
|
|
|
336
|
-
const call = Effect.
|
|
323
|
+
const call = Effect.fnUntraced(function* <MethodName extends keyof ClientApi>(
|
|
337
324
|
stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
|
|
338
325
|
methodName: MethodName,
|
|
339
326
|
...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
|
|
@@ -348,9 +335,7 @@ export const Tag =
|
|
|
348
335
|
return yield* invokeCall(stub, methodName, ...args);
|
|
349
336
|
});
|
|
350
337
|
|
|
351
|
-
const scopedCall = Effect.
|
|
352
|
-
MethodName extends keyof ClientApi,
|
|
353
|
-
>(
|
|
338
|
+
const scopedCall = Effect.fnUntraced(function* <MethodName extends keyof ClientApi>(
|
|
354
339
|
stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
|
|
355
340
|
methodName: MethodName,
|
|
356
341
|
...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
|
|
@@ -425,6 +410,9 @@ const wrapHandlers = <ROut, const Self extends Definition.Any>(
|
|
|
425
410
|
wrapped[key] = (...args: Array<unknown>) =>
|
|
426
411
|
Effect.gen(function* () {
|
|
427
412
|
const decodedArgs = yield* RpcDefinition.decodeArgs(definition, key, args);
|
|
413
|
+
|
|
414
|
+
yield* recordDecodedArgs(decodedArgs);
|
|
415
|
+
|
|
428
416
|
const value = yield* handler(...decodedArgs);
|
|
429
417
|
|
|
430
418
|
return yield* RpcDefinition.encodeSuccess(definition, key, value);
|