()
| 554 | // --- Compaction (reduces JSONL file size by rewriting only current state) --- |
| 555 | |
| 556 | compact() { |
| 557 | // Round-7 (§20.5): same per-PID tmp rationale as _persistState. |
| 558 | // Two concurrent compact() calls (daemon + ad-hoc CLI) racing on |
| 559 | // the same `${messagesFile}.tmp` lose the loser's compacted log. |
| 560 | const tmpFile = `${this._messagesFile}.${process.pid}.tmp`; |
| 561 | const entries = []; |
| 562 | for (const [, msg] of this._messages) { |
| 563 | entries.push(msg); |
| 564 | } |
| 565 | entries.sort((a, b) => a.created_at - b.created_at); |
| 566 | |
| 567 | const lines = entries.map((msg) => jsonlLineForWrite(msg)); |
| 568 | |
| 569 | const fd = fs.openSync(tmpFile, 'w', PRIVATE_FILE_MODE); |
| 570 | try { |
| 571 | for (const line of lines) { |
| 572 | fs.writeSync(fd, line); |
| 573 | } |
| 574 | } finally { |
| 575 | fs.closeSync(fd); |
| 576 | } |
| 577 | bestEffortChmod(tmpFile, PRIVATE_FILE_MODE); |
| 578 | // Windows: renameSync throws EPERM when the destination already exists. |
| 579 | // Remove it first so the swap succeeds on all platforms. |
| 580 | if (process.platform === 'win32') { |
| 581 | try { fs.unlinkSync(this._messagesFile); } catch (e) { |
| 582 | if (e.code !== 'ENOENT') throw e; |
| 583 | } |
| 584 | } |
| 585 | fs.renameSync(tmpFile, this._messagesFile); |
| 586 | bestEffortChmod(this._messagesFile, PRIVATE_FILE_MODE); |
| 587 | this._rebuildIndex(); |
| 588 | } |
| 589 | |
| 590 | close() { |
| 591 | // no-op for JSONL (no file handles to close), but kept for API compatibility |
no test coverage detected