@effect-agent/platform-cloudflare 0.1.0-beta.114 → 0.1.0-beta.116

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/Alarm.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DurableObjectContext } from "./CloudflareBindings.mjs";
2
2
  import { s as CloudflareDurableRuntimeConfig } from "./CloudflareConfig-DKXGo8L3.mjs";
3
- import { Cause, Context, DateTime, Effect, Layer, Option, Schema, Scope } from "effect";
3
+ import { Cause, Context, Effect, Layer, Option, Schema, Scope } from "effect";
4
4
  import { DurableBindingFailure } from "effect-agent/agent-registration";
5
5
  import { DurableAgentRuntime, DurableRuntimeConfig, DurableWorkerFailure, RecoveryFailure } from "effect-agent/durable-agent-runtime";
6
6
  import { ThreadId } from "effect-agent/identifiers";
@@ -10,7 +10,7 @@ import { ThreadProjectionError, ThreadProjectionMaintenance } from "effect-agent
10
10
  import { WakeScheduler } from "effect-agent/wake-scheduler";
11
11
  import { SqlClient } from "effect/unstable/sql/SqlClient";
12
12
  declare namespace Alarm_d_exports {
13
- export { DurableAlarmError, DurableAlarmService, MaintenancePassFailure, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance, ThreadMaintenanceActivity, ThreadMaintenanceFailpoint, ThreadMaintenanceFailpointHandler, ThreadMaintenanceFailpointLocation, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadPublicationService, ThreadRecoveryFault, publishCommitted };
13
+ export { DurableAlarmError, DurableAlarmService, MaintenancePassFailure, MaintenancePassReport, ThreadHostMaintenance, ThreadHostMaintenanceLane, ThreadMaintenance, ThreadMaintenanceFailpoint, ThreadMaintenanceFailpointHandler, ThreadMaintenanceFailpointLocation, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadPublicationService, ThreadRecoveryFault, publishCommitted };
14
14
  }
