effect-cf 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,10 +10,8 @@ import {
10
10
  OtlpTracer,
11
11
  } from "effect/unstable/observability";
12
12
 
13
- import * as DurableObject from "./DurableObject";
14
13
  import { DurableObjectState } from "./DurableObjectState";
15
14
  import { WorkerConfig, WorkerEnvironment } from "./Environment";
16
- import * as Worker from "./Worker";
17
15
 
18
16
  /** Telemetry signal groups supported by the Cloudflare OTLP layers. */
19
17
  export type Signal = "logs" | "traces" | "metrics";
@@ -516,113 +514,3 @@ export const durableObjectLayer = (options: LayerOptions & DurableObjectResource
516
514
  }),
517
515
  ),
518
516
  );
519
-
520
- const provideRpcLayer = <
521
- Rpc extends Record<string, (...args: Array<any>) => Effect.Effect<any, any, any>>,
522
- RLayer,
523
- >(
524
- rpc: Rpc | undefined,
525
- layer: Layer.Layer<never, never, RLayer>,
526
- ): Rpc | undefined => {
527
- if (rpc === undefined) {
528
- return undefined;
529
- }
530
-
531
- return new Proxy(rpc, {
532
- get(target, property, receiver) {
533
- const handler = Reflect.get(target, property, receiver);
534
- if (typeof handler !== "function") {
535
- return handler;
536
- }
537
-
538
- return (...args: Array<any>) =>
539
- Effect.suspend(() => Reflect.apply(handler, receiver, args)).pipe(Effect.provide(layer));
540
- },
541
- });
542
- };
543
-
544
- /**
545
- * Instrument Worker lifecycle handlers with event-scoped OTLP telemetry.
546
- *
547
- * Each defined handler is provided with {@link workerLayer}. Because Worker
548
- * handlers run inside `Effect.scoped`, the exporter scope closes and flushes at
549
- * the end of each Cloudflare event.
550
- */
551
- export const instrumentWorkerOptions =
552
- (options?: LayerOptions & WorkerResourceOptions) =>
553
- <ROut, Rpc extends Worker.WorkerRpc<ROut>>(
554
- handlers: Worker.WorkerOptions<ROut, Rpc>,
555
- ): Worker.WorkerOptions<ROut | WorkerEnvironment | CloudflareOtlpSettings, Rpc> => {
556
- const telemetryLayer = workerLayer(options);
557
- const queue = handlers.queue;
558
-
559
- return {
560
- ...handlers,
561
- fetch:
562
- handlers.fetch === undefined
563
- ? undefined
564
- : handlers.fetch.pipe(Effect.provide(telemetryLayer)),
565
- queue:
566
- queue === undefined
567
- ? undefined
568
- : (batch) => queue(batch).pipe(Effect.provide(telemetryLayer)),
569
- rpc: provideRpcLayer(handlers.rpc, telemetryLayer),
570
- };
571
- };
572
-
573
- /**
574
- * Instrument Durable Object lifecycle handlers with event-scoped OTLP telemetry.
575
- *
576
- * Each defined handler is provided with {@link durableObjectLayer}. Because
577
- * Durable Object handlers run inside `Effect.scoped`, the exporter scope closes
578
- * and flushes at the end of each Cloudflare event.
579
- */
580
- export const instrumentDurableObjectOptions =
581
- (options?: LayerOptions & DurableObjectResourceOptions) =>
582
- <ROut, Rpc extends DurableObject.DurableObjectRpc<ROut>>(
583
- handlers: DurableObject.DurableObjectOptions<ROut, Rpc>,
584
- ): DurableObject.DurableObjectOptions<
585
- ROut | WorkerEnvironment | DurableObjectState | CloudflareOtlpSettings,
586
- Rpc
587
- > => {
588
- const telemetryLayer = durableObjectLayer(options);
589
- const alarm = handlers.alarm;
590
- const webSocketMessage = handlers.webSocketMessage;
591
- const webSocketClose = handlers.webSocketClose;
592
- const webSocketError = handlers.webSocketError;
593
-
594
- return {
595
- ...handlers,
596
- initialize:
597
- handlers.initialize === undefined
598
- ? undefined
599
- : handlers.initialize.pipe(Effect.provide(telemetryLayer)),
600
- fetch:
601
- handlers.fetch === undefined
602
- ? undefined
603
- : handlers.fetch.pipe(Effect.provide(telemetryLayer)),
604
- alarms:
605
- handlers.alarms === undefined
606
- ? undefined
607
- : handlers.alarms.pipe(Effect.provide(telemetryLayer)),
608
- alarm:
609
- alarm === undefined
610
- ? undefined
611
- : (alarmInfo) => alarm(alarmInfo).pipe(Effect.provide(telemetryLayer)),
612
- webSocketMessage:
613
- webSocketMessage === undefined
614
- ? undefined
615
- : (socket, message) =>
616
- webSocketMessage(socket, message).pipe(Effect.provide(telemetryLayer)),
617
- webSocketClose:
618
- webSocketClose === undefined
619
- ? undefined
620
- : (socket, code, reason, wasClean) =>
621
- webSocketClose(socket, code, reason, wasClean).pipe(Effect.provide(telemetryLayer)),
622
- webSocketError:
623
- webSocketError === undefined
624
- ? undefined
625
- : (socket, error) => webSocketError(socket, error).pipe(Effect.provide(telemetryLayer)),
626
- rpc: provideRpcLayer(handlers.rpc, telemetryLayer),
627
- };
628
- };
@@ -10,6 +10,7 @@ import type * as Binding from "./Binding";
10
10
  import * as DurableObjectDefinition from "./DurableObjectDefinition";
