effect-cf 0.39.0 → 0.40.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -4,6 +4,11 @@ import { Effect, Layer, type ManagedRuntime, type Scope, Tracer } from "effect";
4
4
  import { NativeRequest } from "./Worker";
5
5
  import { WorkerEnvironment, type WorkerEnv } from "./Environment";
6
6
  import { DurableObjectState, fromDurableObjectState } from "./DurableObjectState";
7
+ import {
8
+ DurableObjectAlarm,
9
+ type AlarmRegistration,
10
+ type AlarmService,
11
+ } from "./DurableObjectAlarm";
7
12
  import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
8
13
  import * as RpcDefinition from "./RpcDefinition";
9
14
  import * as Entrypoint from "./internal/Entrypoint";
@@ -13,7 +18,11 @@ import type { ReceiverOptions, RpcInvocationInfo } from "./RpcTracing";
13
18
 
14
19
  const reservedMethodNames: ReadonlySet<string> = RpcDefinition.reservedMethodNames;
15
20
 
16
- type RuntimeContext<ROut> = DurableObjectState | WorkerEnvironment | ROut;
21
+ export type RuntimeContext<ROut> =
22
+ | DurableObjectState
23
+ | DurableObjectAlarm
24
+ | WorkerEnvironment
25
+ | ROut;
17
26
 
18
27
  type HandlerContext<ROut> = RuntimeContext<ROut> | Scope.Scope;
19
28
 
@@ -65,24 +74,27 @@ export type DurableObjectRpcApi<Rpc extends DurableObjectRpc<ROut>, ROut> = {
65
74
  };
66
75
 
67
76
  export type RpcHandlers<ROut, Api> = {
68
- readonly [Key in keyof Api as Key extends keyof CloudflareDurableObject<WorkerEnv>
69
- ? never
70
- : Key extends string
71
- ? [Api[Key]] extends [never]
72
- ? never
73
- : Api[Key] extends (...args: Array<any>) => Promise<any>
74
- ? Key
75
- : never
76
- : never]: Api[Key] extends (...args: infer Args) => Promise<infer A>
77
+ readonly [
78
+ Key in keyof Api as Key extends keyof CloudflareDurableObject<WorkerEnv>
79
+ ? never
80
+ : Key extends string
81
+ ? [Api[Key]] extends [never]
82
+ ? never
83
+ : Api[Key] extends (...args: Array<any>) => Promise<any>
84
+ ? Key
85
+ : never
86
+ : never
87
+ ]: Api[Key] extends (...args: infer Args) => Promise<infer A>
77
88
  ? (...args: Args) => DurableObjectHandler<ROut, A>
78
89
  : never;
79
90
  };
80
91
 
81
- export interface DurableObjectOptions<
92
+ interface DurableObjectOptionsBase<
82
93
  RRuntime,
83
94
  REvent = never,
84
95
  EventLayerError = never,
