@earendil-works/pi-durable 1.4.0 → 1.5.1
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/CHANGELOG.md +204 -0
- package/README.md +195 -85
- package/dist/codemode/codemode.d.ts +6 -5
- package/dist/codemode/codemode.d.ts.map +1 -1
- package/dist/codemode/codemode.js +10 -49
- package/dist/codemode/codemode.js.map +1 -1
- package/dist/codemode/models.d.ts +2 -1
- package/dist/codemode/models.d.ts.map +1 -1
- package/dist/codemode/models.js +4 -4
- package/dist/codemode/models.js.map +1 -1
- package/dist/documents.d.ts +1 -1
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +1 -1
- package/dist/documents.js.map +1 -1
- package/dist/entries.d.ts +1 -1
- package/dist/entries.d.ts.map +1 -1
- package/dist/entries.js.map +1 -1
- package/dist/env/index.d.ts +13 -11
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/node-watch.d.ts +4 -4
- package/dist/env/node-watch.d.ts.map +1 -1
- package/dist/env/node-watch.js +18 -16
- package/dist/env/node-watch.js.map +1 -1
- package/dist/env/node.d.ts +3 -3
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +1 -1
- package/dist/env/node.js.map +1 -1
- package/dist/errors.d.ts +18 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +26 -3
- package/dist/errors.js.map +1 -1
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +37 -3
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.d.ts +8 -3
- package/dist/harness/compaction.d.ts.map +1 -1
- package/dist/harness/compaction.js +40 -11
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +24 -8
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +71 -25
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/define.d.ts +8 -4
- package/dist/harness/define.d.ts.map +1 -1
- package/dist/harness/define.js +4 -0
- package/dist/harness/define.js.map +1 -1
- package/dist/harness/generation.d.ts +39 -6
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +451 -100
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts +3 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +87 -62
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +7 -2
- package/dist/harness/inbox.d.ts.map +1 -1
- package/dist/harness/inbox.js +5 -2
- package/dist/harness/inbox.js.map +1 -1
- package/dist/harness/json.d.ts +21 -5
- package/dist/harness/json.d.ts.map +1 -1
- package/dist/harness/json.js +23 -5
- package/dist/harness/json.js.map +1 -1
- package/dist/harness/live.d.ts +124 -17
- package/dist/harness/live.d.ts.map +1 -1
- package/dist/harness/live.js +93 -12
- package/dist/harness/live.js.map +1 -1
- package/dist/harness/partial-changes.d.ts +54 -0
- package/dist/harness/partial-changes.d.ts.map +1 -0
- package/dist/harness/partial-changes.js +163 -0
- package/dist/harness/partial-changes.js.map +1 -0
- package/dist/harness/prompt.d.ts +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/provider.d.ts +1 -1
- package/dist/harness/provider.d.ts.map +1 -1
- package/dist/harness/provider.js.map +1 -1
- package/dist/harness/registry-reader.d.ts +11 -0
- package/dist/harness/registry-reader.d.ts.map +1 -0
- package/dist/harness/registry-reader.js +25 -0
- package/dist/harness/registry-reader.js.map +1 -0
- package/dist/harness/scheduler.d.ts +48 -20
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +1006 -380
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +18 -11
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +63 -26
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +14 -9
- package/dist/harness/task-graph.d.ts.map +1 -1
- package/dist/harness/task-graph.js +38 -28
- package/dist/harness/task-graph.js.map +1 -1
- package/dist/harness/telemetry.d.ts +11 -11
- package/dist/harness/telemetry.d.ts.map +1 -1
- package/dist/harness/telemetry.js +7 -7
- package/dist/harness/telemetry.js.map +1 -1
- package/dist/harness/tool.d.ts +3 -1
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +172 -56
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/transcript.d.ts +72 -0
- package/dist/harness/transcript.d.ts.map +1 -0
- package/dist/harness/transcript.js +264 -0
- package/dist/harness/transcript.js.map +1 -0
- package/dist/harness/types.d.ts +237 -61
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage.d.ts +2 -1
- package/dist/harness/usage.d.ts.map +1 -1
- package/dist/harness/usage.js +1 -1
- package/dist/harness/usage.js.map +1 -1
- package/dist/harness/util.d.ts +4 -2
- package/dist/harness/util.d.ts.map +1 -1
- package/dist/harness/util.js +9 -0
- package/dist/harness/util.js.map +1 -1
- package/dist/harness/view.d.ts +44 -22
- package/dist/harness/view.d.ts.map +1 -1
- package/dist/harness/view.js +130 -71
- package/dist/harness/view.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/search.d.ts +19 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +107 -44
- package/dist/search.js.map +1 -1
- package/dist/session/cached-storage.d.ts +72 -0
- package/dist/session/cached-storage.d.ts.map +1 -0
- package/dist/session/cached-storage.js +269 -0
- package/dist/session/cached-storage.js.map +1 -0
- package/dist/session/conversation-tails.d.ts +33 -0
- package/dist/session/conversation-tails.d.ts.map +1 -0
- package/dist/session/conversation-tails.js +101 -0
- package/dist/session/conversation-tails.js.map +1 -0
- package/dist/session/forks.d.ts +1 -1
- package/dist/session/forks.d.ts.map +1 -1
- package/dist/session/forks.js.map +1 -1
- package/dist/session/keys.d.ts +69 -0
- package/dist/session/keys.d.ts.map +1 -0
- package/dist/session/keys.js +161 -0
- package/dist/session/keys.js.map +1 -0
- package/dist/session/session.d.ts +169 -26
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +1029 -171
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +84 -5
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +227 -40
- package/dist/session/transaction.js.map +1 -1
- package/dist/session/writer.d.ts +74 -0
- package/dist/session/writer.d.ts.map +1 -0
- package/dist/session/writer.js +294 -0
- package/dist/session/writer.js.map +1 -0
- package/dist/storage/indexeddb.d.ts +12 -3
- package/dist/storage/indexeddb.d.ts.map +1 -1
- package/dist/storage/indexeddb.js +91 -41
- package/dist/storage/indexeddb.js.map +1 -1
- package/dist/storage/jsonl/index.d.ts +1 -1
- package/dist/storage/jsonl/index.d.ts.map +1 -1
- package/dist/storage/jsonl/index.js +1 -1
- package/dist/storage/jsonl/index.js.map +1 -1
- package/dist/storage/jsonl/node.d.ts +3 -3
- package/dist/storage/jsonl/node.d.ts.map +1 -1
- package/dist/storage/jsonl/node.js +2 -2
- package/dist/storage/jsonl/node.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +46 -5
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +158 -43
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.d.ts +9 -3
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +28 -2
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/commit.d.ts +52 -0
- package/dist/storage/sqlite/commit.d.ts.map +1 -0
- package/dist/storage/sqlite/commit.js +371 -0
- package/dist/storage/sqlite/commit.js.map +1 -0
- package/dist/storage/sqlite/rows.d.ts +33 -0
- package/dist/storage/sqlite/rows.d.ts.map +1 -0
- package/dist/storage/sqlite/rows.js +53 -0
- package/dist/storage/sqlite/rows.js.map +1 -0
- package/dist/storage/sqlite/statements.d.ts +13 -0
- package/dist/storage/sqlite/statements.d.ts.map +1 -0
- package/dist/storage/sqlite/statements.js +88 -0
- package/dist/storage/sqlite/statements.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +23 -10
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +120 -261
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/storage/store/commit.d.ts +33 -0
- package/dist/storage/store/commit.d.ts.map +1 -0
- package/dist/storage/store/commit.js +286 -0
- package/dist/storage/store/commit.js.map +1 -0
- package/dist/storage/store/documents.d.ts +43 -0
- package/dist/storage/store/documents.d.ts.map +1 -0
- package/dist/storage/store/documents.js +102 -0
- package/dist/storage/store/documents.js.map +1 -0
- package/dist/storage/store/index.d.ts +2 -0
- package/dist/storage/store/index.d.ts.map +1 -0
- package/dist/storage/store/index.js +2 -0
- package/dist/storage/store/index.js.map +1 -0
- package/dist/storage/store/queries.d.ts +49 -0
- package/dist/storage/store/queries.d.ts.map +1 -0
- package/dist/storage/store/queries.js +93 -0
- package/dist/storage/store/queries.js.map +1 -0
- package/dist/storage/store/storage.d.ts +60 -0
- package/dist/storage/store/storage.d.ts.map +1 -0
- package/dist/storage/store/storage.js +438 -0
- package/dist/storage/store/storage.js.map +1 -0
- package/dist/storage/store/tables.d.ts +54 -0
- package/dist/storage/store/tables.d.ts.map +1 -0
- package/dist/storage/store/tables.js +108 -0
- package/dist/storage/store/tables.js.map +1 -0
- package/dist/testing/commit-writes.d.ts +5 -0
- package/dist/testing/commit-writes.d.ts.map +1 -0
- package/dist/testing/commit-writes.js +12 -0
- package/dist/testing/commit-writes.js.map +1 -0
- package/dist/testing/env-conformance.js +7 -7
- package/dist/testing/env-conformance.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/storage-benchmark.d.ts.map +1 -1
- package/dist/testing/storage-benchmark.js +18 -17
- package/dist/testing/storage-benchmark.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +354 -69
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/tool-search/tool-search.d.ts +8 -14
- package/dist/tool-search/tool-search.d.ts.map +1 -1
- package/dist/tool-search/tool-search.js +25 -13
- package/dist/tool-search/tool-search.js.map +1 -1
- package/dist/tools/bash.d.ts +13 -13
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +16 -15
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +7 -7
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +9 -9
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/file-mutation-queue.d.ts +1 -1
- package/dist/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/tools/file-mutation-queue.js.map +1 -1
- package/dist/tools/index.d.ts +32 -32
- package/dist/tools/path-utils.d.ts +1 -1
- package/dist/tools/path-utils.d.ts.map +1 -1
- package/dist/tools/path-utils.js.map +1 -1
- package/dist/tools/read.d.ts +6 -6
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +19 -8
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +4 -4
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +5 -5
- package/dist/tools/write.js.map +1 -1
- package/dist/types.d.ts +228 -78
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +19 -12
- package/dist/harness/events.d.ts +0 -186
- package/dist/harness/events.d.ts.map +0 -1
- package/dist/harness/events.js +0 -373
- package/dist/harness/events.js.map +0 -1
- package/dist/session/observation.d.ts +0 -42
- package/dist/session/observation.d.ts.map +0 -1
- package/dist/session/observation.js +0 -281
- package/dist/session/observation.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/storage/jsonl/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/storage/jsonl/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,8 @@ import { MemoryStorage } from "../memory.js";
|
|
|
4
4
|
const FORMAT_VERSION = 1;
|
|
5
5
|
const MAIN_FILE = "main.jsonl";
|
|
6
6
|
const RECLAIM_SUFFIX = ".reclaim";
|
|
7
|
+
/** Read-only opens retried after racing a writer's commits; each retry means another commit landed meanwhile. */
|
|
8
|
+
const READ_ONLY_OPEN_ATTEMPTS = 5;
|
|
7
9
|
const textDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
8
10
|
export class JsonlCorruptionError extends Error {
|
|
9
11
|
constructor(message, cause) {
|
|
@@ -11,6 +13,13 @@ export class JsonlCorruptionError extends Error {
|
|
|
11
13
|
this.name = "JsonlCorruptionError";
|
|
12
14
|
}
|
|
13
15
|
}
|
|
16
|
+
/** `commit()` on a storage opened with `readOnly: true`. Nothing was written. */
|
|
17
|
+
export class JsonlStorageReadOnlyError extends Error {
|
|
18
|
+
constructor() {
|
|
19
|
+
super("JSONL storage is open read-only");
|
|
20
|
+
this.name = "JsonlStorageReadOnlyError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
14
23
|
export class JsonlStoragePoisonedError extends Error {
|
|
15
24
|
constructor(cause) {
|
|
16
25
|
super("JSONL storage is poisoned and must be reopened", { cause });
|
|
@@ -147,38 +156,122 @@ export class JsonlStorage {
|
|
|
147
156
|
directory;
|
|
148
157
|
mainPath;
|
|
149
158
|
fsync;
|
|
159
|
+
readOnly;
|
|
150
160
|
memory = new MemoryStorage();
|
|
151
161
|
currentOnlyDocuments = new Set();
|
|
152
162
|
liveTaskSidecars = new Set();
|
|
153
163
|
closed = false;
|
|
164
|
+
/** Bytes of `main.jsonl` recovery read (`mainChanged()`). */
|
|
165
|
+
mainSize = 0;
|
|
154
166
|
poisonError;
|
|
167
|
+
/** Settles once the batch being appended is; its batches are in memory already (`commit()`). */
|
|
168
|
+
writing;
|
|
155
169
|
constructor(fs, directory, mainPath, options) {
|
|
156
170
|
this.fs = fs;
|
|
157
171
|
this.directory = directory;
|
|
158
172
|
this.mainPath = mainPath;
|
|
159
173
|
this.fsync = options.fsync ?? false;
|
|
174
|
+
this.readOnly = options.readOnly ?? false;
|
|
160
175
|
}
|
|
161
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* Open or create a JSONL storage directory using the supplied filesystem. With `readOnly`, open an existing one
|
|
178
|
+
* without writing; a recovery that fails while `main.jsonl` changes raced a writer's commit and is retried.
|
|
179
|
+
*/
|
|
162
180
|
static async open(directory, fs, context, options = {}) {
|
|
163
181
|
const absolute = await fs.absolutePath(directory, context);
|
|
164
182
|
if (!absolute.ok)
|
|
165
183
|
throw errorFromFile("path resolution", absolute.error);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
if (options.readOnly !== true) {
|
|
185
|
+
const created = await fs.createDir(absolute.value, { recursive: true }, context);
|
|
186
|
+
if (!created.ok)
|
|
187
|
+
throw errorFromFile("directory creation", created.error);
|
|
188
|
+
}
|
|
169
189
|
const mainPathResult = await fs.joinPath([absolute.value, MAIN_FILE], context);
|
|
170
190
|
if (!mainPathResult.ok)
|
|
171
191
|
throw errorFromFile("path join", mainPathResult.error);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
192
|
+
for (let attempt = 1;; attempt++) {
|
|
193
|
+
const storage = new JsonlStorage(fs, absolute.value, mainPathResult.value, options);
|
|
194
|
+
try {
|
|
195
|
+
await storage.recover(context);
|
|
196
|
+
return storage;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (!storage.readOnly ||
|
|
200
|
+
attempt === READ_ONLY_OPEN_ATTEMPTS ||
|
|
201
|
+
!(error instanceof JsonlCorruptionError) ||
|
|
202
|
+
!(await storage.mainChanged(context))) {
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
175
207
|
}
|
|
176
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Commit `batches` with one append per affected file: every batch's sidecar records first, then their markers in one
|
|
210
|
+
* write of `main.jsonl`. Each batch is checked against the state the earlier ones left, so memory applies each one
|
|
211
|
+
* as it passes, and reads wait until the appends are done; the first batch refused ends the batch. A torn append of
|
|
212
|
+
* the markers recovers a shorter complete prefix; any failure once a batch applied poisons the storage.
|
|
213
|
+
*/
|
|
214
|
+
async commit(batches, receive, context) {
|
|
177
215
|
this.assertUsable();
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
const
|
|
216
|
+
if (this.readOnly)
|
|
217
|
+
throw new JsonlStorageReadOnlyError();
|
|
218
|
+
const seqs = [];
|
|
219
|
+
const encoded = [];
|
|
220
|
+
const reclamations = new Map();
|
|
221
|
+
let refused;
|
|
222
|
+
const writing = Promise.withResolvers();
|
|
223
|
+
this.writing = writing.promise;
|
|
224
|
+
try {
|
|
225
|
+
for (const writes of batches) {
|
|
226
|
+
let prepared;
|
|
227
|
+
try {
|
|
228
|
+
prepared = this.memory.prepareCommit(writes);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
refused = { error };
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
const commit = this.encodeCommit(prepared.seq, prepared.writes);
|
|
235
|
+
// A sidecar an earlier batch replaces keeps what the later ones append, unless they replace it again.
|
|
236
|
+
for (const [file, content] of commit.sidecars) {
|
|
237
|
+
const replacement = reclamations.get(file);
|
|
238
|
+
if (replacement !== undefined)
|
|
239
|
+
reclamations.set(file, replacement + content);
|
|
240
|
+
}
|
|
241
|
+
for (const [file, content] of this.planReclamations(prepared.writes, commit))
|
|
242
|
+
reclamations.set(file, content);
|
|
243
|
+
seqs.push(prepared.apply());
|
|
244
|
+
this.adoptSidecarState(prepared.writes);
|
|
245
|
+
encoded.push(commit);
|
|
246
|
+
}
|
|
247
|
+
if (encoded.length > 0)
|
|
248
|
+
await this.append(encoded, context);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
// Memory runs ahead of the file: once a batch applied, any failure poisons the storage before the reads
|
|
252
|
+
// waiting on the append go on, so none of them returns what is not stored.
|
|
253
|
+
if (seqs.length === 0)
|
|
254
|
+
throw error;
|
|
255
|
+
throw this.poison(error instanceof Error ? error : new Error("JSONL commit failed", { cause: error }));
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
this.writing = undefined;
|
|
259
|
+
writing.resolve();
|
|
260
|
+
}
|
|
261
|
+
// The markers are appended: the batches are durable. Reclaiming sidecars comes after the receipts.
|
|
262
|
+
for (const [index, seq] of seqs.entries())
|
|
263
|
+
receive(index, seq);
|
|
264
|
+
await this.reclaimSidecars(reclamations, context);
|
|
265
|
+
return refused === undefined ? { stored: seqs.length } : { stored: seqs.length, refused: refused.error };
|
|
266
|
+
}
|
|
267
|
+
/** Append the sidecar records of `commits`, flushed with `fsync`, then their markers in one write of `main.jsonl`. */
|
|
268
|
+
async append(commits, context) {
|
|
269
|
+
const contents = new Map();
|
|
270
|
+
for (const commit of commits) {
|
|
271
|
+
for (const [file, content] of commit.sidecars)
|
|
272
|
+
contents.set(file, (contents.get(file) ?? "") + content);
|
|
273
|
+
}
|
|
274
|
+
const sidecars = await Promise.all([...contents].map(async ([file, content]) => ({
|
|
182
275
|
file,
|
|
183
276
|
content,
|
|
184
277
|
path: await this.resolveFile(file, context),
|
|
@@ -195,59 +288,56 @@ export class JsonlStorage {
|
|
|
195
288
|
throw this.poison(errorFromFile(`flush of ${sidecar.file}`, result.error));
|
|
196
289
|
}
|
|
197
290
|
}
|
|
198
|
-
const
|
|
291
|
+
const markers = commits.length === 1 ? commits[0].marker : commits.map((commit) => commit.marker).join("");
|
|
292
|
+
const marker = await this.fs.appendFile(this.mainPath, markers, context);
|
|
199
293
|
if (!marker.ok)
|
|
200
294
|
throw this.poison(errorFromFile(`append to ${MAIN_FILE}`, marker.error));
|
|
201
|
-
const seq = prepared.apply();
|
|
202
|
-
this.adoptSidecarState(prepared.writes);
|
|
203
|
-
await this.reclaimSidecars(reclamations, context);
|
|
204
|
-
return seq;
|
|
205
295
|
}
|
|
206
296
|
async mintId() {
|
|
207
297
|
return this.store.mintId();
|
|
208
298
|
}
|
|
209
299
|
async conversation(id, context) {
|
|
210
|
-
return this.
|
|
300
|
+
return (await this.readable()).conversation(id, context);
|
|
211
301
|
}
|
|
212
302
|
async scanConversations(query, limit, cursor, context) {
|
|
213
|
-
return this.
|
|
303
|
+
return (await this.readable()).scanConversations(query, limit, cursor, context);
|
|
214
304
|
}
|
|
215
305
|
async entry(idOrConversationId, idOrContext, context) {
|
|
216
306
|
if (context === undefined)
|
|
217
|
-
return this.
|
|
307
|
+
return (await this.readable()).entry(idFromNumber(idOrConversationId), idOrContext);
|
|
218
308
|
if (typeof idOrContext !== "number")
|
|
219
309
|
throw new StorageRequestError("Storage.entry() requires an entry ID");
|
|
220
|
-
return this.
|
|
310
|
+
return (await this.readable()).entry(idFromNumber(idOrConversationId), idFromNumber(idOrContext), context);
|
|
221
311
|
}
|
|
222
312
|
async findLatestHeadMarker(conversationId, atOrBeforeEntryId, context) {
|
|
223
|
-
return this.
|
|
313
|
+
return (await this.readable()).findLatestHeadMarker(conversationId, atOrBeforeEntryId, context);
|
|
224
314
|
}
|
|
225
315
|
async scanEntries(query, limit, cursor, context) {
|
|
226
|
-
return this.
|
|
316
|
+
return (await this.readable()).scanEntries(query, limit, cursor, context);
|
|
227
317
|
}
|
|
228
318
|
async task(id, context) {
|
|
229
|
-
return this.
|
|
319
|
+
return (await this.readable()).task(id, context);
|
|
230
320
|
}
|
|
231
321
|
async scanTasks(query, limit, cursor, context) {
|
|
232
|
-
return this.
|
|
322
|
+
return (await this.readable()).scanTasks(query, limit, cursor, context);
|
|
233
323
|
}
|
|
234
324
|
async submission(id, context) {
|
|
235
|
-
return this.
|
|
325
|
+
return (await this.readable()).submission(id, context);
|
|
236
326
|
}
|
|
237
327
|
async scanSubmissions(query, limit, cursor, context) {
|
|
238
|
-
return this.
|
|
328
|
+
return (await this.readable()).scanSubmissions(query, limit, cursor, context);
|
|
239
329
|
}
|
|
240
330
|
async submissionByRequest(conversationId, requestId, context) {
|
|
241
|
-
return this.
|
|
331
|
+
return (await this.readable()).submissionByRequest(conversationId, requestId, context);
|
|
242
332
|
}
|
|
243
333
|
async findDocument(address, at, context) {
|
|
244
|
-
return this.
|
|
334
|
+
return (await this.readable()).findDocument(address, at, context);
|
|
245
335
|
}
|
|
246
336
|
async document(id, at, context) {
|
|
247
|
-
return this.
|
|
337
|
+
return (await this.readable()).document(id, at, context);
|
|
248
338
|
}
|
|
249
339
|
async scanDocuments(query, limit, cursor, context) {
|
|
250
|
-
return this.
|
|
340
|
+
return (await this.readable()).scanDocuments(query, limit, cursor, context);
|
|
251
341
|
}
|
|
252
342
|
async close(context) {
|
|
253
343
|
if (this.closed)
|
|
@@ -412,11 +502,17 @@ export class JsonlStorage {
|
|
|
412
502
|
}
|
|
413
503
|
await this.fs.renameFile(temporaryPath.value, path.value, context);
|
|
414
504
|
}
|
|
505
|
+
/**
|
|
506
|
+
* Load the confirmed state. A writable open also repairs the files: it truncates torn lines and unconfirmed sidecar
|
|
507
|
+
* tails, removes leftover reclaim files, and reclaims sidecar records later commits made unnecessary. A read-only
|
|
508
|
+
* open skips the same records in memory only.
|
|
509
|
+
*/
|
|
415
510
|
async recover(context) {
|
|
416
511
|
const fs = this.fs;
|
|
417
512
|
const directory = this.directory;
|
|
418
513
|
const memory = this.memory;
|
|
419
|
-
const main = await
|
|
514
|
+
const main = await this.readLines(this.mainPath, MAIN_FILE, context, (text, line) => parseMainMarker(text, line));
|
|
515
|
+
this.mainSize = main.size;
|
|
420
516
|
let previousSeq = 0;
|
|
421
517
|
for (const marker of main.lines) {
|
|
422
518
|
if (marker.value.seq <= previousSeq) {
|
|
@@ -428,7 +524,7 @@ export class JsonlStorage {
|
|
|
428
524
|
if (!listed.ok)
|
|
429
525
|
throw errorFromFile("directory listing", listed.error);
|
|
430
526
|
for (const info of listed.value) {
|
|
431
|
-
if (info.kind === "file" && isReclaimFileName(info.name)) {
|
|
527
|
+
if (!this.readOnly && info.kind === "file" && isReclaimFileName(info.name)) {
|
|
432
528
|
await fs.remove(info.path, { force: true }, context);
|
|
433
529
|
}
|
|
434
530
|
}
|
|
@@ -442,7 +538,7 @@ export class JsonlStorage {
|
|
|
442
538
|
const pathResult = await fs.joinPath([directory, file], context);
|
|
443
539
|
if (!pathResult.ok)
|
|
444
540
|
throw errorFromFile("path join", pathResult.error);
|
|
445
|
-
const parsed = await
|
|
541
|
+
const parsed = await this.readLines(pathResult.value, file, context, (text, line) => parseSidecarRecord(text, file, line));
|
|
446
542
|
parsedFiles.set(file, parsed);
|
|
447
543
|
let previous;
|
|
448
544
|
for (const line of parsed.lines) {
|
|
@@ -578,7 +674,7 @@ export class JsonlStorage {
|
|
|
578
674
|
unconfirmedAt = line.start;
|
|
579
675
|
}
|
|
580
676
|
}
|
|
581
|
-
if (unconfirmedAt !== undefined) {
|
|
677
|
+
if (unconfirmedAt !== undefined && !this.readOnly) {
|
|
582
678
|
const truncated = await fs.truncateFile(parsed.path, unconfirmedAt, context);
|
|
583
679
|
if (!truncated.ok)
|
|
584
680
|
throw errorFromFile(`tail truncation of ${file}`, truncated.error);
|
|
@@ -603,7 +699,8 @@ export class JsonlStorage {
|
|
|
603
699
|
reclamations.set(file, retainedLines.map((line) => jsonLine(line.value)).join(""));
|
|
604
700
|
}
|
|
605
701
|
}
|
|
606
|
-
|
|
702
|
+
if (!this.readOnly)
|
|
703
|
+
await this.reclaimSidecars(reclamations, context);
|
|
607
704
|
for (const id of currentOnlyDocuments)
|
|
608
705
|
this.currentOnlyDocuments.add(id);
|
|
609
706
|
for (const [id, live] of finalTaskIsLive) {
|
|
@@ -624,20 +721,23 @@ export class JsonlStorage {
|
|
|
624
721
|
confirmed.add(key);
|
|
625
722
|
return line.value;
|
|
626
723
|
}
|
|
627
|
-
|
|
628
|
-
|
|
724
|
+
/** Parse the complete lines of `path`; a torn final line is truncated, or only skipped when read-only. */
|
|
725
|
+
async readLines(path, name, context, parse) {
|
|
726
|
+
const read = await this.fs.readBinaryFile(path, context);
|
|
629
727
|
if (!read.ok) {
|
|
630
728
|
if (read.error.code === "not_found")
|
|
631
|
-
return { path, lines: [] };
|
|
729
|
+
return { path, lines: [], size: 0 };
|
|
632
730
|
throw errorFromFile(`read of ${name}`, read.error);
|
|
633
731
|
}
|
|
634
732
|
const bytes = read.value;
|
|
635
733
|
let completeSize = bytes.length;
|
|
636
734
|
if (completeSize > 0 && bytes[completeSize - 1] !== 0x0a) {
|
|
637
735
|
completeSize = bytes.lastIndexOf(0x0a) + 1;
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
736
|
+
if (!this.readOnly) {
|
|
737
|
+
const truncated = await this.fs.truncateFile(path, completeSize, context);
|
|
738
|
+
if (!truncated.ok)
|
|
739
|
+
throw errorFromFile(`torn-line truncation of ${name}`, truncated.error);
|
|
740
|
+
}
|
|
641
741
|
}
|
|
642
742
|
const lines = [];
|
|
643
743
|
let start = 0;
|
|
@@ -656,7 +756,16 @@ export class JsonlStorage {
|
|
|
656
756
|
start = end + 1;
|
|
657
757
|
lineNumber++;
|
|
658
758
|
}
|
|
659
|
-
return { path, lines };
|
|
759
|
+
return { path, lines, size: bytes.length };
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Whether `main.jsonl` changed size since recovery read it. A writer reclaims a sidecar only after appending the
|
|
763
|
+
* marker that allows it, so sidecars read while `main.jsonl` stayed the same match its markers; a recovery that
|
|
764
|
+
* failed while it changed may have read a sidecar a newer commit reclaimed.
|
|
765
|
+
*/
|
|
766
|
+
async mainChanged(context) {
|
|
767
|
+
const info = await this.fs.fileInfo(this.mainPath, context);
|
|
768
|
+
return info.ok && info.value.size !== this.mainSize;
|
|
660
769
|
}
|
|
661
770
|
async resolveFile(file, context) {
|
|
662
771
|
const path = await this.fs.joinPath([this.directory, file], context);
|
|
@@ -668,6 +777,12 @@ export class JsonlStorage {
|
|
|
668
777
|
this.assertUsable();
|
|
669
778
|
return this.memory;
|
|
670
779
|
}
|
|
780
|
+
/** The state to read once no batch is being appended: memory runs ahead of the file while one is. */
|
|
781
|
+
async readable() {
|
|
782
|
+
while (this.writing !== undefined)
|
|
783
|
+
await this.writing;
|
|
784
|
+
return this.store;
|
|
785
|
+
}
|
|
671
786
|
poison(cause) {
|
|
672
787
|
this.poisonError ??= new JsonlStoragePoisonedError(cause);
|
|
673
788
|
return this.poisonError;
|