@effect-agent/storage-memory 0.1.0-beta.37 → 0.1.0-beta.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { AttemptId, ConversationId, ReceiptId, SubmissionId } from "@effect-agent/core";
2
- import { AbortCommand, AbortIntent, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionRequest, AdmissionResult, AppendConflict, AppendResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, CanonicalRecordEnvelope, CanonicalSequence, CheckpointRejected, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, ConversationExport, ConversationExportRequest, ConversationMaterialization, ConversationNotMaterialized, ConversationObservation, ConversationRead, ConversationStore, ConversationStoreError, ConversationTail, ConversationTailRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, EMPTY_TAIL_DIGEST, FenceRejected, FencedAppendRequest, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, LoadCheckpointRequest, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, ObservationOffset, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, PersistedJson, ProducerEpoch, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, SaveCheckpointRequest, ScheduleCapacityError, ScheduleChange, ScheduleConflict, ScheduleDueCursor, ScheduleFailpoint, ScheduleKey, ScheduleNotFound, ScheduleOwner, SchedulePageRequest, ScheduleRecord, ScheduleStorageError, ScheduleStore, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, applyScheduleChange, compareScheduleKeys, compareScheduleNames, defaultSchedulingLimits, digestCanonicalBatch, scheduleDeadline, scheduleKeyOf, scheduleKeyString, scheduleOwnerKey, scheduleUsesCapacity, settlementFailureFromRecord } from "@effect-agent/session";
1
+ import { AttemptId, ReceiptId, SubmissionId, ThreadId } from "@effect-agent/core";
2
+ import { AbortCommand, AbortIntent, AcceptedEvent, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionRequest, AdmissionResult, AppendConflict, AppendResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, CanonicalRecordEnvelope, CanonicalSequence, CheckpointRejected, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, DeliveryChange, Digest, EMPTY_TAIL_DIGEST, FenceRejected, FencedAppendRequest, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, LoadCheckpointRequest, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, ObservationOffset, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, PersistedJson, ProducerEpoch, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, SaveCheckpointRequest, ScheduleCapacityError, ScheduleChange, ScheduleConflict, ScheduleDueCursor, ScheduleFailpoint, ScheduleKey, ScheduleNotFound, ScheduleOwner, SchedulePageRequest, ScheduleRecord, ScheduleStorageError, ScheduleStore, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SourcePartition, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubscriptionDelivery, SubscriptionDeliveryKey, SubscriptionError, SubscriptionFailpoint, SubscriptionKey, SubscriptionLimits, SubscriptionName, SubscriptionRecord, SubscriptionScanCursors, SubscriptionStore, SuspendRequest, SuspensionSnapshot, ThreadExport, ThreadExportRequest, ThreadMaterialization, ThreadNotMaterialized, ThreadObservation, ThreadRead, ThreadStore, ThreadStoreError, ThreadTail, ThreadTailRequest, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, applyScheduleChange, applySubscriptionDeliveryChange, compareScheduleKeys, compareScheduleNames, defaultSchedulingLimits, digestCanonicalBatch, scheduleDeadline, scheduleKeyOf, scheduleKeyString, scheduleOwnerKey, scheduleUsesCapacity, settlementFailureFromRecord, subscriptionCanSelect, subscriptionDeliveryCanSelect, subscriptionDeliveryKeyString, subscriptionKeyString } from "@effect-agent/thread";
3
3
  import { Clock, Crypto, DateTime, Duration, Effect, Encoding, Layer, Option, PubSub, Ref, Result, Schema, Stream } from "effect";
4
4
  //#region src/memory-ledger.ts
5
5
  const MAX_SUBMISSIONS = 65536;
@@ -46,7 +46,7 @@ const ledgerError = (operation, message, cause) => cause === void 0 ? LedgerErro
46
46
  message,
47
47
  cause
48
48
  });
