effect-cf 0.52.0 → 0.54.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 (45) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +7 -1
  3. package/dist/AnalyticsEngine.d.mts +2 -2
  4. package/dist/AnalyticsEngine.mjs +3 -3
  5. package/dist/CloudflareOtlp.d.mts +3 -3
  6. package/dist/CloudflareOtlp.mjs +7 -7
  7. package/dist/D1.d.mts +1 -1
  8. package/dist/DurableObject.d.mts +1 -0
  9. package/dist/DurableObject.mjs +33 -11
  10. package/dist/DurableObjectAlarm.d.mts +74 -9
  11. package/dist/DurableObjectAlarm.mjs +245 -35
  12. package/dist/DurableObjectRpcWebSocket.d.mts +3 -3
  13. package/dist/DurableObjectRpcWebSocket.mjs +3 -3
  14. package/dist/DurableObjectSqlite.d.mts +1 -1
  15. package/dist/DurableObjectSqlite.mjs +2 -2
  16. package/dist/HyperdrivePg.d.mts +1 -1
  17. package/dist/Mcp.d.mts +3 -3
  18. package/dist/Mcp.mjs +3 -3
  19. package/dist/RateLimit.d.mts +50 -0
  20. package/dist/RateLimit.mjs +46 -0
  21. package/dist/RpcWebSocketClient.d.mts +6 -6
  22. package/dist/RpcWebSocketClient.mjs +3 -3
  23. package/dist/Worker.d.mts +2 -2
  24. package/dist/Worker.mjs +4 -4
  25. package/dist/index.d.mts +2 -1
  26. package/dist/index.mjs +2 -1
  27. package/dist/internal/Runtime.mjs +5 -3
  28. package/dist/internal/Telemetry.mjs +1 -1
  29. package/package.json +5 -5
  30. package/src/AnalyticsEngine.ts +4 -4
  31. package/src/CloudflareOtlp.ts +8 -8
  32. package/src/D1.ts +1 -1
  33. package/src/DurableObject.ts +93 -30
  34. package/src/DurableObjectAlarm.ts +601 -59
  35. package/src/DurableObjectRpcWebSocket.ts +3 -3
  36. package/src/DurableObjectSqlite.ts +2 -2
  37. package/src/HyperdrivePg.ts +1 -1
  38. package/src/Mcp.ts +4 -4
  39. package/src/RateLimit.ts +110 -0
  40. package/src/RpcWebSocketClient.ts +6 -6
  41. package/src/Worker.ts +4 -4
  42. package/src/index.ts +1 -0
  43. package/src/internal/Entrypoint.ts +8 -4
  44. package/src/internal/Runtime.ts +12 -7
  45. package/src/internal/Telemetry.ts +1 -1
@@ -0,0 +1,46 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
+ import { layer as layer$1 } from "./Binding.mjs";
3
+ import * as Context from "effect/Context";
4
+ import * as Data from "effect/Data";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Predicate from "effect/Predicate";
7
+ //#region src/RateLimit.ts
8
+ var RateLimit_exports = /* @__PURE__ */ __exportAll({
9
+ RateLimitOperationError: () => RateLimitOperationError,
10
+ Tag: () => Tag,
11
+ isRateLimit: () => isRateLimit,
12
+ layer: () => layer,
13
+ make: () => make,
14
+ makeClient: () => makeClient
15
+ });
16
+ const expectedRateLimit = "Rate Limiting binding with limit()";
17
+ var RateLimitOperationError = class extends Data.TaggedError("RateLimitOperationError") {
18
+ get message() {
19
+ return `Cloudflare rate limit binding "${this.binding}" operation "limit" failed`;
20
+ }
21
+ };
22
+ const isRateLimit = (value) => Predicate.hasProperty(value, "limit") && Predicate.isFunction(value.limit);
23
+ const makeClient = (definition) => (rateLimit) => ({
24
+ definition,
25
+ limit: (options) => Effect.tryPromise({
26
+ try: () => rateLimit.limit(options),
27
+ catch: (cause) => new RateLimitOperationError({
28
+ binding: definition.binding,
29
+ operation: "limit",
30
+ cause
31
+ })
32
+ }),
33
+ rawUnsafe: Effect.succeed(rateLimit)
34
+ });
35
+ const layer = (tag, definition) => layer$1(tag, definition.binding, isRateLimit, makeClient(definition), { expected: expectedRateLimit });
36
+ const make = (id) => Tag()(id);
37
+ const Tag = () => (id) => {
38
+ const tag = Context.Service()(id);
39
+ const makeLayer = (definition) => layer(tag, definition);
40
+ return Object.assign(tag, {
41
+ id,
42
+ layer: makeLayer
43
+ });
44
+ };
45
+ //#endregion
46
+ export { RateLimitOperationError, RateLimit_exports, Tag, isRateLimit, layer, make, makeClient };
@@ -1,11 +1,11 @@
1
1
  import * as Context from "effect/Context";
