foottrace 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/lib/slice/keysReadSources.d.ts +10 -2
- package/dist/esm/lib/slice/keysReadSources.js +46 -12
- package/dist/esm/lib/time-travel/bundles.d.ts +4 -0
- package/dist/esm/lib/time-travel/bundles.js +9 -0
- package/dist/lib/slice/keysReadSources.js +46 -12
- package/dist/lib/time-travel/bundles.js +9 -0
- package/dist/types/lib/slice/keysReadSources.d.ts +10 -2
- package/dist/types/lib/time-travel/bundles.d.ts +4 -0
- package/package.json +1 -1
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* the call site instead: `(id) => recorder.getByKey(id)?.keysRead ?? []`.
|
|
10
10
|
*/
|
|
11
11
|
import type { KeysReadLookup } from '../memory/backtrack.js';
|
|
12
|
-
import type { ExecutionTree } from '../memory/types.js';
|
|
13
12
|
import type { KeysReadSource } from './types.js';
|
|
14
13
|
/**
|
|
15
14
|
* Post-hoc reads from a finished run's execution tree — ZERO setup.
|
|
@@ -36,8 +35,17 @@ import type { KeysReadSource } from './types.js';
|
|
|
36
35
|
* Passing multiple trees widens READ resolution only (useful when one log
|
|
37
36
|
* genuinely spans them); it does not let a root-log slice cross a subflow
|
|
38
37
|
* mount — see README.md § Subflow boundaries.
|
|
38
|
+
*
|
|
39
|
+
* A live {@link ExecutionTree} or unknown saved JSON uses this same traversal.
|
|
40
|
+
* Malformed reader fields throw a TypeError naming their source path; no partial
|
|
41
|
+
* reads strategy is returned. Optional fields may be absent or undefined, but
|
|
42
|
+
* null nodes, sparse arrays and wrongly shaped fields are not missing evidence
|
|
43
|
+
* to silently skip. Only read-map KEYS are inspected, never their stored values.
|
|
44
|
+
* Cycles/shared objects are visited once; existing traversal order is retained.
|
|
45
|
+
* Coverage counts observed nodes and nonempty read sets, not capture completeness.
|
|
46
|
+
* This is an ordinary-data boundary, not a sandbox for hostile proxies/accessors.
|
|
39
47
|
*/
|
|
40
|
-
export declare function keysReadFromExecutionTree(tree:
|
|
48
|
+
export declare function keysReadFromExecutionTree(tree: unknown): KeysReadSource;
|
|
41
49
|
/**
|
|
42
50
|
* Reads from a prebuilt map — e.g. collected live from
|
|
43
51
|
* `ScopeRecorder.onRead` events, or deserialized from a stored trace.
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* drag this leaf library up the dependency DAG. The adapter is a one-liner at
|
|
9
9
|
* the call site instead: `(id) => recorder.getByKey(id)?.keysRead ?? []`.
|
|
10
10
|
*/
|
|
11
|
+
/** Narrow only the object shape this reader uses; stored values stay opaque. */
|
|
12
|
+
function isReadTreeObject(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
11
15
|
/**
|
|
12
16
|
* Post-hoc reads from a finished run's execution tree — ZERO setup.
|
|
13
17
|
*
|
|
@@ -33,33 +37,63 @@
|
|
|
33
37
|
* Passing multiple trees widens READ resolution only (useful when one log
|
|
34
38
|
* genuinely spans them); it does not let a root-log slice cross a subflow
|
|
35
39
|
* mount — see README.md § Subflow boundaries.
|
|
40
|
+
*
|
|
41
|
+
* A live {@link ExecutionTree} or unknown saved JSON uses this same traversal.
|
|
42
|
+
* Malformed reader fields throw a TypeError naming their source path; no partial
|
|
43
|
+
* reads strategy is returned. Optional fields may be absent or undefined, but
|
|
44
|
+
* null nodes, sparse arrays and wrongly shaped fields are not missing evidence
|
|
45
|
+
* to silently skip. Only read-map KEYS are inspected, never their stored values.
|
|
46
|
+
* Cycles/shared objects are visited once; existing traversal order is retained.
|
|
47
|
+
* Coverage counts observed nodes and nonempty read sets, not capture completeness.
|
|
48
|
+
* This is an ordinary-data boundary, not a sandbox for hostile proxies/accessors.
|
|
36
49
|
*/
|
|
37
50
|
export function keysReadFromExecutionTree(tree) {
|
|
38
51
|
const byStep = new Map();
|
|
39
52
|
let steps = 0;
|
|
40
53
|
// The tree is acyclic by construction (next/children), but this walker is
|
|
41
|
-
// also handed CONSUMER-provided data — a visited set makes a
|
|
54
|
+
// also handed CONSUMER-provided data — a visited set makes a cycle in a
|
|
42
55
|
// hand-built tree a non-event instead of an infinite loop.
|
|
43
56
|
const visited = new Set();
|
|
44
|
-
const stack = [
|
|
57
|
+
const stack = [];
|
|
58
|
+
if (Array.isArray(tree)) {
|
|
59
|
+
// Indexing retains holes as invalid evidence, unlike flat()/map().
|
|
60
|
+
for (let i = 0; i < tree.length; i++)
|
|
61
|
+
stack.push({ node: tree[i], path: `$[${i}]` });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
stack.push({ node: tree, path: '$' });
|
|
65
|
+
}
|
|
45
66
|
while (stack.length > 0) {
|
|
46
|
-
const node = stack.pop();
|
|
67
|
+
const { node, path } = stack.pop();
|
|
68
|
+
if (!isReadTreeObject(node))
|
|
69
|
+
throw new TypeError(`keysReadFromExecutionTree: ${path} must be an object`);
|
|
47
70
|
if (visited.has(node))
|
|
48
71
|
continue;
|
|
49
72
|
visited.add(node);
|
|
50
|
-
|
|
73
|
+
const { runtimeStageId, stageReads, next, children } = node;
|
|
74
|
+
if (runtimeStageId !== undefined && typeof runtimeStageId !== 'string') {
|
|
75
|
+
throw new TypeError(`keysReadFromExecutionTree: ${path}.runtimeStageId must be a string when present`);
|
|
76
|
+
}
|
|
77
|
+
if (stageReads !== undefined && !isReadTreeObject(stageReads)) {
|
|
78
|
+
throw new TypeError(`keysReadFromExecutionTree: ${path}.stageReads must be an object when present`);
|
|
79
|
+
}
|
|
80
|
+
if (children !== undefined && !Array.isArray(children)) {
|
|
81
|
+
throw new TypeError(`keysReadFromExecutionTree: ${path}.children must be an array when present`);
|
|
82
|
+
}
|
|
83
|
+
if (runtimeStageId) {
|
|
51
84
|
steps++;
|
|
52
|
-
if (
|
|
53
|
-
const keys = Object.keys(
|
|
85
|
+
if (stageReads !== undefined) {
|
|
86
|
+
const keys = Object.keys(stageReads);
|
|
54
87
|
if (keys.length > 0)
|
|
55
|
-
byStep.set(
|
|
88
|
+
byStep.set(runtimeStageId, keys);
|
|
56
89
|
}
|
|
57
90
|
}
|
|
58
|
-
if (
|
|
59
|
-
stack.push(node.next);
|
|
60
|
-
if (
|
|
61
|
-
for (
|
|
62
|
-
stack.push(
|
|
91
|
+
if (next !== undefined)
|
|
92
|
+
stack.push({ node: next, path: `${path}.next` });
|
|
93
|
+
if (children !== undefined) {
|
|
94
|
+
for (let i = 0; i < children.length; i++)
|
|
95
|
+
stack.push({ node: children[i], path: `${path}.children[${i}]` });
|
|
96
|
+
}
|
|
63
97
|
}
|
|
64
98
|
return {
|
|
65
99
|
kind: 'execution-tree',
|
|
@@ -41,6 +41,10 @@ export declare function readTree(value: unknown): ExecutionTree | undefined;
|
|
|
41
41
|
* `changedSince` reads its paths), so a row without one is not a bundle the
|
|
42
42
|
* fold can survive — it is the crash a gap exists to prevent. `CommitBundle`
|
|
43
43
|
* declares it required, so no live log is affected.
|
|
44
|
+
* - Each trace entry needs a known verb and a string `path`. Readers use
|
|
45
|
+
* string path operations; accepting a number can silently lose a causal
|
|
46
|
+
* writer in a short log and crash the indexed lookup in a longer one.
|
|
47
|
+
* Empty strings and historical path spellings remain valid strings.
|
|
44
48
|
* - `updates` / `overwrite`, when present, must be plain objects. They are
|
|
45
49
|
* read only AT THE PATHS THE TRACE NAMES, so an absent one is harmless and
|
|
46
50
|
* is tolerated, exactly as the fold tolerates an absent `redactedPaths`.
|
|
@@ -63,6 +63,10 @@ export function readTree(value) {
|
|
|
63
63
|
* `changedSince` reads its paths), so a row without one is not a bundle the
|
|
64
64
|
* fold can survive — it is the crash a gap exists to prevent. `CommitBundle`
|
|
65
65
|
* declares it required, so no live log is affected.
|
|
66
|
+
* - Each trace entry needs a known verb and a string `path`. Readers use
|
|
67
|
+
* string path operations; accepting a number can silently lose a causal
|
|
68
|
+
* writer in a short log and crash the indexed lookup in a longer one.
|
|
69
|
+
* Empty strings and historical path spellings remain valid strings.
|
|
66
70
|
* - `updates` / `overwrite`, when present, must be plain objects. They are
|
|
67
71
|
* read only AT THE PATHS THE TRACE NAMES, so an absent one is harmless and
|
|
68
72
|
* is tolerated, exactly as the fold tolerates an absent `redactedPaths`.
|
|
@@ -95,6 +99,11 @@ export function bundleRefusal(row) {
|
|
|
95
99
|
if (!isVerb(verb)) {
|
|
96
100
|
return `trace[${i}].verb is ${verb === undefined ? 'missing' : JSON.stringify(verb)}, not ${VERBS.join(' | ')}`;
|
|
97
101
|
}
|
|
102
|
+
const path = isPlainish(entry) ? entry.path : undefined;
|
|
103
|
+
if (typeof path !== 'string') {
|
|
104
|
+
const kind = path === undefined ? 'missing' : path === null ? 'null' : Array.isArray(path) ? 'an array' : `a ${typeof path}`;
|
|
105
|
+
return `trace[${i}].path is ${kind}, not a string`;
|
|
106
|
+
}
|
|
98
107
|
}
|
|
99
108
|
return undefined;
|
|
100
109
|
}
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.resolveKeysReadSource = exports.keysReadFromMap = exports.keysReadFromExecutionTree = void 0;
|
|
14
|
+
/** Narrow only the object shape this reader uses; stored values stay opaque. */
|
|
15
|
+
function isReadTreeObject(value) {
|
|
16
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
14
18
|
/**
|
|
15
19
|
* Post-hoc reads from a finished run's execution tree — ZERO setup.
|
|
16
20
|
*
|
|
@@ -36,33 +40,63 @@ exports.resolveKeysReadSource = exports.keysReadFromMap = exports.keysReadFromEx
|
|
|
36
40
|
* Passing multiple trees widens READ resolution only (useful when one log
|
|
37
41
|
* genuinely spans them); it does not let a root-log slice cross a subflow
|
|
38
42
|
* mount — see README.md § Subflow boundaries.
|
|
43
|
+
*
|
|
44
|
+
* A live {@link ExecutionTree} or unknown saved JSON uses this same traversal.
|
|
45
|
+
* Malformed reader fields throw a TypeError naming their source path; no partial
|
|
46
|
+
* reads strategy is returned. Optional fields may be absent or undefined, but
|
|
47
|
+
* null nodes, sparse arrays and wrongly shaped fields are not missing evidence
|
|
48
|
+
* to silently skip. Only read-map KEYS are inspected, never their stored values.
|
|
49
|
+
* Cycles/shared objects are visited once; existing traversal order is retained.
|
|
50
|
+
* Coverage counts observed nodes and nonempty read sets, not capture completeness.
|
|
51
|
+
* This is an ordinary-data boundary, not a sandbox for hostile proxies/accessors.
|
|
39
52
|
*/
|
|
40
53
|
function keysReadFromExecutionTree(tree) {
|
|
41
54
|
const byStep = new Map();
|
|
42
55
|
let steps = 0;
|
|
43
56
|
// The tree is acyclic by construction (next/children), but this walker is
|
|
44
|
-
// also handed CONSUMER-provided data — a visited set makes a
|
|
57
|
+
// also handed CONSUMER-provided data — a visited set makes a cycle in a
|
|
45
58
|
// hand-built tree a non-event instead of an infinite loop.
|
|
46
59
|
const visited = new Set();
|
|
47
|
-
const stack = [
|
|
60
|
+
const stack = [];
|
|
61
|
+
if (Array.isArray(tree)) {
|
|
62
|
+
// Indexing retains holes as invalid evidence, unlike flat()/map().
|
|
63
|
+
for (let i = 0; i < tree.length; i++)
|
|
64
|
+
stack.push({ node: tree[i], path: `$[${i}]` });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
stack.push({ node: tree, path: '$' });
|
|
68
|
+
}
|
|
48
69
|
while (stack.length > 0) {
|
|
49
|
-
const node = stack.pop();
|
|
70
|
+
const { node, path } = stack.pop();
|
|
71
|
+
if (!isReadTreeObject(node))
|
|
72
|
+
throw new TypeError(`keysReadFromExecutionTree: ${path} must be an object`);
|
|
50
73
|
if (visited.has(node))
|
|
51
74
|
continue;
|
|
52
75
|
visited.add(node);
|
|
53
|
-
|
|
76
|
+
const { runtimeStageId, stageReads, next, children } = node;
|
|
77
|
+
if (runtimeStageId !== undefined && typeof runtimeStageId !== 'string') {
|
|
78
|
+
throw new TypeError(`keysReadFromExecutionTree: ${path}.runtimeStageId must be a string when present`);
|
|
79
|
+
}
|
|
80
|
+
if (stageReads !== undefined && !isReadTreeObject(stageReads)) {
|
|
81
|
+
throw new TypeError(`keysReadFromExecutionTree: ${path}.stageReads must be an object when present`);
|
|
82
|
+
}
|
|
83
|
+
if (children !== undefined && !Array.isArray(children)) {
|
|
84
|
+
throw new TypeError(`keysReadFromExecutionTree: ${path}.children must be an array when present`);
|
|
85
|
+
}
|
|
86
|
+
if (runtimeStageId) {
|
|
54
87
|
steps++;
|
|
55
|
-
if (
|
|
56
|
-
const keys = Object.keys(
|
|
88
|
+
if (stageReads !== undefined) {
|
|
89
|
+
const keys = Object.keys(stageReads);
|
|
57
90
|
if (keys.length > 0)
|
|
58
|
-
byStep.set(
|
|
91
|
+
byStep.set(runtimeStageId, keys);
|
|
59
92
|
}
|
|
60
93
|
}
|
|
61
|
-
if (
|
|
62
|
-
stack.push(node.next);
|
|
63
|
-
if (
|
|
64
|
-
for (
|
|
65
|
-
stack.push(
|
|
94
|
+
if (next !== undefined)
|
|
95
|
+
stack.push({ node: next, path: `${path}.next` });
|
|
96
|
+
if (children !== undefined) {
|
|
97
|
+
for (let i = 0; i < children.length; i++)
|
|
98
|
+
stack.push({ node: children[i], path: `${path}.children[${i}]` });
|
|
99
|
+
}
|
|
66
100
|
}
|
|
67
101
|
return {
|
|
68
102
|
kind: 'execution-tree',
|
|
@@ -68,6 +68,10 @@ exports.readTree = readTree;
|
|
|
68
68
|
* `changedSince` reads its paths), so a row without one is not a bundle the
|
|
69
69
|
* fold can survive — it is the crash a gap exists to prevent. `CommitBundle`
|
|
70
70
|
* declares it required, so no live log is affected.
|
|
71
|
+
* - Each trace entry needs a known verb and a string `path`. Readers use
|
|
72
|
+
* string path operations; accepting a number can silently lose a causal
|
|
73
|
+
* writer in a short log and crash the indexed lookup in a longer one.
|
|
74
|
+
* Empty strings and historical path spellings remain valid strings.
|
|
71
75
|
* - `updates` / `overwrite`, when present, must be plain objects. They are
|
|
72
76
|
* read only AT THE PATHS THE TRACE NAMES, so an absent one is harmless and
|
|
73
77
|
* is tolerated, exactly as the fold tolerates an absent `redactedPaths`.
|
|
@@ -100,6 +104,11 @@ function bundleRefusal(row) {
|
|
|
100
104
|
if (!(0, verbs_js_1.isVerb)(verb)) {
|
|
101
105
|
return `trace[${i}].verb is ${verb === undefined ? 'missing' : JSON.stringify(verb)}, not ${verbs_js_1.VERBS.join(' | ')}`;
|
|
102
106
|
}
|
|
107
|
+
const path = isPlainish(entry) ? entry.path : undefined;
|
|
108
|
+
if (typeof path !== 'string') {
|
|
109
|
+
const kind = path === undefined ? 'missing' : path === null ? 'null' : Array.isArray(path) ? 'an array' : `a ${typeof path}`;
|
|
110
|
+
return `trace[${i}].path is ${kind}, not a string`;
|
|
111
|
+
}
|
|
103
112
|
}
|
|
104
113
|
return undefined;
|
|
105
114
|
}
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* the call site instead: `(id) => recorder.getByKey(id)?.keysRead ?? []`.
|
|
10
10
|
*/
|
|
11
11
|
import type { KeysReadLookup } from '../memory/backtrack.js';
|
|
12
|
-
import type { ExecutionTree } from '../memory/types.js';
|
|
13
12
|
import type { KeysReadSource } from './types.js';
|
|
14
13
|
/**
|
|
15
14
|
* Post-hoc reads from a finished run's execution tree — ZERO setup.
|
|
@@ -36,8 +35,17 @@ import type { KeysReadSource } from './types.js';
|
|
|
36
35
|
* Passing multiple trees widens READ resolution only (useful when one log
|
|
37
36
|
* genuinely spans them); it does not let a root-log slice cross a subflow
|
|
38
37
|
* mount — see README.md § Subflow boundaries.
|
|
38
|
+
*
|
|
39
|
+
* A live {@link ExecutionTree} or unknown saved JSON uses this same traversal.
|
|
40
|
+
* Malformed reader fields throw a TypeError naming their source path; no partial
|
|
41
|
+
* reads strategy is returned. Optional fields may be absent or undefined, but
|
|
42
|
+
* null nodes, sparse arrays and wrongly shaped fields are not missing evidence
|
|
43
|
+
* to silently skip. Only read-map KEYS are inspected, never their stored values.
|
|
44
|
+
* Cycles/shared objects are visited once; existing traversal order is retained.
|
|
45
|
+
* Coverage counts observed nodes and nonempty read sets, not capture completeness.
|
|
46
|
+
* This is an ordinary-data boundary, not a sandbox for hostile proxies/accessors.
|
|
39
47
|
*/
|
|
40
|
-
export declare function keysReadFromExecutionTree(tree:
|
|
48
|
+
export declare function keysReadFromExecutionTree(tree: unknown): KeysReadSource;
|
|
41
49
|
/**
|
|
42
50
|
* Reads from a prebuilt map — e.g. collected live from
|
|
43
51
|
* `ScopeRecorder.onRead` events, or deserialized from a stored trace.
|
|
@@ -41,6 +41,10 @@ export declare function readTree(value: unknown): ExecutionTree | undefined;
|
|
|
41
41
|
* `changedSince` reads its paths), so a row without one is not a bundle the
|
|
42
42
|
* fold can survive — it is the crash a gap exists to prevent. `CommitBundle`
|
|
43
43
|
* declares it required, so no live log is affected.
|
|
44
|
+
* - Each trace entry needs a known verb and a string `path`. Readers use
|
|
45
|
+
* string path operations; accepting a number can silently lose a causal
|
|
46
|
+
* writer in a short log and crash the indexed lookup in a longer one.
|
|
47
|
+
* Empty strings and historical path spellings remain valid strings.
|
|
44
48
|
* - `updates` / `overwrite`, when present, must be plain objects. They are
|
|
45
49
|
* read only AT THE PATHS THE TRACE NAMES, so an absent one is harmless and
|
|
46
50
|
* is tolerated, exactly as the fold tolerates an absent `redactedPaths`.
|
package/package.json
CHANGED