@effect-agent/storage-cloudflare 0.1.0-beta.6 → 0.1.0-beta.60
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/DoMemoryStore.d.mts +29 -0
- package/dist/DoMemoryStore.mjs +55 -0
- package/dist/DoMemoryStore.mjs.map +1 -0
- package/dist/DoMessageDeliveryStore.d.mts +17 -0
- package/dist/DoMessageDeliveryStore.mjs +32 -0
- package/dist/DoMessageDeliveryStore.mjs.map +1 -0
- package/dist/DoScheduleStore.d.mts +36 -0
- package/dist/DoScheduleStore.mjs +388 -0
- package/dist/DoScheduleStore.mjs.map +1 -0
- package/dist/DoStorageConfig.d.mts +45 -0
- package/dist/DoStorageConfig.mjs +52 -0
- package/dist/DoStorageConfig.mjs.map +1 -0
- package/dist/DoStorageError-UrxrvbnF.d.mts +98 -0
- package/dist/DoStorageError.d.mts +2 -0
- package/dist/DoStorageError.mjs +162 -0
- package/dist/DoStorageError.mjs.map +1 -0
- package/dist/DoStorageFailpoint.d.mts +17 -0
- package/dist/DoStorageFailpoint.mjs +14 -0
- package/dist/DoStorageFailpoint.mjs.map +1 -0
- package/dist/DoStorageFailpointTesting.d.mts +34 -0
- package/dist/DoStorageFailpointTesting.mjs +35 -0
- package/dist/DoStorageFailpointTesting.mjs.map +1 -0
- package/dist/DoStorageVersion-Pt3jEHdW.d.mts +10 -0
- package/dist/DoStorageVersion.d.mts +2 -0
- package/dist/DoStorageVersion.mjs +8 -0
- package/dist/DoStorageVersion.mjs.map +1 -0
- package/dist/DoSubmissionLedger.d.mts +23 -0
- package/dist/DoSubmissionLedger.mjs +1826 -0
- package/dist/DoSubmissionLedger.mjs.map +1 -0
- package/dist/DoSubscriptionStore.d.mts +25 -0
- package/dist/DoSubscriptionStore.mjs +183 -0
- package/dist/DoSubscriptionStore.mjs.map +1 -0
- package/dist/DoThreadStore.d.mts +59 -0
- package/dist/DoThreadStore.mjs +432 -0
- package/dist/DoThreadStore.mjs.map +1 -0
- package/dist/MemoryProtocol.d.mts +19796 -0
- package/dist/MemoryProtocol.mjs +182 -0
- package/dist/MemoryProtocol.mjs.map +1 -0
- package/dist/PortProtocol.d.mts +2951 -0
- package/dist/PortProtocol.mjs +176 -0
- package/dist/PortProtocol.mjs.map +1 -0
- package/dist/PortRouting.d.mts +82 -0
- package/dist/PortRouting.mjs +405 -0
- package/dist/PortRouting.mjs.map +1 -0
- package/dist/do-journal-Brv7xSH5.mjs +869 -0
- package/dist/do-journal-Brv7xSH5.mjs.map +1 -0
- package/dist/index.d.mts +14 -1505
- package/dist/index.mjs +14 -3695
- package/dist/migrations-soC86CQA.mjs +267 -0
- package/dist/migrations-soC86CQA.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -45
- package/src/DoMemoryStore.ts +59 -0
- package/src/DoMessageDeliveryStore.ts +53 -0
- package/src/DoScheduleStore.ts +622 -0
- package/src/{errors.ts → DoStorageError.ts} +8 -3
- package/src/DoStorageFailpoint.ts +20 -0
- package/src/{do-storage-failpoint.ts → DoStorageFailpointTesting.ts} +5 -18
- package/src/DoStorageVersion.ts +2 -0
- package/src/{do-ledger.ts → DoSubmissionLedger.ts} +757 -123
- package/src/DoSubscriptionStore.ts +329 -0
- package/src/DoThreadStore.ts +924 -0
- package/src/MemoryProtocol.ts +291 -0
- package/src/{port-protocol.ts → PortProtocol.ts} +41 -34
- package/src/{routing.ts → PortRouting.ts} +266 -259
- package/src/index.ts +13 -32
- package/src/{do-journal.ts → internal/do-journal.ts} +495 -214
- package/src/internal/message-delivery-schema.ts +24 -0
- package/src/{migrations.ts → internal/migrations.ts} +40 -35
- package/dist/index.mjs.map +0 -1
- package/src/do-conversation-store.ts +0 -850
- /package/src/{do-storage-config.ts → DoStorageConfig.ts} +0 -0
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { CanonicalSequence, ProducerEpoch } from "@effect-agent/
|
|
1
|
+
import { CanonicalSequence, ProducerEpoch } from "@effect-agent/thread/Records";
|
|
2
|
+
import { checkV2ThreadLayout } from "@effect-agent/thread/SqlStorageV2Upgrade";
|
|
2
3
|
import { SqliteMigrator } from "@effect/sql-sqlite-do";
|
|
3
|
-
import { Effect, Schema } from "effect";
|
|
4
|
+
import { Effect, Schema, Stream } from "effect";
|
|
4
5
|
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
5
6
|
import { SqlError } from "effect/unstable/sql/SqlError";
|
|
6
7
|
|
|
7
8
|
import {
|
|
9
|
+
type DoStorageFailpointError,
|
|
8
10
|
DoAppendConflict,
|
|
9
11
|
DoCheckpointConflict,
|
|
10
12
|
DoFenceRejected,
|
|
11
13
|
DoStorageCompatibilityError,
|
|
12
14
|
DoStorageCorruptionError,
|
|
13
15
|
DoStorageError,
|
|
14
|
-
DoStorageFailpointError,
|
|
15
16
|
DoValueBoundExceeded,
|
|
16
17
|
type DoStorageFailpointLocation,
|
|
17
|
-
} from "
|
|
18
|
+
} from "../DoStorageError.ts";
|
|
19
|
+
import { createMessageDeliveryTables } from "./message-delivery-schema.ts";
|
|
18
20
|
import { CurrentDoStorageVersion, doMigrations } from "./migrations.ts";
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -25,18 +27,22 @@ import { CurrentDoStorageVersion, doMigrations } from "./migrations.ts";
|
|
|
25
27
|
*/
|
|
26
28
|
const BoundedStoredText = Schema.String.check(Schema.isMaxLength(2_000_000));
|
|
27
29
|
const BoundedIdentifier = Schema.NonEmptyString.check(Schema.isMaxLength(1024));
|
|
28
|
-
const
|
|
30
|
+
const MAX_RECORDS_PER_THREAD = 65_536;
|
|
29
31
|
const MAX_IDENTIFIER_LENGTH = 1_024;
|
|
32
|
+
const MAX_READ_PAGE_JSON_BYTES = 4 * 1024 * 1024;
|
|
30
33
|
/** Durable Object SQL storage allows at most 100 bound parameters per statement. */
|
|
31
34
|
const MAX_BOUND_PARAMETERS = 100;
|
|
35
|
+
const isSqlError = Schema.is(SqlError);
|
|
32
36
|
|
|
33
37
|
const storedTextBytes = (value: string): number => new TextEncoder().encode(value).byteLength;
|
|
34
38
|
|
|
35
39
|
const chunked = <A>(values: ReadonlyArray<A>, size: number): Array<ReadonlyArray<A>> => {
|
|
36
40
|
const chunks: Array<ReadonlyArray<A>> = [];
|
|
41
|
+
|
|
37
42
|
for (let index = 0; index < values.length; index += size) {
|
|
38
43
|
chunks.push(values.slice(index, index + size));
|
|
39
44
|
}
|
|
45
|
+
|
|
40
46
|
return chunks;
|
|
41
47
|
};
|
|
42
48
|
|
|
@@ -48,8 +54,8 @@ class DoNameRow extends Schema.Class<DoNameRow>("DoNameRow")({
|
|
|
48
54
|
name: BoundedIdentifier,
|
|
49
55
|
}) {}
|
|
50
56
|
|
|
51
|
-
class
|
|
52
|
-
|
|
57
|
+
class ThreadRow extends Schema.Class<ThreadRow>("ThreadRow")({
|
|
58
|
+
thread_id: BoundedIdentifier,
|
|
53
59
|
created_at: Schema.NonEmptyString.check(Schema.isMaxLength(128)),
|
|
54
60
|
producer_epoch: ProducerEpoch,
|
|
55
61
|
tail_digest: BoundedStoredText,
|
|
@@ -60,7 +66,7 @@ class BatchRow extends Schema.Class<BatchRow>("BatchRow")({
|
|
|
60
66
|
batch_digest: BoundedStoredText,
|
|
61
67
|
batch_id: BoundedIdentifier,
|
|
62
68
|
batch_json: BoundedStoredText,
|
|
63
|
-
|
|
69
|
+
thread_id: BoundedIdentifier,
|
|
64
70
|
first_sequence: CanonicalSequence,
|
|
65
71
|
last_sequence: CanonicalSequence,
|
|
66
72
|
tail_digest: BoundedStoredText,
|
|
@@ -68,15 +74,22 @@ class BatchRow extends Schema.Class<BatchRow>("BatchRow")({
|
|
|
68
74
|
|
|
69
75
|
class RecordRow extends Schema.Class<RecordRow>("RecordRow")({
|
|
70
76
|
batch_id: BoundedIdentifier,
|
|
71
|
-
|
|
77
|
+
thread_id: BoundedIdentifier,
|
|
72
78
|
record_id: BoundedIdentifier,
|
|
73
79
|
record_json: BoundedStoredText,
|
|
74
80
|
sequence: CanonicalSequence,
|
|
75
81
|
}) {}
|
|
76
82
|
|
|
83
|
+
const ReadPlanRow = Schema.Struct({
|
|
84
|
+
sequence: CanonicalSequence,
|
|
85
|
+
record_json_bytes: Schema.Natural.check(Schema.isLessThanOrEqualTo(MAX_READ_PAGE_JSON_BYTES)),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
type ReadPage = [typeof ReadPlanRow.Type, ...Array<typeof ReadPlanRow.Type>];
|
|
89
|
+
|
|
77
90
|
class CheckpointRow extends Schema.Class<CheckpointRow>("CheckpointRow")({
|
|
78
91
|
checkpoint_json: BoundedStoredText,
|
|
79
|
-
|
|
92
|
+
thread_id: BoundedIdentifier,
|
|
80
93
|
tail_digest: BoundedStoredText,
|
|
81
94
|
through_sequence: CanonicalSequence,
|
|
82
95
|
}) {}
|
|
@@ -94,7 +107,7 @@ export class RawAppendRequest extends Schema.Class<RawAppendRequest>(
|
|
|
94
107
|
batchDigest: BoundedStoredText,
|
|
95
108
|
batchId: BoundedIdentifier,
|
|
96
109
|
batchJson: BoundedStoredText,
|
|
97
|
-
|
|
110
|
+
threadId: BoundedIdentifier,
|
|
98
111
|
expectedTailDigest: BoundedStoredText,
|
|
99
112
|
expectedTailSequence: CanonicalSequence,
|
|
100
113
|
producerEpoch: ProducerEpoch,
|
|
@@ -114,7 +127,7 @@ export class RawAppendResult extends Schema.Class<RawAppendResult>(
|
|
|
114
127
|
export class RawReadRequest extends Schema.Class<RawReadRequest>(
|
|
115
128
|
"@effect-agent/storage-cloudflare/RawReadRequest",
|
|
116
129
|
)({
|
|
117
|
-
|
|
130
|
+
threadId: BoundedIdentifier,
|
|
118
131
|
fromSequenceExclusive: CanonicalSequence,
|
|
119
132
|
limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1_024)),
|
|
120
133
|
}) {}
|
|
@@ -123,17 +136,15 @@ export class RawCheckpoint extends Schema.Class<RawCheckpoint>(
|
|
|
123
136
|
"@effect-agent/storage-cloudflare/RawCheckpoint",
|
|
124
137
|
)({
|
|
125
138
|
checkpointJson: BoundedStoredText,
|
|
126
|
-
|
|
139
|
+
threadId: BoundedIdentifier,
|
|
127
140
|
tailDigest: BoundedStoredText,
|
|
128
141
|
throughSequence: CanonicalSequence,
|
|
129
142
|
}) {}
|
|
130
143
|
|
|
131
|
-
export class
|
|
132
|
-
"@effect-agent/storage-cloudflare/
|
|
144
|
+
export class RawThreadExport extends Schema.Class<RawThreadExport>(
|
|
145
|
+
"@effect-agent/storage-cloudflare/RawThreadExport",
|
|
133
146
|
)({
|
|
134
|
-
|
|
135
|
-
checkpoints: Schema.Array(CheckpointRow),
|
|
136
|
-
conversation: ConversationRow,
|
|
147
|
+
thread: ThreadRow,
|
|
137
148
|
records: Schema.Array(RecordRow),
|
|
138
149
|
}) {}
|
|
139
150
|
|
|
@@ -167,7 +178,7 @@ const storageError =
|
|
|
167
178
|
});
|
|
168
179
|
|
|
169
180
|
/** Decode raw Durable Object SQLite rows against a Schema, reporting failures as typed corruption. */
|
|
170
|
-
export const decodeRows = Effect.fn(
|
|
181
|
+
export const decodeRows = Effect.fn(
|
|
171
182
|
<A, I>(
|
|
172
183
|
schema: Schema.Codec<ReadonlyArray<A>, ReadonlyArray<I>>,
|
|
173
184
|
table: string,
|
|
@@ -186,7 +197,7 @@ export const decodeRows = Effect.fn("DoJournal.decodeRows")(
|
|
|
186
197
|
);
|
|
187
198
|
|
|
188
199
|
/** Decode exactly one raw row against a Schema, reporting failures as typed corruption. */
|
|
189
|
-
export const decodeSingleRow = Effect.fn(
|
|
200
|
+
export const decodeSingleRow = Effect.fn(
|
|
190
201
|
<A, I>(
|
|
191
202
|
schema: Schema.Codec<ReadonlyArray<A>, ReadonlyArray<I>>,
|
|
192
203
|
table: string,
|
|
@@ -208,6 +219,147 @@ export const decodeSingleRow = Effect.fn("DoJournal.decodeSingleRow")(
|
|
|
208
219
|
),
|
|
209
220
|
);
|
|
210
221
|
|
|
222
|
+
/** Column inventory of the supported v3 predecessor, independent of physical column order. */
|
|
223
|
+
const predecessorColumns = {
|
|
224
|
+
effect_agent_threads: [
|
|
225
|
+
"thread_id",
|
|
226
|
+
"created_at",
|
|
227
|
+
"tail_sequence",
|
|
228
|
+
"tail_digest",
|
|
229
|
+
"producer_epoch",
|
|
230
|
+
],
|
|
231
|
+
effect_agent_canonical_batches: [
|
|
232
|
+
"thread_id",
|
|
233
|
+
"batch_id",
|
|
234
|
+
"first_sequence",
|
|
235
|
+
"last_sequence",
|
|
236
|
+
"batch_digest",
|
|
237
|
+
"tail_digest",
|
|
238
|
+
"batch_json",
|
|
239
|
+
],
|
|
240
|
+
effect_agent_canonical_records: ["thread_id", "sequence", "record_id", "batch_id", "record_json"],
|
|
241
|
+
effect_agent_checkpoints: ["thread_id", "through_sequence", "tail_digest", "checkpoint_json"],
|
|
242
|
+
effect_agent_submissions: [
|
|
243
|
+
"submission_id",
|
|
244
|
+
"thread_id",
|
|
245
|
+
"queue_sequence",
|
|
246
|
+
"principal",
|
|
247
|
+
"idempotency_key",
|
|
248
|
+
"agent_id",
|
|
249
|
+
"agent_digests_json",
|
|
250
|
+
"deployment_id",
|
|
251
|
+
"input_json",
|
|
252
|
+
"input_digest",
|
|
253
|
+
"receipt_id",
|
|
254
|
+
"state",
|
|
255
|
+
"settled_outcome",
|
|
256
|
+
"created_at",
|
|
257
|
+
"ready_at",
|
|
258
|
+
"input_applied_record_id",
|
|
259
|
+
"input_applied_sequence",
|
|
260
|
+
"joined_host_submission_id",
|
|
261
|
+
"suspended_reason_json",
|
|
262
|
+
"suspended_at",
|
|
263
|
+
"unknown_reason",
|
|
264
|
+
"unknown_tool_call_ids_json",
|
|
265
|
+
"parent_submission_id",
|
|
266
|
+
"parent_tool_call_id",
|
|
267
|
+
"admission_group",
|
|
268
|
+
"admission_fence_json",
|
|
269
|
+
],
|
|
270
|
+
effect_agent_submission_ownership: [
|
|
271
|
+
"submission_id",
|
|
272
|
+
"attempt_id",
|
|
273
|
+
"ownership_token",
|
|
274
|
+
"producer_epoch",
|
|
275
|
+
"owner_producer_id",
|
|
276
|
+
"lease_expires_at",
|
|
277
|
+
],
|
|
278
|
+
effect_agent_attempts: [
|
|
279
|
+
"attempt_id",
|
|
280
|
+
"submission_id",
|
|
281
|
+
"thread_id",
|
|
282
|
+
"owner_producer_id",
|
|
283
|
+
"producer_epoch",
|
|
284
|
+
"claimed_at",
|
|
285
|
+
],
|
|
286
|
+
effect_agent_settlement_reservations: [
|
|
287
|
+
"submission_id",
|
|
288
|
+
"settlement_id",
|
|
289
|
+
"outcome",
|
|
290
|
+
"record_id",
|
|
291
|
+
"record_json",
|
|
292
|
+
"record_digest",
|
|
293
|
+
"reserved_at",
|
|
294
|
+
"finalized_at",
|
|
295
|
+
],
|
|
296
|
+
effect_agent_abort_intents: [
|
|
297
|
+
"submission_id",
|
|
298
|
+
"author",
|
|
299
|
+
"reason",
|
|
300
|
+
"requested_at",
|
|
301
|
+
"canonical_record_id",
|
|
302
|
+
],
|
|
303
|
+
effect_agent_approval_decisions: [
|
|
304
|
+
"submission_id",
|
|
305
|
+
"tool_call_id",
|
|
306
|
+
"decision",
|
|
307
|
+
"resolver",
|
|
308
|
+
"reason",
|
|
309
|
+
"decided_at",
|
|
310
|
+
],
|
|
311
|
+
effect_agent_unknown_resolutions: [
|
|
312
|
+
"submission_id",
|
|
313
|
+
"tool_call_id",
|
|
314
|
+
"author",
|
|
315
|
+
"reason",
|
|
316
|
+
"resolution_json",
|
|
317
|
+
"resolved_at",
|
|
318
|
+
],
|
|
319
|
+
effect_agent_child_reservations: [
|
|
320
|
+
"reservation_id",
|
|
321
|
+
"parent_submission_id",
|
|
322
|
+
"parent_tool_call_id",
|
|
323
|
+
"child_submission_id",
|
|
324
|
+
"status",
|
|
325
|
+
"allocation_json",
|
|
326
|
+
"allocation_digest",
|
|
327
|
+
"accounting_json",
|
|
328
|
+
"reserved_at",
|
|
329
|
+
"release_began_at",
|
|
330
|
+
"released_at",
|
|
331
|
+
],
|
|
332
|
+
effect_agent_meta: ["key", "value"],
|
|
333
|
+
effect_agent_child_settlements: [
|
|
334
|
+
"parent_submission_id",
|
|
335
|
+
"child_submission_id",
|
|
336
|
+
"child_outcome",
|
|
337
|
+
"recorded_at",
|
|
338
|
+
],
|
|
339
|
+
} as const;
|
|
340
|
+
|
|
341
|
+
const checkPredecessorLayout = Effect.fn("DoJournal.checkPredecessorLayout")(function* () {
|
|
342
|
+
const sql = yield* SqlClient.SqlClient;
|
|
343
|
+
|
|
344
|
+
for (const [table, expected] of Object.entries(predecessorColumns)) {
|
|
345
|
+
const columns = yield* decodeRows(
|
|
346
|
+
Schema.Array(Schema.Struct({ name: BoundedIdentifier })),
|
|
347
|
+
table,
|
|
348
|
+
"schema",
|
|
349
|
+
yield* sql.unsafe(`PRAGMA table_info(${table})`),
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const names = new Set<string>(expected);
|
|
353
|
+
|
|
354
|
+
if (columns.length !== names.size || columns.some((column) => !names.has(column.name)))
|
|
355
|
+
return yield* DoStorageCompatibilityError.make({
|
|
356
|
+
actualVersion: 3,
|
|
357
|
+
supportedVersion: CurrentDoStorageVersion,
|
|
358
|
+
message: `The v3 ${table} columns do not match the supported predecessor; no upgrade was committed.`,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
211
363
|
const REQUIRED_TABLES = [
|
|
212
364
|
"effect_agent_abort_intents",
|
|
213
365
|
"effect_agent_approval_decisions",
|
|
@@ -217,7 +369,7 @@ const REQUIRED_TABLES = [
|
|
|
217
369
|
"effect_agent_checkpoints",
|
|
218
370
|
"effect_agent_child_reservations",
|
|
219
371
|
"effect_agent_child_settlements",
|
|
220
|
-
"
|
|
372
|
+
"effect_agent_threads",
|
|
221
373
|
"effect_agent_meta",
|
|
222
374
|
"effect_agent_settlement_reservations",
|
|
223
375
|
"effect_agent_submission_ownership",
|
|
@@ -226,7 +378,7 @@ const REQUIRED_TABLES = [
|
|
|
226
378
|
] as const;
|
|
227
379
|
|
|
228
380
|
/**
|
|
229
|
-
*
|
|
381
|
+
* Supported-predecessor or fresh storage gate (DEPLOY-008) over `effect_agent_meta` instead of
|
|
230
382
|
* `PRAGMA user_version` (unverified on Durable Object SQL storage; a meta table is portable
|
|
231
383
|
* regardless). No WAL check (Durable Object storage owns durability and confirms writes
|
|
232
384
|
* through output gates) and no busy timeout (a Durable Object has exactly one writer): the
|
|
@@ -243,6 +395,7 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
243
395
|
WHERE type = 'table'
|
|
244
396
|
AND name = 'effect_agent_meta'
|
|
245
397
|
`.pipe(Effect.mapError(storageError("read storage version table")));
|
|
398
|
+
|
|
246
399
|
const metaTables = yield* decodeRows(
|
|
247
400
|
Schema.Array(DoNameRow),
|
|
248
401
|
"sqlite_master",
|
|
@@ -258,6 +411,7 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
258
411
|
AND name LIKE 'effect_agent_%'
|
|
259
412
|
ORDER BY name
|
|
260
413
|
`.pipe(Effect.mapError(storageError("inspect unversioned storage")));
|
|
414
|
+
|
|
261
415
|
const existing = yield* decodeRows(
|
|
262
416
|
Schema.Array(DoNameRow),
|
|
263
417
|
"sqlite_master",
|
|
@@ -270,7 +424,7 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
270
424
|
actualVersion: 0,
|
|
271
425
|
supportedVersion: CurrentDoStorageVersion,
|
|
272
426
|
message:
|
|
273
|
-
"The Durable Object contains unversioned Effect Agent tables.
|
|
427
|
+
"The Durable Object contains unversioned Effect Agent tables. Refusing to mutate ambiguous stored data; retain it for inspection with its original writer.",
|
|
274
428
|
});
|
|
275
429
|
}
|
|
276
430
|
|
|
@@ -292,6 +446,7 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
292
446
|
FROM effect_agent_meta
|
|
293
447
|
WHERE key = 'storage_version'
|
|
294
448
|
`.pipe(Effect.mapError(storageError("read storage version")));
|
|
449
|
+
|
|
295
450
|
const version = yield* decodeSingleRow(
|
|
296
451
|
Schema.Array(DoMetaRow),
|
|
297
452
|
"effect_agent_meta",
|
|
@@ -299,18 +454,91 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
299
454
|
versionRows,
|
|
300
455
|
);
|
|
301
456
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
457
|
+
if (version.value === "2" || version.value === "3") {
|
|
458
|
+
yield* sql
|
|
459
|
+
.withTransaction(
|
|
460
|
+
Effect.gen(function* () {
|
|
461
|
+
const current = yield* sql<{
|
|
462
|
+
value: string;
|
|
463
|
+
}>`SELECT value FROM effect_agent_meta WHERE key='storage_version'`;
|
|
464
|
+
|
|
465
|
+
if (current.length === 1 && current[0].value === String(CurrentDoStorageVersion))
|
|
466
|
+
return;
|
|
467
|
+
if (current.length !== 1 || (current[0].value !== "2" && current[0].value !== "3"))
|
|
468
|
+
return yield* DoStorageCompatibilityError.make({
|
|
469
|
+
actualVersion: -1,
|
|
470
|
+
supportedVersion: CurrentDoStorageVersion,
|
|
471
|
+
message: "Storage version changed while acquiring the upgrade transaction.",
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const required = yield* decodeRows(
|
|
475
|
+
Schema.Array(DoNameRow),
|
|
476
|
+
"sqlite_master",
|
|
477
|
+
"required_tables",
|
|
478
|
+
yield* sql`SELECT name FROM sqlite_master WHERE type='table' AND name IN ${sql.in([...REQUIRED_TABLES])}`,
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
if (required.length !== REQUIRED_TABLES.length)
|
|
482
|
+
return yield* DoStorageCompatibilityError.make({
|
|
483
|
+
actualVersion: Number(current[0].value),
|
|
484
|
+
supportedVersion: CurrentDoStorageVersion,
|
|
485
|
+
message:
|
|
486
|
+
"The predecessor store is missing required tables. Retain the original store for inspection; no upgrade was committed.",
|
|
487
|
+
});
|
|
488
|
+
if (current[0].value === "2") {
|
|
489
|
+
yield* checkV2ThreadLayout();
|
|
490
|
+
for (const statement of [
|
|
491
|
+
sql`ALTER TABLE effect_agent_submissions ADD COLUMN admission_group TEXT`,
|
|
492
|
+
sql`ALTER TABLE effect_agent_submissions ADD COLUMN admission_fence_json TEXT`,
|
|
493
|
+
sql`CREATE INDEX effect_agent_submissions_group ON effect_agent_submissions (thread_id, admission_group, state)`,
|
|
494
|
+
]) {
|
|
495
|
+
yield* failpoint("upgrade:before-mutation");
|
|
496
|
+
yield* statement;
|
|
497
|
+
yield* failpoint("upgrade:after-mutation");
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (current[0].value === "3") yield* checkPredecessorLayout();
|
|
501
|
+
yield* failpoint("upgrade:before-mutation");
|
|
502
|
+
yield* sql`ALTER TABLE effect_agent_submissions ADD COLUMN worker_admission_json TEXT`;
|
|
503
|
+
yield* failpoint("upgrade:after-mutation");
|
|
504
|
+
yield* failpoint("upgrade:before-mutation");
|
|
505
|
+
yield* sql`ALTER TABLE effect_agent_submissions ADD COLUMN message_admission_json TEXT`;
|
|
506
|
+
yield* failpoint("upgrade:after-mutation");
|
|
507
|
+
yield* failpoint("upgrade:before-mutation");
|
|
508
|
+
yield* createMessageDeliveryTables;
|
|
509
|
+
yield* failpoint("upgrade:after-mutation");
|
|
510
|
+
yield* failpoint("upgrade:before-version");
|
|
511
|
+
yield* sql`UPDATE effect_agent_meta SET value='4' WHERE key='storage_version'`;
|
|
512
|
+
yield* failpoint("upgrade:after-version");
|
|
513
|
+
}),
|
|
514
|
+
)
|
|
515
|
+
.pipe(
|
|
516
|
+
Effect.catchTag("DoStorageFailpointError", (error) =>
|
|
517
|
+
DoStorageError.make({
|
|
518
|
+
cause: error,
|
|
519
|
+
operation: "upgrade storage",
|
|
520
|
+
message: error.message,
|
|
521
|
+
}),
|
|
522
|
+
),
|
|
523
|
+
Effect.catchTag("StorageUpgradeError", (error) =>
|
|
524
|
+
DoStorageCorruptionError.make({
|
|
525
|
+
table: error.table,
|
|
526
|
+
rowKey: error.rowKey,
|
|
527
|
+
message: error.message,
|
|
528
|
+
}),
|
|
529
|
+
),
|
|
530
|
+
Effect.catchTag("SqlError", storageError("upgrade supported thread storage")),
|
|
531
|
+
);
|
|
532
|
+
} else if (version.value !== String(CurrentDoStorageVersion)) {
|
|
306
533
|
const actualVersion = Number.parseInt(version.value, 10);
|
|
534
|
+
|
|
307
535
|
return yield* DoStorageCompatibilityError.make({
|
|
308
536
|
actualVersion: Number.isSafeInteger(actualVersion) ? actualVersion : -1,
|
|
309
537
|
supportedVersion: CurrentDoStorageVersion,
|
|
310
538
|
message:
|
|
311
|
-
`The Durable Object uses
|
|
539
|
+
`The Durable Object uses unsupported storage version ${version.value}; ` +
|
|
312
540
|
`this build supports exactly version ${CurrentDoStorageVersion}. ` +
|
|
313
|
-
"
|
|
541
|
+
"Only supported v2 and v3 can be upgraded automatically. Keep the original store and use a compatible library version.",
|
|
314
542
|
});
|
|
315
543
|
}
|
|
316
544
|
}
|
|
@@ -319,21 +547,23 @@ const ensureCurrentStorage = Effect.fn("DoJournal.ensureCurrentStorage")(functio
|
|
|
319
547
|
SELECT name
|
|
320
548
|
FROM sqlite_master
|
|
321
549
|
WHERE type = 'table'
|
|
322
|
-
AND name IN ${sql.in([...REQUIRED_TABLES])}
|
|
550
|
+
AND name IN ${sql.in([...REQUIRED_TABLES, "effect_agent_message_deliveries"])}
|
|
323
551
|
ORDER BY name
|
|
324
552
|
`.pipe(Effect.mapError(storageError("verify storage tables")));
|
|
553
|
+
|
|
325
554
|
const required = yield* decodeRows(
|
|
326
555
|
Schema.Array(DoNameRow),
|
|
327
556
|
"sqlite_master",
|
|
328
557
|
"required_tables",
|
|
329
558
|
requiredRows,
|
|
330
559
|
);
|
|
331
|
-
|
|
560
|
+
|
|
561
|
+
if (required.length !== REQUIRED_TABLES.length + 1) {
|
|
332
562
|
return yield* DoStorageCompatibilityError.make({
|
|
333
563
|
actualVersion: CurrentDoStorageVersion,
|
|
334
564
|
supportedVersion: CurrentDoStorageVersion,
|
|
335
565
|
message:
|
|
336
|
-
"The Durable Object claims the current format but is missing required tables.
|
|
566
|
+
"The Durable Object claims the current format but is missing required tables. Retain the original store for inspection.",
|
|
337
567
|
});
|
|
338
568
|
}
|
|
339
569
|
|
|
@@ -351,6 +581,7 @@ const makeJournal = (
|
|
|
351
581
|
value: string,
|
|
352
582
|
): Effect.Effect<void, DoValueBoundExceeded> => {
|
|
353
583
|
const actualBytes = storedTextBytes(value);
|
|
584
|
+
|
|
354
585
|
return actualBytes > maxStoredValueBytes
|
|
355
586
|
? Effect.fail(
|
|
356
587
|
DoValueBoundExceeded.make({
|
|
@@ -378,14 +609,12 @@ const makeJournal = (
|
|
|
378
609
|
(operation: string) =>
|
|
379
610
|
<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E | DoStorageError> =>
|
|
380
611
|
sql.withTransaction(effect).pipe(
|
|
381
|
-
Effect.mapError((error) =>
|
|
382
|
-
error instanceof SqlError ? storageError(operation)(error) : error,
|
|
383
|
-
),
|
|
612
|
+
Effect.mapError((error) => (isSqlError(error) ? storageError(operation)(error) : error)),
|
|
384
613
|
Effect.withSpan("DoJournal.withWriteTransaction", { attributes: { operation } }),
|
|
385
614
|
);
|
|
386
615
|
|
|
387
616
|
const materialize = Effect.fn("DoJournal.materialize")(function* (
|
|
388
|
-
|
|
617
|
+
threadId: string,
|
|
389
618
|
createdAt: string,
|
|
390
619
|
emptyTailDigest: string,
|
|
391
620
|
producerEpoch: ProducerEpoch,
|
|
@@ -393,54 +622,57 @@ const makeJournal = (
|
|
|
393
622
|
void,
|
|
394
623
|
DoFenceRejected | DoStorageCorruptionError | DoStorageError | DoValueBoundExceeded
|
|
395
624
|
> {
|
|
396
|
-
if (
|
|
625
|
+
if (threadId.length > MAX_IDENTIFIER_LENGTH) {
|
|
397
626
|
return yield* DoStorageError.make({
|
|
398
|
-
operation: "materialize
|
|
399
|
-
message: "
|
|
627
|
+
operation: "materialize thread",
|
|
628
|
+
message: "Thread identity exceeds the Durable Object storage bounds.",
|
|
400
629
|
});
|
|
401
630
|
}
|
|
402
|
-
yield* checkValueBound("materialize
|
|
631
|
+
yield* checkValueBound("materialize thread", emptyTailDigest);
|
|
403
632
|
yield* withWriteTransaction("materialize transaction")(
|
|
404
633
|
Effect.gen(function* () {
|
|
405
634
|
const existingRows = yield* sql<Record<string, unknown>>`
|
|
406
635
|
SELECT
|
|
407
|
-
|
|
636
|
+
thread_id,
|
|
408
637
|
created_at,
|
|
409
638
|
tail_sequence,
|
|
410
639
|
tail_digest,
|
|
411
640
|
producer_epoch
|
|
412
|
-
FROM
|
|
413
|
-
WHERE
|
|
414
|
-
`.pipe(Effect.mapError(storageError("read materialized
|
|
641
|
+
FROM effect_agent_threads
|
|
642
|
+
WHERE thread_id = ${threadId}
|
|
643
|
+
`.pipe(Effect.mapError(storageError("read materialized thread")));
|
|
644
|
+
|
|
415
645
|
const existing = yield* decodeRows(
|
|
416
|
-
Schema.Array(
|
|
417
|
-
"
|
|
418
|
-
|
|
646
|
+
Schema.Array(ThreadRow),
|
|
647
|
+
"effect_agent_threads",
|
|
648
|
+
threadId,
|
|
419
649
|
existingRows,
|
|
420
650
|
);
|
|
651
|
+
|
|
421
652
|
if (existing.length > 1) {
|
|
422
653
|
return yield* DoStorageCorruptionError.make({
|
|
423
|
-
table: "
|
|
424
|
-
rowKey:
|
|
425
|
-
message: "A
|
|
654
|
+
table: "effect_agent_threads",
|
|
655
|
+
rowKey: threadId,
|
|
656
|
+
message: "A thread primary key returned more than one row.",
|
|
426
657
|
});
|
|
427
658
|
}
|
|
428
659
|
if (existing.length === 0) {
|
|
429
660
|
yield* sql`
|
|
430
|
-
INSERT INTO
|
|
431
|
-
|
|
661
|
+
INSERT INTO effect_agent_threads (
|
|
662
|
+
thread_id,
|
|
432
663
|
created_at,
|
|
433
664
|
tail_sequence,
|
|
434
665
|
tail_digest,
|
|
435
666
|
producer_epoch
|
|
436
667
|
) VALUES (
|
|
437
|
-
${
|
|
668
|
+
${threadId},
|
|
438
669
|
${createdAt},
|
|
439
670
|
0,
|
|
440
671
|
${emptyTailDigest},
|
|
441
672
|
${producerEpoch}
|
|
442
673
|
)
|
|
443
|
-
`.pipe(Effect.mapError(storageError("materialize
|
|
674
|
+
`.pipe(Effect.mapError(storageError("materialize thread")));
|
|
675
|
+
|
|
444
676
|
return;
|
|
445
677
|
}
|
|
446
678
|
if (producerEpoch < existing[0].producer_epoch) {
|
|
@@ -452,41 +684,35 @@ const makeJournal = (
|
|
|
452
684
|
}
|
|
453
685
|
if (producerEpoch > existing[0].producer_epoch) {
|
|
454
686
|
yield* sql`
|
|
455
|
-
UPDATE
|
|
687
|
+
UPDATE effect_agent_threads
|
|
456
688
|
SET producer_epoch = ${producerEpoch}
|
|
457
|
-
WHERE
|
|
689
|
+
WHERE thread_id = ${threadId}
|
|
458
690
|
`.pipe(Effect.mapError(storageError("advance materialization epoch")));
|
|
459
691
|
}
|
|
460
692
|
}),
|
|
461
693
|
);
|
|
462
694
|
});
|
|
463
695
|
|
|
464
|
-
const
|
|
465
|
-
conversationId: string,
|
|
466
|
-
) {
|
|
696
|
+
const getThread = Effect.fn("DoJournal.getThread")(function* (threadId: string) {
|
|
467
697
|
const rows = yield* sql<Record<string, unknown>>`
|
|
468
698
|
SELECT
|
|
469
|
-
|
|
699
|
+
thread_id,
|
|
470
700
|
created_at,
|
|
471
701
|
tail_sequence,
|
|
472
702
|
tail_digest,
|
|
473
703
|
producer_epoch
|
|
474
|
-
FROM
|
|
475
|
-
WHERE
|
|
476
|
-
`.pipe(Effect.mapError(storageError("read
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
"effect_agent_conversations",
|
|
480
|
-
conversationId,
|
|
481
|
-
rows,
|
|
482
|
-
);
|
|
704
|
+
FROM effect_agent_threads
|
|
705
|
+
WHERE thread_id = ${threadId}
|
|
706
|
+
`.pipe(Effect.mapError(storageError("read thread")));
|
|
707
|
+
|
|
708
|
+
return yield* decodeRows(Schema.Array(ThreadRow), "effect_agent_threads", threadId, rows);
|
|
483
709
|
});
|
|
484
710
|
|
|
485
711
|
const append = Effect.fn("DoJournal.append")(function* (
|
|
486
712
|
request: RawAppendRequest,
|
|
487
713
|
): Effect.fn.Return<RawAppendResult, AppendError> {
|
|
488
714
|
if (
|
|
489
|
-
request.
|
|
715
|
+
request.threadId.length > MAX_IDENTIFIER_LENGTH ||
|
|
490
716
|
request.batchId.length > MAX_IDENTIFIER_LENGTH ||
|
|
491
717
|
request.records.some((record) => record.recordId.length > MAX_IDENTIFIER_LENGTH)
|
|
492
718
|
) {
|
|
@@ -504,9 +730,11 @@ const makeJournal = (
|
|
|
504
730
|
(record) => checkValueBound("append canonical record", record.recordJson),
|
|
505
731
|
{ discard: true },
|
|
506
732
|
);
|
|
733
|
+
|
|
507
734
|
return yield* withWriteTransaction("append transaction")(
|
|
508
735
|
Effect.gen(function* () {
|
|
509
736
|
const recordIds = request.records.map((record) => record.recordId);
|
|
737
|
+
|
|
510
738
|
if (new Set(recordIds).size !== recordIds.length) {
|
|
511
739
|
return yield* DoAppendConflict.make({
|
|
512
740
|
message: `Batch ${request.batchId} contains duplicate canonical record IDs.`,
|
|
@@ -514,34 +742,35 @@ const makeJournal = (
|
|
|
514
742
|
});
|
|
515
743
|
}
|
|
516
744
|
|
|
517
|
-
const
|
|
745
|
+
const threadRows = yield* sql<Record<string, unknown>>`
|
|
518
746
|
SELECT
|
|
519
|
-
|
|
747
|
+
thread_id,
|
|
520
748
|
created_at,
|
|
521
749
|
tail_sequence,
|
|
522
750
|
tail_digest,
|
|
523
751
|
producer_epoch
|
|
524
|
-
FROM
|
|
525
|
-
WHERE
|
|
752
|
+
FROM effect_agent_threads
|
|
753
|
+
WHERE thread_id = ${request.threadId}
|
|
526
754
|
`.pipe(Effect.mapError(storageError("read append tail")));
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
755
|
+
|
|
756
|
+
const thread = yield* decodeSingleRow(
|
|
757
|
+
Schema.Array(ThreadRow),
|
|
758
|
+
"effect_agent_threads",
|
|
759
|
+
request.threadId,
|
|
760
|
+
threadRows,
|
|
532
761
|
);
|
|
533
762
|
|
|
534
|
-
if (request.producerEpoch !==
|
|
763
|
+
if (request.producerEpoch !== thread.producer_epoch) {
|
|
535
764
|
return yield* DoFenceRejected.make({
|
|
536
765
|
producerEpoch: request.producerEpoch,
|
|
537
|
-
actualEpoch:
|
|
538
|
-
message: `Producer epoch ${request.producerEpoch} is not the current epoch ${
|
|
766
|
+
actualEpoch: thread.producer_epoch,
|
|
767
|
+
message: `Producer epoch ${request.producerEpoch} is not the current epoch ${thread.producer_epoch}.`,
|
|
539
768
|
});
|
|
540
769
|
}
|
|
541
770
|
|
|
542
771
|
const batchRows = yield* sql<Record<string, unknown>>`
|
|
543
772
|
SELECT
|
|
544
|
-
|
|
773
|
+
thread_id,
|
|
545
774
|
batch_id,
|
|
546
775
|
first_sequence,
|
|
547
776
|
last_sequence,
|
|
@@ -549,31 +778,34 @@ const makeJournal = (
|
|
|
549
778
|
tail_digest,
|
|
550
779
|
batch_json
|
|
551
780
|
FROM effect_agent_canonical_batches
|
|
552
|
-
WHERE
|
|
781
|
+
WHERE thread_id = ${request.threadId}
|
|
553
782
|
AND batch_id = ${request.batchId}
|
|
554
783
|
`.pipe(Effect.mapError(storageError("read idempotent batch")));
|
|
784
|
+
|
|
555
785
|
const batches = yield* decodeRows(
|
|
556
786
|
Schema.Array(BatchRow),
|
|
557
787
|
"effect_agent_canonical_batches",
|
|
558
|
-
`${request.
|
|
788
|
+
`${request.threadId}/${request.batchId}`,
|
|
559
789
|
batchRows,
|
|
560
790
|
);
|
|
561
791
|
|
|
562
792
|
if (batches.length > 1) {
|
|
563
793
|
return yield* DoStorageCorruptionError.make({
|
|
564
794
|
table: "effect_agent_canonical_batches",
|
|
565
|
-
rowKey: `${request.
|
|
795
|
+
rowKey: `${request.threadId}/${request.batchId}`,
|
|
566
796
|
message: "A canonical batch primary key returned more than one row.",
|
|
567
797
|
});
|
|
568
798
|
}
|
|
569
799
|
if (batches.length === 1) {
|
|
570
800
|
const existing = batches[0];
|
|
801
|
+
|
|
571
802
|
if (existing.batch_digest !== request.batchDigest) {
|
|
572
803
|
return yield* DoAppendConflict.make({
|
|
573
804
|
message: `Batch ${request.batchId} already exists with different canonical content.`,
|
|
574
805
|
reason: "batch-digest",
|
|
575
806
|
});
|
|
576
807
|
}
|
|
808
|
+
|
|
577
809
|
return RawAppendResult.make({
|
|
578
810
|
firstSequence: existing.first_sequence,
|
|
579
811
|
lastSequence: existing.last_sequence,
|
|
@@ -583,46 +815,48 @@ const makeJournal = (
|
|
|
583
815
|
}
|
|
584
816
|
|
|
585
817
|
if (
|
|
586
|
-
request.expectedTailSequence !==
|
|
587
|
-
request.expectedTailDigest !==
|
|
818
|
+
request.expectedTailSequence !== thread.tail_sequence ||
|
|
819
|
+
request.expectedTailDigest !== thread.tail_digest
|
|
588
820
|
) {
|
|
589
821
|
return yield* DoAppendConflict.make({
|
|
590
822
|
message:
|
|
591
823
|
`Expected tail ${request.expectedTailSequence}/${request.expectedTailDigest} ` +
|
|
592
|
-
`but found ${
|
|
824
|
+
`but found ${thread.tail_sequence}/${thread.tail_digest}.`,
|
|
593
825
|
reason: "tail",
|
|
594
|
-
actualTailSequence:
|
|
595
|
-
actualTailDigest:
|
|
826
|
+
actualTailSequence: thread.tail_sequence,
|
|
827
|
+
actualTailDigest: thread.tail_digest,
|
|
596
828
|
});
|
|
597
829
|
}
|
|
598
|
-
if (
|
|
830
|
+
if (thread.tail_sequence + request.records.length > MAX_RECORDS_PER_THREAD) {
|
|
599
831
|
return yield* DoStorageError.make({
|
|
600
832
|
operation: "append canonical batch",
|
|
601
|
-
message: `
|
|
833
|
+
message: `Thread record limit ${MAX_RECORDS_PER_THREAD} would be exceeded.`,
|
|
602
834
|
});
|
|
603
835
|
}
|
|
604
836
|
|
|
605
837
|
// Chunked to respect the Durable Object platform's 100-bound-parameter statement
|
|
606
838
|
// limit: a batch may carry up to 256 records.
|
|
607
839
|
const existingRecords: Array<RecordRow> = [];
|
|
840
|
+
|
|
608
841
|
for (const chunk of chunked(recordIds, MAX_BOUND_PARAMETERS - 10)) {
|
|
609
842
|
const existingRecordRows = yield* sql<Record<string, unknown>>`
|
|
610
843
|
SELECT
|
|
611
|
-
|
|
844
|
+
thread_id,
|
|
612
845
|
sequence,
|
|
613
846
|
record_id,
|
|
614
847
|
batch_id,
|
|
615
848
|
record_json
|
|
616
849
|
FROM effect_agent_canonical_records
|
|
617
|
-
WHERE
|
|
850
|
+
WHERE thread_id = ${request.threadId}
|
|
618
851
|
AND record_id IN ${sql.in([...chunk])}
|
|
619
852
|
ORDER BY sequence
|
|
620
853
|
`.pipe(Effect.mapError(storageError("check canonical record identities")));
|
|
854
|
+
|
|
621
855
|
existingRecords.push(
|
|
622
856
|
...(yield* decodeRows(
|
|
623
857
|
Schema.Array(RecordRow),
|
|
624
858
|
"effect_agent_canonical_records",
|
|
625
|
-
`${request.
|
|
859
|
+
`${request.threadId}/record_ids`,
|
|
626
860
|
existingRecordRows,
|
|
627
861
|
)),
|
|
628
862
|
);
|
|
@@ -635,7 +869,7 @@ const makeJournal = (
|
|
|
635
869
|
}
|
|
636
870
|
|
|
637
871
|
const firstSequence = yield* Schema.decodeUnknownEffect(CanonicalSequence)(
|
|
638
|
-
|
|
872
|
+
thread.tail_sequence + 1,
|
|
639
873
|
).pipe(
|
|
640
874
|
Effect.mapError((error) =>
|
|
641
875
|
DoStorageError.make({
|
|
@@ -645,6 +879,7 @@ const makeJournal = (
|
|
|
645
879
|
}),
|
|
646
880
|
),
|
|
647
881
|
);
|
|
882
|
+
|
|
648
883
|
const lastSequence = yield* Schema.decodeUnknownEffect(CanonicalSequence)(
|
|
649
884
|
firstSequence + request.records.length - 1,
|
|
650
885
|
).pipe(
|
|
@@ -659,7 +894,7 @@ const makeJournal = (
|
|
|
659
894
|
|
|
660
895
|
yield* sql`
|
|
661
896
|
INSERT INTO effect_agent_canonical_batches (
|
|
662
|
-
|
|
897
|
+
thread_id,
|
|
663
898
|
batch_id,
|
|
664
899
|
first_sequence,
|
|
665
900
|
last_sequence,
|
|
@@ -667,7 +902,7 @@ const makeJournal = (
|
|
|
667
902
|
tail_digest,
|
|
668
903
|
batch_json
|
|
669
904
|
) VALUES (
|
|
670
|
-
${request.
|
|
905
|
+
${request.threadId},
|
|
671
906
|
${request.batchId},
|
|
672
907
|
${firstSequence},
|
|
673
908
|
${lastSequence},
|
|
@@ -684,13 +919,13 @@ const makeJournal = (
|
|
|
684
919
|
Effect.gen(function* () {
|
|
685
920
|
yield* sql`
|
|
686
921
|
INSERT INTO effect_agent_canonical_records (
|
|
687
|
-
|
|
922
|
+
thread_id,
|
|
688
923
|
sequence,
|
|
689
924
|
record_id,
|
|
690
925
|
batch_id,
|
|
691
926
|
record_json
|
|
692
927
|
) VALUES (
|
|
693
|
-
${request.
|
|
928
|
+
${request.threadId},
|
|
694
929
|
${firstSequence + index},
|
|
695
930
|
${record.recordId},
|
|
696
931
|
${request.batchId},
|
|
@@ -703,13 +938,13 @@ const makeJournal = (
|
|
|
703
938
|
);
|
|
704
939
|
|
|
705
940
|
yield* sql`
|
|
706
|
-
UPDATE
|
|
941
|
+
UPDATE effect_agent_threads
|
|
707
942
|
SET
|
|
708
943
|
tail_sequence = ${lastSequence},
|
|
709
944
|
tail_digest = ${request.tailDigest},
|
|
710
945
|
producer_epoch = ${request.producerEpoch}
|
|
711
|
-
WHERE
|
|
712
|
-
`.pipe(Effect.mapError(storageError("advance
|
|
946
|
+
WHERE thread_id = ${request.threadId}
|
|
947
|
+
`.pipe(Effect.mapError(storageError("advance thread tail")));
|
|
713
948
|
yield* failpoint("append:after-tail-update");
|
|
714
949
|
|
|
715
950
|
return RawAppendResult.make({
|
|
@@ -723,105 +958,137 @@ const makeJournal = (
|
|
|
723
958
|
});
|
|
724
959
|
|
|
725
960
|
const read = Effect.fn("DoJournal.read")(function* (request: RawReadRequest) {
|
|
726
|
-
|
|
961
|
+
// Capture membership without retaining payloads. Append-only sequences keep each later
|
|
962
|
+
// payload query inside this snapshot, even when new records arrive during consumption.
|
|
963
|
+
const planRows = yield* sql<Record<string, unknown>>`
|
|
727
964
|
SELECT
|
|
728
|
-
conversation_id,
|
|
729
965
|
sequence,
|
|
730
|
-
|
|
731
|
-
batch_id,
|
|
732
|
-
record_json
|
|
966
|
+
length(CAST(record_json AS BLOB)) AS record_json_bytes
|
|
733
967
|
FROM effect_agent_canonical_records
|
|
734
|
-
WHERE
|
|
968
|
+
WHERE thread_id = ${request.threadId}
|
|
735
969
|
AND sequence > ${request.fromSequenceExclusive}
|
|
736
970
|
ORDER BY sequence
|
|
737
971
|
LIMIT ${request.limit}
|
|
738
972
|
`.pipe(Effect.mapError(storageError("read canonical records")));
|
|
739
|
-
|
|
740
|
-
|
|
973
|
+
|
|
974
|
+
const plan = yield* decodeRows(
|
|
975
|
+
Schema.Array(ReadPlanRow),
|
|
741
976
|
"effect_agent_canonical_records",
|
|
742
|
-
`${request.
|
|
743
|
-
|
|
977
|
+
`${request.threadId}>${request.fromSequenceExclusive}`,
|
|
978
|
+
planRows,
|
|
744
979
|
);
|
|
980
|
+
|
|
981
|
+
const mismatch = () =>
|
|
982
|
+
DoStorageCorruptionError.make({
|
|
983
|
+
table: "effect_agent_canonical_records",
|
|
984
|
+
rowKey: `${request.threadId}>${request.fromSequenceExclusive}`,
|
|
985
|
+
message: "Canonical read membership, sequence, or payload size changed from its read plan.",
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
if (plan.length > request.limit) return yield* mismatch();
|
|
989
|
+
|
|
990
|
+
const pages: Array<ReadPage> = [];
|
|
991
|
+
let page: ReadPage | undefined;
|
|
992
|
+
let pageBytes = 0;
|
|
993
|
+
let previousSequence = request.fromSequenceExclusive;
|
|
994
|
+
|
|
995
|
+
for (const row of plan) {
|
|
996
|
+
if (row.sequence !== previousSequence + 1) return yield* mismatch();
|
|
997
|
+
previousSequence = row.sequence;
|
|
998
|
+
if (page === undefined || pageBytes + row.record_json_bytes > MAX_READ_PAGE_JSON_BYTES) {
|
|
999
|
+
page = [row];
|
|
1000
|
+
pages.push(page);
|
|
1001
|
+
pageBytes = row.record_json_bytes;
|
|
1002
|
+
} else {
|
|
1003
|
+
page.push(row);
|
|
1004
|
+
pageBytes += row.record_json_bytes;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const readPage = Effect.fn("DoJournal.readPage")(function* (page: ReadPage) {
|
|
1009
|
+
const rows = yield* sql<Record<string, unknown>>`
|
|
1010
|
+
SELECT thread_id, sequence, record_id, batch_id, record_json
|
|
1011
|
+
FROM effect_agent_canonical_records
|
|
1012
|
+
WHERE thread_id = ${request.threadId}
|
|
1013
|
+
AND sequence >= ${page[0].sequence}
|
|
1014
|
+
AND sequence <= ${page[page.length - 1].sequence}
|
|
1015
|
+
ORDER BY sequence
|
|
1016
|
+
`.pipe(Effect.mapError(storageError("read canonical records")));
|
|
1017
|
+
|
|
1018
|
+
const decoded = yield* decodeRows(
|
|
1019
|
+
Schema.Array(RecordRow),
|
|
1020
|
+
"effect_agent_canonical_records",
|
|
1021
|
+
`${request.threadId}/${page[0].sequence}`,
|
|
1022
|
+
rows,
|
|
1023
|
+
);
|
|
1024
|
+
|
|
1025
|
+
if (
|
|
1026
|
+
decoded.length !== page.length ||
|
|
1027
|
+
decoded.some(
|
|
1028
|
+
(row, index) =>
|
|
1029
|
+
row.thread_id !== request.threadId ||
|
|
1030
|
+
row.sequence !== page[index].sequence ||
|
|
1031
|
+
storedTextBytes(row.record_json) !== page[index].record_json_bytes,
|
|
1032
|
+
)
|
|
1033
|
+
) {
|
|
1034
|
+
return yield* mismatch();
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
return decoded;
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
return {
|
|
1041
|
+
count: plan.length,
|
|
1042
|
+
records: Stream.fromIterable(pages).pipe(
|
|
1043
|
+
Stream.flatMap((page) => Stream.fromIterableEffect(readPage(page))),
|
|
1044
|
+
),
|
|
1045
|
+
};
|
|
745
1046
|
});
|
|
746
1047
|
|
|
747
|
-
const
|
|
748
|
-
conversationId: string,
|
|
749
|
-
) {
|
|
1048
|
+
const exportThread = Effect.fn("DoJournal.exportThread")(function* (threadId: string) {
|
|
750
1049
|
return yield* sql
|
|
751
1050
|
.withTransaction(
|
|
752
1051
|
Effect.gen(function* () {
|
|
753
|
-
const
|
|
1052
|
+
const threadRows = yield* sql<Record<string, unknown>>`
|
|
754
1053
|
SELECT
|
|
755
|
-
|
|
1054
|
+
thread_id,
|
|
756
1055
|
created_at,
|
|
757
1056
|
tail_sequence,
|
|
758
1057
|
tail_digest,
|
|
759
1058
|
producer_epoch
|
|
760
|
-
FROM
|
|
761
|
-
WHERE
|
|
762
|
-
`.pipe(Effect.mapError(storageError("export
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1059
|
+
FROM effect_agent_threads
|
|
1060
|
+
WHERE thread_id = ${threadId}
|
|
1061
|
+
`.pipe(Effect.mapError(storageError("export thread")));
|
|
1062
|
+
|
|
1063
|
+
const thread = yield* decodeSingleRow(
|
|
1064
|
+
Schema.Array(ThreadRow),
|
|
1065
|
+
"effect_agent_threads",
|
|
1066
|
+
threadId,
|
|
1067
|
+
threadRows,
|
|
768
1068
|
);
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
conversation_id,
|
|
773
|
-
batch_id,
|
|
774
|
-
first_sequence,
|
|
775
|
-
last_sequence,
|
|
776
|
-
batch_digest,
|
|
777
|
-
tail_digest,
|
|
778
|
-
batch_json
|
|
779
|
-
FROM effect_agent_canonical_batches
|
|
780
|
-
WHERE conversation_id = ${conversationId}
|
|
781
|
-
ORDER BY first_sequence
|
|
782
|
-
`.pipe(Effect.mapError(storageError("export canonical batches")));
|
|
1069
|
+
|
|
1070
|
+
yield* failpoint("export:after-thread-read");
|
|
1071
|
+
|
|
783
1072
|
const recordRows = yield* sql<Record<string, unknown>>`
|
|
784
1073
|
SELECT
|
|
785
|
-
|
|
1074
|
+
thread_id,
|
|
786
1075
|
sequence,
|
|
787
1076
|
record_id,
|
|
788
1077
|
batch_id,
|
|
789
1078
|
record_json
|
|
790
1079
|
FROM effect_agent_canonical_records
|
|
791
|
-
WHERE
|
|
1080
|
+
WHERE thread_id = ${threadId}
|
|
792
1081
|
ORDER BY sequence
|
|
793
1082
|
`.pipe(Effect.mapError(storageError("export canonical records")));
|
|
794
|
-
const checkpointRows = yield* sql<Record<string, unknown>>`
|
|
795
|
-
SELECT
|
|
796
|
-
conversation_id,
|
|
797
|
-
through_sequence,
|
|
798
|
-
tail_digest,
|
|
799
|
-
checkpoint_json
|
|
800
|
-
FROM effect_agent_checkpoints
|
|
801
|
-
WHERE conversation_id = ${conversationId}
|
|
802
|
-
ORDER BY through_sequence
|
|
803
|
-
`.pipe(Effect.mapError(storageError("export checkpoints")));
|
|
804
1083
|
|
|
805
|
-
return
|
|
806
|
-
|
|
807
|
-
batches: yield* decodeRows(
|
|
808
|
-
Schema.Array(BatchRow),
|
|
809
|
-
"effect_agent_canonical_batches",
|
|
810
|
-
conversationId,
|
|
811
|
-
batchRows,
|
|
812
|
-
),
|
|
1084
|
+
return RawThreadExport.make({
|
|
1085
|
+
thread,
|
|
813
1086
|
records: yield* decodeRows(
|
|
814
1087
|
Schema.Array(RecordRow),
|
|
815
1088
|
"effect_agent_canonical_records",
|
|
816
|
-
|
|
1089
|
+
threadId,
|
|
817
1090
|
recordRows,
|
|
818
1091
|
),
|
|
819
|
-
checkpoints: yield* decodeRows(
|
|
820
|
-
Schema.Array(CheckpointRow),
|
|
821
|
-
"effect_agent_checkpoints",
|
|
822
|
-
conversationId,
|
|
823
|
-
checkpointRows,
|
|
824
|
-
),
|
|
825
1092
|
});
|
|
826
1093
|
}),
|
|
827
1094
|
)
|
|
@@ -835,7 +1102,7 @@ const makeJournal = (
|
|
|
835
1102
|
const saveCheckpoint = Effect.fn("DoJournal.saveCheckpoint")(function* (
|
|
836
1103
|
checkpoint: RawCheckpoint,
|
|
837
1104
|
): Effect.fn.Return<void, CheckpointError> {
|
|
838
|
-
if (checkpoint.
|
|
1105
|
+
if (checkpoint.threadId.length > MAX_IDENTIFIER_LENGTH) {
|
|
839
1106
|
return yield* DoStorageError.make({
|
|
840
1107
|
operation: "save checkpoint",
|
|
841
1108
|
message: "Checkpoint identity exceeds the Durable Object storage bounds.",
|
|
@@ -844,50 +1111,54 @@ const makeJournal = (
|
|
|
844
1111
|
yield* checkValueBound("save checkpoint", checkpoint.checkpointJson);
|
|
845
1112
|
yield* withWriteTransaction("checkpoint transaction")(
|
|
846
1113
|
Effect.gen(function* () {
|
|
847
|
-
const
|
|
1114
|
+
const threadRows = yield* sql<Record<string, unknown>>`
|
|
848
1115
|
SELECT
|
|
849
|
-
|
|
1116
|
+
thread_id,
|
|
850
1117
|
created_at,
|
|
851
1118
|
tail_sequence,
|
|
852
1119
|
tail_digest,
|
|
853
1120
|
producer_epoch
|
|
854
|
-
FROM
|
|
855
|
-
WHERE
|
|
1121
|
+
FROM effect_agent_threads
|
|
1122
|
+
WHERE thread_id = ${checkpoint.threadId}
|
|
856
1123
|
`.pipe(Effect.mapError(storageError("read checkpoint tail")));
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1124
|
+
|
|
1125
|
+
const thread = yield* decodeSingleRow(
|
|
1126
|
+
Schema.Array(ThreadRow),
|
|
1127
|
+
"effect_agent_threads",
|
|
1128
|
+
checkpoint.threadId,
|
|
1129
|
+
threadRows,
|
|
862
1130
|
);
|
|
863
|
-
|
|
1131
|
+
|
|
1132
|
+
if (checkpoint.throughSequence > thread.tail_sequence) {
|
|
864
1133
|
return yield* DoCheckpointConflict.make({
|
|
865
1134
|
message:
|
|
866
1135
|
`Checkpoint sequence ${checkpoint.throughSequence} is after canonical tail ` +
|
|
867
|
-
`${
|
|
1136
|
+
`${thread.tail_sequence}.`,
|
|
868
1137
|
});
|
|
869
1138
|
}
|
|
870
1139
|
|
|
871
1140
|
const checkpointRows = yield* sql<Record<string, unknown>>`
|
|
872
1141
|
SELECT
|
|
873
|
-
|
|
1142
|
+
thread_id,
|
|
874
1143
|
through_sequence,
|
|
875
1144
|
tail_digest,
|
|
876
1145
|
checkpoint_json
|
|
877
1146
|
FROM effect_agent_checkpoints
|
|
878
|
-
WHERE
|
|
1147
|
+
WHERE thread_id = ${checkpoint.threadId}
|
|
879
1148
|
AND through_sequence = ${checkpoint.throughSequence}
|
|
880
1149
|
`.pipe(Effect.mapError(storageError("read idempotent checkpoint")));
|
|
1150
|
+
|
|
881
1151
|
const existing = yield* decodeRows(
|
|
882
1152
|
Schema.Array(CheckpointRow),
|
|
883
1153
|
"effect_agent_checkpoints",
|
|
884
|
-
`${checkpoint.
|
|
1154
|
+
`${checkpoint.threadId}/${checkpoint.throughSequence}`,
|
|
885
1155
|
checkpointRows,
|
|
886
1156
|
);
|
|
1157
|
+
|
|
887
1158
|
if (existing.length > 1) {
|
|
888
1159
|
return yield* DoStorageCorruptionError.make({
|
|
889
1160
|
table: "effect_agent_checkpoints",
|
|
890
|
-
rowKey: `${checkpoint.
|
|
1161
|
+
rowKey: `${checkpoint.threadId}/${checkpoint.throughSequence}`,
|
|
891
1162
|
message: "A checkpoint primary key returned more than one row.",
|
|
892
1163
|
});
|
|
893
1164
|
}
|
|
@@ -900,17 +1171,18 @@ const makeJournal = (
|
|
|
900
1171
|
message: "A different checkpoint already exists at this canonical sequence.",
|
|
901
1172
|
});
|
|
902
1173
|
}
|
|
1174
|
+
|
|
903
1175
|
return;
|
|
904
1176
|
}
|
|
905
1177
|
|
|
906
1178
|
yield* sql`
|
|
907
1179
|
INSERT INTO effect_agent_checkpoints (
|
|
908
|
-
|
|
1180
|
+
thread_id,
|
|
909
1181
|
through_sequence,
|
|
910
1182
|
tail_digest,
|
|
911
1183
|
checkpoint_json
|
|
912
1184
|
) VALUES (
|
|
913
|
-
${checkpoint.
|
|
1185
|
+
${checkpoint.threadId},
|
|
914
1186
|
${checkpoint.throughSequence},
|
|
915
1187
|
${checkpoint.tailDigest},
|
|
916
1188
|
${checkpoint.checkpointJson}
|
|
@@ -921,44 +1193,47 @@ const makeJournal = (
|
|
|
921
1193
|
});
|
|
922
1194
|
|
|
923
1195
|
const loadCheckpoint = Effect.fn("DoJournal.loadCheckpoint")(function* (
|
|
924
|
-
|
|
1196
|
+
threadId: string,
|
|
925
1197
|
atOrBeforeSequence: CanonicalSequence,
|
|
926
1198
|
) {
|
|
927
1199
|
const rows = yield* sql<Record<string, unknown>>`
|
|
928
1200
|
SELECT
|
|
929
|
-
|
|
1201
|
+
thread_id,
|
|
930
1202
|
through_sequence,
|
|
931
1203
|
tail_digest,
|
|
932
1204
|
checkpoint_json
|
|
933
1205
|
FROM effect_agent_checkpoints
|
|
934
|
-
WHERE
|
|
1206
|
+
WHERE thread_id = ${threadId}
|
|
935
1207
|
AND through_sequence <= ${atOrBeforeSequence}
|
|
936
1208
|
ORDER BY through_sequence DESC
|
|
937
1209
|
LIMIT 1
|
|
938
1210
|
`.pipe(Effect.mapError(storageError("load checkpoint")));
|
|
1211
|
+
|
|
939
1212
|
return yield* decodeRows(
|
|
940
1213
|
Schema.Array(CheckpointRow),
|
|
941
1214
|
"effect_agent_checkpoints",
|
|
942
|
-
`${
|
|
1215
|
+
`${threadId}<=${atOrBeforeSequence}`,
|
|
943
1216
|
rows,
|
|
944
1217
|
);
|
|
945
1218
|
});
|
|
946
1219
|
|
|
947
1220
|
const getTailDigestAt = Effect.fn("DoJournal.getTailDigestAt")(function* (
|
|
948
|
-
|
|
1221
|
+
threadId: string,
|
|
949
1222
|
sequence: CanonicalSequence,
|
|
950
1223
|
) {
|
|
951
1224
|
if (sequence === 0) {
|
|
952
|
-
const
|
|
953
|
-
|
|
1225
|
+
const threads = yield* getThread(threadId);
|
|
1226
|
+
|
|
1227
|
+
return threads.length === 0
|
|
954
1228
|
? []
|
|
955
|
-
: [
|
|
1229
|
+
: [threads[0].tail_sequence === 0 ? threads[0].tail_digest : undefined].filter(
|
|
956
1230
|
(value): value is string => value !== undefined,
|
|
957
1231
|
);
|
|
958
1232
|
}
|
|
1233
|
+
|
|
959
1234
|
const rows = yield* sql<Record<string, unknown>>`
|
|
960
1235
|
SELECT
|
|
961
|
-
|
|
1236
|
+
thread_id,
|
|
962
1237
|
batch_id,
|
|
963
1238
|
first_sequence,
|
|
964
1239
|
last_sequence,
|
|
@@ -966,15 +1241,17 @@ const makeJournal = (
|
|
|
966
1241
|
tail_digest,
|
|
967
1242
|
batch_json
|
|
968
1243
|
FROM effect_agent_canonical_batches
|
|
969
|
-
WHERE
|
|
1244
|
+
WHERE thread_id = ${threadId}
|
|
970
1245
|
AND last_sequence = ${sequence}
|
|
971
1246
|
`.pipe(Effect.mapError(storageError("read canonical digest at sequence")));
|
|
1247
|
+
|
|
972
1248
|
const batches = yield* decodeRows(
|
|
973
1249
|
Schema.Array(BatchRow),
|
|
974
1250
|
"effect_agent_canonical_batches",
|
|
975
|
-
`${
|
|
1251
|
+
`${threadId}/${sequence}`,
|
|
976
1252
|
rows,
|
|
977
1253
|
);
|
|
1254
|
+
|
|
978
1255
|
return batches.map((batch) => batch.tail_digest);
|
|
979
1256
|
});
|
|
980
1257
|
|
|
@@ -982,19 +1259,20 @@ const makeJournal = (
|
|
|
982
1259
|
return yield* sql
|
|
983
1260
|
.withTransaction(
|
|
984
1261
|
Effect.gen(function* () {
|
|
985
|
-
const
|
|
1262
|
+
const threads = yield* sql<Record<string, unknown>>`
|
|
986
1263
|
SELECT
|
|
987
|
-
|
|
1264
|
+
thread_id,
|
|
988
1265
|
created_at,
|
|
989
1266
|
tail_sequence,
|
|
990
1267
|
tail_digest,
|
|
991
1268
|
producer_epoch
|
|
992
|
-
FROM
|
|
993
|
-
ORDER BY
|
|
994
|
-
`.pipe(Effect.mapError(storageError("scan
|
|
1269
|
+
FROM effect_agent_threads
|
|
1270
|
+
ORDER BY thread_id
|
|
1271
|
+
`.pipe(Effect.mapError(storageError("scan threads")));
|
|
1272
|
+
|
|
995
1273
|
const batches = yield* sql<Record<string, unknown>>`
|
|
996
1274
|
SELECT
|
|
997
|
-
|
|
1275
|
+
thread_id,
|
|
998
1276
|
batch_id,
|
|
999
1277
|
first_sequence,
|
|
1000
1278
|
last_sequence,
|
|
@@ -1002,33 +1280,36 @@ const makeJournal = (
|
|
|
1002
1280
|
tail_digest,
|
|
1003
1281
|
batch_json
|
|
1004
1282
|
FROM effect_agent_canonical_batches
|
|
1005
|
-
ORDER BY
|
|
1283
|
+
ORDER BY thread_id, first_sequence
|
|
1006
1284
|
`.pipe(Effect.mapError(storageError("scan canonical batches")));
|
|
1285
|
+
|
|
1007
1286
|
const records = yield* sql<Record<string, unknown>>`
|
|
1008
1287
|
SELECT
|
|
1009
|
-
|
|
1288
|
+
thread_id,
|
|
1010
1289
|
sequence,
|
|
1011
1290
|
record_id,
|
|
1012
1291
|
batch_id,
|
|
1013
1292
|
record_json
|
|
1014
1293
|
FROM effect_agent_canonical_records
|
|
1015
|
-
ORDER BY
|
|
1294
|
+
ORDER BY thread_id, sequence
|
|
1016
1295
|
`.pipe(Effect.mapError(storageError("scan canonical records")));
|
|
1296
|
+
|
|
1017
1297
|
const checkpoints = yield* sql<Record<string, unknown>>`
|
|
1018
1298
|
SELECT
|
|
1019
|
-
|
|
1299
|
+
thread_id,
|
|
1020
1300
|
through_sequence,
|
|
1021
1301
|
tail_digest,
|
|
1022
1302
|
checkpoint_json
|
|
1023
1303
|
FROM effect_agent_checkpoints
|
|
1024
|
-
ORDER BY
|
|
1304
|
+
ORDER BY thread_id, through_sequence
|
|
1025
1305
|
`.pipe(Effect.mapError(storageError("scan checkpoints")));
|
|
1306
|
+
|
|
1026
1307
|
return {
|
|
1027
|
-
|
|
1028
|
-
Schema.Array(
|
|
1029
|
-
"
|
|
1308
|
+
threads: yield* decodeRows(
|
|
1309
|
+
Schema.Array(ThreadRow),
|
|
1310
|
+
"effect_agent_threads",
|
|
1030
1311
|
"startup_scan",
|
|
1031
|
-
|
|
1312
|
+
threads,
|
|
1032
1313
|
),
|
|
1033
1314
|
batches: yield* decodeRows(
|
|
1034
1315
|
Schema.Array(BatchRow),
|
|
@@ -1061,8 +1342,8 @@ const makeJournal = (
|
|
|
1061
1342
|
return {
|
|
1062
1343
|
append,
|
|
1063
1344
|
checkValueBound,
|
|
1064
|
-
|
|
1065
|
-
|
|
1345
|
+
exportThread,
|
|
1346
|
+
getThread,
|
|
1066
1347
|
getTailDigestAt,
|
|
1067
1348
|
loadCheckpoint,
|
|
1068
1349
|
materialize,
|