effect-cf 0.33.0 → 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.
Files changed (73) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +31 -1079
  3. package/dist/{Artifacts-DnHQA-Ax.d.mts → Artifacts-B3r3RuTB.d.mts} +1 -48
  4. package/dist/{Artifacts-CSIk1Oml.mjs → Artifacts-CBxWGeZB.mjs} +2 -7
  5. package/dist/{Binding-pfilNJmP.mjs → Binding-C82jRhdr.mjs} +1 -2
  6. package/dist/{Binding-xJJJPmiD.d.mts → Binding-DKcLrQCM.d.mts} +0 -45
  7. package/dist/ComputerArtifacts.d.mts +1 -3
  8. package/dist/ComputerArtifacts.mjs +1 -2
  9. package/dist/ComputerWorkspace.d.mts +1 -10
  10. package/dist/ComputerWorkspace.mjs +1 -2
  11. package/dist/ComputerWorkspaceHost.d.mts +1 -9
  12. package/dist/{ContainerNamespace-CFjiLw8b.d.mts → ContainerNamespace-DbLaeFhN.d.mts} +1 -14
  13. package/dist/{DurableObjectState-BpRjz6-q.d.mts → DurableObjectState-CrEunfQ-.d.mts} +82 -252
  14. package/dist/{DurableObjectState-CVLLse69.mjs → DurableObjectState-De4ZojhY.mjs} +1 -39
  15. package/dist/{ErrorMessage-BM0K-Nik.mjs → ErrorMessage-Byx2OgNd.mjs} +0 -1
  16. package/dist/{Hyperdrive-z-9fFz15.d.mts → Hyperdrive-HrwFlZ6x.d.mts} +1 -4
  17. package/dist/HyperdrivePg.d.mts +2 -11
  18. package/dist/HyperdrivePg.mjs +0 -8
  19. package/dist/Sandbox.d.mts +2 -32
  20. package/dist/Sandbox.mjs +2 -11
  21. package/dist/Vitest.d.mts +3 -31
  22. package/dist/Vitest.mjs +2 -17
  23. package/dist/{WorkflowBinding-lBR4_-op.d.mts → WorkflowBinding-BumVQTuW.d.mts} +2 -5
  24. package/dist/index.d.mts +34 -480
  25. package/dist/index.mjs +174 -312
  26. package/package.json +1 -1
  27. package/src/AiGateway.ts +0 -6
  28. package/src/AnalyticsEngine.ts +0 -9
  29. package/src/Artifacts.ts +0 -47
  30. package/src/Binding.ts +0 -8
  31. package/src/BrowserRendering.ts +0 -4
  32. package/src/Cache.ts +0 -19
  33. package/src/CloudflareOtlp.ts +3 -39
  34. package/src/ComputerArtifacts.ts +0 -2
  35. package/src/ComputerWorkspace.ts +0 -9
  36. package/src/ComputerWorkspaceHost.ts +0 -8
  37. package/src/ContainerNamespace.ts +0 -13
  38. package/src/D1.ts +0 -22
  39. package/src/DurableObject.ts +45 -25
  40. package/src/DurableObjectAlarm.ts +6 -210
  41. package/src/DurableObjectDefinition.ts +8 -20
  42. package/src/DurableObjectNamespace.ts +57 -112
  43. package/src/DurableObjectRpcWebSocket.ts +3 -19
  44. package/src/DurableObjectSqlite.ts +0 -12
  45. package/src/DurableObjectState.ts +0 -18
  46. package/src/DurableObjectStorage.ts +2 -35
  47. package/src/DurableObjectWebSocket.ts +0 -31
  48. package/src/Email.ts +0 -12
  49. package/src/Environment.ts +0 -37
  50. package/src/Hyperdrive.ts +0 -3
  51. package/src/HyperdrivePg.ts +0 -9
  52. package/src/Images.ts +0 -5
  53. package/src/Kv.ts +0 -15
  54. package/src/QueueBinding.ts +0 -2
  55. package/src/R2.ts +0 -5
  56. package/src/RpcDefinition.ts +0 -2
  57. package/src/RpcTracing.ts +133 -0
  58. package/src/Sandbox.ts +0 -30
  59. package/src/ServiceBinding.ts +48 -125
  60. package/src/Vectorize.ts +0 -5
  61. package/src/Vitest.ts +0 -28
  62. package/src/WebTransport.ts +0 -15
  63. package/src/Worker.ts +36 -57
  64. package/src/WorkerDefinition.ts +5 -14
  65. package/src/WorkersAi.ts +0 -4
  66. package/src/Workflow.ts +0 -5
  67. package/src/WorkflowBinding.ts +0 -3
  68. package/src/index.ts +1 -0
  69. package/src/internal/Entrypoint.ts +23 -4
  70. package/src/internal/ErrorMessage.ts +0 -1
  71. package/src/internal/RpcInvocation.ts +18 -1
  72. package/src/internal/Runtime.ts +12 -7
  73. 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) => {
@@ -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
- type RunOptions = {
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
- const RunSymbol = Symbol.for("effect-cf/DurableObject/run");
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
- /** Optional fetch handler for HTTP/WebSocket requests. */
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> & DurableObjectRpcApi<Rpc, ROut>;
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.fn("DurableObject.rpc")(function* <MethodName extends keyof ClientApi>(
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.fn("DurableObject.call")(function* <MethodName extends keyof ClientApi>(
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.fn("DurableObject.scopedCall")(function* <
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);