15
15
  declare const DurableAlarmError_base: Schema.Class<DurableAlarmError, Schema.TaggedStruct<"DurableAlarmError", {
16
16
  readonly operation: Schema.String;
@@ -114,61 +114,44 @@ declare const ThreadPublication_base: Context.ServiceClass<ThreadPublication, "@
114
114
  export declare class ThreadPublication extends ThreadPublication_base {
115
115
  static readonly layer: Layer.Layer<ThreadPublication, never, never>;
116
116
  }
117
- declare const ThreadMaintenanceActivity_base: Context.ServiceClass<ThreadMaintenanceActivity, "@effect-agent/platform-cloudflare/ThreadMaintenanceActivity", {
118
- /**
119
- * Account for one finite selection/dispatch, through joined completion or interruption.
120
- * Register before reading/claiming work. After dispatch closes the body is not started.
121
- * Host waves retain their declared allowance from this call; message Claims keep the driver's
122
- * own deadline. Waiting for notifications belongs outside this bracket.
123
- */
124
- readonly run: <R>(wave: Effect.Effect<void, DurableAlarmError, R>) => Effect.Effect<void, DurableAlarmError, R>;
125
- /** Acknowledge all initial subscriptions/scans. Composite hosts use `all` below. */
126
- readonly ready: Effect.Effect<void>;
127
- /**
128
- * Acquire an independent scoped subscription before the initial scan and `ready`.
129
- * The returned wait observes coalesced native scan hints, including those published before
130
- * its first execution. Each hint requests a current local due-work check.
131
- */
132
- readonly subscribeChanges: Effect.Effect<Effect.Effect<void>, never, Scope.Scope>;
133
- }>;
134
- /** Event-local accounting shared by the existing maintenance pumps and native scheduler. */
135
- export declare class ThreadMaintenanceActivity extends ThreadMaintenanceActivity_base {
136
- /** Join child pumps; acknowledge the parent only after every child is ready or has exited. */
137
- static readonly all: <A, E, R>(lanes: readonly Effect.Effect<A, E, R>[]) => Effect.Effect<readonly A[], E, R | ThreadMaintenanceActivity>;
138
- }
139
117
  /**
140
- * Host-assembled native message recovery. The driver bounds each actual Claim and persists its
141
- * timeout/retry before this pump returns. Do not add a second timer starting at batch selection:
142
- * local Claim setup may take time, and expiration still owes the driver's local retry commit.
118
+ * Host-assembled native message recovery. Preparation is a bounded local selection; the
119
+ * driver bounds each actual Claim and persists its timeout/retry before `run` returns.
120
+ * The alarm must not add a timer starting at selection: Claim setup and retry commits belong
121
+ * to the driver. The prepared allowance decides only whether a wave fits this event.
143
122
  */
144
123
  export declare const ThreadMessageDelivery: Context.Reference<{
145
- readonly drainUntil: (dispatchClosed: Effect.Effect<void>, dispatchUntil: DateTime.Utc) => Effect.Effect<void, DurableAlarmError, Scope.Scope | ThreadMaintenanceActivity>;
124
+ readonly prepare: Effect.Effect<{
125
+ readonly timeoutMillis: number;
126
+ readonly run: Effect.Effect<void, DurableAlarmError>;
127
+ }, DurableAlarmError>;
146
128
  readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;
147
129
  }>;
148
130
  /**
149
- * Application obligations sharing this Object's alarm. Admit one initial external wave even on
150
- * a caught-up pass, then respond to wakes and ThreadMaintenanceActivity.subscribeChanges until
151
- * dispatchClosed. Yield the event's ThreadMaintenanceActivity to bracket each finite lane with
152
- * run and acknowledge initial setup with ready.
153
- * Passive subscriptions do not count as active work. Closure stops both new waves and native
154
- * Attempts; local admission/control subscriptions belong to the event Scope until teardown.
155
- * No deadline sleeps or automatic retry loops. Return after already-admitted waves finish.
131
+ * One independently schedulable application obligation. `run` selects and joins one finite
132
+ * wave, including its retry/receipt commits. The alarm gives every lane an initial opportunity,
133
+ * then checks its local pending deadline on completion, native progress, wakes and bounded scans.
134
+ * No subscriptions, readiness acknowledgements, deadline sleeps or dispatch loops are needed.
156
135
  *
157
- * Declare a finite whole-wave allowance (1..300000ms): maximum for parallel lanes, sum for
158
- * sequential operations. Admit a wave only if its allowance fits before dispatchUntil. Later
159
- * arrivals cannot renew an admitted wave's allowance. Maintenance bounds each registered wave,
160
- * interrupts and joins event Scope, then reads local deadlines under the mutation gate.
136
+ * Declare a whole-wave allowance (1..300000ms), including selection, local commits and cleanup. A wave
137
+ * starts only if that allowance fits the event and later arrivals cannot renew it. A failed
138
+ * lane is not retried within the event. The alarm joins admitted work and closes its Scope
139
+ * before reading durable deadlines under the shared ThreadMutationGate.
161
140
  *
162
- * Setup and pendingDeadline are bounded local operations. Persist claims/envelopes before
163
- * dispatch; interrupted waits leave exact retries/receipts recoverable. Cancellation is local,
164
- * not remote rollback. Retry accounting is unchanged. Network waits/finalizers must be
165
- * interruptible; short local atomic commits may be uninterruptible. Hooks never write the raw
166
- * alarm slot. Required native publication gates belong to ThreadPublication.
141
+ * `pendingDeadline` is bounded local control state, never a retained-history scan. Persist
142
+ * envelopes/claims before dispatch; cancellation is local, not remote rollback. Network waits
143
+ * and finalizers must be interruptible; short atomic commits may be uninterruptible. Mutations
144
+ * use the shared gate and producers notify the existing WakeScheduler after commit. Hooks never
145
+ * write the raw alarm slot. Required native publication gates belong to ThreadPublication.
167
146
  */
168
- export declare const ThreadHostMaintenance: Context.Reference<{
147
+ export interface ThreadHostMaintenanceLane {
169
148
  readonly dispatchTimeoutMillis: number;
170
- readonly drainUntil: (dispatchClosed: Effect.Effect<void>, dispatchUntil: DateTime.Utc) => Effect.Effect<void, DurableAlarmError, Scope.Scope | ThreadMaintenanceActivity>;
149
+ readonly run: Effect.Effect<void, DurableAlarmError, Scope.Scope>;
171
150
  readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;
151
+ }
152
+ /** Independent lanes share the existing alarm; compose hosts by concatenating their lanes. */
153
+ export declare const ThreadHostMaintenance: Context.Reference<{
154
+ readonly lanes: ReadonlyArray<ThreadHostMaintenanceLane>;
172
155
  }>;
173
156
  /** @internal A committed source operation must not become a failed operation because delivery failed. */
174
157
  export declare const publishCommitted: Effect.Effect<void, never, ThreadPublication>;
@@ -244,7 +227,7 @@ declare const ThreadMaintenance_base: Context.ServiceClass<ThreadMaintenance, "@
244
227
  * retain their durable backoff.
245
228
  * 3. Keep native and delivery admission open together while finite waves remain active, so
246
229
  * fresh replies and abort controls can progress during unrelated cleanup. Close atomically
247
- * at quiescence, or at the original ten-minute yield deadline, before retiring listeners.
230
+ * at quiescence, or at the original ten-minute yield deadline, then join admitted waves.
248
231
  * 4. Native message delivery retains its driver-owned Claim deadline. Host/backfill waves are
249
232
  * bounded independently; incoming native work never restarts or cancels their attempts.
250
233
  * Auxiliary failures are reported after the current native opportunity.
package/dist/Alarm.mjs CHANGED
@@ -2,7 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { DurableObjectContext } from "./CloudflareBindings.mjs";
3
3
  import { AuxiliaryDispatchMillis, CloudflareDurableRuntimeConfig } from "./CloudflareConfig.mjs";
4
4
  import { r as safeCauseMessage } from "./boundary-BguazVkh.mjs";
5
- import { Cause, Clock, Context, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, PubSub, Random, Ref, Result, Schema, Scope, Semaphore, Stream, Struct } from "effect";
5
+ import { Cause, Clock, Context, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, Random, Ref, Result, Schema, Scope, Semaphore, Stream, Struct } from "effect";
6
6
  import "effect-agent/agent-registration";
7
7
  import { DurableAgentRuntime, DurableRuntimeConfig, RecoveryBlocked, RecoveryFailure, RecoverySweepResult, isolateRecovery } from "effect-agent/durable-agent-runtime";
8
8
  import { SubmissionId, ThreadId } from "effect-agent/identifiers";
@@ -18,7 +18,6 @@ var Alarm_exports = /* @__PURE__ */ __exportAll({
18
18
  MaintenancePassReport: () => MaintenancePassReport,
19
19
  ThreadHostMaintenance: () => ThreadHostMaintenance,
20
20
  ThreadMaintenance: () => ThreadMaintenance,
21
- ThreadMaintenanceActivity: () => ThreadMaintenanceActivity,
22
21
  ThreadMaintenanceFailpoint: () => ThreadMaintenanceFailpoint,
23
22
  ThreadMessageDelivery: () => ThreadMessageDelivery,
24
23
  ThreadMutationGate: () => ThreadMutationGate,
@@ -112,61 +111,21 @@ var ThreadPublication = class extends Context.Service()("@effect-agent/platform-
112
111
  pendingDeadline: Effect.succeed(Option.none())
113
112
  });
114
113
  };
115
- /** Event-local accounting shared by the existing maintenance pumps and native scheduler. */
116
- var ThreadMaintenanceActivity = class ThreadMaintenanceActivity extends Context.Service()("@effect-agent/platform-cloudflare/ThreadMaintenanceActivity") {
117
- /** Join child pumps; acknowledge the parent only after every child is ready or has exited. */
118
- static all = Effect.fnUntraced(function* (lanes) {
119
- const activity = yield* ThreadMaintenanceActivity;
120
- let remaining = lanes.length;
121
- if (remaining === 0) return yield* activity.ready.pipe(Effect.as([]));
122
- return yield* Effect.forEach(lanes, (lane) => {
123
- let ready = false;
124
- const child = {
125
- ...activity,
126
- ready: Effect.suspend(() => {
127
- if (ready) return Effect.void;
128
- ready = true;
129
- return --remaining === 0 ? activity.ready : Effect.void;
130
- }).pipe(Effect.uninterruptible)
131
- };
132
- return lane.pipe(Effect.provideService(ThreadMaintenanceActivity, child), Effect.onExit(() => child.ready));
133
- }, { concurrency: "unbounded" });
134
- });
135
- };
136
114
  /**
137
- * Host-assembled native message recovery. The driver bounds each actual Claim and persists its
138
- * timeout/retry before this pump returns. Do not add a second timer starting at batch selection:
139
- * local Claim setup may take time, and expiration still owes the driver's local retry commit.
115
+ * Host-assembled native message recovery. Preparation is a bounded local selection; the
116
+ * driver bounds each actual Claim and persists its timeout/retry before `run` returns.
117
+ * The alarm must not add a timer starting at selection: Claim setup and retry commits belong
118
+ * to the driver. The prepared allowance decides only whether a wave fits this event.
140
119
  */
141
120
  const ThreadMessageDelivery = Context.Reference("@effect-agent/platform-cloudflare/ThreadMessageDelivery", { defaultValue: () => ({
142
- drainUntil: () => Effect.void,
143
- pendingDeadline: Effect.succeed(Option.none())
144
- }) });
145
- /**
146
- * Application obligations sharing this Object's alarm. Admit one initial external wave even on
147
- * a caught-up pass, then respond to wakes and ThreadMaintenanceActivity.subscribeChanges until
148
- * dispatchClosed. Yield the event's ThreadMaintenanceActivity to bracket each finite lane with
149
- * run and acknowledge initial setup with ready.
150
- * Passive subscriptions do not count as active work. Closure stops both new waves and native
151
- * Attempts; local admission/control subscriptions belong to the event Scope until teardown.
152
- * No deadline sleeps or automatic retry loops. Return after already-admitted waves finish.
153
- *
154
- * Declare a finite whole-wave allowance (1..300000ms): maximum for parallel lanes, sum for
155
- * sequential operations. Admit a wave only if its allowance fits before dispatchUntil. Later
156
- * arrivals cannot renew an admitted wave's allowance. Maintenance bounds each registered wave,
157
- * interrupts and joins event Scope, then reads local deadlines under the mutation gate.
158
- *
159
- * Setup and pendingDeadline are bounded local operations. Persist claims/envelopes before
160
- * dispatch; interrupted waits leave exact retries/receipts recoverable. Cancellation is local,
161
- * not remote rollback. Retry accounting is unchanged. Network waits/finalizers must be
162
- * interruptible; short local atomic commits may be uninterruptible. Hooks never write the raw
163
- * alarm slot. Required native publication gates belong to ThreadPublication.
164
- */
165
- const ThreadHostMaintenance = Context.Reference("@effect-agent/platform-cloudflare/ThreadHostMaintenance", { defaultValue: () => ({
166
- dispatchTimeoutMillis: 1,
167
- drainUntil: () => Effect.void,
121
+ prepare: Effect.succeed({
122
+ timeoutMillis: 1,
123
+ run: Effect.void
124
+ }),
168
125
  pendingDeadline: Effect.succeed(Option.none())
169
126
  }) });
127
+ /** Independent lanes share the existing alarm; compose hosts by concatenating their lanes. */
128
+ const ThreadHostMaintenance = Context.Reference("@effect-agent/platform-cloudflare/ThreadHostMaintenance", { defaultValue: () => ({ lanes: [] }) });
170
129
  const earliestDeadline = (left, right) => Option.isSome(left) ? Option.isSome(right) ? Option.some(Math.min(left.value, right.value)) : left : right;
171
130
  /** @internal A committed source operation must not become a failed operation because delivery failed. */
172
131
  const publishCommitted = Effect.gen(function* () {
@@ -313,7 +272,7 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
313
272
  * retain their durable backoff.
314
273
  * 3. Keep native and delivery admission open together while finite waves remain active, so
315
274
  * fresh replies and abort controls can progress during unrelated cleanup. Close atomically
316
- * at quiescence, or at the original ten-minute yield deadline, before retiring listeners.
275
+ * at quiescence, or at the original ten-minute yield deadline, then join admitted waves.
317
276
  * 4. Native message delivery retains its driver-owned Claim deadline. Host/backfill waves are
318
277
  * bounded independently; incoming native work never restarts or cancels their attempts.
319
278
  * Auxiliary failures are reported after the current native opportunity.
@@ -338,7 +297,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
338
297
  const authorizer = yield* OperationAuthorizer;
339
298
  const projectionDeadline = projection.pendingDeadline.pipe(Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError("Thread projection deadline unavailable", cause).pipe(Effect.as(Option.some(0)))));
340
299
  const pendingDeadline = Effect.gen(function* () {
341
- return earliestDeadline(earliestDeadline(yield* publication.pendingDeadline, yield* messages.pendingDeadline), earliestDeadline(yield* projectionDeadline, yield* host.pendingDeadline));
300
+ return earliestDeadline(earliestDeadline(yield* publication.pendingDeadline, yield* messages.pendingDeadline), earliestDeadline(yield* projectionDeadline, (yield* Effect.forEach(host.lanes, (lane) => lane.pendingDeadline)).reduce(earliestDeadline, Option.none())));
342
301
  });
343
302
  const maintenancePassGate = yield* Semaphore.make(1);
344
303
  const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));
@@ -664,47 +623,17 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
664
623
  const notified = (yield* Stream.toPull(wakes.wakes)).pipe(Effect.asVoid, Effect.catch(() => Effect.never));
665
624
  const started = yield* beginNative(observed);
666
625
  const auxiliaryScope = yield* Effect.acquireRelease(Scope.make("parallel"), (scope, exit) => Scope.close(scope, exit));
667
- const dispatchClosed = yield* Deferred.make();
668
- const stopDispatch = Deferred.await(dispatchClosed);
669
- const checks = yield* PubSub.sliding(1);
670
- yield* Effect.addFinalizer(() => PubSub.shutdown(checks));
671
- let dispatchOpen = true;
672
- let activityChanged = yield* Deferred.make();
673
- const signalActivity = Effect.suspend(() => Deferred.succeed(activityChanged, void 0));
674
- const closeDispatch = Effect.sync(() => {
675
- dispatchOpen = false;
676
- }).pipe(Effect.andThen(Deferred.succeed(dispatchClosed, void 0)));
677
- const makeActivity = Effect.fnUntraced(function* (allowance) {
678
- const initialized = yield* Deferred.make();
679
- let active = 0;
680
- let failed = false;
681
- return {
682
- activity: {
683
- ready: Deferred.succeed(initialized, void 0).pipe(Effect.andThen(signalActivity), Effect.asVoid, Effect.uninterruptible),
684
- subscribeChanges: PubSub.subscribe(checks).pipe(Effect.map(PubSub.take)),
685
- run: (wave) => Effect.acquireUseRelease(Effect.sync(() => {
686
- if (!dispatchOpen) return false;
687
- active++;
688
- return true;
689
- }), (admitted) => !admitted ? Effect.void : (allowance === void 0 ? wave : wave.pipe(Effect.timeoutOrElse({
690
- duration: allowance,
691
- orElse: () => DurableAlarmError.make({
692
- operation: "host dispatch allowance",
693
- message: "The admitted host wave exceeded its allowance; durable work remains pending"
694
- })
695
- }))).pipe(Effect.onExit((exit) => Effect.sync(() => {
696
- if (Exit.isFailure(exit)) failed = true;
697
- }))), (admitted) => Effect.sync(() => {
698
- if (admitted) active--;
699
- }).pipe(Effect.andThen(signalActivity)))
700
- },
701
- initialized: Deferred.await(initialized),
702
- quiet: () => active === 0 && Deferred.isDoneUnsafe(initialized),
703
- failed: () => failed
704
- };
705
- });
706
- const messageActivity = yield* makeActivity();
707
- const hostActivity = yield* makeActivity(host.dispatchTimeoutMillis);
626
+ const fork = (work) => Effect.forkIn(work, auxiliaryScope);
627
+ const lanes = host.lanes.map((lane) => ({
628
+ ...lane,
629
+ initial: true,
630
+ check: true,
631
+ exhausted: false,
632
+ fiber: void 0
633
+ }));
634
+ let messageExhausted = false;
635
+ let delivery;
636
+ let failure;
708
637
  const recovery = {
709
638
  queue: yield* Deferred.make(),
710
639
  pending: /* @__PURE__ */ new Set(),
@@ -716,7 +645,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
716
645
  recovered: 0,
717
646
  repaired: false
718
647
  };
719
- const recoveryFiber = yield* Effect.forkIn(Effect.gen(function* () {
648
+ const recoveryFiber = yield* fork(Effect.gen(function* () {
720
649
  for (const threadId of yield* Deferred.await(recovery.queue)) {
721
650
  yield* failpoint.hit("maintenance:select:before");
722
651
  yield* runTransaction("select old recovery lane", () => ctx.storage.transaction(async (transaction) => {
@@ -731,90 +660,156 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
731
660
  recovery.pending.delete(threadId);
732
661
  yield* wakes.notify(threadId);
733
662
  }
734
- }), auxiliaryScope);
735
- const deliveryFiber = yield* Effect.forkIn(Scope.provide(auxiliaryScope)(messages.drainUntil(stopDispatch, dispatchUntil).pipe(Effect.provideService(ThreadMaintenanceActivity, messageActivity.activity), Effect.onExit(() => messageActivity.activity.ready))), auxiliaryScope);
736
- const hostFiber = yield* Effect.forkIn(Scope.provide(auxiliaryScope)(Effect.gen(function* () {
737
- yield* Schema.decodeEffect(AuxiliaryDispatchMillis)(host.dispatchTimeoutMillis).pipe(Effect.mapError((cause) => DurableAlarmError.make({
738
- operation: "host dispatch allowance",
739
- message: "Declare an integer whole-wave allowance between 1 and 300000 milliseconds",
740
- cause
741
- })));
742
- const initialization = hostActivity.initialized.pipe(Effect.timeoutOrElse({
743
- duration: host.dispatchTimeoutMillis,
744
- orElse: () => DurableAlarmError.make({
745
- operation: "host initialization",
746
- message: "The host did not account for initial maintenance before its allowance; durable work remains pending"
747
- })
748
- }), Effect.andThen(Effect.never));
749
- yield* Effect.raceFirst(host.drainUntil(stopDispatch, dispatchUntil).pipe(Effect.provideService(ThreadMaintenanceActivity, hostActivity.activity)), initialization);
750
- }).pipe(Effect.onExit(() => hostActivity.activity.ready))), auxiliaryScope);
751
- const backfill = yield* Effect.forkIn(drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection), Effect.timeoutOption(config.projectionDispatchTimeoutMillis), Effect.onExit(() => signalActivity)), auxiliaryScope);
752
- let result = yield* advance(started, yieldAfter, observed, recovery);
753
- recovery.started = true;
754
- yield* Deferred.succeed(recovery.queue, []);
663
+ }));
664
+ const backfill = yield* fork(drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection), Effect.timeoutOption(config.projectionDispatchTimeoutMillis)));
665
+ let backfillObserved = false;
666
+ let recoveryObserved = false;
667
+ let native = yield* fork(advance(started, yieldAfter, observed, recovery));
668
+ let result = {
669
+ phase: "caught-up",
670
+ settled: 0,
671
+ nonterminal: started.nonterminal,
672
+ nextAttemptAt: void 0
673
+ };
755
674
  let phase = result.phase;
