effect-cf 0.20.1 → 0.22.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 (53) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/{Hyperdrive-RTjgirMV.d.mts → Hyperdrive-COyg7R26.d.mts} +40 -13
  4. package/dist/HyperdrivePg.d.mts +13 -5
  5. package/dist/HyperdrivePg.mjs +12 -7
  6. package/dist/index.d.mts +708 -549
  7. package/dist/index.mjs +924 -587
  8. package/package.json +10 -5
  9. package/src/AiGateway.ts +82 -47
  10. package/src/AnalyticsEngine.ts +74 -58
  11. package/src/Binding.ts +66 -13
  12. package/src/BrowserRendering.ts +30 -7
  13. package/src/Cache.ts +32 -8
  14. package/src/CloudflareOtlp.ts +32 -9
  15. package/src/ContainerNamespace.ts +87 -55
  16. package/src/D1.ts +13 -3
  17. package/src/DurableObject.ts +27 -164
  18. package/src/DurableObjectAlarm.ts +30 -9
  19. package/src/DurableObjectDefinition.ts +58 -54
  20. package/src/DurableObjectNamespace.ts +99 -96
  21. package/src/DurableObjectRpcWebSocket.ts +14 -6
  22. package/src/DurableObjectSqlite.ts +6 -3
  23. package/src/DurableObjectState.ts +6 -9
  24. package/src/DurableObjectStorage.ts +7 -1
  25. package/src/DurableObjectWebSocket.ts +51 -41
  26. package/src/Email.ts +40 -13
  27. package/src/Environment.ts +26 -6
  28. package/src/Hyperdrive.ts +2 -2
  29. package/src/HyperdrivePg.ts +25 -13
  30. package/src/Images.ts +77 -82
  31. package/src/Kv.ts +37 -27
  32. package/src/Queue.ts +6 -1
  33. package/src/QueueBinding.ts +18 -21
  34. package/src/QueueDefinition.ts +15 -14
  35. package/src/R2.ts +107 -29
  36. package/src/Rpc.ts +3 -1
  37. package/src/RpcDefinition.ts +177 -133
  38. package/src/ServiceBinding.ts +112 -108
  39. package/src/Vectorize.ts +59 -14
  40. package/src/Worker.ts +172 -192
  41. package/src/WorkerDefinition.ts +5 -10
  42. package/src/WorkersAi.ts +27 -17
  43. package/src/Workflow.ts +54 -49
  44. package/src/WorkflowBinding.ts +19 -11
  45. package/src/WorkflowDefinition.ts +24 -27
  46. package/src/index.ts +4 -3
  47. package/src/internal/Entrypoint.ts +3 -1
  48. package/src/internal/ErrorMessage.ts +24 -0
  49. package/src/internal/RpcInvocation.ts +11 -8
  50. package/src/internal/Runtime.ts +52 -0
  51. package/src/internal/WorkerContext.ts +14 -2
  52. package/src/Entry.ts +0 -259
  53. package/src/internal/Clock.ts +0 -37
package/src/Worker.ts CHANGED
@@ -1,37 +1,48 @@
1
1
  import { WorkerEntrypoint as CloudflareWorkerEntrypoint } from "cloudflare:workers";
2
2
  import {
3
3
  type Cause,
4
- ConfigProvider,
5
4
  Context,
6
5
  Effect,
7
6
  Layer,
8
- ManagedRuntime,
9
- type Schema as S,
7
+ type ManagedRuntime,
8
+ Option,
10
9
  type Scope,
11
10
  } from "effect";
12
11
  import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
12
+ import { OtlpExporter } from "effect/unstable/observability";
13
13
 
14
- import type * as Binding from "./Binding";
15
- import { WorkerConfig, WorkerEnvironment, type WorkerEnv } from "./Environment";
14
+ import { WorkerEnvironment, type WorkerEnv } from "./Environment";
16
15
  import { fromMessage, fromMessageBatch, type QueueHandler } from "./Queue";
17
- import type * as Rpc from "./Rpc";
18
16
  import type * as RpcDefinition from "./RpcDefinition";
