effect-cf 0.33.0 → 0.35.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 (74) hide show
  1. package/CHANGELOG.md +22 -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 +11 -12
  10. package/dist/ComputerWorkspace.mjs +31 -10
  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-CVLLse69.mjs → DurableObjectState-DcqElJ4e.mjs} +82 -89
  14. package/dist/{DurableObjectState-BpRjz6-q.d.mts → DurableObjectState-DpYdNZgT.d.mts} +102 -266
  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 +8 -26
  23. package/dist/{WorkflowBinding-lBR4_-op.d.mts → WorkflowBinding-Cn3AuUyP.d.mts} +2 -5
  24. package/dist/index.d.mts +42 -480
  25. package/dist/index.mjs +267 -380
  26. package/package.json +1 -1
  27. package/src/AiGateway.ts +0 -6
  28. package/src/AnalyticsEngine.ts +8 -10
  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 +6 -39
  34. package/src/ComputerArtifacts.ts +0 -2
  35. package/src/ComputerWorkspace.ts +53 -35
  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 +51 -32
  40. package/src/DurableObjectAlarm.ts +6 -210
  41. package/src/DurableObjectDefinition.ts +8 -20
  42. package/src/DurableObjectNamespace.ts +88 -115
  43. package/src/DurableObjectRpcWebSocket.ts +5 -46
  44. package/src/DurableObjectSqlite.ts +0 -12
  45. package/src/DurableObjectState.ts +18 -38
  46. package/src/DurableObjectStorage.ts +55 -70
  47. package/src/DurableObjectWebSocket.ts +23 -72
  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 +5 -17
  57. package/src/RpcTracing.ts +133 -0
  58. package/src/Sandbox.ts +0 -30
  59. package/src/ServiceBinding.ts +83 -128
  60. package/src/Vectorize.ts +0 -5
  61. package/src/Vitest.ts +18 -43
  62. package/src/WebTransport.ts +0 -15
  63. package/src/Worker.ts +140 -108
  64. package/src/WorkerDefinition.ts +5 -14
  65. package/src/WorkersAi.ts +21 -18
  66. package/src/Workflow.ts +45 -54
  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/NativeCallback.ts +55 -0
  72. package/src/internal/RpcInvocation.ts +18 -1
  73. package/src/internal/Runtime.ts +12 -7
  74. package/src/internal/WorkerContext.ts +5 -14
@@ -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);