@effect-agent/platform-cloudflare 0.0.1-beta.6 → 0.1.0-beta.8

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,22 @@
1
1
  {
2
2
  "name": "@effect-agent/platform-cloudflare",
3
- "version": "0.0.1-beta.6",
3
+ "version": "0.1.0-beta.8",
4
+ "exports": {
5
+ ".": {
6
+ "types": "./dist/index.d.mts",
7
+ "default": "./dist/index.mjs"
8
+ }
9
+ },
10
+ "dependencies": {
11
+ "@effect-agent/core": "0.1.0-beta.8",
12
+ "@effect-agent/sandbox": "0.1.0-beta.8",
13
+ "@effect-agent/session": "0.1.0-beta.8",
14
+ "@effect-agent/storage-cloudflare": "0.1.0-beta.8",
15
+ "@effect/platform-browser": "4.0.0-beta.107",
16
+ "@effect/sql-sqlite-do": "4.0.0-beta.107",
17
+ "effect": "4.0.0-beta.107",
18
+ "effect-cf": "0.25.3"
19
+ },
4
20
  "description": "Class DC layer assembly for Effect Agent: SQLite-backed Conversation Durable Objects with alarm-driven recovery.",
5
21
  "license": "MIT",
6
22
  "repository": {
@@ -13,12 +29,6 @@
13
29
  "src"
14
30
  ],
15
31
  "type": "module",
16
- "exports": {
17
- ".": {
18
- "types": "./dist/index.d.mts",
19
- "default": "./dist/index.mjs"
20
- }
21
- },
22
32
  "publishConfig": {
23
33
  "access": "public"
24
34
  },
@@ -27,21 +37,12 @@
27
37
  "check": "tsc --noEmit -p tsconfig.json",
28
38
  "test": "vitest run"
29
39
  },
