@fuzdev/fuz_util 0.42.0 → 0.44.0

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.
Files changed (59) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +19 -12
  3. package/dist/async.d.ts +2 -2
  4. package/dist/async.d.ts.map +1 -1
  5. package/dist/async.js +2 -2
  6. package/dist/benchmark.d.ts +179 -0
  7. package/dist/benchmark.d.ts.map +1 -0
  8. package/dist/benchmark.js +400 -0
  9. package/dist/benchmark_baseline.d.ts +195 -0
  10. package/dist/benchmark_baseline.d.ts.map +1 -0
  11. package/dist/benchmark_baseline.js +388 -0
  12. package/dist/benchmark_format.d.ts +87 -0
  13. package/dist/benchmark_format.d.ts.map +1 -0
  14. package/dist/benchmark_format.js +266 -0
  15. package/dist/benchmark_stats.d.ts +112 -0
  16. package/dist/benchmark_stats.d.ts.map +1 -0
  17. package/dist/benchmark_stats.js +219 -0
  18. package/dist/benchmark_types.d.ts +174 -0
  19. package/dist/benchmark_types.d.ts.map +1 -0
  20. package/dist/benchmark_types.js +1 -0
  21. package/dist/git.d.ts +12 -0
  22. package/dist/git.d.ts.map +1 -1
  23. package/dist/git.js +14 -0
  24. package/dist/library_json.d.ts +3 -3
  25. package/dist/library_json.d.ts.map +1 -1
  26. package/dist/library_json.js +1 -1
  27. package/dist/maths.d.ts +4 -0
  28. package/dist/maths.d.ts.map +1 -1
  29. package/dist/maths.js +8 -0
  30. package/dist/object.js +1 -1
  31. package/dist/source_json.d.ts +4 -4
  32. package/dist/stats.d.ts +180 -0
  33. package/dist/stats.d.ts.map +1 -0
  34. package/dist/stats.js +402 -0
  35. package/dist/string.d.ts +13 -0
  36. package/dist/string.d.ts.map +1 -1
  37. package/dist/string.js +58 -0
  38. package/dist/time.d.ts +165 -0
  39. package/dist/time.d.ts.map +1 -0
  40. package/dist/time.js +264 -0
  41. package/dist/timings.d.ts +1 -7
  42. package/dist/timings.d.ts.map +1 -1
  43. package/dist/timings.js +16 -16
  44. package/package.json +21 -19
  45. package/src/lib/async.ts +3 -3
  46. package/src/lib/benchmark.ts +498 -0
  47. package/src/lib/benchmark_baseline.ts +538 -0
  48. package/src/lib/benchmark_format.ts +314 -0
  49. package/src/lib/benchmark_stats.ts +311 -0
  50. package/src/lib/benchmark_types.ts +197 -0
  51. package/src/lib/git.ts +24 -0
  52. package/src/lib/library_json.ts +3 -3
  53. package/src/lib/maths.ts +8 -0
  54. package/src/lib/object.ts +1 -1
  55. package/src/lib/stats.ts +534 -0
  56. package/src/lib/string.ts +66 -0
  57. package/src/lib/time.ts +319 -0
  58. package/src/lib/timings.ts +17 -17
  59. package/src/lib/types.ts +2 -2
