@yielded/agent-storage-cloudflare 0.1.0-beta.167 → 0.1.0-beta.168

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/DoMemoryStore.mjs +1 -1
  2. package/dist/DoMessageDeliveryStore.d.mts +1 -1
  3. package/dist/DoMessageDeliveryStore.mjs +3 -2
  4. package/dist/DoMessageDeliveryStore.mjs.map +1 -1
  5. package/dist/DoScheduleStore.mjs +16 -37
  6. package/dist/DoScheduleStore.mjs.map +1 -1
  7. package/dist/DoStorageConfig.d.mts +2 -10
  8. package/dist/DoStorageConfig.mjs +2 -11
  9. package/dist/DoStorageConfig.mjs.map +1 -1
  10. package/dist/{DoStorageError-CGuAfSwF.d.mts → DoStorageError-CxjMnzUA.d.mts} +7 -18
  11. package/dist/DoStorageError.d.mts +2 -2
  12. package/dist/DoStorageError.mjs +14 -30
  13. package/dist/DoStorageError.mjs.map +1 -1
  14. package/dist/DoStorageFailpoint.d.mts +1 -1
  15. package/dist/DoStorageFailpointTesting.d.mts +1 -1
  16. package/dist/{DoStorageVersion-iNSyi3cJ.d.mts → DoStorageVersion-DTFoLglB.d.mts} +3 -4
  17. package/dist/DoStorageVersion.d.mts +1 -1
  18. package/dist/DoStorageVersion.mjs +2 -2
  19. package/dist/DoStorageVersion.mjs.map +1 -1
  20. package/dist/DoSubmissionLedger.d.mts +7 -5
  21. package/dist/DoSubmissionLedger.mjs +378 -528
  22. package/dist/DoSubmissionLedger.mjs.map +1 -1
  23. package/dist/DoSubscriptionStore.mjs +5 -24
  24. package/dist/DoSubscriptionStore.mjs.map +1 -1
  25. package/dist/{DoThreadStore-D_83OjCH.d.mts → DoThreadStore-Bd4fTns3.d.mts} +17 -18
  26. package/dist/DoThreadStore-CwBvTLoq.mjs +468 -0
  27. package/dist/DoThreadStore-CwBvTLoq.mjs.map +1 -0
  28. package/dist/DoThreadStore.d.mts +2 -2
  29. package/dist/DoThreadStore.mjs +4 -529
  30. package/dist/MemoryProtocol.mjs +3 -3
  31. package/dist/MemoryProtocol.mjs.map +1 -1
  32. package/dist/PortProtocol.d.mts +11727 -3410
  33. package/dist/PortProtocol.mjs +103 -15
  34. package/dist/PortProtocol.mjs.map +1 -1
  35. package/dist/PortRouting.d.mts +14 -9
  36. package/dist/PortRouting.mjs +155 -29
  37. package/dist/PortRouting.mjs.map +1 -1
  38. package/dist/do-journal-Cawnsetj.mjs +761 -0
  39. package/dist/do-journal-Cawnsetj.mjs.map +1 -0
  40. package/dist/index.d.mts +3 -3
  41. package/dist/index.mjs +1 -1
  42. package/dist/migrations-D_EP3KYf.mjs +148 -0
  43. package/dist/migrations-D_EP3KYf.mjs.map +1 -0
  44. package/dist/{owned-state-DHm2aRXx.mjs → owned-state-BHzUi0eb.mjs} +50 -19
  45. package/dist/owned-state-BHzUi0eb.mjs.map +1 -0
  46. package/package.json +1 -1
  47. package/src/DoMessageDeliveryStore.ts +2 -1
  48. package/src/DoScheduleStore.ts +19 -59
  49. package/src/DoStorageConfig.ts +2 -11
  50. package/src/DoStorageError.ts +3 -26
  51. package/src/DoStorageVersion.ts +1 -1
  52. package/src/DoSubmissionLedger.ts +1806 -2030
  53. package/src/DoSubscriptionStore.ts +5 -47
  54. package/src/DoThreadStore.ts +276 -622
  55. package/src/MemoryProtocol.ts +3 -3
  56. package/src/PortProtocol.ts +198 -12
  57. package/src/PortRouting.ts +527 -108
  58. package/src/internal/canonical-append.ts +15 -0
  59. package/src/internal/do-journal.ts +548 -1440
  60. package/src/internal/migrations.ts +184 -287
  61. package/src/internal/owned-state.ts +81 -30
  62. package/src/internal/prompt-checkpoint.ts +158 -0
  63. package/src/internal/storage-span.ts +2 -5
  64. package/dist/DoThreadStore.mjs.map +0 -1
  65. package/dist/do-journal-DUhjZkOf.mjs +0 -1352
  66. package/dist/do-journal-DUhjZkOf.mjs.map +0 -1
  67. package/dist/migrations-DtKz_Gxr.mjs +0 -306
  68. package/dist/migrations-DtKz_Gxr.mjs.map +0 -1
  69. package/dist/owned-state-DHm2aRXx.mjs.map +0 -1
  70. package/src/internal/message-delivery-schema.ts +0 -24
  71. package/src/internal/recovery-checkpoint-schema.ts +0 -17
