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.
Files changed (128) hide show
  1. package/README.md +137 -4
  2. package/dist/cause.js +14 -0
  3. package/dist/esm/cause.d.ts +8 -0
  4. package/dist/esm/cause.js +5 -0
  5. package/dist/esm/index.d.ts +7 -2
  6. package/dist/esm/index.js +2 -1
  7. package/dist/esm/lib/cause/admission.d.ts +27 -0
  8. package/dist/esm/lib/cause/admission.js +59 -0
  9. package/dist/esm/lib/cause/explain.d.ts +5 -0
  10. package/dist/esm/lib/cause/explain.js +114 -0
  11. package/dist/esm/lib/cause/ranking.d.ts +6 -0
  12. package/dist/esm/lib/cause/ranking.js +10 -0
  13. package/dist/esm/lib/cause/recordAdapter.d.ts +9 -0
  14. package/dist/esm/lib/cause/recordAdapter.js +49 -0
  15. package/dist/esm/lib/cause/recordChildren.d.ts +4 -0
  16. package/dist/esm/lib/cause/recordChildren.js +79 -0
  17. package/dist/esm/lib/cause/recordControl.d.ts +13 -0
  18. package/dist/esm/lib/cause/recordControl.js +66 -0
  19. package/dist/esm/lib/cause/recordCoverage.d.ts +24 -0
  20. package/dist/esm/lib/cause/recordCoverage.js +87 -0
  21. package/dist/esm/lib/cause/recordIdentity.d.ts +9 -0
  22. package/dist/esm/lib/cause/recordIdentity.js +67 -0
  23. package/dist/esm/lib/cause/recordLoading.d.ts +7 -0
  24. package/dist/esm/lib/cause/recordLoading.js +116 -0
  25. package/dist/esm/lib/cause/recordParents.d.ts +4 -0
  26. package/dist/esm/lib/cause/recordParents.js +86 -0
  27. package/dist/esm/lib/cause/recordScope.d.ts +8 -0
  28. package/dist/esm/lib/cause/recordScope.js +60 -0
  29. package/dist/esm/lib/cause/recordTypes.d.ts +67 -0
  30. package/dist/esm/lib/cause/recordTypes.js +1 -0
  31. package/dist/esm/lib/cause/recordValidation.d.ts +33 -0
  32. package/dist/esm/lib/cause/recordValidation.js +198 -0
  33. package/dist/esm/lib/cause/session.d.ts +30 -0
  34. package/dist/esm/lib/cause/session.js +161 -0
  35. package/dist/esm/lib/cause/storedRecordAdapter.d.ts +11 -0
  36. package/dist/esm/lib/cause/storedRecordAdapter.js +57 -0
  37. package/dist/esm/lib/cause/strategies.d.ts +3 -0
  38. package/dist/esm/lib/cause/strategies.js +21 -0
  39. package/dist/esm/lib/cause/types.d.ts +90 -0
  40. package/dist/esm/lib/cause/types.js +1 -0
  41. package/dist/esm/lib/cause/validation.d.ts +24 -0
  42. package/dist/esm/lib/cause/validation.js +134 -0
  43. package/dist/esm/lib/cause/walk.d.ts +12 -0
  44. package/dist/esm/lib/cause/walk.js +174 -0
  45. package/dist/esm/lib/ids/attemptId.d.ts +63 -0
  46. package/dist/esm/lib/ids/attemptId.js +91 -0
  47. package/dist/esm/lib/memory/EventLog.d.ts +89 -3
  48. package/dist/esm/lib/memory/EventLog.js +242 -3
  49. package/dist/esm/lib/memory/honesty.d.ts +6 -4
  50. package/dist/esm/lib/memory/honesty.js +7 -4
  51. package/dist/esm/lib/memory/sink.d.ts +148 -0
  52. package/dist/esm/lib/memory/sink.js +92 -0
  53. package/dist/esm/lib/memory/types.d.ts +4 -1
  54. package/dist/esm/lib/memory/types.js +1 -1
  55. package/dist/esm/lib/time-travel/bundles.d.ts +2 -0
  56. package/dist/esm/lib/time-travel/bundles.js +16 -0
  57. package/dist/esm/lib/time-travel/commitStops.js +21 -2
  58. package/dist/esm/lib/time-travel/index.d.ts +2 -0
  59. package/dist/esm/lib/time-travel/index.js +1 -0
  60. package/dist/esm/lib/time-travel/storedRecord.d.ts +195 -0
  61. package/dist/esm/lib/time-travel/storedRecord.js +494 -0
  62. package/dist/esm/lib/time-travel/timeTravel.js +11 -0
  63. package/dist/esm/lib/time-travel/types.d.ts +4 -1
  64. package/dist/esm/write.d.ts +7 -0
  65. package/dist/esm/write.js +6 -0
  66. package/dist/index.js +6 -2
  67. package/dist/lib/cause/admission.js +67 -0
  68. package/dist/lib/cause/explain.js +119 -0
  69. package/dist/lib/cause/ranking.js +14 -0
  70. package/dist/lib/cause/recordAdapter.js +55 -0
  71. package/dist/lib/cause/recordChildren.js +83 -0
  72. package/dist/lib/cause/recordControl.js +74 -0
  73. package/dist/lib/cause/recordCoverage.js +99 -0
  74. package/dist/lib/cause/recordIdentity.js +73 -0
  75. package/dist/lib/cause/recordLoading.js +121 -0
  76. package/dist/lib/cause/recordParents.js +90 -0
  77. package/dist/lib/cause/recordScope.js +66 -0
  78. package/dist/lib/cause/recordTypes.js +2 -0
  79. package/dist/lib/cause/recordValidation.js +213 -0
  80. package/dist/lib/cause/session.js +165 -0
  81. package/dist/lib/cause/storedRecordAdapter.js +61 -0
  82. package/dist/lib/cause/strategies.js +26 -0
  83. package/dist/lib/cause/types.js +2 -0
  84. package/dist/lib/cause/validation.js +150 -0
  85. package/dist/lib/cause/walk.js +180 -0
  86. package/dist/lib/ids/attemptId.js +96 -0
  87. package/dist/lib/memory/EventLog.js +242 -3
  88. package/dist/lib/memory/honesty.js +7 -4
  89. package/dist/lib/memory/sink.js +98 -0
  90. package/dist/lib/memory/types.js +1 -1
  91. package/dist/lib/time-travel/bundles.js +18 -1
  92. package/dist/lib/time-travel/commitStops.js +21 -2
  93. package/dist/lib/time-travel/index.js +3 -1
  94. package/dist/lib/time-travel/storedRecord.js +498 -0
  95. package/dist/lib/time-travel/timeTravel.js +11 -0
  96. package/dist/types/cause.d.ts +8 -0
  97. package/dist/types/index.d.ts +7 -2
  98. package/dist/types/lib/cause/admission.d.ts +27 -0
  99. package/dist/types/lib/cause/explain.d.ts +5 -0
  100. package/dist/types/lib/cause/ranking.d.ts +6 -0
  101. package/dist/types/lib/cause/recordAdapter.d.ts +9 -0
  102. package/dist/types/lib/cause/recordChildren.d.ts +4 -0
  103. package/dist/types/lib/cause/recordControl.d.ts +13 -0
  104. package/dist/types/lib/cause/recordCoverage.d.ts +24 -0
  105. package/dist/types/lib/cause/recordIdentity.d.ts +9 -0
  106. package/dist/types/lib/cause/recordLoading.d.ts +7 -0
  107. package/dist/types/lib/cause/recordParents.d.ts +4 -0
  108. package/dist/types/lib/cause/recordScope.d.ts +8 -0
  109. package/dist/types/lib/cause/recordTypes.d.ts +67 -0
  110. package/dist/types/lib/cause/recordValidation.d.ts +33 -0
  111. package/dist/types/lib/cause/session.d.ts +30 -0
  112. package/dist/types/lib/cause/storedRecordAdapter.d.ts +11 -0
  113. package/dist/types/lib/cause/strategies.d.ts +3 -0
  114. package/dist/types/lib/cause/types.d.ts +90 -0
  115. package/dist/types/lib/cause/validation.d.ts +24 -0
  116. package/dist/types/lib/cause/walk.d.ts +12 -0
  117. package/dist/types/lib/ids/attemptId.d.ts +63 -0
  118. package/dist/types/lib/memory/EventLog.d.ts +89 -3
  119. package/dist/types/lib/memory/honesty.d.ts +6 -4
  120. package/dist/types/lib/memory/sink.d.ts +148 -0
  121. package/dist/types/lib/memory/types.d.ts +4 -1
  122. package/dist/types/lib/time-travel/bundles.d.ts +2 -0
  123. package/dist/types/lib/time-travel/index.d.ts +2 -0
  124. package/dist/types/lib/time-travel/storedRecord.d.ts +195 -0
  125. package/dist/types/lib/time-travel/types.d.ts +4 -1
  126. package/dist/types/write.d.ts +7 -0
  127. package/dist/write.js +8 -1
  128. package/package.json +67 -13
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uniqueGaps = exports.gap = exports.nodeIds = exports.adjacency = exports.gaps = exports.attention = exports.refs = exports.list = exports.name = exports.plain = exports.own = exports.observePromise = exports.AdmissionError = void 0;
4
+ const admission_js_1 = require("./admission.js");
5
+ /** Graph-port refusals carry their code through Session; loader refusals are handled at the record boundary. */
6
+ class AdmissionError extends admission_js_1.EvidenceRefusal {
7
+ code;
8
+ constructor(code = 'invalid-data') {
9
+ super(code, 'Invalid cause evidence');
10
+ this.code = code;
11
+ }
12
+ }
13
+ exports.AdmissionError = AdmissionError;
14
+ /**
15
+ * Only ordinary native promise wrappers belong to the async host contract.
16
+ * An own constructor could run an accessor/species during intrinsic then().
17
+ * Exotic wrappers remain the trusted host's responsibility, including rejection handling.
18
+ */
19
+ function observePromise(raw, accept, reject) {
20
+ if (!raw || typeof raw !== 'object' || Object.getPrototypeOf(raw) !== Promise.prototype)
21
+ return false;
22
+ if (Object.getOwnPropertyDescriptor(raw, 'constructor'))
23
+ throw new AdmissionError();
24
+ try {
25
+ Promise.prototype.then.call(raw, accept, reject);
26
+ }
27
+ catch {
28
+ throw new AdmissionError();
29
+ }
30
+ return true;
31
+ }
32
+ exports.observePromise = observePromise;
33
+ const refuseGraph = (code) => new AdmissionError(code);
34
+ const graphDescriptors = { translateTrapErrors: false, requireOwnValue: false };
35
+ function own(value, key) {
36
+ return (0, admission_js_1.ownData)(value, key, refuseGraph, graphDescriptors);
37
+ }
38
+ exports.own = own;
39
+ function plain(value) {
40
+ if (!value || typeof value !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
41
+ throw new AdmissionError();
42
+ }
43
+ exports.plain = plain;
44
+ function name(value, maximum = 2048) {
45
+ return (0, admission_js_1.boundedName)(value, maximum, refuseGraph);
46
+ }
47
+ exports.name = name;
48
+ function list(value, maximum, read, overflow = 'invalid-data') {
49
+ return (0, admission_js_1.boundedList)(value, maximum, read, {
50
+ refuse: refuseGraph,
51
+ overflow,
52
+ freeze: true,
53
+ descriptors: graphDescriptors,
54
+ });
55
+ }
56
+ exports.list = list;
57
+ const reasons = new Set([
58
+ 'ERROR_RECORDED',
59
+ 'UNKNOWN_EFFECT_RECORDED',
60
+ 'RETRY_STOP_RECORDED',
61
+ 'TIMEOUT_RECORDED',
62
+ 'RETRY_RECORDED',
63
+ 'QUALITY_DROP_RECORDED',
64
+ 'EVIDENCE_NOT_COLLECTED',
65
+ 'OUTLIER_RECORDED',
66
+ 'BUSINESS_RULE_MATCHED',
67
+ ]);
68
+ const codes = new Set([
69
+ 'not-collected',
70
+ 'untracked-input',
71
+ 'redacted',
72
+ 'conservative',
73
+ 'scope-boundary',
74
+ 'invalid-data',
75
+ 'unavailable',
76
+ 'timeout',
77
+ 'cancelled',
78
+ 'cycle',
79
+ 'node-limit',
80
+ 'depth-limit',
81
+ 'edge-limit',
82
+ 'call-limit',
83
+ 'truncated',
84
+ 'strategy-failed',
85
+ ]);
86
+ const refs = (value) => list(value, 16, (item) => name(item, 512));
87
+ exports.refs = refs;
88
+ function attention(value) {
89
+ return list(value, 32, (item) => {
90
+ plain(item);
91
+ const kind = own(item, 'kind'), reason = own(item, 'reason');
92
+ if (typeof kind !== 'string' ||
93
+ !/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(kind) ||
94
+ typeof reason !== 'string' ||
95
+ !reasons.has(reason))
96
+ throw new AdmissionError();
97
+ const evidence = (0, exports.refs)(own(item, 'evidence'));
98
+ if (kind !== 'not-collected' && evidence.length === 0)
99
+ throw new AdmissionError();
100
+ return Object.freeze({ kind, reason: reason, evidence });
101
+ });
102
+ }
103
+ exports.attention = attention;
104
+ function gaps(value) {
105
+ return list(value, 32, (item) => {
106
+ plain(item);
107
+ const code = own(item, 'code'), nodeId = own(item, 'nodeId');
108
+ if (typeof code !== 'string' || !codes.has(code))
109
+ throw new AdmissionError();
110
+ return Object.freeze({
111
+ code: code,
112
+ ...(nodeId === undefined ? {} : { nodeId: name(nodeId) }),
113
+ evidence: (0, exports.refs)(own(item, 'evidence')),
114
+ });
115
+ });
116
+ }
117
+ exports.gaps = gaps;
118
+ function adjacency(value, maximum) {
119
+ plain(value);
120
+ const complete = own(value, 'complete');
121
+ if (typeof complete !== 'boolean')
122
+ throw new AdmissionError();
123
+ const edges = list(own(value, 'edges'), maximum, (item) => {
124
+ plain(item);
125
+ const nodeId = name(own(item, 'nodeId')), kind = own(item, 'kind'), attribution = own(item, 'attribution');
126
+ if ((kind !== 'data' && kind !== 'control') ||
127
+ (attribution !== 'per-write' && attribution !== 'stage' && attribution !== 'recorded-control') ||
128
+ (kind === 'control' && attribution !== 'recorded-control') ||
129
+ (kind === 'data' && attribution === 'recorded-control'))
130
+ throw new AdmissionError();
131
+ return Object.freeze({ nodeId, kind, attribution, evidence: (0, exports.refs)(own(item, 'evidence')) });
132
+ }, 'edge-limit');
133
+ return Object.freeze({ edges, complete });
134
+ }
135
+ exports.adjacency = adjacency;
136
+ const nodeIds = (value, max) => list(value, max, (item) => name(item), 'node-limit');
137
+ exports.nodeIds = nodeIds;
138
+ const gap = (code, nodeId) => Object.freeze({ code, ...(nodeId === undefined ? {} : { nodeId }), evidence: Object.freeze([]) });
139
+ exports.gap = gap;
140
+ function uniqueGaps(items) {
141
+ const seen = new Set();
142
+ return Object.freeze(items.filter((item) => {
143
+ const key = JSON.stringify(item);
144
+ if (seen.has(key))
145
+ return false;
146
+ seen.add(key);
147
+ return true;
148
+ }));
149
+ }
150
+ exports.uniqueGaps = uniqueGaps;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pathTo = exports.frontier = exports.walk = void 0;
4
+ const validation_js_1 = require("./validation.js");
5
+ /** BFS discovers the shallowest arrival; a repeated diamond node is not a cycle. */
6
+ async function walk(session, seeds, direction) {
7
+ const queue = [], seen = new Set(), marks = [], edges = [], missing = [];
8
+ const enqueue = (id, depth) => {
9
+ if (seen.has(id))
10
+ return;
11
+ const refusal = session.admit(id);
12
+ if (refusal) {
13
+ missing.push(refusal);
14
+ return;
15
+ }
16
+ seen.add(id);
17
+ queue.push([id, depth]);
18
+ };
19
+ for (const id of seeds)
20
+ enqueue(id, 0);
21
+ for (let index = 0; index < queue.length; index++) {
22
+ const [id, depth] = queue[index], stopped = session.stopGap(id);
23
+ if (stopped) {
24
+ missing.push(stopped);
25
+ break;
26
+ }
27
+ const marked = await session.read('attention', id, validation_js_1.attention);
28
+ if (marked.gap)
29
+ missing.push(marked.gap);
30
+ if (marked.value?.length) {
31
+ marks.push(Object.freeze({ nodeId: id, attention: marked.value }));
32
+ if (marked.value.some((item) => item.kind === 'not-collected'))
33
+ missing.push((0, validation_js_1.gap)('not-collected', id));
34
+ }
35
+ const coverage = await session.read('gaps', id, validation_js_1.gaps);
36
+ if (coverage.gap)
37
+ missing.push(coverage.gap);
38
+ if (coverage.value)
39
+ missing.push(...coverage.value);
40
+ const page = await session.read(direction, id, (raw) => (0, validation_js_1.adjacency)(raw, session.limits.maxEdges));
41
+ if (page.gap) {
42
+ missing.push(page.gap);
43
+ continue;
44
+ }
45
+ if (!page.value)
46
+ continue;
47
+ if (!page.value.complete)
48
+ missing.push((0, validation_js_1.gap)('not-collected', id));
49
+ if (depth >= session.limits.maxDepth) {
50
+ if (page.value.edges.length)
51
+ missing.push((0, validation_js_1.gap)('depth-limit', id));
52
+ continue;
53
+ }
54
+ for (const edge of page.value.edges) {
55
+ if (session.edges >= session.limits.maxEdges) {
56
+ missing.push((0, validation_js_1.gap)('edge-limit', id));
57
+ break;
58
+ }
59
+ session.edges++;
60
+ const refusal = session.admit(edge.nodeId);
61
+ if (refusal) {
62
+ missing.push(refusal);
63
+ continue;
64
+ }
65
+ edges.push(Object.freeze({
66
+ from: id,
67
+ to: edge.nodeId,
68
+ kind: edge.kind,
69
+ attribution: edge.attribution,
70
+ evidence: edge.evidence,
71
+ }));
72
+ if (edge.attribution === 'stage')
73
+ missing.push((0, validation_js_1.gap)('conservative', id));
74
+ enqueue(edge.nodeId, depth + 1);
75
+ }
76
+ }
77
+ if (hasCycle(edges))
78
+ missing.push((0, validation_js_1.gap)('cycle'));
79
+ return Object.freeze({
80
+ nodes: Object.freeze(queue.map((row) => row[0])),
81
+ marked: Object.freeze(marks),
82
+ edges: Object.freeze(edges),
83
+ gaps: (0, validation_js_1.uniqueGaps)(missing),
84
+ });
85
+ }
86
+ exports.walk = walk;
87
+ /** Iterative three-colour walk; shared nodes remain distinguishable from back edges. */
88
+ function hasCycle(edges) {
89
+ const next = new Map();
90
+ for (const edge of edges) {
91
+ if (!next.has(edge.from))
92
+ next.set(edge.from, []);
93
+ next.get(edge.from).push(edge.to);
94
+ }
95
+ const state = new Map();
96
+ for (const seed of next.keys()) {
97
+ if (state.has(seed))
98
+ continue;
99
+ const stack = [[seed, 0]];
100
+ state.set(seed, 1);
101
+ while (stack.length) {
102
+ const frame = stack[stack.length - 1], children = next.get(frame[0]) ?? [];
103
+ if (frame[1] >= children.length) {
104
+ state.set(frame[0], 2);
105
+ stack.pop();
106
+ continue;
107
+ }
108
+ const child = children[frame[1]++];
109
+ if (state.get(child) === 1)
110
+ return true;
111
+ if (!state.has(child)) {
112
+ state.set(child, 1);
113
+ stack.push([child, 0]);
114
+ }
115
+ }
116
+ }
117
+ return false;
118
+ }
119
+ function frontier(result) {
120
+ const marked = new Set(result.marked
121
+ .filter((item) => item.attention.some((mark) => mark.kind !== 'not-collected'))
122
+ .map((item) => item.nodeId));
123
+ const pending = new Map(result.nodes.map((id) => [id, 0]));
124
+ const dependents = new Map();
125
+ for (const edge of result.edges) {
126
+ pending.set(edge.from, (pending.get(edge.from) ?? 0) + 1);
127
+ if (!dependents.has(edge.to))
128
+ dependents.set(edge.to, []);
129
+ dependents.get(edge.to).push(edge.from);
130
+ }
131
+ // Process causal ancestors first, propagating one boolean through the DAG.
132
+ // This is O(nodes + edges), rather than a fresh ancestry scan per marked node.
133
+ const queue = [...pending].filter(([, count]) => count === 0).map(([id]) => id);
134
+ const earlierMark = new Set(), eligible = new Set();
135
+ for (let i = 0; i < queue.length; i++) {
136
+ const id = queue[i];
137
+ if (marked.has(id) && !earlierMark.has(id))
138
+ eligible.add(id);
139
+ for (const dependent of dependents.get(id) ?? []) {
140
+ if (marked.has(id) || earlierMark.has(id))
141
+ earlierMark.add(dependent);
142
+ const count = pending.get(dependent) - 1;
143
+ pending.set(dependent, count);
144
+ if (count === 0)
145
+ queue.push(dependent);
146
+ }
147
+ }
148
+ // Nodes left in a cycle cannot supply a validated earliest ancestor.
149
+ return result.marked.filter((item) => eligible.has(item.nodeId));
150
+ }
151
+ exports.frontier = frontier;
152
+ function pathTo(edges, start, target) {
153
+ const queue = [start], previous = new Map([[start, undefined]]);
154
+ const next = new Map();
155
+ for (const edge of edges) {
156
+ if (!next.has(edge.from))
157
+ next.set(edge.from, []);
158
+ next.get(edge.from).push(edge.to);
159
+ }
160
+ for (let i = 0; i < queue.length; i++) {
161
+ const id = queue[i];
162
+ if (id === target)
163
+ break;
164
+ for (const child of next.get(id) ?? [])
165
+ if (!previous.has(child)) {
166
+ previous.set(child, id);
167
+ queue.push(child);
168
+ }
169
+ }
170
+ if (!previous.has(target))
171
+ return Object.freeze([]);
172
+ const path = [];
173
+ let id = target;
174
+ while (id !== undefined) {
175
+ path.push(id);
176
+ id = previous.get(id);
177
+ }
178
+ return Object.freeze(path.reverse());
179
+ }
180
+ exports.pathTo = pathTo;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * attemptId — the address of one attempt under a stage execution.
4
+ *
5
+ * Format:
6
+ * <runtimeStageId>@<n> attempt n of the stage execution 'charge#4@2'
7
+ * <runtimeStageId>@<n>.<k>@<m> attempt m of the k-th call made 'charge#4@2.1@3'
8
+ * during stage attempt n
9
+ *
10
+ * Every number after the runtimeStageId is a positive integer, counted from 1: attempt 1 is the first
11
+ * try, call 1 the first call the stage attempt made. Written without leading zeros, so one attempt has
12
+ * one spelling and an id is a safe join key.
13
+ *
14
+ * PARSED FROM THE RIGHT. A runtimeStageId always ends in `#<execution index>`, and the suffix grammar
15
+ * holds no `#`, so the LAST `#` of an attempt id is its execution delimiter whatever the stage id
16
+ * contains — `@`, `.` and `~` stay legal in stage ids, and no byte is newly reserved (the id doors'
17
+ * refusal, FootPrint's `refuseReservedId`, is untouched). The delimiter is read from the grammar's one
18
+ * owner, `runtimeStageId.ts`.
19
+ *
20
+ * Imports nothing but the runtime-ID grammar: a leaf of the record, checked by the package fence.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * buildAttemptId('charge#4', 2); // 'charge#4@2'
25
+ * buildAttemptId('charge#4', 2, { ordinal: 1, attempt: 3 }); // 'charge#4@2.1@3'
26
+ * parseAttemptId('sf/v1.2@x#7@3'); // { runtimeStageId: 'sf/v1.2@x#7', attempt: 3 }
27
+ * parseAttemptId('charge#4'); // undefined — a stage execution, not an attempt
28
+ * ```
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.parseAttemptId = exports.buildAttemptId = void 0;
32
+ const runtimeStageId_js_1 = require("./runtimeStageId.js");
33
+ /**
34
+ * What follows the last `#`: the execution index, `@` and the attempt, then — for a call — `.`, the
35
+ * call's ordinal, `@` and the call's attempt. Counts are canonical positive integers.
36
+ */
37
+ const AFTER_EXECUTION_DELIMITER = /^(\d+)@([1-9]\d*)(?:\.([1-9]\d*)@([1-9]\d*))?$/;
38
+ const EXECUTION_INDEX = /^\d+$/;
39
+ /** True when `id` ends in `#<digits>` — the one shape every runtimeStageId has. */
40
+ function endsInExecutionIndex(id) {
41
+ const at = id.lastIndexOf(runtimeStageId_js_1.EXECUTION_DELIMITER);
42
+ return at !== -1 && EXECUTION_INDEX.test(id.slice(at + 1));
43
+ }
44
+ /** Refuse a count the parser could not give back: a positive safe integer, or a `RangeError`. */
45
+ function requireCount(name, value) {
46
+ if (!Number.isSafeInteger(value) || value < 1) {
47
+ throw new RangeError(`buildAttemptId: ${name} must be a positive integer (counted from 1), got ${String(value)}`);
48
+ }
49
+ }
50
+ /**
51
+ * Build the id of one attempt.
52
+ *
53
+ * @param runtimeStageId The stage execution the attempt belongs to (`[subflowPath/]stageId#index`).
54
+ * @param attempt The stage attempt, from 1.
55
+ * @param call For an attempt of a call made inside the stage attempt: the call's `ordinal` (1 for the
56
+ * first call that stage attempt made) and its own `attempt` (from 1).
57
+ * @throws TypeError when `runtimeStageId` does not end in `#<execution index>`, and RangeError when a
58
+ * count is not a positive integer — what `parseAttemptId` could not give back is never written.
59
+ */
60
+ function buildAttemptId(runtimeStageId, attempt, call) {
61
+ if (typeof runtimeStageId !== 'string' || !endsInExecutionIndex(runtimeStageId)) {
62
+ throw new TypeError(`buildAttemptId: ${JSON.stringify(runtimeStageId)} is not a runtimeStageId (it must end in #<execution index>)`);
63
+ }
64
+ requireCount('attempt', attempt);
65
+ if (call === undefined)
66
+ return `${runtimeStageId}@${attempt}`;
67
+ requireCount('call ordinal', call.ordinal);
68
+ requireCount('call attempt', call.attempt);
69
+ return `${runtimeStageId}@${attempt}.${call.ordinal}@${call.attempt}`;
70
+ }
71
+ exports.buildAttemptId = buildAttemptId;
72
+ /**
73
+ * Parse an attempt id into its parts, or `undefined` when `id` is not one (a bare runtimeStageId, a
74
+ * count of 0, a leading zero, a missing part). Never throws.
75
+ *
76
+ * `parseAttemptId(buildAttemptId(...parts))` gives the parts back, whatever the stage id contains.
77
+ */
78
+ function parseAttemptId(id) {
79
+ if (typeof id !== 'string')
80
+ return undefined;
81
+ const at = id.lastIndexOf(runtimeStageId_js_1.EXECUTION_DELIMITER);
82
+ if (at === -1)
83
+ return undefined;
84
+ const parts = AFTER_EXECUTION_DELIMITER.exec(id.slice(at + 1));
85
+ if (!parts)
86
+ return undefined;
87
+ const [, index, attempt, ordinal, callAttempt] = parts;
88
+ const counts = [attempt, ordinal, callAttempt].filter((n) => n !== undefined).map(Number);
89
+ if (!counts.every(Number.isSafeInteger))
90
+ return undefined;
91
+ const runtimeStageId = id.slice(0, at + 1 + index.length);
92
+ if (ordinal === undefined)
93
+ return { runtimeStageId, attempt: counts[0] };
94
+ return { runtimeStageId, attempt: counts[0], call: { ordinal: counts[1], attempt: counts[2] } };
95
+ }
96
+ exports.parseAttemptId = parseAttemptId;