@@ -0,0 +1,197 @@
1
+ import type {BenchmarkStats} from './benchmark_stats.js';
2
+ import type {Timer} from './time.js';
3
+
4
+ /**
5
+ * Configuration options for a benchmark suite.
6
+ */
7
+ export interface BenchmarkConfig {
8
+ /**
9
+ * Target duration to run each benchmark task in milliseconds.
10
+ * The benchmark will run until this duration is reached or max_iterations is hit.
11
+ * Default: 1000ms
12
+ */
13
+ duration_ms?: number;
14
+
15
+ /**
16
+ * Number of warmup iterations before actual measurements.
17
+ * Warmup helps stabilize JIT compilation and caches.
18
+ * Default: 5
19
+ */
20
+ warmup_iterations?: number;
21
+
22
+ /**
23
+ * Cooldown time between tasks in milliseconds.
24
+ * Helps prevent interference between benchmarks.
25
+ * Default: 100ms
26
+ */
27
+ cooldown_ms?: number;
28
+
29
+ /**
30
+ * Minimum number of iterations to run.
31
+ * Default: 10
32
+ */
33
+ min_iterations?: number;
34
+
35
+ /**
36
+ * Maximum number of iterations to run.
37
+ * Prevents infinite loops if function is extremely fast.
38
+ * Default: 100000
39
+ */
40
+ max_iterations?: number;
41
+
42
+ /**
43
+ * Custom timer to use for measurements.
44
+ * Default: timer_default (auto-detects environment)
45
+ */
46
+ timer?: Timer;
47
+
48
+ /**
49
+ * Callback invoked after each iteration completes.
50
+ * Useful for triggering garbage collection, logging progress, early termination,
51
+ * or custom instrumentation.
52
+ *
53
+ * **Note**: The callback time is NOT included in iteration measurements - it runs
54
+ * after the timing capture. However, frequent GC calls will slow overall benchmark
55
+ * execution time.
56
+ *
57
+ * @param task_name - Name of the current task being benchmarked
58
+ * @param iteration - Current iteration number (1-indexed)
59
+ * @param abort - Call to stop the benchmark early for this task
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * // Trigger GC between iterations (run node with --expose-gc)
64
+ * new Benchmark({
65
+ * on_iteration: () => {
66
+ * if (globalThis.gc) globalThis.gc();
67
+ * }
68
+ * })
69
+ *
70
+ * // Log progress for long-running benchmarks
71
+ * new Benchmark({
72
+ * on_iteration: (name, iteration) => {
73
+ * if (iteration % 1000 === 0) {
74
+ * console.log(`${name}: ${iteration} iterations`);
75
+ * }
76
+ * }
77
+ * })
78
+ *
79
+ * // Stop early when converged
80
+ * new Benchmark({
81
+ * on_iteration: (name, iteration, abort) => {
82
+ * if (iteration > 1000 && has_stabilized()) abort();
83
+ * }
84
+ * })
85
+ * ```
86
+ */
87
+ on_iteration?: (task_name: string, iteration: number, abort: () => void) => void;
88
+
89
+ /**
90
+ * Callback invoked after each task completes.
91
+ * Useful for logging progress during long benchmark runs.
92
+ *
93
+ * @param result - The completed benchmark result
94
+ * @param index - Zero-based index of the completed task
95
+ * @param total - Total number of tasks to run
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * new Benchmark({
100
+ * on_task_complete: (result, index, total) => {
101
+ * console.log(`[${index + 1}/${total}] ${result.name}: ${result.stats.ops_per_second.toFixed(0)} ops/sec`);
102
+ * }
103
+ * })
104
+ * ```
105
+ */
106
+ on_task_complete?: (result: BenchmarkResult, index: number, total: number) => void;
107
+ }
108
+
109
+ /**
110
+ * A benchmark task to execute.
111
+ */
112
+ export interface BenchmarkTask {
113
+ /** Name of the task (for display) */
114
+ name: string;
115
+
116
+ /** Function to benchmark (sync or async). Return values are ignored. */
117
+ fn: () => unknown;
118
+
119
+ /**
120
+ * Optional setup function run before benchmarking this task.
121
+ * Not included in timing measurements.
122
+ */
123
+ setup?: () => void | Promise<void>;
124
+
125
+ /**
126
+ * Optional teardown function run after benchmarking this task.
127
+ * Not included in timing measurements.
128
+ */
129
+ teardown?: () => void | Promise<void>;
130
+
131
+ /**
132
+ * If true, skip this task during benchmark runs.
133
+ * Useful for temporarily disabling tasks during development.
134
+ */
135
+ skip?: boolean;
136
+
137
+ /**
138
+ * If true, run only this task (and other tasks marked `only`).
139
+ * Useful for focusing on specific tasks during development.
140
+ */
141
+ only?: boolean;
142
+
143
+ /**
144
+ * Hint for whether the function is sync or async.
145
+ * If not provided, automatically detected during warmup.
146
+ * Setting this explicitly skips per-iteration promise checking for sync functions.
147
+ */
148
+ async?: boolean;
149
+ }
150
+
151
+ /**
152
+ * Result from running a single benchmark task.
153
+ */
154
+ export interface BenchmarkResult {
155
+ /** Task name */
156
+ name: string;
157
+
158
+ /** Statistical analysis of the benchmark */
159
+ stats: BenchmarkStats;
160
+
161
+ /** Number of iterations executed */
162
+ iterations: number;
163
+
164
+ /** Total time spent benchmarking (including warmup) in milliseconds */
165
+ total_time_ms: number;
166
+
167
+ /**
168
+ * Raw timing data for each iteration in nanoseconds.
169
+ * Useful for custom statistical analysis, histogram generation,
170
+ * or exporting to external tools.
171
+ */
172
+ timings_ns: Array<number>;
173
+ }
174
+
175
+ /**
176
+ * Options for table formatting.
177
+ */
178
+ export interface BenchmarkFormatTableOptions {
179
+ /**
180
+ * Group results by category using filter functions.
181
+ */
182
+ groups?: Array<BenchmarkGroup>;
183
+ }
184
+
185
+ /**
186
+ * A group definition for organizing benchmark results.
187
+ */
188
+ export interface BenchmarkGroup {
189
+ /** Display name for the group */
190
+ name: string;
191
+
192
+ /** Optional description shown below the group name */
193
+ description?: string;
194
+
195
+ /** Filter function to determine which results belong to this group */
196
+ filter: (result: BenchmarkResult) => boolean;
197
+ }
package/src/lib/git.ts CHANGED
@@ -6,6 +6,30 @@ import type {Flavored} from './types.js';
6
6
  import {to_file_path} from './path.js';