49
- const validate$1 = Effect.fn("MemorySubmissionLedger.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => ledgerError(operation, `Invalid ${operation} request`, error))));
49
+ const validate$2 = Effect.fn("MemorySubmissionLedger.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => ledgerError(operation, `Invalid ${operation} request`, error))));
50
50
  const decodeSubmissionId = Schema.decodeSync(SubmissionId);
51
51
  const decodeReceiptId = Schema.decodeSync(ReceiptId);
52
52
  const decodeAttemptId = Schema.decodeSync(AttemptId);
@@ -56,10 +56,10 @@ const decodeProducerEpoch = Schema.decodeSync(ProducerEpoch);
56
56
  const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
57
57
  const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
58
58
  const utc = (millis) => DateTime.toUtc(DateTime.makeUnsafe(millis));
59
- const admissionKey = (conversationId, principal, idempotencyKey) => `${conversationId}\u001f${principal}\u001f${idempotencyKey}`;
59
+ const admissionKey = (threadId, principal, idempotencyKey) => `${threadId}\u001f${principal}\u001f${idempotencyKey}`;
60
60
  const toSnapshot = (row) => SubmissionSnapshot.make({
61
61
  submissionId: row.submissionId,
62
- conversationId: row.conversationId,
62
+ threadId: row.threadId,
63
63
  queueSequence: row.queueSequence,
64
64
  principal: row.principal,
65
65
  idempotencyKey: row.idempotencyKey,
@@ -90,10 +90,10 @@ const toReservationSnapshot = (row) => ChildBudgetReservationSnapshot.make({
90
90
  });
91
91
  /** Linkage equality: both absent, or both present naming the same parent Tool Call. */
92
92
  const sameParentLinkage = (left, right) => left === void 0 ? right === void 0 : right !== void 0 && left.parentSubmissionId === right.parentSubmissionId && left.parentToolCallId === right.parentToolCallId;
93
- const laneEpoch = (state, conversationId) => state.lanes.get(conversationId)?.producerEpoch ?? 0;
93
+ const laneEpoch = (state, threadId) => state.lanes.get(threadId)?.producerEpoch ?? 0;
94
94
  const ownershipLost = (state, stored) => OwnershipLost.make({
95
95
  submissionId: stored.row.submissionId,
96
- actualEpoch: decodeProducerEpoch(laneEpoch(state, stored.row.conversationId))
96
+ actualEpoch: decodeProducerEpoch(laneEpoch(state, stored.row.threadId))
97
97
  });
98
98
  /** The presented token owns the lane only while it matches the live ownership record. */
99
99
  const ownsLane = (stored, ownershipToken) => stored.ownership !== void 0 && stored.ownership.ownershipToken === ownershipToken;
@@ -105,10 +105,10 @@ const withChildReservation = (state, reservation) => ({
105
105
  ...state,
106
106
  childReservations: new Map(state.childReservations).set(reservation.reservationId, reservation)
107
107
  });
108
- const findHead = (state, conversationId) => {
108
+ const findHead = (state, threadId) => {
109
109
  let head;
110
110
  for (const stored of state.submissions.values()) {
111
- if (stored.row.conversationId !== conversationId || stored.row.state === "settled") continue;
111
+ if (stored.row.threadId !== threadId || stored.row.state === "settled") continue;
112
112
  if (head === void 0 || stored.row.queueSequence < head.row.queueSequence) head = stored;
113
113
  }
114
114
  return head;
@@ -158,16 +158,16 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
158
158
  const leaseMillis = Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION);
159
159
  const capabilities = Effect.succeed(LedgerCapabilities.make({ durability: "non-durable" }));
160
160
  const admit = Effect.fn("MemorySubmissionLedger.admit")((unvalidated) => Effect.gen(function* () {
161
- const request = yield* validate$1(AdmissionRequest, "admit", unvalidated);
161
+ const request = yield* validate$2(AdmissionRequest, "admit", unvalidated);
162
162
  const nowMillis = yield* Clock.currentTimeMillis;
163
163
  const decision = yield* Ref.modify(state, (current) => {
164
- const key = admissionKey(request.conversationId, request.principal, request.idempotencyKey);
164
+ const key = admissionKey(request.threadId, request.principal, request.idempotencyKey);
165
165
  const existingId = current.admissionIndex.get(key);
166
166
  if (existingId !== void 0) {
167
167
  const existing = current.submissions.get(existingId);
168
168
  if (existing === void 0) return [failure(ledgerError("admit", "Admission index references a missing Submission")), current];
169
169
  if (existing.row.inputDigest !== request.inputDigest || !sameParentLinkage(existing.row.parentLinkage, request.parentLinkage)) return [failure(AdmissionConflict.make({
170
- conversationId: request.conversationId,
170
+ threadId: request.threadId,
171
171
  principal: request.principal,
172
172
  idempotencyKey: request.idempotencyKey,
173
173
  existingInputDigest: existing.row.inputDigest,
@@ -182,14 +182,14 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
182
182
  })), current];
183
183
  }
184
184
  if (current.submissions.size >= MAX_SUBMISSIONS) return [failure(ledgerError("admit", `In-memory submission limit ${MAX_SUBMISSIONS} exceeded`)), current];
185
- const lane = current.lanes.get(request.conversationId) ?? {
185
+ const lane = current.lanes.get(request.threadId) ?? {
186
186
  nextQueueSequence: 1,
187
187
  producerEpoch: 0
188
188
  };
189
189
  const mintCounter = current.mintCounter + 1;
190
190
  const row = {
191
191
  submissionId: decodeSubmissionId(`submission-memory-${mintCounter}`),
192
- conversationId: request.conversationId,
192
+ threadId: request.threadId,
193
193
  queueSequence: decodeQueueSequence(lane.nextQueueSequence),
194
194
  principal: request.principal,
195
195
  idempotencyKey: request.idempotencyKey,
@@ -218,7 +218,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
218
218
  unknownResolutions: /* @__PURE__ */ new Map()
219
219
  });
220
220
  const admissionIndex = new Map(current.admissionIndex).set(key, row.submissionId);
221
- const lanes = new Map(current.lanes).set(request.conversationId, {
221
+ const lanes = new Map(current.lanes).set(request.threadId, {
222
222
  nextQueueSequence: lane.nextQueueSequence + 1,
223
223
  producerEpoch: lane.producerEpoch
224
224
  });
@@ -240,7 +240,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
240
240
  return decision.value;
241
241
  }));
242
242
  const markReady = Effect.fn("MemorySubmissionLedger.markReady")((unvalidated) => Effect.gen(function* () {
243
- const request = yield* validate$1(MarkReadyRequest, "markReady", unvalidated);
243
+ const request = yield* validate$2(MarkReadyRequest, "markReady", unvalidated);
244
244
  const nowMillis = yield* Clock.currentTimeMillis;
245
245
  const decision = yield* Ref.modify(state, (current) => {
246
246
  const stored = current.submissions.get(request.submissionId);
@@ -258,33 +258,33 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
258
258
  if (decision._tag === "failure") return yield* decision.error;
259
259
  }));
260
260
  const lookup = Effect.fn("MemorySubmissionLedger.lookup")((unvalidated) => Effect.gen(function* () {
261
- const request = yield* validate$1(SubmissionLookup, "lookup", unvalidated);
261
+ const request = yield* validate$2(SubmissionLookup, "lookup", unvalidated);
262
262
  const current = yield* Ref.get(state);
263
- const submissionId = request._tag === "SubmissionLookupById" ? request.submissionId : current.admissionIndex.get(admissionKey(request.conversationId, request.principal, request.idempotencyKey));
263
+ const submissionId = request._tag === "SubmissionLookupById" ? request.submissionId : current.admissionIndex.get(admissionKey(request.threadId, request.principal, request.idempotencyKey));
264
264
  const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
265
265
  return stored === void 0 ? Option.none() : Option.some(toSnapshot(stored.row));
266
266
  }));
267
267
  const resolveAdmission = Effect.fn("MemorySubmissionLedger.resolveAdmission")((unvalidated) => Effect.gen(function* () {
268
- const request = yield* validate$1(SubmissionLookupByKey, "resolveAdmission", unvalidated);
268
+ const request = yield* validate$2(SubmissionLookupByKey, "resolveAdmission", unvalidated);
269
269
  if (options.resolveAdmissionFault !== void 0) {
270
270
  const fault = yield* options.resolveAdmissionFault;
271
271
  if (Option.isSome(fault)) return AdmissionIndeterminate.make({ reason: fault.value });
272
272
  }
273
273
  const current = yield* Ref.get(state);
274
- const submissionId = current.admissionIndex.get(admissionKey(request.conversationId, request.principal, request.idempotencyKey));
274
+ const submissionId = current.admissionIndex.get(admissionKey(request.threadId, request.principal, request.idempotencyKey));
275
275
  const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
276
276
  return stored === void 0 ? AdmissionNotAdmitted.make() : AdmissionAdmitted.make({ submission: toSnapshot(stored.row) });
277
277
  }));
278
278
  const claim = Effect.fn("MemorySubmissionLedger.claim")((unvalidated) => Effect.gen(function* () {
279
- const request = yield* validate$1(ClaimRequest, "claim", unvalidated);
279
+ const request = yield* validate$2(ClaimRequest, "claim", unvalidated);
280
280
  const nowMillis = yield* Clock.currentTimeMillis;
281
281
  const decision = yield* Ref.modify(state, (current) => {
282
- const head = findHead(current, request.conversationId);
282
+ const head = findHead(current, request.threadId);
283
283
  if (head === void 0) return [success(Option.none()), current];
284
284
  if (BLOCKED_HEAD_STATES.has(head.row.state) || head.row.state === "unknown" && head.abortIntent === void 0) return [success(Option.none()), current];
285
285
  if (head.ownership !== void 0 && head.ownership.leaseExpiresAtMillis > nowMillis && head.ownership.ownerProducerId !== request.producerId) return [success(Option.none()), current];
286
- const lane = current.lanes.get(request.conversationId);
287
- if (lane === void 0) return [failure(ledgerError("claim", "Claimable head without a Conversation lane")), current];
286
+ const lane = current.lanes.get(request.threadId);
287
+ if (lane === void 0) return [failure(ledgerError("claim", "Claimable head without a Thread lane")), current];
288
288
  const producerEpoch = decodeProducerEpoch(lane.producerEpoch + 1);
289
289
  const mintCounter = current.mintCounter + 1;
290
290
  const ownership = {
@@ -303,7 +303,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
303
303
  row,
304
304
  ownership
305
305
  });
306
- const lanes = new Map(next.lanes).set(request.conversationId, {
306
+ const lanes = new Map(next.lanes).set(request.threadId, {
307
307
  nextQueueSequence: lane.nextQueueSequence,
308
308
  producerEpoch: lane.producerEpoch + 1
309
309
  });
@@ -324,7 +324,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
324
324
  return decision.value;
325
325
  }));
326
326
  const renewOwnership = Effect.fn("MemorySubmissionLedger.renewOwnership")((unvalidated) => Effect.gen(function* () {
327
- const request = yield* validate$1(RenewOwnershipRequest, "renewOwnership", unvalidated);
327
+ const request = yield* validate$2(RenewOwnershipRequest, "renewOwnership", unvalidated);
328
328
  const nowMillis = yield* Clock.currentTimeMillis;
329
329
  const decision = yield* Ref.modify(state, (current) => {
330
330
  const stored = current.submissions.get(request.submissionId);
@@ -346,7 +346,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
346
346
  return decision.value;
347
347
  }));
348
348
  const releaseOwnership = Effect.fn("MemorySubmissionLedger.releaseOwnership")((unvalidated) => Effect.gen(function* () {
349
- const request = yield* validate$1(ReleaseOwnershipRequest, "releaseOwnership", unvalidated);
349
+ const request = yield* validate$2(ReleaseOwnershipRequest, "releaseOwnership", unvalidated);
350
350
  const decision = yield* Ref.modify(state, (current) => {
351
351
  const stored = current.submissions.get(request.submissionId);
352
352
  if (stored === void 0) return [failure(ledgerError("releaseOwnership", `Unknown Submission ${request.submissionId}`)), current];
@@ -359,7 +359,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
359
359
  if (decision._tag === "failure") return yield* decision.error;
360
360
  }));
361
361
  const markInputApplied = Effect.fn("MemorySubmissionLedger.markInputApplied")((unvalidated) => Effect.gen(function* () {
362
- const request = yield* validate$1(MarkInputAppliedRequest, "markInputApplied", unvalidated);
362
+ const request = yield* validate$2(MarkInputAppliedRequest, "markInputApplied", unvalidated);
363
363
  const decision = yield* Ref.modify(state, (current) => {
364
364
  const stored = current.submissions.get(request.submissionId);
365
365
  if (stored === void 0) return [failure(ledgerError("markInputApplied", `Unknown Submission ${request.submissionId}`)), current];
@@ -381,7 +381,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
381
381
  if (decision._tag === "failure") return yield* decision.error;
382
382
  }));
383
383
  const reserveSettlement = Effect.fn("MemorySubmissionLedger.reserveSettlement")((unvalidated) => Effect.gen(function* () {
384
- const request = yield* validate$1(SettlementReservation, "reserveSettlement", unvalidated);
384
+ const request = yield* validate$2(SettlementReservation, "reserveSettlement", unvalidated);
385
385
  const decision = yield* Ref.modify(state, (current) => {
386
386
  const stored = current.submissions.get(request.submissionId);
387
387
  if (stored === void 0) return [failure(ledgerError("reserveSettlement", `Unknown Submission ${request.submissionId}`)), current];
@@ -431,7 +431,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
431
431
  return decision.value;
432
432
  }));
433
433
  const finalizeSettlement = Effect.fn("MemorySubmissionLedger.finalizeSettlement")((unvalidated) => Effect.gen(function* () {
434
- const request = yield* validate$1(SettlementFinalization, "finalizeSettlement", unvalidated);
434
+ const request = yield* validate$2(SettlementFinalization, "finalizeSettlement", unvalidated);
435
435
  const nowMillis = yield* Clock.currentTimeMillis;
436
436
  const decision = yield* Ref.modify(state, (current) => {
437
437
  const stored = current.submissions.get(request.submissionId);
@@ -478,7 +478,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
478
478
  return decision.value;
479
479
  }));
480
480
  const requestAbort = Effect.fn("MemorySubmissionLedger.requestAbort")((unvalidated) => Effect.gen(function* () {
481
- const request = yield* validate$1(AbortCommand, "requestAbort", unvalidated);
481
+ const request = yield* validate$2(AbortCommand, "requestAbort", unvalidated);
482
482
  const nowMillis = yield* Clock.currentTimeMillis;
483
483
  const decision = yield* Ref.modify(state, (current) => {
484
484
  const stored = current.submissions.get(request.submissionId);
@@ -513,13 +513,13 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
513
513
  return decision.value;
514
514
  }));
515
515
  const claimJoining = Effect.fn("MemorySubmissionLedger.claimJoining")((unvalidated) => Effect.gen(function* () {
516
- const request = yield* validate$1(ClaimJoiningRequest, "claimJoining", unvalidated);
516
+ const request = yield* validate$2(ClaimJoiningRequest, "claimJoining", unvalidated);
517
517
  const decision = yield* Ref.modify(state, (current) => {
518
518
  const host = current.submissions.get(request.hostSubmissionId);
519
519
  if (host === void 0) return [failure(ledgerError("claimJoining", `Unknown Submission ${request.hostSubmissionId}`)), current];
520
- if (host.row.conversationId !== request.conversationId) return [failure(ledgerError("claimJoining", `Host Submission ${request.hostSubmissionId} does not belong to Conversation ${request.conversationId}`)), current];
520
+ if (host.row.threadId !== request.threadId) return [failure(ledgerError("claimJoining", `Host Submission ${request.hostSubmissionId} does not belong to Thread ${request.threadId}`)), current];
521
521
  if (!ownsLane(host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
522
- const later = [...current.submissions.values()].filter((stored) => stored.row.conversationId === request.conversationId && stored.row.queueSequence > host.row.queueSequence).sort((left, right) => left.row.queueSequence - right.row.queueSequence);
522
+ const later = [...current.submissions.values()].filter((stored) => stored.row.threadId === request.threadId && stored.row.queueSequence > host.row.queueSequence).sort((left, right) => left.row.queueSequence - right.row.queueSequence);
523
523
  const claims = [];
524
524
  const submissions = new Map(current.submissions);
525
525
  for (const stored of later) {
@@ -550,7 +550,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
550
550
  return decision.value;
551
551
  }));
552
552
  const markJoined = Effect.fn("MemorySubmissionLedger.markJoined")((unvalidated) => Effect.gen(function* () {
553
- const request = yield* validate$1(MarkJoinedRequest, "markJoined", unvalidated);
553
+ const request = yield* validate$2(MarkJoinedRequest, "markJoined", unvalidated);
554
554
  const decision = yield* Ref.modify(state, (current) => {
555
555
  const stored = current.submissions.get(request.submissionId);
556
556
  if (stored === void 0) return [failure(ledgerError("markJoined", `Unknown Submission ${request.submissionId}`)), current];
@@ -579,7 +579,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
579
579
  if (decision._tag === "failure") return yield* decision.error;
580
580
  }));
581
581
  const revertJoining = Effect.fn("MemorySubmissionLedger.revertJoining")((unvalidated) => Effect.gen(function* () {
582
- const request = yield* validate$1(RevertJoiningRequest, "revertJoining", unvalidated);
582
+ const request = yield* validate$2(RevertJoiningRequest, "revertJoining", unvalidated);
583
583
  const decision = yield* Ref.modify(state, (current) => {
584
584
  const stored = current.submissions.get(request.submissionId);
585
585
  if (stored === void 0) return [failure(ledgerError("revertJoining", `Unknown Submission ${request.submissionId}`)), current];
@@ -596,7 +596,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
596
596
  if (decision._tag === "failure") return yield* decision.error;
597
597
  }));
598
598
  const suspend = Effect.fn("MemorySubmissionLedger.suspend")((unvalidated) => Effect.gen(function* () {
599
- const request = yield* validate$1(SuspendRequest, "suspend", unvalidated);
599
+ const request = yield* validate$2(SuspendRequest, "suspend", unvalidated);
600
600
  const nowMillis = yield* Clock.currentTimeMillis;
601
601
  const decision = yield* Ref.modify(state, (current) => {
602
602
  const stored = current.submissions.get(request.submissionId);
@@ -631,7 +631,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
631
631
  return decision.value;
632
632
  }));
633
633
  const recordApprovalDecision = Effect.fn("MemorySubmissionLedger.recordApprovalDecision")((unvalidated) => Effect.gen(function* () {
634
- const command = yield* validate$1(ApprovalDecisionCommand, "recordApprovalDecision", unvalidated);
634
+ const command = yield* validate$2(ApprovalDecisionCommand, "recordApprovalDecision", unvalidated);
635
635
  const nowMillis = yield* Clock.currentTimeMillis;
636
636
  const decision = yield* Ref.modify(state, (current) => {
637
637
  const stored = current.submissions.get(command.submissionId);
@@ -676,7 +676,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
676
676
  return decision.value;
677
677
  }));
678
678
  const markUnknown = Effect.fn("MemorySubmissionLedger.markUnknown")((unvalidated) => Effect.gen(function* () {
679
- const request = yield* validate$1(MarkUnknownRequest, "markUnknown", unvalidated);
679
+ const request = yield* validate$2(MarkUnknownRequest, "markUnknown", unvalidated);
680
680
  const decision = yield* Ref.modify(state, (current) => {
681
681
  const stored = current.submissions.get(request.submissionId);
682
682
  if (stored === void 0) return [failure(ledgerError("markUnknown", `Unknown Submission ${request.submissionId}`)), current];
@@ -709,7 +709,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
709
709
  if (decision._tag === "failure") return yield* decision.error;
710
710
  }));
711
711
  const recordUnknownResolution = Effect.fn("MemorySubmissionLedger.recordUnknownResolution")((unvalidated) => Effect.gen(function* () {
712
- const command = yield* validate$1(UnknownResolutionCommand, "recordUnknownResolution", unvalidated);
712
+ const command = yield* validate$2(UnknownResolutionCommand, "recordUnknownResolution", unvalidated);
713
713
  const nowMillis = yield* Clock.currentTimeMillis;
714
714
  const decision = yield* Ref.modify(state, (current) => {
715
715
  const stored = current.submissions.get(command.submissionId);
@@ -750,7 +750,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
750
750
  return decision.value;
751
751
  }));
752
752
  const recordChildSettled = Effect.fn("MemorySubmissionLedger.recordChildSettled")((unvalidated) => Effect.gen(function* () {
753
- const request = yield* validate$1(ChildSettledNotification, "recordChildSettled", unvalidated);
753
+ const request = yield* validate$2(ChildSettledNotification, "recordChildSettled", unvalidated);
754
754
  const decision = yield* Ref.modify(state, (current) => {
755
755
  const parent = current.submissions.get(request.parentSubmissionId);
756
756
  if (parent === void 0) return [failure(ledgerError("recordChildSettled", `Unknown Submission ${request.parentSubmissionId}`)), current];
@@ -776,7 +776,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
776
776
  return decision.value;
777
777
  }));
778
778
  const reserveChildBudget = Effect.fn("MemorySubmissionLedger.reserveChildBudget")((unvalidated) => Effect.gen(function* () {
779
- const request = yield* validate$1(ChildBudgetReservationRequest, "reserveChildBudget", unvalidated);
779
+ const request = yield* validate$2(ChildBudgetReservationRequest, "reserveChildBudget", unvalidated);
780
780
  const nowMillis = yield* Clock.currentTimeMillis;
781
781
  const decision = yield* Ref.modify(state, (current) => {
782
782
  const existing = current.childReservations.get(request.reservationId);
@@ -821,7 +821,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
821
821
  return decision.value;
822
822
  }));
823
823
  const attachChildToReservation = Effect.fn("MemorySubmissionLedger.attachChildToReservation")((unvalidated) => Effect.gen(function* () {
824
- const request = yield* validate$1(AttachChildToReservationRequest, "attachChildToReservation", unvalidated);
824
+ const request = yield* validate$2(AttachChildToReservationRequest, "attachChildToReservation", unvalidated);
825
825
  const decision = yield* Ref.modify(state, (current) => {
826
826
  const reservation = current.childReservations.get(request.reservationId);
827
827
  if (reservation === void 0) return [failure(ledgerError("attachChildToReservation", `Unknown child reservation ${request.reservationId}`)), current];
@@ -852,7 +852,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
852
852
  return decision.value;
853
853
  }));
854
854
  const beginChildBudgetRelease = Effect.fn("MemorySubmissionLedger.beginChildBudgetRelease")((unvalidated) => Effect.gen(function* () {
855
- const request = yield* validate$1(BeginChildBudgetReleaseRequest, "beginChildBudgetRelease", unvalidated);
855
+ const request = yield* validate$2(BeginChildBudgetReleaseRequest, "beginChildBudgetRelease", unvalidated);
856
856
  const nowMillis = yield* Clock.currentTimeMillis;
857
857
  const decision = yield* Ref.modify(state, (current) => {
858
858
  const reservation = current.childReservations.get(request.reservationId);
@@ -877,7 +877,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
877
877
  return decision.value;
878
878
  }));
879
879
  const releaseChildBudget = Effect.fn("MemorySubmissionLedger.releaseChildBudget")((unvalidated) => Effect.gen(function* () {
880
- const request = yield* validate$1(ReleaseChildBudgetRequest, "releaseChildBudget", unvalidated);
880
+ const request = yield* validate$2(ReleaseChildBudgetRequest, "releaseChildBudget", unvalidated);
881
881
  const nowMillis = yield* Clock.currentTimeMillis;
882
882
  const decision = yield* Ref.modify(state, (current) => {
883
883
  const reservation = current.childReservations.get(request.reservationId);
@@ -899,11 +899,11 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
899
899
  return decision.value;
900
900
  }));
901
901
  const scanNonterminal = Stream.unwrap(Ref.get(state).pipe(Effect.map((current) => {
902
- const snapshots = [...current.submissions.values()].filter((stored) => stored.row.state !== "settled").sort((left, right) => left.row.conversationId < right.row.conversationId ? -1 : left.row.conversationId > right.row.conversationId ? 1 : left.row.queueSequence - right.row.queueSequence).map((stored) => toSnapshot(stored.row));
902
+ const snapshots = [...current.submissions.values()].filter((stored) => stored.row.state !== "settled").sort((left, right) => left.row.threadId < right.row.threadId ? -1 : left.row.threadId > right.row.threadId ? 1 : left.row.queueSequence - right.row.queueSequence).map((stored) => toSnapshot(stored.row));
903
903
  return Stream.fromIterable(snapshots);
904
904
  })));
905
905
  const loadRecoverySnapshot = Effect.fn("MemorySubmissionLedger.loadRecoverySnapshot")((unvalidated) => Effect.gen(function* () {
906
- const request = yield* validate$1(RecoverySnapshotRequest, "loadRecoverySnapshot", unvalidated);
906
+ const request = yield* validate$2(RecoverySnapshotRequest, "loadRecoverySnapshot", unvalidated);
907
907
  const current = yield* Ref.get(state);
908
908
  const stored = current.submissions.get(request.submissionId);
909
909
  if (stored === void 0) return yield* ledgerError("loadRecoverySnapshot", `Unknown Submission ${request.submissionId}`);
@@ -1150,77 +1150,667 @@ const makeScheduleStore = Effect.gen(function* () {
1150
1150
  const memoryScheduleStoreLayer = () => Layer.effect(ScheduleStore, makeScheduleStore);
1151
1151
  const MemoryScheduleStoreLive = memoryScheduleStoreLayer();
1152
1152
  //#endregion
1153
+ //#region src/memory-subscription-store.ts
1154
+ const error = (reason, code) => SubscriptionError.make({
1155
+ reason,
1156
+ code
1157
+ });
1158
+ const samePartition = (left, right) => left.tenantId === right.tenantId && left.address === right.address;
1159
+ const sameSource = (left, right) => left.name === right.name && left.version === right.version;
1160
+ const encode = (schema, value, code) => Result.try({
1161
+ try: () => Schema.encodeSync(Schema.fromJsonString(schema))(value),
1162
+ catch: () => error("corrupt", code)
1163
+ });
1164
+ const decode = (schema, value, code) => Result.try({
1165
+ try: () => Schema.decodeSync(Schema.fromJsonString(schema))(value),
1166
+ catch: () => error("corrupt", code)
1167
+ });
1168
+ const decodeEffect = (schema, value, code) => Effect.fromResult(decode(schema, value, code));
1169
+ const validate$1 = (schema, value, code) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => error("validation", code)));
1170
+ const jsonBytes = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
1171
+ const sameDeliveryIdentity = (left, right) => subscriptionDeliveryKeyString(left.key) === subscriptionDeliveryKeyString(right.key) && left.deliveryId === right.deliveryId && left.source.name === right.source.name && left.source.version === right.source.version && left.threadId === right.threadId && left.admissionKey === right.admissionKey && left.subscriptionFingerprint === right.subscriptionFingerprint && left.eventDigest === right.eventDigest;
1172
+ const candidateIndexKey = (record) => JSON.stringify([
1173
+ record.configuration.source.name,
1174
+ record.configuration.source.version,
1175
+ record.configuration.matchingKey
1176
+ ]);
1177
+ const eventCandidateIndexKey = (event) => JSON.stringify([
1178
+ event.source.name,
1179
+ event.source.version,
1180
+ event.matchingKey
1181
+ ]);
1182
+ const sameEventIdentity = (left, right) => samePartition(left.partition, right.partition) && left.eventId === right.eventId && sameSource(left.source, right.source) && left.matchingKey === right.matchingKey && left.payloadDigest === right.payloadDigest;
1183
+ const deliveryBelongsTo = (delivery, record, event) => subscriptionKeyString(delivery.key.subscription) === subscriptionKeyString(record.key) && delivery.key.eventId === event.eventId && sameSource(delivery.source, event.source);
1184
+ const makeMemorySubscriptionStore = Effect.fn("makeMemorySubscriptionStore")(function* (ownedPartition) {
1185
+ const partition = yield* validate$1(SourcePartition, ownedPartition, "partition");
1186
+ const state = yield* Ref.make({
1187
+ sequence: 0,
1188
+ registrations: /* @__PURE__ */ new Map(),
1189
+ events: /* @__PURE__ */ new Map(),
1190
+ deliveries: /* @__PURE__ */ new Map(),
1191
+ registrationIndex: /* @__PURE__ */ new Map(),
1192
+ candidateIndex: /* @__PURE__ */ new Map(),
1193
+ ownerRegistrationCounts: /* @__PURE__ */ new Map(),
1194
+ eventIndex: /* @__PURE__ */ new Map(),
1195
+ deliveryIndex: /* @__PURE__ */ new Map(),
1196
+ ownerDeliveryCounts: /* @__PURE__ */ new Map(),
1197
+ scanCursors: {
1198
+ events: "",
1199
+ deliveries: "",
1200
+ recovery: 0
1201
+ }
1202
+ });
1203
+ const failpoint = yield* SubscriptionFailpoint;
1204
+ const requirePartition = (value, code) => samePartition(value.partition, partition) ? Effect.succeed(value) : Effect.fail(error("validation", code));
1205
+ const requireKey = (key, code) => validate$1(SubscriptionKey, key, code).pipe(Effect.flatMap((decoded) => requirePartition(decoded, code)));
1206
+ const register = Effect.fn("MemorySubscriptionStore.register")(function* (input, inputLimits) {
1207
+ const record = yield* validate$1(SubscriptionRecord, input, "register-record");
1208
+ const limits = yield* validate$1(SubscriptionLimits, inputLimits, "register-limits");
1209
+ yield* requirePartition(record.key, "register-partition");
1210
+ yield* failpoint.hit("subscription:register:before");
1211
+ const result = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1212
+ const key = subscriptionKeyString(record.key);
1213
+ const existingText = current.registrations.get(key);
1214
+ if (existingText !== void 0) {
1215
+ const existing = decode(SubscriptionRecord, existingText, "register-existing");
1216
+ if (Result.isFailure(existing)) return [Result.fail(existing.failure), current];
1217
+ return existing.success.creationFingerprint === record.creationFingerprint ? [Result.succeed(existing.success), current] : [Result.fail(error("conflict", "registration-identity")), current];
1218
+ }
1219
+ if (jsonBytes(record.configuration.context) > limits.maxContextBytes) return [Result.fail(error("capacity", "context-bytes")), current];
1220
+ if (jsonBytes(record.configuration.parameters) > limits.maxPayloadBytes) return [Result.fail(error("capacity", "parameters-bytes")), current];
1221
+ if (record.configuration.expiresAtMillis - record.createdAtMillis > limits.maxLifetimeMillis) return [Result.fail(error("capacity", "lifetime")), current];
1222
+ if (current.registrations.size >= limits.maxRegistrations) return [Result.fail(error("capacity", "registrations")), current];
1223
+ const ownerCount = current.ownerRegistrationCounts.get(record.key.ownerId) ?? 0;
1224
+ if (ownerCount >= limits.maxRegistrationsPerOwner) return [Result.fail(error("capacity", "owner-registrations")), current];
1225
+ const assigned = {
1226
+ ...record,
1227
+ ordinal: current.sequence + 1
1228
+ };
1229
+ const encoded = encode(SubscriptionRecord, assigned, "register-encode");
1230
+ if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1231
+ const registrations = new Map(current.registrations);
1232
+ registrations.set(key, encoded.success);
1233
+ const registrationIndex = new Map(current.registrationIndex);
1234
+ registrationIndex.set(key, {
1235
+ key: assigned.key,
1236
+ ordinal: assigned.ordinal,
1237
+ state: assigned.state,
1238
+ recoveryAt: assigned.recovery?.nextAttemptAtMillis ?? null
1239
+ });
1240
+ const candidateIndex = new Map(current.candidateIndex);
1241
+ const candidateKey = candidateIndexKey(assigned);
1242
+ candidateIndex.set(candidateKey, [...candidateIndex.get(candidateKey) ?? [], key]);
1243
+ const ownerRegistrationCounts = new Map(current.ownerRegistrationCounts);
1244
+ ownerRegistrationCounts.set(assigned.key.ownerId, ownerCount + 1);
1245
+ return [Result.succeed(assigned), {
1246
+ ...current,
1247
+ sequence: assigned.ordinal,
1248
+ registrations,
1249
+ registrationIndex,
1250
+ candidateIndex,
1251
+ ownerRegistrationCounts
1252
+ }];
1253
+ })).pipe(Effect.flatMap(Effect.fromResult));
1254
+ yield* failpoint.hit("subscription:register:after");
1255
+ return result;
1256
+ });
1257
+ const get = Effect.fn("MemorySubscriptionStore.get")(function* (input) {
1258
+ const key = yield* requireKey(input, "get-key");
1259
+ const text = (yield* Ref.get(state)).registrations.get(subscriptionKeyString(key));
1260
+ return text === void 0 ? null : yield* decodeEffect(SubscriptionRecord, text, "get-record");
1261
+ });
1262
+ const list = Effect.fn("MemorySubscriptionStore.list")(function* (ownerId, after, limit) {
1263
+ if (!Number.isSafeInteger(after) || after < 0 || !Number.isSafeInteger(limit) || limit <= 0) return yield* error("validation", "list-page");
1264
+ const current = yield* Ref.get(state);
1265
+ const records = [];
1266
+ for (const [storageKey, indexed] of current.registrationIndex) {
1267
+ if (indexed.key.ownerId !== ownerId || indexed.ordinal <= after) continue;
1268
+ const text = current.registrations.get(storageKey);
1269
+ if (text === void 0) return yield* error("corrupt", "list-index");
1270
+ records.push(yield* decodeEffect(SubscriptionRecord, text, "list-record"));
1271
+ }
1272
+ records.sort((a, b) => a.ordinal - b.ordinal);
1273
+ return records.slice(0, limit);
1274
+ });
1275
+ const cancel = Effect.fn("MemorySubscriptionStore.cancel")(function* (input) {
1276
+ const key = yield* requireKey(input, "cancel-key");
1277
+ yield* failpoint.hit("subscription:cancel:before");
1278
+ const result = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1279
+ const storageKey = subscriptionKeyString(key);
1280
+ const text = current.registrations.get(storageKey);
1281
+ if (text === void 0) return [Result.fail(error("not-found", "subscription")), current];
1282
+ const decoded = decode(SubscriptionRecord, text, "cancel-record");
1283
+ if (Result.isFailure(decoded)) return [decoded, current];
1284
+ if (decoded.success.state === "cancelled") return [Result.succeed(decoded.success), current];
1285
+ const cancelled = {
1286
+ ...decoded.success,
1287
+ state: "cancelled",
1288
+ recovery: null
1289
+ };
1290
+ const encoded = encode(SubscriptionRecord, cancelled, "cancel-encode");
1291
+ if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1292
+ const registrations = new Map(current.registrations);
1293
+ registrations.set(storageKey, encoded.success);
1294
+ const registrationIndex = new Map(current.registrationIndex);
1295
+ const indexed = registrationIndex.get(storageKey);
1296
+ if (indexed === void 0) return [Result.fail(error("corrupt", "cancel-index")), current];
1297
+ registrationIndex.set(storageKey, {
1298
+ ...indexed,
1299
+ state: "cancelled",
1300
+ recoveryAt: null
1301
+ });
1302
+ return [Result.succeed(cancelled), {
1303
+ ...current,
1304
+ registrations,
1305
+ registrationIndex
1306
+ }];
1307
+ })).pipe(Effect.flatMap(Effect.fromResult));
1308
+ yield* failpoint.hit("subscription:cancel:after");
1309
+ return result;
1310
+ });
1311
+ const accept = Effect.fn("MemorySubscriptionStore.accept")(function* (input, inputLimits) {
1312
+ const event = yield* validate$1(AcceptedEvent, input, "accept-event");
1313
+ const limits = yield* validate$1(SubscriptionLimits, inputLimits, "accept-limits");
1314
+ yield* requirePartition(event, "accept-partition");
1315
+ yield* failpoint.hit("subscription:accept:before");
1316
+ const result = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1317
+ const existingText = current.events.get(event.eventId);
1318
+ if (existingText !== void 0) {
1319
+ const existing = decode(AcceptedEvent, existingText, "accept-existing");
1320
+ if (Result.isFailure(existing)) return [Result.fail(existing.failure), current];
1321
+ return sameEventIdentity(existing.success, event) ? [Result.succeed(existing.success), current] : [Result.fail(error("conflict", "event-identity")), current];
1322
+ }
1323
+ if (jsonBytes(event.payload) > limits.maxPayloadBytes) return [Result.fail(error("capacity", "payload-bytes")), current];
1324
+ if (current.events.size >= limits.maxEvents) return [Result.fail(error("capacity", "events")), current];
1325
+ const accepted = {
1326
+ ...event,
1327
+ cutoff: current.sequence + 1,
1328
+ cursor: 0,
1329
+ routingComplete: false,
1330
+ routingFailure: null
1331
+ };
1332
+ const encoded = encode(AcceptedEvent, accepted, "accept-encode");
1333
+ if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1334
+ const events = new Map(current.events);
1335
+ events.set(accepted.eventId, encoded.success);
1336
+ const eventIndex = new Map(current.eventIndex);
1337
+ eventIndex.set(accepted.eventId, {
1338
+ routingComplete: false,
1339
+ nextAttemptAtMillis: accepted.nextAttemptAtMillis
1340
+ });
1341
+ return [Result.succeed(accepted), {
1342
+ ...current,
1343
+ sequence: accepted.cutoff,
1344
+ events,
1345
+ eventIndex
1346
+ }];
1347
+ })).pipe(Effect.flatMap(Effect.fromResult));
1348
+ yield* failpoint.hit("subscription:accept:after");
1349
+ return result;
1350
+ });
1351
+ const event = Effect.fn("MemorySubscriptionStore.event")(function* (eventId) {
1352
+ const text = (yield* Ref.get(state)).events.get(eventId);
1353
+ return text === void 0 ? null : yield* decodeEffect(AcceptedEvent, text, "event-record");
1354
+ });
1355
+ const pendingEvents = Effect.fn("MemorySubscriptionStore.pendingEvents")(function* (nowMillis, after, limit) {
1356
+ const events = [];
1357
+ for (const [eventId, indexed] of (yield* Ref.get(state)).eventIndex) if (!indexed.routingComplete && indexed.nextAttemptAtMillis <= nowMillis && compareScheduleNames(eventId, after) > 0) events.push(eventId);
1358
+ events.sort(compareScheduleNames);
1359
+ return events.slice(0, limit);
1360
+ });
1361
+ const candidates = Effect.fn("MemorySubscriptionStore.candidates")(function* (input, limit) {
1362
+ const accepted = yield* validate$1(AcceptedEvent, input, "candidates-event");
1363
+ yield* requirePartition(accepted, "candidates-partition");
1364
+ const stored = yield* event(accepted.eventId);
1365
+ if (stored === null || !sameEventIdentity(stored, accepted)) return yield* error(stored === null ? "not-found" : "conflict", "event");
1366
+ const current = yield* Ref.get(state);
1367
+ const records = [];
1368
+ for (const storageKey of current.candidateIndex.get(eventCandidateIndexKey(stored)) ?? []) {
1369
+ const indexed = current.registrationIndex.get(storageKey);
1370
+ if (indexed === void 0) return yield* error("corrupt", "candidate-index");
1371
+ if (indexed.ordinal <= stored.cursor || indexed.ordinal > stored.cutoff) continue;
1372
+ const text = current.registrations.get(storageKey);
1373
+ if (text === void 0) return yield* error("corrupt", "candidate-record");
1374
+ records.push(yield* decodeEffect(SubscriptionRecord, text, "candidate-record"));
1375
+ }
1376
+ records.sort((a, b) => a.ordinal - b.ordinal);
1377
+ return records.slice(0, limit);
1378
+ });
1379
+ const select = Effect.fn("MemorySubscriptionStore.select")(function* (inputEvent, inputDeliveries, cursor, complete, nowMillis, inputLimits) {
1380
+ const suppliedEvent = yield* validate$1(AcceptedEvent, inputEvent, "select-event");
1381
+ const deliveries = yield* validate$1(Schema.Array(SubscriptionDelivery), inputDeliveries, "select-deliveries");
1382
+ const limits = yield* validate$1(SubscriptionLimits, inputLimits, "select-limits");
1383
+ yield* requirePartition(suppliedEvent, "select-partition");
1384
+ yield* failpoint.hit("subscription:select:before");
1385
+ const effectiveNowMillis = Math.max(nowMillis, yield* Clock.currentTimeMillis);
1386
+ yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1387
+ const eventText = current.events.get(suppliedEvent.eventId);
1388
+ if (eventText === void 0) return [Result.fail(error("not-found", "event")), current];
1389
+ const decodedEvent = decode(AcceptedEvent, eventText, "select-event-record");
1390
+ if (Result.isFailure(decodedEvent)) return [Result.fail(decodedEvent.failure), current];
1391
+ const accepted = decodedEvent.success;
1392
+ if (!sameEventIdentity(accepted, suppliedEvent) || suppliedEvent.cursor !== accepted.cursor) return [Result.fail(error("conflict", "event-cursor")), current];
1393
+ if (accepted.routingComplete) return [complete && cursor === accepted.cursor ? Result.void : Result.fail(error("conflict", "routing-complete")), current];
1394
+ if (!Number.isSafeInteger(cursor) || cursor < accepted.cursor || cursor > accepted.cutoff) return [Result.fail(error("validation", "cursor")), current];
1395
+ const additions = [];
1396
+ const updates = [];
1397
+ const additionIndex = [];
1398
+ const registrationUpdates = [];
1399
+ const owners = new Map(current.ownerDeliveryCounts);
1400
+ for (const delivery of deliveries) {
1401
+ const recordText = current.registrations.get(subscriptionKeyString(delivery.key.subscription));
1402
+ if (recordText === void 0) return [Result.fail(error("not-found", "subscription")), current];
1403
+ const record = decode(SubscriptionRecord, recordText, "select-registration");
1404
+ if (Result.isFailure(record)) return [Result.fail(record.failure), current];
1405
+ if (!deliveryBelongsTo(delivery, record.success, accepted) || !subscriptionDeliveryCanSelect(delivery, record.success, accepted) || record.success.ordinal <= accepted.cursor || record.success.ordinal > cursor) return [Result.fail(error("conflict", "selection")), current];
1406
+ if (!subscriptionCanSelect(record.success, accepted, effectiveNowMillis, false)) continue;
1407
+ const deliveryKey = subscriptionDeliveryKeyString(delivery.key);
1408
+ const existingText = current.deliveries.get(deliveryKey);
1409
+ if (existingText !== void 0) {
1410
+ const existing = decode(SubscriptionDelivery, existingText, "select-existing");
1411
+ if (Result.isFailure(existing)) return [Result.fail(existing.failure), current];
1412
+ if (!sameDeliveryIdentity(existing.success, delivery)) return [Result.fail(error("conflict", "delivery-identity")), current];
1413
+ continue;
1414
+ }
1415
+ const encodedDelivery = encode(SubscriptionDelivery, delivery, "select-delivery-encode");
1416
+ if (Result.isFailure(encodedDelivery)) return [Result.fail(encodedDelivery.failure), current];
1417
+ additions.push([deliveryKey, encodedDelivery.success]);
1418
+ additionIndex.push([
1419
+ deliveryKey,
1420
+ {
1421
+ key: delivery.key,
1422
+ state: delivery.state,
1423
+ nextAttemptAtMillis: delivery.retry.nextAttemptAtMillis
1424
+ },
1425
+ record.success.key.ownerId
1426
+ ]);
1427
+ const ownerId = record.success.key.ownerId;
1428
+ owners.set(ownerId, (owners.get(ownerId) ?? 0) + 1);
1429
+ if ((owners.get(ownerId) ?? 0) > limits.maxDeliveriesPerOwner) return [Result.fail(error("capacity", "owner-deliveries")), current];
1430
+ if (record.success.configuration.mode === "once") {
1431
+ const consumed = {
1432
+ ...record.success,
1433
+ state: "consumed",
1434
+ recovery: null
1435
+ };
1436
+ const encodedRecord = encode(SubscriptionRecord, consumed, "select-registration-encode");
1437
+ if (Result.isFailure(encodedRecord)) return [Result.fail(encodedRecord.failure), current];
1438
+ const consumedKey = subscriptionKeyString(consumed.key);
1439
+ updates.push([consumedKey, encodedRecord.success]);
1440
+ const indexed = current.registrationIndex.get(consumedKey);
1441
+ if (indexed === void 0) return [Result.fail(error("corrupt", "selection-index")), current];
1442
+ registrationUpdates.push([consumedKey, {
1443
+ ...indexed,
1444
+ state: "consumed",
1445
+ recoveryAt: null
1446
+ }]);
1447
+ }
1448
+ }
1449
+ if (current.deliveries.size + additions.length > limits.maxDeliveries) return [Result.fail(error("capacity", "deliveries")), current];
1450
+ const registrations = new Map(current.registrations);
1451
+ for (const [key, value] of updates) registrations.set(key, value);
1452
+ const nextDeliveries = new Map(current.deliveries);
1453
+ for (const [key, value] of additions) nextDeliveries.set(key, value);
1454
+ const deliveryIndex = new Map(current.deliveryIndex);
1455
+ for (const [key, value] of additionIndex) deliveryIndex.set(key, value);
1456
+ const registrationIndex = new Map(current.registrationIndex);
1457
+ for (const [key, value] of registrationUpdates) registrationIndex.set(key, value);
1458
+ const nextEvent = {
1459
+ ...accepted,
1460
+ cursor,
1461
+ routingComplete: complete,
1462
+ routingFailure: null
1463
+ };
1464
+ const encodedEvent = encode(AcceptedEvent, nextEvent, "select-event-encode");
1465
+ if (Result.isFailure(encodedEvent)) return [Result.fail(encodedEvent.failure), current];
1466
+ const events = new Map(current.events);
1467
+ events.set(nextEvent.eventId, encodedEvent.success);
1468
+ const eventIndex = new Map(current.eventIndex);
1469
+ eventIndex.set(nextEvent.eventId, {
1470
+ routingComplete: nextEvent.routingComplete,
1471
+ nextAttemptAtMillis: nextEvent.nextAttemptAtMillis
1472
+ });
1473
+ return [Result.void, {
1474
+ ...current,
1475
+ registrations,
1476
+ registrationIndex,
1477
+ events,
1478
+ eventIndex,
1479
+ deliveries: nextDeliveries,
1480
+ deliveryIndex,
1481
+ ownerDeliveryCounts: owners
1482
+ }];
1483
+ })).pipe(Effect.flatMap(Effect.fromResult));
1484
+ yield* failpoint.hit("subscription:select:after");
1485
+ });
1486
+ const catchUp = Effect.fn("MemorySubscriptionStore.catchUp")(function* (inputEvent, inputDelivery, nowMillis, inputLimits) {
1487
+ const suppliedEvent = yield* validate$1(AcceptedEvent, inputEvent, "catch-up-event");
1488
+ const delivery = yield* validate$1(SubscriptionDelivery, inputDelivery, "catch-up-delivery");
1489
+ const limits = yield* validate$1(SubscriptionLimits, inputLimits, "catch-up-limits");
1490
+ yield* requirePartition(suppliedEvent, "catch-up-partition");
1491
+ yield* failpoint.hit("subscription:catch-up:before");
1492
+ const effectiveNowMillis = Math.max(nowMillis, yield* Clock.currentTimeMillis);
1493
+ yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1494
+ const eventText = current.events.get(suppliedEvent.eventId);
1495
+ const recordText = current.registrations.get(subscriptionKeyString(delivery.key.subscription));
1496
+ if (eventText === void 0 || recordText === void 0) return [Result.fail(error("not-found", eventText === void 0 ? "event" : "subscription")), current];
1497
+ const accepted = decode(AcceptedEvent, eventText, "catch-up-event-record");
1498
+ if (Result.isFailure(accepted)) return [Result.fail(accepted.failure), current];
1499
+ const record = decode(SubscriptionRecord, recordText, "catch-up-registration");
1500
+ if (Result.isFailure(record)) return [Result.fail(record.failure), current];
1501
+ if (!sameEventIdentity(accepted.success, suppliedEvent) || !deliveryBelongsTo(delivery, record.success, accepted.success) || !subscriptionDeliveryCanSelect(delivery, record.success, accepted.success) || record.success.configuration.mode !== "once") return [Result.fail(error("conflict", "catch-up-identity")), current];
1502
+ const key = subscriptionDeliveryKeyString(delivery.key);
1503
+ const existingText = current.deliveries.get(key);
1504
+ if (existingText !== void 0) {
1505
+ const existing = decode(SubscriptionDelivery, existingText, "catch-up-existing");
1506
+ if (Result.isFailure(existing)) return [Result.fail(existing.failure), current];
1507
+ return sameDeliveryIdentity(existing.success, delivery) ? [Result.void, current] : [Result.fail(error("conflict", "delivery-identity")), current];
1508
+ }
1509
+ if (!subscriptionCanSelect(record.success, accepted.success, effectiveNowMillis, true)) return [Result.fail(error("conflict", "catch-up-eligibility")), current];
1510
+ if (current.deliveries.size >= limits.maxDeliveries) return [Result.fail(error("capacity", "deliveries")), current];
1511
+ const ownerCount = current.ownerDeliveryCounts.get(record.success.key.ownerId) ?? 0;
1512
+ if (ownerCount >= limits.maxDeliveriesPerOwner) return [Result.fail(error("capacity", "owner-deliveries")), current];
1513
+ const encodedDelivery = encode(SubscriptionDelivery, delivery, "catch-up-delivery-encode");
1514
+ if (Result.isFailure(encodedDelivery)) return [Result.fail(encodedDelivery.failure), current];
1515
+ const consumed = {
1516
+ ...record.success,
1517
+ state: "consumed",
1518
+ recovery: null
1519
+ };
1520
+ const encodedRecord = encode(SubscriptionRecord, consumed, "catch-up-registration-encode");
1521
+ if (Result.isFailure(encodedRecord)) return [Result.fail(encodedRecord.failure), current];
1522
+ const deliveries = new Map(current.deliveries);
1523
+ deliveries.set(key, encodedDelivery.success);
1524
+ const registrations = new Map(current.registrations);
1525
+ const consumedKey = subscriptionKeyString(consumed.key);
1526
+ registrations.set(consumedKey, encodedRecord.success);
1527
+ const registrationIndex = new Map(current.registrationIndex);
1528
+ const indexed = registrationIndex.get(consumedKey);
1529
+ if (indexed === void 0) return [Result.fail(error("corrupt", "catch-up-index")), current];
1530
+ registrationIndex.set(consumedKey, {
1531
+ ...indexed,
1532
+ state: "consumed",
1533
+ recoveryAt: null
1534
+ });
1535
+ const deliveryIndex = new Map(current.deliveryIndex);
1536
+ deliveryIndex.set(key, {
1537
+ key: delivery.key,
1538
+ state: delivery.state,
1539
+ nextAttemptAtMillis: delivery.retry.nextAttemptAtMillis
1540
+ });
1541
+ const ownerDeliveryCounts = new Map(current.ownerDeliveryCounts);
1542
+ ownerDeliveryCounts.set(consumed.key.ownerId, ownerCount + 1);
1543
+ return [Result.void, {
1544
+ ...current,
1545
+ deliveries,
1546
+ deliveryIndex,
1547
+ ownerDeliveryCounts,
1548
+ registrations,
1549
+ registrationIndex
1550
+ }];
1551
+ })).pipe(Effect.flatMap(Effect.fromResult));
1552
+ yield* failpoint.hit("subscription:catch-up:after");
1553
+ });
1554
+ const deferEvent = Effect.fn("MemorySubscriptionStore.deferEvent")(function* (eventId, nextAttemptAtMillis, code) {
1555
+ const routingFailure = code === void 0 ? "routing-failed" : yield* validate$1(SubscriptionName, code, "routing-failure");
1556
+ yield* failpoint.hit("subscription:defer-event:before");
1557
+ yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1558
+ const text = current.events.get(eventId);
1559
+ if (text === void 0) return [Result.fail(error("not-found", "event")), current];
1560
+ const accepted = decode(AcceptedEvent, text, "defer-event-record");
1561
+ if (Result.isFailure(accepted)) return [Result.fail(accepted.failure), current];
1562
+ const updated = {
1563
+ ...accepted.success,
1564
+ nextAttemptAtMillis,
1565
+ routingFailure
1566
+ };
1567
+ const encoded = encode(AcceptedEvent, updated, "defer-event-encode");
1568
+ if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1569
+ const events = new Map(current.events);
1570
+ events.set(eventId, encoded.success);
1571
+ const eventIndex = new Map(current.eventIndex);
1572
+ const indexed = eventIndex.get(eventId);
1573
+ if (indexed === void 0) return [Result.fail(error("corrupt", "defer-event-index")), current];
1574
+ eventIndex.set(eventId, {
1575
+ ...indexed,
1576
+ nextAttemptAtMillis
1577
+ });
1578
+ return [Result.void, {
1579
+ ...current,
1580
+ events,
1581
+ eventIndex
1582
+ }];
1583
+ })).pipe(Effect.flatMap(Effect.fromResult));
1584
+ yield* failpoint.hit("subscription:defer-event:after");
1585
+ });
1586
+ const delivery = Effect.fn("MemorySubscriptionStore.delivery")(function* (input) {
1587
+ const key = yield* validate$1(SubscriptionDeliveryKey, input, "delivery-key");
1588
+ yield* requirePartition(key.subscription, "delivery-partition");
1589
+ const text = (yield* Ref.get(state)).deliveries.get(subscriptionDeliveryKeyString(key));
1590
+ return text === void 0 ? null : yield* decodeEffect(SubscriptionDelivery, text, "delivery-record");
1591
+ });
1592
+ const pendingDeliveries = Effect.fn("MemorySubscriptionStore.pendingDeliveries")(function* (nowMillis, after, limit) {
1593
+ const items = [];
1594
+ for (const [storageKey, item] of (yield* Ref.get(state)).deliveryIndex) if (item.state !== "delivered" && item.state !== "refused" && item.nextAttemptAtMillis <= nowMillis && compareScheduleNames(storageKey, after) > 0) items.push(item.key);
1595
+ items.sort((a, b) => compareScheduleNames(subscriptionDeliveryKeyString(a), subscriptionDeliveryKeyString(b)));
1596
+ return items.slice(0, limit);
1597
+ });
1598
+ const listDeliveries = Effect.fn("MemorySubscriptionStore.listDeliveries")(function* (input, after, limit) {
1599
+ const key = yield* requireKey(input, "list-deliveries-key");
1600
+ const items = [];
1601
+ for (const text of (yield* Ref.get(state)).deliveries.values()) {
1602
+ const item = yield* decodeEffect(SubscriptionDelivery, text, "list-delivery");
1603
+ const itemKey = subscriptionDeliveryKeyString(item.key);
1604
+ if (subscriptionKeyString(item.key.subscription) === subscriptionKeyString(key) && compareScheduleNames(itemKey, after) > 0) items.push(item);
1605
+ }
1606
+ items.sort((a, b) => compareScheduleNames(subscriptionDeliveryKeyString(a.key), subscriptionDeliveryKeyString(b.key)));
1607
+ return items.slice(0, limit);
1608
+ });
1609
+ const changeDelivery = Effect.fn("MemorySubscriptionStore.changeDelivery")(function* (inputKey, inputDeliveryId, inputChange) {
1610
+ const key = yield* validate$1(SubscriptionDeliveryKey, inputKey, "change-delivery-key");
1611
+ const deliveryId = yield* validate$1(Digest, inputDeliveryId, "change-delivery-id");
1612
+ const change = yield* validate$1(DeliveryChange, inputChange, "change-delivery-change");
1613
+ yield* requirePartition(key.subscription, "change-delivery-partition");
1614
+ yield* failpoint.hit(`subscription:delivery-${change._tag.toLowerCase()}:before`);
1615
+ const effectiveChange = change._tag === "Prepare" ? {
1616
+ ...change,
1617
+ nowMillis: Math.max(change.nowMillis, yield* Clock.currentTimeMillis)
1618
+ } : change;
1619
+ const result = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1620
+ const storageKey = subscriptionDeliveryKeyString(key);
1621
+ const text = current.deliveries.get(storageKey);
1622
+ if (text === void 0) return [Result.fail(error("not-found", "delivery")), current];
1623
+ const decoded = decode(SubscriptionDelivery, text, "change-delivery-record");
1624
+ if (Result.isFailure(decoded)) return [decoded, current];
1625
+ const registrationText = current.registrations.get(subscriptionKeyString(key.subscription));
1626
+ if (registrationText === void 0) return [Result.fail(error("corrupt", "delivery-registration")), current];
1627
+ const registration = decode(SubscriptionRecord, registrationText, "delivery-registration");
1628
+ if (Result.isFailure(registration)) return [Result.fail(registration.failure), current];
1629
+ const transition = applySubscriptionDeliveryChange(decoded.success, registration.success, deliveryId, effectiveChange);
1630
+ if (Result.isFailure(transition)) return [Result.fail(transition.failure), current];
1631
+ if (transition.success === decoded.success) return [Result.succeed(decoded.success), current];
1632
+ const updated = transition.success;
1633
+ const encoded = encode(SubscriptionDelivery, updated, "change-delivery-encode");
1634
+ if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1635
+ const deliveries = new Map(current.deliveries);
1636
+ deliveries.set(storageKey, encoded.success);
1637
+ const deliveryIndex = new Map(current.deliveryIndex);
1638
+ const indexed = deliveryIndex.get(storageKey);
1639
+ if (indexed === void 0) return [Result.fail(error("corrupt", "delivery-index")), current];
1640
+ deliveryIndex.set(storageKey, {
1641
+ ...indexed,
1642
+ state: updated.state,
1643
+ nextAttemptAtMillis: updated.retry.nextAttemptAtMillis
1644
+ });
1645
+ return [Result.succeed(updated), {
1646
+ ...current,
1647
+ deliveries,
1648
+ deliveryIndex
1649
+ }];
1650
+ })).pipe(Effect.flatMap(Effect.fromResult));
1651
+ yield* failpoint.hit(`subscription:delivery-${change._tag.toLowerCase()}:after`);
1652
+ return result;
1653
+ });
1654
+ const recovering = Effect.fn("MemorySubscriptionStore.recovering")(function* (nowMillis, after, limit) {
1655
+ const records = [];
1656
+ for (const item of (yield* Ref.get(state)).registrationIndex.values()) if (item.ordinal > after && item.state === "active" && item.recoveryAt !== null && item.recoveryAt <= nowMillis) records.push({
1657
+ key: item.key,
1658
+ ordinal: item.ordinal
1659
+ });
1660
+ records.sort((a, b) => a.ordinal - b.ordinal);
1661
+ return records.slice(0, limit);
1662
+ });
1663
+ const deferRecovery = Effect.fn("MemorySubscriptionStore.deferRecovery")(function* (input, recovery) {
1664
+ const key = yield* requireKey(input, "defer-recovery-key");
1665
+ yield* failpoint.hit("subscription:defer-recovery:before");
1666
+ yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1667
+ const storageKey = subscriptionKeyString(key);
1668
+ const text = current.registrations.get(storageKey);
1669
+ if (text === void 0) return [Result.fail(error("not-found", "subscription")), current];
1670
+ const record = decode(SubscriptionRecord, text, "defer-recovery-record");
1671
+ if (Result.isFailure(record)) return [Result.fail(record.failure), current];
1672
+ const updated = {
1673
+ ...record.success,
1674
+ recovery: record.success.state === "active" ? recovery : null
1675
+ };
1676
+ const encoded = encode(SubscriptionRecord, updated, "defer-recovery-encode");
1677
+ if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1678
+ const registrations = new Map(current.registrations);
1679
+ registrations.set(storageKey, encoded.success);
1680
+ const registrationIndex = new Map(current.registrationIndex);
1681
+ const indexed = registrationIndex.get(storageKey);
1682
+ if (indexed === void 0) return [Result.fail(error("corrupt", "recovery-index")), current];
1683
+ registrationIndex.set(storageKey, {
1684
+ ...indexed,
1685
+ recoveryAt: updated.recovery?.nextAttemptAtMillis ?? null
1686
+ });
1687
+ return [Result.void, {
1688
+ ...current,
1689
+ registrations,
1690
+ registrationIndex
1691
+ }];
1692
+ })).pipe(Effect.flatMap(Effect.fromResult));
1693
+ yield* failpoint.hit("subscription:defer-recovery:after");
1694
+ });
1695
+ const readScanCursors = Ref.get(state).pipe(Effect.map((current) => current.scanCursors));
1696
+ const advanceScanCursors = Effect.fn("MemorySubscriptionStore.advanceScanCursors")(function* (input) {
1697
+ const cursors = yield* validate$1(SubscriptionScanCursors, input, "scan-cursors");
1698
+ yield* failpoint.hit("subscription:advance-scan-cursors:before");
1699
+ yield* Effect.uninterruptible(Ref.update(state, (current) => ({
1700
+ ...current,
1701
+ scanCursors: cursors
1702
+ })));
1703
+ yield* failpoint.hit("subscription:advance-scan-cursors:after");
1704
+ });
1705
+ const nextDeadline = Effect.gen(function* () {
1706
+ let deadline = null;
1707
+ const current = yield* Ref.get(state);
1708
+ if (current.scanCursors.events !== "" || current.scanCursors.deliveries !== "" || current.scanCursors.recovery !== 0) return 0;
1709
+ const consider = (value) => {
1710
+ if (deadline === null || value < deadline) deadline = value;
1711
+ };
1712
+ for (const accepted of current.eventIndex.values()) if (!accepted.routingComplete) consider(accepted.nextAttemptAtMillis);
1713
+ for (const item of current.deliveryIndex.values()) if (item.state !== "delivered" && item.state !== "refused") consider(item.nextAttemptAtMillis);
1714
+ for (const record of current.registrationIndex.values()) if (record.state === "active" && record.recoveryAt !== null) consider(record.recoveryAt);
1715
+ return deadline;
1716
+ }).pipe(Effect.withSpan("MemorySubscriptionStore.nextDeadline"));
1717
+ return SubscriptionStore.of({
1718
+ partition,
1719
+ register,
1720
+ get,
1721
+ list,
1722
+ cancel,
1723
+ accept,
1724
+ event,
1725
+ pendingEvents,
1726
+ candidates,
1727
+ select,
1728
+ catchUp,
1729
+ deferEvent,
1730
+ delivery,
1731
+ pendingDeliveries,
1732
+ listDeliveries,
1733
+ changeDelivery,
1734
+ recovering,
1735
+ deferRecovery,
1736
+ readScanCursors,
1737
+ advanceScanCursors,
1738
+ nextDeadline
1739
+ });
1740
+ });
1741
+ const memorySubscriptionStoreLayer = (partition) => Layer.effect(SubscriptionStore, makeMemorySubscriptionStore(partition));
1742
+ //#endregion
1153
1743
  //#region src/memory-storage.ts
1154
- const MAX_CONVERSATIONS = 256;
1155
- const MAX_RECORDS_PER_CONVERSATION = 65536;
1156
- const MAX_CHECKPOINTS_PER_CONVERSATION = 1024;
1157
- const storeError = (operation, message, cause) => cause === void 0 ? ConversationStoreError.make({
1744
+ const MAX_THREADS = 256;
1745
+ const MAX_RECORDS_PER_THREAD = 65536;
1746
+ const MAX_CHECKPOINTS_PER_THREAD = 1024;
1747
+ const storeError = (operation, message, cause) => cause === void 0 ? ThreadStoreError.make({
1158
1748
  operation,
1159
1749
  message
1160
- }) : ConversationStoreError.make({
1750
+ }) : ThreadStoreError.make({
1161
1751
  operation,
1162
1752
  message,
1163
1753
  cause
1164
1754
  });
1165
- const validate = Effect.fn("MemoryConversationStore.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => storeError(operation, `Invalid ${operation} request`, error))));
1755
+ const validate = Effect.fn("MemoryThreadStore.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => storeError(operation, `Invalid ${operation} request`, error))));
1166
1756
  const decodeCanonicalSequence = Schema.decodeSync(CanonicalSequence);
1167
1757
  const ZERO_CANONICAL_SEQUENCE = decodeCanonicalSequence(0);
1168
- const offsetSequence = Effect.fn("MemoryConversationStore.offsetSequence")((conversationId, offset) => {
1758
+ const offsetSequence = Effect.fn("MemoryThreadStore.offsetSequence")((threadId, offset) => {
1169
1759
  if (offset === void 0) return Effect.succeed(ZERO_CANONICAL_SEQUENCE);
1170
- const prefix = `memory:v1:${Encoding.encodeBase64(conversationId)}:`;
1760
+ const prefix = `memory:v1:${Encoding.encodeBase64(threadId)}:`;
1171
1761
  const encodedSequence = offset.startsWith(prefix) ? offset.slice(prefix.length) : "";
1172
1762
  if (!/^\d+$/.test(encodedSequence)) return Effect.fail(storeError("observe", "Malformed observation offset"));
1173
1763
  const sequence = Number(encodedSequence);
1174
1764
  return Number.isSafeInteger(sequence) ? Schema.decodeUnknownEffect(CanonicalSequence)(sequence).pipe(Effect.mapError(() => storeError("observe", "Malformed observation offset"))) : Effect.fail(storeError("observe", "Malformed observation offset"));
1175
1765
  });
1176
- const observationOffset = (conversationId, sequence) => Schema.decodeSync(ObservationOffset)(`memory:v1:${Encoding.encodeBase64(conversationId)}:${sequence}`);
1177
- const findConversation = Effect.fn("MemoryConversationStore.findConversation")((state, conversationId) => {
1178
- const conversation = state.conversations.get(conversationId);
1179
- return conversation === void 0 ? Effect.fail(ConversationNotMaterialized.make({ conversationId })) : Effect.succeed(conversation);
1766
+ const observationOffset = (threadId, sequence) => Schema.decodeSync(ObservationOffset)(`memory:v1:${Encoding.encodeBase64(threadId)}:${sequence}`);
1767
+ const findThread = Effect.fn("MemoryThreadStore.findThread")((state, threadId) => {
1768
+ const thread = state.threads.get(threadId);
1769
+ return thread === void 0 ? Effect.fail(ThreadNotMaterialized.make({ threadId })) : Effect.succeed(thread);
1180
1770
  });
1181
1771
  const CheckpointVersionEnvelope = Schema.Struct({ checkpoint: Schema.Struct({
1182
- conversationId: ConversationId,
1772
+ threadId: ThreadId,
1183
1773
  schemaVersion: Schema.Natural
1184
1774
  }) });
1185
- const validateCheckpointVersion = Effect.fn("MemoryConversationStore.validateCheckpointVersion")(function* (value) {
1775
+ const validateCheckpointVersion = Effect.fn("MemoryThreadStore.validateCheckpointVersion")(function* (value) {
1186
1776
  const envelope = yield* Schema.decodeUnknownEffect(CheckpointVersionEnvelope)(value).pipe(Effect.mapError(() => storeError("saveCheckpoint", "Invalid saveCheckpoint request")));
1187
1777
  if (envelope.checkpoint.schemaVersion !== 1) return yield* CheckpointRejected.make({
1188
- conversationId: envelope.checkpoint.conversationId,
1778
+ threadId: envelope.checkpoint.threadId,
1189
1779
  reason: "unsupported-version"
1190
1780
  });
1191
1781
  });
1192
- const makeConversationStore = Effect.gen(function* () {
1782
+ const makeThreadStore = Effect.gen(function* () {
1193
1783
  const crypto = yield* Crypto.Crypto;
1194
- const state = yield* Ref.make({ conversations: /* @__PURE__ */ new Map() });
1784
+ const state = yield* Ref.make({ threads: /* @__PURE__ */ new Map() });
1195
1785
  const updates = yield* PubSub.sliding(1);
1196
1786
  yield* Effect.addFinalizer(() => PubSub.shutdown(updates));
1197
- const materialize = Effect.fn("MemoryConversationStore.materialize")((unvalidated) => Effect.gen(function* () {
1198
- const request = yield* validate(ConversationMaterialization, "materialize", unvalidated);
1787
+ const materialize = Effect.fn("MemoryThreadStore.materialize")((unvalidated) => Effect.gen(function* () {
1788
+ const request = yield* validate(ThreadMaterialization, "materialize", unvalidated);
1199
1789
  const decision = yield* Ref.modify(state, (current) => {
1200
- const existing = current.conversations.get(request.conversationId);
1790
+ const existing = current.threads.get(request.threadId);
1201
1791
  if (existing !== void 0) {
1202
1792
  if (request.producerEpoch < existing.producerEpoch) return [{
1203
1793
  _tag: "failure",
1204
1794
  error: FenceRejected.make({
1205
- conversationId: request.conversationId,
1795
+ threadId: request.threadId,
1206
1796
  actualEpoch: existing.producerEpoch,
1207
1797
  attemptedEpoch: request.producerEpoch
1208
1798
  })
1209
1799
  }, current];
1210
1800
  if (request.producerEpoch === existing.producerEpoch) return [{ _tag: "success" }, current];
1211
- const conversations = new Map(current.conversations);
1212
- conversations.set(request.conversationId, {
1801
+ const threads = new Map(current.threads);
1802
+ threads.set(request.threadId, {
1213
1803
  ...existing,
1214
1804
  producerEpoch: request.producerEpoch
1215
1805
  });
1216
- return [{ _tag: "success" }, { conversations }];
1806
+ return [{ _tag: "success" }, { threads }];
1217
1807
  }
1218
- if (current.conversations.size >= MAX_CONVERSATIONS) return [{
1808
+ if (current.threads.size >= MAX_THREADS) return [{
1219
1809
  _tag: "failure",
1220
- error: storeError("materialize", `In-memory conversation limit ${MAX_CONVERSATIONS} exceeded`)
1810
+ error: storeError("materialize", `In-memory thread limit ${MAX_THREADS} exceeded`)
1221
1811
  }, current];
1222
- const conversations = new Map(current.conversations);
1223
- conversations.set(request.conversationId, {
1812
+ const threads = new Map(current.threads);
1813
+ threads.set(request.threadId, {
1224
1814
  producerEpoch: request.producerEpoch,
1225
1815
  tailSequence: ZERO_CANONICAL_SEQUENCE,
1226
1816
  tailDigest: EMPTY_TAIL_DIGEST,
@@ -1230,33 +1820,33 @@ const makeConversationStore = Effect.gen(function* () {
1230
1820
  tailDigests: /* @__PURE__ */ new Map([[ZERO_CANONICAL_SEQUENCE, EMPTY_TAIL_DIGEST]]),
1231
1821
  checkpoints: /* @__PURE__ */ new Map()
1232
1822
  });
1233
- return [{ _tag: "success" }, { conversations }];
1823
+ return [{ _tag: "success" }, { threads }];
1234
1824
  });
1235
1825
  if (decision._tag === "failure") return yield* decision.error;
1236
1826
  }));
1237
- const append = Effect.fn("MemoryConversationStore.append")((unvalidated) => Effect.gen(function* () {
1827
+ const append = Effect.fn("MemoryThreadStore.append")((unvalidated) => Effect.gen(function* () {
1238
1828
  const request = yield* validate(FencedAppendRequest, "append", unvalidated);
1239
1829
  const digest = yield* digestCanonicalBatch(request.expectedTailDigest, request.batch).pipe(Effect.provideService(Crypto.Crypto, crypto), Effect.mapError((error) => storeError("append", error.message, error)));
1240
1830
  const decision = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1241
- const conversation = current.conversations.get(request.conversationId);
1242
- if (conversation === void 0) return [{
1831
+ const thread = current.threads.get(request.threadId);
1832
+ if (thread === void 0) return [{
1243
1833
  _tag: "failure",
1244
- error: ConversationNotMaterialized.make({ conversationId: request.conversationId })
1834
+ error: ThreadNotMaterialized.make({ threadId: request.threadId })
1245
1835
  }, current];
1246
- if (request.producerEpoch !== conversation.producerEpoch) return [{
1836
+ if (request.producerEpoch !== thread.producerEpoch) return [{
1247
1837
  _tag: "failure",
1248
1838
  error: FenceRejected.make({
1249
- conversationId: request.conversationId,
1250
- actualEpoch: conversation.producerEpoch,
1839
+ threadId: request.threadId,
1840
+ actualEpoch: thread.producerEpoch,
1251
1841
  attemptedEpoch: request.producerEpoch
1252
1842
  })
1253
1843
  }, current];
1254
- const previous = conversation.batches.get(request.batch.batchId);
1844
+ const previous = thread.batches.get(request.batch.batchId);
1255
1845
  if (previous !== void 0) {
1256
1846
  if (previous.digest !== digest) return [{
1257
1847
  _tag: "failure",
1258
1848
  error: AppendConflict.make({
1259
- conversationId: request.conversationId,
1849
+ threadId: request.threadId,
1260
1850
  batchId: request.batch.batchId,
1261
1851
  reason: "batch-digest"
1262
1852
  })
@@ -1272,26 +1862,26 @@ const makeConversationStore = Effect.gen(function* () {
1272
1862
  records: []
1273
1863
  }, current];
1274
1864
  }
1275
- if (request.expectedTailSequence !== conversation.tailSequence || request.expectedTailDigest !== conversation.tailDigest) return [{
1865
+ if (request.expectedTailSequence !== thread.tailSequence || request.expectedTailDigest !== thread.tailDigest) return [{
1276
1866
  _tag: "failure",
1277
1867
  error: AppendConflict.make({
1278
- conversationId: request.conversationId,
1868
+ threadId: request.threadId,
1279
1869
  batchId: request.batch.batchId,
1280
1870
  reason: "tail",
1281
- actualTailSequence: conversation.tailSequence,
1282
- actualTailDigest: conversation.tailDigest
1871
+ actualTailSequence: thread.tailSequence,
1872
+ actualTailDigest: thread.tailDigest
1283
1873
  })
1284
1874
  }, current];
1285
- if (conversation.records.length + request.batch.records.length > MAX_RECORDS_PER_CONVERSATION) return [{
1875
+ if (thread.records.length + request.batch.records.length > MAX_RECORDS_PER_THREAD) return [{
1286
1876
  _tag: "failure",
1287
- error: storeError("append", `In-memory record limit ${MAX_RECORDS_PER_CONVERSATION} exceeded`)
1877
+ error: storeError("append", `In-memory record limit ${MAX_RECORDS_PER_THREAD} exceeded`)
1288
1878
  }, current];
1289
1879
  const batchRecordIds = /* @__PURE__ */ new Set();
1290
1880
  for (const record of request.batch.records) {
1291
- if (conversation.recordIds.has(record.recordId) || batchRecordIds.has(record.recordId)) return [{
1881
+ if (thread.recordIds.has(record.recordId) || batchRecordIds.has(record.recordId)) return [{
1292
1882
  _tag: "failure",
1293
1883
  error: AppendConflict.make({
1294
- conversationId: request.conversationId,
1884
+ threadId: request.threadId,
1295
1885
  batchId: request.batch.batchId,
1296
1886
  reason: "record-identity"
1297
1887
  })
@@ -1299,37 +1889,37 @@ const makeConversationStore = Effect.gen(function* () {
1299
1889
  batchRecordIds.add(record.recordId);
1300
1890
  }
1301
1891
  const records = request.batch.records.map((record, index) => {
1302
- const sequence = decodeCanonicalSequence(conversation.tailSequence + index + 1);
1892
+ const sequence = decodeCanonicalSequence(thread.tailSequence + index + 1);
1303
1893
  return CanonicalRecordEnvelope.make({
1304
- conversationId: request.conversationId,
1894
+ threadId: request.threadId,
1305
1895
  batchId: request.batch.batchId,
1306
1896
  sequence,
1307
- offset: observationOffset(request.conversationId, sequence),
1897
+ offset: observationOffset(request.threadId, sequence),
1308
1898
  record
1309
1899
  });
1310
1900
  });
1311
- const lastSequence = decodeCanonicalSequence(conversation.tailSequence + records.length);
1901
+ const lastSequence = decodeCanonicalSequence(thread.tailSequence + records.length);
1312
1902
  const result = AppendResult.make({
1313
- firstSequence: decodeCanonicalSequence(conversation.tailSequence + 1),
1903
+ firstSequence: decodeCanonicalSequence(thread.tailSequence + 1),
1314
1904
  lastSequence,
1315
1905
  tailDigest: digest,
1316
1906
  replayed: false
1317
1907
  });
1318
- const batches = new Map(conversation.batches);
1908
+ const batches = new Map(thread.batches);
1319
1909
  batches.set(request.batch.batchId, {
1320
1910
  digest,
1321
1911
  result
1322
1912
  });
1323
- const recordIds = new Set(conversation.recordIds);
1913
+ const recordIds = new Set(thread.recordIds);
1324
1914
  for (const recordId of batchRecordIds) recordIds.add(recordId);
1325
- const tailDigests = new Map(conversation.tailDigests);
1915
+ const tailDigests = new Map(thread.tailDigests);
1326
1916
  tailDigests.set(lastSequence, digest);
1327
- const conversations = new Map(current.conversations);
1328
- conversations.set(request.conversationId, {
1329
- ...conversation,
1917
+ const threads = new Map(current.threads);
1918
+ threads.set(request.threadId, {
1919
+ ...thread,
1330
1920
  tailSequence: lastSequence,
1331
1921
  tailDigest: digest,
1332
- records: [...conversation.records, ...records],
1922
+ records: [...thread.records, ...records],
1333
1923
  recordIds,
1334
1924
  batches,
1335
1925
  tailDigests
@@ -1338,118 +1928,120 @@ const makeConversationStore = Effect.gen(function* () {
1338
1928
  _tag: "success",
1339
1929
  result,
1340
1930
  records
1341
- }, { conversations }];
1931
+ }, { threads }];
1342
1932
  }).pipe(Effect.tap((decision) => decision._tag === "success" && decision.records.length > 0 ? PubSub.publish(updates, void 0) : Effect.void)));
1343
1933
  if (decision._tag === "failure") return yield* decision.error;
1344
1934
  return decision.result;
1345
1935
  }));
1346
- const readSnapshot = Effect.fn("MemoryConversationStore.readSnapshot")((conversationId, afterSequence, limit) => Ref.get(state).pipe(Effect.flatMap((current) => findConversation(current, conversationId)), Effect.map((conversation) => conversation.records.filter((record) => record.sequence > (afterSequence ?? ZERO_CANONICAL_SEQUENCE)).slice(0, limit))));
1936
+ const readSnapshot = Effect.fn("MemoryThreadStore.readSnapshot")((threadId, afterSequence, limit) => Ref.get(state).pipe(Effect.flatMap((current) => findThread(current, threadId)), Effect.map((thread) => thread.records.filter((record) => record.sequence > (afterSequence ?? ZERO_CANONICAL_SEQUENCE)).slice(0, limit))));
1347
1937
  const read = (unvalidated) => Stream.unwrap(Effect.gen(function* () {
1348
- const request = yield* validate(ConversationRead, "read", unvalidated);
1349
- const records = yield* readSnapshot(request.conversationId, request.afterSequence, request.limit);
1938
+ const request = yield* validate(ThreadRead, "read", unvalidated);
1939
+ const records = yield* readSnapshot(request.threadId, request.afterSequence, request.limit);
1350
1940
  return Stream.fromIterable(records);
1351
1941
  }));
1352
1942
  const observe = (unvalidated) => Stream.unwrap(Effect.gen(function* () {
1353
- const request = yield* validate(ConversationObservation, "observe", unvalidated);
1354
- const afterSequence = yield* offsetSequence(request.conversationId, request.afterOffset);
1943
+ const request = yield* validate(ThreadObservation, "observe", unvalidated);
1944
+ const afterSequence = yield* offsetSequence(request.threadId, request.afterOffset);
1355
1945
  return Stream.unwrap(Effect.gen(function* () {
1356
1946
  const subscription = yield* PubSub.subscribe(updates);
1357
- const initial = yield* readSnapshot(request.conversationId, afterSequence, MAX_RECORDS_PER_CONVERSATION);
1947
+ const initial = yield* readSnapshot(request.threadId, afterSequence, MAX_RECORDS_PER_THREAD);
1358
1948
  const highWater = initial.length === 0 ? afterSequence : initial.at(-1)?.sequence ?? afterSequence;
1359
- const live = Stream.fromEffectRepeat(PubSub.take(subscription)).pipe(Stream.mapAccumEffect(() => highWater, (lastSequence) => readSnapshot(request.conversationId, lastSequence, MAX_RECORDS_PER_CONVERSATION).pipe(Effect.map((records) => [records.at(-1)?.sequence ?? lastSequence, records]))));
1949
+ const live = Stream.fromEffectRepeat(PubSub.take(subscription)).pipe(Stream.mapAccumEffect(() => highWater, (lastSequence) => readSnapshot(request.threadId, lastSequence, MAX_RECORDS_PER_THREAD).pipe(Effect.map((records) => [records.at(-1)?.sequence ?? lastSequence, records]))));
1360
1950
  return Stream.fromIterable(initial).pipe(Stream.concat(live));
1361
1951
  }));
1362
1952
  }));
1363
- const exportConversation = Effect.fn("MemoryConversationStore.export")((unvalidated) => Effect.gen(function* () {
1364
- const request = yield* validate(ConversationExportRequest, "export", unvalidated);
1365
- const conversation = yield* Ref.get(state).pipe(Effect.flatMap((current) => findConversation(current, request.conversationId)));
1366
- return ConversationExport.make({
1367
- format: "effect-agent/conversation@1",
1368
- conversationId: request.conversationId,
1369
- tailSequence: conversation.tailSequence,
1370
- tailDigest: conversation.tailDigest,
1371
- records: conversation.records
1953
+ const exportThread = Effect.fn("MemoryThreadStore.export")((unvalidated) => Effect.gen(function* () {
1954
+ const request = yield* validate(ThreadExportRequest, "export", unvalidated);
1955
+ const thread = yield* Ref.get(state).pipe(Effect.flatMap((current) => findThread(current, request.threadId)));
1956
+ return ThreadExport.make({
1957
+ format: "effect-agent/thread@1",
1958
+ threadId: request.threadId,
1959
+ tailSequence: thread.tailSequence,
1960
+ tailDigest: thread.tailDigest,
1961
+ records: thread.records
1372
1962
  });
1373
1963
  }));
1374
- const inspectTail = Effect.fn("MemoryConversationStore.inspectTail")((unvalidated) => Effect.gen(function* () {
1375
- const request = yield* validate(ConversationTailRequest, "inspectTail", unvalidated);
1376
- const conversation = yield* Ref.get(state).pipe(Effect.flatMap((current) => findConversation(current, request.conversationId)));
1377
- return ConversationTail.make({
1378
- conversationId: request.conversationId,
1379
- tailSequence: conversation.tailSequence,
1380
- tailDigest: conversation.tailDigest,
1381
- producerEpoch: conversation.producerEpoch
1964
+ const inspectTail = Effect.fn("MemoryThreadStore.inspectTail")((unvalidated) => Effect.gen(function* () {
1965
+ const request = yield* validate(ThreadTailRequest, "inspectTail", unvalidated);
1966
+ const thread = yield* Ref.get(state).pipe(Effect.flatMap((current) => findThread(current, request.threadId)));
1967
+ return ThreadTail.make({
1968
+ threadId: request.threadId,
1969
+ tailSequence: thread.tailSequence,
1970
+ tailDigest: thread.tailDigest,
1971
+ producerEpoch: thread.producerEpoch
1382
1972
  });
1383
1973
  }));
1384
- const saveCheckpoint = Effect.fn("MemoryConversationStore.saveCheckpoint")((unvalidated) => Effect.gen(function* () {
1974
+ const saveCheckpoint = Effect.fn("MemoryThreadStore.saveCheckpoint")((unvalidated) => Effect.gen(function* () {
1385
1975
  yield* validateCheckpointVersion(unvalidated);
1386
1976
  const request = yield* validate(SaveCheckpointRequest, "saveCheckpoint", unvalidated);
1387
1977
  const decision = yield* Ref.modify(state, (current) => {
1388
1978
  const checkpoint = request.checkpoint;
1389
- const conversation = current.conversations.get(checkpoint.conversationId);
1390
- if (conversation === void 0) return [{
1979
+ const thread = current.threads.get(checkpoint.threadId);
1980
+ if (thread === void 0) return [{
1391
1981
  _tag: "failure",
1392
- error: ConversationNotMaterialized.make({ conversationId: checkpoint.conversationId })
1982
+ error: ThreadNotMaterialized.make({ threadId: checkpoint.threadId })
1393
1983
  }, current];
1394
- if (checkpoint.throughSequence > conversation.tailSequence) return [{
1984
+ if (checkpoint.throughSequence > thread.tailSequence) return [{
1395
1985
  _tag: "failure",
1396
1986
  error: CheckpointRejected.make({
1397
- conversationId: checkpoint.conversationId,
1987
+ threadId: checkpoint.threadId,
1398
1988
  reason: "ahead-of-tail"
1399
1989
  })
1400
1990
  }, current];
1401
- if (conversation.tailDigests.get(checkpoint.throughSequence) !== checkpoint.tailDigest) return [{
1991
+ if (thread.tailDigests.get(checkpoint.throughSequence) !== checkpoint.tailDigest) return [{
1402
1992
  _tag: "failure",
1403
1993
  error: CheckpointRejected.make({
1404
- conversationId: checkpoint.conversationId,
1994
+ threadId: checkpoint.threadId,
1405
1995
  reason: "digest-mismatch"
1406
1996
  })
1407
1997
  }, current];
1408
- if (!conversation.checkpoints.has(checkpoint.throughSequence) && conversation.checkpoints.size >= MAX_CHECKPOINTS_PER_CONVERSATION) return [{
1998
+ if (!thread.checkpoints.has(checkpoint.throughSequence) && thread.checkpoints.size >= MAX_CHECKPOINTS_PER_THREAD) return [{
1409
1999
  _tag: "failure",
1410
- error: storeError("saveCheckpoint", `In-memory checkpoint limit ${MAX_CHECKPOINTS_PER_CONVERSATION} exceeded`)
2000
+ error: storeError("saveCheckpoint", `In-memory checkpoint limit ${MAX_CHECKPOINTS_PER_THREAD} exceeded`)
1411
2001
  }, current];
1412
- const checkpoints = new Map(conversation.checkpoints);
2002
+ const checkpoints = new Map(thread.checkpoints);
1413
2003
  checkpoints.set(checkpoint.throughSequence, checkpoint);
1414
- const conversations = new Map(current.conversations);
1415
- conversations.set(checkpoint.conversationId, {
1416
- ...conversation,
2004
+ const threads = new Map(current.threads);
2005
+ threads.set(checkpoint.threadId, {
2006
+ ...thread,
1417
2007
  checkpoints
1418
2008
  });
1419
- return [{ _tag: "success" }, { conversations }];
2009
+ return [{ _tag: "success" }, { threads }];
1420
2010
  });
1421
2011
  if (decision._tag === "failure") return yield* decision.error;
1422
2012
  }));
1423
- const loadCheckpoint = Effect.fn("MemoryConversationStore.loadCheckpoint")((unvalidated) => Effect.gen(function* () {
2013
+ const loadCheckpoint = Effect.fn("MemoryThreadStore.loadCheckpoint")((unvalidated) => Effect.gen(function* () {
1424
2014
  const request = yield* validate(LoadCheckpointRequest, "loadCheckpoint", unvalidated);
1425
- const conversation = yield* Ref.get(state).pipe(Effect.flatMap((current) => findConversation(current, request.conversationId)));
1426
- const maximum = request.atOrBeforeSequence ?? conversation.tailSequence;
2015
+ const thread = yield* Ref.get(state).pipe(Effect.flatMap((current) => findThread(current, request.threadId)));
2016
+ const maximum = request.atOrBeforeSequence ?? thread.tailSequence;
1427
2017
  let selected;
1428
- for (const [sequence, checkpoint] of conversation.checkpoints) if (sequence <= maximum && (selected === void 0 || sequence > selected.throughSequence)) selected = checkpoint;
1429
- if (selected !== void 0 && conversation.tailDigests.get(selected.throughSequence) !== selected.tailDigest) return yield* CheckpointRejected.make({
1430
- conversationId: request.conversationId,
2018
+ for (const [sequence, checkpoint] of thread.checkpoints) if (sequence <= maximum && (selected === void 0 || sequence > selected.throughSequence)) selected = checkpoint;
2019
+ if (selected !== void 0 && thread.tailDigests.get(selected.throughSequence) !== selected.tailDigest) return yield* CheckpointRejected.make({
2020
+ threadId: request.threadId,
1431
2021
  reason: "digest-mismatch"
1432
2022
  });
1433
2023
  return Option.fromNullishOr(selected);
1434
2024
  }));
1435
- return ConversationStore.of({
2025
+ return ThreadStore.of({
1436
2026
  materialize,
1437
2027
  append,
1438
2028
  read,
1439
2029
  observe,
1440
- export: exportConversation,
2030
+ export: exportThread,
1441
2031
  inspectTail,
1442
- saveCheckpoint,
1443
- loadCheckpoint
2032
+ checkpoints: {
2033
+ save: saveCheckpoint,
2034
+ load: loadCheckpoint
2035
+ }
1444
2036
  });
1445
2037
  });
1446
- const MemoryConversationStoreLive = Layer.effect(ConversationStore, makeConversationStore);
2038
+ const MemoryThreadStoreLive = Layer.effect(ThreadStore, makeThreadStore);
1447
2039
  /**
1448
- * In-memory canonical Conversation persistence. Durable accepted work is served by the separate
1449
- * SubmissionLedger port; this Layer deliberately provides only the ConversationStore.
2040
+ * In-memory canonical Thread persistence. Durable accepted work is served by the separate
2041
+ * SubmissionLedger port; this Layer deliberately provides only the ThreadStore.
1450
2042
  */
1451
- const MemoryStorageLive = MemoryConversationStoreLive;
2043
+ const MemoryStorageLive = MemoryThreadStoreLive;
1452
2044
  //#endregion
1453
- export { MemoryConversationStoreLive, MemoryScheduleStoreLive, MemoryStorageLive, MemorySubmissionLedgerLive, memoryScheduleStoreLayer, memorySubmissionLedgerLayer };
2045
+ export { MemoryScheduleStoreLive, MemoryStorageLive, MemorySubmissionLedgerLive, MemoryThreadStoreLive, memoryScheduleStoreLayer, memorySubmissionLedgerLayer, memorySubscriptionStoreLayer };
1454
2046
 
1455
2047
  //# sourceMappingURL=index.mjs.map