30
- "dependencies": {
31
- "@effect-agent/core": "0.0.1-beta.6",
32
- "@effect-agent/sandbox": "0.0.1-beta.5",
33
- "@effect-agent/session": "0.0.1-beta.6",
34
- "@effect-agent/storage-cloudflare": "0.0.1-beta.6",
35
- "@effect/platform-browser": "4.0.0-beta.107",
36
- "@effect/sql-sqlite-do": "4.0.0-beta.107",
37
- "effect": "4.0.0-beta.107"
38
- },
39
40
  "devDependencies": {
40
41
  "@cloudflare/vitest-pool-workers": "0.21.3",
41
42
  "@cloudflare/workers-types": "5.20260813.1",
42
- "@effect-agent/capabilities": "0.0.1-beta.5",
43
- "@effect-agent/engine": "0.0.1-beta.5",
44
- "@effect-agent/testing": "0.0.1-beta.5",
43
+ "@effect-agent/capabilities": "0.1.0-beta.6",
44
+ "@effect-agent/engine": "0.1.0-beta.6",
45
+ "@effect-agent/testing": "0.1.0-beta.6",
45
46
  "@effect/vitest": "4.0.0-beta.107",
46
47
  "esbuild": "0.28.1",
47
48
  "miniflare": "4.20260730.0",
package/src/config.ts CHANGED
@@ -103,8 +103,6 @@ export class CloudflareDurableRuntimeConfigValue extends Schema.Class<Cloudflare
103
103
  abortPollInterval: PositiveMillis,
104
104
  /** Canonical observation poll cadence of the Durable Object store. */
105
105
  observationPollInterval: NonNegativeMillis,
106
- /** Cooperative background-export budget; native Object delivery never awaits the flush. */
107
- telemetryFlushTimeout: PositiveMillis,
108
106
  /** Per-value byte bound; must stay under the platform's 2 MB SQLite value limit. */
109
107
  maxStoredValueBytes: Schema.Int.check(
110
108
  Schema.isGreaterThan(0),
@@ -131,7 +129,6 @@ export const CLOUDFLARE_RUNTIME_DEFAULTS = {
131
129
  leaseRenewalInterval: 10_000,
132
130
  abortPollInterval: 500,
133
131
  observationPollInterval: 25,
134
- telemetryFlushTimeout: 2_000,
135
132
  maxStoredValueBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
136
133
  verifyOnOpen: false,
137
134
  maxQueueDepthPerLane: 256,
@@ -29,16 +29,26 @@ import {
29
29
  SubmissionLookupByKey,
30
30
  type DurableSubmitAgent,
31
31
  } from "@effect-agent/session";
32
- import { DurableObject } from "cloudflare:workers";
33
- import { Effect, Layer, ManagedRuntime, Option, Schema, Stream } from "effect";
32
+ import type { DurableObject as CloudflareDurableObject } from "cloudflare:workers";
33
+ import { Effect, Layer, Option, Schema, Stream } from "effect";
34
+ import {
35
+ DurableObject as EffectCfDurableObject,
36
+ DurableObjectState as EffectCfDurableObjectState,
37
+ WorkerEnvironment,
38
+ } from "effect-cf";
34
39
 
35
- import { ConversationMaintenance, DurableAlarmError, DurableAlarmService } from "./alarm.ts";
40
+ import {
41
+ ConversationMaintenance,
42
+ DurableAlarmError,
43
+ DurableAlarmService,
44
+ type MaintenancePassFailure,
45
+ } from "./alarm.ts";
36
46
  import {
37
47
  ConversationObjectIdentity,
38
48
  DurableObjectContext,
39
- conversationNamespaceLayer,
49
+ ConversationObjectNamespace,
50
+ conversationNamespaceFromEnv,
40
51
  type CloudflareBindingError,
41
- type ConversationObjectNamespace,
42
52
  } from "./bindings.ts";
43
53
  import {
44
54
  AbortRecorded,
@@ -68,18 +78,10 @@ import {
68
78
  type CloudflareDurableRuntimeOptions,
69
79
  type CloudflareDurableRuntimeServices,
70
80
  } from "./layers.ts";
71
- import {
72
- flushCloudflareRuntimeTelemetry,
73
- logCloudflareWaitUntilRegistrationFailure,
74
- makeCloudflareTelemetryFlushCoordinator,
75
- registerCloudflareTelemetryAfterNativeSettlement,
76
- withCloudflareNativeSpanFailure,
77
- type CloudflareTelemetryFlushCoordinator,
78
- } from "./telemetry-internal.ts";
79
- import { CloudflareRuntimeTelemetry } from "./telemetry.ts";
80
81
 
81
82
  /**
82
- * `makeConversationObjectClass(options, telemetry?)` — the Conversation Durable Object (plan §1.4,
83
+ * `makeConversationObjectClass(options, observability?)` — the Conversation Durable Object
84
+ * (plan §1.4,
83
85
  * D-P6-1): a factory returning a class that applications export from their Worker entry.
84
86
  * One SQLite-backed Object per Conversation is the serialized owner (durability §6); the
85
87
  * Object never runs `runResolvedWorker`'s infinite loop — each ingress event or alarm runs
@@ -105,15 +107,12 @@ export interface ConversationObjectOptions extends CloudflareDurableRuntimeOptio
105
107
  readonly namespaceBinding: string;
106
108
  }
107
109
 
108
- type ConversationObjectError<TelemetryError> =
110
+ type EndpointServices = CloudflareDurableRuntimeServices | DurableObjectContext;
111
+ type RuntimeServices = EndpointServices | ConversationObjectNamespace;
112
+ type ConversationObjectInitializationError =
109
113
  | CloudflareDurableRuntimeInitializationError
110
114
  | CloudflareBindingError
111
- | TelemetryError;
112
-
113
- type EndpointServices =
114
- | CloudflareDurableRuntimeServices
115
- | DurableObjectContext
116
- | CloudflareRuntimeTelemetry;
115
+ | MaintenancePassFailure;
117
116
 
118
117
  /** Port envelope tags whose owner-side execution durably mutates this Object's lane. */
119
118
  const MUTATING_PORT_TAGS: ReadonlySet<string> = new Set([
@@ -609,71 +608,56 @@ const wakeEndpoint: Effect.Effect<void, never, EndpointServices> = Effect.gen(fu
609
608
  );
610
609
  });
611
610
 
612
- const alarmEndpoint: Effect.Effect<void, unknown, EndpointServices> = Effect.gen(function* () {
613
- const maintenance = yield* ConversationMaintenance;
614
- // Typed pass failures propagate: the rejected promise makes workerd retry the alarm
615
- // (at-least-once delivery), and the pass's own pre-arm keeps the slot committed meanwhile.
616
- yield* maintenance.pass;
617
- });
618
-
619
- const gateEndpoint: Effect.Effect<void, unknown, EndpointServices> = Effect.gen(function* () {
620
- // Forcing ConversationMaintenance forces the whole Layer stack: migration + exact-version
621
- // check + configuration decode (DEPLOY-008 fails typed here, before any mutation), then
622
- // the defensive local ensure-alarm half of the invariant. LOCAL-ONLY by construction.
623
- const maintenance = yield* ConversationMaintenance;
624
- yield* maintenance.ensureAlarm;
625
- });
611
+ const alarmEndpoint: Effect.Effect<void, MaintenancePassFailure, EndpointServices> = Effect.gen(
612
+ function* () {
613
+ const maintenance = yield* ConversationMaintenance;
614
+ // Typed pass failures propagate: the rejected promise makes workerd retry the alarm
615
+ // (at-least-once delivery), and the pass's own pre-arm keeps the slot committed meanwhile.
616
+ yield* maintenance.pass;
617
+ },
618
+ );
626
619
 
627
- const NATIVE_ENTRYPOINTS = [
628
- "submit",
629
- "await_settlement",
630
- "observe",
631
- "abort",
632
- "resolve_approval",
633
- "resolve_unknown",
634
- "explain",
635
- "verify",
636
- "retry",
637
- "obligations",
638
- "port_call",
639
- "wake",
640
- "alarm",
641
- ] as const;
642
-
643
- type NativeEntrypoint = (typeof NATIVE_ENTRYPOINTS)[number];
620
+ const gateEndpoint: Effect.Effect<void, MaintenancePassFailure, EndpointServices> = Effect.gen(
621
+ function* () {
622
+ // Forcing ConversationMaintenance forces the whole Layer stack: migration + exact-version
623
+ // check + configuration decode (DEPLOY-008 fails typed here, before any mutation), then
624
+ // the defensive local ensure-alarm half of the invariant. LOCAL-ONLY by construction.
625
+ const maintenance = yield* ConversationMaintenance;
626
+ yield* maintenance.ensureAlarm;
627
+ },
628
+ );
644
629
 
645
630
  /**
646
- * Owner-side delivery measurement. The exact endpoint Cause is hidden behind a bounded typed
647
- * marker while the span closes, then restored together with any newly composed real reasons.
631
+ * Adapter from effect-cf's native Durable Object services to Effect Agent's existing platform
632
+ * ports. effect-cf owns the cached ManagedRuntime and supplies these values once per Object
633
+ * incarnation; the durable runtime continues to depend only on the narrow services below.
648
634
  */
649
- const observeNativeEntrypoint = <A, E>(
650
- entrypoint: NativeEntrypoint,
651
- effect: Effect.Effect<A, E, EndpointServices>,
652
- ): Effect.Effect<A, E, EndpointServices> =>
653
- Effect.gen(function* () {
654
- const identity = yield* ConversationObjectIdentity;
655
- const config = yield* CloudflareDurableRuntimeConfig;
656
- return yield* withCloudflareNativeSpanFailure(effect, (masked) =>
657
- masked.pipe(
658
- Effect.withSpan(`effect_agent.cloudflare.conversation_object.${entrypoint}`, {
659
- kind: "server",
660
- attributes: {
661
- "effect_agent.cloudflare.entrypoint": entrypoint,
662
- "effect_agent.deployment.id": config.deploymentId,
663
- conversationId: identity.conversationId,
664
- producerId: identity.producerId,
665
- },
666
- }),
667
- ),
668
- );
669
- });
670
-
671
- const flushNativeEntrypointTelemetry = Effect.flatMap(CloudflareDurableRuntimeConfig, (config) =>
672
- flushCloudflareRuntimeTelemetry(config.telemetryFlushTimeout),
673
- );
635
+ const effectCfPlatformLayer = (
636
+ namespaceBinding: string,
637
+ ): Layer.Layer<
638
+ DurableObjectContext | ConversationObjectNamespace,
639
+ CloudflareBindingError,
640
+ EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment
641
+ > => {
642
+ const context = Layer.effect(DurableObjectContext)(
643
+ Effect.gen(function* () {
644
+ const state = yield* EffectCfDurableObjectState.DurableObjectState;
645
+ const env = yield* WorkerEnvironment;
646
+ return DurableObjectContext.of({ ctx: state.raw, env });
647
+ }),
648
+ );
649
+ const namespace = Layer.effect(ConversationObjectNamespace)(
650
+ Effect.gen(function* () {
651
+ const env = yield* WorkerEnvironment;
652
+ const binding = yield* conversationNamespaceFromEnv(env, namespaceBinding);
653
+ return ConversationObjectNamespace.of({ namespace: binding });
654
+ }),
655
+ );
656
+ return Layer.merge(context, namespace);
657
+ };
674
658
 
675
659
  /** The public endpoint surface of one Conversation Object instance. */
676
- export interface ConversationObjectInstance extends DurableObject {
660
+ export interface ConversationObjectInstance extends CloudflareDurableObject {
677
661
  submitEncoded(encoded: unknown): Promise<unknown>;
678
662
  awaitSettlementEncoded(encoded: unknown): Promise<unknown>;
679
663
  observePage(encoded: unknown): Promise<unknown>;
@@ -686,7 +670,7 @@ export interface ConversationObjectInstance extends DurableObject {
686
670
  obligationsEncoded(encoded: unknown): Promise<unknown>;
687
671
  portCall(encoded: unknown): Promise<unknown>;
688
672
  wake(): Promise<void>;
689
- alarm(): Promise<void>;
673
+ alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> | void;
690
674
  }
691
675
 
692
676
  /** The constructor shape workerd instantiates for each Conversation Object. */
@@ -696,150 +680,87 @@ export interface ConversationObjectClass {
696
680
 
697
681
  /**
698
682
  * Build the application's Conversation Object class (export it from the Worker entry).
699
- * `telemetry` is the host composition boundary: it may require the Object context/namespace and
700
- * retain a typed acquisition error, while any additional output services remain available to the
701
- * cached runtime. `TelemetryError` stays the first explicit generic for source compatibility;
702
- * additional outputs infer from the Layer. Omitting it installs the content-free no-op service.
703
- * The explicit return type is what makes declaration emit possible: the class body carries a
704
- * private runtime field, and TS4094 rejects inferring an exported anonymous class type around it.
683
+ * effect-cf owns the cached ManagedRuntime, native RPC methods, event scopes, and post-handler
684
+ * OTLP flush scheduling for RPC and alarm events. The optional outer Layer is built per native
685
+ * event, so a host can install Tracer/Logger/Metric services and `OtlpExporter.Flusher` without
686
+ * Effect Agent owning exporter lifecycle machinery.
705
687
  */
706
- export const makeConversationObjectClass = <
707
- TelemetryError = never,
708
- AdditionalTelemetryOutputs = never,
709
- >(
688
+ export const makeConversationObjectClass = <EventLayerError = never, EventServices = never>(
710
689
  options: ConversationObjectOptions,
711
- telemetry?: Layer.Layer<
712
- CloudflareRuntimeTelemetry | AdditionalTelemetryOutputs,
713
- TelemetryError,
714
- DurableObjectContext | ConversationObjectNamespace
690
+ observability?: Layer.Layer<
691
+ EventServices,
692
+ EventLayerError,
693
+ | DurableObjectContext
694
+ | ConversationObjectNamespace
695
+ | EffectCfDurableObjectState.DurableObjectState
696
+ | WorkerEnvironment
715
697
  >,
716
698
  ): ConversationObjectClass => {
717
- class ConversationObject extends DurableObject {
718
- readonly #runtime: ManagedRuntime.ManagedRuntime<
719
- EndpointServices,
720
- ConversationObjectError<TelemetryError>
721
- >;
722
- readonly #ctx: DurableObjectState;
723
- readonly #telemetryFlush: CloudflareTelemetryFlushCoordinator;
724
-
725
- constructor(ctx: DurableObjectState, env: Cloudflare.Env) {
726
- super(ctx, env);
727
- this.#ctx = ctx;
728
- const platform = Layer.mergeAll(
729
- DurableObjectContext.layer(ctx, env),
730
- conversationNamespaceLayer(env, options.namespaceBinding),
731
- );
732
- // Host observability is composed at the Worker edge, not hidden in runtime options. Its
733
- // typed acquisition failure remains in the ManagedRuntime error, while the Object context
734
- // and namespace are available to Layers that derive exporter configuration from `env`.
735
- const hostTelemetry = (telemetry ?? CloudflareRuntimeTelemetry.layerNoop).pipe(
736
- Layer.provideMerge(platform),
737
- );
738
- this.#runtime = ManagedRuntime.make(
739
- CloudflareDurableRuntime.layer(options).pipe(
740
- // Supplying the host Layer outermost makes its Logger/Tracer/Metric runtime
741
- // configuration observe application Layer acquisition and every native endpoint.
742
- Layer.provideMerge(hostTelemetry),
743
- ),
744
- );
745
- this.#telemetryFlush = makeCloudflareTelemetryFlushCoordinator(
746
- () => this.#runtime.runPromise(flushNativeEntrypointTelemetry),
747
- {
748
- onReservationDropped: () => {
749
- void this.#runtime.runSyncExit(
750
- Effect.logWarning(
751
- "Cloudflare telemetry delivery coalesced at the batch reservation limit",
752
- ).pipe(
753
- Effect.annotateLogs({
754
- "effect_agent.cloudflare.telemetry.failure_kind": "reservation_limit",
755
- }),
756
- ),
757
- );
758
- },
759
- },
760
- );
761
- // The constructor gate: local-only checks; never the recovery pass (deadlock argument,
762
- // plan §1.4). A failure here fails every delivery with the typed construction error.
763
- ctx.blockConcurrencyWhile(() => this.#runtime.runPromise(gateEndpoint));
764
- }
699
+ const application: Layer.Layer<
700
+ RuntimeServices,
701
+ CloudflareDurableRuntimeInitializationError | CloudflareBindingError,
702
+ EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment
703
+ > = CloudflareDurableRuntime.layer(options).pipe(
704
+ Layer.provideMerge(effectCfPlatformLayer(options.namespaceBinding)),
705
+ );
765
706
 
766
- #runNative<A, E>(
767
- entrypoint: NativeEntrypoint,
768
- effect: Effect.Effect<A, E, EndpointServices>,
769
- ): Promise<A> {
770
- const delivery = this.#runtime.runPromise(observeNativeEntrypoint(entrypoint, effect));
771
- // Reserve synchronously with the current native delivery, but do not await export from the
772
- // RPC/alarm Promise. Each pending/trailing/queued batch waits for its assigned deliveries to
773
- // settle and only its first owner registers the shared background Promise. Even an
774
- // uninterruptible exporter therefore cannot delay a caller, suppress alarm retry, accumulate
775
- // concurrent exporter work, an unbounded waitUntil registration queue, or unbounded retained
776
- // delivery Promises. Excess arrivals are diagnosed and lossy-coalesced at the batch cap.
777
- // Registration failure and every asynchronous flush failure remain isolated from delivery.
778
- return registerCloudflareTelemetryAfterNativeSettlement(
779
- (background) => this.#ctx.waitUntil(background),
780
- delivery,
781
- this.#telemetryFlush.reserve,
782
- (cause) => {
783
- // Native entrypoints run only after blockConcurrencyWhile has built the cached runtime.
784
- // Effect Logger invocation is synchronous; runSyncExit captures a broken logger without
785
- // starting an unscoped fiber or changing the delivery Promise. The exact platform value
786
- // stays confined to this registration callback; the automatic Logger diagnostic is
787
- // deliberately content-free.
788
- void this.#runtime.runSyncExit(logCloudflareWaitUntilRegistrationFailure(cause));
789
- },
707
+ // The storage/config Layer must acquire inside Cloudflare's constructor gate. effect-cf owns
708
+ // the ManagedRuntime, while this effectContext ensures its first Layer build enters the gate
709
+ // before migration, compatibility checks, or alarm inspection touch Object storage.
710
+ const runtime: Layer.Layer<
711
+ RuntimeServices,
712
+ ConversationObjectInitializationError,
713
+ EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment
714
+ > = Layer.effectContext(
715
+ Effect.gen(function* () {
716
+ const state = yield* EffectCfDurableObjectState.DurableObjectState;
717
+ const scope = yield* Effect.scope;
718
+ return yield* state.blockConcurrencyWhile(
719
+ Effect.gen(function* () {
720
+ const services = yield* Layer.buildWithScope(application, scope);
721
+ yield* gateEndpoint.pipe(Effect.provide(services));
722
+ return services;
723
+ }),
790
724
  );
791
- }
792
-
793
- async submitEncoded(encoded: unknown): Promise<unknown> {
794
- return this.#runNative("submit", submitEndpoint(encoded));
795
- }
796
-
797
- async awaitSettlementEncoded(encoded: unknown): Promise<unknown> {
798
- return this.#runNative("await_settlement", awaitSettlementEndpoint(encoded));
799
- }
800
-
801
- async observePage(encoded: unknown): Promise<unknown> {
802
- return this.#runNative("observe", observePageEndpoint(encoded));
803
- }
804
-
805
- async abortEncoded(encoded: unknown): Promise<unknown> {
806
- return this.#runNative("abort", abortEndpoint(encoded));
807
- }
808
-
809
- async resolveApprovalEncoded(encoded: unknown): Promise<unknown> {
810
- return this.#runNative("resolve_approval", resolveApprovalEndpoint(encoded));
811
- }
812
-
813
- async resolveUnknownEncoded(encoded: unknown): Promise<unknown> {
814
- return this.#runNative("resolve_unknown", resolveUnknownEndpoint(encoded));
815
- }
816
-
817
- async explainEncoded(encoded: unknown): Promise<unknown> {
818
- return this.#runNative("explain", explainEndpoint(encoded));
819
- }
820
-
821
- async verifyEncoded(encoded: unknown): Promise<unknown> {
822
- return this.#runNative("verify", verifyEndpoint(encoded));
823
- }
824
-
825
- async retryEncoded(encoded: unknown): Promise<unknown> {
826
- return this.#runNative("retry", retryEndpoint(encoded));
827
- }
828
-
829
- async obligationsEncoded(encoded: unknown): Promise<unknown> {
830
- return this.#runNative("obligations", obligationsEndpoint(encoded));
831
- }
832
-
833
- async portCall(encoded: unknown): Promise<unknown> {
834
- return this.#runNative("port_call", portCallEndpoint(encoded));
835
- }
725
+ }),
726
+ );
836
727
 
837
- async wake(): Promise<void> {
838
- await this.#runNative("wake", wakeEndpoint);
839
- }
728
+ const rpc = {
729
+ submitEncoded: (encoded: unknown) => submitEndpoint(encoded),
730
+ awaitSettlementEncoded: (encoded: unknown) => awaitSettlementEndpoint(encoded),
731
+ observePage: (encoded: unknown) => observePageEndpoint(encoded),
732
+ abortEncoded: (encoded: unknown) => abortEndpoint(encoded),
733
+ resolveApprovalEncoded: (encoded: unknown) => resolveApprovalEndpoint(encoded),
734
+ resolveUnknownEncoded: (encoded: unknown) => resolveUnknownEndpoint(encoded),
735
+ explainEncoded: (encoded: unknown) => explainEndpoint(encoded),
736
+ verifyEncoded: (encoded: unknown) => verifyEndpoint(encoded),
737
+ retryEncoded: (encoded: unknown) => retryEndpoint(encoded),
738
+ obligationsEncoded: (encoded: unknown) => obligationsEndpoint(encoded),
739
+ portCall: (encoded: unknown) => portCallEndpoint(encoded),
740
+ wake: () => wakeEndpoint,
741
+ } satisfies EffectCfDurableObject.DurableObjectRpc<RuntimeServices | EventServices>;
742
+
743
+ const EffectCfConversationObject = EffectCfDurableObject.make<
744
+ RuntimeServices,
745
+ ConversationObjectInitializationError,
746
+ EventServices,
747
+ EventLayerError,
748
+ typeof rpc
749
+ >(runtime, {
750
+ ...(observability === undefined ? {} : { eventLayer: observability }),
751
+ // Force the gated runtime Layer when Cloudflare loads this Object incarnation. Recovery stays
752
+ // in each bounded pass so cross-Object initialization cannot deadlock.
753
+ initialize: Effect.void,
754
+ rpc,
755
+ alarm: () => alarmEndpoint,
756
+ });
840
757
 
841
- override async alarm(): Promise<void> {
842
- await this.#runNative("alarm", alarmEndpoint);
758
+ // effect-cf's class type keeps `alarm` optional even when the handler option is present. This
759
+ // concrete override reflects this factory's stronger contract while delegating execution to
760
+ // the effect-cf runtime unchanged.
761
+ class ConversationObject extends EffectCfConversationObject {
762
+ override alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> | void {
763
+ return super.alarm?.(alarmInfo);
843
764
  }
844
765
  }
845
766
 
package/src/index.ts CHANGED
@@ -17,7 +17,6 @@ export * from "./config.ts";
17
17
  export * from "./alarm.ts";
18
18
  export * from "./wake-scheduler.ts";
19
19
  export * from "./transport.ts";
20
- export * from "./telemetry.ts";
21
20
  export * from "./layers.ts";
22
21
  export * from "./conversation-object.ts";
23
22
  export * from "./client.ts";
package/src/layers.ts CHANGED
@@ -31,8 +31,8 @@ import { Context, Duration, Effect, Layer, Schema } from "effect";
31
31
  import { ConversationMaintenance, DurableAlarmService } from "./alarm.ts";
32
32
  import {
33
33
  ConversationObjectIdentity,
34
+ ConversationObjectNamespace,
34
35
  DurableObjectContext,
35
- type ConversationObjectNamespace,
36
36
  } from "./bindings.ts";
37
37
  import {
38
38
  CLOUDFLARE_RUNTIME_DEFAULTS,
@@ -69,11 +69,6 @@ export interface CloudflareDurableRuntimeOptions {
69
69
  readonly abortPollInterval?: number | undefined;
70
70
  /** Milliseconds; default 25. */
71
71
  readonly observationPollInterval?: number | undefined;
72
- /**
73
- * Milliseconds; default 2000. Cooperative budget for the background exporter flush registered
74
- * after each native RPC, wake, or alarm span. Delivery never awaits the flush.
75
- */
76
- readonly telemetryFlushTimeout?: number | undefined;
77
72
  /** Bytes; default just under the 2 MB platform value limit. */
78
73
  readonly maxStoredValueBytes?: number | undefined;
79
74
  /** Default false. */
@@ -185,8 +180,6 @@ const configFromOptions = (
185
180
  abortPollInterval: options.abortPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.abortPollInterval,
186
181
  observationPollInterval:
187
182
  options.observationPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.observationPollInterval,
188
- telemetryFlushTimeout:
189
- options.telemetryFlushTimeout ?? CLOUDFLARE_RUNTIME_DEFAULTS.telemetryFlushTimeout,
190
183
  maxStoredValueBytes:
191
184
  options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes,
192
185
  verifyOnOpen: options.verifyOnOpen ?? CLOUDFLARE_RUNTIME_DEFAULTS.verifyOnOpen,
@@ -261,9 +254,9 @@ const resolveBindings = (
261
254
  * Storage compatibility is verified during construction: an incompatible database fails the
262
255
  * Layer typed (`DoStorageCompatibilityError`) before anything is mutated (DEPLOY-008).
263
256
  *
264
- * Requires the two binding services (`DurableObjectContext`, `ConversationObjectNamespace`).
265
- * Host observability belongs to the Worker composition edge because native entrypoint lifecycle
266
- * instrumentation, rather than this durable runtime assembly, consumes it (DEPLOY-010).
257
+ * Requires only the two binding services (`DurableObjectContext`,
258
+ * `ConversationObjectNamespace`) platform values enter exclusively through Layers
259
+ * (DEPLOY-010).
267
260
  */
268
261
  export class CloudflareDurableRuntime {
269
262
  static layer(
@@ -372,13 +365,11 @@ export class CloudflareDurableRuntime {
372
365
  Layer.provideMerge(base),
373
366
  );
374
367
 
375
- const application = Layer.mergeAll(
368
+ return Layer.mergeAll(
376
369
  runtimeStack,
377
370
  ConversationMaintenance.layer.pipe(Layer.provide(runtimeStack)),
378
371
  portsEndpointLayer,
379
372
  );
380
-
381
- return application;
382
373
  }),
383
374
  );
384
375
  }