Skip to content

Commit 94014e7

Browse files
committed
refactor: break circular import between human.ts and trace.ts
Extract formatRelativeTime and computeSpanDurationMs into a new time-utils.ts module. Both are pure utility functions that were causing a circular dependency: trace.ts → human.ts (formatRelativeTime) human.ts → trace.ts (computeSpanDurationMs) Now both import from time-utils.ts which only depends on types/ and markdown.ts — no cycle. Update all direct importers (span/view.ts, test files) to use the new module path.
1 parent 9a5cb9a commit 94014e7

7 files changed

Lines changed: 75 additions & 59 deletions

File tree

src/commands/span/view.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from "../../lib/formatters/index.js";
2222
import { filterFields } from "../../lib/formatters/json.js";
2323
import { CommandOutput } from "../../lib/formatters/output.js";
24-
import { computeSpanDurationMs } from "../../lib/formatters/trace.js";
24+
import { computeSpanDurationMs } from "../../lib/formatters/time-utils.js";
2525
import { validateSpanId } from "../../lib/hex-id.js";
2626
import {
2727
applyFreshFlag,

src/lib/formatters/human.ts

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
} from "./markdown.js";
4040
import { sparkline } from "./sparkline.js";
4141
import { type Column, writeTable } from "./table.js";
42-
import { computeSpanDurationMs } from "./trace.js";
42+
import { computeSpanDurationMs } from "./time-utils.js";
4343

4444
// Color tag maps
4545

@@ -200,42 +200,8 @@ export function formatStatusLabel(status: string | undefined): string {
200200
);
201201
}
202202

203-
// Date Formatting
204-
205-
/**
206-
* Format a date as relative time (e.g., "2h ago", "3d ago") or short date for older dates.
207-
*
208-
* - < 1 hour: "Xm ago"
209-
* - < 24 hours: "Xh ago"
210-
* - < 3 days: "Xd ago"
211-
* - >= 3 days: Short date (e.g., "Jan 18")
212-
*/
213-
export function formatRelativeTime(dateString: string | undefined): string {
214-
if (!dateString) {
215-
return colorTag("muted", "—");
216-
}
217-
218-
const date = new Date(dateString);
219-
const now = Date.now();
220-
const diffMs = now - date.getTime();
221-
const diffMins = Math.floor(diffMs / 60_000);
222-
const diffHours = Math.floor(diffMs / 3_600_000);
223-
const diffDays = Math.floor(diffMs / 86_400_000);
224-
225-
let text: string;
226-
if (diffMins < 60) {
227-
text = `${diffMins}m ago`;
228-
} else if (diffHours < 24) {
229-
text = `${diffHours}h ago`;
230-
} else if (diffDays < 3) {
231-
text = `${diffDays}d ago`;
232-
} else {
233-
// Short date: "Jan 18"
234-
text = date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
235-
}
236-
237-
return text;
238-
}
203+
// formatRelativeTime moved to time-utils.ts to break circular import
204+
import { formatRelativeTime } from "./time-utils.js";
239205

240206
// Issue Formatting
241207

src/lib/formatters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export * from "./output.js";
1414
export * from "./seer.js";
1515
export * from "./sparkline.js";
1616
export * from "./table.js";
17+
export * from "./time-utils.js";
1718
export * from "./trace.js";

