foottrace 1.0.3 → 1.2.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 +137 -4
- package/dist/cause.js +14 -0
- package/dist/esm/cause.d.ts +8 -0
- package/dist/esm/cause.js +5 -0
- package/dist/esm/index.d.ts +7 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/lib/cause/admission.d.ts +27 -0
- package/dist/esm/lib/cause/admission.js +59 -0
- package/dist/esm/lib/cause/explain.d.ts +5 -0
- package/dist/esm/lib/cause/explain.js +114 -0
- package/dist/esm/lib/cause/ranking.d.ts +6 -0
- package/dist/esm/lib/cause/ranking.js +10 -0
- package/dist/esm/lib/cause/recordAdapter.d.ts +9 -0
- package/dist/esm/lib/cause/recordAdapter.js +49 -0
- package/dist/esm/lib/cause/recordChildren.d.ts +4 -0
- package/dist/esm/lib/cause/recordChildren.js +79 -0
- package/dist/esm/lib/cause/recordControl.d.ts +13 -0
- package/dist/esm/lib/cause/recordControl.js +66 -0
- package/dist/esm/lib/cause/recordCoverage.d.ts +24 -0
- package/dist/esm/lib/cause/recordCoverage.js +87 -0
- package/dist/esm/lib/cause/recordIdentity.d.ts +9 -0
- package/dist/esm/lib/cause/recordIdentity.js +67 -0
- package/dist/esm/lib/cause/recordLoading.d.ts +7 -0
- package/dist/esm/lib/cause/recordLoading.js +116 -0
- package/dist/esm/lib/cause/recordParents.d.ts +4 -0
- package/dist/esm/lib/cause/recordParents.js +86 -0
- package/dist/esm/lib/cause/recordScope.d.ts +8 -0
- package/dist/esm/lib/cause/recordScope.js +60 -0
- package/dist/esm/lib/cause/recordTypes.d.ts +67 -0
- package/dist/esm/lib/cause/recordTypes.js +1 -0
- package/dist/esm/lib/cause/recordValidation.d.ts +33 -0
- package/dist/esm/lib/cause/recordValidation.js +198 -0
- package/dist/esm/lib/cause/session.d.ts +30 -0
- package/dist/esm/lib/cause/session.js +161 -0
- package/dist/esm/lib/cause/storedRecordAdapter.d.ts +11 -0
- package/dist/esm/lib/cause/storedRecordAdapter.js +57 -0
- package/dist/esm/lib/cause/strategies.d.ts +3 -0
- package/dist/esm/lib/cause/strategies.js +21 -0
- package/dist/esm/lib/cause/types.d.ts +90 -0
- package/dist/esm/lib/cause/types.js +1 -0
- package/dist/esm/lib/cause/validation.d.ts +24 -0
- package/dist/esm/lib/cause/validation.js +134 -0
- package/dist/esm/lib/cause/walk.d.ts +12 -0
- package/dist/esm/lib/cause/walk.js +174 -0
- package/dist/esm/lib/ids/attemptId.d.ts +63 -0
- package/dist/esm/lib/ids/attemptId.js +91 -0
- package/dist/esm/lib/memory/EventLog.d.ts +89 -3
- package/dist/esm/lib/memory/EventLog.js +242 -3
- package/dist/esm/lib/memory/honesty.d.ts +6 -4
- package/dist/esm/lib/memory/honesty.js +7 -4
- package/dist/esm/lib/memory/sink.d.ts +148 -0
- package/dist/esm/lib/memory/sink.js +92 -0
- package/dist/esm/lib/memory/types.d.ts +4 -1
- package/dist/esm/lib/memory/types.js +1 -1
- package/dist/esm/lib/time-travel/bundles.d.ts +2 -0
- package/dist/esm/lib/time-travel/bundles.js +16 -0
- package/dist/esm/lib/time-travel/commitStops.js +21 -2
- package/dist/esm/lib/time-travel/index.d.ts +2 -0
- package/dist/esm/lib/time-travel/index.js +1 -0
- package/dist/esm/lib/time-travel/storedRecord.d.ts +195 -0
- package/dist/esm/lib/time-travel/storedRecord.js +494 -0
- package/dist/esm/lib/time-travel/timeTravel.js +11 -0
- package/dist/esm/lib/time-travel/types.d.ts +4 -1
- package/dist/esm/write.d.ts +7 -0
- package/dist/esm/write.js +6 -0
- package/dist/index.js +6 -2
- package/dist/lib/cause/admission.js +67 -0
- package/dist/lib/cause/explain.js +119 -0
- package/dist/lib/cause/ranking.js +14 -0
- package/dist/lib/cause/recordAdapter.js +55 -0
- package/dist/lib/cause/recordChildren.js +83 -0
- package/dist/lib/cause/recordControl.js +74 -0
- package/dist/lib/cause/recordCoverage.js +99 -0
- package/dist/lib/cause/recordIdentity.js +73 -0
- package/dist/lib/cause/recordLoading.js +121 -0
- package/dist/lib/cause/recordParents.js +90 -0
- package/dist/lib/cause/recordScope.js +66 -0
- package/dist/lib/cause/recordTypes.js +2 -0
- package/dist/lib/cause/recordValidation.js +213 -0
- package/dist/lib/cause/session.js +165 -0
- package/dist/lib/cause/storedRecordAdapter.js +61 -0
- package/dist/lib/cause/strategies.js +26 -0
- package/dist/lib/cause/types.js +2 -0
- package/dist/lib/cause/validation.js +150 -0
- package/dist/lib/cause/walk.js +180 -0
- package/dist/lib/ids/attemptId.js +96 -0
- package/dist/lib/memory/EventLog.js +242 -3
- package/dist/lib/memory/honesty.js +7 -4
- package/dist/lib/memory/sink.js +98 -0
- package/dist/lib/memory/types.js +1 -1
- package/dist/lib/time-travel/bundles.js +18 -1
- package/dist/lib/time-travel/commitStops.js +21 -2
- package/dist/lib/time-travel/index.js +3 -1
- package/dist/lib/time-travel/storedRecord.js +498 -0
- package/dist/lib/time-travel/timeTravel.js +11 -0
- package/dist/types/cause.d.ts +8 -0
- package/dist/types/index.d.ts +7 -2
- package/dist/types/lib/cause/admission.d.ts +27 -0
- package/dist/types/lib/cause/explain.d.ts +5 -0
- package/dist/types/lib/cause/ranking.d.ts +6 -0
- package/dist/types/lib/cause/recordAdapter.d.ts +9 -0
- package/dist/types/lib/cause/recordChildren.d.ts +4 -0
- package/dist/types/lib/cause/recordControl.d.ts +13 -0
- package/dist/types/lib/cause/recordCoverage.d.ts +24 -0
- package/dist/types/lib/cause/recordIdentity.d.ts +9 -0
- package/dist/types/lib/cause/recordLoading.d.ts +7 -0
- package/dist/types/lib/cause/recordParents.d.ts +4 -0
- package/dist/types/lib/cause/recordScope.d.ts +8 -0
- package/dist/types/lib/cause/recordTypes.d.ts +67 -0
- package/dist/types/lib/cause/recordValidation.d.ts +33 -0
- package/dist/types/lib/cause/session.d.ts +30 -0
- package/dist/types/lib/cause/storedRecordAdapter.d.ts +11 -0
- package/dist/types/lib/cause/strategies.d.ts +3 -0
- package/dist/types/lib/cause/types.d.ts +90 -0
- package/dist/types/lib/cause/validation.d.ts +24 -0
- package/dist/types/lib/cause/walk.d.ts +12 -0
- package/dist/types/lib/ids/attemptId.d.ts +63 -0
- package/dist/types/lib/memory/EventLog.d.ts +89 -3
- package/dist/types/lib/memory/honesty.d.ts +6 -4
- package/dist/types/lib/memory/sink.d.ts +148 -0
- package/dist/types/lib/memory/types.d.ts +4 -1
- package/dist/types/lib/time-travel/bundles.d.ts +2 -0
- package/dist/types/lib/time-travel/index.d.ts +2 -0
- package/dist/types/lib/time-travel/storedRecord.d.ts +195 -0
- package/dist/types/lib/time-travel/types.d.ts +4 -1
- package/dist/types/write.d.ts +7 -0
- package/dist/write.js +8 -1
- package/package.json +67 -13
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { isExecutionKey, isWithinSubflow, parseRuntimeStageId, stageIdOf } from '../ids/runtimeStageId.js';
|
|
12
12
|
import { buildCommitIndex, inferLegacyPhases, recordsPhases } from '../memory/commitLogUtils.js';
|
|
13
|
+
import { isPlainish } from './bundles.js';
|
|
13
14
|
/**
|
|
14
15
|
* Every `runtimeStageId` in the execution tree that the engine marked as a
|
|
15
16
|
* subflow mount. Authoritative, as is a bundle's recorded `phase: 'exit'`
|
|
@@ -89,7 +90,7 @@ export function commitStops(commitLog, executionTree, subflowResults) {
|
|
|
89
90
|
// the MOUNT's commit, made before any of them ran. It gets no stop of its
|
|
90
91
|
// own; it belongs to the `'start'` bookend, which is where a reader looks
|
|
91
92
|
// for "the input this chart began with" anyway. See {@link seedCommits}.
|
|
92
|
-
const seeds = seedCommits(commitLog, executionTree);
|
|
93
|
+
const seeds = seedCommits(commitLog, executionTree, subflowResults);
|
|
93
94
|
const firsts = [];
|
|
94
95
|
for (const [id, i] of buildCommitIndex(commitLog))
|
|
95
96
|
if (id && i >= seeds)
|
|
@@ -159,6 +160,22 @@ function phasesOf(commitLog, haveTree) {
|
|
|
159
160
|
return commitLog.map((bundle) => bundle.phase);
|
|
160
161
|
return haveTree ? [] : inferLegacyPhases(commitLog);
|
|
161
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* True when the results map's entry for `head` IS this log — its history opens with this very bundle.
|
|
165
|
+
* The log is then that mount's own, and its head run is the mount's seed (R13: a seed carries its
|
|
166
|
+
* mount's runtimeStageId). This is what a TREE-LESS cursor over a mount log reads in place of the
|
|
167
|
+
* tree's root — a stored log read back by `readStoredRecord`, or a log whose stage after the seed was
|
|
168
|
+
* lost — so the seed opens `start` instead of standing as a stop (marked as its own mount).
|
|
169
|
+
*/
|
|
170
|
+
function opensOwnMount(subflowResults, head, first) {
|
|
171
|
+
if (!isPlainish(subflowResults))
|
|
172
|
+
return false;
|
|
173
|
+
const result = subflowResults[head];
|
|
174
|
+
if (!isPlainish(result) || !isPlainish(result.treeContext))
|
|
175
|
+
return false;
|
|
176
|
+
const history = result.treeContext.history;
|
|
177
|
+
return Array.isArray(history) && history[0] === first;
|
|
178
|
+
}
|
|
162
179
|
/** `commitStops` as a {@link TimeTravelStrategy} — the default for `timeTravel`. */
|
|
163
180
|
export const commitStopsStrategy = { stopsFor: commitStops };
|
|
164
181
|
/**
|
|
@@ -179,7 +196,7 @@ export const commitStopsStrategy = { stopsFor: commitStops };
|
|
|
179
196
|
* whose id carries the same subflow path. Without a tree such a log reads as
|
|
180
197
|
* one stop.
|
|
181
198
|
*/
|
|
182
|
-
function seedCommits(commitLog, executionTree) {
|
|
199
|
+
function seedCommits(commitLog, executionTree, subflowResults) {
|
|
183
200
|
const head = commitLog[0]?.runtimeStageId;
|
|
184
201
|
if (head === undefined)
|
|
185
202
|
return 0;
|
|
@@ -192,6 +209,8 @@ function seedCommits(commitLog, executionTree) {
|
|
|
192
209
|
let next = 1;
|
|
193
210
|
while (next < commitLog.length && commitLog[next].runtimeStageId === head)
|
|
194
211
|
next++;
|
|
212
|
+
if (executionTree === undefined && opensOwnMount(subflowResults, head, commitLog[0]))
|
|
213
|
+
return next;
|
|
195
214
|
const address = stageIdOf(head);
|
|
196
215
|
if (next >= commitLog.length) {
|
|
197
216
|
const rootId = executionTree?.id;
|
|
@@ -17,6 +17,8 @@ export { filterStops, splitAxis } from './axis.js';
|
|
|
17
17
|
export { isCommitBundle } from './bundles.js';
|
|
18
18
|
export { commitStops, commitStopsStrategy } from './commitStops.js';
|
|
19
19
|
export { stateAt } from './stateAt.js';
|
|
20
|
+
export type { StoredLog, StoredNote, StoredRecord, StoredRecordLimits } from './storedRecord.js';
|
|
21
|
+
export { readStoredRecord } from './storedRecord.js';
|
|
20
22
|
export { tagStops } from './tagStops.js';
|
|
21
23
|
export { timeTravel } from './timeTravel.js';
|
|
22
24
|
export type { AxisRefusal, AxisSplit, BookendedAxis, FoldBasis, FoldedState, FoldSource, LogGap, Mark, Move, MoveRefusal, Stop, StopKind, TimeTravel, TimeTravelOptions, TimeTravelSource, TimeTravelStrategy, } from './types.js';
|
|
@@ -16,5 +16,6 @@ export { filterStops, splitAxis } from './axis.js';
|
|
|
16
16
|
export { isCommitBundle } from './bundles.js';
|
|
17
17
|
export { commitStops, commitStopsStrategy } from './commitStops.js';
|
|
18
18
|
export { stateAt } from './stateAt.js';
|
|
19
|
+
export { readStoredRecord } from './storedRecord.js';
|
|
19
20
|
export { tagStops } from './tagStops.js';
|
|
20
21
|
export { timeTravel } from './timeTravel.js';
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storedRecord.ts — reading back what a sink stored (1.1.0).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS. A log that writes to a `RecordSink` (`foottrace/write`) leaves its record in
|
|
5
|
+
* storage AS IT LANDS: an `opened` entry with the fold base, one `commit` entry per bundle, `note`
|
|
6
|
+
* entries, and a `closed` entry with how the log ended. A run that is killed never writes its
|
|
7
|
+
* `closed` entry. This reader turns stored entries back into logs a person or a model can read —
|
|
8
|
+
* and says, before any fold, which of them never finished (`complete: false`) and what each one is
|
|
9
|
+
* missing.
|
|
10
|
+
*
|
|
11
|
+
* WHAT IT READS. Entries come back from storage unvalidated (a parsed file, a table scan) — untrusted
|
|
12
|
+
* input — so it takes `Iterable<unknown>` and checks each one where it is read: the 9.18.0 law for
|
|
13
|
+
* stored recordings, no cast at the seam. An entry it does not read into a log is never dropped in
|
|
14
|
+
* silence: it is listed in `unread` with its position and the reason.
|
|
15
|
+
*
|
|
16
|
+
* THE LAWS IT KEEPS.
|
|
17
|
+
* 1. Grouped by address. Every entry names its log (`logRunId` + `drillPath`); entries of many
|
|
18
|
+
* logs may arrive interleaved, and each log keeps its own entries in arrival order.
|
|
19
|
+
* 2. A commit sits at its own index. `EventLog · record` stamps `bundle.idx` before the sink sees
|
|
20
|
+
* the bundle, so a commit lost on the way (a sink that failed once — the writer goes on) leaves a
|
|
21
|
+
* GAP at its index instead of shifting every commit after it; so does a bundle that is not a
|
|
22
|
+
* readable bundle. Every gap keeps its index, and the fold reports it (`skipped`) with the same
|
|
23
|
+
* reason the stored log gives (`gaps`). One place, one entry: the first READABLE commit entry
|
|
24
|
+
* for an index stands (a torn first delivery yields to a readable redelivery), the first
|
|
25
|
+
* `opened` stands, and every other entry for a place already held — a redelivery or a conflict,
|
|
26
|
+
* the very same object included — is listed in `unread`. Payloads are never compared, so no
|
|
27
|
+
* stored value is walked.
|
|
28
|
+
* 3. Complete means the writer finished — and the reader read it all. A log is `complete` when a
|
|
29
|
+
* `closed` entry covers every stored commit (`closed.commits` is past the last index); the
|
|
30
|
+
* covering close with the largest `commits` gives the `outcome` and the length, so no loss an
|
|
31
|
+
* earlier close showed can hide behind a later one. A log that was closed and then went on (a
|
|
32
|
+
* later commit) is not complete until it is closed again. A closed log whose store lost commits
|
|
33
|
+
* is complete AND has gaps (`closed.commits` makes even a lost LAST commit a gap). A log a limit
|
|
34
|
+
* cut, and every log once reading stopped at the `entries` limit, is not complete: what the
|
|
35
|
+
* reader did not read could have gone on.
|
|
36
|
+
* 4. Opaque stays opaque. `meta` and a note's `kind` and `data` are handed back as stored, never
|
|
37
|
+
* read or merged.
|
|
38
|
+
* 5. Read, never touched. Nothing here copies, freezes or edits the entries it is given — not the
|
|
39
|
+
* base, not `meta`: the stored log hands back their own values.
|
|
40
|
+
* 6. Bounded work (see {@link StoredRecordLimits}). Every walk is a loop over the input, never a
|
|
41
|
+
* recursion, and never through an input's own iterator except the input itself; the work per
|
|
42
|
+
* entry is bounded by the address limits; the rows a log holds are its stored commits plus gaps
|
|
43
|
+
* from ONE budget for the whole record, spent on the logs that need the fewest gaps first (so a
|
|
44
|
+
* corrupt log asking for the most is the one cut); and reading stops after `entries` entries. Whatever a
|
|
45
|
+
* limit stops is listed in `unread` by name — never a hang, never a silent cut.
|
|
46
|
+
*
|
|
47
|
+
* Entries are data. An entry whose fields throw while it is checked (a getter, a revoked proxy) is
|
|
48
|
+
* listed in `unread`, and what the reader computes with — the kind, a commit's index and bundle, a
|
|
49
|
+
* close's length — is taken once, as checked. An in-memory accessor that changes or throws on a later
|
|
50
|
+
* read is out of contract, as everywhere in the record (docs/extraction.md): parsed JSON and structured
|
|
51
|
+
* clones carry none.
|
|
52
|
+
*
|
|
53
|
+
* DAG position: memory ← time-travel; this file is a reader beside `timeTravel` and imports only
|
|
54
|
+
* record files.
|
|
55
|
+
*/
|
|
56
|
+
import type { LogAddress } from '../memory/eventPosition.js';
|
|
57
|
+
import { type LogOutcome, type Meta, type RecordEntry } from '../memory/sink.js';
|
|
58
|
+
import type { CommitBundle } from '../memory/types.js';
|
|
59
|
+
import type { LogGap, TimeTravelSource } from './types.js';
|
|
60
|
+
/** A note the writer kept beside a log (`EventLog · note`), as stored. */
|
|
61
|
+
export interface StoredNote {
|
|
62
|
+
/** The note's kind, as the writer named it. */
|
|
63
|
+
readonly kind: string;
|
|
64
|
+
/** When it was written (epoch ms). */
|
|
65
|
+
readonly at: number;
|
|
66
|
+
/** What the writer put in it — opaque. */
|
|
67
|
+
readonly data: Meta;
|
|
68
|
+
}
|
|
69
|
+
/** One log read back from a sink's entries. */
|
|
70
|
+
export interface StoredLog {
|
|
71
|
+
/** Which log this is: the run leg that owned it and the mounts it sits under (root: `drillPath: []`). */
|
|
72
|
+
readonly address: LogAddress;
|
|
73
|
+
/** The fold base the `opened` entry carried; ABSENT when no readable `opened` entry was stored. */
|
|
74
|
+
readonly initialState?: Record<string, unknown>;
|
|
75
|
+
/**
|
|
76
|
+
* The bundles, each at its own index (`bundle.idx`). An index with no readable bundle holds an empty
|
|
77
|
+
* stand-in (it belongs to no stage and carries nothing) and is named in {@link gaps} — check `gaps`
|
|
78
|
+
* before a causal query, which takes the bundles as they are; fold {@link source}, not this, so the
|
|
79
|
+
* fold reports them.
|
|
80
|
+
*/
|
|
81
|
+
readonly commitLog: readonly CommitBundle[];
|
|
82
|
+
/** Every index of {@link commitLog} that holds no readable bundle, with why — empty when the log is whole. */
|
|
83
|
+
readonly gaps: readonly LogGap[];
|
|
84
|
+
/**
|
|
85
|
+
* The notes, in arrival order. A store that delivers an entry twice as two copies repeats its note:
|
|
86
|
+
* payloads are never compared (the very same object twice is listed in `unread` instead).
|
|
87
|
+
*/
|
|
88
|
+
readonly notes: readonly StoredNote[];
|
|
89
|
+
/**
|
|
90
|
+
* `true` when a `closed` entry covers every stored commit and the reader read the log whole: the
|
|
91
|
+
* writer said it ended. `false` for a log that never closed — a crashed run, or one still going —
|
|
92
|
+
* for one that went on after its last close, and for one the reader did not read whole (a limit
|
|
93
|
+
* cut it, or reading stopped at the `entries` limit — see `StoredRecord.unread`).
|
|
94
|
+
*/
|
|
95
|
+
readonly complete: boolean;
|
|
96
|
+
/** How the covering `closed` entry said the log ended; ABSENT while the log is not complete. */
|
|
97
|
+
readonly outcome?: LogOutcome;
|
|
98
|
+
/**
|
|
99
|
+
* What the writer put on the `opened` entry and on the covering `closed` entry (present only while
|
|
100
|
+
* the log is complete) — opaque, never merged.
|
|
101
|
+
*/
|
|
102
|
+
readonly meta: {
|
|
103
|
+
readonly opened?: Meta;
|
|
104
|
+
readonly closed?: Meta;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* The entries this log was read from, in arrival order, as stored — the evidence the rest is read
|
|
108
|
+
* from (each entry's `at`, every `closed` entry). An entry listed in `StoredRecord.unread` is not
|
|
109
|
+
* here. A commit's bundle is the stored row: {@link gaps} names one that is not a readable bundle.
|
|
110
|
+
*/
|
|
111
|
+
readonly entries: readonly RecordEntry[];
|
|
112
|
+
/**
|
|
113
|
+
* The log as `timeTravel` and `stateAt` read it: `{ commitLog, initialState?, subflowResults? }`. A
|
|
114
|
+
* gap is a row the fold refuses with the same reason {@link gaps} gives (`skipped`), so a fold that
|
|
115
|
+
* crosses one says so. `subflowResults` holds every stored mount log of the run leg — this one too,
|
|
116
|
+
* when it is a mount — keyed by each mount's runtimeStageId (the last segment of its `drillPath`):
|
|
117
|
+
* one flat map per leg, as a live snapshot's, so the cursor marks those stops as mounts and
|
|
118
|
+
* `drill(mountRuntimeStageId)` opens the mount's own stored log. A mount log's head commit stamped
|
|
119
|
+
* with its own mount id is its seed (it opens `start`, as on a live cursor). The same object on
|
|
120
|
+
* every call.
|
|
121
|
+
*/
|
|
122
|
+
source(): TimeTravelSource;
|
|
123
|
+
}
|
|
124
|
+
/** Every log a sink's entries hold, and every entry no log holds. */
|
|
125
|
+
export interface StoredRecord {
|
|
126
|
+
/** Every log, keyed by its address as a JSON array — `["<logRunId>", ...drillPath]` — in first-arrival order. */
|
|
127
|
+
readonly logs: ReadonlyMap<string, StoredLog>;
|
|
128
|
+
/** The root log of the run leg `logRunId` (`drillPath: []`), or `undefined` when none was stored. */
|
|
129
|
+
root(logRunId: string): StoredLog | undefined;
|
|
130
|
+
/** The log at `address`, or `undefined` when none was stored. */
|
|
131
|
+
log(address: LogAddress): StoredLog | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Entries not read into any log, by position in the input, with the reason: an entry that could not
|
|
134
|
+
* be read, one past a limit of the reader, or one for a place another entry holds (a second `opened`,
|
|
135
|
+
* a second commit for an index, the same entry again). In input order; empty when every entry was
|
|
136
|
+
* read. A commit that could not be read at all, or that a limit cut, leaves no row: its index is a
|
|
137
|
+
* gap only when a later stored commit or a close reaches past it.
|
|
138
|
+
*/
|
|
139
|
+
readonly unread: readonly {
|
|
140
|
+
readonly index: number;
|
|
141
|
+
readonly reason: string;
|
|
142
|
+
}[];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The reader's limits — what bounds its work on untrusted input. Each defaults to the value below; a
|
|
146
|
+
* caller that reads larger records raises it. Whatever a limit stops is listed in `unread` by name.
|
|
147
|
+
*/
|
|
148
|
+
export interface StoredRecordLimits {
|
|
149
|
+
/**
|
|
150
|
+
* Entries read; past it, reading stops, one `unread` item says so, and no log reads complete.
|
|
151
|
+
* Default 16,777,216 (2^24).
|
|
152
|
+
*/
|
|
153
|
+
readonly entries?: number;
|
|
154
|
+
/**
|
|
155
|
+
* Gaps held across the WHOLE record — rows for commits the store does not have — spent on the logs
|
|
156
|
+
* that need the fewest first. A commit (or a close) that would need more is unread. Default
|
|
157
|
+
* 1,048,576 (2^20). The other rows a log holds are its stored commits.
|
|
158
|
+
*/
|
|
159
|
+
readonly missingCommits?: number;
|
|
160
|
+
/** Segments in an address's `drillPath`. Default 1,024. */
|
|
161
|
+
readonly drillDepth?: number;
|
|
162
|
+
/** Characters in an address — its `logRunId` and `drillPath` segments together. Default 65,536. */
|
|
163
|
+
readonly addressLength?: number;
|
|
164
|
+
}
|
|
165
|
+
/** The defaults — internal to the package; documented on {@link StoredRecordLimits}. */
|
|
166
|
+
export declare const DEFAULT_LIMITS: Required<StoredRecordLimits>;
|
|
167
|
+
/**
|
|
168
|
+
* Read a sink's stored entries back into logs.
|
|
169
|
+
*
|
|
170
|
+
* Bounded on untrusted input: no recursion, no payload walk, work per entry bounded by the address
|
|
171
|
+
* limits, at most `limits.missingCommits` gap rows across the record, and at most `limits.entries`
|
|
172
|
+
* entries read — whatever a limit stops is listed in `unread` by name.
|
|
173
|
+
*
|
|
174
|
+
* @param entries What a `RecordSink` stored (`memorySink().entries`, or entries parsed back from a
|
|
175
|
+
* file or a table), in the order they were written — typed `unknown` because stored entries come
|
|
176
|
+
* back unvalidated; each is checked here.
|
|
177
|
+
* @param limits Raise a default to read a larger record ({@link StoredRecordLimits}).
|
|
178
|
+
* @returns Every log by address, the run-leg roots, and every entry not read into a log.
|
|
179
|
+
* @throws RangeError when a limit is not a non-negative integer or `Infinity`; TypeError when
|
|
180
|
+
* `entries` is not iterable; whatever the input's own iterator throws. Never for an entry that is
|
|
181
|
+
* data (parsed JSON, a structured clone).
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* import { readStoredRecord, stateAt, timeTravel } from 'foottrace';
|
|
186
|
+
*
|
|
187
|
+
* const stored = readStoredRecord(entries); // e.g. JSON lines read back after a crash
|
|
188
|
+
* const run = stored.root('run-1');
|
|
189
|
+
* run?.complete; // false — it never closed
|
|
190
|
+
* run?.notes.map((n) => n.kind); // ['attempt', 'attempt'] — kept as they happened
|
|
191
|
+
* timeTravel(run!.source()).last(); // the cursor over what landed
|
|
192
|
+
* stateAt(run!.source(), run!.commitLog.length - 1).skipped; // a lost commit, by index and reason
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
export declare function readStoredRecord(entries: Iterable<unknown>, limits?: StoredRecordLimits): StoredRecord;
|