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
package/src/Worker.ts CHANGED
@@ -1,26 +1,35 @@
1
1
  import { WorkerEntrypoint as CloudflareWorkerEntrypoint } from "cloudflare:workers";
2
- import { type Cause, Context, Effect, Layer, type ManagedRuntime, type Scope } from "effect";
3
- import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
2
+ import {
3
+ type Cause,
4
+ Context,
5
+ Effect,
6
+ Layer,
7
+ type ManagedRuntime,
8
+ References,
9
+ type Scope,
10
+ Tracer,
11
+ } from "effect";
12
+ import {
13
+ HttpEffect,
14
+ HttpMiddleware,
15
+ HttpServerRequest,
16
+ HttpServerResponse,
17
+ } from "effect/unstable/http";
4
18
 
5
19
  import { WorkerEnvironment, type WorkerEnv } from "./Environment";
6
20
  import { fromMessage, fromMessageBatch, type QueueHandler } from "./Queue";
7
- import type * as RpcDefinition from "./RpcDefinition";
21
+ import * as RpcDefinition from "./RpcDefinition";
22
+ import type { ReceiverOptions, RpcInvocationInfo } from "./RpcTracing";
8
23
  import * as Entrypoint from "./internal/Entrypoint";
9
24
  import * as Runtime from "./internal/Runtime";
10
25
  import * as Telemetry from "./internal/Telemetry";
11
26
  import { fromExecutionContext } from "./internal/WorkerContext";
12
27
 
13
- /**
14
- * Access to Cloudflare's native `ExecutionContext`.
15
- */
16
28
  export class ExecutionContext extends Context.Service<
17
29
  ExecutionContext,
18
30
  globalThis.ExecutionContext
19
31
  >()("effect-cf/ExecutionContext") {}
20
32
 
21
- /**
22
- * Options for {@link WorkerContextService.waitUntil}.
23
- */
24
33
  export interface WorkerContextWaitUntilOptions<E, R> {
25
34
  /**
26
35
  * Failure mode for the background effect: `"observe"` (default) logs or
@@ -28,13 +37,9 @@ export interface WorkerContextWaitUntilOptions<E, R> {
28
37
  * native `waitUntil` promise.
29
38
  */
30
39
  readonly mode?: "observe" | "propagate";
31
- /** Custom failure handler for the background effect. */
32
40
  readonly onFailure?: (cause: Cause.Cause<E>) => Effect.Effect<void, never, R>;
33
41
  }
34
42
 