19
- import type * as ServiceBinding from "./ServiceBinding";
20
- import * as WorkerDefinition from "./WorkerDefinition";
21
- import * as CloudflareClock from "./internal/Clock";
22
17
  import * as Entrypoint from "./internal/Entrypoint";
23
- import { fromExecutionContext, type RunWaitUntilEffect } from "./internal/WorkerContext";
18
+ import * as Runtime from "./internal/Runtime";
19
+ import { fromExecutionContext } from "./internal/WorkerContext";
24
20
 
21
+ /**
22
+ * Access to Cloudflare's native `ExecutionContext`.
23
+ */
25
24
  export class ExecutionContext extends Context.Service<
26
25
  ExecutionContext,
27
26
  globalThis.ExecutionContext
28
27
  >()("effect-cf/ExecutionContext") {}
29
28
 
29
+ /**
30
+ * Options for {@link WorkerContextService.waitUntil}.
31
+ */
30
32
  export interface WorkerContextWaitUntilOptions<E, R> {
33
+ /**
34
+ * Failure mode for the background effect: `"observe"` (default) logs or
35
+ * routes the failure to `onFailure`, while `"propagate"` also rejects the
36
+ * native `waitUntil` promise.
37
+ */
31
38
  readonly mode?: "observe" | "propagate";
39
+ /** Custom failure handler for the background effect. */
32
40
  readonly onFailure?: (cause: Cause.Cause<E>) => Effect.Effect<void, never, R>;
33
41
  }
34
42
 