756
- let settled = result.settled;
757
- observed.nativeOnly = false;
758
- const hostJoin = yield* Effect.forkIn(Effect.gen(function* () {
759
- yield* stopDispatch;
760
- const remaining = Math.max(1, DateTime.toEpochMillis(dispatchUntil) - (yield* Clock.currentTimeMillis));
761
- const outcome = yield* Fiber.join(hostFiber).pipe(Effect.timeoutOption(Math.min(host.dispatchTimeoutMillis, remaining)));
762
- yield* Effect.annotateCurrentSpan({ "host.timedOut": Option.isNone(outcome) });
763
- }), auxiliaryScope);
764
- const retired = yield* Effect.forkChild(Fiber.joinAll([
765
- deliveryFiber,
766
- hostJoin,
767
- backfill,
768
- recoveryFiber
769
- ]));
675
+ let settled = 0;
676
+ let nativeCheck = false;
677
+ const until = DateTime.toEpochMillis(yieldAfter);
678
+ const dispatchEnd = DateTime.toEpochMillis(dispatchUntil);
679
+ const checkLanes = () => {
680
+ for (const lane of lanes) lane.check = true;
681
+ };
770
682
  while (true) {
771
- const recoveryFinished = recoveryFiber.pollUnsafe() !== void 0;
772
- const retiredExit = retired.pollUnsafe();
773
- if (recoveryFinished) {
774
- if (recovery.needsCheckpoint) {
775
- yield* Fiber.join(recoveryFiber);
776
- result = yield* advance(started, yieldAfter, observed, recovery, settled === 0);
777
- if (result.phase === "actionable") phase = "actionable";
778
- settled += result.settled;
779
- recovery.needsCheckpoint = false;
780
- yield* PubSub.publish(checks, void 0);
683
+ if (native?.pollUnsafe() !== void 0) {
684
+ result = yield* Fiber.join(native);
685
+ native = void 0;
686
+ if (result.phase === "actionable") phase = "actionable";
687
+ settled += result.settled;
688
+ observed.nativeOnly = false;
689
+ recovery.started = true;
690
+ yield* Deferred.succeed(recovery.queue, []);
691
+ checkLanes();
692
+ }
693
+ if (!recoveryObserved && recoveryFiber.pollUnsafe() !== void 0) {
694
+ yield* Fiber.join(recoveryFiber);
695
+ recoveryObserved = true;
696
+ nativeCheck = recovery.needsCheckpoint;
697
+ }
698
+ if (!backfillObserved) {
699
+ const exit = backfill.pollUnsafe();
700
+ if (exit !== void 0) {
701
+ backfillObserved = true;
702
+ if (Exit.isFailure(exit)) failure ??= exit.cause;
781
703
  }
782
704
  }
783
- if (retiredExit !== void 0 && Exit.isFailure(retiredExit)) break;
784
- if (recoveryFinished && backfill.pollUnsafe() !== void 0 && messageActivity.quiet() && hostActivity.quiet()) {
785
- const now = yield* Clock.currentTimeMillis;
786
- const messageDeadline = yield* messages.pendingDeadline;
787
- const hostDeadline = yield* host.pendingDeadline;
788
- const messageDue = deliveryFiber.pollUnsafe() === void 0 && !messageActivity.failed() && Option.isSome(messageDeadline) && messageDeadline.value <= now;
789
- const hostDue = hostFiber.pollUnsafe() === void 0 && !hostActivity.failed() && now + host.dispatchTimeoutMillis <= DateTime.toEpochMillis(dispatchUntil) && Option.isSome(hostDeadline) && hostDeadline.value <= now;
790
- if (yield* Effect.sync(() => {
791
- if (messageDue || hostDue || !messageActivity.quiet() || !hostActivity.quiet()) return false;
792
- dispatchOpen = false;
793
- return true;
794
- })) break;
795
- yield* PubSub.publish(checks, void 0);
705
+ for (const lane of lanes) {
706
+ const exit = lane.fiber?.pollUnsafe();
707
+ if (exit === void 0) continue;
708
+ lane.fiber = void 0;
709
+ if (Exit.isFailure(exit)) {
710
+ failure ??= exit.cause;
711
+ lane.exhausted = true;
712
+ } else lane.check ||= exit.value;
713
+ }
714
+ const deliveryExit = delivery?.pollUnsafe();
715
+ if (deliveryExit !== void 0) {
716
+ delivery = void 0;
717
+ if (Exit.isFailure(deliveryExit)) {
718
+ failure ??= deliveryExit.cause;
719
+ messageExhausted = true;
720
+ }
796
721
  }
722
+ if (failure !== void 0 && native === void 0) return yield* Effect.failCause(failure);
797
723
  const now = yield* Clock.currentTimeMillis;
798
- const until = DateTime.toEpochMillis(yieldAfter);
799
724
  if (now >= until) break;
800
- const next = Math.min(result.nextAttemptAt ?? Infinity, now + config.wakeScanInterval, until);
801
- const recoveryDone = recoveryFinished ? Effect.never : Fiber.await(recoveryFiber);
802
- const retirementDone = retiredExit === void 0 ? Fiber.await(retired) : Effect.never;
803
- const changed = activityChanged;
804
- const ready = yield* Effect.raceFirst(Effect.raceFirst(notified, Effect.sleep(Math.max(0, next - now))).pipe(Effect.as("native")), Effect.raceFirst(recoveryDone.pipe(Effect.as("recovery")), Effect.raceFirst(retirementDone.pipe(Effect.as("retired")), Deferred.await(changed).pipe(Effect.as("activity")))));
805
- if (ready === "recovery" || ready === "retired") continue;
806
- if (ready === "activity") activityChanged = yield* Deferred.make();
807
- if ((yield* Clock.currentTimeMillis) >= until) break;
808
- if (ready === "native") yield* PubSub.publish(checks, void 0);
809
- const awakened = yield* beginNative(observed);
810
- result = yield* advance(awakened, yieldAfter, observed, recovery);
725
+ for (const lane of lanes) {
726
+ if (lane.fiber !== void 0 || lane.exhausted || !lane.check) continue;
727
+ lane.check = false;
728
+ lane.fiber = yield* fork(Effect.gen(function* () {
729
+ yield* Schema.decodeEffect(AuxiliaryDispatchMillis)(lane.dispatchTimeoutMillis).pipe(Effect.mapError((cause) => DurableAlarmError.make({
730
+ operation: "host dispatch allowance",
731
+ message: "Declare an integer whole-wave allowance between 1 and 300000 milliseconds",
732
+ cause
733
+ })));
734
+ if ((yield* Clock.currentTimeMillis) + lane.dispatchTimeoutMillis > dispatchEnd) {
735
+ lane.exhausted = true;
736
+ return false;
737
+ }
738
+ return yield* Effect.gen(function* () {
739
+ if (!lane.initial) {
740
+ const deadline = yield* lane.pendingDeadline;
741
+ if (Option.isNone(deadline) || deadline.value > (yield* Clock.currentTimeMillis)) return false;
742
+ }
743
+ const selectedAt = yield* Clock.currentTimeMillis;
744
+ if (selectedAt >= until || selectedAt + lane.dispatchTimeoutMillis > dispatchEnd) {
745
+ lane.exhausted = true;
746
+ return false;
747
+ }
748
+ lane.initial = false;
749
+ yield* lane.run;
750
+ return true;
751
+ }).pipe(Effect.scoped, Effect.timeoutOrElse({
752
+ duration: lane.dispatchTimeoutMillis,
753
+ orElse: () => DurableAlarmError.make({
754
+ operation: "host dispatch allowance",
755
+ message: "The admitted host wave exceeded its allowance; durable work remains pending"
756
+ })
757
+ }));
758
+ }));
759
+ }
760
+ if (delivery === void 0 && !messageExhausted) {
761
+ const selected = yield* Effect.gen(function* () {
762
+ const deadline = yield* messages.pendingDeadline;
763
+ return Option.isSome(deadline) && deadline.value <= (yield* Clock.currentTimeMillis) ? Option.some(yield* messages.prepare) : Option.none();
764
+ }).pipe(Effect.exit);
765
+ if (Exit.isFailure(selected)) {
766
+ failure ??= selected.cause;
767
+ messageExhausted = true;
768
+ } else if (Option.isSome(selected.value)) {
769
+ const wave = selected.value.value;
770
+ const selectedAt = yield* Clock.currentTimeMillis;
771
+ if (selectedAt >= until || selectedAt + wave.timeoutMillis > dispatchEnd) messageExhausted = true;
772
+ else delivery = yield* fork(wave.run);
773
+ }
774
+ }
775
+ if (native === void 0 && recoveryObserved && !recovery.needsCheckpoint && backfillObserved && delivery === void 0 && lanes.every((lane) => lane.fiber === void 0)) break;
776
+ if (native === void 0 && nativeCheck) {
777
+ nativeCheck = false;
778
+ const checkpoint = recoveryObserved && recovery.needsCheckpoint;
779
+ if (checkpoint) recovery.needsCheckpoint = false;
780
+ native = yield* fork(Effect.gen(function* () {
781
+ const awakened = checkpoint ? started : yield* beginNative(observed);
782
+ return yield* advance(awakened, yieldAfter, observed, recovery, !checkpoint || settled === 0);
783
+ }));
784
+ }
785
+ const next = Math.min(native === void 0 ? result.nextAttemptAt ?? Infinity : Infinity, now + config.wakeScanInterval, until);
786
+ const completing = [
787
+ ...native === void 0 ? [] : [native],
788
+ ...recoveryObserved ? [] : [recoveryFiber],
789
+ ...backfillObserved ? [] : [backfill],
790
+ ...delivery === void 0 ? [] : [delivery],
791
+ ...lanes.flatMap((lane) => lane.fiber === void 0 ? [] : [lane.fiber])
792
+ ];
793
+ if ((yield* Effect.raceAllFirst([...completing.map((fiber) => Fiber.await(fiber).pipe(Effect.as("completed"))), Effect.raceFirst(notified, Effect.sleep(Math.max(0, next - now))).pipe(Effect.as("scan"))])) === "scan") {
794
+ nativeCheck = true;
795
+ checkLanes();
796
+ }
797
+ }
798
+ if (native !== void 0) {
799
+ result = yield* Fiber.join(native);
811
800
  if (result.phase === "actionable") phase = "actionable";
812
801
  settled += result.settled;
813
802
  observed.nativeOnly = false;
814
- if (result.settled > 0) yield* PubSub.publish(checks, void 0);
803
+ recovery.started = true;
804
+ yield* Deferred.succeed(recovery.queue, []);
815
805
  }
816
- yield* closeDispatch;
817
- yield* Fiber.join(retired);
806
+ yield* Fiber.joinAll([
807
+ recoveryFiber,
808
+ backfill,
809
+ ...delivery === void 0 ? [] : [delivery],
810
+ ...lanes.flatMap((lane) => lane.fiber === void 0 ? [] : [lane.fiber])
811
+ ]);
812
+ if (failure !== void 0) return yield* Effect.failCause(failure);
818
813
  if (recovery.needsCheckpoint) {
819
814
  result = yield* advance(started, yieldAfter, observed, recovery, false);
820
815
  if (result.phase === "actionable") phase = "actionable";
@@ -875,6 +870,6 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
875
870
  }));
876
871
  };
877
872
  //#endregion
878
- export { DurableAlarmError, DurableAlarmService, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance, ThreadMaintenanceActivity, ThreadMaintenanceFailpoint, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadRecoveryFault, publishCommitted, Alarm_exports as t };
873
+ export { DurableAlarmError, DurableAlarmService, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance, ThreadMaintenanceFailpoint, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadRecoveryFault, publishCommitted, Alarm_exports as t };
879
874
 
880
875
  //# sourceMappingURL=Alarm.mjs.map