35
- /**
36
- * Effect wrapper around `ExecutionContext` background APIs.
37
- */
38
43
  export interface WorkerContextService {
39
44
  readonly raw: globalThis.ExecutionContext;
40
45
  waitUntil<A, E, R, R2 = never>(
@@ -46,36 +51,17 @@ export interface WorkerContextService {
46
51
  options?: Omit<WorkerContextWaitUntilOptions<E, R2>, "mode">,
47
52
  ): Effect.Effect<void, never, R | R2>;
48
53
  readonly passThroughOnException: Effect.Effect<void>;
49
- /** Abort the current stateless Worker invocation (`ExecutionContext.abort`). */
50
54
  abort(reason?: string): Effect.Effect<void>;
51
55
  }
52
56
 
53
- /**
54
- * Service used inside handlers to schedule background work via `waitUntil`.
55
- *
56
- * @example
57
- * ```ts
58
- * const handler = Effect.gen(function* () {
59
- * const ctx = yield* Worker.WorkerContext;
60
- * yield* ctx.waitUntil(Effect.log("flush analytics"));
61
- * return new Response("ok");
62
- * });
63
- * ```
64
- */
65
57
  export class WorkerContext extends Context.Service<WorkerContext, WorkerContextService>()(
66
58
  "effect-cf/WorkerContext",
67
59
  ) {}
68
60
 
69
- /**
70
- * Access to the incoming `Request` currently handled by a worker or Durable Object fetch.
71
- */
72
61
  export class NativeRequest extends Context.Service<NativeRequest, Request>()(
73
62
  "effect-cf/NativeRequest",
74
63
  ) {}
75
64
 
76
- /**
77
- * Returns `true` when the request is a websocket upgrade request.
78
- */
79
65
  export const isWebSocketUpgrade = (request: Request): boolean =>
80
66
  request.headers.get("Upgrade")?.toLowerCase() === "websocket";
81
67
 
@@ -95,20 +81,13 @@ export type ReservedMethodName =
95
81
  | "webSocketError";
96
82
 
97
83
  const reservedMethodNames = new Set<string>([
98
- "constructor",
99
- "dup",
100
- "fetch",
101
- "connect",
84
+ ...RpcDefinition.reservedMethodNames,
102
85
  "queue",
103
86
  "scheduled",
104
87
  "tail",
105
88
  "tailStream",
106
89
  "test",
107
90
  "trace",
108
- "alarm",
109
- "webSocketMessage",
110
- "webSocketClose",
111
- "webSocketError",
112
91
  ]);
113
92
 
114
93
  type WorkerBaseContext<ROut> = ExecutionContext | WorkerContext | WorkerEnvironment | ROut;
@@ -120,14 +99,16 @@ type WorkerFetchContext<ROut> =
120
99
  type WorkerRpcContext<ROut> = WorkerBaseContext<ROut> | Scope.Scope;
121
100
 
122
101
  type RuntimeContext<ROut> = WorkerBaseContext<ROut>;
123
- const RunSymbol = Symbol.for("effect-cf/Worker/run");
102
+ /** Override to instrument the whole event, then call super[RunSymbol]. */
103
+ export const RunSymbol = Symbol.for("effect-cf/Worker/run");
124
104
  const FetchSymbol = Symbol.for("effect-cf/Worker/fetch");
125
105
 
126
- /**
127
- * Successful result of a worker fetch handler: either a native `Response`
128
- * (returned to Cloudflare untouched) or an Effect `HttpServerResponse`
129
- * (rendered through the Effect HTTP pipeline).
130
- */
106
+ /** Metadata available before an event effect or its event layer starts. */
107
+ export interface RunOptions {
108
+ readonly event?: "fetch" | "rpc" | "queue";
109
+ readonly rpc?: RpcInvocationInfo;
110
+ }
111
+
131
112
  export type WorkerFetchSuccess = Response | HttpServerResponse.HttpServerResponse;
132
113
 
133
114
  /**
@@ -173,9 +154,6 @@ export type RpcHandlers<ROut, Api> = {
173
154
  : never;
174
155
  };
175
156
 
176
- /**
177
- * Options for creating a worker class with Effect handlers.
178
- */
179
157
  export interface WorkerOptions<
180
158
  RRuntime,
181
159
  REvent = never,
@@ -185,19 +163,19 @@ export interface WorkerOptions<
185
163
  /**
186
164
  * Layer provided around each Cloudflare event handled by this Worker.
187
165
  *
188
- * The layer is built inside the event's Effect scope and finalized when the
189
- * event effect completes. Use this for event-scoped resources such as OTLP
190
- * trace/log exporters. Worker fetch and RPC handlers schedule a best-effort
191
- * flush capped at two seconds; queue handlers do not flush automatically.
166
+ * The layer is built inside the event's Effect scope. For fetch handlers that
167
+ * return an Effect `HttpServerResponse` stream, its scope remains open until
168
+ * the response body finishes or is canceled. Native `Response` values pass
169
+ * through unchanged, so their body streams do not carry this scoped-lifetime
170
+ * guarantee. Fetch, queue, and RPC handlers schedule a best-effort telemetry
171
+ * flush capped at two seconds.
192
172
  */
193
173
  readonly eventLayer?: Layer.Layer<REvent, EventLayerError, WorkerBaseContext<RRuntime>>;
194
- /** Main request handler. */
195
174
  readonly fetch?: Effect.Effect<
196
175
  WorkerFetchSuccess,
197
176
  unknown,
198
177
  WorkerFetchContext<RRuntime | REvent>
199
178
  >;
200
- /** Queue consumer handler invoked per message batch. */
201
179
  readonly queue?: QueueHandler<RRuntime | REvent>;
202
180
  /**
203
181
  * Optional RPC methods exposed as class instance methods.
@@ -207,6 +185,8 @@ export interface WorkerOptions<
207
185
  * flush silently settles within two seconds even if the exporter does not.
208
186
  */
209
187
  readonly rpc?: Rpc;
188
+ /** Accept live native trace metadata. The client must also enable rpcTracing. */
189
+ readonly rpcTracing?: ReceiverOptions;
210
190
  }
