@philiprehberger/timer 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 philiprehberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # @philiprehberger/timer
2
+
3
+ [![CI](https://github.com/philiprehberger/ts-timer/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/ts-timer/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/@philiprehberger/timer.svg)](https://www.npmjs.com/package/@philiprehberger/timer)
5
+ [![License](https://img.shields.io/github/license/philiprehberger/ts-timer)](LICENSE)
6
+
7
+ Precise timing utilities — measure, benchmark, countdown
8
+
9
+ ## Requirements
10
+
11
+ - Node.js >= 18
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @philiprehberger/timer
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Timer
22
+
23
+ ```ts
24
+ import { timer } from '@philiprehberger/timer';
25
+
26
+ const t = timer();
27
+
28
+ // Do some work...
29
+ t.lap();
30
+ // Do more work...
31
+ t.lap();
32
+
33
+ console.log(t.format()); // "245ms"
34
+ console.log(t.laps()); // [{ index: 1, split: 120, elapsed: 120 }, ...]
35
+
36
+ const total = t.stop();
37
+ ```
38
+
39
+ ### Measure
40
+
41
+ ```ts
42
+ import { measure, measureAsync } from '@philiprehberger/timer';
43
+
44
+ const { result, duration } = measure(() => heavyComputation());
45
+ console.log(`Result: ${result}, took ${duration}ms`);
46
+
47
+ const { result: data, duration: ms } = await measureAsync(() => fetch('/api'));
48
+ console.log(`Fetched in ${ms}ms`);
49
+ ```
50
+
51
+ ### Benchmark
52
+
53
+ ```ts
54
+ import { benchmark } from '@philiprehberger/timer';
55
+
56
+ const stats = benchmark(() => JSON.parse('{"a":1}'), {
57
+ iterations: 5000,
58
+ warmup: 500,
59
+ });
60
+
61
+ console.log(`Mean: ${stats.mean.toFixed(4)}ms`);
62
+ console.log(`Median: ${stats.median.toFixed(4)}ms`);
63
+ console.log(`P95: ${stats.p95.toFixed(4)}ms`);
64
+ console.log(`P99: ${stats.p99.toFixed(4)}ms`);
65
+ console.log(`Min: ${stats.min.toFixed(4)}ms`);
66
+ console.log(`Max: ${stats.max.toFixed(4)}ms`);
67
+ console.log(`Ops/sec: ${stats.ops.toFixed(0)}`);
68
+ ```
69
+
70
+ ### Format Duration
71
+
72
+ ```ts
73
+ import { formatDuration } from '@philiprehberger/timer';
74
+
75
+ formatDuration(0); // "0ms"
76
+ formatDuration(1500); // "1s 500ms"
77
+ formatDuration(3_661_500); // "1h 1m 1s 500ms"
78
+ ```
79
+
80
+ ## API
81
+
82
+ ### `timer(): Timer`
83
+
84
+ Creates a high-resolution timer.
85
+
86
+ - `.elapsed(): number` — Current elapsed time in milliseconds.
87
+ - `.stop(): number` — Stops the timer; returns elapsed time.
88
+ - `.reset(): void` — Resets and restarts the timer.
89
+ - `.lap(): Lap` — Records a lap; returns `{ index, split, elapsed }`.
90
+ - `.laps(): Lap[]` — Returns all recorded laps.
91
+ - `.format(): string` — Formats the elapsed time as a human-readable string.
92
+
93
+ ### `measure<T>(fn: () => T): MeasureResult<T>`
94
+
95
+ Measures execution time of a synchronous function. Returns `{ result, duration }`.
96
+
97
+ ### `measureAsync<T>(fn: () => Promise<T>): Promise<MeasureResult<T>>`
98
+
99
+ Measures execution time of an asynchronous function. Returns `{ result, duration }`.
100
+
101
+ ### `benchmark(fn: () => void, options?: BenchmarkOptions): BenchmarkResult`
102
+
103
+ Benchmarks a function over many iterations.
104
+
105
+ **Options:**
106
+ - `iterations` — Number of measured iterations (default: `1000`).
107
+ - `warmup` — Number of warmup iterations (default: `100`).
108
+
109
+ **Result:**
110
+ - `mean` — Arithmetic mean in ms.
111
+ - `median` — Median in ms.
112
+ - `p95` — 95th percentile in ms.
113
+ - `p99` — 99th percentile in ms.
114
+ - `min` — Minimum duration in ms.
115
+ - `max` — Maximum duration in ms.
116
+ - `ops` — Operations per second.
117
+ - `iterations` — Number of iterations run.
118
+
119
+ ### `formatDuration(ms: number): string`
120
+
121
+ Formats a millisecond value into a human-readable string (e.g. `"1h 2m 3s 45ms"`).
122
+
123
+ ## Development
124
+
125
+ ```bash
126
+ npm install
127
+ npm run build
128
+ npm test
129
+ npm run typecheck
130
+ ```
131
+
132
+ ## License
133
+
134
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,121 @@
1
+ 'use strict';
2
+
3
+ // src/timer.ts
4
+ function formatDuration(ms) {
5
+ const parts = decompose(ms);
6
+ const segments = [];
7
+ if (parts.hours > 0) segments.push(`${parts.hours}h`);
8
+ if (parts.minutes > 0) segments.push(`${parts.minutes}m`);
9
+ if (parts.seconds > 0) segments.push(`${parts.seconds}s`);
10
+ if (parts.milliseconds > 0 || segments.length === 0) {
11
+ segments.push(`${parts.milliseconds}ms`);
12
+ }
13
+ return segments.join(" ");
14
+ }
15
+ function decompose(ms) {
16
+ const totalMs = Math.max(0, Math.round(ms));
17
+ const hours = Math.floor(totalMs / 36e5);
18
+ const minutes = Math.floor(totalMs % 36e5 / 6e4);
19
+ const seconds = Math.floor(totalMs % 6e4 / 1e3);
20
+ const milliseconds = totalMs % 1e3;
21
+ return { hours, minutes, seconds, milliseconds };
22
+ }
23
+ function timer() {
24
+ let startTime = performance.now();
25
+ let stopTime = null;
26
+ const lapRecords = [];
27
+ let lastLapTime = startTime;
28
+ function elapsed() {
29
+ if (stopTime !== null) {
30
+ return stopTime - startTime;
31
+ }
32
+ return performance.now() - startTime;
33
+ }
34
+ function stop() {
35
+ if (stopTime === null) {
36
+ stopTime = performance.now();
37
+ }
38
+ return stopTime - startTime;
39
+ }
40
+ function reset() {
41
+ startTime = performance.now();
42
+ stopTime = null;
43
+ lapRecords.length = 0;
44
+ lastLapTime = startTime;
45
+ }
46
+ function lap() {
47
+ const now = stopTime !== null ? stopTime : performance.now();
48
+ const split = now - lastLapTime;
49
+ const totalElapsed = now - startTime;
50
+ const record = {
51
+ index: lapRecords.length + 1,
52
+ split,
53
+ elapsed: totalElapsed
54
+ };
55
+ lapRecords.push(record);
56
+ lastLapTime = now;
57
+ return record;
58
+ }
59
+ function laps() {
60
+ return [...lapRecords];
61
+ }
62
+ function format() {
63
+ return formatDuration(elapsed());
64
+ }
65
+ return { elapsed, stop, reset, lap, laps, format };
66
+ }
67
+
68
+ // src/measure.ts
69
+ function measure(fn) {
70
+ const start = performance.now();
71
+ const result = fn();
72
+ const duration = performance.now() - start;
73
+ return { result, duration };
74
+ }
75
+ async function measureAsync(fn) {
76
+ const start = performance.now();
77
+ const result = await fn();
78
+ const duration = performance.now() - start;
79
+ return { result, duration };
80
+ }
81
+
82
+ // src/benchmark.ts
83
+ function percentile(sorted, p) {
84
+ if (sorted.length === 0) return 0;
85
+ const index = p / 100 * (sorted.length - 1);
86
+ const lower = Math.floor(index);
87
+ const upper = Math.ceil(index);
88
+ if (lower === upper) return sorted[lower];
89
+ const weight = index - lower;
90
+ return sorted[lower] * (1 - weight) + sorted[upper] * weight;
91
+ }
92
+ function benchmark(fn, options = {}) {
93
+ const { iterations = 1e3, warmup = 100 } = options;
94
+ for (let i = 0; i < warmup; i++) {
95
+ fn();
96
+ }
97
+ const durations = new Array(iterations);
98
+ for (let i = 0; i < iterations; i++) {
99
+ const start = performance.now();
100
+ fn();
101
+ durations[i] = performance.now() - start;
102
+ }
103
+ durations.sort((a, b) => a - b);
104
+ const sum = durations.reduce((acc, d) => acc + d, 0);
105
+ const mean = sum / iterations;
106
+ const median = percentile(durations, 50);
107
+ const p95 = percentile(durations, 95);
108
+ const p99 = percentile(durations, 99);
109
+ const min = durations[0];
110
+ const max = durations[durations.length - 1];
111
+ const ops = mean > 0 ? 1e3 / mean : Infinity;
112
+ return { mean, median, p95, p99, min, max, ops, iterations };
113
+ }
114
+
115
+ exports.benchmark = benchmark;
116
+ exports.formatDuration = formatDuration;
117
+ exports.measure = measure;
118
+ exports.measureAsync = measureAsync;
119
+ exports.timer = timer;
120
+ //# sourceMappingURL=index.cjs.map
121
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/timer.ts","../src/measure.ts","../src/benchmark.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Timer, Lap, FormattedDuration } from './types.js';\n\n/**\n * Formats a millisecond duration into a human-readable string.\n *\n * @param ms - Duration in milliseconds.\n * @returns A formatted string like \"1h 2m 3s 45ms\" or \"0ms\".\n */\nexport function formatDuration(ms: number): string {\n const parts = decompose(ms);\n const segments: string[] = [];\n\n if (parts.hours > 0) segments.push(`${parts.hours}h`);\n if (parts.minutes > 0) segments.push(`${parts.minutes}m`);\n if (parts.seconds > 0) segments.push(`${parts.seconds}s`);\n if (parts.milliseconds > 0 || segments.length === 0) {\n segments.push(`${parts.milliseconds}ms`);\n }\n\n return segments.join(' ');\n}\n\n/**\n * Decomposes milliseconds into hours, minutes, seconds, and remaining milliseconds.\n */\nfunction decompose(ms: number): FormattedDuration {\n const totalMs = Math.max(0, Math.round(ms));\n const hours = Math.floor(totalMs / 3_600_000);\n const minutes = Math.floor((totalMs % 3_600_000) / 60_000);\n const seconds = Math.floor((totalMs % 60_000) / 1_000);\n const milliseconds = totalMs % 1_000;\n\n return { hours, minutes, seconds, milliseconds };\n}\n\n/**\n * Creates a high-resolution timer.\n *\n * @returns A `Timer` object with `elapsed`, `stop`, `reset`, `lap`, `laps`, and `format` methods.\n *\n * @example\n * ```ts\n * const t = timer();\n * // ... do work ...\n * console.log(t.format()); // \"123ms\"\n * t.lap();\n * // ... more work ...\n * t.lap();\n * console.log(t.laps());\n * const total = t.stop();\n * ```\n */\nexport function timer(): Timer {\n let startTime = performance.now();\n let stopTime: number | null = null;\n const lapRecords: Lap[] = [];\n let lastLapTime = startTime;\n\n function elapsed(): number {\n if (stopTime !== null) {\n return stopTime - startTime;\n }\n return performance.now() - startTime;\n }\n\n function stop(): number {\n if (stopTime === null) {\n stopTime = performance.now();\n }\n return stopTime - startTime;\n }\n\n function reset(): void {\n startTime = performance.now();\n stopTime = null;\n lapRecords.length = 0;\n lastLapTime = startTime;\n }\n\n function lap(): Lap {\n const now = stopTime !== null ? stopTime : performance.now();\n const split = now - lastLapTime;\n const totalElapsed = now - startTime;\n const record: Lap = {\n index: lapRecords.length + 1,\n split,\n elapsed: totalElapsed,\n };\n lapRecords.push(record);\n lastLapTime = now;\n return record;\n }\n\n function laps(): Lap[] {\n return [...lapRecords];\n }\n\n function format(): string {\n return formatDuration(elapsed());\n }\n\n return { elapsed, stop, reset, lap, laps, format };\n}\n","import type { MeasureResult } from './types.js';\n\n/**\n * Measures the execution time of a synchronous function.\n *\n * @param fn - The function to measure.\n * @returns An object containing the function's return value and the duration in milliseconds.\n *\n * @example\n * ```ts\n * const { result, duration } = measure(() => heavyComputation());\n * console.log(`Took ${duration}ms`);\n * ```\n */\nexport function measure<T>(fn: () => T): MeasureResult<T> {\n const start = performance.now();\n const result = fn();\n const duration = performance.now() - start;\n return { result, duration };\n}\n\n/**\n * Measures the execution time of an asynchronous function.\n *\n * @param fn - The async function to measure.\n * @returns A promise resolving to an object with the return value and duration in milliseconds.\n *\n * @example\n * ```ts\n * const { result, duration } = await measureAsync(() => fetch('/api/data'));\n * console.log(`Took ${duration}ms`);\n * ```\n */\nexport async function measureAsync<T>(fn: () => Promise<T>): Promise<MeasureResult<T>> {\n const start = performance.now();\n const result = await fn();\n const duration = performance.now() - start;\n return { result, duration };\n}\n","import type { BenchmarkOptions, BenchmarkResult } from './types.js';\n\n/**\n * Computes a percentile value from a **sorted** array of numbers.\n */\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const index = (p / 100) * (sorted.length - 1);\n const lower = Math.floor(index);\n const upper = Math.ceil(index);\n if (lower === upper) return sorted[lower];\n const weight = index - lower;\n return sorted[lower] * (1 - weight) + sorted[upper] * weight;\n}\n\n/**\n * Benchmarks a synchronous function over many iterations.\n *\n * @param fn - The function to benchmark.\n * @param options - Configuration for iterations and warmup.\n * @returns Statistics including mean, median, p95, p99, min, max, and ops/sec.\n *\n * @example\n * ```ts\n * const stats = benchmark(() => JSON.parse('{\"a\":1}'));\n * console.log(`Mean: ${stats.mean.toFixed(4)}ms, Ops: ${stats.ops.toFixed(0)}/sec`);\n * ```\n */\nexport function benchmark(\n fn: () => void,\n options: BenchmarkOptions = {},\n): BenchmarkResult {\n const { iterations = 1000, warmup = 100 } = options;\n\n // Warmup phase\n for (let i = 0; i < warmup; i++) {\n fn();\n }\n\n // Measurement phase\n const durations: number[] = new Array(iterations);\n for (let i = 0; i < iterations; i++) {\n const start = performance.now();\n fn();\n durations[i] = performance.now() - start;\n }\n\n durations.sort((a, b) => a - b);\n\n const sum = durations.reduce((acc, d) => acc + d, 0);\n const mean = sum / iterations;\n const median = percentile(durations, 50);\n const p95 = percentile(durations, 95);\n const p99 = percentile(durations, 99);\n const min = durations[0];\n const max = durations[durations.length - 1];\n const ops = mean > 0 ? 1000 / mean : Infinity;\n\n return { mean, median, p95, p99, min, max, ops, iterations };\n}\n"]}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Represents a duration broken down into units.
3
+ */
4
+ interface FormattedDuration {
5
+ hours: number;
6
+ minutes: number;
7
+ seconds: number;
8
+ milliseconds: number;
9
+ }
10
+ /**
11
+ * A lap record captured by the timer.
12
+ */
13
+ interface Lap {
14
+ /** Lap index (1-based). */
15
+ index: number;
16
+ /** Time since the previous lap (or start), in milliseconds. */
17
+ split: number;
18
+ /** Total elapsed time at the moment the lap was recorded, in milliseconds. */
19
+ elapsed: number;
20
+ }
21
+ /**
22
+ * The object returned by `timer()`.
23
+ */
24
+ interface Timer {
25
+ /** Returns the elapsed time in milliseconds. */
26
+ elapsed(): number;
27
+ /** Stops the timer and returns the elapsed time in milliseconds. */
28
+ stop(): number;
29
+ /** Resets the timer to zero and starts it again. */
30
+ reset(): void;
31
+ /** Records a lap and returns the lap data. */
32
+ lap(): Lap;
33
+ /** Returns all recorded laps. */
34
+ laps(): Lap[];
35
+ /** Formats the current elapsed time into a human-readable string. */
36
+ format(): string;
37
+ }
38
+ /**
39
+ * Options for the `benchmark` function.
40
+ */
41
+ interface BenchmarkOptions {
42
+ /** Number of measured iterations (default: 1000). */
43
+ iterations?: number;
44
+ /** Number of warmup iterations before measurement (default: 100). */
45
+ warmup?: number;
46
+ }
47
+ /**
48
+ * Result of a benchmark run.
49
+ */
50
+ interface BenchmarkResult {
51
+ /** Arithmetic mean in milliseconds. */
52
+ mean: number;
53
+ /** Median in milliseconds. */
54
+ median: number;
55
+ /** 95th percentile in milliseconds. */
56
+ p95: number;
57
+ /** 99th percentile in milliseconds. */
58
+ p99: number;
59
+ /** Minimum duration in milliseconds. */
60
+ min: number;
61
+ /** Maximum duration in milliseconds. */
62
+ max: number;
63
+ /** Operations per second based on mean. */
64
+ ops: number;
65
+ /** Total number of measured iterations. */
66
+ iterations: number;
67
+ }
68
+ /**
69
+ * Result of `measure` or `measureAsync`.
70
+ */
71
+ interface MeasureResult<T> {
72
+ /** The return value of the measured function. */
73
+ result: T;
74
+ /** Duration in milliseconds. */
75
+ duration: number;
76
+ }
77
+
78
+ /**
79
+ * Formats a millisecond duration into a human-readable string.
80
+ *
81
+ * @param ms - Duration in milliseconds.
82
+ * @returns A formatted string like "1h 2m 3s 45ms" or "0ms".
83
+ */
84
+ declare function formatDuration(ms: number): string;
85
+ /**
86
+ * Creates a high-resolution timer.
87
+ *
88
+ * @returns A `Timer` object with `elapsed`, `stop`, `reset`, `lap`, `laps`, and `format` methods.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const t = timer();
93
+ * // ... do work ...
94
+ * console.log(t.format()); // "123ms"
95
+ * t.lap();
96
+ * // ... more work ...
97
+ * t.lap();
98
+ * console.log(t.laps());
99
+ * const total = t.stop();
100
+ * ```
101
+ */
102
+ declare function timer(): Timer;
103
+
104
+ /**
105
+ * Measures the execution time of a synchronous function.
106
+ *
107
+ * @param fn - The function to measure.
108
+ * @returns An object containing the function's return value and the duration in milliseconds.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const { result, duration } = measure(() => heavyComputation());
113
+ * console.log(`Took ${duration}ms`);
114
+ * ```
115
+ */
116
+ declare function measure<T>(fn: () => T): MeasureResult<T>;
117
+ /**
118
+ * Measures the execution time of an asynchronous function.
119
+ *
120
+ * @param fn - The async function to measure.
121
+ * @returns A promise resolving to an object with the return value and duration in milliseconds.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const { result, duration } = await measureAsync(() => fetch('/api/data'));
126
+ * console.log(`Took ${duration}ms`);
127
+ * ```
128
+ */
129
+ declare function measureAsync<T>(fn: () => Promise<T>): Promise<MeasureResult<T>>;
130
+
131
+ /**
132
+ * Benchmarks a synchronous function over many iterations.
133
+ *
134
+ * @param fn - The function to benchmark.
135
+ * @param options - Configuration for iterations and warmup.
136
+ * @returns Statistics including mean, median, p95, p99, min, max, and ops/sec.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const stats = benchmark(() => JSON.parse('{"a":1}'));
141
+ * console.log(`Mean: ${stats.mean.toFixed(4)}ms, Ops: ${stats.ops.toFixed(0)}/sec`);
142
+ * ```
143
+ */
144
+ declare function benchmark(fn: () => void, options?: BenchmarkOptions): BenchmarkResult;
145
+
146
+ export { type BenchmarkOptions, type BenchmarkResult, type FormattedDuration, type Lap, type MeasureResult, type Timer, benchmark, formatDuration, measure, measureAsync, timer };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Represents a duration broken down into units.
3
+ */
4
+ interface FormattedDuration {
5
+ hours: number;
6
+ minutes: number;
7
+ seconds: number;
8
+ milliseconds: number;
9
+ }
10
+ /**
11
+ * A lap record captured by the timer.
12
+ */
13
+ interface Lap {
14
+ /** Lap index (1-based). */
15
+ index: number;
16
+ /** Time since the previous lap (or start), in milliseconds. */
17
+ split: number;
18
+ /** Total elapsed time at the moment the lap was recorded, in milliseconds. */
19
+ elapsed: number;
20
+ }
21
+ /**
22
+ * The object returned by `timer()`.
23
+ */
24
+ interface Timer {
25
+ /** Returns the elapsed time in milliseconds. */
26
+ elapsed(): number;
27
+ /** Stops the timer and returns the elapsed time in milliseconds. */
28
+ stop(): number;
29
+ /** Resets the timer to zero and starts it again. */
30
+ reset(): void;
31
+ /** Records a lap and returns the lap data. */
32
+ lap(): Lap;
33
+ /** Returns all recorded laps. */
34
+ laps(): Lap[];
35
+ /** Formats the current elapsed time into a human-readable string. */
36
+ format(): string;
37
+ }
38
+ /**
39
+ * Options for the `benchmark` function.
40
+ */
41
+ interface BenchmarkOptions {
42
+ /** Number of measured iterations (default: 1000). */
43
+ iterations?: number;
44
+ /** Number of warmup iterations before measurement (default: 100). */
45
+ warmup?: number;
46
+ }
47
+ /**
48
+ * Result of a benchmark run.
49
+ */
50
+ interface BenchmarkResult {
51
+ /** Arithmetic mean in milliseconds. */
52
+ mean: number;
53
+ /** Median in milliseconds. */
54
+ median: number;
55
+ /** 95th percentile in milliseconds. */
56
+ p95: number;
57
+ /** 99th percentile in milliseconds. */
58
+ p99: number;
59
+ /** Minimum duration in milliseconds. */
60
+ min: number;
61
+ /** Maximum duration in milliseconds. */
62
+ max: number;
63
+ /** Operations per second based on mean. */
64
+ ops: number;
65
+ /** Total number of measured iterations. */
66
+ iterations: number;
67
+ }
68
+ /**
69
+ * Result of `measure` or `measureAsync`.
70
+ */
71
+ interface MeasureResult<T> {
72
+ /** The return value of the measured function. */
73
+ result: T;
74
+ /** Duration in milliseconds. */
75
+ duration: number;
76
+ }
77
+
78
+ /**
79
+ * Formats a millisecond duration into a human-readable string.
80
+ *
81
+ * @param ms - Duration in milliseconds.
82
+ * @returns A formatted string like "1h 2m 3s 45ms" or "0ms".
83
+ */
84
+ declare function formatDuration(ms: number): string;
85
+ /**
86
+ * Creates a high-resolution timer.
87
+ *
88
+ * @returns A `Timer` object with `elapsed`, `stop`, `reset`, `lap`, `laps`, and `format` methods.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const t = timer();
93
+ * // ... do work ...
94
+ * console.log(t.format()); // "123ms"
95
+ * t.lap();
96
+ * // ... more work ...
97
+ * t.lap();
98
+ * console.log(t.laps());
99
+ * const total = t.stop();
100
+ * ```
101
+ */
102
+ declare function timer(): Timer;
103
+
104
+ /**
105
+ * Measures the execution time of a synchronous function.
106
+ *
107
+ * @param fn - The function to measure.
108
+ * @returns An object containing the function's return value and the duration in milliseconds.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const { result, duration } = measure(() => heavyComputation());
113
+ * console.log(`Took ${duration}ms`);
114
+ * ```
115
+ */
116
+ declare function measure<T>(fn: () => T): MeasureResult<T>;
117
+ /**
118
+ * Measures the execution time of an asynchronous function.
119
+ *
120
+ * @param fn - The async function to measure.
121
+ * @returns A promise resolving to an object with the return value and duration in milliseconds.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const { result, duration } = await measureAsync(() => fetch('/api/data'));
126
+ * console.log(`Took ${duration}ms`);
127
+ * ```
128
+ */
129
+ declare function measureAsync<T>(fn: () => Promise<T>): Promise<MeasureResult<T>>;
130
+
131
+ /**
132
+ * Benchmarks a synchronous function over many iterations.
133
+ *
134
+ * @param fn - The function to benchmark.
135
+ * @param options - Configuration for iterations and warmup.
136
+ * @returns Statistics including mean, median, p95, p99, min, max, and ops/sec.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const stats = benchmark(() => JSON.parse('{"a":1}'));
141
+ * console.log(`Mean: ${stats.mean.toFixed(4)}ms, Ops: ${stats.ops.toFixed(0)}/sec`);
142
+ * ```
143
+ */
144
+ declare function benchmark(fn: () => void, options?: BenchmarkOptions): BenchmarkResult;
145
+
146
+ export { type BenchmarkOptions, type BenchmarkResult, type FormattedDuration, type Lap, type MeasureResult, type Timer, benchmark, formatDuration, measure, measureAsync, timer };
package/dist/index.js ADDED
@@ -0,0 +1,115 @@
1
+ // src/timer.ts
2
+ function formatDuration(ms) {
3
+ const parts = decompose(ms);
4
+ const segments = [];
5
+ if (parts.hours > 0) segments.push(`${parts.hours}h`);
6
+ if (parts.minutes > 0) segments.push(`${parts.minutes}m`);
7
+ if (parts.seconds > 0) segments.push(`${parts.seconds}s`);
8
+ if (parts.milliseconds > 0 || segments.length === 0) {
9
+ segments.push(`${parts.milliseconds}ms`);
10
+ }
11
+ return segments.join(" ");
12
+ }
13
+ function decompose(ms) {
14
+ const totalMs = Math.max(0, Math.round(ms));
15
+ const hours = Math.floor(totalMs / 36e5);
16
+ const minutes = Math.floor(totalMs % 36e5 / 6e4);
17
+ const seconds = Math.floor(totalMs % 6e4 / 1e3);
18
+ const milliseconds = totalMs % 1e3;
19
+ return { hours, minutes, seconds, milliseconds };
20
+ }
21
+ function timer() {
22
+ let startTime = performance.now();
23
+ let stopTime = null;
24
+ const lapRecords = [];
25
+ let lastLapTime = startTime;
26
+ function elapsed() {
27
+ if (stopTime !== null) {
28
+ return stopTime - startTime;
29
+ }
30
+ return performance.now() - startTime;
31
+ }
32
+ function stop() {
33
+ if (stopTime === null) {
34
+ stopTime = performance.now();
35
+ }
36
+ return stopTime - startTime;
37
+ }
38
+ function reset() {
39
+ startTime = performance.now();
40
+ stopTime = null;
41
+ lapRecords.length = 0;
42
+ lastLapTime = startTime;
43
+ }
44
+ function lap() {
45
+ const now = stopTime !== null ? stopTime : performance.now();
46
+ const split = now - lastLapTime;
47
+ const totalElapsed = now - startTime;
48
+ const record = {
49
+ index: lapRecords.length + 1,
50
+ split,
51
+ elapsed: totalElapsed
52
+ };
53
+ lapRecords.push(record);
54
+ lastLapTime = now;
55
+ return record;
56
+ }
57
+ function laps() {
58
+ return [...lapRecords];
59
+ }
60
+ function format() {
61
+ return formatDuration(elapsed());
62
+ }
63
+ return { elapsed, stop, reset, lap, laps, format };
64
+ }
65
+
66
+ // src/measure.ts
67
+ function measure(fn) {
68
+ const start = performance.now();
69
+ const result = fn();
70
+ const duration = performance.now() - start;
71
+ return { result, duration };
72
+ }
73
+ async function measureAsync(fn) {
74
+ const start = performance.now();
75
+ const result = await fn();
76
+ const duration = performance.now() - start;
77
+ return { result, duration };
78
+ }
79
+
80
+ // src/benchmark.ts
81
+ function percentile(sorted, p) {
82
+ if (sorted.length === 0) return 0;
83
+ const index = p / 100 * (sorted.length - 1);
84
+ const lower = Math.floor(index);
85
+ const upper = Math.ceil(index);
86
+ if (lower === upper) return sorted[lower];
87
+ const weight = index - lower;
88
+ return sorted[lower] * (1 - weight) + sorted[upper] * weight;
89
+ }
90
+ function benchmark(fn, options = {}) {
91
+ const { iterations = 1e3, warmup = 100 } = options;
92
+ for (let i = 0; i < warmup; i++) {
93
+ fn();
94
+ }
95
+ const durations = new Array(iterations);
96
+ for (let i = 0; i < iterations; i++) {
97
+ const start = performance.now();
98
+ fn();
99
+ durations[i] = performance.now() - start;
100
+ }
101
+ durations.sort((a, b) => a - b);
102
+ const sum = durations.reduce((acc, d) => acc + d, 0);
103
+ const mean = sum / iterations;
104
+ const median = percentile(durations, 50);
105
+ const p95 = percentile(durations, 95);
106
+ const p99 = percentile(durations, 99);
107
+ const min = durations[0];
108
+ const max = durations[durations.length - 1];
109
+ const ops = mean > 0 ? 1e3 / mean : Infinity;
110
+ return { mean, median, p95, p99, min, max, ops, iterations };
111
+ }
112
+
113
+ export { benchmark, formatDuration, measure, measureAsync, timer };
114
+ //# sourceMappingURL=index.js.map
115
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/timer.ts","../src/measure.ts","../src/benchmark.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Timer, Lap, FormattedDuration } from './types.js';\n\n/**\n * Formats a millisecond duration into a human-readable string.\n *\n * @param ms - Duration in milliseconds.\n * @returns A formatted string like \"1h 2m 3s 45ms\" or \"0ms\".\n */\nexport function formatDuration(ms: number): string {\n const parts = decompose(ms);\n const segments: string[] = [];\n\n if (parts.hours > 0) segments.push(`${parts.hours}h`);\n if (parts.minutes > 0) segments.push(`${parts.minutes}m`);\n if (parts.seconds > 0) segments.push(`${parts.seconds}s`);\n if (parts.milliseconds > 0 || segments.length === 0) {\n segments.push(`${parts.milliseconds}ms`);\n }\n\n return segments.join(' ');\n}\n\n/**\n * Decomposes milliseconds into hours, minutes, seconds, and remaining milliseconds.\n */\nfunction decompose(ms: number): FormattedDuration {\n const totalMs = Math.max(0, Math.round(ms));\n const hours = Math.floor(totalMs / 3_600_000);\n const minutes = Math.floor((totalMs % 3_600_000) / 60_000);\n const seconds = Math.floor((totalMs % 60_000) / 1_000);\n const milliseconds = totalMs % 1_000;\n\n return { hours, minutes, seconds, milliseconds };\n}\n\n/**\n * Creates a high-resolution timer.\n *\n * @returns A `Timer` object with `elapsed`, `stop`, `reset`, `lap`, `laps`, and `format` methods.\n *\n * @example\n * ```ts\n * const t = timer();\n * // ... do work ...\n * console.log(t.format()); // \"123ms\"\n * t.lap();\n * // ... more work ...\n * t.lap();\n * console.log(t.laps());\n * const total = t.stop();\n * ```\n */\nexport function timer(): Timer {\n let startTime = performance.now();\n let stopTime: number | null = null;\n const lapRecords: Lap[] = [];\n let lastLapTime = startTime;\n\n function elapsed(): number {\n if (stopTime !== null) {\n return stopTime - startTime;\n }\n return performance.now() - startTime;\n }\n\n function stop(): number {\n if (stopTime === null) {\n stopTime = performance.now();\n }\n return stopTime - startTime;\n }\n\n function reset(): void {\n startTime = performance.now();\n stopTime = null;\n lapRecords.length = 0;\n lastLapTime = startTime;\n }\n\n function lap(): Lap {\n const now = stopTime !== null ? stopTime : performance.now();\n const split = now - lastLapTime;\n const totalElapsed = now - startTime;\n const record: Lap = {\n index: lapRecords.length + 1,\n split,\n elapsed: totalElapsed,\n };\n lapRecords.push(record);\n lastLapTime = now;\n return record;\n }\n\n function laps(): Lap[] {\n return [...lapRecords];\n }\n\n function format(): string {\n return formatDuration(elapsed());\n }\n\n return { elapsed, stop, reset, lap, laps, format };\n}\n","import type { MeasureResult } from './types.js';\n\n/**\n * Measures the execution time of a synchronous function.\n *\n * @param fn - The function to measure.\n * @returns An object containing the function's return value and the duration in milliseconds.\n *\n * @example\n * ```ts\n * const { result, duration } = measure(() => heavyComputation());\n * console.log(`Took ${duration}ms`);\n * ```\n */\nexport function measure<T>(fn: () => T): MeasureResult<T> {\n const start = performance.now();\n const result = fn();\n const duration = performance.now() - start;\n return { result, duration };\n}\n\n/**\n * Measures the execution time of an asynchronous function.\n *\n * @param fn - The async function to measure.\n * @returns A promise resolving to an object with the return value and duration in milliseconds.\n *\n * @example\n * ```ts\n * const { result, duration } = await measureAsync(() => fetch('/api/data'));\n * console.log(`Took ${duration}ms`);\n * ```\n */\nexport async function measureAsync<T>(fn: () => Promise<T>): Promise<MeasureResult<T>> {\n const start = performance.now();\n const result = await fn();\n const duration = performance.now() - start;\n return { result, duration };\n}\n","import type { BenchmarkOptions, BenchmarkResult } from './types.js';\n\n/**\n * Computes a percentile value from a **sorted** array of numbers.\n */\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const index = (p / 100) * (sorted.length - 1);\n const lower = Math.floor(index);\n const upper = Math.ceil(index);\n if (lower === upper) return sorted[lower];\n const weight = index - lower;\n return sorted[lower] * (1 - weight) + sorted[upper] * weight;\n}\n\n/**\n * Benchmarks a synchronous function over many iterations.\n *\n * @param fn - The function to benchmark.\n * @param options - Configuration for iterations and warmup.\n * @returns Statistics including mean, median, p95, p99, min, max, and ops/sec.\n *\n * @example\n * ```ts\n * const stats = benchmark(() => JSON.parse('{\"a\":1}'));\n * console.log(`Mean: ${stats.mean.toFixed(4)}ms, Ops: ${stats.ops.toFixed(0)}/sec`);\n * ```\n */\nexport function benchmark(\n fn: () => void,\n options: BenchmarkOptions = {},\n): BenchmarkResult {\n const { iterations = 1000, warmup = 100 } = options;\n\n // Warmup phase\n for (let i = 0; i < warmup; i++) {\n fn();\n }\n\n // Measurement phase\n const durations: number[] = new Array(iterations);\n for (let i = 0; i < iterations; i++) {\n const start = performance.now();\n fn();\n durations[i] = performance.now() - start;\n }\n\n durations.sort((a, b) => a - b);\n\n const sum = durations.reduce((acc, d) => acc + d, 0);\n const mean = sum / iterations;\n const median = percentile(durations, 50);\n const p95 = percentile(durations, 95);\n const p99 = percentile(durations, 99);\n const min = durations[0];\n const max = durations[durations.length - 1];\n const ops = mean > 0 ? 1000 / mean : Infinity;\n\n return { mean, median, p95, p99, min, max, ops, iterations };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@philiprehberger/timer",
3
+ "version": "0.1.3",
4
+ "description": "Precise timing utilities — measure, benchmark, countdown",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "dev": "tsup --watch",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "npm run build",
29
+ "test": "node --test"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.0.0",
33
+ "tsup": "^8.0.0",
34
+ "typescript": "^5.0.0"
35
+ },
36
+ "keywords": [
37
+ "timer",
38
+ "benchmark",
39
+ "measure",
40
+ "performance",
41
+ "stopwatch",
42
+ "duration"
43
+ ],
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/philiprehberger/ts-timer.git"
48
+ },
49
+ "homepage": "https://github.com/philiprehberger/ts-timer#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/philiprehberger/ts-timer/issues"
52
+ },
53
+ "author": "Philip Rehberger",
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ },
57
+ "sideEffects": false
58
+ }