2
2
  import * as Layer from "effect/Layer";
3
- import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
4
- import * as RpcClient from "effect/unstable/rpc/RpcClient";
5
- import * as Socket from "effect/unstable/socket/Socket";
6
- import * as Rpc from "effect/unstable/rpc/Rpc";
7
- import { RpcClientError } from "effect/unstable/rpc/RpcClientError";
8
- import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
3
+ import * as RpcSerialization from "effect/rpc/RpcSerialization";
4
+ import * as RpcClient from "effect/rpc/RpcClient";
5
+ import * as Socket from "effect/socket/Socket";
6
+ import * as Rpc from "effect/rpc/Rpc";
7
+ import { RpcClientError } from "effect/rpc/RpcClientError";
8
+ import * as RpcGroup from "effect/rpc/RpcGroup";
9
9
  declare namespace RpcWebSocketClient_d_exports {
10
10
  export { LayerOptions, layer };
11
11
  }
@@ -1,8 +1,8 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
2
2
  import * as Layer from "effect/Layer";
3
- import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
4
- import * as RpcClient from "effect/unstable/rpc/RpcClient";
5
- import * as Socket from "effect/unstable/socket/Socket";
3
+ import * as RpcSerialization from "effect/rpc/RpcSerialization";
4
+ import * as RpcClient from "effect/rpc/RpcClient";
5
+ import * as Socket from "effect/socket/Socket";
6
6
  //#region src/RpcWebSocketClient.ts
7
7
  var RpcWebSocketClient_exports = /* @__PURE__ */ __exportAll({ layer: () => layer });
