@earendil-works/pi-agent-core 0.84.3 → 0.85.0
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/README.md +4 -0
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +32 -25
- package/dist/agent-loop.js.map +1 -1
- package/dist/harness/agent-harness.d.ts +639 -384
- package/dist/harness/agent-harness.d.ts.map +1 -1
- package/dist/harness/agent-harness.js +5 -251
- package/dist/harness/agent-harness.js.map +1 -1
- package/dist/harness/compaction/branch-summarization.d.ts +12 -6
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/harness/compaction/branch-summarization.js +33 -26
- package/dist/harness/compaction/branch-summarization.js.map +1 -1
- package/dist/harness/compaction/compaction.d.ts +30 -8
- package/dist/harness/compaction/compaction.d.ts.map +1 -1
- package/dist/harness/compaction/compaction.js +53 -63
- package/dist/harness/compaction/compaction.js.map +1 -1
- package/dist/harness/config.d.ts +9 -0
- package/dist/harness/config.d.ts.map +1 -0
- package/dist/harness/config.js +27 -0
- package/dist/harness/config.js.map +1 -0
- package/dist/harness/context.d.ts +9 -0
- package/dist/harness/context.d.ts.map +1 -0
- package/dist/harness/context.js +13 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/harness/env/nodejs.d.ts +24 -28
- package/dist/harness/env/nodejs.d.ts.map +1 -1
- package/dist/harness/env/nodejs.js +242 -87
- package/dist/harness/env/nodejs.js.map +1 -1
- package/dist/harness/events.d.ts +20 -38
- package/dist/harness/events.d.ts.map +1 -1
- package/dist/harness/events.js +240 -55
- package/dist/harness/events.js.map +1 -1
- package/dist/harness/execution/assistant.d.ts +42 -0
- package/dist/harness/execution/assistant.d.ts.map +1 -0
- package/dist/harness/execution/assistant.js +82 -0
- package/dist/harness/execution/assistant.js.map +1 -0
- package/dist/harness/execution/effect-gate.d.ts +22 -0
- package/dist/harness/execution/effect-gate.d.ts.map +1 -0
- package/dist/harness/execution/effect-gate.js +49 -0
- package/dist/harness/execution/effect-gate.js.map +1 -0
- package/dist/harness/execution/tools.d.ts +67 -0
- package/dist/harness/execution/tools.d.ts.map +1 -0
- package/dist/harness/execution/tools.js +121 -0
- package/dist/harness/execution/tools.js.map +1 -0
- package/dist/harness/hooks.d.ts +38 -0
- package/dist/harness/hooks.d.ts.map +1 -0
- package/dist/harness/hooks.js +406 -0
- package/dist/harness/hooks.js.map +1 -0
- package/dist/harness/messages.d.ts +2 -2
- package/dist/harness/messages.d.ts.map +1 -1
- package/dist/harness/messages.js.map +1 -1
- package/dist/harness/prompt-templates.d.ts +3 -2
- package/dist/harness/prompt-templates.d.ts.map +1 -1
- package/dist/harness/prompt-templates.js +17 -17
- package/dist/harness/prompt-templates.js.map +1 -1
- package/dist/harness/result.d.ts +93 -0
- package/dist/harness/result.d.ts.map +1 -1
- package/dist/harness/result.js +40 -0
- package/dist/harness/result.js.map +1 -1
- package/dist/harness/runtime/drive/boundary.d.ts +28 -0
- package/dist/harness/runtime/drive/boundary.d.ts.map +1 -0
- package/dist/harness/runtime/drive/boundary.js +170 -0
- package/dist/harness/runtime/drive/boundary.js.map +1 -0
- package/dist/harness/runtime/drive/checkpoint.d.ts +8 -0
- package/dist/harness/runtime/drive/checkpoint.d.ts.map +1 -0
- package/dist/harness/runtime/drive/checkpoint.js +133 -0
- package/dist/harness/runtime/drive/checkpoint.js.map +1 -0
- package/dist/harness/runtime/drive/deferred.d.ts +15 -0
- package/dist/harness/runtime/drive/deferred.d.ts.map +1 -0
- package/dist/harness/runtime/drive/deferred.js +179 -0
- package/dist/harness/runtime/drive/deferred.js.map +1 -0
- package/dist/harness/runtime/drive/generation.d.ts +8 -0
- package/dist/harness/runtime/drive/generation.d.ts.map +1 -0
- package/dist/harness/runtime/drive/generation.js +205 -0
- package/dist/harness/runtime/drive/generation.js.map +1 -0
- package/dist/harness/runtime/drive/reconcile.d.ts +5 -0
- package/dist/harness/runtime/drive/reconcile.d.ts.map +1 -0
- package/dist/harness/runtime/drive/reconcile.js +140 -0
- package/dist/harness/runtime/drive/reconcile.js.map +1 -0
- package/dist/harness/runtime/drive/recovery.d.ts +8 -0
- package/dist/harness/runtime/drive/recovery.d.ts.map +1 -0
- package/dist/harness/runtime/drive/recovery.js +85 -0
- package/dist/harness/runtime/drive/recovery.js.map +1 -0
- package/dist/harness/runtime/drive/response.d.ts +23 -0
- package/dist/harness/runtime/drive/response.d.ts.map +1 -0
- package/dist/harness/runtime/drive/response.js +394 -0
- package/dist/harness/runtime/drive/response.js.map +1 -0
- package/dist/harness/runtime/drive/retry.d.ts +4 -0
- package/dist/harness/runtime/drive/retry.d.ts.map +1 -0
- package/dist/harness/runtime/drive/retry.js +37 -0
- package/dist/harness/runtime/drive/retry.js.map +1 -0
- package/dist/harness/runtime/drive/structural.d.ts +32 -0
- package/dist/harness/runtime/drive/structural.d.ts.map +1 -0
- package/dist/harness/runtime/drive/structural.js +929 -0
- package/dist/harness/runtime/drive/structural.js.map +1 -0
- package/dist/harness/runtime/drive/terminal.d.ts +7 -0
- package/dist/harness/runtime/drive/terminal.d.ts.map +1 -0
- package/dist/harness/runtime/drive/terminal.js +49 -0
- package/dist/harness/runtime/drive/terminal.js.map +1 -0
- package/dist/harness/runtime/drive/tool-placement.d.ts +14 -0
- package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -0
- package/dist/harness/runtime/drive/tool-placement.js +244 -0
- package/dist/harness/runtime/drive/tool-placement.js.map +1 -0
- package/dist/harness/runtime/drive/tools.d.ts +6 -0
- package/dist/harness/runtime/drive/tools.d.ts.map +1 -0
- package/dist/harness/runtime/drive/tools.js +449 -0
- package/dist/harness/runtime/drive/tools.js.map +1 -0
- package/dist/harness/runtime/drive.d.ts +6 -0
- package/dist/harness/runtime/drive.d.ts.map +1 -0
- package/dist/harness/runtime/drive.js +93 -0
- package/dist/harness/runtime/drive.js.map +1 -0
- package/dist/harness/runtime/harness.d.ts +59 -0
- package/dist/harness/runtime/harness.d.ts.map +1 -0
- package/dist/harness/runtime/harness.js +311 -0
- package/dist/harness/runtime/harness.js.map +1 -0
- package/dist/harness/runtime/index.d.ts +2 -0
- package/dist/harness/runtime/index.d.ts.map +1 -0
- package/dist/harness/runtime/index.js +2 -0
- package/dist/harness/runtime/index.js.map +1 -0
- package/dist/harness/runtime/lane.d.ts +101 -0
- package/dist/harness/runtime/lane.d.ts.map +1 -0
- package/dist/harness/runtime/lane.js +1575 -0
- package/dist/harness/runtime/lane.js.map +1 -0
- package/dist/harness/runtime/progress.d.ts +15 -0
- package/dist/harness/runtime/progress.d.ts.map +1 -0
- package/dist/harness/runtime/progress.js +66 -0
- package/dist/harness/runtime/progress.js.map +1 -0
- package/dist/harness/runtime/reducer.d.ts +5 -0
- package/dist/harness/runtime/reducer.d.ts.map +1 -0
- package/dist/harness/runtime/reducer.js +240 -0
- package/dist/harness/runtime/reducer.js.map +1 -0
- package/dist/harness/runtime/restore.d.ts +24 -0
- package/dist/harness/runtime/restore.d.ts.map +1 -0
- package/dist/harness/runtime/restore.js +117 -0
- package/dist/harness/runtime/restore.js.map +1 -0
- package/dist/harness/runtime/transcript.d.ts +21 -0
- package/dist/harness/runtime/transcript.d.ts.map +1 -0
- package/dist/harness/runtime/transcript.js +72 -0
- package/dist/harness/runtime/transcript.js.map +1 -0
- package/dist/harness/runtime/types.d.ts +105 -0
- package/dist/harness/runtime/types.d.ts.map +1 -0
- package/dist/harness/runtime/types.js +61 -0
- package/dist/harness/runtime/types.js.map +1 -0
- package/dist/harness/session/commit.d.ts +53 -0
- package/dist/harness/session/commit.d.ts.map +1 -0
- package/dist/harness/session/commit.js +57 -0
- package/dist/harness/session/commit.js.map +1 -0
- package/dist/harness/session/context.d.ts +6 -18
- package/dist/harness/session/context.d.ts.map +1 -1
- package/dist/harness/session/context.js +31 -49
- package/dist/harness/session/context.js.map +1 -1
- package/dist/harness/session/fork-policy.d.ts +13 -0
- package/dist/harness/session/fork-policy.d.ts.map +1 -0
- package/dist/harness/session/fork-policy.js +22 -0
- package/dist/harness/session/fork-policy.js.map +1 -0
- package/dist/harness/session/fork.d.ts +18 -0
- package/dist/harness/session/fork.d.ts.map +1 -0
- package/dist/harness/session/fork.js +127 -0
- package/dist/harness/session/fork.js.map +1 -0
- package/dist/harness/session/in-memory-storage-state.d.ts +39 -0
- package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -0
- package/dist/harness/session/in-memory-storage-state.js +216 -0
- package/dist/harness/session/in-memory-storage-state.js.map +1 -0
- package/dist/harness/session/index.d.ts +9 -5
- package/dist/harness/session/index.d.ts.map +1 -1
- package/dist/harness/session/index.js +7 -4
- package/dist/harness/session/index.js.map +1 -1
- package/dist/harness/session/jsonl/codec.d.ts +19 -8
- package/dist/harness/session/jsonl/codec.d.ts.map +1 -1
- package/dist/harness/session/jsonl/codec.js +34 -218
- package/dist/harness/session/jsonl/codec.js.map +1 -1
- package/dist/harness/session/jsonl/index.d.ts +4 -0
- package/dist/harness/session/jsonl/index.d.ts.map +1 -0
- package/dist/harness/session/jsonl/index.js +4 -0
- package/dist/harness/session/jsonl/index.js.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts +18 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.js +383 -0
- package/dist/harness/session/jsonl/legacy-v3.js.map +1 -0
- package/dist/harness/session/jsonl/repo.d.ts +26 -19
- package/dist/harness/session/jsonl/repo.d.ts.map +1 -1
- package/dist/harness/session/jsonl/repo.js +266 -156
- package/dist/harness/session/jsonl/repo.js.map +1 -1
- package/dist/harness/session/jsonl/storage.d.ts +39 -41
- package/dist/harness/session/jsonl/storage.d.ts.map +1 -1
- package/dist/harness/session/jsonl/storage.js +238 -181
- package/dist/harness/session/jsonl/storage.js.map +1 -1
- package/dist/harness/session/jsonl/types.d.ts +23 -22
- package/dist/harness/session/jsonl/types.d.ts.map +1 -1
- package/dist/harness/session/jsonl/types.js +2 -1
- package/dist/harness/session/jsonl/types.js.map +1 -1
- package/dist/harness/session/memory.d.ts +46 -40
- package/dist/harness/session/memory.d.ts.map +1 -1
- package/dist/harness/session/memory.js +346 -113
- package/dist/harness/session/memory.js.map +1 -1
- package/dist/harness/session/mutation-line.d.ts +8 -0
- package/dist/harness/session/mutation-line.d.ts.map +1 -0
- package/dist/harness/session/mutation-line.js +21 -0
- package/dist/harness/session/mutation-line.js.map +1 -0
- package/dist/harness/session/session.d.ts +78 -46
- package/dist/harness/session/session.d.ts.map +1 -1
- package/dist/harness/session/session.js +343 -209
- package/dist/harness/session/session.js.map +1 -1
- package/dist/harness/session/testing/benchmark/datasets.d.ts +12 -0
- package/dist/harness/session/testing/benchmark/datasets.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/datasets.js +24 -0
- package/dist/harness/session/testing/benchmark/datasets.js.map +1 -0
- package/dist/harness/session/testing/benchmark/session-repo.d.ts +42 -0
- package/dist/harness/session/testing/benchmark/session-repo.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/session-repo.js +127 -0
- package/dist/harness/session/testing/benchmark/session-repo.js.map +1 -0
- package/dist/harness/session/testing/benchmark/storage.d.ts +25 -0
- package/dist/harness/session/testing/benchmark/storage.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/storage.js +114 -0
- package/dist/harness/session/testing/benchmark/storage.js.map +1 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts +21 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance/session-repo.js +590 -0
- package/dist/harness/session/testing/conformance/session-repo.js.map +1 -0
- package/dist/harness/session/testing/conformance/storage.d.ts +4 -0
- package/dist/harness/session/testing/conformance/storage.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance/storage.js +600 -0
- package/dist/harness/session/testing/conformance/storage.js.map +1 -0
- package/dist/harness/session/testing/gating-storage.d.ts +26 -0
- package/dist/harness/session/testing/gating-storage.d.ts.map +1 -0
- package/dist/harness/session/testing/gating-storage.js +100 -0
- package/dist/harness/session/testing/gating-storage.js.map +1 -0
- package/dist/harness/session/testing/index.d.ts +9 -2
- package/dist/harness/session/testing/index.d.ts.map +1 -1
- package/dist/harness/session/testing/index.js +8 -1
- package/dist/harness/session/testing/index.js.map +1 -1
- package/dist/harness/session/testing/instrumented-storage.d.ts +11 -0
- package/dist/harness/session/testing/instrumented-storage.d.ts.map +1 -0
- package/dist/harness/session/testing/instrumented-storage.js +16 -0
- package/dist/harness/session/testing/instrumented-storage.js.map +1 -0
- package/dist/harness/session/testing/storage-decorator.d.ts +20 -0
- package/dist/harness/session/testing/storage-decorator.d.ts.map +1 -0
- package/dist/harness/session/testing/storage-decorator.js +41 -0
- package/dist/harness/session/testing/storage-decorator.js.map +1 -0
- package/dist/harness/session/testing/types.d.ts +5 -7
- package/dist/harness/session/testing/types.d.ts.map +1 -1
- package/dist/harness/session/testing/types.js.map +1 -1
- package/dist/harness/session/types.d.ts +424 -259
- package/dist/harness/session/types.d.ts.map +1 -1
- package/dist/harness/session/types.js +7 -8
- package/dist/harness/session/types.js.map +1 -1
- package/dist/harness/session/values.d.ts +95 -0
- package/dist/harness/session/values.d.ts.map +1 -0
- package/dist/harness/session/values.js +81 -0
- package/dist/harness/session/values.js.map +1 -0
- package/dist/harness/skills.d.ts +3 -2
- package/dist/harness/skills.d.ts.map +1 -1
- package/dist/harness/skills.js +29 -26
- package/dist/harness/skills.js.map +1 -1
- package/dist/harness/telemetry.d.ts +64 -43
- package/dist/harness/telemetry.d.ts.map +1 -1
- package/dist/harness/telemetry.js +45 -26
- package/dist/harness/telemetry.js.map +1 -1
- package/dist/harness/tools/bash.d.ts +4 -4
- package/dist/harness/tools/bash.d.ts.map +1 -1
- package/dist/harness/tools/bash.js +66 -93
- package/dist/harness/tools/bash.js.map +1 -1
- package/dist/harness/tools/edit.d.ts.map +1 -1
- package/dist/harness/tools/edit.js +10 -10
- package/dist/harness/tools/edit.js.map +1 -1
- package/dist/harness/tools/file-mutation-queue.d.ts +2 -1
- package/dist/harness/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/harness/tools/file-mutation-queue.js +5 -5
- package/dist/harness/tools/file-mutation-queue.js.map +1 -1
- package/dist/harness/tools/path-utils.d.ts +3 -2
- package/dist/harness/tools/path-utils.d.ts.map +1 -1
- package/dist/harness/tools/path-utils.js +5 -5
- package/dist/harness/tools/path-utils.js.map +1 -1
- package/dist/harness/tools/read.d.ts +2 -1
- package/dist/harness/tools/read.d.ts.map +1 -1
- package/dist/harness/tools/read.js +4 -6
- package/dist/harness/tools/read.js.map +1 -1
- package/dist/harness/tools/write.d.ts.map +1 -1
- package/dist/harness/tools/write.js +7 -7
- package/dist/harness/tools/write.js.map +1 -1
- package/dist/harness/types.d.ts +108 -45
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/utils/adaptive-publisher.d.ts +24 -0
- package/dist/harness/utils/adaptive-publisher.d.ts.map +1 -0
- package/dist/harness/utils/adaptive-publisher.js +79 -0
- package/dist/harness/utils/adaptive-publisher.js.map +1 -0
- package/dist/harness/utils/output-capture.d.ts +31 -0
- package/dist/harness/utils/output-capture.d.ts.map +1 -0
- package/dist/harness/utils/output-capture.js +217 -0
- package/dist/harness/utils/output-capture.js.map +1 -0
- package/dist/harness/utils/shell-output.d.ts +12 -5
- package/dist/harness/utils/shell-output.d.ts.map +1 -1
- package/dist/harness/utils/shell-output.js +64 -174
- package/dist/harness/utils/shell-output.js.map +1 -1
- package/dist/harness/utils/truncate.d.ts +1 -0
- package/dist/harness/utils/truncate.d.ts.map +1 -1
- package/dist/harness/utils/truncate.js +1 -1
- package/dist/harness/utils/truncate.js.map +1 -1
- package/dist/harness/utils/usage.d.ts +4 -0
- package/dist/harness/utils/usage.d.ts.map +1 -0
- package/dist/harness/utils/usage.js +33 -0
- package/dist/harness/utils/usage.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -6
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +40 -10
- package/dist/proxy.js.map +1 -1
- package/dist/search/index.d.ts +24 -16
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -1
- package/dist/search/index.js.map +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +25 -4
- package/dist/harness/reducer.d.ts +0 -100
- package/dist/harness/reducer.d.ts.map +0 -1
- package/dist/harness/reducer.js +0 -415
- package/dist/harness/reducer.js.map +0 -1
- package/dist/harness/session/jsonl/errors.d.ts +0 -9
- package/dist/harness/session/jsonl/errors.d.ts.map +0 -1
- package/dist/harness/session/jsonl/errors.js +0 -19
- package/dist/harness/session/jsonl/errors.js.map +0 -1
- package/dist/harness/session/jsonl.d.ts +0 -3
- package/dist/harness/session/jsonl.d.ts.map +0 -1
- package/dist/harness/session/jsonl.js +0 -2
- package/dist/harness/session/jsonl.js.map +0 -1
- package/dist/harness/session/state.d.ts +0 -65
- package/dist/harness/session/state.d.ts.map +0 -1
- package/dist/harness/session/state.js +0 -319
- package/dist/harness/session/state.js.map +0 -1
- package/dist/harness/session/testing/conformance.d.ts +0 -4
- package/dist/harness/session/testing/conformance.d.ts.map +0 -1
- package/dist/harness/session/testing/conformance.js +0 -762
- package/dist/harness/session/testing/conformance.js.map +0 -1
- package/dist/search/scanning.d.ts +0 -29
- package/dist/search/scanning.d.ts.map +0 -1
- package/dist/search/scanning.js +0 -103
- package/dist/search/scanning.js.map +0 -1
|
@@ -1,43 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
private appendMutation;
|
|
41
|
-
private applyMutation;
|
|
1
|
+
import type { Context } from "../../context.ts";
|
|
2
|
+
import { type ForkDestinationSnapshot, type ForkSourceSnapshot } from "../fork.ts";
|
|
3
|
+
import type { CommitResult, Entry, EntryScan, EntryStructure, SessionStats, Storage, StorageBranchScan, UsageRow, UsageScan, Write } from "../types.ts";
|
|
4
|
+
import type { ListElement, ListReadOptions, StoredValue, Value, ValueList } from "../values.ts";
|
|
5
|
+
import { type JsonlStorageHeader, type JsonlStorageOptions } from "./types.ts";
|
|
6
|
+
/** JSONL storage backed by an injected filesystem capability. */
|
|
7
|
+
export declare class JsonlStorage implements Storage {
|
|
8
|
+
private readonly fileSystem;
|
|
9
|
+
private readonly path;
|
|
10
|
+
private readonly now;
|
|
11
|
+
readonly header: JsonlStorageHeader;
|
|
12
|
+
private backing;
|
|
13
|
+
private readonly storageState;
|
|
14
|
+
private commitQueue;
|
|
15
|
+
private state;
|
|
16
|
+
private closePromise;
|
|
17
|
+
private constructor();
|
|
18
|
+
static create(options: JsonlStorageOptions, header: JsonlStorageHeader, initialWrites: Write[], context: Context): Promise<JsonlStorage>;
|
|
19
|
+
/** Atomically create storage from a complete prepared snapshot. */
|
|
20
|
+
static createFromForkSnapshot(options: JsonlStorageOptions, header: JsonlStorageHeader, snapshot: ForkDestinationSnapshot, context: Context): Promise<JsonlStorage>;
|
|
21
|
+
static open(options: JsonlStorageOptions, context: Context): Promise<JsonlStorage>;
|
|
22
|
+
private static openLegacyV3;
|
|
23
|
+
private replayCommitted;
|
|
24
|
+
commit(writes: Write[], context: Context): Promise<CommitResult>;
|
|
25
|
+
private applyCommit;
|
|
26
|
+
private upgradeLegacyV3ToV4;
|
|
27
|
+
getEntries(ids: string[], _context: Context): Promise<Map<string, Entry>>;
|
|
28
|
+
getValue<T>(address: Value<T>, _context: Context): Promise<StoredValue<T> | undefined>;
|
|
29
|
+
scanValues<T>(prefix: Value<T>, _context: Context): Promise<StoredValue<T>[]>;
|
|
30
|
+
readList<T>(address: ValueList<T>, options: ListReadOptions | undefined, _context: Context): Promise<ListElement<T>[]>;
|
|
31
|
+
scanBranch(query: StorageBranchScan, _context: Context): Promise<Entry[]>;
|
|
32
|
+
scanBranchStructure(query: StorageBranchScan, _context: Context): Promise<EntryStructure[]>;
|
|
33
|
+
scanEntries(query: EntryScan, _context: Context): Promise<Entry[]>;
|
|
34
|
+
scanUsage(query: UsageScan, _context: Context): Promise<UsageRow[]>;
|
|
35
|
+
getStats(_context: Context): Promise<SessionStats>;
|
|
36
|
+
private withImportedUsage;
|
|
37
|
+
/** Capture the state needed to fork at one serialized boundary between commits. */
|
|
38
|
+
captureForkSource(_context: Context): Promise<ForkSourceSnapshot>;
|
|
39
|
+
close(_context: Context): Promise<void>;
|
|
42
40
|
}
|
|
43
41
|
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/jsonl/storage.ts"],"names":[],"mappings":"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","sourcesContent":["import { type SessionMutation, SessionState } from \"../state.ts\";\nimport {\n\ttype BranchBounds,\n\ttype Entry,\n\ttype EntryQuery,\n\ttype ForkOptions,\n\ttype LanePointer,\n\ttype LaneRecord,\n\ttype LogItem,\n\ttype LogOptions,\n\ttype NewRecord,\n\ttype OperationStartedRecord,\n\ttype ProvisionedEntry,\n\ttype RecordQuery,\n\tSessionError,\n\ttype SessionStats,\n\ttype SessionStorage,\n} from \"../types.ts\";\nimport { encodeHeader, encodeMutation, metadataFromHeader, parseHeader, parseMutation } from \"./codec.ts\";\nimport { fileResult, invalidFile, JsonlDecodeError } from \"./errors.ts\";\nimport type { JsonlSessionMetadata, JsonlSessionRepoFileSystem, JsonlV4Header } from \"./types.ts\";\n\n/**\n * Build a complete sibling temporary file, then atomically rename it over the destination.\n * The populate callback must create or overwrite `tempPath` with the complete file. The\n * destination is untouched until the rename commits, so a process crash while populating\n * can leave only the ignored `.tmp` file behind.\n *\n * Rejects when population or rename fails. On rejection, temporary-file removal is\n * best-effort and the original error is preserved. Callers must serialize publications to\n * the same destination because they share its deterministic `.tmp` path.\n */\nasync function publishFileAtomically(\n\tfs: JsonlSessionRepoFileSystem,\n\tdestinationPath: string,\n\tpopulate: (tempPath: string) => Promise<void>,\n): Promise<void> {\n\tconst tempPath = `${destinationPath}.tmp`;\n\ttry {\n\t\tawait populate(tempPath);\n\t\tfileResult(await fs.renameFile(tempPath, destinationPath), `Failed to publish staged file ${destinationPath}`);\n\t} catch (error) {\n\t\tawait fs.remove(tempPath, { force: true });\n\t\tthrow error;\n\t}\n}\n\nexport class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata> {\n\tprivate readonly fs: JsonlSessionRepoFileSystem;\n\tprivate readonly metadata: JsonlSessionMetadata;\n\tprivate readonly state = new SessionState();\n\tprivate tail: Promise<void> = Promise.resolve();\n\n\tconstructor(fs: JsonlSessionRepoFileSystem, metadata: JsonlSessionMetadata) {\n\t\tthis.fs = fs;\n\t\tthis.metadata = structuredClone(metadata);\n\t}\n\n\tstatic async create(\n\t\tfs: JsonlSessionRepoFileSystem,\n\t\tpath: string,\n\t\theader: JsonlV4Header,\n\t): Promise<JsonlSessionStorage> {\n\t\tfileResult(await fs.writeFile(path, encodeHeader(header)), `Failed to initialize session ${path}`);\n\t\tconst fileInfo = fileResult(await fs.fileInfo(path), `Failed to read session metadata ${path}`);\n\t\treturn new JsonlSessionStorage(fs, metadataFromHeader(header, path, fileInfo.mtimeMs));\n\t}\n\n\tstatic async load(fs: JsonlSessionRepoFileSystem, path: string): Promise<JsonlSessionStorage> {\n\t\tconst content = fileResult(await fs.readTextFile(path), `Failed to read session ${path}`);\n\t\tconst physicalLines = content.split(\"\\n\");\n\t\tif (physicalLines.at(-1) === \"\") physicalLines.pop();\n\t\tif (physicalLines.length === 0 || !physicalLines[0]) {\n\t\t\tthrow invalidFile(path, 1, new JsonlDecodeError(\"schema\", \"is missing a header\"));\n\t\t}\n\t\tconst headerResult = parseHeader(physicalLines[0]);\n\t\tif (!headerResult.ok) throw invalidFile(path, 1, headerResult.error);\n\t\tconst fileInfo = fileResult(await fs.fileInfo(path), `Failed to read session metadata ${path}`);\n\t\tconst storage = new JsonlSessionStorage(fs, metadataFromHeader(headerResult.value, path, fileInfo.mtimeMs));\n\t\tfor (let index = 1; index < physicalLines.length; index++) {\n\t\t\tconst line = physicalLines[index]!;\n\t\t\tconst mutationResult = parseMutation(line);\n\t\t\tif (!mutationResult.ok) {\n\t\t\t\tconst isTornTail = index === physicalLines.length - 1 && mutationResult.error.kind === \"syntax\";\n\t\t\t\tif (isTornTail) {\n\t\t\t\t\t// Drop the unacknowledged partial append by atomically publishing the valid prefix.\n\t\t\t\t\tconst validPrefix = `${physicalLines.slice(0, index).join(\"\\n\")}\\n`;\n\t\t\t\t\tawait publishFileAtomically(fs, path, async (tempPath) => {\n\t\t\t\t\t\tfileResult(await fs.writeFile(tempPath, validPrefix), `Failed to stage torn-tail repair ${path}`);\n\t\t\t\t\t});\n\t\t\t\t\treturn storage;\n\t\t\t\t}\n\t\t\t\tthrow invalidFile(path, index + 1, mutationResult.error);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tstorage.applyMutation(mutationResult.value);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof SessionError && error.code === \"invalid_entry\") {\n\t\t\t\t\tthrow invalidFile(path, index + 1, error);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tif (!content.endsWith(\"\\n\")) {\n\t\t\tfileResult(await fs.appendFile(path, \"\\n\"), `Failed to repair unterminated session tail ${path}`);\n\t\t}\n\t\treturn storage;\n\t}\n\n\tasync fork(path: string, header: JsonlV4Header, options: ForkOptions): Promise<JsonlSessionStorage> {\n\t\tconst mutations = this.state.createForkMutations(options);\n\t\tawait publishFileAtomically(this.fs, path, async (tempPath) => {\n\t\t\tconst targetStorage = await JsonlSessionStorage.create(this.fs, tempPath, header);\n\t\t\tfor (const mutation of mutations) {\n\t\t\t\tawait targetStorage.appendMutation(mutation);\n\t\t\t\ttargetStorage.applyMutation(mutation);\n\t\t\t}\n\t\t});\n\t\treturn JsonlSessionStorage.load(this.fs, path);\n\t}\n\n\tasync drain(): Promise<void> {\n\t\tawait this.tail;\n\t}\n\n\tasync getMetadata(): Promise<JsonlSessionMetadata> {\n\t\treturn structuredClone(this.metadata);\n\t}\n\n\tasync getLanes(): Promise<LanePointer[]> {\n\t\treturn this.state.getLanes();\n\t}\n\n\tcreateLane(lane: string, at: string | null): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.state.validateNewLane(lane);\n\t\t\tthis.state.validateTarget(at);\n\t\t\tconst mutation: SessionMutation = { kind: \"lane\", seq: this.state.nextSequence, lane, leafId: at };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t});\n\t}\n\n\tmoveLane(lane: string, to: string | null): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.state.requireLane(lane);\n\t\t\tthis.state.validateTarget(to);\n\t\t\tconst mutation: SessionMutation = { kind: \"lane\", seq: this.state.nextSequence, lane, leafId: to };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t});\n\t}\n\n\tappendEntry<TEntry extends Entry>(newEntry: ProvisionedEntry<TEntry>, lane: string): Promise<TEntry> {\n\t\treturn this.enqueue(async () => {\n\t\t\tconst parentId = this.state.requireLane(lane);\n\t\t\tthis.state.validateUnusedId(newEntry.id);\n\t\t\tconst entry = {\n\t\t\t\t...structuredClone(newEntry),\n\t\t\t\tparentId,\n\t\t\t\tseq: this.state.nextSequence,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t} as unknown as TEntry;\n\t\t\tconst mutation: SessionMutation = { kind: \"entry\", lane, entry };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t\treturn structuredClone(entry);\n\t\t});\n\t}\n\n\tappendRecord<TRecord extends LaneRecord>(newRecord: NewRecord<TRecord>): Promise<TRecord> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.state.requireLane(newRecord.lane);\n\t\t\tthis.state.validateUnusedId(newRecord.id);\n\t\t\tconst currentOpenOperationId = this.state.findOpenOperations(newRecord.lane, { limit: 1 })[0]?.id;\n\t\t\tif (newRecord.type === \"operation_started\" && currentOpenOperationId !== undefined) {\n\t\t\t\tthrow new SessionError(\n\t\t\t\t\t\"storage\",\n\t\t\t\t\t`Lane ${newRecord.lane} already has an open operation ${currentOpenOperationId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst record = {\n\t\t\t\t...structuredClone(newRecord),\n\t\t\t\tseq: this.state.nextSequence,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t} as unknown as TRecord;\n\t\t\tconst mutation: SessionMutation = { kind: \"record\", record };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t\treturn structuredClone(record);\n\t\t});\n\t}\n\n\tasync getEntry(id: string): Promise<Entry | undefined> {\n\t\tconst entry = this.state.getEntry(id);\n\t\treturn entry === undefined ? undefined : structuredClone(entry);\n\t}\n\n\tasync findEntries(query: EntryQuery = {}): Promise<Entry[]> {\n\t\treturn structuredClone(this.state.findEntries(query));\n\t}\n\n\tasync findEntriesOnBranch(query: EntryQuery & BranchBounds & { start: string }): Promise<Entry[]> {\n\t\treturn structuredClone(this.state.findEntriesOnBranch(query));\n\t}\n\n\tasync findRecords<K extends LaneRecord[\"type\"]>(\n\t\tquery: RecordQuery & { type: K },\n\t): Promise<Extract<LaneRecord, { type: K }>[]>;\n\tasync findRecords(query?: RecordQuery): Promise<LaneRecord[]>;\n\tasync findRecords(query: RecordQuery = {}): Promise<LaneRecord[]> {\n\t\treturn structuredClone(this.state.findRecords(query));\n\t}\n\n\tasync findOpenOperations(lane: string, options?: { limit?: number }): Promise<OperationStartedRecord[]> {\n\t\treturn structuredClone(this.state.findOpenOperations(lane, options));\n\t}\n\n\tasync getLog(options: LogOptions = {}): Promise<LogItem[]> {\n\t\treturn structuredClone(this.state.getLog(options));\n\t}\n\n\tasync getName(): Promise<string | undefined> {\n\t\treturn this.state.getName();\n\t}\n\n\tsetName(name: string | undefined): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tconst mutation: SessionMutation = { kind: \"fact\", seq: this.state.nextSequence, fact: \"name\", name };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t});\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.state.getLabel(id);\n\t}\n\n\tsetLabel(id: string, label: string | undefined): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.state.validateTarget(id);\n\t\t\tconst mutation: SessionMutation = {\n\t\t\t\tkind: \"fact\",\n\t\t\t\tseq: this.state.nextSequence,\n\t\t\t\tfact: \"label\",\n\t\t\t\ttargetId: id,\n\t\t\t\tlabel,\n\t\t\t};\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t});\n\t}\n\n\tasync getStats(): Promise<SessionStats> {\n\t\treturn structuredClone(this.state.getStats());\n\t}\n\n\tprivate enqueue<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst result = this.tail.then(operation);\n\t\tthis.tail = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn result;\n\t}\n\n\tprivate async appendMutation(mutation: SessionMutation): Promise<void> {\n\t\tfileResult(\n\t\t\tawait this.fs.appendFile(this.metadata.path, encodeMutation(mutation)),\n\t\t\t`Failed to append session ${this.metadata.path}`,\n\t\t);\n\t}\n\n\tprivate applyMutation(mutation: SessionMutation): void {\n\t\tthis.state.applyMutation(mutation);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/jsonl/storage.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Usage } from \"@earendil-works/pi-ai\";\nimport { uuidv7 } from \"@earendil-works/pi-ai/utils/uuid\";\nimport type { Context } from \"../../context.ts\";\nimport type { FileError, FileSystem, Result } from \"../../types.ts\";\nimport { insertUsage } from \"../commit.ts\";\nimport { type ForkDestinationSnapshot, type ForkSourceSnapshot, forkSnapshotWrites } from \"../fork.ts\";\nimport {\n\ttype CommittedEntryWrite,\n\ttype CommittedListAppendWrite,\n\ttype CommittedListDeleteWrite,\n\ttype CommittedUsageWrite,\n\ttype CommittedValueDeleteWrite,\n\ttype CommittedValueSetWrite,\n\ttype CommittedWrite,\n\tInMemoryStorageState,\n} from \"../in-memory-storage-state.ts\";\nimport type {\n\tCommitResult,\n\tEntry,\n\tEntryScan,\n\tEntryStructure,\n\tSessionStats,\n\tStorage,\n\tStorageBranchScan,\n\tUsageRow,\n\tUsageScan,\n\tWrite,\n} from \"../types.ts\";\nimport type { ListElement, ListReadOptions, StoredValue, Value, ValueList } from \"../values.ts\";\nimport { type LegacyV3SessionHeader, parseJsonlSessionHeader } from \"./codec.ts\";\nimport { normalizeLegacyV3Header, normalizeLegacyV3Records } from \"./legacy-v3.ts\";\nimport { JSONL_STORAGE_VERSION, type JsonlStorageHeader, type JsonlStorageOptions } from \"./types.ts\";\n\nfunction fileValue<T>(result: Result<T, FileError>, action: string): T {\n\tif (!result.ok) throw new Error(`${action}: ${result.error.message}`, { cause: result.error });\n\treturn result.value;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction requireSafeInteger(value: unknown, field: string, minimum: number): void {\n\tif (!Number.isSafeInteger(value) || (value as number) < minimum) throw new Error(`Invalid JSONL ${field}`);\n}\n\nfunction parseCommittedWrite(value: unknown): CommittedWrite {\n\tif (!isRecord(value)) throw new Error(\"Invalid JSONL transaction write\");\n\trequireSafeInteger(value.seq, \"write seq\", 1);\n\tswitch (value.kind) {\n\t\tcase \"entry\":\n\t\t\trequireSafeInteger(value.timestamp, \"entry timestamp\", 0);\n\t\t\treturn value as unknown as CommittedEntryWrite;\n\t\tcase \"usage\":\n\t\t\treturn value as unknown as CommittedUsageWrite;\n\t\tcase \"value\":\n\t\t\tif (value.op === \"set\") return value as unknown as CommittedValueSetWrite;\n\t\t\tif (value.op === \"delete\") return value as unknown as CommittedValueDeleteWrite;\n\t\t\tthrow new Error(`Invalid JSONL value operation: ${String(value.op)}`);\n\t\tcase \"list\":\n\t\t\tif (value.op === \"append\") return value as unknown as CommittedListAppendWrite;\n\t\t\tif (value.op === \"delete\") return value as unknown as CommittedListDeleteWrite;\n\t\t\tthrow new Error(`Invalid JSONL list operation: ${String(value.op)}`);\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid JSONL write kind: ${String(value.kind)}`);\n\t}\n}\n\nfunction parseTransaction(line: string): CommittedWrite[] {\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(line);\n\t} catch (error) {\n\t\tthrow new Error(\"Invalid JSONL transaction: not valid JSON\", { cause: error });\n\t}\n\treturn (Array.isArray(value) ? value : [value]).map(parseCommittedWrite);\n}\n\nfunction serializeTransaction(writes: CommittedWrite[]): string {\n\treturn JSON.stringify(writes.length === 1 ? writes[0] : writes);\n}\n\nfunction serializeStorage(header: JsonlStorageHeader, transactions: CommittedWrite[][]): string {\n\treturn `${[JSON.stringify(header), ...transactions.map(serializeTransaction)].join(\"\\n\")}\\n`;\n}\n\nfunction splitCompleteLines(content: string): { lines: string[]; torn: boolean } {\n\tif (content.endsWith(\"\\n\")) return { lines: content.slice(0, -1).split(\"\\n\"), torn: false };\n\tconst lastNewline = content.lastIndexOf(\"\\n\");\n\tif (lastNewline === -1) return { lines: [], torn: true };\n\treturn { lines: content.slice(0, lastNewline).split(\"\\n\"), torn: true };\n}\n\nasync function publishFileAtomically(\n\tfileSystem: FileSystem,\n\tdestinationPath: string,\n\tcontent: string,\n\tcontext: Context,\n): Promise<void> {\n\tconst tempPath = `${destinationPath}.tmp`;\n\ttry {\n\t\tfileValue(\n\t\t\tawait fileSystem.writeFile(tempPath, content, context),\n\t\t\t`Failed to stage JSONL storage ${destinationPath}`,\n\t\t);\n\t\tfileValue(\n\t\t\tawait fileSystem.renameFile(tempPath, destinationPath, context),\n\t\t\t`Failed to publish JSONL storage ${destinationPath}`,\n\t\t);\n\t} catch (error) {\n\t\tawait fileSystem.remove(tempPath, { force: true }, context);\n\t\tthrow error;\n\t}\n}\n\ntype LegacyV3Backing = {\n\tkind: \"v3\";\n\timportedUsage: Usage;\n\tbaselineWrites: readonly CommittedWrite[];\n};\n\ntype JsonlBacking = { kind: \"v4\" } | LegacyV3Backing;\n\n/** JSONL storage backed by an injected filesystem capability. */\nexport class JsonlStorage implements Storage {\n\tprivate readonly fileSystem: FileSystem;\n\tprivate readonly path: string;\n\tprivate readonly now: () => number;\n\treadonly header: JsonlStorageHeader;\n\tprivate backing: JsonlBacking;\n\tprivate readonly storageState = new InMemoryStorageState();\n\tprivate commitQueue: Promise<void> = Promise.resolve();\n\tprivate state: \"open\" | \"closing\" | \"closed\" = \"open\";\n\tprivate closePromise: Promise<void> | undefined;\n\n\tprivate constructor(options: JsonlStorageOptions, header: JsonlStorageHeader, backing: JsonlBacking) {\n\t\tthis.fileSystem = options.fileSystem;\n\t\tthis.path = options.path;\n\t\tthis.now = options.now ?? Date.now;\n\t\tthis.header = header;\n\t\tthis.backing = backing;\n\t}\n\n\tstatic async create(\n\t\toptions: JsonlStorageOptions,\n\t\theader: JsonlStorageHeader,\n\t\tinitialWrites: Write[],\n\t\tcontext: Context,\n\t): Promise<JsonlStorage> {\n\t\tconst storage = new JsonlStorage(options, header, { kind: \"v4\" });\n\t\tconst prepared = storage.storageState.prepareCommit(initialWrites, storage.now());\n\t\tconst transactions = prepared.writes.length === 0 ? [] : [prepared.writes];\n\t\tawait publishFileAtomically(options.fileSystem, options.path, serializeStorage(header, transactions), context);\n\t\tstorage.storageState.applyValidated(prepared.writes);\n\t\treturn storage;\n\t}\n\n\t/** Atomically create storage from a complete prepared snapshot. */\n\tstatic async createFromForkSnapshot(\n\t\toptions: JsonlStorageOptions,\n\t\theader: JsonlStorageHeader,\n\t\tsnapshot: ForkDestinationSnapshot,\n\t\tcontext: Context,\n\t): Promise<JsonlStorage> {\n\t\tconst writes = forkSnapshotWrites(snapshot);\n\t\tconst snapshotHeader = { ...header, nextSeq: snapshot.nextSeq };\n\t\tawait publishFileAtomically(\n\t\t\toptions.fileSystem,\n\t\t\toptions.path,\n\t\t\tserializeStorage(\n\t\t\t\tsnapshotHeader,\n\t\t\t\twrites.map((write) => [write]),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t\treturn JsonlStorage.open(options, context);\n\t}\n\n\tstatic async open(options: JsonlStorageOptions, context: Context): Promise<JsonlStorage> {\n\t\tconst content = fileValue(\n\t\t\tawait options.fileSystem.readTextFile(options.path, context),\n\t\t\t`Failed to read JSONL storage ${options.path}`,\n\t\t);\n\t\tconst { lines, torn } = splitCompleteLines(content);\n\t\tif (lines[0] === undefined || lines[0] === \"\") {\n\t\t\tthrow new Error(`Invalid JSONL storage ${options.path}: missing header`);\n\t\t}\n\t\tconst parsedHeader = parseJsonlSessionHeader(lines[0]);\n\t\tif (!parsedHeader.ok) {\n\t\t\tthrow new Error(`Invalid JSONL storage ${options.path}: invalid header`, { cause: parsedHeader.error });\n\t\t}\n\t\tif (parsedHeader.value.format === \"v3-legacy\") {\n\t\t\treturn JsonlStorage.openLegacyV3(options, parsedHeader.value.header, lines.slice(1), context);\n\t\t}\n\n\t\tconst header = parsedHeader.value.header;\n\t\tif (header.storageVersion !== JSONL_STORAGE_VERSION) {\n\t\t\tthrow new Error(`Session ${header.id} uses unsupported storage version ${header.storageVersion}`);\n\t\t}\n\t\tconst storage = new JsonlStorage(options, header, { kind: \"v4\" });\n\t\tfor (let index = 1; index < lines.length; index++) {\n\t\t\tconst line = lines[index]!;\n\t\t\ttry {\n\t\t\t\tstorage.replayCommitted(parseTransaction(line));\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(`Invalid JSONL storage ${options.path}: line ${index + 1}`, { cause: error });\n\t\t\t}\n\t\t}\n\t\tif (header.nextSeq !== undefined) storage.storageState.advanceNextSeq(header.nextSeq);\n\t\tif (torn) await publishFileAtomically(options.fileSystem, options.path, `${lines.join(\"\\n\")}\\n`, context);\n\t\treturn storage;\n\t}\n\n\tprivate static async openLegacyV3(\n\t\toptions: JsonlStorageOptions,\n\t\theader: LegacyV3SessionHeader,\n\t\trecordLines: readonly string[],\n\t\tcontext: Context,\n\t): Promise<JsonlStorage> {\n\t\tconst { writes, importedUsage, nextSeq } = normalizeLegacyV3Records(recordLines);\n\t\tconst targetHeader = {\n\t\t\t...(await normalizeLegacyV3Header(options.fileSystem, header, context)),\n\t\t\tnextSeq,\n\t\t};\n\t\tconst storage = new JsonlStorage(options, targetHeader, {\n\t\t\tkind: \"v3\",\n\t\t\timportedUsage,\n\t\t\tbaselineWrites: writes,\n\t\t});\n\t\tstorage.replayCommitted(writes);\n\t\treturn storage;\n\t}\n\n\tprivate replayCommitted(writes: readonly CommittedWrite[]): void {\n\t\tthis.storageState.validateCommitted(writes);\n\t\tthis.storageState.applyValidated(writes);\n\t}\n\n\tasync commit(writes: Write[], context: Context): Promise<CommitResult> {\n\t\tif (this.state !== \"open\") throw new Error(\"JsonlStorage is closed\");\n\t\tconst result = this.commitQueue.then(() => this.applyCommit(writes, context));\n\t\tthis.commitQueue = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn result;\n\t}\n\n\tprivate async applyCommit(writes: Write[], context: Context): Promise<CommitResult> {\n\t\tif (this.backing.kind === \"v3\" && writes.length !== 0) {\n\t\t\treturn this.upgradeLegacyV3ToV4(this.backing, writes, context);\n\t\t}\n\t\tconst prepared = this.storageState.prepareCommit(writes, this.now());\n\t\tif (prepared.writes.length !== 0) {\n\t\t\tfileValue(\n\t\t\t\tawait this.fileSystem.appendFile(this.path, `${serializeTransaction(prepared.writes)}\\n`, context),\n\t\t\t\t`Failed to append JSONL storage ${this.path}`,\n\t\t\t);\n\t\t}\n\t\tconst stats = this.storageState.applyValidated(prepared.writes);\n\t\treturn { ...prepared.result, stats: this.withImportedUsage(stats) };\n\t}\n\n\t/** Atomically upgrade legacy v3 backing and preserve the first caller write as a v4 transaction. */\n\tprivate async upgradeLegacyV3ToV4(\n\t\tbacking: LegacyV3Backing,\n\t\tcallerWrites: Write[],\n\t\tcontext: Context,\n\t): Promise<CommitResult> {\n\t\tconst timestamp = this.now();\n\t\tconst prepared = this.storageState.prepareCommit(\n\t\t\t[\n\t\t\t\tinsertUsage({\n\t\t\t\t\tid: uuidv7(timestamp),\n\t\t\t\t\tusage: backing.importedUsage,\n\t\t\t\t\tadjustment: true,\n\t\t\t\t\tdetails: { source: \"v3-import\" },\n\t\t\t\t}),\n\t\t\t\t...callerWrites,\n\t\t\t],\n\t\t\ttimestamp,\n\t\t);\n\n\t\tconst nextSeq = prepared.result.firstSeq + prepared.writes.length;\n\t\tconst upgradedHeader = { ...this.header, nextSeq };\n\t\tawait publishFileAtomically(\n\t\t\tthis.fileSystem,\n\t\t\tthis.path,\n\t\t\tserializeStorage(upgradedHeader, [...backing.baselineWrites.map((write) => [write]), prepared.writes]),\n\t\t\tcontext,\n\t\t);\n\n\t\tconst stats = this.storageState.applyValidated(prepared.writes);\n\t\tthis.backing = { kind: \"v4\" };\n\t\t// The first sequence belongs to the internal usage adjustment; return only caller-write sequences.\n\t\treturn {\n\t\t\t...prepared.result,\n\t\t\tfirstSeq: prepared.result.firstSeq + 1,\n\t\t\tseqs: prepared.result.seqs.slice(1),\n\t\t\tstats,\n\t\t};\n\t}\n\n\tgetEntries(ids: string[], _context: Context): Promise<Map<string, Entry>> {\n\t\tif (this.state !== \"open\") return Promise.reject(new Error(\"JsonlStorage is closed\"));\n\t\treturn Promise.resolve(this.storageState.getEntries(ids));\n\t}\n\n\tgetValue<T>(address: Value<T>, _context: Context): Promise<StoredValue<T> | undefined> {\n\t\tif (this.state !== \"open\") return Promise.reject(new Error(\"JsonlStorage is closed\"));\n\t\treturn Promise.resolve(this.storageState.getValue(address));\n\t}\n\n\tscanValues<T>(prefix: Value<T>, _context: Context): Promise<StoredValue<T>[]> {\n\t\tif (this.state !== \"open\") return Promise.reject(new Error(\"JsonlStorage is closed\"));\n\t\treturn Promise.resolve(this.storageState.scanValues(prefix));\n\t}\n\n\tasync readList<T>(\n\t\taddress: ValueList<T>,\n\t\toptions: ListReadOptions | undefined,\n\t\t_context: Context,\n\t): Promise<ListElement<T>[]> {\n\t\tif (this.state !== \"open\") throw new Error(\"JsonlStorage is closed\");\n\t\treturn this.storageState.readList(address, options);\n\t}\n\n\tasync scanBranch(query: StorageBranchScan, _context: Context): Promise<Entry[]> {\n\t\tif (this.state !== \"open\") throw new Error(\"JsonlStorage is closed\");\n\t\treturn this.storageState.scanBranch(query);\n\t}\n\n\tasync scanBranchStructure(query: StorageBranchScan, _context: Context): Promise<EntryStructure[]> {\n\t\tif (this.state !== \"open\") throw new Error(\"JsonlStorage is closed\");\n\t\treturn this.storageState.scanBranchStructure(query);\n\t}\n\n\tscanEntries(query: EntryScan, _context: Context): Promise<Entry[]> {\n\t\tif (this.state !== \"open\") return Promise.reject(new Error(\"JsonlStorage is closed\"));\n\t\treturn Promise.resolve(this.storageState.scanEntries(query));\n\t}\n\n\tscanUsage(query: UsageScan, _context: Context): Promise<UsageRow[]> {\n\t\tif (this.state !== \"open\") return Promise.reject(new Error(\"JsonlStorage is closed\"));\n\t\treturn Promise.resolve(this.storageState.scanUsage(query));\n\t}\n\n\tgetStats(_context: Context): Promise<SessionStats> {\n\t\tif (this.state !== \"open\") return Promise.reject(new Error(\"JsonlStorage is closed\"));\n\t\treturn Promise.resolve(this.withImportedUsage(this.storageState.getStats()));\n\t}\n\n\tprivate withImportedUsage(stats: SessionStats): SessionStats {\n\t\treturn this.backing.kind === \"v4\" ? stats : { ...stats, usage: this.backing.importedUsage };\n\t}\n\n\t/** Capture the state needed to fork at one serialized boundary between commits. */\n\tcaptureForkSource(_context: Context): Promise<ForkSourceSnapshot> {\n\t\tif (this.state !== \"open\") return Promise.reject(new Error(\"JsonlStorage is closed\"));\n\t\tconst result = this.commitQueue.then(() => this.storageState.snapshotEntriesAndValues());\n\t\tthis.commitQueue = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn result;\n\t}\n\n\tclose(_context: Context): Promise<void> {\n\t\tif (this.closePromise !== undefined) return this.closePromise;\n\t\tthis.state = \"closing\";\n\t\tthis.closePromise = this.commitQueue.then(() => {\n\t\t\tthis.state = \"closed\";\n\t\t});\n\t\treturn this.closePromise;\n\t}\n}\n"]}
|
|
@@ -1,217 +1,274 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { uuidv7 } from "@earendil-works/pi-ai/utils/uuid";
|
|
2
|
+
import { insertUsage } from "../commit.js";
|
|
3
|
+
import { forkSnapshotWrites } from "../fork.js";
|
|
4
|
+
import { InMemoryStorageState, } from "../in-memory-storage-state.js";
|
|
5
|
+
import { parseJsonlSessionHeader } from "./codec.js";
|
|
6
|
+
import { normalizeLegacyV3Header, normalizeLegacyV3Records } from "./legacy-v3.js";
|
|
7
|
+
import { JSONL_STORAGE_VERSION } from "./types.js";
|
|
8
|
+
function fileValue(result, action) {
|
|
9
|
+
if (!result.ok)
|
|
10
|
+
throw new Error(`${action}: ${result.error.message}`, { cause: result.error });
|
|
11
|
+
return result.value;
|
|
12
|
+
}
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function requireSafeInteger(value, field, minimum) {
|
|
17
|
+
if (!Number.isSafeInteger(value) || value < minimum)
|
|
18
|
+
throw new Error(`Invalid JSONL ${field}`);
|
|
19
|
+
}
|
|
20
|
+
function parseCommittedWrite(value) {
|
|
21
|
+
if (!isRecord(value))
|
|
22
|
+
throw new Error("Invalid JSONL transaction write");
|
|
23
|
+
requireSafeInteger(value.seq, "write seq", 1);
|
|
24
|
+
switch (value.kind) {
|
|
25
|
+
case "entry":
|
|
26
|
+
requireSafeInteger(value.timestamp, "entry timestamp", 0);
|
|
27
|
+
return value;
|
|
28
|
+
case "usage":
|
|
29
|
+
return value;
|
|
30
|
+
case "value":
|
|
31
|
+
if (value.op === "set")
|
|
32
|
+
return value;
|
|
33
|
+
if (value.op === "delete")
|
|
34
|
+
return value;
|
|
35
|
+
throw new Error(`Invalid JSONL value operation: ${String(value.op)}`);
|
|
36
|
+
case "list":
|
|
37
|
+
if (value.op === "append")
|
|
38
|
+
return value;
|
|
39
|
+
if (value.op === "delete")
|
|
40
|
+
return value;
|
|
41
|
+
throw new Error(`Invalid JSONL list operation: ${String(value.op)}`);
|
|
42
|
+
default:
|
|
43
|
+
throw new Error(`Invalid JSONL write kind: ${String(value.kind)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function parseTransaction(line) {
|
|
47
|
+
let value;
|
|
48
|
+
try {
|
|
49
|
+
value = JSON.parse(line);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw new Error("Invalid JSONL transaction: not valid JSON", { cause: error });
|
|
53
|
+
}
|
|
54
|
+
return (Array.isArray(value) ? value : [value]).map(parseCommittedWrite);
|
|
55
|
+
}
|
|
56
|
+
function serializeTransaction(writes) {
|
|
57
|
+
return JSON.stringify(writes.length === 1 ? writes[0] : writes);
|
|
58
|
+
}
|
|
59
|
+
function serializeStorage(header, transactions) {
|
|
60
|
+
return `${[JSON.stringify(header), ...transactions.map(serializeTransaction)].join("\n")}\n`;
|
|
61
|
+
}
|
|
62
|
+
function splitCompleteLines(content) {
|
|
63
|
+
if (content.endsWith("\n"))
|
|
64
|
+
return { lines: content.slice(0, -1).split("\n"), torn: false };
|
|
65
|
+
const lastNewline = content.lastIndexOf("\n");
|
|
66
|
+
if (lastNewline === -1)
|
|
67
|
+
return { lines: [], torn: true };
|
|
68
|
+
return { lines: content.slice(0, lastNewline).split("\n"), torn: true };
|
|
69
|
+
}
|
|
70
|
+
async function publishFileAtomically(fileSystem, destinationPath, content, context) {
|
|
16
71
|
const tempPath = `${destinationPath}.tmp`;
|
|
17
72
|
try {
|
|
18
|
-
await
|
|
19
|
-
|
|
73
|
+
fileValue(await fileSystem.writeFile(tempPath, content, context), `Failed to stage JSONL storage ${destinationPath}`);
|
|
74
|
+
fileValue(await fileSystem.renameFile(tempPath, destinationPath, context), `Failed to publish JSONL storage ${destinationPath}`);
|
|
20
75
|
}
|
|
21
76
|
catch (error) {
|
|
22
|
-
await
|
|
77
|
+
await fileSystem.remove(tempPath, { force: true }, context);
|
|
23
78
|
throw error;
|
|
24
79
|
}
|
|
25
80
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
81
|
+
/** JSONL storage backed by an injected filesystem capability. */
|
|
82
|
+
export class JsonlStorage {
|
|
83
|
+
fileSystem;
|
|
84
|
+
path;
|
|
85
|
+
now;
|
|
86
|
+
header;
|
|
87
|
+
backing;
|
|
88
|
+
storageState = new InMemoryStorageState();
|
|
89
|
+
commitQueue = Promise.resolve();
|
|
90
|
+
state = "open";
|
|
91
|
+
closePromise;
|
|
92
|
+
constructor(options, header, backing) {
|
|
93
|
+
this.fileSystem = options.fileSystem;
|
|
94
|
+
this.path = options.path;
|
|
95
|
+
this.now = options.now ?? Date.now;
|
|
96
|
+
this.header = header;
|
|
97
|
+
this.backing = backing;
|
|
98
|
+
}
|
|
99
|
+
static async create(options, header, initialWrites, context) {
|
|
100
|
+
const storage = new JsonlStorage(options, header, { kind: "v4" });
|
|
101
|
+
const prepared = storage.storageState.prepareCommit(initialWrites, storage.now());
|
|
102
|
+
const transactions = prepared.writes.length === 0 ? [] : [prepared.writes];
|
|
103
|
+
await publishFileAtomically(options.fileSystem, options.path, serializeStorage(header, transactions), context);
|
|
104
|
+
storage.storageState.applyValidated(prepared.writes);
|
|
105
|
+
return storage;
|
|
106
|
+
}
|
|
107
|
+
/** Atomically create storage from a complete prepared snapshot. */
|
|
108
|
+
static async createFromForkSnapshot(options, header, snapshot, context) {
|
|
109
|
+
const writes = forkSnapshotWrites(snapshot);
|
|
110
|
+
const snapshotHeader = { ...header, nextSeq: snapshot.nextSeq };
|
|
111
|
+
await publishFileAtomically(options.fileSystem, options.path, serializeStorage(snapshotHeader, writes.map((write) => [write])), context);
|
|
112
|
+
return JsonlStorage.open(options, context);
|
|
113
|
+
}
|
|
114
|
+
static async open(options, context) {
|
|
115
|
+
const content = fileValue(await options.fileSystem.readTextFile(options.path, context), `Failed to read JSONL storage ${options.path}`);
|
|
116
|
+
const { lines, torn } = splitCompleteLines(content);
|
|
117
|
+
if (lines[0] === undefined || lines[0] === "") {
|
|
118
|
+
throw new Error(`Invalid JSONL storage ${options.path}: missing header`);
|
|
47
119
|
}
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
50
|
-
throw
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
fileResult(await fs.writeFile(tempPath, validPrefix), `Failed to stage torn-tail repair ${path}`);
|
|
63
|
-
});
|
|
64
|
-
return storage;
|
|
65
|
-
}
|
|
66
|
-
throw invalidFile(path, index + 1, mutationResult.error);
|
|
67
|
-
}
|
|
120
|
+
const parsedHeader = parseJsonlSessionHeader(lines[0]);
|
|
121
|
+
if (!parsedHeader.ok) {
|
|
122
|
+
throw new Error(`Invalid JSONL storage ${options.path}: invalid header`, { cause: parsedHeader.error });
|
|
123
|
+
}
|
|
124
|
+
if (parsedHeader.value.format === "v3-legacy") {
|
|
125
|
+
return JsonlStorage.openLegacyV3(options, parsedHeader.value.header, lines.slice(1), context);
|
|
126
|
+
}
|
|
127
|
+
const header = parsedHeader.value.header;
|
|
128
|
+
if (header.storageVersion !== JSONL_STORAGE_VERSION) {
|
|
129
|
+
throw new Error(`Session ${header.id} uses unsupported storage version ${header.storageVersion}`);
|
|
130
|
+
}
|
|
131
|
+
const storage = new JsonlStorage(options, header, { kind: "v4" });
|
|
132
|
+
for (let index = 1; index < lines.length; index++) {
|
|
133
|
+
const line = lines[index];
|
|
68
134
|
try {
|
|
69
|
-
storage.
|
|
135
|
+
storage.replayCommitted(parseTransaction(line));
|
|
70
136
|
}
|
|
71
137
|
catch (error) {
|
|
72
|
-
|
|
73
|
-
throw invalidFile(path, index + 1, error);
|
|
74
|
-
}
|
|
75
|
-
throw error;
|
|
138
|
+
throw new Error(`Invalid JSONL storage ${options.path}: line ${index + 1}`, { cause: error });
|
|
76
139
|
}
|
|
77
140
|
}
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
141
|
+
if (header.nextSeq !== undefined)
|
|
142
|
+
storage.storageState.advanceNextSeq(header.nextSeq);
|
|
143
|
+
if (torn)
|
|
144
|
+
await publishFileAtomically(options.fileSystem, options.path, `${lines.join("\n")}\n`, context);
|
|
81
145
|
return storage;
|
|
82
146
|
}
|
|
83
|
-
async
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
147
|
+
static async openLegacyV3(options, header, recordLines, context) {
|
|
148
|
+
const { writes, importedUsage, nextSeq } = normalizeLegacyV3Records(recordLines);
|
|
149
|
+
const targetHeader = {
|
|
150
|
+
...(await normalizeLegacyV3Header(options.fileSystem, header, context)),
|
|
151
|
+
nextSeq,
|
|
152
|
+
};
|
|
153
|
+
const storage = new JsonlStorage(options, targetHeader, {
|
|
154
|
+
kind: "v3",
|
|
155
|
+
importedUsage,
|
|
156
|
+
baselineWrites: writes,
|
|
91
157
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
async drain() {
|
|
95
|
-
await this.tail;
|
|
96
|
-
}
|
|
97
|
-
async getMetadata() {
|
|
98
|
-
return structuredClone(this.metadata);
|
|
99
|
-
}
|
|
100
|
-
async getLanes() {
|
|
101
|
-
return this.state.getLanes();
|
|
158
|
+
storage.replayCommitted(writes);
|
|
159
|
+
return storage;
|
|
102
160
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.state.validateTarget(at);
|
|
107
|
-
const mutation = { kind: "lane", seq: this.state.nextSequence, lane, leafId: at };
|
|
108
|
-
await this.appendMutation(mutation);
|
|
109
|
-
this.applyMutation(mutation);
|
|
110
|
-
});
|
|
161
|
+
replayCommitted(writes) {
|
|
162
|
+
this.storageState.validateCommitted(writes);
|
|
163
|
+
this.storageState.applyValidated(writes);
|
|
111
164
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.applyMutation(mutation);
|
|
119
|
-
});
|
|
165
|
+
async commit(writes, context) {
|
|
166
|
+
if (this.state !== "open")
|
|
167
|
+
throw new Error("JsonlStorage is closed");
|
|
168
|
+
const result = this.commitQueue.then(() => this.applyCommit(writes, context));
|
|
169
|
+
this.commitQueue = result.then(() => undefined, () => undefined);
|
|
170
|
+
return result;
|
|
120
171
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const mutation = { kind: "entry", lane, entry };
|
|
132
|
-
await this.appendMutation(mutation);
|
|
133
|
-
this.applyMutation(mutation);
|
|
134
|
-
return structuredClone(entry);
|
|
135
|
-
});
|
|
172
|
+
async applyCommit(writes, context) {
|
|
173
|
+
if (this.backing.kind === "v3" && writes.length !== 0) {
|
|
174
|
+
return this.upgradeLegacyV3ToV4(this.backing, writes, context);
|
|
175
|
+
}
|
|
176
|
+
const prepared = this.storageState.prepareCommit(writes, this.now());
|
|
177
|
+
if (prepared.writes.length !== 0) {
|
|
178
|
+
fileValue(await this.fileSystem.appendFile(this.path, `${serializeTransaction(prepared.writes)}\n`, context), `Failed to append JSONL storage ${this.path}`);
|
|
179
|
+
}
|
|
180
|
+
const stats = this.storageState.applyValidated(prepared.writes);
|
|
181
|
+
return { ...prepared.result, stats: this.withImportedUsage(stats) };
|
|
136
182
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
183
|
+
/** Atomically upgrade legacy v3 backing and preserve the first caller write as a v4 transaction. */
|
|
184
|
+
async upgradeLegacyV3ToV4(backing, callerWrites, context) {
|
|
185
|
+
const timestamp = this.now();
|
|
186
|
+
const prepared = this.storageState.prepareCommit([
|
|
187
|
+
insertUsage({
|
|
188
|
+
id: uuidv7(timestamp),
|
|
189
|
+
usage: backing.importedUsage,
|
|
190
|
+
adjustment: true,
|
|
191
|
+
details: { source: "v3-import" },
|
|
192
|
+
}),
|
|
193
|
+
...callerWrites,
|
|
194
|
+
], timestamp);
|
|
195
|
+
const nextSeq = prepared.result.firstSeq + prepared.writes.length;
|
|
196
|
+
const upgradedHeader = { ...this.header, nextSeq };
|
|
197
|
+
await publishFileAtomically(this.fileSystem, this.path, serializeStorage(upgradedHeader, [...backing.baselineWrites.map((write) => [write]), prepared.writes]), context);
|
|
198
|
+
const stats = this.storageState.applyValidated(prepared.writes);
|
|
199
|
+
this.backing = { kind: "v4" };
|
|
200
|
+
// The first sequence belongs to the internal usage adjustment; return only caller-write sequences.
|
|
201
|
+
return {
|
|
202
|
+
...prepared.result,
|
|
203
|
+
firstSeq: prepared.result.firstSeq + 1,
|
|
204
|
+
seqs: prepared.result.seqs.slice(1),
|
|
205
|
+
stats,
|
|
206
|
+
};
|
|
155
207
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
208
|
+
getEntries(ids, _context) {
|
|
209
|
+
if (this.state !== "open")
|
|
210
|
+
return Promise.reject(new Error("JsonlStorage is closed"));
|
|
211
|
+
return Promise.resolve(this.storageState.getEntries(ids));
|
|
159
212
|
}
|
|
160
|
-
|
|
161
|
-
|
|
213
|
+
getValue(address, _context) {
|
|
214
|
+
if (this.state !== "open")
|
|
215
|
+
return Promise.reject(new Error("JsonlStorage is closed"));
|
|
216
|
+
return Promise.resolve(this.storageState.getValue(address));
|
|
162
217
|
}
|
|
163
|
-
|
|
164
|
-
|
|
218
|
+
scanValues(prefix, _context) {
|
|
219
|
+
if (this.state !== "open")
|
|
220
|
+
return Promise.reject(new Error("JsonlStorage is closed"));
|
|
221
|
+
return Promise.resolve(this.storageState.scanValues(prefix));
|
|
165
222
|
}
|
|
166
|
-
async
|
|
167
|
-
|
|
223
|
+
async readList(address, options, _context) {
|
|
224
|
+
if (this.state !== "open")
|
|
225
|
+
throw new Error("JsonlStorage is closed");
|
|
226
|
+
return this.storageState.readList(address, options);
|
|
168
227
|
}
|
|
169
|
-
async
|
|
170
|
-
|
|
228
|
+
async scanBranch(query, _context) {
|
|
229
|
+
if (this.state !== "open")
|
|
230
|
+
throw new Error("JsonlStorage is closed");
|
|
231
|
+
return this.storageState.scanBranch(query);
|
|
171
232
|
}
|
|
172
|
-
async
|
|
173
|
-
|
|
233
|
+
async scanBranchStructure(query, _context) {
|
|
234
|
+
if (this.state !== "open")
|
|
235
|
+
throw new Error("JsonlStorage is closed");
|
|
236
|
+
return this.storageState.scanBranchStructure(query);
|
|
174
237
|
}
|
|
175
|
-
|
|
176
|
-
|
|
238
|
+
scanEntries(query, _context) {
|
|
239
|
+
if (this.state !== "open")
|
|
240
|
+
return Promise.reject(new Error("JsonlStorage is closed"));
|
|
241
|
+
return Promise.resolve(this.storageState.scanEntries(query));
|
|
177
242
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
this.applyMutation(mutation);
|
|
183
|
-
});
|
|
243
|
+
scanUsage(query, _context) {
|
|
244
|
+
if (this.state !== "open")
|
|
245
|
+
return Promise.reject(new Error("JsonlStorage is closed"));
|
|
246
|
+
return Promise.resolve(this.storageState.scanUsage(query));
|
|
184
247
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return this.enqueue(async () => {
|
|
190
|
-
this.state.validateTarget(id);
|
|
191
|
-
const mutation = {
|
|
192
|
-
kind: "fact",
|
|
193
|
-
seq: this.state.nextSequence,
|
|
194
|
-
fact: "label",
|
|
195
|
-
targetId: id,
|
|
196
|
-
label,
|
|
197
|
-
};
|
|
198
|
-
await this.appendMutation(mutation);
|
|
199
|
-
this.applyMutation(mutation);
|
|
200
|
-
});
|
|
248
|
+
getStats(_context) {
|
|
249
|
+
if (this.state !== "open")
|
|
250
|
+
return Promise.reject(new Error("JsonlStorage is closed"));
|
|
251
|
+
return Promise.resolve(this.withImportedUsage(this.storageState.getStats()));
|
|
201
252
|
}
|
|
202
|
-
|
|
203
|
-
return
|
|
253
|
+
withImportedUsage(stats) {
|
|
254
|
+
return this.backing.kind === "v4" ? stats : { ...stats, usage: this.backing.importedUsage };
|
|
204
255
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.
|
|
256
|
+
/** Capture the state needed to fork at one serialized boundary between commits. */
|
|
257
|
+
captureForkSource(_context) {
|
|
258
|
+
if (this.state !== "open")
|
|
259
|
+
return Promise.reject(new Error("JsonlStorage is closed"));
|
|
260
|
+
const result = this.commitQueue.then(() => this.storageState.snapshotEntriesAndValues());
|
|
261
|
+
this.commitQueue = result.then(() => undefined, () => undefined);
|
|
208
262
|
return result;
|
|
209
263
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
this.
|
|
264
|
+
close(_context) {
|
|
265
|
+
if (this.closePromise !== undefined)
|
|
266
|
+
return this.closePromise;
|
|
267
|
+
this.state = "closing";
|
|
268
|
+
this.closePromise = this.commitQueue.then(() => {
|
|
269
|
+
this.state = "closed";
|
|
270
|
+
});
|
|
271
|
+
return this.closePromise;
|
|
215
272
|
}
|
|
216
273
|
}
|
|
217
274
|
//# sourceMappingURL=storage.js.map
|