@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 +21 -0
- package/README.md +134 -0
- package/dist/index.cjs +121 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +115 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
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
|
+
[](https://github.com/philiprehberger/ts-timer/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@philiprehberger/timer)
|
|
5
|
+
[](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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|