43
+ /**
44
+ * Effect wrapper around `ExecutionContext` background APIs.
45
+ */
35
46
  export interface WorkerContextService {
36
47
  readonly raw: globalThis.ExecutionContext;
37
48
  waitUntil<A, E, R, R2 = never>(
@@ -45,14 +56,32 @@ export interface WorkerContextService {
45
56
  readonly passThroughOnException: Effect.Effect<void>;
46
57
  }
47
58
 
59
+ /**
60
+ * Service used inside handlers to schedule background work via `waitUntil`.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const handler = Effect.gen(function* () {
65
+ * const ctx = yield* Worker.WorkerContext;
66
+ * yield* ctx.waitUntil(Effect.log("flush analytics"));
67
+ * return new Response("ok");
68
+ * });
69
+ * ```
70
+ */
48
71
  export class WorkerContext extends Context.Service<WorkerContext, WorkerContextService>()(
49
72
  "effect-cf/WorkerContext",
50
73
  ) {}
51
74
 
75
+ /**
76
+ * Access to the incoming `Request` currently handled by a worker or Durable Object fetch.
77
+ */
52
78
  export class NativeRequest extends Context.Service<NativeRequest, Request>()(
53
79
  "effect-cf/NativeRequest",
54
80
  ) {}
55
81
 
82
+ /**
83
+ * Returns `true` when the request is a websocket upgrade request.
84
+ */
56
85
  export const isWebSocketUpgrade = (request: Request): boolean =>
57
86
  request.headers.get("Upgrade")?.toLowerCase() === "websocket";
58
87
 
@@ -102,17 +131,32 @@ type RunOptions = {
102
131
  };
103
132
 
104
133
  const RunSymbol = Symbol.for("effect-cf/Worker/run");
134
+ const FetchSymbol = Symbol.for("effect-cf/Worker/fetch");
105
135
 
136
+ /**
137
+ * Successful result of a worker fetch handler: either a native `Response`
138
+ * (returned to Cloudflare untouched) or an Effect `HttpServerResponse`
139
+ * (rendered through the Effect HTTP pipeline).
140
+ */
106
141
  export type WorkerFetchSuccess = Response | HttpServerResponse.HttpServerResponse;
107
142
 
143
+ /**
144
+ * Effect type for `fetch` handlers executed by {@link make}.
145
+ */
108
146
  export type WorkerHandler<ROut, A = WorkerFetchSuccess> = Effect.Effect<
109
147
  A,
110
148
  unknown,
111
149
  WorkerFetchContext<ROut>
112
150
  >;
113
151
 
152
+ /**
153
+ * Effect type for worker RPC handlers.
154
+ */
114
155
  export type WorkerRpcHandler<ROut, A = unknown> = Effect.Effect<A, unknown, WorkerRpcContext<ROut>>;
115
156
 
157
+ /**
158
+ * Shape of worker RPC handlers passed to {@link make}.
159
+ */
116
160
  export type WorkerRpc<ROut> = Record<string, (...args: Array<any>) => WorkerRpcHandler<ROut>>;
117
161
 
118
162
  export type WorkerRpcShape<Rpc extends WorkerRpc<ROut>, ROut> = {
@@ -139,6 +183,9 @@ export type RpcHandlers<ROut, Api> = {
139
183
  : never;
140
184
  };
141
185
 
186
+ /**
187
+ * Options for creating a worker class with Effect handlers.
188
+ */
142
189
  export interface WorkerOptions<
143
190
  RRuntime,
144
191
  REvent = never,
@@ -153,12 +200,15 @@ export interface WorkerOptions<
153
200
  * trace/log exporters that should flush at event completion.
154
201
  */
155
202
  readonly eventLayer?: Layer.Layer<REvent, EventLayerError, WorkerBaseContext<RRuntime>>;
203
+ /** Main request handler. */
156
204
  readonly fetch?: Effect.Effect<
157
205
  WorkerFetchSuccess,
158
206
  unknown,
159
207
  WorkerFetchContext<RRuntime | REvent>
160
208
  >;
209
+ /** Queue consumer handler invoked per message batch. */
161
210
  readonly queue?: QueueHandler<RRuntime | REvent>;
211
+ /** Optional RPC methods exposed as class instance methods. */
162
212
  readonly rpc?: Rpc;
163
213
  }
164
214
 
@@ -169,6 +219,9 @@ export type FetchWorkerOptions<RRuntime, REvent = never, EventLayerError = never
169
219
  readonly rpc?: never;
170
220
  };
171
221
 
222
+ /**
223
+ * Cloudflare `WorkerEntrypoint` constructor produced by {@link make}.
224
+ */
172
225
  export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
173
226
  ctx: globalThis.ExecutionContext,
174
227
  env: WorkerEnv,
@@ -177,6 +230,9 @@ export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
177
230
  queue(batch: globalThis.MessageBatch): Promise<void>;
178
231
  } & WorkerRpcShape<Rpc, ROut>;
179
232
 