src/lib/formatters/time-utils.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Time and duration utility functions for formatters.
3+
*
4+
* Extracted to break the circular import between `human.ts` and `trace.ts`:
5+
* both modules need these utilities but neither should depend on the other.
6+
*/
7+
8+
import type { TraceSpan } from "../../types/index.js";
9+
import { colorTag } from "./markdown.js";
10+
11+
/**
12+
* Format a date string as a relative time label.
13+
*
14+
* - Under 60 minutes: "5m ago"
15+
* - Under 24 hours: "3h ago"
16+
* - Under 3 days: "2d ago"
17+
* - Otherwise: short date like "Jan 18"
18+
*
19+
* Returns a muted "—" when the input is undefined.
20+
*
21+
* @param dateString - ISO date string or undefined
22+
* @returns Human-readable relative time string
23+
*/
24+
export function formatRelativeTime(dateString: string | undefined): string {
25+
if (!dateString) {
26+
return colorTag("muted", "—");
27+
}
28+
29+
const date = new Date(dateString);
30+
const now = Date.now();
31+
const diffMs = now - date.getTime();
32+
const diffMins = Math.floor(diffMs / 60_000);
33+
const diffHours = Math.floor(diffMs / 3_600_000);
34+
const diffDays = Math.floor(diffMs / 86_400_000);
35+
36+
let text: string;
37+
if (diffMins < 60) {
38+
text = `${diffMins}m ago`;
39+
} else if (diffHours < 24) {
40+
text = `${diffHours}h ago`;
41+
} else if (diffDays < 3) {
42+
text = `${diffDays}d ago`;
43+
} else {
44+
// Short date: "Jan 18"
45+
text = date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
46+
}
47+
48+
return text;
49+
}
50+
51+
/**
52+
* Compute the duration of a span in milliseconds.
53+
* Prefers the API-provided `duration` field, falls back to timestamp arithmetic.
54+
*
55+
* @returns Duration in milliseconds, or undefined if not computable
56+
*/
57+
export function computeSpanDurationMs(span: TraceSpan): number | undefined {
58+
if (span.duration !== undefined && Number.isFinite(span.duration)) {
59+
return span.duration;
60+
}
61+
const endTs = span.end_timestamp || span.timestamp;
62+
if (endTs !== undefined && Number.isFinite(endTs)) {
63+
const ms = (endTs - span.start_timestamp) * 1000;
64+
return ms >= 0 ? ms : undefined;
65+
}
66+
return;
67+
}

src/lib/formatters/trace.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import type {
1010
TraceSpan,
1111
TransactionListItem,
1212
} from "../../types/index.js";
13-
import { formatRelativeTime } from "./human.js";
1413
import {
1514
colorTag,
1615
escapeMarkdownCell,
@@ -25,6 +24,7 @@ import {
2524
} from "./markdown.js";
2625
import { type Column, formatTable } from "./table.js";
2726
import { renderTextTable } from "./text-table.js";
27+
import { computeSpanDurationMs, formatRelativeTime } from "./time-utils.js";
2828

2929
/**
3030
* Format a duration in milliseconds to a human-readable string.
@@ -292,24 +292,6 @@ export function formatTraceSummary(summary: TraceSummary): string {
292292
// Flat span utilities (for span list / span view)
293293
// ---------------------------------------------------------------------------
294294

295-
/**
296-
* Compute the duration of a span in milliseconds.
297-
* Prefers the API-provided `duration` field, falls back to timestamp arithmetic.
298-
*
299-
* @returns Duration in milliseconds, or undefined if not computable
300-
*/
301-
export function computeSpanDurationMs(span: TraceSpan): number | undefined {
302-
if (span.duration !== undefined && Number.isFinite(span.duration)) {
303-
return span.duration;
304-
}
305-
const endTs = span.end_timestamp || span.timestamp;
306-
if (endTs !== undefined && Number.isFinite(endTs)) {
307-
const ms = (endTs - span.start_timestamp) * 1000;
308-
return ms >= 0 ? ms : undefined;
309-
}
310-
return;
311-
}
312-
313295
/** Flat span for list output — no nested children */
314296
export type FlatSpan = {
315297
span_id: string;

test/lib/formatters/human.utils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ import {
1717
import {
1818
formatDuration,
1919
formatExpiration,
20-
formatRelativeTime,
2120
formatStatusIcon,
2221
formatStatusLabel,
2322
maskToken,
2423
} from "../../../src/lib/formatters/human.js";
24+
import { formatRelativeTime } from "../../../src/lib/formatters/time-utils.js";
2525
import { DEFAULT_NUM_RUNS } from "../../model-based/helpers.js";
2626

2727
// Helper to strip ANSI codes and markdown color tags for content testing.

test/lib/formatters/trace.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
*/
1212

1313
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
14+
import { computeSpanDurationMs } from "../../../src/lib/formatters/time-utils.js";
1415
import {
15-
computeSpanDurationMs,
1616
computeTraceSummary,
1717
findSpanById,
1818
formatTraceDuration,

0 commit comments

Comments
 (0)