effect-cf 0.53.0 → 0.55.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/dist/index.mjs CHANGED
@@ -35,6 +35,7 @@ import { Email_exports } from "./Email.mjs";
35
35
  import { Hyperdrive_exports } from "./Hyperdrive.mjs";
36
36
  import { Images_exports } from "./Images.mjs";
37
37
  import { Kv_exports } from "./Kv.mjs";
38
+ import { RateLimit_exports } from "./RateLimit.mjs";
38
39
  import { R2_exports } from "./R2.mjs";
39
40
  import { RpcWebSocketClient_exports } from "./RpcWebSocketClient.mjs";
40
41
  import { Vectorize_exports } from "./Vectorize.mjs";
@@ -42,4 +43,4 @@ import { WebTransport_exports } from "./WebTransport.mjs";
42
43
  import { WorkflowBinding_exports } from "./WorkflowBinding.mjs";
43
44
  import { WorkflowDefinition_exports } from "./WorkflowDefinition.mjs";
44
45
  import { Workflow_exports } from "./Workflow.mjs";
45
- export { AiGateway_exports as AiGateway, AnalyticsEngine_exports as AnalyticsEngine, Artifacts_exports as Artifacts, Binding_exports as Binding, BrowserRendering_exports as BrowserRendering, Cache_exports as Cache, CloudflareOtlp_exports as CloudflareOtlp, CloudflareTracer_exports as CloudflareTracer, ContainerFiles_exports as ContainerFiles, ContainerNamespace_exports as ContainerNamespace, D1_exports as D1, DurableObject_exports as DurableObject, DurableObjectAlarm_exports as DurableObjectAlarm, DurableObjectContainer_exports as DurableObjectContainer, DurableObjectDefinition_exports as DurableObjectDefinition, DurableObjectNamespace_exports as DurableObjectNamespace, DurableObjectRpcWebSocket_exports as DurableObjectRpcWebSocket, DurableObjectSqlite_exports as DurableObjectSqlite, DurableObjectState_exports as DurableObjectState, DurableObjectStorage_exports as DurableObjectStorage, DurableObjectWebSocket_exports as DurableObjectWebSocket, Email_exports as Email, Environment_exports as Environment, Hyperdrive_exports as Hyperdrive, Images_exports as Images, Kv_exports as Kv, Queue_exports as Queue, QueueBinding_exports as QueueBinding, QueueDefinition_exports as QueueDefinition, R2_exports as R2, Rpc_exports as Rpc, RpcDefinition_exports as RpcDefinition, RpcTargets_exports as RpcTargets, RpcTracing_exports as RpcTracing, RpcWebSocketClient_exports as RpcWebSocketClient, ServiceBinding_exports as ServiceBinding, Vectorize_exports as Vectorize, WebTransport_exports as WebTransport, Worker_exports as Worker, WorkerConfig, WorkerDefinition_exports as WorkerDefinition, WorkerEnvironment, WorkersAi_exports as WorkersAi, Workflow_exports as Workflow, WorkflowBinding_exports as WorkflowBinding, WorkflowDefinition_exports as WorkflowDefinition };
46
+ export { AiGateway_exports as AiGateway, AnalyticsEngine_exports as AnalyticsEngine, Artifacts_exports as Artifacts, Binding_exports as Binding, BrowserRendering_exports as BrowserRendering, Cache_exports as Cache, CloudflareOtlp_exports as CloudflareOtlp, CloudflareTracer_exports as CloudflareTracer, ContainerFiles_exports as ContainerFiles, ContainerNamespace_exports as ContainerNamespace, D1_exports as D1, DurableObject_exports as DurableObject, DurableObjectAlarm_exports as DurableObjectAlarm, DurableObjectContainer_exports as DurableObjectContainer, DurableObjectDefinition_exports as DurableObjectDefinition, DurableObjectNamespace_exports as DurableObjectNamespace, DurableObjectRpcWebSocket_exports as DurableObjectRpcWebSocket, DurableObjectSqlite_exports as DurableObjectSqlite, DurableObjectState_exports as DurableObjectState, DurableObjectStorage_exports as DurableObjectStorage, DurableObjectWebSocket_exports as DurableObjectWebSocket, Email_exports as Email, Environment_exports as Environment, Hyperdrive_exports as Hyperdrive, Images_exports as Images, Kv_exports as Kv, Queue_exports as Queue, QueueBinding_exports as QueueBinding, QueueDefinition_exports as QueueDefinition, R2_exports as R2, RateLimit_exports as RateLimit, Rpc_exports as Rpc, RpcDefinition_exports as RpcDefinition, RpcTargets_exports as RpcTargets, RpcTracing_exports as RpcTracing, RpcWebSocketClient_exports as RpcWebSocketClient, ServiceBinding_exports as ServiceBinding, Vectorize_exports as Vectorize, WebTransport_exports as WebTransport, Worker_exports as Worker, WorkerConfig, WorkerDefinition_exports as WorkerDefinition, WorkerEnvironment, WorkersAi_exports as WorkersAi, Workflow_exports as Workflow, WorkflowBinding_exports as WorkflowBinding, WorkflowDefinition_exports as WorkflowDefinition };
@@ -31,14 +31,16 @@ const makeEntrypointRuntime = (layer, env, services) => {
31
31
  const entrypointServices = Layer.mergeAll(services, ConfigProvider.layer(WorkerConfig.providerFromEnv(env)), Layer.succeed(WorkerEnvironment, env));
32
32
  return ManagedRuntime.make(provideEntrypointServices(layer, entrypointServices));
33
33
  };