11
11
  import type * as DurableObjectNamespace from "./DurableObjectNamespace";
12
12
  import type * as Rpc from "./Rpc";
13
+ import * as CloudflareClock from "./internal/Clock";
13
14
  import * as Entrypoint from "./internal/Entrypoint";
14
15
 
15
16
  const reservedMethodNames = new Set<string>([
@@ -27,6 +28,9 @@ type RuntimeContext<ROut> = DurableObjectState | WorkerEnvironment | ROut;
27
28
  type HandlerContext<ROut> = RuntimeContext<ROut> | Scope.Scope;
28
29
 
29
30
  type FetchContext<ROut> = HandlerContext<ROut> | NativeRequest;
31
+ type RunOptions = {
32
+ readonly eventLayer?: boolean;
33
+ };
30
34
 
31
35
  const RunSymbol = Symbol.for("effect-cf/DurableObject/run");
32
36
 
@@ -72,7 +76,24 @@ export type RpcHandlers<ROut, Api> = {
72
76
  /**
73
77
  * Options for creating a Durable Object class backed by Effect handlers.
74
78
  */
75
- export interface DurableObjectOptions<ROut, Rpc extends DurableObjectRpc<ROut>> {
79
+ export interface DurableObjectOptions<
80
+ RRuntime,
81
+ REvent = never,
82
+ EventLayerError = never,
83
+ Rpc extends DurableObjectRpc<RRuntime | REvent> = Record<never, never>,
84
+ > {
85
+ /**
86
+ * Layer provided around each Cloudflare event handled by this Durable Object.
87
+ *
88
+ * The layer is built inside the event's Effect scope and finalized when the
89
+ * event effect completes. It is not applied to `initialize`, which is an
90
+ * instance-load lifecycle hook rather than a platform event.
91
+ */
92
+ readonly eventLayer?: Layer.Layer<
93
+ REvent,
94
+ EventLayerError,
95
+ DurableObjectState | WorkerEnvironment | RRuntime
96
+ >;
76
97
  /**
77
98
  * Effect run when Cloudflare loads this Durable Object instance into memory.
78
99
  *
@@ -81,11 +102,11 @@ export interface DurableObjectOptions<ROut, Rpc extends DurableObjectRpc<ROut>>
81
102
  * the same Durable Object id again after eviction or restart; use Durable
82
103
  * Object storage if work must happen only once per id.
83
104
  */
84
- readonly initialize?: Effect.Effect<void, unknown, HandlerContext<ROut>>;
105
+ readonly initialize?: Effect.Effect<void, unknown, HandlerContext<RRuntime>>;
85
106
  /** Optional RPC methods exposed as Durable Object instance methods. */
86
107
  readonly rpc?: Rpc;
87
108
  /** Optional fetch handler for HTTP/WebSocket requests. */
88
- readonly fetch?: Effect.Effect<Response, unknown, FetchContext<ROut>>;
109
+ readonly fetch?: Effect.Effect<Response, unknown, FetchContext<RRuntime | REvent>>;
89
110
  /**
90
111
  * Optional logical alarm processing effect.
91
112
  *
@@ -93,24 +114,24 @@ export interface DurableObjectOptions<ROut, Rpc extends DurableObjectRpc<ROut>>
93
114
  * `DurableObjectAlarm.processDue(...)` so the reusable scheduler stays inside
94
115
  * the Durable Object's single managed runtime boundary.
95
116
  */
96
- readonly alarms?: Effect.Effect<unknown, unknown, HandlerContext<ROut>>;
117
+ readonly alarms?: Effect.Effect<unknown, unknown, HandlerContext<RRuntime | REvent>>;
97
118
  readonly alarm?: (
98
119
  alarmInfo?: globalThis.AlarmInvocationInfo,
99
- ) => Effect.Effect<void, unknown, HandlerContext<ROut>>;
120
+ ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
100
121
  readonly webSocketMessage?: (
101
122
  socket: DurableWebSocket,
102
123
  message: string | ArrayBuffer,
103
- ) => Effect.Effect<void, unknown, HandlerContext<ROut>>;
124
+ ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
104
125
  readonly webSocketClose?: (
105
126
  socket: DurableWebSocket,
106
127
  code: number,
107
128
  reason: string,
108
129
  wasClean: boolean,
109
- ) => Effect.Effect<void, unknown, HandlerContext<ROut>>;
130
+ ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
110
131
  readonly webSocketError?: (
111
132
  socket: DurableWebSocket,
112
133
  error: unknown,
113
- ) => Effect.Effect<void, unknown, HandlerContext<ROut>>;
134
+ ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
114
135
  }
115
136
 
116
137
  /**
@@ -127,11 +148,13 @@ export type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (
127
148
  export const make = <
128
149
  ROut,
129
150
  LayerError,
130
- const Rpc extends DurableObjectRpc<ROut> = Record<never, never>,
151
+ REvent = never,
152
+ EventLayerError = never,
153
+ const Rpc extends DurableObjectRpc<ROut | REvent> = Record<never, never>,
131
154
  >(
132
155
  layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
133
- options: DurableObjectOptions<ROut, Rpc> = {},
134
- ): DurableObjectClass<Rpc, ROut> => {
156
+ options: DurableObjectOptions<ROut, REvent, EventLayerError, Rpc> = {},
157
+ ): DurableObjectClass<Rpc, ROut | REvent> => {
135
158
  class EffectDurableObject extends CloudflareDurableObject<WorkerEnv> {
136
159
  readonly runtime: ManagedRuntime.ManagedRuntime<RuntimeContext<ROut>, LayerError>;
137
160
 
@@ -139,6 +162,7 @@ export const make = <
139
162
  super(state, env);
140
163
 
141
164
  const services = Layer.mergeAll(
165
+ CloudflareClock.layer,
142
166
  Layer.succeed(DurableObjectState, fromDurableObjectState(state)),
143
167
  Layer.succeed(WorkerEnvironment, env),
144
168
  );
@@ -149,12 +173,24 @@ export const make = <
149
173
 
150
174
  const initialize = options.initialize;
151
175
  if (initialize !== undefined) {
152
- state.waitUntil(this[RunSymbol](initialize));
176
+ state.waitUntil(this[RunSymbol](initialize, { eventLayer: false }));
153
177
  }
154
178
  }
155
179
 
156
- [RunSymbol]<A, E>(effect: Effect.Effect<A, E, HandlerContext<ROut>>): Promise<A> {
157
- return this.runtime.runPromise(Effect.scoped(effect));
180
+ [RunSymbol]<A, E>(
181
+ effect: Effect.Effect<A, E, HandlerContext<ROut | REvent>>,
182
+ runOptions: RunOptions = {},
183
+ ): Promise<A> {
184
+ const effectWithEventLayer =
185
+ runOptions.eventLayer === false || options.eventLayer === undefined
186
+ ? effect
187
+ : effect.pipe(Effect.provide(options.eventLayer, { local: true }));
188
+
189
+ return this.runtime.runPromise(
190
+ Effect.scoped(
191
+ effectWithEventLayer as Effect.Effect<A, E | EventLayerError, HandlerContext<ROut>>,
192
+ ),
193
+ );
158
194
  }
159
195
 
160
196
  fetch(request: Request): Promise<Response> {
@@ -223,7 +259,9 @@ export const make = <
223
259
  (self, effect) => self[RunSymbol](effect),
224
260
  );
225
261
 
226
- return Entrypoint.assumeEntrypointClass<DurableObjectClass<Rpc, ROut>>(EffectDurableObject);
262
+ return Entrypoint.assumeEntrypointClass<DurableObjectClass<Rpc, ROut | REvent>>(
263
+ EffectDurableObject,
264
+ );
227
265
  };
228
266
 
229
267
  export type ServiceFreeSchema = S.Codec<any, any, never, never>;
@@ -285,11 +323,16 @@ export type Handlers<ROut, Self extends Definition.Any> = {
285
323
  ) => DurableObjectHandler<ROut, Method.Success<Self["methods"][Key]>>;
286
324
  };
287
325
 
288
- export interface Options<ROut, Self extends Definition.Any> extends Omit<
289
- DurableObjectOptions<ROut, Handlers<ROut, Self>>,
326
+ export interface Options<
327
+ ROut,
328
+ Self extends Definition.Any,
329
+ REvent = never,
330
+ EventLayerError = never,
331
+ > extends Omit<
332
+ DurableObjectOptions<ROut, REvent, EventLayerError, Handlers<ROut | REvent, Self>>,
290
333
  "rpc"
291
334
  > {
292
- readonly rpc: Handlers<ROut, Self>;
335
+ readonly rpc: Handlers<ROut | REvent, Self>;
293
336
  }
294
337
 
295
338
  export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Context.ServiceClass<
@@ -307,10 +350,10 @@ export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Co
307
350
  > & {
308
351
  readonly id: Id;
309
352
  readonly methods: MethodsShape;
310
- readonly make: <ROut, LayerError>(
353
+ readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
311
354
  layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
312
- options: Options<ROut, Definition<Id, MethodsShape>>,
313
- ) => DurableObjectClass<Handlers<ROut, Definition<Id, MethodsShape>>, ROut>;
355
+ options: Options<ROut, Definition<Id, MethodsShape>, REvent, EventLayerError>,
356
+ ) => DurableObjectClass<Handlers<ROut | REvent, Definition<Id, MethodsShape>>, ROut | REvent>;
314
357
  readonly layer: (
315
358
  options: LayerOptions,
316
359
  ) => Layer.Layer<
@@ -115,11 +115,21 @@ type BoundaryHandlers<ROut, Self extends Definition.Any> = {
115
115
  /**
116
116
  * Durable Object constructor options for a specific RPC definition.
117
117
  */
118
- export interface Options<ROut, Self extends Definition.Any> extends Omit<
119
- DurableObjectEntrypoint.DurableObjectOptions<ROut, Handlers<ROut, Self>>,
118
+ export interface Options<
119
+ ROut,
120
+ Self extends Definition.Any,
121
+ REvent = never,
122
+ EventLayerError = never,
123
+ > extends Omit<
124
+ DurableObjectEntrypoint.DurableObjectOptions<
125
+ ROut,
126
+ REvent,
127
+ EventLayerError,
128
+ Handlers<ROut | REvent, Self>
129
+ >,
120
130
  "rpc"
121
131
  > {
122
- readonly rpc: Handlers<ROut, Self>;
132
+ readonly rpc: Handlers<ROut | REvent, Self>;
123
133
  }
124
134
 
125
135
  export type LayerOptions = {
@@ -141,12 +151,12 @@ export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Co
141
151
  > & {
142
152
  readonly id: Id;
143
153
  readonly methods: MethodsShape;
144
- readonly make: <ROut, LayerError>(
154
+ readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
145
155
  layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
146
- options: Options<ROut, Definition<Id, MethodsShape>>,
156
+ options: Options<ROut, Definition<Id, MethodsShape>, REvent, EventLayerError>,
147
157
  ) => DurableObjectEntrypoint.DurableObjectClass<
148
- Handlers<ROut, Definition<Id, MethodsShape>>,
149
- ROut
158
+ Handlers<ROut | REvent, Definition<Id, MethodsShape>>,
159
+ ROut | REvent
150
160
  >;
151
161
  readonly layer: (
152
162
  options: LayerOptions,
@@ -195,9 +205,9 @@ const makeDefinition = <Id extends string, const MethodsShape extends Methods>(
195
205
  const definition: SelfDefinition = RpcDefinition.make(id, methods);
196
206
 
197
207
  return Object.assign(definition, {
198
- make: <ROut, LayerError>(
208
+ make: <ROut, LayerError, REvent = never, EventLayerError = never>(
199
209
  layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
200
- options: Options<ROut, SelfDefinition>,
210
+ options: Options<ROut, SelfDefinition, REvent, EventLayerError>,
201
211
  ) =>
202
212
  DurableObjectEntrypoint.make(layer, {
203
213
  ...options,
package/src/Worker.ts CHANGED
@@ -10,6 +10,7 @@ import type * as Rpc from "./Rpc";
10
10
  import type * as RpcDefinition from "./RpcDefinition";
11
11
  import type * as ServiceBinding from "./ServiceBinding";
12
12
  import * as WorkerDefinition from "./WorkerDefinition";
13
+ import * as CloudflareClock from "./internal/Clock";
13
14
  import * as Entrypoint from "./internal/Entrypoint";
14
15
  import { fromExecutionContext, type RunWaitUntilEffect } from "./internal/WorkerContext";
15
16
 
@@ -88,6 +89,9 @@ type WorkerFetchContext<ROut> =
88
89
  type WorkerRpcContext<ROut> = WorkerBaseContext<ROut> | Scope.Scope;
89
90
 
90
91
  type RuntimeContext<ROut> = WorkerBaseContext<ROut>;
92
+ type RunOptions = {
93
+ readonly eventLayer?: boolean;
94
+ };
91
95
 
92
96
  const RunSymbol = Symbol.for("effect-cf/Worker/run");
93
97
 
@@ -127,13 +131,33 @@ export type RpcHandlers<ROut, Api> = {
127
131
  : never;
128
132
  };
129
133
 
130
- export interface WorkerOptions<ROut, Rpc extends WorkerRpc<ROut>> {
131
- readonly fetch?: Effect.Effect<WorkerFetchSuccess, unknown, WorkerFetchContext<ROut>>;
132
- readonly queue?: QueueHandler<ROut>;
134
+ export interface WorkerOptions<
135
+ RRuntime,
136
+ REvent = never,
137
+ EventLayerError = never,
138
+ Rpc extends WorkerRpc<RRuntime | REvent> = Record<never, never>,
139
+ > {
140
+ /**
141
+ * Layer provided around each Cloudflare event handled by this Worker.
142
+ *
143
+ * The layer is built inside the event's Effect scope and finalized when the
144
+ * event effect completes. Use this for event-scoped resources such as OTLP
145
+ * trace/log exporters that should flush at event completion.
146
+ */
147
+ readonly eventLayer?: Layer.Layer<REvent, EventLayerError, WorkerBaseContext<RRuntime>>;
148
+ readonly fetch?: Effect.Effect<
149
+ WorkerFetchSuccess,
150
+ unknown,
151
+ WorkerFetchContext<RRuntime | REvent>
152
+ >;
153
+ readonly queue?: QueueHandler<RRuntime | REvent>;
133
154
  readonly rpc?: Rpc;
134
155
  }
135
156
 
136
- export type FetchWorkerOptions<ROut> = Omit<WorkerOptions<ROut, Record<never, never>>, "rpc"> & {
157
+ export type FetchWorkerOptions<RRuntime, REvent = never, EventLayerError = never> = Omit<
158
+ WorkerOptions<RRuntime, REvent, EventLayerError, Record<never, never>>,
159
+ "rpc"
160
+ > & {
137
161
  readonly rpc?: never;
138
162
  };
139
163
 
@@ -173,28 +197,40 @@ const renderFetchSuccess = <E, R>(
173
197
  ),
174
198
  );
175
199
 
176
- const isWorkerOptions = <ROut, Rpc extends WorkerRpc<ROut>>(
177
- options: WorkerOptions<ROut, Rpc> | WorkerHandler<ROut>,
178
- ): options is WorkerOptions<ROut, Rpc> =>
200
+ const isWorkerOptions = <ROut, REvent, EventLayerError, Rpc extends WorkerRpc<ROut | REvent>>(
201
+ options: WorkerOptions<ROut, REvent, EventLayerError, Rpc> | WorkerHandler<ROut>,
202
+ ): options is WorkerOptions<ROut, REvent, EventLayerError, Rpc> =>
179
203
  typeof options === "object" &&
180
204
  options !== null &&
181
- ("fetch" in options || "queue" in options || "rpc" in options);
205
+ ("eventLayer" in options || "fetch" in options || "queue" in options || "rpc" in options);
182
206
 
183
207
  export function make<ROut, LayerError>(
184
208
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
185
209
  fetch: WorkerHandler<ROut>,
186
210
  ): WorkerClass<Record<never, never>, ROut>;
187
- export function make<ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Record<never, never>>(
211
+ export function make<
212
+ ROut,
213
+ LayerError,
214
+ REvent = never,
215
+ EventLayerError = never,
216
+ const Rpc extends WorkerRpc<ROut | REvent> = Record<never, never>,
217
+ >(
188
218
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
189
- options: WorkerOptions<ROut, Rpc>,
190
- ): WorkerClass<Rpc, ROut>;
191
- export function make<ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Record<never, never>>(
219
+ options: WorkerOptions<ROut, REvent, EventLayerError, Rpc>,
220
+ ): WorkerClass<Rpc, ROut | REvent>;
221
+ export function make<
222
+ ROut,
223
+ LayerError,
224
+ REvent = never,
225
+ EventLayerError = never,
226
+ const Rpc extends WorkerRpc<ROut | REvent> = Record<never, never>,
227
+ >(
192
228
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
193
- optionsOrFetch: WorkerOptions<ROut, Rpc> | WorkerHandler<ROut>,
194
- ): WorkerClass<Rpc, ROut> {
229
+ optionsOrFetch: WorkerOptions<ROut, REvent, EventLayerError, Rpc> | WorkerHandler<ROut>,
230
+ ): WorkerClass<Rpc, ROut | REvent> {
195
231
  const options = isWorkerOptions(optionsOrFetch)
196
232
  ? optionsOrFetch
197
- : ({ fetch: optionsOrFetch } as WorkerOptions<ROut, Rpc>);
233
+ : ({ fetch: optionsOrFetch } as WorkerOptions<ROut, REvent, EventLayerError, Rpc>);
198
234
 
199
235
  class EffectWorker extends CloudflareWorkerEntrypoint<WorkerEnv> {
200
236
  readonly runtime: ManagedRuntime.ManagedRuntime<RuntimeContext<ROut>, LayerError>;
@@ -206,6 +242,7 @@ export function make<ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Recor
206
242
  Promise.reject(new Error("WorkerContext runtime is not initialized"));
207
243
 
208
244
  const services = Layer.mergeAll(
245
+ CloudflareClock.layer,
209
246
  Layer.succeed(ExecutionContext, ctx),
210
247
  ConfigProvider.layer(Effect.succeed(WorkerConfig.providerFromEnv(env))),
211
248
  Layer.succeed(
@@ -226,8 +263,24 @@ export function make<ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Recor
226
263
  this.runtime.runPromiseExit(effect as Effect.Effect<A, E, RuntimeContext<ROut>>);
227
264
  }
228
265
 
229
- [RunSymbol]<A, E>(effect: Effect.Effect<A, E, RuntimeContext<ROut> | Scope.Scope>): Promise<A> {
230
- return this.runtime.runPromise(Effect.scoped(effect));
266
+ [RunSymbol]<A, E>(
267
+ effect: Effect.Effect<A, E, RuntimeContext<ROut> | REvent | Scope.Scope>,
268
+ runOptions: RunOptions = {},
269
+ ): Promise<A> {
270
+ const effectWithEventLayer =
271
+ runOptions.eventLayer === false || options.eventLayer === undefined
272
+ ? effect
273
+ : effect.pipe(Effect.provide(options.eventLayer, { local: true }));
274
+
275
+ return this.runtime.runPromise(
276
+ Effect.scoped(
277
+ effectWithEventLayer as Effect.Effect<
278
+ A,
279
+ E | EventLayerError,
280
+ RuntimeContext<ROut> | Scope.Scope
281
+ >,
282
+ ),
283
+ );
231
284
  }
232
285
 
233
286
  fetch(request: Request): Promise<Response> {
@@ -246,7 +299,7 @@ export function make<ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Recor
246
299
  renderFetchSuccess(fetchHandler).pipe(Effect.provide(requestServices)) as Effect.Effect<
247
300
  Response,
248
301
  unknown,
249
- RuntimeContext<ROut> | Scope.Scope
302
+ RuntimeContext<ROut> | REvent | Scope.Scope
250
303
  >,
251
304
  );
252
305
  }
@@ -272,12 +325,18 @@ export function make<ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Recor
272
325
  (self, effect) => self[RunSymbol](effect),
273
326
  );
274
327
 
275
- return Entrypoint.assumeEntrypointClass<WorkerClass<Rpc, ROut>>(EffectWorker);
328
+ return Entrypoint.assumeEntrypointClass<WorkerClass<Rpc, ROut | REvent>>(EffectWorker);
276
329
  }
277
330
 
278
- export const makeFetchHandler = <ROut, LayerError, Env extends WorkerEnv = WorkerEnv>(
331
+ export const makeFetchHandler = <
332
+ ROut,
333
+ LayerError,
334
+ REvent = never,
335
+ EventLayerError = never,
336
+ Env extends WorkerEnv = WorkerEnv,
337
+ >(
279
338
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
280
- options: FetchWorkerOptions<ROut>,
339
+ options: FetchWorkerOptions<ROut, REvent, EventLayerError>,
281
340
  ): FetchHandler<Env> => {
282
341
  const WorkerClass = make(layer, options);
283
342
 
@@ -341,11 +400,13 @@ export type Handlers<ROut, Self extends Definition.Any> = {
341
400
  ) => WorkerRpcHandler<ROut, Method.Success<Self["methods"][Key]>>;
342
401
  };
343
402
 
344
- export interface Options<ROut, Self extends Definition.Any> extends Omit<
345
- WorkerOptions<ROut, Handlers<ROut, Self>>,
346
- "rpc"
347
- > {
348
- readonly rpc: Handlers<ROut, Self>;
403
+ export interface Options<
404
+ ROut,
405
+ Self extends Definition.Any,
406
+ REvent = never,
407
+ EventLayerError = never,
408
+ > extends Omit<WorkerOptions<ROut, REvent, EventLayerError, Handlers<ROut | REvent, Self>>, "rpc"> {
409
+ readonly rpc: Handlers<ROut | REvent, Self>;
349
410
  }
350
411
 
351
412
  export type LayerOptions = WorkerDefinition.LayerOptions;
@@ -365,10 +426,10 @@ export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Co
365
426
  > & {
366
427
  readonly id: Id;
367
428
  readonly methods: MethodsShape;
368
- readonly make: <ROut, LayerError>(
429
+ readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
369
430
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
370
- options: Options<ROut, Definition<Id, MethodsShape>>,
371
- ) => WorkerClass<Handlers<ROut, Definition<Id, MethodsShape>>, ROut>;
431
+ options: Options<ROut, Definition<Id, MethodsShape>, REvent, EventLayerError>,
432
+ ) => WorkerClass<Handlers<ROut | REvent, Definition<Id, MethodsShape>>, ROut | REvent>;
372
433
  readonly layer: (
373
434
  options: LayerOptions,
374
435
  ) => Layer.Layer<
@@ -118,11 +118,16 @@ type BoundaryHandlers<ROut, Self extends Definition.Any> = {
118
118
  /**
119
119
  * Worker constructor options for a specific RPC definition.
120
120
  */
121
- export interface Options<ROut, Self extends Definition.Any> extends Omit<
122
- WorkerEntrypoint.WorkerOptions<ROut, Handlers<ROut, Self>>,
121
+ export interface Options<
122
+ ROut,
123
+ Self extends Definition.Any,
124
+ REvent = never,
125
+ EventLayerError = never,
126
+ > extends Omit<
127
+ WorkerEntrypoint.WorkerOptions<ROut, REvent, EventLayerError, Handlers<ROut | REvent, Self>>,
123
128
  "rpc"
124
129
  > {
125
- readonly rpc: Handlers<ROut, Self>;
130
+ readonly rpc: Handlers<ROut | REvent, Self>;
126
131
  }
127
132
 
128
133
  export type LayerOptions = {
@@ -144,14 +149,17 @@ export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Co
144
149
  > & {
145
150
  readonly id: Id;
146
151
  readonly methods: MethodsShape;
147
- readonly make: <ROut, LayerError>(
152
+ readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
148
153
  layer: Layer.Layer<
149
154
  ROut,
150
155
  LayerError,
151
156
  WorkerEntrypoint.ExecutionContext | WorkerEntrypoint.WorkerContext | WorkerEnvironment
152
157
  >,
153
- options: Options<ROut, Definition<Id, MethodsShape>>,
154
- ) => WorkerEntrypoint.WorkerClass<Handlers<ROut, Definition<Id, MethodsShape>>, ROut>;
158
+ options: Options<ROut, Definition<Id, MethodsShape>, REvent, EventLayerError>,
159
+ ) => WorkerEntrypoint.WorkerClass<
160
+ Handlers<ROut | REvent, Definition<Id, MethodsShape>>,
161
+ ROut | REvent
162
+ >;
155
163
  readonly layer: (
156
164
  options: LayerOptions,
157
165
  ) => Layer.Layer<
@@ -199,13 +207,13 @@ const makeDefinition = <Id extends string, const MethodsShape extends Methods>(
199
207
  const definition: SelfDefinition = RpcDefinition.make(id, methods);
200
208
 
201
209
  return Object.assign(definition, {
202
- make: <ROut, LayerError>(
210
+ make: <ROut, LayerError, REvent = never, EventLayerError = never>(
203
211
  layer: Layer.Layer<
204
212
  ROut,
205
213
  LayerError,
206
214
  WorkerEntrypoint.ExecutionContext | WorkerEntrypoint.WorkerContext | WorkerEnvironment
207
215
  >,
208
- options: Options<ROut, SelfDefinition>,
216
+ options: Options<ROut, SelfDefinition, REvent, EventLayerError>,
209
217
  ) =>
210
218
  WorkerEntrypoint.make(layer, {
211
219
  ...options,
@@ -0,0 +1,28 @@
1
+ import { Clock, Duration, Effect, Layer } from "effect";
2
+
3
+ const maxTimerMillis = 2 ** 31 - 1;
4
+ const nanosPerMilli = BigInt(1_000_000);
5
+
6
+ export const clock: Clock.Clock = {
7
+ currentTimeMillisUnsafe: () => Date.now(),
8
+ currentTimeMillis: Effect.sync(() => clock.currentTimeMillisUnsafe()),
9
+ currentTimeNanosUnsafe: () => BigInt(Date.now()) * nanosPerMilli,
10
+ currentTimeNanos: Effect.sync(() => clock.currentTimeNanosUnsafe()),
11
+ sleep: (duration) => {
12
+ const millis = Duration.toMillis(duration);
13
+ if (millis <= 0) {
14
+ return Effect.yieldNow;
15
+ }
16
+
17
+ return Effect.callback<void>((resume) => {
18
+ if (millis > maxTimerMillis) {
19
+ return;
20
+ }
21
+
22
+ const handle = setTimeout(() => resume(Effect.void), millis);
23
+ return Effect.sync(() => clearTimeout(handle));
24
+ });
25
+ },
26
+ };
27
+
28
+ export const layer = Layer.effect(Clock.Clock, Effect.succeed(clock));