85
- Rpc extends DurableObjectRpc<RRuntime | REvent> = Record<never, never>,
96
+ Rpc extends DurableObjectRpc<RRuntime | REvent | RAlarm> = Record<never, never>,
97
+ RAlarm = never,
86
98
  > {
87
99
  /**
88
100
  * Layer provided around each Cloudflare event handled by this Durable Object.
@@ -94,7 +106,7 @@ export interface DurableObjectOptions<
94
106
  readonly eventLayer?: Layer.Layer<
95
107
  REvent,
96
108
  EventLayerError,
97
- DurableObjectState | WorkerEnvironment | RRuntime
109
+ RuntimeContext<NoInfer<RRuntime | RAlarm>>
98
110
  >;
99
111
  /**
100
112
  * Effect run when Cloudflare loads this Durable Object instance into memory.
@@ -104,7 +116,7 @@ export interface DurableObjectOptions<
104
116
  * the same Durable Object id again after eviction or restart; use Durable
105
117
  * Object storage if work must happen only once per id.
106
118
  */
107
- readonly initialize?: Effect.Effect<void, unknown, HandlerContext<RRuntime>>;
119
+ readonly initialize?: Effect.Effect<void, unknown, HandlerContext<NoInfer<RRuntime | RAlarm>>>;
108
120
  /**
109
121
  * Optional RPC methods exposed as Durable Object instance methods.
110
122
  *
@@ -116,9 +128,20 @@ export interface DurableObjectOptions<
116
128
  readonly rpc?: Rpc;
117
129
  /** Accept live native trace metadata. The client must also enable rpcTracing. */
118
130
  readonly rpcTracing?: ReceiverOptions;
119
- readonly fetch?: Effect.Effect<Response, unknown, FetchContext<RRuntime | REvent>>;
131
+ readonly fetch?: Effect.Effect<
132
+ Response,
133
+ unknown,
134
+ FetchContext<NoInfer<RRuntime | REvent | RAlarm>>
135
+ >;
120
136
 
121
- readonly alarms?: Effect.Effect<unknown, unknown, HandlerContext<RRuntime | REvent>>;
137
+ /** Register handlers to provide their typed scheduler to application layers and all DO events. */
138
+ readonly alarms?:
139
+ | Effect.Effect<unknown, unknown, HandlerContext<NoInfer<RRuntime> | REvent | NoInfer<RAlarm>>>
140
+ | AlarmRegistration<
141
+ RAlarm,
142
+ HandlerContext<NoInfer<RRuntime> | REvent | NoInfer<RAlarm>>,
143
+ unknown
144
+ >;
122
145
  /**
123
146
  * Optional raw alarm handler.
124
147
  *
@@ -129,23 +152,58 @@ export interface DurableObjectOptions<
129
152
  */
130
153
  readonly alarm?: (
131
154
  alarmInfo?: globalThis.AlarmInvocationInfo,
132
- ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
155
+ ) => Effect.Effect<void, unknown, HandlerContext<NoInfer<RRuntime> | REvent | NoInfer<RAlarm>>>;
133
156
  readonly webSocketMessage?: (
134
157
  socket: DurableWebSocket,
135
158
  message: string | ArrayBuffer,
136
- ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
159
+ ) => Effect.Effect<void, unknown, HandlerContext<NoInfer<RRuntime> | REvent | NoInfer<RAlarm>>>;
137
160
  readonly webSocketClose?: (
138
161
  socket: DurableWebSocket,
139
162
  code: number,
140
163
  reason: string,
141
164
  wasClean: boolean,
142
- ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
165
+ ) => Effect.Effect<void, unknown, HandlerContext<NoInfer<RRuntime> | REvent | NoInfer<RAlarm>>>;
143
166
  readonly webSocketError?: (
144
167
  socket: DurableWebSocket,
145
168
  cause: unknown,
146
- ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
169
+ ) => Effect.Effect<void, unknown, HandlerContext<NoInfer<RRuntime> | REvent | NoInfer<RAlarm>>>;
147
170
  }
148
171
 
172
+ // Providing a scheduler through an ordinary layer does not install its dispatcher.
173
+ type CheckAlarmRegistration<RProvided, RAlarm> = [
174
+ Exclude<Extract<NoInfer<RProvided>, AlarmService>, NoInfer<RAlarm>>,
175
+ ] extends [never]
176
+ ? unknown
177
+ : never;
178
+
179
+ /** A declared event or alarm service must have its corresponding provider. */
180
+ export type DurableObjectOptions<
181
+ RRuntime,
182
+ REvent = never,
183
+ EventLayerError = never,
184
+ Rpc extends DurableObjectRpc<RRuntime | REvent | RAlarm> = Record<never, never>,
185
+ RAlarm = never,
186
+ > = DurableObjectOptionsBase<RRuntime, REvent, EventLayerError, Rpc, RAlarm> &
187
+ CheckAlarmRegistration<RRuntime | REvent, RAlarm> &
188
+ ([Exclude<REvent, RuntimeContext<RRuntime | RAlarm> | Scope.Scope>] extends [never]
189
+ ? unknown
190
+ : {
191
+ readonly eventLayer: Layer.Layer<
192
+ REvent,
193
+ EventLayerError,
194
+ RuntimeContext<NoInfer<RRuntime | RAlarm>>
195
+ >;
196
+ }) &
197
+ ([RAlarm] extends [never]
198
+ ? unknown
199
+ : {
200
+ readonly alarms: AlarmRegistration<
201
+ RAlarm,
202
+ HandlerContext<NoInfer<RRuntime> | REvent | NoInfer<RAlarm>>,
203
+ unknown
204
+ >;
205
+ });
206
+
149
207
  /**
150
208
  * Cloudflare `DurableObject` constructor produced by {@link make}.
151
209
  */