@@ -0,0 +1,761 @@
1
+ import { DoAppendConflict, DoCheckpointConflict, DoStorageCorruptionError, DoStorageError, DoValueBoundExceeded } from "./DoStorageError.mjs";
2
+ import { n as ensureDoStorageLayout } from "./migrations-D_EP3KYf.mjs";
3
+ import { n as ownedRows, r as ownedState } from "./owned-state-BHzUi0eb.mjs";
4
+ import { SqlLifecycleRetainer, SqlLifecycleSource, makeSqlLifecyclePublication } from "@yielded/agent-storage-sql/sql-lifecycle-publication";
5
+ import { SqlStorageOwner } from "@yielded/agent/sql-memory-store";
6
+ import { Cause, Clock, Effect, Option, Predicate, Result, Schema, Stream } from "effect";
7
+ import * as SqlClient$1 from "effect/sql/SqlClient";
8
+ import { makeCanonicalStorage, recordBatchId } from "@yielded/agent-storage-sql/sql-canonical-storage";
9
+ import { makeSqlExecutions } from "@yielded/agent-storage-sql/sql-journal";
10
+ import { SqlStorageProgress } from "@yielded/agent-storage-sql/sql-storage-progress";
11
+ import { canonicalRecordJson, makeSqlThreadArchiveRange } from "@yielded/agent-storage-sql/sql-thread-archive-range";
12
+ import { makeProgressAppendValidation } from "@yielded/agent-storage-sql/sql-thread-native-reads";
13
+ import { makeSqlThreadWork } from "@yielded/agent-storage-sql/sql-thread-work";
14
+ import { ThreadId } from "@yielded/agent/identifiers";
15
+ import { LifecyclePublicationError, LifecyclePublicationFact } from "@yielded/agent/lifecycle-publication";
16
+ import { CanonicalRecord, CanonicalSequence } from "@yielded/agent/records";
17
+ import { ThreadStoreDiagnostic, ThreadStoreError } from "@yielded/agent/thread-store";
18
+ import { SqlError } from "effect/sql/SqlError";
19
+ //#region src/internal/storage-span.ts
20
+ const isAppendContention = (error) => (error._tag === "DoAppendConflict" || error._tag === "AppendConflict") && error.reason === "tail";
21
+ /** Expected refusals remain typed at the port, but are outcomes of the storage operation. */
22
+ const storageResult = (effect, expected) => effect.pipe(Effect.map(Result.succeed), Effect.catchIf(expected, (error) => Effect.succeed(Result.fail(error))));
23
+ /** Only error discriminants enter telemetry; messages, SQL, identities and payloads do not. */
24
+ const annotateStorageError = (error) => Effect.annotateCurrentSpan({
25
+ "error.type": error._tag,
26
+ ...Predicate.hasProperty(error, "cause") && Predicate.hasProperty(error.cause, "_tag") && typeof error.cause._tag === "string" ? { "error.cause.type": error.cause._tag } : {}
27
+ });
28
+ const withStorageSpan = (name, expected = () => false) => (effect) => storageResult(effect, expected).pipe(Effect.tap((result) => Result.isFailure(result) ? Effect.annotateCurrentSpan({ "storage.outcome": result.failure._tag }) : Effect.void), Effect.tapError(annotateStorageError), Effect.withSpan(name), Effect.flatMap(Effect.fromResult));
29
+ //#endregion
30
+ //#region src/internal/do-journal.ts
31
+ /**
32
+ * Static schema ceiling for stored text columns. Writes are bounded in BYTES by the
33
+ * configured `maxStoredValueBytes` (always ≤ 2,000,000); UTF-8 byte length is never smaller
34
+ * than UTF-16 string length, so any value that passed the byte bound also passes this
35
+ * decode-side character ceiling.
36
+ */
37
+ const BoundedStoredText = Schema.String.check(Schema.isMaxLength(2e6));
38
+ const BoundedIdentifier = Schema.NonEmptyString.check(Schema.isMaxLength(1024));
39
+ const MAX_IDENTIFIER_LENGTH = 1024;
40
+ const MAX_READ_PAGE_JSON_BYTES = 4194304;
41
+ const MAX_RECORD_CACHE_JSON_BYTES = 8388608;
42
+ const MAX_RECORD_CACHE_ENTRIES = 4096;
43
+ /** Durable Object SQL storage allows at most 100 bound parameters per statement. */
44
+ const MAX_BOUND_PARAMETERS = 100;
45
+ const isSqlError$1 = Schema.is(SqlError);
46
+ const storedTextBytes = (value) => new TextEncoder().encode(value).byteLength;
47
+ const chunked = (values, size) => {
48
+ const chunks = [];
49
+ for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
50
+ return chunks;
51
+ };
52
+ var ThreadRow = class extends Schema.Class("ThreadRow")({
53
+ thread_id: BoundedIdentifier,
54
+ created_at: Schema.NullOr(Schema.NonEmptyString.check(Schema.isMaxLength(128))),
55
+ tail_sequence: CanonicalSequence
56
+ }) {};
57
+ const threadRows = ownedRows(ThreadRow, "effect_agent_threads", (row) => row.thread_id, "thread_id");
58
+ var RecordRow = class extends Schema.Class("RecordRow")({
59
+ batch_id: Schema.fromJsonString(BoundedIdentifier),
60
+ thread_id: BoundedIdentifier,
61
+ record_id: Schema.fromJsonString(BoundedIdentifier),
62
+ record_json: BoundedStoredText,
63
+ sequence: CanonicalSequence
64
+ }) {};
65
+ const isolateRecords = /* @__PURE__ */ new Map();
66
+ let isolateRecordBytes = 0;
67
+ const releaseRecord = (entry) => {
68
+ if (!isolateRecords.delete(entry)) return;
69
+ isolateRecordBytes -= entry.bytes;
70
+ entry.evict();
71
+ };
72
+ const recordCaches = /* @__PURE__ */ new WeakMap();
73
+ const makeRecordCache = () => {
74
+ const records = /* @__PURE__ */ new Map();
75
+ const key = (thread, sequence) => JSON.stringify([thread, sequence]);
76
+ return {
77
+ clear: () => {
78
+ for (const entry of records.values()) releaseRecord(entry);
79
+ },
80
+ get: (thread, sequence) => records.get(key(thread, sequence))?.row,
81
+ prefix: (thread, through) => {
82
+ const prefix = [];
83
+ for (let sequence = 1; sequence <= through; sequence++) {
84
+ const row = records.get(key(thread, sequence));
85
+ if (row === void 0) return void 0;
86
+ prefix.push(row.row);
87
+ }
88
+ return prefix;
89
+ },
90
+ put: (row, capturedBytes) => {
91
+ const id = key(row.thread_id, row.sequence);
92
+ const prior = records.get(id);
93
+ if (prior?.row === row) return;
94
+ const size = capturedBytes ?? storedTextBytes(row.record_json);
95
+ if (prior !== void 0) releaseRecord(prior);
96
+ const entry = {
97
+ row,
98
+ bytes: size,
99
+ evict: () => records.delete(id)
100
+ };
101
+ records.set(id, entry);
102
+ isolateRecords.set(entry, true);
103
+ isolateRecordBytes += size;
104
+ while (isolateRecordBytes > MAX_RECORD_CACHE_JSON_BYTES || isolateRecords.size > MAX_RECORD_CACHE_ENTRIES) {
105
+ const oldest = isolateRecords.keys().next().value;
106
+ if (oldest === void 0) break;
107
+ releaseRecord(oldest);
108
+ }
109
+ }
110
+ };
111
+ };
112
+ const ReadPlanRow = Schema.Struct({
113
+ sequence: CanonicalSequence,
114
+ record_json_bytes: Schema.Natural.check(Schema.isLessThanOrEqualTo(MAX_READ_PAGE_JSON_BYTES))
115
+ });
116
+ var CheckpointRow = class extends Schema.Class("CheckpointRow")({
117
+ checkpoint_json: BoundedStoredText,
118
+ thread_id: BoundedIdentifier,
119
+ through_sequence: CanonicalSequence
120
+ }) {};
121
+ const LifecycleCursorRow = Schema.Struct({
122
+ thread_id: BoundedIdentifier,
123
+ through_sequence: CanonicalSequence
124
+ });
125
+ const lifecycleCursorRows = ownedRows(LifecycleCursorRow, "effect_agent_lifecycle_cursors", (row) => row.thread_id, "thread_id");
126
+ const initializedLifecycleSources = /* @__PURE__ */ new WeakSet();
127
+ var RawAppendResult = class extends Schema.Class("@effect-agent/storage-cloudflare/RawAppendResult")({
128
+ firstSequence: CanonicalSequence,
129
+ lastSequence: CanonicalSequence,
130
+ replayed: Schema.Boolean
131
+ }) {};
132
+ var RawReadRequest = class extends Schema.Class("@effect-agent/storage-cloudflare/RawReadRequest")({
133
+ threadId: BoundedIdentifier,
134
+ fromSequenceExclusive: CanonicalSequence,
135
+ limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1024))
136
+ }) {};
137
+ var RawCheckpoint = class extends Schema.Class("@effect-agent/storage-cloudflare/RawCheckpoint")({
138
+ checkpointJson: BoundedStoredText,
139
+ threadId: BoundedIdentifier,
140
+ throughSequence: CanonicalSequence
141
+ }) {};
142
+ const noFailpoint = () => Effect.void;
143
+ const storageError = (operation) => (error) => DoStorageError.make({
144
+ cause: error,
145
+ operation,
146
+ message: error.message,
147
+ diagnostic: ThreadStoreDiagnostic.make({
148
+ causeTag: error._tag,
149
+ operation,
150
+ issueTag: error.reason._tag
151
+ })
152
+ });
153
+ /** Decode raw Durable Object SQLite rows against a Schema, reporting failures as typed corruption. */
154
+ const decodeRows = (schema, table, rowKey, rows, decoder = table) => Schema.decodeUnknownEffect(schema)(rows).pipe(Effect.mapError((error) => DoStorageCorruptionError.make({
155
+ table,
156
+ rowKey,
157
+ message: "Stored rows do not satisfy the storage schema",
158
+ diagnostic: ThreadStoreDiagnostic.make({
159
+ causeTag: error._tag,
160
+ operation: "decode storage rows",
161
+ decoder,
162
+ issueTag: error.issue._tag
163
+ })
164
+ })), Effect.tapError((error) => Effect.annotateCurrentSpan({
165
+ "storage.failure.operation": error.diagnostic?.operation,
166
+ "storage.failure.cause": error.diagnostic?.causeTag,
167
+ "storage.failure.decoder": decoder,
168
+ "storage.failure.issue": error.diagnostic?.issueTag
169
+ })));
170
+ /** Decode exactly one raw row against a Schema, reporting failures as typed corruption. */
171
+ const decodeSingleRow = (schema, table, rowKey, rows) => decodeRows(schema, table, rowKey, rows).pipe(Effect.flatMap((decoded) => decoded.length === 1 ? Effect.succeed(decoded[0]) : Effect.fail(DoStorageCorruptionError.make({
172
+ table,
173
+ rowKey,
174
+ message: `Expected exactly one row but found ${decoded.length}.`
175
+ }))));
176
+ /** Open a validated layout and bind this Object's storage owner. */
177
+ const ensureCurrentStorage = Effect.fnUntraced(function* (sql, failpoint = noFailpoint, maxStoredValueBytes) {
178
+ yield* ensureDoStorageLayout().pipe(Effect.provideService(SqlClient$1.SqlClient, sql));
179
+ const state = yield* ownedState(sql);
180
+ const owner = (yield* SqlStorageOwner) ?? state;
181
+ let journal;
182
+ const lifecycle = yield* makeSqlLifecyclePublication(void 0, maxStoredValueBytes).pipe(Effect.provideService(SqlStorageOwner, owner), Effect.provideService(SqlLifecycleSource, {
183
+ beforeRetain: (threadId) => Effect.suspend(() => journal === void 0 ? Effect.fail(LifecyclePublicationError.make({ reason: "unavailable" })) : journal.flushCanonical(threadId)),
184
+ beforePending: Effect.suspend(() => journal === void 0 ? Effect.fail(LifecyclePublicationError.make({ reason: "unavailable" })) : journal.flushPublications())
185
+ }), Effect.provideService(SqlClient$1.SqlClient, sql), Effect.mapError((cause) => DoStorageError.make({
186
+ operation: "initialize lifecycle publication",
187
+ message: "Native publication storage unavailable",
188
+ cause
189
+ })));
190
+ journal = yield* makeJournal(sql, failpoint, maxStoredValueBytes, lifecycle, state).pipe(Effect.provideService(SqlStorageOwner, owner));
191
+ if (lifecycle !== void 0) yield* journal.initializeLifecycleSource();
192
+ return journal;
193
+ });
194
+ const makeJournal = (sql, failpoint, maxStoredValueBytes, lifecycle, state) => Effect.gen(function* () {
195
+ const owner = (yield* SqlStorageOwner) ?? state;
196
+ const progress = yield* SqlStorageProgress;
197
+ const work = yield* makeSqlThreadWork({
198
+ read: (body) => state.read(body),
199
+ write: (body) => owner.transaction(body.pipe(Effect.tap(() => state.invalidate))).pipe(Effect.provideService(SqlClient$1.SqlClient, sql), Effect.catchTag("SqlError", (cause) => ThreadStoreError.make({
200
+ operation: "rebuild Thread work",
201
+ message: "Thread work transaction failed",
202
+ cause
203
+ })))
204
+ }).pipe(Effect.provideService(SqlClient$1.SqlClient, sql));
205
+ yield* Effect.acquireRelease(Effect.sync(() => state.invalidators.add(work.invalidate)), () => Effect.sync(() => state.invalidators.delete(work.invalidate)));
206
+ const workFailure = (cause) => DoStorageError.make({
207
+ operation: "publish Thread work",
208
+ message: "Canonical work metadata could not be published",
209
+ cause
210
+ });
211
+ const validateProgress = yield* makeProgressAppendValidation().pipe(Effect.provideService(SqlClient$1.SqlClient, sql));
212
+ const executions = yield* makeSqlExecutions().pipe(Effect.provideService(SqlClient$1.SqlClient, sql));
213
+ const executionConflict = (effect) => effect.pipe(Effect.catchTag("AppendConflict", (error) => Effect.fail(DoAppendConflict.make({
214
+ reason: error.reason,
215
+ message: "Active execution conflicts with the captured commit",
216
+ ...error.actualTailSequence === void 0 ? {} : { actualTailSequence: error.actualTailSequence }
217
+ }))));
218
+ const threads = threadRows(state, sql);
219
+ const cursors = lifecycleCursorRows(state, sql);
220
+ let records = recordCaches.get(state);
221
+ if (records === void 0) {
222
+ records = makeRecordCache();
223
+ recordCaches.set(state, records);
224
+ state.invalidators.add(records.clear);
225
+ }
226
+ const recordCache = records;
227
+ const archives = yield* makeSqlThreadArchiveRange({
228
+ read: (body) => state.read(body),
229
+ write: (body) => owner.transaction(body.pipe(Effect.tap(() => state.invalidate))).pipe(Effect.provideService(SqlClient$1.SqlClient, sql), Effect.catchTag("SqlError", (cause) => ThreadStoreError.make({
230
+ operation: "archive write",
231
+ message: "Archive transaction failed.",
232
+ cause
233
+ })))
234
+ }).pipe(Effect.provideService(SqlClient$1.SqlClient, sql));
235
+ const recordJson = canonicalRecordJson(sql);
236
+ const physical = yield* makeCanonicalStorage().pipe(Effect.provideService(SqlClient$1.SqlClient, sql));
237
+ /** Typed pre-write refusal for any single value over the configured byte bound. */
238
+ const checkValueBound = (operation, value, actualBytes = storedTextBytes(value)) => {
239
+ return actualBytes > maxStoredValueBytes ? Effect.fail(DoValueBoundExceeded.make({
240
+ actualBytes,
241
+ maxBytes: maxStoredValueBytes,
242
+ operation
243
+ })) : Effect.void;
244
+ };
245
+ /**
246
+ * Runs one journal write transaction on the Durable Object storage-backed
247
+ * `withTransaction` (`ctx.storage.transaction()` under the hood). Within one Durable
248
+ * Object there is exactly ONE writer, so the Node `BEGIN IMMEDIATE` + busy-retry +
249
+ * `SqliteWriteContention` machinery has no analogue here and is deliberately absent.
250
+ * The shared local owner gate covers the transaction and acknowledgement.
251
+ *
252
+ * Journal write transactions are always top level: the Durable Object client rejects
253
+ * nested transactions, so new journal operations must not wrap this helper inside another
254
+ * transaction. Expected outcomes may be captured only when every such refusal precedes
255
+ * mutation; real faults still roll back. Successful preflight reads never authorize a write.
256
+ */
257
+ const withWriteTransaction = (operation, expected = () => false) => (effect) => state.owner.withMutation(owner.transaction(storageResult(effect, expected))).pipe(Effect.provideService(SqlClient$1.SqlClient, sql), Effect.mapError((error) => isSqlError$1(error) ? storageError(operation)(error) : error), Effect.tapError(annotateStorageError), Effect.withSpan("DoJournal.withWriteTransaction", { attributes: { operation } }), Effect.flatMap(Effect.fromResult));
258
+ const materialize = Effect.fnUntraced(function* (threadId, createdAt) {
259
+ if (threadId.length > MAX_IDENTIFIER_LENGTH) return yield* DoStorageError.make({
260
+ operation: "materialize thread",
261
+ message: "Thread identity exceeds the Durable Object storage bounds."
262
+ });
263
+ if ((yield* getThread(threadId)).length === 1) return;
264
+ yield* withWriteTransaction("materialize transaction")(Effect.gen(function* () {
265
+ const existingRows = yield* getThread(threadId);
266
+ const existing = yield* decodeRows(Schema.Array(ThreadRow), "effect_agent_threads", threadId, existingRows);
267
+ if (existing.length > 1) return yield* DoStorageCorruptionError.make({
268
+ table: "effect_agent_threads",
269
+ rowKey: threadId,
270
+ message: "A thread primary key returned more than one row."
271
+ });
272
+ if (existing.length === 0) {
273
+ yield* sql`
274
+ INSERT INTO effect_agent_threads (
275
+ thread_id,
276
+ created_at,
277
+ tail_sequence
278
+ ) VALUES (
279
+ ${threadId},
280
+ ${createdAt},
281
+ 0
282
+ ) ON CONFLICT(thread_id) DO UPDATE SET created_at=excluded.created_at
283
+ WHERE effect_agent_threads.created_at IS NULL
284
+ RETURNING *`.pipe(threads.write, Effect.mapError((error) => error._tag === "SqlError" ? storageError("materialize thread")(error) : error));
285
+ if (lifecycle !== void 0) yield* sql`INSERT INTO effect_agent_lifecycle_cursors (thread_id, through_sequence)
286
+ VALUES (${threadId}, 0) RETURNING *`.pipe(cursors.write, Effect.mapError((error) => error._tag === "SqlError" ? storageError("initialize lifecycle source")(error) : error));
287
+ return;
288
+ }
289
+ }));
290
+ }, withStorageSpan("DoJournal.materialize"));
291
+ const getThread = Effect.fnUntraced(function* (threadId) {
292
+ return (yield* threads.by("thread_id", threadId).pipe(Effect.mapError((error) => error._tag === "SqlError" ? storageError("read thread")(error) : error))).filter((row) => row.thread_id === threadId && row.created_at !== null);
293
+ });
294
+ const hasRecord = Effect.fnUntraced(function* (threadId, recordId) {
295
+ const thread = (yield* getThread(threadId))[0];
296
+ const prefix = recordCache.prefix(threadId, thread?.tail_sequence ?? 0);
297
+ if (prefix !== void 0) return prefix.some((row) => row.record_id === recordId);
298
+ return (yield* sql`SELECT record_id FROM effect_agent_canonical_records
299
+ WHERE thread_id = ${threadId} AND record_id = ${JSON.stringify(recordId)}`.pipe(Effect.mapError(storageError("read canonical record identity")))).length > 0;
300
+ });
301
+ const prepareAppend = Effect.fnUntraced(function* (request) {
302
+ if (request.threadId.length > MAX_IDENTIFIER_LENGTH || request.batchId.length > MAX_IDENTIFIER_LENGTH || request.executionWrites.some(({ update }) => update.checkpoint.progress.runId.length > MAX_IDENTIFIER_LENGTH) || request.records.some((record) => record.recordId.length > MAX_IDENTIFIER_LENGTH)) return yield* DoStorageError.make({
303
+ operation: "append canonical batch",
304
+ message: "Canonical identifiers exceed the Durable Object storage bounds."
305
+ });
306
+ yield* checkValueBound("append canonical batch", request.batchJson, request.batchBytes);
307
+ for (const record of request.records) if (record.recordBytes > maxStoredValueBytes) return yield* DoValueBoundExceeded.make({
308
+ actualBytes: record.recordBytes,
309
+ maxBytes: maxStoredValueBytes,
310
+ operation: "append canonical record"
311
+ });
312
+ for (const write of request.executionWrites) yield* checkValueBound("commit active execution", write.json, write.bytes);
313
+ const recordIds = request.records.map((record) => record.recordId);
314
+ if (new Set(recordIds).size !== recordIds.length) return yield* DoAppendConflict.make({
315
+ message: `Batch ${request.batchId} contains duplicate canonical record IDs.`,
316
+ reason: "record-identity"
317
+ });
318
+ return request;
319
+ });
320
+ const appendPrepared = Effect.fnUntraced(function* (request) {
321
+ const recordIds = request.records.map((record) => record.recordId);
322
+ const threadRows = yield* getThread(request.threadId);
323
+ const thread = threadRows.length === 1 ? threadRows[0] : void 0;
324
+ if (thread === void 0) return yield* DoStorageCorruptionError.make({
325
+ table: "effect_agent_threads",
326
+ rowKey: request.threadId,
327
+ message: `Expected exactly one row but found ${threadRows.length}.`
328
+ });
329
+ const prefix = recordCache.prefix(request.threadId, thread.tail_sequence);
330
+ const replay = yield* executionConflict(executions.replay(request));
331
+ if (replay !== void 0) return RawAppendResult.make(replay);
332
+ const existing = prefix !== void 0 && !prefix.some((row) => row.batch_id === request.batchId) ? void 0 : yield* archives.replay(request.threadId, request.batchId);
333
+ if (existing !== void 0) {
334
+ if (existing.batch_json !== request.batchJson) return yield* DoAppendConflict.make({
335
+ message: `Batch ${request.batchId} already exists with different canonical content.`,
336
+ reason: "batch-content"
337
+ });
338
+ return RawAppendResult.make({
339
+ firstSequence: existing.first_sequence,
340
+ lastSequence: existing.last_sequence,
341
+ replayed: true
342
+ });
343
+ }
344
+ yield* executionConflict(executions.rejectSuperseded(request));
345
+ if (request.expectedTailSequence !== thread.tail_sequence) return yield* DoAppendConflict.make({
346
+ message: `Expected tail ${request.expectedTailSequence} but found ${thread.tail_sequence}.`,
347
+ reason: "tail",
348
+ actualTailSequence: thread.tail_sequence
349
+ });
350
+ const existingRecords = prefix === void 0 ? [] : prefix.filter((row) => recordIds.includes(row.record_id));
351
+ for (const chunk of prefix === void 0 ? chunked(recordIds, 90) : []) {
352
+ const existingRecordRows = yield* sql`
353
+ SELECT
354
+ thread_id,
355
+ sequence,
356
+ record_id,
357
+ ${recordBatchId(sql)} AS batch_id,
358
+ ${recordJson} AS record_json
359
+ FROM effect_agent_canonical_records
360
+ WHERE thread_id = ${request.threadId}
361
+ AND record_id IN ${sql.in(chunk.map((id) => JSON.stringify(id)))}
362
+ `.pipe(Effect.mapError(storageError("check canonical record identities")));
363
+ existingRecords.push(...yield* decodeRows(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.threadId}/record_ids`, existingRecordRows));
364
+ }
365
+ if (existingRecords.length > 0) return yield* DoAppendConflict.make({
366
+ message: `Canonical record ID ${existingRecords[0].record_id} already exists.`,
367
+ reason: "record-identity"
368
+ });
369
+ const firstSequence = yield* Schema.decodeEffect(CanonicalSequence)(thread.tail_sequence + (request.records.length === 0 ? 0 : 1)).pipe(Effect.mapError((error) => DoStorageError.make({
370
+ cause: error,
371
+ operation: "append canonical batch",
372
+ message: error.message
373
+ })));
374
+ const lastSequence = yield* Schema.decodeEffect(CanonicalSequence)(thread.tail_sequence + request.records.length).pipe(Effect.mapError((error) => DoStorageError.make({
375
+ cause: error,
376
+ operation: "append canonical batch",
377
+ message: error.message
378
+ })));
379
+ yield* executionConflict(executions.validate(request));
380
+ yield* validateProgress(request);
381
+ const result = RawAppendResult.make({
382
+ firstSequence,
383
+ lastSequence,
384
+ replayed: false
385
+ });
386
+ if (request.records.length > 0) yield* sql`
387
+ INSERT INTO effect_agent_canonical_batches (
388
+ thread_id,
389
+ batch_id,
390
+ first_sequence,
391
+ last_sequence,
392
+ producer_id_json
393
+ ) VALUES (
394
+ ${request.threadId},
395
+ ${JSON.stringify(request.batchId)},
396
+ ${firstSequence},
397
+ ${lastSequence},
398
+ ${request.producerIdJson}
399
+ )
400
+ `.pipe(Effect.mapError(storageError("insert canonical batch")));
401
+ yield* failpoint("append:after-batch-insert");
402
+ const records = (yield* physical.records(request.threadId, firstSequence, request.records)).map(({ columns, memberships }, index) => ({
403
+ record: request.records[index],
404
+ canonical: request.records[index].canonical,
405
+ row: columns,
406
+ memberships
407
+ }));
408
+ for (const group of chunked(records, Math.floor(MAX_BOUND_PARAMETERS / 18))) {
409
+ yield* sql`INSERT INTO effect_agent_canonical_records ${sql.insert(group.map(({ row }) => row))}`.pipe(Effect.mapError(storageError("insert canonical records")));
410
+ const memberships = group.flatMap(({ memberships }) => memberships);
411
+ for (const members of chunked(memberships, Math.floor(MAX_BOUND_PARAMETERS / 3))) yield* sql`INSERT INTO effect_agent_record_runs ${sql.insert(members)}`.pipe(Effect.mapError(storageError("index canonical Run membership")));
412
+ for (const { row, record } of group) {
413
+ recordCache.put(RecordRow.make({
414
+ ...row,
415
+ record_id: record.recordId,
416
+ batch_id: request.batchId,
417
+ record_json: record.recordJson,
418
+ sequence: Schema.decodeSync(CanonicalSequence)(row.sequence)
419
+ }), record.recordBytes);
420
+ yield* failpoint("append:after-record-insert");
421
+ }
422
+ }
423
+ if (request.records.length > 0) {
424
+ const workFields = yield* work.apply(yield* Schema.decodeEffect(ThreadId)(request.threadId).pipe(Effect.mapError(workFailure)), firstSequence, request.records.map((record) => record.canonical)).pipe(Effect.mapError(workFailure));
425
+ yield* sql`UPDATE effect_agent_threads SET ${sql.update({
426
+ tail_sequence: lastSequence,
427
+ ...workFields
428
+ })}
429
+ WHERE thread_id=${request.threadId} RETURNING *`.pipe(threads.write, Effect.mapError((error) => error._tag === "SqlError" ? storageError("advance thread tail")(error) : error));
430
+ }
431
+ yield* executionConflict(executions.commit(request, result));
432
+ if (request.executionWrites.length > 0) yield* failpoint("append:after-record-insert");
433
+ if (lifecycle !== void 0 && request.executionWrites.some(({ update }) => update.facts.some(({ payload }) => payload._tag === "RunStarted"))) yield* flushCanonical(request.threadId).pipe(Effect.provideService(SqlLifecycleRetainer, { retainMany: lifecycle.retainMany }), Effect.mapError((cause) => DoStorageError.make({
434
+ operation: "retain lifecycle start prefix",
435
+ message: "Lifecycle start intent could not be retained",
436
+ cause
437
+ })));
438
+ for (const { update } of request.executionWrites) for (const fact of update.facts) if (lifecycle !== void 0 && Schema.is(Schema.toType(LifecyclePublicationFact))(fact.payload)) yield* lifecycle.retain({
439
+ id: JSON.stringify([
440
+ request.threadId,
441
+ "record",
442
+ fact.recordId
443
+ ]),
444
+ ownerThreadId: request.threadId,
445
+ canonicalSequence: lastSequence,
446
+ createdAt: fact.createdAt,
447
+ fact: fact.payload
448
+ }).pipe(Effect.mapError((cause) => DoStorageError.make({
449
+ operation: "retain operational lifecycle",
450
+ message: cause.message,
451
+ cause
452
+ })));
453
+ yield* failpoint("append:after-tail-update");
454
+ if (lifecycle !== void 0 && records.some(({ canonical: { payload } }) => payload._tag === "RunStarted" || payload._tag === "SubagentStarted")) yield* flushCanonical(request.threadId).pipe(Effect.provideService(SqlLifecycleRetainer, { retainMany: lifecycle.retainMany }), Effect.mapError((cause) => DoStorageError.make({
455
+ operation: "retain lifecycle start prefix",
456
+ message: "Lifecycle start intent could not be retained",
457
+ cause
458
+ })));
459
+ yield* progress.committed("canonical").pipe(Effect.catchCause((cause) => Effect.failCause(Cause.map(cause, (error) => DoStorageError.make({
460
+ operation: "enroll canonical progress",
461
+ message: error.message,
462
+ cause: error
463
+ })))));
464
+ return result;
465
+ });
466
+ const append = Effect.fnUntraced(function* (request, observed) {
467
+ yield* prepareAppend(request);
468
+ if (request.records.length > 0 && request.expectedTailSequence !== observed.tail_sequence) {
469
+ if ((yield* sql`
470
+ SELECT batch_id FROM effect_agent_canonical_batches
471
+ WHERE thread_id = ${request.threadId} AND batch_id = ${JSON.stringify(request.batchId)}
472
+ UNION ALL SELECT last_commit_id FROM effect_agent_executions
473
+ WHERE thread_id = ${request.threadId} AND last_commit_id = ${request.batchId}
474
+ LIMIT 1
475
+ `.pipe(Effect.mapError(storageError("preflight append replay")))).length === 0) return yield* DoAppendConflict.make({
476
+ reason: "tail",
477
+ message: "The canonical tail has advanced.",
478
+ actualTailSequence: observed.tail_sequence
479
+ });
480
+ }
481
+ return yield* withWriteTransaction("append transaction", isAppendContention)(appendPrepared(request));
482
+ }, withStorageSpan("DoJournal.append", isAppendContention));
483
+ const read = Effect.fnUntraced(function* (request) {
484
+ const thread = (yield* getThread(request.threadId))[0];
485
+ const through = Math.min(thread?.tail_sequence ?? 0, request.fromSequenceExclusive + request.limit);
486
+ const cached = [];
487
+ for (let sequence = request.fromSequenceExclusive + 1; sequence <= through; sequence++) {
488
+ const record = recordCache.get(request.threadId, sequence);
489
+ if (record === void 0) break;
490
+ cached.push(record);
491
+ }
492
+ if (cached.length === Math.max(0, through - request.fromSequenceExclusive)) return {
493
+ count: cached.length,
494
+ records: Stream.fromIterable(cached)
495
+ };
496
+ const planRows = yield* sql`
497
+ SELECT
498
+ sequence,
499
+ length(CAST(${recordJson} AS BLOB)) AS record_json_bytes
500
+ FROM effect_agent_canonical_records
501
+ WHERE thread_id = ${request.threadId}
502
+ AND sequence > ${request.fromSequenceExclusive}
503
+ ORDER BY sequence
504
+ LIMIT ${request.limit}
505
+ `.pipe(Effect.mapError(storageError("read canonical records")));
506
+ const plan = yield* decodeRows(Schema.Array(ReadPlanRow), "effect_agent_canonical_records", `${request.threadId}>${request.fromSequenceExclusive}`, planRows, "ReadPlanRow");
507
+ const mismatch = () => DoStorageCorruptionError.make({
508
+ table: "effect_agent_canonical_records",
509
+ rowKey: `${request.threadId}>${request.fromSequenceExclusive}`,
510
+ message: "Canonical read membership, sequence, or payload size changed from its read plan."
511
+ });
512
+ if (plan.length > request.limit) return yield* mismatch();
513
+ const pages = [];
514
+ let page;
515
+ let pageBytes = 0;
516
+ let previousSequence = request.fromSequenceExclusive;
517
+ for (const row of plan) {
518
+ if (row.sequence !== previousSequence + 1) return yield* mismatch();
519
+ previousSequence = row.sequence;
520
+ if (page === void 0 || pageBytes + row.record_json_bytes > MAX_READ_PAGE_JSON_BYTES) {
521
+ page = [row];
522
+ pages.push(page);
523
+ pageBytes = row.record_json_bytes;
524
+ } else {
525
+ page.push(row);
526
+ pageBytes += row.record_json_bytes;
527
+ }
528
+ }
529
+ const readPage = Effect.fnUntraced(function* (page) {
530
+ const rows = yield* sql`
531
+ SELECT thread_id, sequence, record_id, ${recordBatchId(sql)} AS batch_id, ${recordJson} AS record_json
532
+ FROM effect_agent_canonical_records
533
+ WHERE thread_id = ${request.threadId}
534
+ AND sequence >= ${page[0].sequence}
535
+ AND sequence <= ${page[page.length - 1].sequence}
536
+ ORDER BY sequence
537
+ `.pipe(Effect.mapError(storageError("read canonical records")));
538
+ const decoded = yield* decodeRows(Schema.Array(RecordRow), "effect_agent_canonical_records", `${request.threadId}/${page[0].sequence}`, rows, "RecordRow");
539
+ if (decoded.length !== page.length || decoded.some((row, index) => row.thread_id !== request.threadId || row.sequence !== page[index].sequence || storedTextBytes(row.record_json) !== page[index].record_json_bytes)) return yield* mismatch();
540
+ for (const row of decoded) recordCache.put(row);
541
+ return decoded;
542
+ });
543
+ return {
544
+ count: plan.length,
545
+ records: Stream.fromIterable(pages).pipe(Stream.flatMap((page) => Stream.fromIterableEffect(state.read(readPage(page)))))
546
+ };
547
+ });
548
+ const saveCheckpoint = Effect.fnUntraced(function* (checkpoint) {
549
+ if (checkpoint.threadId.length > MAX_IDENTIFIER_LENGTH) return yield* DoStorageError.make({
550
+ operation: "save checkpoint",
551
+ message: "Checkpoint identity exceeds the Durable Object storage bounds."
552
+ });
553
+ yield* checkValueBound("save checkpoint", checkpoint.checkpointJson);
554
+ yield* withWriteTransaction("checkpoint transaction")(Effect.gen(function* () {
555
+ const threadRows = yield* getThread(checkpoint.threadId);
556
+ const thread = yield* decodeSingleRow(Schema.Array(ThreadRow), "effect_agent_threads", checkpoint.threadId, threadRows);
557
+ if (checkpoint.throughSequence > thread.tail_sequence) return yield* DoCheckpointConflict.make({
558
+ reason: "ahead-of-tail",
559
+ message: `Checkpoint sequence ${checkpoint.throughSequence} is after canonical tail ${thread.tail_sequence}.`
560
+ });
561
+ if (!(yield* isBatchBoundary(checkpoint.threadId, checkpoint.throughSequence))) return yield* DoCheckpointConflict.make({
562
+ reason: "not-batch-boundary",
563
+ message: "Checkpoint sequence is not the end of a complete canonical batch."
564
+ });
565
+ const checkpointRows = yield* sql`
566
+ SELECT
567
+ thread_id,
568
+ through_sequence,
569
+ checkpoint_json
570
+ FROM effect_agent_checkpoints
571
+ WHERE thread_id = ${checkpoint.threadId}
572
+ AND through_sequence = ${checkpoint.throughSequence}
573
+ `.pipe(Effect.mapError(storageError("read idempotent checkpoint")));
574
+ const existing = yield* decodeRows(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${checkpoint.threadId}/${checkpoint.throughSequence}`, checkpointRows);
575
+ if (existing.length > 1) return yield* DoStorageCorruptionError.make({
576
+ table: "effect_agent_checkpoints",
577
+ rowKey: `${checkpoint.threadId}/${checkpoint.throughSequence}`,
578
+ message: "A checkpoint primary key returned more than one row."
579
+ });
580
+ if (existing.length === 1) {
581
+ if (existing[0].checkpoint_json !== checkpoint.checkpointJson) return yield* DoCheckpointConflict.make({
582
+ reason: "corrupt",
583
+ message: "A different checkpoint already exists at this canonical sequence."
584
+ });
585
+ return;
586
+ }
587
+ yield* sql`
588
+ INSERT INTO effect_agent_checkpoints (
589
+ thread_id,
590
+ through_sequence,
591
+ checkpoint_json
592
+ ) VALUES (
593
+ ${checkpoint.threadId},
594
+ ${checkpoint.throughSequence},
595
+ ${checkpoint.checkpointJson}
596
+ )
597
+ `.pipe(Effect.mapError(storageError("insert checkpoint")));
598
+ }));
599
+ });
600
+ const loadCheckpoint = Effect.fnUntraced(function* (threadId, atOrBeforeSequence) {
601
+ const rows = yield* sql`
602
+ SELECT
603
+ thread_id,
604
+ through_sequence,
605
+ checkpoint_json
606
+ FROM effect_agent_checkpoints
607
+ WHERE thread_id = ${threadId}
608
+ AND through_sequence <= ${atOrBeforeSequence}
609
+ ORDER BY through_sequence DESC
610
+ LIMIT 1
611
+ `.pipe(Effect.mapError(storageError("load checkpoint")));
612
+ return yield* decodeRows(Schema.Array(CheckpointRow), "effect_agent_checkpoints", `${threadId}<=${atOrBeforeSequence}`, rows);
613
+ });
614
+ const isBatchBoundary = Effect.fnUntraced(function* (threadId, sequence) {
615
+ if (sequence === 0) return true;
616
+ const rows = yield* sql`
617
+ SELECT last_sequence FROM effect_agent_canonical_batches
618
+ WHERE thread_id = ${threadId}
619
+ AND batch_id = (
620
+ SELECT ${recordBatchId(sql)} FROM effect_agent_canonical_records
621
+ WHERE thread_id = ${threadId} AND sequence = ${sequence}
622
+ )
623
+ AND last_sequence = ${sequence}
624
+ `.pipe(Effect.mapError(storageError("read canonical batch boundary")));
625
+ return (yield* decodeRows(Schema.Array(Schema.Struct({ last_sequence: CanonicalSequence })), "effect_agent_canonical_batches", `${threadId}/${sequence}`, rows)).length === 1;
626
+ });
627
+ const sourceFailure = (cause) => LifecyclePublicationError.make({
628
+ reason: "unavailable",
629
+ cause
630
+ });
631
+ const sourceThreads = Effect.fnUntraced(function* () {
632
+ const all = yield* threads.matching("lifecycle-owners", (row) => row.created_at !== null, sql`SELECT * FROM effect_agent_threads WHERE created_at IS NOT NULL LIMIT 129`, 128);
633
+ if (all.length <= 128) return all;
634
+ return yield* Schema.decodeUnknownEffect(Schema.Array(ThreadRow))(yield* sql`SELECT t.* FROM effect_agent_threads t LEFT JOIN effect_agent_lifecycle_cursors c ON c.thread_id=t.thread_id
635
+ WHERE t.created_at IS NOT NULL AND (c.thread_id IS NULL OR t.tail_sequence > c.through_sequence) ORDER BY t.thread_id LIMIT 128`);
636
+ });
637
+ const initializeLifecycleSource = Effect.fnUntraced(function* () {
638
+ if (initializedLifecycleSources.has(state)) return;
639
+ yield* state.transaction(Effect.gen(function* () {
640
+ if ((yield* sql`SELECT name FROM sqlite_master WHERE name='effect_agent_lifecycle_cursors'`).length === 0) yield* sql`CREATE TABLE effect_agent_lifecycle_cursors (
641
+ thread_id TEXT PRIMARY KEY, through_sequence INTEGER NOT NULL
642
+ )`;
643
+ yield* sql`INSERT INTO effect_agent_lifecycle_cursors(thread_id, through_sequence)
644
+ SELECT thread_id, tail_sequence FROM effect_agent_threads WHERE created_at IS NOT NULL
645
+ ON CONFLICT(thread_id) DO NOTHING`;
646
+ yield* sourceThreads();
647
+ })).pipe(Effect.provideService(SqlClient$1.SqlClient, sql), Effect.mapError((cause) => DoStorageError.make({
648
+ operation: "initialize lifecycle source",
649
+ message: "Lifecycle source cursor unavailable",
650
+ cause
651
+ })));
652
+ initializedLifecycleSources.add(state);
653
+ });
654
+ /** The journal is durable intent; only the bounded publication wave materializes a write set. */
655
+ const flushCanonical = Effect.fnUntraced(function* (threadId, maxRecords = Infinity) {
656
+ const { retainMany } = yield* SqlLifecycleRetainer;
657
+ const thread = (yield* getThread(threadId))[0];
658
+ if (thread === void 0) return;
659
+ const cursor = (yield* cursors.by("thread_id", threadId))[0];
660
+ if (cursor === void 0 || cursor.through_sequence > thread.tail_sequence) return yield* LifecyclePublicationError.make({ reason: "corrupt" });
661
+ let through = cursor.through_sequence;
662
+ const bound = Math.min(thread.tail_sequence, through + maxRecords);
663
+ while (through < bound) {
664
+ const page = yield* read(RawReadRequest.make({
665
+ threadId,
666
+ fromSequenceExclusive: through,
667
+ limit: Math.min(128, bound - through)
668
+ }));
669
+ const facts = [];
670
+ let bytes = 0;
671
+ yield* Stream.runForEach(page.records, (row) => Effect.gen(function* () {
672
+ const record = yield* Schema.decodeEffect(Schema.fromJsonString(CanonicalRecord))(row.record_json);
673
+ if (record.recordId !== row.record_id || row.sequence !== through + 1) return yield* LifecyclePublicationError.make({ reason: "corrupt" });
674
+ if (Schema.is(Schema.toType(LifecyclePublicationFact))(record.payload)) {
675
+ if (bytes + storedTextBytes(row.record_json) > MAX_READ_PAGE_JSON_BYTES && facts.length > 0) {
676
+ yield* retainMany(facts);
677
+ facts.length = 0;
678
+ bytes = 0;
679
+ }
680
+ facts.push({
681
+ id: JSON.stringify([
682
+ threadId,
683
+ "record",
684
+ record.recordId
685
+ ]),
686
+ ownerThreadId: yield* Schema.decodeEffect(ThreadId)(threadId),
687
+ canonicalSequence: row.sequence,
688
+ createdAt: record.createdAt,
689
+ fact: record.payload
690
+ });
691
+ bytes += storedTextBytes(row.record_json);
692
+ }
693
+ through = row.sequence;
694
+ }));
695
+ if (page.count === 0) return yield* LifecyclePublicationError.make({ reason: "corrupt" });
696
+ yield* retainMany(facts);
697
+ yield* sql`UPDATE effect_agent_lifecycle_cursors SET through_sequence=${through} WHERE thread_id=${threadId} RETURNING *`.pipe(cursors.write);
698
+ }
699
+ }, Effect.mapError(sourceFailure));
700
+ const flushPublications = Effect.fnUntraced(function* () {
701
+ let owners = 0;
702
+ for (const thread of yield* sourceThreads()) {
703
+ const cursor = (yield* cursors.by("thread_id", thread.thread_id))[0];
704
+ if (cursor !== void 0 && cursor.through_sequence === thread.tail_sequence) continue;
705
+ yield* flushCanonical(thread.thread_id, 1024);
706
+ if (++owners === 4) break;
707
+ }
708
+ }, Effect.mapError(sourceFailure));
709
+ const sourcePending = Effect.gen(function* () {
710
+ for (const thread of yield* sourceThreads()) {
711
+ const cursor = (yield* cursors.by("thread_id", thread.thread_id))[0];
712
+ if (cursor === void 0 || cursor.through_sequence > thread.tail_sequence) return yield* LifecyclePublicationError.make({ reason: "corrupt" });
713
+ if (cursor.through_sequence < thread.tail_sequence) return true;
714
+ }
715
+ return false;
716
+ }).pipe(Effect.mapError(sourceFailure));
717
+ const ownedLifecycle = lifecycle === void 0 ? void 0 : {
718
+ ...lifecycle,
719
+ storage: {
720
+ ...lifecycle.storage,
721
+ retainedPendingDeadline: lifecycle.storage.pendingDeadline,
722
+ pendingDeadline: state.read(Effect.gen(function* () {
723
+ const deadline = yield* lifecycle.storage.pendingDeadline;
724
+ if (!(yield* sourcePending)) return deadline;
725
+ const now = yield* Clock.currentTimeMillis;
726
+ return Option.some(Option.isSome(deadline) ? Math.min(now, deadline.value) : now);
727
+ }))
728
+ }
729
+ };
730
+ return {
731
+ archives: archives.storage,
732
+ archiveRanges: archives,
733
+ work,
734
+ state,
735
+ owner,
736
+ threads,
737
+ lifecycle: ownedLifecycle,
738
+ initializeLifecycleSource,
739
+ flushCanonical,
740
+ flushPublications,
741
+ append,
742
+ prepareAppend,
743
+ appendPrepared,
744
+ checkCommitIntent: (request) => executionConflict(executions.checkCommitIntent(request)),
745
+ readExecution: executions.read,
746
+ checkValueBound,
747
+ getThread,
748
+ hasRecord,
749
+ isBatchBoundary,
750
+ loadCheckpoint,
751
+ materialize,
752
+ read: (request) => state.read(read(request)),
753
+ saveCheckpoint,
754
+ withWriteTransaction
755
+ };
756
+ });
757
+ const initializeDoJournal = ensureCurrentStorage;
758
+ //#endregion
759
+ export { isAppendContention as a, initializeDoJournal as i, RawReadRequest as n, withStorageSpan as o, decodeRows as r, RawCheckpoint as t };
760
+
761
+ //# sourceMappingURL=do-journal-Cawnsetj.mjs.map