8
8
  /**
package/dist/Worker.d.mts CHANGED
@@ -10,8 +10,8 @@ import * as Context$1 from "effect/Context";
10
10
  import * as Effect from "effect/Effect";
11
11
  import * as Layer$1 from "effect/Layer";
12
12
  import { WorkerEntrypoint } from "cloudflare:workers";
13
- import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
14
- import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
13
+ import * as HttpServerRequest from "effect/http/HttpServerRequest";
14
+ import * as HttpServerResponse from "effect/http/HttpServerResponse";
15
15
  import { Cause, Scope } from "effect";
16
16
  declare namespace Worker_d_exports {
17
17
  export { Api, BindingNotFoundError, BindingValidationError, Definition, ExecutionContext, FetchHandler, FetchWorkerOptions, HandlerEffect, Handlers, LayerOptions, Method, Methods, NativeRequest, NativeSchema, NoReservedMethods, Options, ReservedMethodName, RpcHandlers, RpcSchema, RunOptions, RunSymbol, ServerApi, ServiceBindingFetchError, ServiceBindingRpcError, ServiceCall, ServiceFreeSchema, ServiceRpc, ServiceScopedCall, Tag, TagClass, WorkerClass, WorkerContext, WorkerContextService, WorkerContextWaitUntilOptions, WorkerFetchSuccess, WorkerHandler, WorkerOptions, WorkerRpc, WorkerRpcContract, WorkerRpcHandler, WorkerRpcContract as WorkerRpcShape, implement, isWebSocketUpgrade, make, makeFetchHandler, method, native };
package/dist/Worker.mjs CHANGED
@@ -13,10 +13,10 @@ import * as Layer from "effect/Layer";
13
13
  import { WorkerEntrypoint } from "cloudflare:workers";
14
14
  import * as Tracer from "effect/Tracer";
15
15
  import * as References from "effect/References";
16
- import * as HttpEffect from "effect/unstable/http/HttpEffect";
17
- import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware";
18
- import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
19
- import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
16
+ import * as HttpEffect from "effect/http/HttpEffect";
17
+ import * as HttpMiddleware from "effect/http/HttpMiddleware";
18
+ import * as HttpServerRequest from "effect/http/HttpServerRequest";
19
+ import * as HttpServerResponse from "effect/http/HttpServerResponse";
20
20
  //#region src/Worker.ts
21
21
  var Worker_exports = /* @__PURE__ */ __exportAll({
22
22
  ExecutionContext: () => ExecutionContext,
package/dist/index.d.mts CHANGED
@@ -36,10 +36,11 @@ import { DurableObjectSqlite_d_exports } from "./DurableObjectSqlite.mjs";
36
36
  import { Email_d_exports } from "./Email.mjs";
37
37
  import { Images_d_exports } from "./Images.mjs";
38
38
  import { Kv_d_exports } from "./Kv.mjs";
39
+ import { RateLimit_d_exports } from "./RateLimit.mjs";
39
40
  import { R2_d_exports } from "./R2.mjs";
40
41
  import { RpcTargets_d_exports } from "./RpcTargets.mjs";
41
42
  import { Vectorize_d_exports } from "./Vectorize.mjs";
42
43
  import { WebTransport_d_exports } from "./WebTransport.mjs";
43
44
  import { WorkflowDefinition_d_exports } from "./WorkflowDefinition.mjs";
44
45
  import { Workflow_d_exports } from "./Workflow.mjs";
45
- export { AiGateway_d_exports as AiGateway, AnalyticsEngine_d_exports as AnalyticsEngine, Artifacts_d_exports as Artifacts, Binding_d_exports as Binding, BrowserRendering_d_exports as BrowserRendering, Cache_d_exports as Cache, CloudflareOtlp_d_exports as CloudflareOtlp, CloudflareTracer_d_exports as CloudflareTracer, ContainerFiles_d_exports as ContainerFiles, ContainerNamespace_d_exports as ContainerNamespace, D1_d_exports as D1, DurableObject_d_exports as DurableObject, DurableObjectAlarm_d_exports as DurableObjectAlarm, DurableObjectContainer_d_exports as DurableObjectContainer, DurableObjectDefinition_d_exports as DurableObjectDefinition, DurableObjectNamespace_d_exports as DurableObjectNamespace, DurableObjectRpcWebSocket_d_exports as DurableObjectRpcWebSocket, DurableObjectSqlite_d_exports as DurableObjectSqlite, DurableObjectState_d_exports as DurableObjectState, DurableObjectStorage_d_exports as DurableObjectStorage, DurableObjectWebSocket_d_exports as DurableObjectWebSocket, Email_d_exports as Email, Environment_d_exports as Environment, Hyperdrive_d_exports as Hyperdrive, Images_d_exports as Images, Kv_d_exports as Kv, Queue_d_exports as Queue, QueueBinding_d_exports as QueueBinding, QueueDefinition_d_exports as QueueDefinition, R2_d_exports as R2, Rpc_d_exports as Rpc, RpcDefinition_d_exports as RpcDefinition, RpcTargets_d_exports as RpcTargets, RpcTracing_d_exports as RpcTracing, RpcWebSocketClient_d_exports as RpcWebSocketClient, ServiceBinding_d_exports as ServiceBinding, Vectorize_d_exports as Vectorize, WebTransport_d_exports as WebTransport, Worker_d_exports as Worker, WorkerConfig, WorkerDefinition_d_exports as WorkerDefinition, type WorkerEnv, WorkerEnvironment, WorkersAi_d_exports as WorkersAi, Workflow_d_exports as Workflow, WorkflowBinding_d_exports as WorkflowBinding, WorkflowDefinition_d_exports as WorkflowDefinition };
46
+ export { AiGateway_d_exports as AiGateway, AnalyticsEngine_d_exports as AnalyticsEngine, Artifacts_d_exports as Artifacts, Binding_d_exports as Binding, BrowserRendering_d_exports as BrowserRendering, Cache_d_exports as Cache, CloudflareOtlp_d_exports as CloudflareOtlp, CloudflareTracer_d_exports as CloudflareTracer, ContainerFiles_d_exports as ContainerFiles, ContainerNamespace_d_exports as ContainerNamespace, D1_d_exports as D1, DurableObject_d_exports as DurableObject, DurableObjectAlarm_d_exports as DurableObjectAlarm, DurableObjectContainer_d_exports as DurableObjectContainer, DurableObjectDefinition_d_exports as DurableObjectDefinition, DurableObjectNamespace_d_exports as DurableObjectNamespace, DurableObjectRpcWebSocket_d_exports as DurableObjectRpcWebSocket, DurableObjectSqlite_d_exports as DurableObjectSqlite, DurableObjectState_d_exports as DurableObjectState, DurableObjectStorage_d_exports as DurableObjectStorage, DurableObjectWebSocket_d_exports as DurableObjectWebSocket, Email_d_exports as Email, Environment_d_exports as Environment, Hyperdrive_d_exports as Hyperdrive, Images_d_exports as Images, Kv_d_exports as Kv, Queue_d_exports as Queue, QueueBinding_d_exports as QueueBinding, QueueDefinition_d_exports as QueueDefinition, R2_d_exports as R2, RateLimit_d_exports as RateLimit, Rpc_d_exports as Rpc, RpcDefinition_d_exports as RpcDefinition, RpcTargets_d_exports as RpcTargets, RpcTracing_d_exports as RpcTracing, RpcWebSocketClient_d_exports as RpcWebSocketClient, ServiceBinding_d_exports as ServiceBinding, Vectorize_d_exports as Vectorize, WebTransport_d_exports as WebTransport, Worker_d_exports as Worker, WorkerConfig, WorkerDefinition_d_exports as WorkerDefinition, type WorkerEnv, WorkerEnvironment, WorkersAi_d_exports as WorkersAi, Workflow_d_exports as Workflow, WorkflowBinding_d_exports as WorkflowBinding, WorkflowDefinition_d_exports as WorkflowDefinition };
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 };
@@ -1,6 +1,6 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Option from "effect/Option";
3
- import { Flusher } from "effect/unstable/observability/OtlpExporter";
3
+ import { Flusher } from "effect/observability/OtlpExporter";
4
4
  //#region src/internal/Telemetry.ts
5
5
  /** Maximum event lifetime spent on an internally scheduled telemetry flush. */
6
6
  const scheduledFlushTimeout = "2 seconds";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.52.0",
3
+ "version": "0.54.0",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -100,11 +100,11 @@
100
100
  "@cloudflare/sandbox": "^0.13.0-next.738.2 || ^1.0.0",
101
101
  "@cloudflare/vitest-plugin": "^1.0.0",
102
102
  "@cloudflare/workers-types": "^5.20260926.1",
103
- "@effect/sql-d1": "^4.0.0-rc.115",
104
- "@effect/sql-pg": "^4.0.0-rc.115",
105
- "@effect/sql-sqlite-do": "^4.0.0-rc.115",
103
+ "@effect/sql-d1": "^4.0.0",
104
+ "@effect/sql-pg": "^4.0.0",
105
+ "@effect/sql-sqlite-do": "^4.0.0",
106
106
  "@modelcontextprotocol/server": "^2.0.0",
107
- "effect": "^4.0.0-rc.115"
107
+ "effect": "^4.0.0"
108
108
  },