@@ -166,27 +224,62 @@ export type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (
166
224
  * that layer are not guaranteed to run. Put resources that require timely
167
225
  * release in `eventLayer`, whose scope closes after each handled event.
168
226
  */
169
- export const make = <
227
+ export function make<ROut, LayerError>(
228
+ layer: Layer.Layer<ROut, LayerError, RuntimeContext<never>> & CheckAlarmRegistration<ROut, never>,
229
+ ): DurableObjectClass<Record<never, never>, ROut>;
230
+ export function make<
231
+ ROut,
232
+ LayerError,
233
+ REvent = never,
234
+ EventLayerError = never,
235
+ const Rpc extends DurableObjectRpc<NoInfer<ROut> | REvent | NoInfer<RAlarm>> = Record<
236
+ never,
237
+ never
238
+ >,
239
+ RAlarm = never,
240
+ >(
241
+ layer: Layer.Layer<ROut, LayerError, RuntimeContext<NoInfer<RAlarm>>>,
242
+ options: DurableObjectOptions<ROut, REvent, EventLayerError, Rpc, RAlarm>,
243
+ ): DurableObjectClass<Rpc, ROut | REvent | RAlarm>;
244
+ export function make<
170
245
  ROut,
171
246
  LayerError,
172
247
  REvent = never,
173
248
  EventLayerError = never,
174
- const Rpc extends DurableObjectRpc<ROut | REvent> = Record<never, never>,
249
+ const Rpc extends DurableObjectRpc<NoInfer<ROut> | REvent | NoInfer<RAlarm>> = Record<
250
+ never,
251
+ never
252
+ >,
253
+ RAlarm = never,
175
254
  >(
176
- layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
177
- options: DurableObjectOptions<ROut, REvent, EventLayerError, Rpc> = {},
178
- ): DurableObjectClass<Rpc, ROut | REvent> => {
255
+ layer: Layer.Layer<ROut, LayerError, RuntimeContext<NoInfer<RAlarm>>>,
256
+ options: DurableObjectOptionsBase<ROut, REvent, EventLayerError, Rpc, RAlarm> = {},
257
+ ): DurableObjectClass<Rpc, ROut | REvent | RAlarm> {
258
+ const registration =
259
+ options.alarms !== undefined && !Effect.isEffect(options.alarms) ? options.alarms : undefined;
260
+ const logicalAlarms = (
261
+ registration?.run ?? (Effect.isEffect(options.alarms) ? options.alarms : undefined)
262
+ )?.pipe(Effect.asVoid);
263
+
179
264
  class EffectDurableObject extends CloudflareDurableObject<WorkerEnv> {
180
- readonly runtime: ManagedRuntime.ManagedRuntime<RuntimeContext<ROut>, LayerError>;
265
+ readonly runtime: ManagedRuntime.ManagedRuntime<RuntimeContext<ROut | RAlarm>, LayerError>;
181
266
 
182
267
  constructor(state: globalThis.DurableObjectState, env: WorkerEnv) {
183
268
  super(state, env);
184
269
 
185
- this.runtime = Runtime.makeEntrypointRuntime<ROut, LayerError, DurableObjectState>(
186
- layer,
187
- env,
188
- Layer.succeed(DurableObjectState, fromDurableObjectState(state)),
270
+ const baseServices = DurableObjectAlarm.layer.pipe(
271
+ Layer.provideMerge(Layer.succeed(DurableObjectState, fromDurableObjectState(state))),
189
272
  );
273
+ // SAFETY: RAlarm is inferred only from the registration's layer; without a registration it is never.
274
+ const services = (registration?.layer ?? Layer.empty).pipe(
275
+ Layer.provideMerge(baseServices),
276
+ ) as Layer.Layer<DurableObjectState | DurableObjectAlarm | RAlarm>;
277
+
278
+ this.runtime = Runtime.makeEntrypointRuntime<
279
+ ROut,
280
+ LayerError,
281
+ DurableObjectState | DurableObjectAlarm | RAlarm
282
+ >(layer, env, services);
190
283
 
191
284
  const initialize = options.initialize;
192
285
 
@@ -196,7 +289,7 @@ export const make = <
196
289
  }
197
290
 
198
291
  [RunSymbol]<A, E>(
199
- effect: Effect.Effect<A, E, HandlerContext<ROut | REvent>>,
292
+ effect: Effect.Effect<A, E, HandlerContext<ROut | REvent | RAlarm>>,
200
293
  runOptions: RunOptions = {},
201
294
  ): Promise<A> {
202
295
  const eventLayer = runOptions.eventLayer === false ? undefined : options.eventLayer;
@@ -204,11 +297,11 @@ export const make = <
204
297
  const parentSpan = parent === undefined ? undefined : Tracer.externalSpan(parent);
205
298
 
206
299
  if (eventLayer === undefined) {
207
- // SAFETY: event-layer bypass is used only for initialize; otherwise an absent layer means REvent is never.
300
+ // SAFETY: initialize cannot require event services; without an event layer, all requirements are already provided.
208
301
  return Runtime.runEventPromise(
209
302
  this.runtime,
210
303
  // @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
211
- effect as Effect.Effect<A, E, HandlerContext<ROut>>,
304
+ effect as Effect.Effect<A, E, HandlerContext<ROut | RAlarm>>,
212
305
  undefined,
213
306
  parentSpan,
214
307
  );
@@ -217,7 +310,7 @@ export const make = <
217
310
  return Runtime.runEventPromise<
218
311
  A,
219
312
  E,
220
- RuntimeContext<ROut>,
313
+ RuntimeContext<ROut | RAlarm>,
221
314
  REvent,
222
315
  EventLayerError,
223
316
  LayerError
@@ -237,7 +330,6 @@ export const make = <
237
330
  }
238
331
 
239
332
  alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> | void {
240
- const logicalAlarms = options.alarms?.pipe(Effect.asVoid);
241
333
  const rawAlarm = options.alarm?.(alarmInfo);
242
334
  const alarmEffect =
243
335
  logicalAlarms !== undefined && rawAlarm !== undefined
@@ -295,10 +387,10 @@ export const make = <
295
387
  options.rpcTracing,
296
388
  );
297
389
 
298
- return Entrypoint.assumeEntrypointClass<DurableObjectClass<Rpc, ROut | REvent>>(
390
+ return Entrypoint.assumeEntrypointClass<DurableObjectClass<Rpc, ROut | REvent | RAlarm>>(
299
391
  EffectDurableObject,
300
392
  );
301
- };
393
+ }
302
394
 
303
395
  export type {
304
396
  Api,
@@ -368,6 +368,63 @@ export type DefinedAlarmHandlers<Definitions extends AlarmDefinitions, R = never
368
368
  ) => Effect.Effect<void, E, R>;
369
369
  };
370
370
 
371
+ /** A discriminated union keeps each tag paired with its decoded payload type. */
372
+ export type DefinedScheduleAlarmInput<Definitions extends AlarmDefinitions> = {
373
+ readonly [Tag in keyof Definitions & string]: Omit<ScheduleAlarmInput<Tag>, "payload"> & {
374
+ readonly payload: AlarmDefinitionPayload<Definitions[Tag]>;
375
+ };
376
+ }[keyof Definitions & string];
377
+
378
+ export interface DefinedAlarmTransaction<Definitions extends AlarmDefinitions> {
379
+ readonly scheduleAlarm: (
380
+ input: DefinedScheduleAlarmInput<Definitions>,
381
+ ) => ReturnType<AlarmScheduler["scheduleAlarm"]>;
382
+ readonly cancelAlarm: (
383
+ input: AlarmRef<keyof Definitions & string>,
384
+ ) => ReturnType<AlarmScheduler["cancelAlarm"]>;
385
+ }
386
+
387
+ const TypedAlarmSchedulerTypeId: unique symbol = Symbol.for(
388
+ "effect-cf/DurableObjectAlarm/TypedScheduler",
389
+ );
390
+
391
+ /** Identifies service requirements whose scheduler needs a registered dispatcher. */
392
+ export interface AlarmService {
393
+ readonly Service: {
394
+ readonly [TypedAlarmSchedulerTypeId]: typeof TypedAlarmSchedulerTypeId;
395
+ };
396
+ }
397
+
398
+ export interface DefinedAlarmScheduler<
399
+ Definitions extends AlarmDefinitions,
400
+ > extends DefinedAlarmTransaction<Definitions> {
401
+ readonly [TypedAlarmSchedulerTypeId]: typeof TypedAlarmSchedulerTypeId;
402
+ readonly transaction: <A, E, R>(
403
+ closure: (alarms: DefinedAlarmTransaction<Definitions>) => Effect.Effect<A, E, R>,
404
+ ) => Effect.Effect<A, E | StorageOperationError, R>;
405
+ }
406
+
407
+ /** Registers a dispatcher and the service authorized to schedule its alarms. */
408
+ export interface AlarmRegistration<Self, R = never, E = never> {
409
+ readonly layer: Layer.Layer<Self, never, DurableObjectAlarm>;
410
+ readonly run: Effect.Effect<
411
+ ProcessDueAlarmsResult,
412
+ E | DurableObjectAlarmError,
413
+ R | DurableObjectAlarm
414
+ >;
415
+ }
416
+
417
+ export interface AlarmTagClass<
418
+ Self,
419
+ Id extends string,
420
+ Definitions extends AlarmDefinitions,
421
+ > extends Context.ServiceClass<Self, Id, DefinedAlarmScheduler<Definitions>> {
422
+ readonly handlers: <R = never, E = never>(
423
+ handlers: DefinedAlarmHandlers<Definitions, R, E>,
424
+ options?: ProcessDueAlarmsOptions<R, E>,
425
+ ) => AlarmRegistration<Self, R, E>;
426
+ }
427
+
371
428
  const isAlarmDefinitionConfig = (
372
429
  definition: AlarmDefinitionEntry,
373
430
  ): definition is AlarmDefinitionConfig => Predicate.isObject(definition) && "payload" in definition;
@@ -391,55 +448,133 @@ const getAlarmDefinitionFailureAction = (
391
448
  : { mode: definition.failure };
392
449
  };
393
450
 
394
- export const define = <const Definitions extends AlarmDefinitions>(definitions: Definitions) => ({
395
- handlers: <R = never, E = never>(
396
- handlers: DefinedAlarmHandlers<Definitions, R, E>,
397
- options?: ProcessDueAlarmsOptions<R, E>,
398
- ) =>
399
- processDue(
400
- (event) =>
401
- Effect.gen(function* () {
402
- const definition = definitions[event.tag];
451
+ const makeDefinition = <const Definitions extends AlarmDefinitions>(definitions: Definitions) => {
452
+ const definitionFor = (tag: string) =>
453
+ Object.hasOwn(definitions, tag) ? definitions[tag] : undefined;
403
454
 
404
- if (definition === undefined) {
405
- return;
406
- }
455
+ const bind = (mutations: AlarmTransaction): DefinedAlarmTransaction<Definitions> => ({
456
+ scheduleAlarm: Effect.fn("DefinedAlarms.scheduleAlarm")(function* (
457
+ input: DefinedScheduleAlarmInput<Definitions>,
458
+ ) {
459
+ const definition = definitionFor(input.tag);
460
+
461
+ if (definition === undefined) {
462
+ return yield* Effect.fail(
463
+ new InvalidAlarmRefError({
464
+ cause: new Error(`Unknown alarm tag "${input.tag}"`),
465
+ }),
466
+ );
467
+ }
468
+ const payload = yield* S.encodeEffect(getAlarmDefinitionSchema(definition))(
469
+ input.payload,
470
+ ).pipe(
471
+ Effect.flatMap(S.decodeUnknownEffect(S.Json)),
472
+ Effect.mapError((cause) => new InvalidAlarmPayloadError({ cause })),
473
+ );
407
474
 
408
- const schema = getAlarmDefinitionSchema(definition);
409
- // SAFETY: schema is selected from the same tagged definition used to select its handler.
410
- const payload = yield* (
411
- S.decodeUnknownEffect(schema)(event.payload) as Effect.Effect<
412
- AlarmDefinitionPayload<Definitions[keyof Definitions & string]>,
413
- unknown
414
- >
415
- ).pipe(
416
- Effect.mapError(
417
- (cause) =>
475
+ yield* mutations.scheduleAlarm({ ...input, payload });
476
+ }),
477
+ cancelAlarm: Effect.fn("DefinedAlarms.cancelAlarm")(function* (
478
+ input: AlarmRef<keyof Definitions & string>,
479
+ ) {
480
+ if (definitionFor(input.tag) === undefined) {
481
+ return yield* Effect.fail(
482
+ new InvalidAlarmRefError({
483
+ cause: new Error(`Unknown alarm tag "${input.tag}"`),
484
+ }),
485
+ );
486
+ }
487
+
488
+ yield* mutations.cancelAlarm(input);
489
+ }),
490
+ });
491
+
492
+ return {
493
+ make: (alarms: AlarmScheduler): DefinedAlarmScheduler<Definitions> => ({
494
+ [TypedAlarmSchedulerTypeId]: TypedAlarmSchedulerTypeId,
495
+ ...bind(alarms),
496
+ transaction: (closure) => alarms.transaction((tx) => closure(bind(tx))),
497
+ }),
498
+ handlers: <R = never, E = never>(
499
+ handlers: DefinedAlarmHandlers<Definitions, R, E>,
500
+ options?: ProcessDueAlarmsOptions<R, E>,
501
+ ) =>
502
+ processDue(
503
+ (event) =>
504
+ Effect.gen(function* () {
505
+ const definition = definitionFor(event.tag);
506
+
507
+ if (definition === undefined) {
508
+ return yield* Effect.fail(
418
509
  new StoredAlarmDecodeError({
419
- cause,
510
+ cause: new Error(`Unknown alarm tag "${event.tag}"`),
420
511
  storageId: getScheduledEventId(event),
421
512
  }),
422
- ),
423
- );
424
- const handler = handlers[event.tag];
513
+ );
514
+ }
425
515
 
426
- // SAFETY: event.tag indexes the matching definition and handler, whose payload schema was decoded above.
427
- yield* handler({ ...event, payload } as never);
428
- }),
429
- {
430
- ...options,
431
- onFailure: (failure) =>
432
- Effect.gen(function* () {
433
- const action = getAlarmDefinitionFailureAction(definitions[failure.tag]);
434
- const optionAction =
435
- options?.onFailure === undefined ? undefined : yield* options.onFailure(failure);
516
+ const schema = getAlarmDefinitionSchema(definition);
517
+ // SAFETY: schema is selected from the same tagged definition used to select its handler.
518
+ const payload = yield* (
519
+ S.decodeUnknownEffect(schema)(event.payload) as Effect.Effect<
520
+ AlarmDefinitionPayload<Definitions[keyof Definitions & string]>,
521
+ unknown
522
+ >
523
+ ).pipe(
524
+ Effect.mapError(
525
+ (cause) =>
526
+ new StoredAlarmDecodeError({
527
+ cause,
528
+ storageId: getScheduledEventId(event),
529
+ }),
530
+ ),
531
+ );
532
+ const handler = handlers[event.tag];
436
533
 
437
- return action ?? optionAction;
534
+ // SAFETY: event.tag indexes the matching definition and handler, whose payload schema was decoded above.
535
+ yield* handler({ ...event, payload } as never);
438
536
  }),
439
- },
440
- ),
537
+ {
538
+ ...options,
539
+ onFailure: (failure) =>
540
+ Effect.gen(function* () {
541
+ const action = getAlarmDefinitionFailureAction(definitionFor(failure.tag));
542
+ const optionAction =
543
+ options?.onFailure === undefined ? undefined : yield* options.onFailure(failure);
544
+
545
+ return action ?? optionAction;
546
+ }),
547
+ },
548
+ ),
549
+ };
550
+ };
551
+
552
+ /** Handler-only definitions for applications using the raw scheduler. Prefer Tag for typed scheduling. */
553
+ export const define = <const Definitions extends AlarmDefinitions>(definitions: Definitions) => ({
554
+ handlers: makeDefinition(definitions).handlers,
441
555
  });
442
556
 
557
+ /** The typed scheduler becomes available when its handlers are registered on a Durable Object. */
558
+ export const Tag =
559
+ <Self>() =>
560
+ <const Id extends string, const Definitions extends AlarmDefinitions>(
561
+ id: Id,
562
+ definitions: Definitions,
563
+ ): AlarmTagClass<Self, Id, Definitions> => {
564
+ const tag = Context.Service<Self, DefinedAlarmScheduler<Definitions>>()(id);
565
+ const definition = makeDefinition(definitions);
566
+
567
+ return Object.assign(tag, {
568
+ handlers: <R = never, E = never>(
569
+ handlers: DefinedAlarmHandlers<Definitions, R, E>,
570
+ options?: ProcessDueAlarmsOptions<R, E>,
571
+ ): AlarmRegistration<Self, R, E> => ({
572
+ layer: Layer.effect(tag, Effect.map(DurableObjectAlarm, definition.make)),
573
+ run: definition.handlers(handlers, options),
574
+ }),
575
+ });
576
+ };
577
+
443
578
  export class DurableObjectAlarm extends Context.Service<DurableObjectAlarm, AlarmScheduler>()(
444
579
  "effect-cf/DurableObjectAlarm",
445
580
  ) {
@@ -723,6 +858,6 @@ export class DurableObjectAlarm extends Context.Service<DurableObjectAlarm, Alar
723
858
  scheduleAlarm: (input) => transaction((alarms) => alarms.scheduleAlarm(input)),
724
859
  transaction,
725
860
  });
726
- }).pipe(Effect.withSpan("DurableObjectAlarm.layer")),
861
+ }),
727
862
  );
728
863
  }