7
7
  import {fs_exists} from './fs.js';
8
8
 
9
+ /**
10
+ * Basic git repository info.
11
+ */
12
+ export interface GitInfo {
13
+ commit: string | null;
14
+ branch: string | null;
15
+ }
16
+
17
+ /**
18
+ * Get basic git info (commit hash and branch name) without throwing.
19
+ * Returns null values if git commands fail (e.g., not in a git repo).
20
+ */
21
+ export const git_info_get = async (options?: SpawnOptions): Promise<GitInfo> => {
22
+ const [commit_result, branch_result] = await Promise.all([
23
+ spawn_out('git', ['rev-parse', 'HEAD'], options).catch(() => ({stdout: null})),
24
+ spawn_out('git', ['rev-parse', '--abbrev-ref', 'HEAD'], options).catch(() => ({stdout: null})),
25
+ ]);
26
+
27
+ return {
28
+ commit: commit_result.stdout?.trim() || null,
29
+ branch: branch_result.stdout?.trim() || null,
30
+ };
31
+ };
32
+
9
33
  export const GitOrigin = z.string();
10
34
  export type GitOrigin = Flavored<string, 'GitOrigin'>;
11
35
 
@@ -13,11 +13,11 @@ import type {Url} from './url.js';
13
13
  export interface LibraryJson {
14
14
  package_json: PackageJson;
15
15
  source_json: SourceJson;
16
- /** Package name, e.g. `@ryanatkn/fuz`. */
16
+ /** Package name, e.g. `@fuzdev/fuz_ui`. */
17
17
  name: string;
18
18
  /** Name without scope, e.g. `fuz`. */
19
19
  repo_name: string;
20
- /** GitHub repo URL, e.g. `https://github.com/ryanatkn/fuz`. */
20
+ /** GitHub repo URL, e.g. `https://github.com/fuzdev/fuz_ui`. */
21
21
  repo_url: Url;
22
22
  /** GitHub user/org, e.g. `ryanatkn`. */
23
23
  owner_name: string | null;
@@ -95,7 +95,7 @@ export const library_json_parse = (
95
95
  };
96
96
 
97
97
  /**
98
- * Extracts repo name from a package name, e.g. `@ryanatkn/fuz` → `fuz`.
98
+ * Extracts repo name from a package name, e.g. `@fuzdev/fuz_ui` → `fuz`.
99
99
  */
100
100
  export const library_repo_name_parse = (name: string): string => {
101
101
  if (name[0] === '@') {
package/src/lib/maths.ts CHANGED
@@ -89,3 +89,11 @@ export const GR_9 = 76.01315561749645;
89
89
  * golden ratio/mean constants, `1/(GR**9)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
90
90
  */
91
91
  export const GR_9i = 0.013155617496424835;
92
+
93
+ /**
94
+ * Format a number with fixed decimal places and thousands separators.
95
+ */
96
+ export const format_number = (n: number, decimals: number = 2): string => {
97
+ if (!isFinite(n)) return String(n);
98
+ return n.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
99
+ };
package/src/lib/object.ts CHANGED
@@ -85,7 +85,7 @@ export const reorder = <T extends Record<K, any>, K extends string | number>(
85
85
  /**
86
86
  * Frozen empty object with no properties, good for options default values.
87
87
  */
88
- export const EMPTY_OBJECT: Record<string | number | symbol, undefined> & object = Object.freeze({}); // eslint-disable-line @typescript-eslint/no-redundant-type-constituents
88
+ export const EMPTY_OBJECT: Record<string | number | symbol, undefined> & object = Object.freeze({});
89
89
 
90
90
  /**
91
91
  * Performs a depth-first traversal of an object's enumerable properties,