(maxChanges: number = DEFAULT_MAX_CHANGES)
| 82 | * @param maxChanges - Maximum number of changes to retain (default: 10000) |
| 83 | */ |
| 84 | export function createStateTimeline(maxChanges: number = DEFAULT_MAX_CHANGES): StateTimeline { |
| 85 | const changes: StateChange[] = []; |
| 86 | let head = 0; |
| 87 | |
| 88 | const cap = normalizeMaxChanges(maxChanges); |
| 89 | |
| 90 | function recordChange( |
| 91 | frameId: bigint, |
| 92 | timestamp: number, |
| 93 | field: string, |
| 94 | before: unknown, |
| 95 | after: unknown, |
| 96 | ): void { |
| 97 | const change: StateChange = { |
| 98 | frameId, |
| 99 | timestamp, |
| 100 | field, |
| 101 | before, |
| 102 | after, |
| 103 | }; |
| 104 | |
| 105 | changes.push(change); |
| 106 | |
| 107 | // Evict oldest changes if over capacity |
| 108 | while (changes.length - head > cap) { |
| 109 | head++; |
| 110 | } |
| 111 | |
| 112 | // Avoid unbounded growth from a moving head index. |
| 113 | if (head > 0 && head * 2 >= changes.length) { |
| 114 | changes.splice(0, head); |
| 115 | head = 0; |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | function getChanges(since?: bigint): readonly StateChange[] { |
| 120 | if (since === undefined) { |
| 121 | return changes.slice(head); |
| 122 | } |
| 123 | |
| 124 | const result: StateChange[] = []; |
| 125 | for (let i = head; i < changes.length; i++) { |
| 126 | const c = changes[i]; |
| 127 | if (c !== undefined && c.frameId > since) result.push(c); |
| 128 | } |
| 129 | return result; |
| 130 | } |
| 131 | |
| 132 | function getFrameChanges(frameId: bigint): readonly StateChange[] { |
| 133 | const result: StateChange[] = []; |
| 134 | for (let i = head; i < changes.length; i++) { |
| 135 | const c = changes[i]; |
| 136 | if (c !== undefined && c.frameId === frameId) result.push(c); |
| 137 | } |
| 138 | return result; |
| 139 | } |
| 140 | |
| 141 | function clear(): void { |
no test coverage detected