@@ -4,7 +4,6 @@ import type * as Binding from "./Binding";
4
4
  import * as DurableObjectEntrypoint from "./DurableObject";
5
5
  import type { DurableObjectHandler } from "./DurableObject";
6
6
  import * as DurableObjectNamespace from "./DurableObjectNamespace";
7
- import type { DurableObjectState } from "./DurableObjectState";
8
7
  import type * as Rpc from "./Rpc";
9
8
  import * as RpcDefinition from "./RpcDefinition";
10
9
  import { recordDecodedArgs } from "./internal/RpcInvocation";
@@ -118,22 +117,21 @@ type MutableBoundaryHandlers<ROut, Self extends Definition.Any> = {
118
117
  /**
119
118
  * Durable Object constructor options for a specific RPC definition.
120
119
  */
121
- export interface Options<
120
+ export type Options<
122
121
  ROut,
123
122
  Self extends Definition.Any,
124
123
  REvent = never,
125
124
  EventLayerError = never,
126
- > extends Omit<
127
- DurableObjectEntrypoint.DurableObjectOptions<
128
- ROut,
129
- REvent,
130
- EventLayerError,
131
- Handlers<ROut | REvent, Self>
132
- >,
133
- "rpc"
134
- > {
135
- readonly rpc: Handlers<ROut | REvent, Self>;
136
- }
125
+ RAlarm = never,
126
+ > = DurableObjectEntrypoint.DurableObjectOptions<
127
+ ROut,
128
+ REvent,
129
+ EventLayerError,
130
+ Handlers<NoInfer<ROut> | REvent | NoInfer<RAlarm>, Self>,
131
+ RAlarm
132
+ > & {
133
+ readonly rpc: Handlers<NoInfer<ROut> | REvent | NoInfer<RAlarm>, Self>;
134
+ };
137
135
 
138
136
  export type LayerOptions = {
139
137
  readonly binding: string;
@@ -159,12 +157,12 @@ export type TagClass<
159
157
  > & {
160
158
  readonly id: Id;
161
159
  readonly methods: MethodDefinitions;
162
- readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
163
- layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
164
- options: Options<ROut, Definition<Id, MethodDefinitions>, REvent, EventLayerError>,
160
+ readonly make: <ROut, LayerError, REvent = never, EventLayerError = never, RAlarm = never>(
161
+ layer: Layer.Layer<ROut, LayerError, DurableObjectEntrypoint.RuntimeContext<NoInfer<RAlarm>>>,
162
+ options: Options<ROut, Definition<Id, MethodDefinitions>, REvent, EventLayerError, RAlarm>,
165
163
  ) => DurableObjectEntrypoint.DurableObjectClass<
166
- Handlers<ROut | REvent, Definition<Id, MethodDefinitions>>,
167
- ROut | REvent
164
+ Handlers<ROut | REvent | RAlarm, Definition<Id, MethodDefinitions>>,
165
+ ROut | REvent | RAlarm
168
166
  >;
169
167
  readonly layer: (
170
168
  options: LayerOptions,
@@ -201,9 +199,9 @@ const makeDefinition = <Id extends string, const MethodDefinitions extends Metho
201
199
  const definition: SelfDefinition = RpcDefinition.make(id, methods);
202
200
 
203
201
  return Object.assign(definition, {
204
- make: <ROut, LayerError, REvent = never, EventLayerError = never>(
205
- layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
206
- options: Options<ROut, SelfDefinition, REvent, EventLayerError>,
202
+ make: <ROut, LayerError, REvent = never, EventLayerError = never, RAlarm = never>(
203
+ layer: Layer.Layer<ROut, LayerError, DurableObjectEntrypoint.RuntimeContext<NoInfer<RAlarm>>>,
204
+ options: Options<ROut, SelfDefinition, REvent, EventLayerError, RAlarm>,
207
205
  ) =>
208
206
  DurableObjectEntrypoint.make(layer, {
209
207
  ...options,
@@ -17,11 +17,13 @@ interface DurableObjectFetcher {
17
17
  }
18
18
 
19
19
  type RpcClient<Api> = {
20
- readonly [Key in keyof Api as Key extends string
21
- ? Api[Key] extends (...args: Array<any>) => any
22
- ? Key
20
+ readonly [
21
+ Key in keyof Api as Key extends string
22
+ ? Api[Key] extends (...args: Array<any>) => any
23
+ ? Key
24
+ : never
23
25
  : never
24
- : never]: Api[Key];
26
+ ]: Api[Key];
25
27
  };
26
28
 
27
29
  type ReservedMethodName = DurableObjectDefinition.ReservedMethodName | "fetch";
@@ -399,11 +399,10 @@ export const fromDurableObjectStorage = (
399
399
  throw exit;
400
400
  }),
401
401
  ).pipe(
402
- Effect.catch(
403
- (cause): Effect.Effect<never, E | StorageOperationError> =>
404
- aborted !== undefined && cause === aborted && Exit.isFailure(aborted)
405
- ? Effect.failCause(aborted.cause)
406
- : Effect.fail(storageError("transaction", cause)),
402
+ Effect.catch((cause): Effect.Effect<never, E | StorageOperationError> =>
403
+ aborted !== undefined && cause === aborted && Exit.isFailure(aborted)
404
+ ? Effect.failCause(aborted.cause)
405
+ : Effect.fail(storageError("transaction", cause)),
407
406
  ),
408
407
  );
409
408
  }),