211
191
 
212
192
  type WorkerOptionsWithEventLayer<
@@ -241,6 +221,10 @@ export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
241
221
  ) => CloudflareWorkerEntrypoint<WorkerEnv> & {
242
222
  fetch(request: Request): Promise<Response>;
243
223
  queue(batch: globalThis.MessageBatch): Promise<void>;
224
+ [RunSymbol]<A, E>(
225
+ effect: Effect.Effect<A, E, WorkerRpcContext<ROut>>,
226
+ options?: RunOptions,
227
+ ): Promise<A>;
244
228
  [FetchSymbol](request: Request, requestContext: globalThis.ExecutionContext): Promise<Response>;
245
229
  } & WorkerRpcContract<Rpc, ROut>;
246
230
 
@@ -255,9 +239,14 @@ export interface FetchHandler<Env extends WorkerEnv = WorkerEnv> {
255
239
  ) => Promise<Response>;
256
240
  }
257
241
 
258
- const renderFetchSuccess = <E, R>(
242
+ const renderFetchSuccess = <E, R, REvent, EventLayerError, EventLayerRequirements>(
259
243
  effect: Effect.Effect<WorkerFetchSuccess, E, R>,
260
- ): Effect.Effect<Response, E, Exclude<R, Scope.Scope> | HttpServerRequest.HttpServerRequest> =>
244
+ eventLayer: Layer.Layer<REvent, EventLayerError, EventLayerRequirements> | undefined,
245
+ ): Effect.Effect<
246
+ Response,
247
+ E | EventLayerError,
248
+ Exclude<R | EventLayerRequirements, Scope.Scope> | HttpServerRequest.HttpServerRequest
249
+ > =>
261
250
  Effect.suspend(() => {
262
251
  // Native `Response` values bypass all HTTP response processing: WebSocket
263
252
  // upgrade responses and app-level `HttpEffect.toHandled` results pass
@@ -266,26 +255,64 @@ const renderFetchSuccess = <E, R>(
266
255
  let nativeResponse: Response | undefined;
267
256
  let webResponse: Response | undefined;
268
257
 
269
- const handled = HttpEffect.toHandled(
270
- Effect.flatMap(effect, (response) => {
271
- if (response instanceof Response) {
272
- nativeResponse = response;
258
+ const httpApp = Effect.flatMap(effect, (response) => {
259
+ if (response instanceof Response) {
260
+ nativeResponse = response;
273
261
 
274
- return Effect.succeed(HttpServerResponse.empty());
275
- }
262
+ return Effect.succeed(HttpServerResponse.empty());
263
+ }
276
264
 
277
- return Effect.succeed(response);
265
+ return Effect.succeed(response);
266
+ });
267
+ const handleResponse = (
268
+ request: HttpServerRequest.HttpServerRequest,
269
+ response: HttpServerResponse.HttpServerResponse,
270
+ ) =>
271
+ nativeResponse !== undefined
272
+ ? Effect.void
273
+ : Effect.map(Effect.context<never>(), (context) => {
274
+ webResponse = HttpServerResponse.toWeb(HttpEffect.scopeTransferToStream(response), {
275
+ withoutBody: request.method === "HEAD",
276
+ context,
277
+ });
278
+ });
279
+ const telemetryMiddleware = HttpMiddleware.make((self) =>
280
+ Effect.gen(function* () {
281
+ yield* Effect.addFinalizer(() => scheduleTelemetryFlush);
282
+
283
+ return yield* self;
278
284
  }),
279
- (request, response) =>
280
- nativeResponse !== undefined
281
- ? Effect.void
282
- : Effect.map(Effect.context<never>(), (context) => {
283
- webResponse = HttpServerResponse.toWeb(HttpEffect.scopeTransferToStream(response), {
284
- withoutBody: request.method === "HEAD",
285
- context,
286
- });
287
- }),
288
285
  );
286
+ const handled =
287
+ eventLayer === undefined
288
+ ? HttpEffect.toHandled(httpApp, handleResponse, telemetryMiddleware)
289
+ : Effect.flatMap(References.TracerEnabled, (tracerEnabled) =>
290
+ HttpEffect.toHandled(
291
+ httpApp,
292
+ handleResponse,
293
+ HttpMiddleware.make((self) =>
294
+ Effect.provideService(
295
+ Effect.gen(function* () {
296
+ const context = yield* Layer.build(Layer.fresh(eventLayer));
297
+
298
+ yield* Effect.addFinalizer(() =>
299
+ Effect.provideContext(scheduleTelemetryFlush, context),
300
+ );
301
+
302
+ return yield* HttpMiddleware.tracer(self).pipe(
303
+ // The tracer schedules span completion. Let it publish on
304
+ // every exit with event references still installed, before
305
+ // the exporter scope finalizes.
306
+ Effect.onExit(() => Effect.yieldNow),
307
+ Effect.provideContext(context),
308
+ );
309
+ }),
310
+ References.TracerEnabled,
311
+ tracerEnabled,
312
+ ),
313
+ ),
314
+ ).pipe(Effect.withTracerEnabled(false)),
315
+ );
289
316
 
290
317
  // `toHandled` re-fails with the original cause after rendering the error
291
318
  // response, so both branches return the response captured above.
@@ -299,13 +326,13 @@ const isWorkerOptions = <ROut, REvent, EventLayerError, Rpc extends WorkerRpc<RO
299
326
  ): options is WorkerOptions<ROut, REvent, EventLayerError, Rpc> => !Effect.isEffect(options);
300
327
 
301
328
  /**
302
- * Drains buffered OTLP telemetry once the fetch handler has produced its
303
- * response.
329
+ * Drains buffered OTLP telemetry when the fetch Effect scope closes. Effect
330
+ * response streams transfer that scope to the body and schedule the flush when
331
+ * the body finishes or is canceled.
304
332
  *
305
- * The flush is handed to `ctx.waitUntil` so it never delays the response
306
- * (including streaming bodies still being consumed). It is a no-op when the
307
- * context does not contain an `OtlpExporter.Flusher`. The handed-off effect is
308
- * best-effort and silently settles within two seconds.
333
+ * The flush is handed to `ctx.waitUntil` instead of being awaited by the
334
+ * handler. It is a no-op when the context does not contain an
335
+ * `OtlpExporter.Flusher`, and silently settles within two seconds.
309
336
  */
310
337
  const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> =
311
338
  Telemetry.scheduleTelemetryFlush((flush) =>
@@ -313,19 +340,10 @@ const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> =
313
340
  );
314
341
 
315
342
  /**
316
- * Creates a Cloudflare worker class backed by a single managed Effect runtime.
317
- *
318
- * The runtime layer is built when Cloudflare constructs the class and reused
319
- * for every event the instance handles. Layer finalizers do not run when the
320
- * isolate is evicted; Cloudflare provides no shutdown hook. Use `eventLayer`
321
- * for resources that must be finalized per event.
322
- *
323
- * @example
324
- * ```ts
325
- * export default Worker.make(Layer.empty, {
326
- * fetch: Effect.succeed(new Response("ok")),
327
- * });
328
- * ```
343
+ * Cloudflare creates a new `WorkerEntrypoint` instance for each native
344
+ * invocation, so this base layer is not shared across invocations. Its managed
345
+ * runtime has no deterministic disposal point in this adapter; use
346
+ * `eventLayer` for resources whose finalizers must run with an event.
329
347
  */
330
348
  export function make<ROut, LayerError>(
331
349
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
@@ -387,15 +405,22 @@ export function make<
387
405
 
388
406
  [RunSymbol]<A, E>(
389
407
  effect: Effect.Effect<A, E, RuntimeContext<ROut> | REvent | Scope.Scope>,
408
+ runOptions: RunOptions = {},
390
409
  ): Promise<A> {
391
- const eventLayer = options.eventLayer;
410
+ // Fetch builds its event layer in the HTTP scope transferred to the body.
411
+ const eventLayer = runOptions.event === "fetch" ? undefined : options.eventLayer;
412
+ const parent = runOptions.rpc?.parent;
413
+ const parentSpan = parent === undefined ? undefined : Tracer.externalSpan(parent);
392
414
 
393
415
  if (eventLayer === undefined) {
394
- // SAFETY: the public make overloads permit an absent event layer only when REvent is never.
416
+ // SAFETY: fetch provides REvent inside its HTTP middleware; otherwise the
417
+ // public make overloads permit an absent event layer only when REvent is never.
395
418
  return Runtime.runEventPromise(
396
419
  this.runtime,
397
420
  // @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
398
421
  effect as Effect.Effect<A, E, RuntimeContext<ROut> | Scope.Scope>,
422
+ undefined,
423
+ parentSpan,
399
424
  );
400
425
  }
401
426
 
@@ -406,7 +431,7 @@ export function make<
406
431
  REvent,
407
432
  EventLayerError,
408
433
  LayerError
409
- >(this.runtime, effect, eventLayer);
434
+ >(this.runtime, effect, eventLayer, parentSpan);
410
435
  }
411
436
 
412
437
  fetch(request: Request): Promise<Response> {
@@ -431,12 +456,12 @@ export function make<
431
456
  Layer.succeed(WorkerContext, fromExecutionContext(ctx)),
432
457
  );
433
458
 
434
- // SAFETY: requestServices provides the HTTP/request contexts introduced by renderFetchSuccess.
459
+ // The request services wrap eventLayer construction so cached object handlers
460
+ // never build an event layer against a previous request's ExecutionContext.
461
+ // HttpEffect owns the inner scope and transfers it to streaming response bodies.
435
462
  return this[RunSymbol](
436
- renderFetchSuccess(fetchHandler).pipe(
437
- Effect.tap(() => scheduleTelemetryFlush),
438
- Effect.provide(requestServices),
439
- ) as Effect.Effect<Response, unknown, RuntimeContext<ROut> | REvent | Scope.Scope>,
463
+ renderFetchSuccess(fetchHandler, options.eventLayer).pipe(Effect.provide(requestServices)),
464
+ { event: "fetch" },
440
465
  );
441
466
  }
442
467
 
@@ -449,7 +474,12 @@ export function make<
449
474
 
450
475
  const messages = batch.messages.map((message) => fromMessage(message, message.body));
451
476
 
452
- return this[RunSymbol](queueHandler(fromMessageBatch(batch, messages)));
477
+ return this[RunSymbol](
478
+ queueHandler(fromMessageBatch(batch, messages)).pipe(
479
+ Effect.onExit(() => scheduleTelemetryFlush),
480
+ ),
481
+ { event: "queue" },
482
+ );
453
483
  }
454
484
  }
455
485
 
@@ -458,8 +488,9 @@ export function make<
458
488
  EffectWorker.prototype,
459
489
  options.rpc,
460
490
  reservedMethodNames,
461
- (self, effect) => self[RunSymbol](effect),
491
+ (self, effect, rpc) => self[RunSymbol](effect, { event: "rpc", rpc }),
462
492
  () => scheduleTelemetryFlush,
493
+ options.rpcTracing,
463
494
  );
464
495
 
465
496
  return Entrypoint.assumeEntrypointClass<WorkerClass<Rpc, ROut | REvent>>(EffectWorker);
@@ -473,12 +504,13 @@ interface FetchCapableInstance {
473
504
  * Creates an `ExportedHandler`-compatible `{ fetch }` object backed by
474
505
  * {@link make}.
475
506
  *
476
- * The Effect runtime is cached per `env` identity, so the user layer builds
477
- * once per isolate instead of once per request. `ExecutionContext`-backed
478
- * services (`Worker.ExecutionContext`, `Worker.WorkerContext`) are still
479
- * provided fresh for every request. Layer finalizers do not run when the
480
- * isolate is evicted; Cloudflare provides no shutdown hook. Use `eventLayer`
481
- * for resources that must be finalized per event.
507
+ * The Effect runtime is cached per `env` identity, so the base layer builds
508
+ * once for that cached handler. Request handlers and `eventLayer` receive fresh
509
+ * `ExecutionContext` and `WorkerContext` services. A service constructed by the
510
+ * cached base layer from either context instead captures the first request's
511
+ * value, so construct invocation-bound services in `eventLayer`. Cloudflare
512
+ * provides no isolate shutdown hook, so cached base-layer finalizers are not
513
+ * guaranteed to run.
482
514
  */
483
515
  export function makeFetchHandler<
484
516
  ROut,
@@ -6,6 +6,7 @@ import * as WorkerEntrypoint from "./Worker";
6
6
  import type { WorkerRpcHandler } from "./Worker";
7
7
  import type * as Rpc from "./Rpc";
8
8
  import * as RpcDefinition from "./RpcDefinition";
9
+ import { recordDecodedArgs } from "./internal/RpcInvocation";
9
10
  import * as ServiceBinding from "./ServiceBinding";
10
11
 
11
12
  /**
@@ -37,7 +38,6 @@ export namespace Method {
37
38
  ? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
38
39
  : Array<S.Schema.Type<Args[number]>>;
39
40
 
40
- /** Method schemas cross the wire through their canonical JSON codec. */
41
41
  type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = {
42
42
  [Index in keyof Args]: S.Json;
43
43
  };
@@ -153,6 +153,7 @@ export type Options<
153
153
 
154
154
  export type LayerOptions = {
155
155
  readonly binding: string;
156
+ readonly rpcTracing?: boolean;
156
157
  };
157
158
 
158
159
  export type TagClass<
@@ -236,19 +237,6 @@ export const method: {
236
237
  }): Method<Args, Success>;
237
238
  } = RpcDefinition.method;
238
239
 
239
- /**
240
- * Creates a typed worker RPC definition plus helpers for implementation and bindings.
241
- *
242
- * @example
243
- * ```ts
244
- * const CounterWorker = WorkerDefinition.make("CounterWorker", {
245
- * increment: WorkerDefinition.method({
246
- * args: [Schema.Number],
247
- * success: Schema.Number,
248
- * }),
249
- * });
250
- * ```
251
- */
252
240
  const makeDefinition = <Id extends string, const MethodDefinitions extends Methods>(
253
241
  id: Id,
254
242
  methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
@@ -414,6 +402,9 @@ const wrapHandlers = <ROut, const Self extends Definition.Any>(
414
402
  wrapped[key] = (...args: Array<unknown>) =>
415
403
  Effect.gen(function* () {
416
404
  const decodedArgs = yield* RpcDefinition.decodeArgs(definition, key, args);
405
+
406
+ yield* recordDecodedArgs(decodedArgs);
407
+
417
408
  const value = yield* handler(...decodedArgs);
418
409
 
419
410
  return yield* RpcDefinition.encodeSuccess(definition, key, value);
package/src/WorkersAi.ts CHANGED
@@ -18,7 +18,6 @@ import * as ErrorMessage from "./internal/ErrorMessage";
18
18
 
19
19
  const expectedWorkersAiBinding = "Workers AI binding with run(), gateway(), and models()";
20
20
 
21
- /** Error raised when a Workers AI operation fails. */
22
21
  export class WorkersAiOperationError extends Data.TaggedError("WorkersAiOperationError")<{
23
22
  readonly binding: string;
24
23
  readonly operation: string;
@@ -29,9 +28,7 @@ export class WorkersAiOperationError extends Data.TaggedError("WorkersAiOperatio
29
28
  }
30
29
  }
31
30
 
32
- /** Typed Workers AI binding definition. */
33
31
  export interface WorkersAiDefinition {
34
- /** Binding name as configured in `wrangler.jsonc`. */
35
32
  readonly binding: string;
36
33
  }
37
34
 
@@ -117,7 +114,6 @@ export interface WorkersAiClient<ModelList extends AiModelListType = AiModels> {
117
114
 
118
115
  declare const WorkersAiServiceTypeId: unique symbol;
119
116
 
120
- /** Nominal service marker for Workers AI services created with {@link make}. */
121
117
  export interface WorkersAiService<Id extends string> {
122
118
  readonly [WorkersAiServiceTypeId]: {
123
119
  readonly id: Id;
@@ -190,7 +186,7 @@ export const embeddingResponse = (value: {
190
186
  [embeddingDimensionsKey]: value[embeddingDimensionsKey] ?? [],
191
187
  });
192
188
 
193
- const decodeEmbeddingResponse = S.decodeUnknownOption(
189
+ const decodeEmbeddingResponse = S.decodeUnknownEffect(
194
190
  S.Struct({
195
191
  data: S.optional(S.Array(S.Array(S.Number))),
196
192
  [embeddingDimensionsKey]: S.optional(S.Array(S.Number)),
@@ -216,21 +212,28 @@ export const makeClient =
216
212
  () => ai.aiGatewayLogId,
217
213
  ),
218
214
  run,
219
- runEmbedding: Effect.fn("WorkersAi.runEmbedding")(
220
- (model: string, input: WorkersAiEmbeddingInput, options?: WorkersAiOptions) => {
221
- // SAFETY: every WorkersAiEmbeddingInput variant is a string-keyed Workers AI payload.
222
- const modelInput = input as Parameters<WorkersAiBinding["run"]>[1];
215
+ runEmbedding: Effect.fn("WorkersAi.runEmbedding")(function* (
216
+ model: string,
217
+ input: WorkersAiEmbeddingInput,
218
+ options?: WorkersAiOptions,
219
+ ) {
220
+ // SAFETY: every WorkersAiEmbeddingInput variant is a string-keyed Workers AI payload.
221
+ const modelInput = input as Parameters<WorkersAiBinding["run"]>[1];
223
222
 
224
- return run(model, modelInput, options).pipe(
225
- Effect.map((response) =>
226
- Option.match(decodeEmbeddingResponse(response), {
227
- onNone: () => embeddingResponse({}),
228
- onSome: embeddingResponse,
223
+ const response = yield* run(model, modelInput, options);
224
+ const decoded = yield* decodeEmbeddingResponse(response).pipe(
225
+ Effect.mapError(
226
+ (cause) =>
227
+ new WorkersAiOperationError({
228
+ binding: definition.binding,
229
+ operation: "runEmbedding",
230
+ cause,
229
231
  }),
230
- ),
231
- );
232
- },
233
- ),
232
+ ),
233
+ );
234
+
235
+ return embeddingResponse(decoded);
236
+ }),
234
237
  models: Effect.fn("WorkersAi.models")((params?: WorkersAiModelsSearchParams) =>
235
238
  tryWorkersAiPromise(definition.binding, "models", () => ai.models(params)),
236
239
  ),