233
+ /**
234
+ * `ExportedHandler`-compatible fetch object produced by {@link makeFetchHandler}.
235
+ */
180
236
  export interface FetchHandler<Env extends WorkerEnv = WorkerEnv> {
181
237
  readonly fetch: (
182
238
  request: Request,
@@ -185,22 +241,14 @@ export interface FetchHandler<Env extends WorkerEnv = WorkerEnv> {
185
241
  ) => Promise<Response>;
186
242
  }
187
243
 
188
- export const renderHttpResponse = <A extends HttpServerResponse.HttpServerResponse, E, R>(
189
- effect: Effect.Effect<A, E, R>,
190
- ): Effect.Effect<Response, E, R> =>
191
- Effect.flatMap(effect, (response) =>
192
- Effect.map(Effect.context<never>(), (context) =>
193
- HttpServerResponse.toWeb(response, { context }),
194
- ),
195
- );
196
-
197
244
  const renderFetchSuccess = <E, R>(
198
245
  effect: Effect.Effect<WorkerFetchSuccess, E, R>,
199
246
  ): Effect.Effect<Response, E, Exclude<R, Scope.Scope> | HttpServerRequest.HttpServerRequest> =>
200
247
  Effect.suspend(() => {
201
- // Native `Response` values bypass all HTTP response processing (including
202
- // pre-response handlers), so app-level `HttpEffect.toHandled` wrappers and
203
- // WebSocket upgrade responses pass through untouched.
248
+ // Native `Response` values bypass all HTTP response processing: WebSocket
249
+ // upgrade responses and app-level `HttpEffect.toHandled` results pass
250
+ // through untouched. Pre-response handlers still run against a placeholder
251
+ // response, but their output is discarded for native `Response` results.
204
252
  let nativeResponse: Response | undefined;
205
253
  let webResponse: Response | undefined;
206
254
 
@@ -234,11 +282,40 @@ const renderFetchSuccess = <E, R>(
234
282
 
235
283
  const isWorkerOptions = <ROut, REvent, EventLayerError, Rpc extends WorkerRpc<ROut | REvent>>(
236
284
  options: WorkerOptions<ROut, REvent, EventLayerError, Rpc> | WorkerHandler<ROut>,
237
- ): options is WorkerOptions<ROut, REvent, EventLayerError, Rpc> =>
238
- typeof options === "object" &&
239
- options !== null &&
240
- ("eventLayer" in options || "fetch" in options || "queue" in options || "rpc" in options);
241
-
285
+ ): options is WorkerOptions<ROut, REvent, EventLayerError, Rpc> => !Effect.isEffect(options);
286
+
287
+ /**
288
+ * Drains buffered OTLP telemetry once the fetch handler has produced its
289
+ * response.
290
+ *
291
+ * The flush is handed to `ctx.waitUntil` so it never delays the response
292
+ * (including streaming bodies still being consumed). It is a no-op when the
293
+ * context does not contain an `OtlpExporter.Flusher`.
294
+ */
295
+ const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> = Effect.flatMap(
296
+ Effect.serviceOption(OtlpExporter.Flusher),
297
+ Option.match({
298
+ onNone: () => Effect.void,
299
+ onSome: (flusher) =>
300
+ Effect.flatMap(WorkerContext, (workerContext) => workerContext.waitUntil(flusher.flush)),
301
+ }),
302
+ );
303
+
304
+ /**
305
+ * Creates a Cloudflare worker class backed by a single managed Effect runtime.
306
+ *
307
+ * The runtime layer is built when Cloudflare constructs the class and reused
308
+ * for every event the instance handles. Layer finalizers do not run when the
309
+ * isolate is evicted; Cloudflare provides no shutdown hook. Use `eventLayer`
310
+ * for resources that must be finalized per event.
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * export default Worker.make(Layer.empty, {
315
+ * fetch: Effect.succeed(new Response("ok")),
316
+ * });
317
+ * ```
318
+ */
242
319
  export function make<ROut, LayerError>(
243
320
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
244
321
  fetch: WorkerHandler<ROut>,
@@ -273,69 +350,58 @@ export function make<
273
350
  constructor(ctx: globalThis.ExecutionContext, env: WorkerEnv) {
274
351
  super(ctx, env);
275
352
 
276
- let runWaitUntilEffect: RunWaitUntilEffect = () =>
277
- Promise.reject(new Error("WorkerContext runtime is not initialized"));
278
-
279
- const services = Layer.mergeAll(
280
- CloudflareClock.layer,
281
- Layer.succeed(ExecutionContext, ctx),
282
- ConfigProvider.layer(Effect.succeed(WorkerConfig.providerFromEnv(env))),
283
- Layer.succeed(
284
- WorkerContext,
285
- fromExecutionContext(ctx, (effect) => runWaitUntilEffect(effect)),
286
- ),
287
- Layer.succeed(WorkerEnvironment, env),
288
- ) as Layer.Layer<ExecutionContext | WorkerContext | WorkerEnvironment, never, never>;
289
-
290
- const runtimeLayer = Entrypoint.provideEntrypointServices<
353
+ this.runtime = Runtime.makeEntrypointRuntime<
291
354
  ROut,
292
355
  LayerError,
293
- ExecutionContext | WorkerContext | WorkerEnvironment
294
- >(layer, services);
295
-
296
- this.runtime = ManagedRuntime.make(runtimeLayer);
297
- runWaitUntilEffect = <A, E>(effect: Effect.Effect<A, E, never>) =>
298
- this.runtime.runPromiseExit(effect as Effect.Effect<A, E, RuntimeContext<ROut>>);
356
+ ExecutionContext | WorkerContext
357
+ >(
358
+ layer,
359
+ env,
360
+ Layer.mergeAll(
361
+ Layer.succeed(ExecutionContext, ctx),
362
+ Layer.succeed(WorkerContext, fromExecutionContext(ctx)),
363
+ ),
364
+ );
299
365
  }
300
366
 
301
367
  [RunSymbol]<A, E>(
302
368
  effect: Effect.Effect<A, E, RuntimeContext<ROut> | REvent | Scope.Scope>,
303
369
  runOptions: RunOptions = {},
304
370
  ): Promise<A> {
305
- const effectWithEventLayer =
306
- runOptions.eventLayer === false || options.eventLayer === undefined
307
- ? effect
308
- : effect.pipe(Effect.provide(options.eventLayer, { local: true }));
309
-
310
- return this.runtime.runPromise(
311
- Effect.scoped(
312
- effectWithEventLayer as Effect.Effect<
313
- A,
314
- E | EventLayerError,
315
- RuntimeContext<ROut> | Scope.Scope
316
- >,
317
- ),
371
+ return Runtime.runEventPromise(
372
+ this.runtime,
373
+ effect,
374
+ runOptions.eventLayer === false ? undefined : options.eventLayer,
318
375
  );
319
376
  }
320
377
 
321
378
  fetch(request: Request): Promise<Response> {
379
+ return this[FetchSymbol](request);
380
+ }
381
+
382
+ [FetchSymbol](
383
+ request: Request,
384
+ requestContext?: globalThis.ExecutionContext,
385
+ ): Promise<Response> {
322
386
  const fetchHandler = options.fetch;
323
387
 
324
388
  if (fetchHandler === undefined) {
325
389
  return Promise.resolve(new Response("Not Found", { status: 404 }));
326
390
  }
327
391
 
392
+ const ctx = requestContext ?? this.ctx;
328
393
  const requestServices = Layer.mergeAll(
329
394
  Layer.succeed(NativeRequest, request),
330
395
  Layer.succeed(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromWeb(request)),
396
+ Layer.succeed(ExecutionContext, ctx),
397
+ Layer.succeed(WorkerContext, fromExecutionContext(ctx)),
331
398
  );
332
399
 
333
400
  return this[RunSymbol](
334
- renderFetchSuccess(fetchHandler).pipe(Effect.provide(requestServices)) as Effect.Effect<
335
- Response,
336
- unknown,
337
- RuntimeContext<ROut> | REvent | Scope.Scope
338
- >,
401
+ renderFetchSuccess(fetchHandler).pipe(
402
+ Effect.tap(() => scheduleTelemetryFlush),
403
+ Effect.provide(requestServices),
404
+ ) as Effect.Effect<Response, unknown, RuntimeContext<ROut> | REvent | Scope.Scope>,
339
405
  );
340
406
  }
341
407
 
@@ -363,6 +429,21 @@ export function make<
363
429
  return Entrypoint.assumeEntrypointClass<WorkerClass<Rpc, ROut | REvent>>(EffectWorker);
364
430
  }
365
431
 
432
+ interface FetchCapableInstance {
433
+ [FetchSymbol](request: Request, requestContext: globalThis.ExecutionContext): Promise<Response>;
434
+ }
435
+
436
+ /**
437
+ * Creates an `ExportedHandler`-compatible `{ fetch }` object backed by
438
+ * {@link make}.
439
+ *
440
+ * The Effect runtime is cached per `env` identity, so the user layer builds
441
+ * once per isolate instead of once per request. `ExecutionContext`-backed
442
+ * services (`Worker.ExecutionContext`, `Worker.WorkerContext`) are still
443
+ * provided fresh for every request. Layer finalizers do not run when the
444
+ * isolate is evicted; Cloudflare provides no shutdown hook. Use `eventLayer`
445
+ * for resources that must be finalized per event.
446
+ */
366
447
  export const makeFetchHandler = <
367
448
  ROut,
368
449
  LayerError,
@@ -374,136 +455,35 @@ export const makeFetchHandler = <
374
455
  options: FetchWorkerOptions<ROut, REvent, EventLayerError>,
375
456
  ): FetchHandler<Env> => {
376
457
  const WorkerClass = make(layer, options);
458
+ const instances = new WeakMap<WorkerEnv, FetchCapableInstance>();
377
459
 
378
460
  return {
379
- fetch: (request, env, ctx) => Promise.resolve(new WorkerClass(ctx, env).fetch(request)),
380
- };
381
- };
382
-
383
- export type ServiceFreeSchema = S.Codec<any, any, never, never>;
384
-
385
- export interface Method<
386
- Args extends ReadonlyArray<ServiceFreeSchema> = ReadonlyArray<ServiceFreeSchema>,
387
- Success extends ServiceFreeSchema = ServiceFreeSchema,
388
- > {
389
- readonly args: Args;
390
- readonly success: Success;
391
- }
392
-
393
- export namespace Method {
394
- export type Any = Method<ReadonlyArray<ServiceFreeSchema>, ServiceFreeSchema>;
461
+ fetch: (request, env, ctx) => {
462
+ let instance = instances.get(env);
395
463
 
396
- type ArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = Args extends readonly []
397
- ? []
398
- : Args extends readonly [
399
- infer Head extends ServiceFreeSchema,
400
- ...infer Tail extends ReadonlyArray<ServiceFreeSchema>,
401
- ]
402
- ? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
403
- : Array<S.Schema.Type<Args[number]>>;
404
-
405
- export type Args<Self extends Any> = ArgsFromSchemas<Self["args"]>;
406
-
407
- export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
408
- }
409
-
410
- export type Methods = Record<string, Method.Any>;
411
-
412
- export type NoReservedMethods<MethodsShape extends Methods> =
413
- Extract<keyof MethodsShape, ReservedMethodName> extends never ? MethodsShape : never;
414
-
415
- export interface Definition<Id extends string = string, MethodsShape extends Methods = Methods> {
416
- readonly id: Id;
417
- readonly methods: MethodsShape;
418
- }
419
-
420
- export namespace Definition {
421
- export type Any = Definition<string, Methods>;
422
- }
423
-
424
- export type ServerApi<Self extends Definition.Any> = {
425
- readonly [Key in keyof Self["methods"]]: (
426
- ...args: Method.Args<Self["methods"][Key]>
427
- ) => Promise<Method.Success<Self["methods"][Key]>>;
428
- };
429
-
430
- export type Api<Self extends Definition.Any> = Rpc.Provider<ServerApi<Self>, ReservedMethodName>;
431
-
432
- export type Handlers<ROut, Self extends Definition.Any> = {
433
- readonly [Key in keyof Self["methods"]]: (
434
- ...args: Method.Args<Self["methods"][Key]>
435
- ) => WorkerRpcHandler<ROut, Method.Success<Self["methods"][Key]>>;
436
- };
437
-
438
- export interface Options<
439
- ROut,
440
- Self extends Definition.Any,
441
- REvent = never,
442
- EventLayerError = never,
443
- > extends Omit<WorkerOptions<ROut, REvent, EventLayerError, Handlers<ROut | REvent, Self>>, "rpc"> {
444
- readonly rpc: Handlers<ROut | REvent, Self>;
445
- }
464
+ if (instance === undefined) {
465
+ instance = new WorkerClass(ctx, env) as unknown as FetchCapableInstance;
466
+ instances.set(env, instance);
467
+ }
446
468
 
447
- export type LayerOptions = WorkerDefinition.LayerOptions;
448
-
449
- export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Context.ServiceClass<
450
- Self,
451
- Id,
452
- ServiceBinding.ServiceBindingEffectClient<
453
- Api<Definition<Id, MethodsShape>>,
454
- Definition<Id, MethodsShape>
455
- >
456
- > &
457
- ServiceBinding.ServiceBindingStaticClient<
458
- Self,
459
- Api<Definition<Id, MethodsShape>>,
460
- Definition<Id, MethodsShape>
461
- > & {
462
- readonly id: Id;
463
- readonly methods: MethodsShape;
464
- readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
465
- layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
466
- options: Options<ROut, Definition<Id, MethodsShape>, REvent, EventLayerError>,
467
- ) => WorkerClass<Handlers<ROut | REvent, Definition<Id, MethodsShape>>, ROut | REvent>;
468
- readonly layer: (
469
- options: LayerOptions,
470
- ) => Layer.Layer<
471
- Self,
472
- Binding.BindingNotFoundError | Binding.BindingValidationError,
473
- WorkerEnvironment
474
- >;
469
+ return instance[FetchSymbol](request, ctx);
470
+ },
475
471
  };
476
-
477
- export type TagFactory = <Self>() => <Id extends string, const MethodsShape extends Methods>(
478
- id: Id,
479
- methods: MethodsShape & NoReservedMethods<MethodsShape>,
480
- ) => TagClass<Self, Id, MethodsShape>;
481
-
482
- export const Tag = WorkerDefinition.Tag as unknown as TagFactory;
483
-
484
- export const method = WorkerDefinition.method as {
485
- <Success extends ServiceFreeSchema>(definition: {
486
- readonly success: Success;
487
- }): Method<readonly [], Success>;
488
- <
489
- const Args extends ReadonlyArray<ServiceFreeSchema>,
490
- Success extends ServiceFreeSchema,
491
- >(definition: {
492
- readonly args: Args;
493
- readonly success: Success;
494
- }): Method<Args, Success>;
495
472
  };
496
473
 
497
- export const implement = WorkerDefinition.implement as unknown as <
498
- ROut,
499
- const Self extends Definition.Any,
500
- >(
501
- _definition: Self,
502
- handlers: Handlers<ROut, Self>,
503
- ) => Handlers<ROut, Self>;
504
-
505
- export type HandlerEffect<
506
- ROut,
507
- Self extends Definition.Any,
508
- Key extends keyof Self["methods"],
509
- > = WorkerRpcHandler<ROut, Method.Success<Self["methods"][Key]>>;
474
+ export type {
475
+ Api,
476
+ Definition,
477
+ HandlerEffect,
478
+ Handlers,
479
+ LayerOptions,
480
+ Method,
481
+ Methods,
482
+ NoReservedMethods,
483
+ Options,
484
+ ServerApi,
485
+ ServiceFreeSchema,
486
+ TagClass,
487
+ } from "./WorkerDefinition";
488
+
489
+ export { implement, method, Tag } from "./WorkerDefinition";
@@ -37,15 +37,10 @@ export namespace Method {
37
37
  ? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
38
38
  : Array<S.Schema.Type<Args[number]>>;
39
39
 
40
- type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> =
41
- Args extends readonly []
42
- ? []
43
- : Args extends readonly [
44
- infer Head extends ServiceFreeSchema,
45
- ...infer Tail extends ReadonlyArray<ServiceFreeSchema>,
46
- ]
47
- ? [S.Codec.Encoded<Head>, ...EncodedArgsFromSchemas<Tail>]
48
- : Array<S.Codec.Encoded<Args[number]>>;
40
+ /** Method schemas cross the wire through their canonical JSON codec. */
41
+ type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = {
42
+ [Index in keyof Args]: S.Json;
43
+ };
49
44
 
50
45
  export type Args<Self extends Any> = ArgsFromSchemas<Self["args"]>;
51
46
 
@@ -53,7 +48,7 @@ export namespace Method {
53
48
 
54
49
  export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
55
50
 
56
- export type EncodedSuccess<Self extends Any> = S.Codec.Encoded<Self["success"]>;
51
+ export type EncodedSuccess<Self extends Any> = S.Json;
57
52
  }
58
53
 
59
54
  export type Methods = Record<string, Method.Any>;
package/src/WorkersAi.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
 
15
15
  import * as Binding from "./Binding";
16
16
  import type { WorkerEnvironment } from "./Environment";
17
+ import * as ErrorMessage from "./internal/ErrorMessage";
17
18
 
18
19
  const expectedWorkersAiBinding = "Workers AI binding with run(), gateway(), and models()";
19
20
 
@@ -22,7 +23,11 @@ export class WorkersAiOperationError extends Data.TaggedError("WorkersAiOperatio
22
23
  readonly binding: string;
23
24
  readonly operation: string;
24
25
  readonly cause: unknown;
25
- }> {}
26
+ }> {
27
+ override get message(): string {
28
+ return `Workers AI ${this.operation} failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
29
+ }
30
+ }
26
31
 
27
32
  /** Typed Workers AI binding definition. */
28
33
  export interface WorkersAiDefinition {
@@ -101,7 +106,7 @@ export interface WorkersAiClient<ModelList extends AiModelListType = AiModels> {
101
106
  readonly gateway: (
102
107
  gatewayId: string,
103
108
  ) => Effect.Effect<CloudflareAiGateway, WorkersAiOperationError>;
104
- readonly unsafeRaw: Effect.Effect<WorkersAiBinding<ModelList>>;
109
+ readonly rawUnsafe: Effect.Effect<WorkersAiBinding<ModelList>>;
105
110
  readonly definition: WorkersAiDefinition;
106
111
  }
107
112
 
@@ -177,10 +182,12 @@ export const embeddingResponse = (value: {
177
182
  export const makeClient =
178
183
  <ModelList extends AiModelListType = AiModels>(definition: WorkersAiDefinition) =>
179
184
  (ai: WorkersAiBinding<ModelList>): WorkersAiClient<ModelList> => {
180
- const run = ((model: string, input: Record<string, unknown>, options?: WorkersAiOptions) =>
181
- tryWorkersAiPromise(definition.binding, "run", () =>
182
- ai.run(model as string & {}, input, options),
183
- )) as WorkersAiClient<ModelList>["run"];
185
+ const run = Effect.fn("WorkersAi.run")(
186
+ (model: string, input: Record<string, unknown>, options?: WorkersAiOptions) =>
187
+ tryWorkersAiPromise(definition.binding, "run", () =>
188
+ ai.run(model as string & {}, input, options),
189
+ ),
190
+ ) as WorkersAiClient<ModelList>["run"];
184
191
 
185
192
  return {
186
193
  definition,
@@ -190,22 +197,25 @@ export const makeClient =
190
197
  () => ai.aiGatewayLogId,
191
198
  ),
192
199
  run,
193
- runEmbedding: (model, input, options) =>
194
- run(model, input, options).pipe(
195
- Effect.map((response) =>
196
- embeddingResponse(
197
- response as {
198
- readonly data?: ReadonlyArray<ReadonlyArray<number>>;
199
- readonly shape?: ReadonlyArray<number>;
200
- },
200
+ runEmbedding: Effect.fn("WorkersAi.runEmbedding")(
201
+ (model: string, input: WorkersAiEmbeddingInput, options?: WorkersAiOptions) =>
202
+ run(model, input as Record<string, unknown>, options).pipe(
203
+ Effect.map((response) =>
204
+ embeddingResponse(
205
+ response as {
206
+ readonly data?: ReadonlyArray<ReadonlyArray<number>>;
207
+ readonly shape?: ReadonlyArray<number>;
208
+ },
209
+ ),
201
210
  ),
202
211
  ),
203
- ),
204
- models: (params) =>
212
+ ),
213
+ models: Effect.fn("WorkersAi.models")((params?: WorkersAiModelsSearchParams) =>
205
214
  tryWorkersAiPromise(definition.binding, "models", () => ai.models(params)),
215
+ ),
206
216
  gateway: (gatewayId) =>
207
217
  tryWorkersAiSync(definition.binding, "gateway", () => ai.gateway(gatewayId)),
208
- unsafeRaw: Effect.succeed(ai),
218
+ rawUnsafe: Effect.succeed(ai),
209
219
  };
210
220
  };
211
221