34
+ /** Close event resources before an outer lifecycle (such as alarm reconciliation) completes. */
35
+ const scopeEvent = (effect) => Effect.scoped(withScope(effect));
34
36
  function runEventPromise(...args) {
35
37
  const [runtime, effect, eventLayer, parent, onFailure] = args;
36
38
  if (eventLayer === void 0) {
37
- const event = Effect.scoped(withScope(effect));
39
+ const event = scopeEvent(effect);
38
40
  return runPromise(runtime, parent === void 0 ? event : Effect.withParentSpan(event, parent), onFailure);
39
41
  }
40
- const event = Effect.scoped(withScope(effect.pipe(Effect.provide(eventLayer, { local: true }))));
42
+ const event = scopeEvent(effect.pipe(Effect.provide(eventLayer, { local: true })));
41
43
  return runPromise(runtime, parent === void 0 ? event : Effect.withParentSpan(event, parent), onFailure);
42
44
  }
43
45
  //#endregion
44
- export { makeEntrypointRuntime, runEventPromise };
46
+ export { makeEntrypointRuntime, runEventPromise, scopeEvent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.53.0",
3
+ "version": "0.55.0",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,8 +10,14 @@ import { WorkerEnvironment, type WorkerEnv } from "./Environment";
10
10
  import { DurableObjectState, fromDurableObjectState } from "./DurableObjectState";
11
11
  import {
12
12
  DurableObjectAlarm,
13
+ DispatchAfterEvent,
14
+ HasDueAlarms,
15
+ RunAlarm,
16
+ dispatchRawAlarm,
13
17
  type AlarmRegistration,
14
18
  type AlarmService,
19
+ type InvalidAlarmRegistrationError,
20
+ type InvalidScheduleConfigurationError,
15
21
  } from "./DurableObjectAlarm";
16
22
  import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
17
23
  import * as RpcDefinition from "./RpcDefinition";
@@ -229,6 +235,7 @@ export type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (
229
235
  env: WorkerEnv,
230
236
  ) => CloudflareDurableObject<WorkerEnv> &
231
237
  DurableObjectRpcApi<Rpc, ROut> & {
238
+ alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> | void;
232
239
  [RunSymbol]<A, E>(
233
240
  effect: Effect.Effect<A, E, HandlerContext<ROut>>,
234
241
  options?: RunOptions,
@@ -274,12 +281,15 @@ export function make<
274
281
  ): DurableObjectClass<Rpc, ROut | REvent | RAlarm> {
275
282
  const registration =
276
283
  options.alarms !== undefined && !Effect.isEffect(options.alarms) ? options.alarms : undefined;
277
- const logicalAlarms = (
278
- registration?.run ?? (Effect.isEffect(options.alarms) ? options.alarms : undefined)
279
- )?.pipe(Effect.asVoid);
284
+ const legacyAlarms = Effect.isEffect(options.alarms)
285
+ ? options.alarms.pipe(Effect.asVoid)
286
+ : undefined;
280
287
 
281
288
  class EffectDurableObject extends CloudflareDurableObject<WorkerEnv> {
282
- readonly runtime: ManagedRuntime.ManagedRuntime<RuntimeContext<ROut | RAlarm>, LayerError>;
289
+ readonly runtime: ManagedRuntime.ManagedRuntime<
290
+ RuntimeContext<ROut | RAlarm>,
291
+ LayerError | InvalidAlarmRegistrationError
292
+ >;
283
293
 
284
294
  constructor(state: globalThis.DurableObjectState, env: WorkerEnv) {
285
295
  super(state, env);
@@ -290,12 +300,16 @@ export function make<
290
300
  // SAFETY: RAlarm is inferred only from the registration's layer; without a registration it is never.
291
301
  const services = (registration?.layer ?? Layer.empty).pipe(
292
302
  Layer.provideMerge(baseServices),
293
- ) as Layer.Layer<DurableObjectState | DurableObjectAlarm | RAlarm>;
303
+ ) as Layer.Layer<
304
+ DurableObjectState | DurableObjectAlarm | RAlarm,
305
+ InvalidAlarmRegistrationError
306
+ >;
294
307
 
295
308
  this.runtime = Runtime.makeEntrypointRuntime<
296
309
  ROut,
297
310
  LayerError,
298
- DurableObjectState | DurableObjectAlarm | RAlarm
311
+ DurableObjectState | DurableObjectAlarm | RAlarm,
312
+ InvalidAlarmRegistrationError
299
313
  >(layer, env, services);
300
314
 
301
315
  const initialize = options.initialize;
@@ -322,27 +336,54 @@ export function make<
322
336
  const eventLayer = runOptions.eventLayer === false ? undefined : options.eventLayer;
323
337
  const parent = runOptions.rpc?.parent;
324
338
  const parentSpan = parent === undefined ? undefined : Tracer.externalSpan(parent);
339
+ const afterResponse = runOptions.event === "rpc" || runOptions.event === "fetch";
340
+ let dispatchAfterEvent = false;
341
+ const handler = afterResponse
342
+ ? effect.pipe(
343
+ Effect.onExit(() =>
344
+ Effect.flatMap(DurableObjectAlarm, (alarms) =>
345
+ Effect.map(alarms[DispatchAfterEvent], (enabled) => {
346
+ dispatchAfterEvent = enabled;
347
+ }),
348
+ ),
349
+ ),
350
+ )
351
+ : effect;
352
+ let response: Promise<A>;
325
353
 
326
354
  if (eventLayer === undefined) {
327
355
  // SAFETY: initialize cannot require event services; without an event layer, all requirements are already provided.
328
- return Runtime.runEventPromise(
356
+ response = Runtime.runEventPromise(
329
357
  this.runtime,
330
358
  // @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
331
- effect as Effect.Effect<A, E, HandlerContext<ROut | RAlarm>>,
359
+ handler as Effect.Effect<A, E, HandlerContext<ROut | RAlarm>>,
332
360
  undefined,
333
361
  parentSpan,
334
362
  runOptions.onFailure,
335
363
  );
364
+ } else {
365
+ response = Runtime.runEventPromise<
366
+ A,
367
+ E,
368
+ RuntimeContext<ROut | RAlarm>,
369
+ REvent,
370
+ EventLayerError,
371
+ LayerError | InvalidAlarmRegistrationError
372
+ >(this.runtime, handler, eventLayer, parentSpan, runOptions.onFailure);
336
373
  }
337
374
 
338
- return Runtime.runEventPromise<
339
- A,
340
- E,
341
- RuntimeContext<ROut | RAlarm>,
342
- REvent,
343
- EventLayerError,
344
- LayerError
345
- >(this.runtime, effect, eventLayer, parentSpan, runOptions.onFailure);
375
+ if (!afterResponse) return response;
376
+
377
+ return response.finally(() => {
378
+ if (dispatchAfterEvent) {
379
+ this.ctx.waitUntil(
380
+ // The default scheduler yields to a later task, after the response chain unwinds.
381
+ Effect.runPromise(
382
+ Effect.yieldNow.pipe(Effect.andThen(Effect.promise(() => this.#runAlarm(false)))),
383
+ ),
384
+ );
385
+ }
386
+ });
346
387
  }
347
388
 
348
389
  fetch(request: Request): Promise<Response> {
@@ -357,21 +398,43 @@ export function make<
357
398
  });
358
399
  }
359
400
 
360
- alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> | void {
361
- const rawAlarm = options.alarm?.(alarmInfo);
362
- const alarmEffect =
363
- logicalAlarms !== undefined && rawAlarm !== undefined
364
- ? Effect.gen(function* () {
365
- yield* logicalAlarms;
366
- yield* rawAlarm;
367
- })
368
- : (logicalAlarms ?? rawAlarm);
369
-
370
- if (alarmEffect !== undefined) {
371
- return this[RunSymbol](alarmEffect.pipe(Effect.onExit(() => scheduleTelemetryFlush)), {
401
+ alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> {
402
+ return this.#runAlarm(true, alarmInfo);
403
+ }
404
+
405
+ #runAlarm(native: boolean, alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> {
406
+ const event = (beginDispatch: Effect.Effect<void, InvalidScheduleConfigurationError>) => {
407
+ const alarmEffect = Effect.gen(function* () {
408
+ const alarms = yield* DurableObjectAlarm;
409
+
410
+ if (!native && !(yield* alarms[HasDueAlarms])) return;
411
+ const rawAlarm = options.alarm?.(alarmInfo);
412
+ const customAlarm =
413
+ legacyAlarms === undefined
414
+ ? (rawAlarm ?? Effect.void)
415
+ : legacyAlarms.pipe(Effect.andThen(rawAlarm ?? Effect.void));
416
+
417
+ yield* beginDispatch;
418
+ // Custom dispatchers, including Effect-form alarms, recover retained checkpoints.
419
+ yield* registration === undefined || rawAlarm !== undefined
420
+ ? dispatchRawAlarm(customAlarm, registration)
421
+ : registration.run.pipe(Effect.asVoid);
422
+ }).pipe(Effect.onExit(() => scheduleTelemetryFlush));
423
+
424
+ return Runtime.scopeEvent(
425
+ options.eventLayer === undefined
426
+ ? alarmEffect
427
+ : alarmEffect.pipe(Effect.provide(options.eventLayer, { local: true })),
428
+ );
429
+ };
430
+
431
+ return this[RunSymbol](
432
+ Effect.flatMap(DurableObjectAlarm, (alarms) => alarms[RunAlarm](event, native)),
433
+ {
372
434
  event: "alarm",
373
- });
374
- }
435
+ eventLayer: false,
436
+ },
437
+ );
375
438
  }
376
439
 
377
440
  webSocketMessage(socket: WebSocket, message: string | ArrayBuffer): Promise<void> | void {