@yielded/agent-platform-cloudflare 0.1.0-beta.174 → 0.1.0-beta.176
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-4J9B8Fg8.mjs → Alarm-7uipp6LW.mjs} +155 -117
- package/dist/Alarm-7uipp6LW.mjs.map +1 -0
- package/dist/Alarm.d.mts +2 -0
- package/dist/Alarm.mjs +1 -1
- package/dist/CloudflareScheduling.mjs +2 -2
- package/dist/CloudflareSubscriptions.mjs +2 -2
- package/dist/{CloudflareThreadClient-lTm6baDP.d.mts → CloudflareThreadClient-HO_3rkM7.d.mts} +362 -362
- package/dist/{CloudflareThreadClient-DU548EEj.mjs → CloudflareThreadClient-bA3lGA_2.mjs} +4 -4
- package/dist/CloudflareThreadClient-bA3lGA_2.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1 -1
- package/dist/CloudflareThreadClient.mjs +1 -1
- package/dist/{ThreadObject-BLYgzcdT.d.mts → ThreadObject-CxNlhoPL.d.mts} +13 -13
- package/dist/{ThreadObject-C2LF3btz.mjs → ThreadObject-SNcCsrS2.mjs} +52 -50
- package/dist/ThreadObject-SNcCsrS2.mjs.map +1 -0
- package/dist/ThreadObject.d.mts +1 -1
- package/dist/ThreadObject.mjs +1 -1
- package/dist/WakeScheduler.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/{prepared-admission-BoUPHSNQ.mjs → prepared-admission-BvMiFR41.mjs} +2 -2
- package/dist/{prepared-admission-BoUPHSNQ.mjs.map → prepared-admission-BvMiFR41.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/Alarm.ts +295 -201
- package/src/CloudflareThreadClient.ts +3 -2
- package/src/ThreadObject.ts +17 -22
- package/src/internal/layers.ts +124 -82
- package/src/internal/message-delivery.ts +61 -37
- package/dist/Alarm-4J9B8Fg8.mjs.map +0 -1
- package/dist/CloudflareThreadClient-DU548EEj.mjs.map +0 -1
- package/dist/ThreadObject-C2LF3btz.mjs.map +0 -1
|
@@ -386,13 +386,14 @@ const liveTextProtocolFailure = () =>
|
|
|
386
386
|
message: "Provisional text is incomplete or invalid; discard drafts and read canonical records",
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
+
const decodeLiveTextFrame = Schema.decodeUnknownSync(Schema.fromJsonString(LiveTextFrame));
|
|
390
|
+
|
|
389
391
|
const decodeLiveText = (
|
|
390
392
|
threadId: ThreadId,
|
|
391
393
|
source: ReadableStream<unknown>,
|
|
392
394
|
): Stream.Stream<LiveTextFrame, HostProtocolError | ThreadClientError> =>
|
|
393
395
|
Stream.suspend(() => {
|
|
394
396
|
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
395
|
-
const decode = Schema.decodeUnknownSync(Schema.fromJsonString(LiveTextFrame));
|
|
396
397
|
let pending = "";
|
|
397
398
|
let pendingBytes = 0;
|
|
398
399
|
let expected = 0;
|
|
@@ -488,7 +489,7 @@ const decodeLiveText = (
|
|
|
488
489
|
if (newline < 0) break;
|
|
489
490
|
|
|
490
491
|
const frame = yield* Effect.try({
|
|
491
|
-
try: () =>
|
|
492
|
+
try: () => decodeLiveTextFrame(pending),
|
|
492
493
|
catch: liveTextProtocolFailure,
|
|
493
494
|
});
|
|
494
495
|
|
package/src/ThreadObject.ts
CHANGED
|
@@ -351,16 +351,17 @@ const gateAdmissionLimits = Effect.fnUntraced(function* (
|
|
|
351
351
|
);
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
const queueDepth = yield* Stream.runFold(
|
|
355
|
+
ledger.scanNonterminal,
|
|
356
|
+
() => 0,
|
|
357
|
+
(depth, submission) => depth + (submission.threadId === threadId ? 1 : 0),
|
|
357
358
|
);
|
|
358
359
|
|
|
359
|
-
if (
|
|
360
|
+
if (queueDepth >= config.limits.maxQueueDepthPerLane) {
|
|
360
361
|
return yield* refuseFresh(
|
|
361
362
|
AdmissionLimitExceeded.make({
|
|
362
363
|
limit: "queue-depth",
|
|
363
|
-
actual:
|
|
364
|
+
actual: queueDepth,
|
|
364
365
|
maximum: config.limits.maxQueueDepthPerLane,
|
|
365
366
|
}),
|
|
366
367
|
);
|
|
@@ -1186,31 +1187,25 @@ const effectCfPlatformLayer = (
|
|
|
1186
1187
|
DurableObjectContext | ThreadObjectNamespace,
|
|
1187
1188
|
CloudflareBindingError,
|
|
1188
1189
|
EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment
|
|
1189
|
-
> =>
|
|
1190
|
-
|
|
1190
|
+
> =>
|
|
1191
|
+
Layer.effectContext(
|
|
1191
1192
|
Effect.gen(function* () {
|
|
1192
1193
|
const state = yield* EffectCfDurableObjectState.DurableObjectState;
|
|
1193
1194
|
const env = yield* WorkerEnvironment;
|
|
1194
|
-
|
|
1195
|
-
return DurableObjectContext.of({ ctx: state.raw, env });
|
|
1196
|
-
}),
|
|
1197
|
-
);
|
|
1198
|
-
|
|
1199
|
-
const namespace = Layer.effect(ThreadObjectNamespace)(
|
|
1200
|
-
Effect.gen(function* () {
|
|
1201
|
-
const env = yield* WorkerEnvironment;
|
|
1202
1195
|
const binding = yield* threadNamespaceFromEnv(env, namespaceBinding);
|
|
1203
1196
|
|
|
1204
|
-
return
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1197
|
+
return Context.make(DurableObjectContext, { ctx: state.raw, env }).pipe(
|
|
1198
|
+
Context.add(
|
|
1199
|
+
ThreadObjectNamespace,
|
|
1200
|
+
ThreadObjectNamespace.of({
|
|
1201
|
+
get: (threadId) => binding.get(binding.idFromName(threadId)),
|
|
1202
|
+
...(rpcTracing === true ? { rpcTracing: namespaceBinding } : {}),
|
|
1203
|
+
}),
|
|
1204
|
+
),
|
|
1205
|
+
);
|
|
1208
1206
|
}),
|
|
1209
1207
|
);
|
|
1210
1208
|
|
|
1211
|
-
return Layer.merge(context, namespace);
|
|
1212
|
-
};
|
|
1213
|
-
|
|
1214
1209
|
/** The public endpoints and effect-cf invocation hook of one Thread Object instance. */
|
|
1215
1210
|
export interface Instance<EventServices = never> extends InstanceType<
|
|
1216
1211
|
EffectCfDurableObject.DurableObjectClass<
|
package/src/internal/layers.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { submissionLedgerLayer } from "@yielded/agent-storage-cloudflare/do-submission-ledger";
|
|
8
8
|
import {
|
|
9
9
|
threadStoreLayer,
|
|
10
|
+
journalLayer,
|
|
10
11
|
storageConfigLayer,
|
|
11
12
|
storageFailpointLayer,
|
|
12
13
|
sqlOwnerLayer,
|
|
@@ -83,8 +84,15 @@ import {
|
|
|
83
84
|
type WorkerStopCommand,
|
|
84
85
|
} from "@yielded/agent/submission-ledger";
|
|
85
86
|
import { ThreadProjectionMaintenance } from "@yielded/agent/thread-projection-maintenance";
|
|
86
|
-
import
|
|
87
|
-
|
|
87
|
+
import {
|
|
88
|
+
type ThreadReader,
|
|
89
|
+
AppendResult,
|
|
90
|
+
type AppendRequest,
|
|
91
|
+
type ThreadStoreFailure,
|
|
92
|
+
PreparedAppend,
|
|
93
|
+
ThreadStoreError,
|
|
94
|
+
ThreadStore,
|
|
95
|
+
} from "@yielded/agent/thread-store";
|
|
88
96
|
import { ToolReconciler } from "@yielded/agent/tool-reconciler";
|
|
89
97
|
import { type WakeScheduler } from "@yielded/agent/wake-scheduler";
|
|
90
98
|
import {
|
|
@@ -134,7 +142,7 @@ import * as DueQueue from "./due-queue.ts";
|
|
|
134
142
|
import { LiveTextHub } from "./live-text.ts";
|
|
135
143
|
import {
|
|
136
144
|
guardedMessageDeliveryStoreLayer,
|
|
137
|
-
|
|
145
|
+
lazyThreadMessageDeliveryLayer,
|
|
138
146
|
} from "./message-delivery.ts";
|
|
139
147
|
import { cloudflarePreparedInputAdmissionLayer } from "./prepared-admission.ts";
|
|
140
148
|
import { ProgressWaitRegistry } from "./progress-wait.ts";
|
|
@@ -625,10 +633,14 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
|
|
|
625
633
|
verifyOnOpen: config.verifyOnOpen,
|
|
626
634
|
};
|
|
627
635
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
Layer.
|
|
636
|
+
// Memoize these shared compositions within this Object's build, so repeated
|
|
637
|
+
// consumers reuse their contexts as well as the scoped leaf services.
|
|
638
|
+
const infrastructure = Layer.suspend(() =>
|
|
639
|
+
Layer.mergeAll(
|
|
640
|
+
options.lifecyclePublication === undefined ? Layer.empty : lifecyclePublicationLayer,
|
|
641
|
+
storageConfigLayer(storageOptions),
|
|
642
|
+
Layer.effect(SqlClient)(SqlClient),
|
|
643
|
+
),
|
|
632
644
|
);
|
|
633
645
|
|
|
634
646
|
// Preserve the database owner's reader gate and rollback invalidators, while
|
|
@@ -661,7 +673,7 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
|
|
|
661
673
|
}),
|
|
662
674
|
).pipe(Layer.provide(sqlOwnerLayer));
|
|
663
675
|
|
|
664
|
-
const localInfrastructure = Layer.mergeAll(infrastructure, sourceOwner);
|
|
676
|
+
const localInfrastructure = Layer.suspend(() => Layer.mergeAll(infrastructure, sourceOwner));
|
|
665
677
|
|
|
666
678
|
const sourceProgress = Layer.effect(SqlStorageProgress)(
|
|
667
679
|
Effect.gen(function* () {
|
|
@@ -709,41 +721,58 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
|
|
|
709
721
|
|
|
710
722
|
// The same local ports serve routed decorators and owner-side RPC execution.
|
|
711
723
|
// The RPC executor must never receive routed ports and bounce requests between Objects.
|
|
712
|
-
const rawLocalPorts = Layer.
|
|
713
|
-
Layer.
|
|
714
|
-
|
|
724
|
+
const rawLocalPorts = Layer.suspend(() =>
|
|
725
|
+
Layer.mergeAll(threadStoreLayer, submissionLedgerLayer).pipe(
|
|
726
|
+
Layer.provide(journalLayer),
|
|
727
|
+
Layer.provide(localInfrastructure),
|
|
728
|
+
Layer.provide(sourceProgress),
|
|
729
|
+
),
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
const base = Layer.suspend(() =>
|
|
733
|
+
Layer.mergeAll(DurableAlarmService.layer, ProgressWaitRegistry.layer),
|
|
715
734
|
);
|
|
716
735
|
|
|
717
|
-
const base = Layer.mergeAll(DurableAlarmService.layer, ProgressWaitRegistry.layer);
|
|
718
736
|
const wakes = cloudflareWakeSchedulerLayer.pipe(Layer.provide(base));
|
|
719
737
|
|
|
720
|
-
const messageStore =
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
738
|
+
const messageStore = Layer.suspend(() =>
|
|
739
|
+
guardedMessageDeliveryStoreLayer.pipe(
|
|
740
|
+
Layer.provide(
|
|
741
|
+
doMessageDeliveryStoreLayer().pipe(
|
|
742
|
+
Layer.provide(localInfrastructure),
|
|
743
|
+
Layer.provide(sourceProgress),
|
|
744
|
+
),
|
|
725
745
|
),
|
|
746
|
+
Layer.provide(wakes),
|
|
726
747
|
),
|
|
727
|
-
Layer.provide(wakes),
|
|
728
748
|
);
|
|
729
749
|
|
|
730
|
-
const messageRecovery =
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
750
|
+
const messageRecovery = Layer.suspend(() =>
|
|
751
|
+
lazyThreadMessageDeliveryLayer(
|
|
752
|
+
// Four parallel attempts per wave. Native completion stops new wake-driven waves;
|
|
753
|
+
// retained retry deadlines schedule future alarms.
|
|
754
|
+
MessageDeliveryDriver.layer({ batchSize: 4, concurrency: 4 }).pipe(
|
|
755
|
+
Layer.provide(cloudflarePreparedInputAdmissionLayer),
|
|
756
|
+
Layer.provide(CloudflareThreadClient.layer),
|
|
757
|
+
Layer.provide(messageStore),
|
|
758
|
+
Layer.provide(wakes),
|
|
759
|
+
),
|
|
760
|
+
).pipe(Layer.provide(messageStore)),
|
|
738
761
|
);
|
|
739
762
|
|
|
740
|
-
const publication =
|
|
741
|
-
|
|
742
|
-
|
|
763
|
+
const publication =
|
|
764
|
+
options.publication === undefined
|
|
765
|
+
? ThreadPublication.layer
|
|
766
|
+
: options.publication.pipe(
|
|
767
|
+
Layer.provide(Layer.mergeAll(rawLocalPorts, Layer.effect(SqlClient)(SqlClient))),
|
|
768
|
+
);
|
|
743
769
|
|
|
744
|
-
const projection =
|
|
745
|
-
|
|
746
|
-
|
|
770
|
+
const projection =
|
|
771
|
+
options.projection === undefined
|
|
772
|
+
? ThreadProjectionMaintenance.layer
|
|
773
|
+
: options.projection.pipe(
|
|
774
|
+
Layer.provide(Layer.mergeAll(rawLocalPorts, Layer.effect(SqlClient)(SqlClient))),
|
|
775
|
+
);
|
|
747
776
|
|
|
748
777
|
const localPorts =
|
|
749
778
|
options.publication === undefined &&
|
|
@@ -765,6 +794,54 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
|
|
|
765
794
|
// Publication remains outside this local source/index critical section.
|
|
766
795
|
const sourceCommits = yield* Semaphore.make(1);
|
|
767
796
|
|
|
797
|
+
const observeAppend = (
|
|
798
|
+
request: AppendRequest,
|
|
799
|
+
body: Effect.Effect<AppendResult, ThreadStoreFailure>,
|
|
800
|
+
) =>
|
|
801
|
+
mutations
|
|
802
|
+
.withMutation(
|
|
803
|
+
sourceCommits
|
|
804
|
+
.withPermit(
|
|
805
|
+
body.pipe(
|
|
806
|
+
Effect.tap((result) =>
|
|
807
|
+
index
|
|
808
|
+
.applyCommitted(request, result)
|
|
809
|
+
.pipe(
|
|
810
|
+
Effect.catchCause((cause) =>
|
|
811
|
+
Cause.hasInterrupts(cause)
|
|
812
|
+
? Effect.interrupt
|
|
813
|
+
: Effect.logError(
|
|
814
|
+
"Thread projection deferred after source commit",
|
|
815
|
+
cause,
|
|
816
|
+
),
|
|
817
|
+
),
|
|
818
|
+
),
|
|
819
|
+
),
|
|
820
|
+
),
|
|
821
|
+
)
|
|
822
|
+
.pipe(Effect.tap(() => afterCommit)),
|
|
823
|
+
{
|
|
824
|
+
lanes: [
|
|
825
|
+
...(options.projection === undefined ? [] : [DueQueue.Projection]),
|
|
826
|
+
...(options.lifecyclePublication === undefined ||
|
|
827
|
+
!request.batch.records.some((record) =>
|
|
828
|
+
Schema.is(Schema.toType(LifecyclePublicationFact))(record.payload),
|
|
829
|
+
)
|
|
830
|
+
? []
|
|
831
|
+
: [DueQueue.Lifecycle]),
|
|
832
|
+
],
|
|
833
|
+
},
|
|
834
|
+
)
|
|
835
|
+
.pipe(
|
|
836
|
+
Effect.catchTag("DurableAlarmError", (cause) =>
|
|
837
|
+
ThreadStoreError.make({
|
|
838
|
+
operation: "prearm publication append",
|
|
839
|
+
message: cause.message,
|
|
840
|
+
cause,
|
|
841
|
+
}),
|
|
842
|
+
),
|
|
843
|
+
);
|
|
844
|
+
|
|
768
845
|
// Every runtime-owned producer prearms too: a crash between commit and invalidation
|
|
769
846
|
// leaves a NEW, uncertified generation. Source errors keep their native port types.
|
|
770
847
|
const observedStore = ThreadStore.of({
|
|
@@ -787,52 +864,7 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
|
|
|
787
864
|
),
|
|
788
865
|
},
|
|
789
866
|
}),
|
|
790
|
-
append: (request) =>
|
|
791
|
-
mutations
|
|
792
|
-
.withMutation(
|
|
793
|
-
sourceCommits
|
|
794
|
-
.withPermit(
|
|
795
|
-
store
|
|
796
|
-
.append(request)
|
|
797
|
-
.pipe(
|
|
798
|
-
Effect.tap((result) =>
|
|
799
|
-
index
|
|
800
|
-
.applyCommitted(request, result)
|
|
801
|
-
.pipe(
|
|
802
|
-
Effect.catchCause((cause) =>
|
|
803
|
-
Cause.hasInterrupts(cause)
|
|
804
|
-
? Effect.interrupt
|
|
805
|
-
: Effect.logError(
|
|
806
|
-
"Thread projection deferred after source commit",
|
|
807
|
-
cause,
|
|
808
|
-
),
|
|
809
|
-
),
|
|
810
|
-
),
|
|
811
|
-
),
|
|
812
|
-
),
|
|
813
|
-
)
|
|
814
|
-
.pipe(Effect.tap(() => afterCommit)),
|
|
815
|
-
{
|
|
816
|
-
lanes: [
|
|
817
|
-
...(options.projection === undefined ? [] : [DueQueue.Projection]),
|
|
818
|
-
...(options.lifecyclePublication === undefined ||
|
|
819
|
-
!request.batch.records.some((record) =>
|
|
820
|
-
Schema.is(Schema.toType(LifecyclePublicationFact))(record.payload),
|
|
821
|
-
)
|
|
822
|
-
? []
|
|
823
|
-
: [DueQueue.Lifecycle]),
|
|
824
|
-
],
|
|
825
|
-
},
|
|
826
|
-
)
|
|
827
|
-
.pipe(
|
|
828
|
-
Effect.catchTag("DurableAlarmError", (cause) =>
|
|
829
|
-
ThreadStoreError.make({
|
|
830
|
-
operation: "prearm publication append",
|
|
831
|
-
message: cause.message,
|
|
832
|
-
cause,
|
|
833
|
-
}),
|
|
834
|
-
),
|
|
835
|
-
),
|
|
867
|
+
append: (request) => observeAppend(request, store.append(request)),
|
|
836
868
|
});
|
|
837
869
|
|
|
838
870
|
const observeIntent = <A, Failure>(
|
|
@@ -886,6 +918,7 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
|
|
|
886
918
|
);
|
|
887
919
|
|
|
888
920
|
const stopWorker = ledger.stopWorker;
|
|
921
|
+
const appendInput = ledger.appendInput;
|
|
889
922
|
|
|
890
923
|
return Context.make(ThreadStore, observedStore).pipe(
|
|
891
924
|
Context.add(StorageOwner, storageOwner),
|
|
@@ -947,13 +980,22 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
|
|
|
947
980
|
}),
|
|
948
981
|
Context.add(SubmissionLedger, {
|
|
949
982
|
...ledger,
|
|
950
|
-
...(
|
|
983
|
+
...(appendInput === undefined
|
|
984
|
+
? {}
|
|
985
|
+
: {
|
|
986
|
+
appendInput: (submissionId, input) =>
|
|
987
|
+
Effect.flatMap(PreparedAppend.capture(input), (request) =>
|
|
988
|
+
observeAppend(request, appendInput(submissionId, request)),
|
|
989
|
+
),
|
|
990
|
+
}),
|
|
991
|
+
...(options.hostLanesForMutation === undefined &&
|
|
992
|
+
options.lifecyclePublication === undefined
|
|
951
993
|
? {}
|
|
952
994
|
: {
|
|
953
|
-
admit: (
|
|
995
|
+
admit: (...args: Parameters<SubmissionLedger["Service"]["admit"]>) =>
|
|
954
996
|
observeMutation(
|
|
955
|
-
{ _tag: "Admission", request },
|
|
956
|
-
ledger.admit(
|
|
997
|
+
{ _tag: "Admission", request: args[0] },
|
|
998
|
+
ledger.admit(...args),
|
|
957
999
|
false,
|
|
958
1000
|
),
|
|
959
1001
|
}),
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
MessageDeliveryStore,
|
|
6
6
|
} from "@yielded/agent/message-delivery";
|
|
7
7
|
import { WakeScheduler } from "@yielded/agent/wake-scheduler";
|
|
8
|
-
import { Clock, Context, Effect, Layer, Option } from "effect";
|
|
8
|
+
import { Clock, Context, Duration, Effect, Exit, Layer, Option } from "effect";
|
|
9
9
|
|
|
10
10
|
import { DurableAlarmError, ThreadMessageDelivery, ThreadMutationGate } from "../Alarm.ts";
|
|
11
11
|
import { ThreadObjectPlacement } from "../CloudflareBindings.ts";
|
|
@@ -76,45 +76,69 @@ export const guardedMessageDeliveryStoreLayer = Layer.effect(
|
|
|
76
76
|
}),
|
|
77
77
|
);
|
|
78
78
|
|
|
79
|
-
/**
|
|
80
|
-
export const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
/** Build the driver in the Object's Scope only when retained delivery work is due. */
|
|
80
|
+
export const lazyThreadMessageDeliveryLayer = <E, R>(
|
|
81
|
+
driver: Layer.Layer<MessageDeliveryDriver, E, R>,
|
|
82
|
+
) =>
|
|
83
|
+
Layer.fromBuildMemo((memoMap, scope) =>
|
|
84
|
+
Effect.gen(function* () {
|
|
85
|
+
const context = yield* Effect.context<R>();
|
|
86
|
+
const store = yield* MessageDeliveryStore;
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
// Failed builds release their Layer scopes; let a later wave build again.
|
|
89
|
+
const service = yield* Effect.cachedWithTTL(
|
|
90
|
+
Layer.buildWithMemoMap(driver, memoMap, scope).pipe(
|
|
91
|
+
Effect.provide(context),
|
|
92
|
+
Effect.map(Context.get(MessageDeliveryDriver)),
|
|
93
|
+
Effect.mapError((cause) =>
|
|
94
|
+
DurableAlarmError.make({
|
|
95
|
+
operation: "prepare message delivery",
|
|
96
|
+
message: "Durable message recovery remains pending",
|
|
97
|
+
cause,
|
|
98
|
+
}),
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
(exit) => (Exit.isSuccess(exit) ? Duration.infinity : Duration.zero),
|
|
102
|
+
);
|
|
90
103
|
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
const failure = (operation: string) => () =>
|
|
105
|
+
DurableAlarmError.make({
|
|
106
|
+
operation,
|
|
107
|
+
message: "Durable message recovery remains pending",
|
|
108
|
+
});
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// The assembled driver has four permits: a wave is one parallel attempt window.
|
|
97
|
-
const keys = yield* store.due(yield* Clock.currentTimeMillis, 4);
|
|
98
|
-
const records = yield* Effect.forEach(keys, (key) => store.get(key));
|
|
110
|
+
const prepare = Effect.gen(function* () {
|
|
111
|
+
const deadline = yield* store.nextDeadline();
|
|
99
112
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)
|
|
105
|
-
run: Effect.forEach(keys, (key) => driver.process(key), {
|
|
106
|
-
concurrency: 4,
|
|
107
|
-
discard: true,
|
|
108
|
-
}).pipe(
|
|
109
|
-
Effect.andThen(store.nextDeadline()),
|
|
110
|
-
Effect.map(Option.fromNullishOr),
|
|
111
|
-
Effect.mapError(failure("dispatch message delivery")),
|
|
112
|
-
),
|
|
113
|
-
};
|
|
114
|
-
}).pipe(Effect.mapError(failure("prepare message delivery")));
|
|
113
|
+
if (deadline === null || deadline > (yield* Clock.currentTimeMillis))
|
|
114
|
+
return { timeoutMillis: 1, run: Effect.succeed(Option.fromNullishOr(deadline)) };
|
|
115
|
+
// The assembled driver has four permits: a wave is one parallel attempt window.
|
|
116
|
+
const keys = yield* store.due(yield* Clock.currentTimeMillis, 4);
|
|
117
|
+
const records = yield* Effect.forEach(keys, (key) => store.get(key));
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
return {
|
|
120
|
+
timeoutMillis: Math.max(
|
|
121
|
+
1,
|
|
122
|
+
...records.map((record) => record?.policy.attemptTimeoutMillis ?? 1),
|
|
123
|
+
),
|
|
124
|
+
run: Effect.flatMap(service, (driver) =>
|
|
125
|
+
Effect.forEach(keys, (key) => driver.process(key), {
|
|
126
|
+
concurrency: 4,
|
|
127
|
+
discard: true,
|
|
128
|
+
}),
|
|
129
|
+
).pipe(
|
|
130
|
+
Effect.andThen(store.nextDeadline()),
|
|
131
|
+
Effect.map(Option.fromNullishOr),
|
|
132
|
+
Effect.mapError(failure("dispatch message delivery")),
|
|
133
|
+
),
|
|
134
|
+
};
|
|
135
|
+
}).pipe(Effect.mapError(failure("prepare message delivery")));
|
|
136
|
+
|
|
137
|
+
return Context.make(ThreadMessageDelivery, { prepare });
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
/** Message progress shares the native alarm slot without blocking source runtime work. */
|
|
142
|
+
export const threadMessageDeliveryLayer = lazyThreadMessageDeliveryLayer(
|
|
143
|
+
Layer.effect(MessageDeliveryDriver, MessageDeliveryDriver),
|
|
120
144
|
);
|