109
109
  "peerDependenciesMeta": {
110
110
  "@cloudflare/computer": {
@@ -8,10 +8,10 @@ import * as Predicate from "effect/Predicate";
8
8
  import * as Result from "effect/Result";
9
9
  import * as S from "effect/Schema";
10
10
  import type { Redacted } from "effect";
11
- import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
12
- import * as HttpClient from "effect/unstable/http/HttpClient";
13
- import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
14
- import type { Headers, HttpClientResponse } from "effect/unstable/http";
11
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
12
+ import * as HttpClient from "effect/http/HttpClient";
13
+ import * as HttpClientRequest from "effect/http/HttpClientRequest";
14
+ import type { Headers, HttpClientResponse } from "effect/http";
15
15
 
16
16
  import * as Binding from "./Binding";
17
17
  import type { WorkerEnvironment } from "./Environment";
@@ -9,14 +9,14 @@ import * as Effect from "effect/Effect";
9
9
  import * as Layer from "effect/Layer";
10
10
  import * as Option from "effect/Option";
11
11
  import type * as Tracer from "effect/Tracer";
12
- import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
13
- import type { Headers } from "effect/unstable/http";
14
- import * as OtlpExporter from "effect/unstable/observability/OtlpExporter";
15
- import * as OtlpLogger from "effect/unstable/observability/OtlpLogger";
16
- import * as OtlpMetrics from "effect/unstable/observability/OtlpMetrics";
17
- import * as OtlpResource from "effect/unstable/observability/OtlpResource";
18
- import * as OtlpSerialization from "effect/unstable/observability/OtlpSerialization";
19
- import * as OtlpTracer from "effect/unstable/observability/OtlpTracer";
12
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
13
+ import type { Headers } from "effect/http";
14
+ import * as OtlpExporter from "effect/observability/OtlpExporter";
15
+ import * as OtlpLogger from "effect/observability/OtlpLogger";
16
+ import * as OtlpMetrics from "effect/observability/OtlpMetrics";
17
+ import * as OtlpResource from "effect/observability/OtlpResource";
18
+ import * as OtlpSerialization from "effect/observability/OtlpSerialization";
19
+ import * as OtlpTracer from "effect/observability/OtlpTracer";
20
20
 
21
21
  import { DurableObjectState } from "./DurableObjectState";
22
22
  import { WorkerConfig, WorkerEnvironment } from "./Environment";
package/src/D1.ts CHANGED
@@ -3,7 +3,7 @@ import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
4
  import * as Predicate from "effect/Predicate";
5
5
  import type { Config } from "effect";
6
- import type { SqlClient } from "effect/unstable/sql";
6
+ import type { SqlClient } from "effect/sql";
7
7
 
8
8
  import * as Binding from "./Binding";
9
9
  import type { WorkerEnvironment } from "./Environment";
@@ -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 {