measure-fn 3.11.0 → 3.11.2
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/index.ts +692 -195
- package/package.json +2 -2
- package/bun.lock +0 -26
package/index.ts
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
const toAlpha = (num: number): string => {
|
|
4
4
|
let result = '';
|
|
5
5
|
let n = num;
|
|
6
|
+
|
|
6
7
|
do {
|
|
7
8
|
result = String.fromCharCode(97 + (n % 26)) + result;
|
|
8
9
|
n = Math.floor(n / 26) - 1;
|
|
9
10
|
} while (n >= 0);
|
|
11
|
+
|
|
10
12
|
return result;
|
|
11
13
|
};
|
|
12
14
|
|
|
@@ -16,29 +18,65 @@ let maxResultLen = 0;
|
|
|
16
18
|
|
|
17
19
|
export const safeStringify = (value: unknown, limit?: number): string => {
|
|
18
20
|
const cap = limit ?? maxResultLen;
|
|
21
|
+
|
|
19
22
|
if (value === undefined) return '';
|
|
20
23
|
if (value === null) return 'null';
|
|
21
|
-
|
|
22
|
-
if (typeof value === '
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
26
|
+
return String(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof value === 'bigint') {
|
|
30
|
+
return `${value}n`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof value === 'function') {
|
|
34
|
+
return `[Function: ${value.name || 'anonymous'}]`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof value === 'symbol') {
|
|
38
|
+
return value.toString();
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
if (typeof value === 'string') {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
const quoted = JSON.stringify(value);
|
|
43
|
+
|
|
44
|
+
if (cap === 0) {
|
|
45
|
+
return quoted;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return quoted.length > cap ? quoted.slice(0, Math.max(0, cap - 1)) + '…"' : quoted;
|
|
28
49
|
}
|
|
50
|
+
|
|
29
51
|
try {
|
|
30
|
-
const seen = new WeakSet();
|
|
52
|
+
const seen = new WeakSet<object>();
|
|
53
|
+
|
|
31
54
|
const str = JSON.stringify(value, (_key, val) => {
|
|
55
|
+
if (typeof val === 'bigint') {
|
|
56
|
+
return `${val}n`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof val === 'function') {
|
|
60
|
+
return `[Function: ${val.name || 'anonymous'}]`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof val === 'symbol') {
|
|
64
|
+
return val.toString();
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
if (typeof val === 'object' && val !== null) {
|
|
33
68
|
if (seen.has(val)) return '[Circular]';
|
|
34
69
|
seen.add(val);
|
|
35
70
|
}
|
|
36
|
-
|
|
37
|
-
if (typeof val === 'bigint') return `${val}n`;
|
|
71
|
+
|
|
38
72
|
return val;
|
|
39
73
|
});
|
|
40
|
-
|
|
41
|
-
|
|
74
|
+
|
|
75
|
+
if (cap === 0) {
|
|
76
|
+
return str;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return str.length > cap ? str.slice(0, Math.max(0, cap)) + '…' : str;
|
|
42
80
|
} catch {
|
|
43
81
|
return String(value);
|
|
44
82
|
}
|
|
@@ -46,26 +84,32 @@ export const safeStringify = (value: unknown, limit?: number): string => {
|
|
|
46
84
|
|
|
47
85
|
// ─── Duration Formatting ─────────────────────────────────────────────
|
|
48
86
|
|
|
49
|
-
const formatDuration = (ms: number): string => {
|
|
87
|
+
export const formatDuration = (ms: number): string => {
|
|
50
88
|
if (ms < 1000) return `${ms.toFixed(2)}ms`;
|
|
51
89
|
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
90
|
+
|
|
52
91
|
const mins = Math.floor(ms / 60000);
|
|
53
92
|
const secs = Math.round((ms % 60000) / 1000);
|
|
93
|
+
|
|
54
94
|
return `${mins}m ${secs}s`;
|
|
55
95
|
};
|
|
56
96
|
|
|
57
97
|
// ─── Timestamps ──────────────────────────────────────────────────────
|
|
58
98
|
|
|
59
99
|
let timestamps =
|
|
60
|
-
typeof process !== 'undefined' &&
|
|
100
|
+
typeof process !== 'undefined' &&
|
|
101
|
+
(process.env.MEASURE_TIMESTAMPS === '1' ||
|
|
102
|
+
process.env.MEASURE_TIMESTAMPS === 'true');
|
|
61
103
|
|
|
62
104
|
const ts = (): string => {
|
|
63
105
|
if (!timestamps) return '';
|
|
106
|
+
|
|
64
107
|
const now = new Date();
|
|
65
108
|
const h = String(now.getHours()).padStart(2, '0');
|
|
66
109
|
const m = String(now.getMinutes()).padStart(2, '0');
|
|
67
110
|
const s = String(now.getSeconds()).padStart(2, '0');
|
|
68
111
|
const ms = String(now.getMilliseconds()).padStart(3, '0');
|
|
112
|
+
|
|
69
113
|
return `[${h}:${m}:${s}.${ms}] `;
|
|
70
114
|
};
|
|
71
115
|
|
|
@@ -84,10 +128,24 @@ export type MeasureEvent = {
|
|
|
84
128
|
maxResultLength?: number;
|
|
85
129
|
};
|
|
86
130
|
|
|
131
|
+
export type MeasureAction<T = unknown> =
|
|
132
|
+
| string
|
|
133
|
+
| {
|
|
134
|
+
label: string;
|
|
135
|
+
budget?: number;
|
|
136
|
+
timeout?: number;
|
|
137
|
+
maxResultLength?: number;
|
|
138
|
+
result?: (value: T) => unknown;
|
|
139
|
+
meta?: Record<string, unknown>;
|
|
140
|
+
[key: string]: unknown;
|
|
141
|
+
};
|
|
142
|
+
|
|
87
143
|
// ─── Configuration ───────────────────────────────────────────────────
|
|
88
144
|
|
|
89
145
|
export let silent =
|
|
90
|
-
typeof process !== 'undefined' &&
|
|
146
|
+
typeof process !== 'undefined' &&
|
|
147
|
+
(process.env.MEASURE_SILENT === '1' ||
|
|
148
|
+
process.env.MEASURE_SILENT === 'true');
|
|
91
149
|
|
|
92
150
|
let dotEndLabel = true;
|
|
93
151
|
let dotChar = '·';
|
|
@@ -114,54 +172,116 @@ export const configure = (opts: ConfigureOpts) => {
|
|
|
114
172
|
|
|
115
173
|
// ─── Shared Helpers ──────────────────────────────────────────────────
|
|
116
174
|
|
|
117
|
-
const
|
|
118
|
-
return typeof
|
|
119
|
-
? String(actionInternal.label)
|
|
120
|
-
: String(actionInternal);
|
|
175
|
+
const isActionObject = (value: unknown): value is Exclude<MeasureAction, string> => {
|
|
176
|
+
return typeof value === 'object' && value !== null;
|
|
121
177
|
};
|
|
122
178
|
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
179
|
+
const buildActionLabel = (actionInternal: MeasureAction): string => {
|
|
180
|
+
if (isActionObject(actionInternal) && 'label' in actionInternal) {
|
|
181
|
+
return String(actionInternal.label);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return String(actionInternal);
|
|
127
185
|
};
|
|
128
186
|
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
if ('
|
|
132
|
-
return undefined;
|
|
187
|
+
const extractBudget = (actionInternal: MeasureAction): number | undefined => {
|
|
188
|
+
if (!isActionObject(actionInternal)) return undefined;
|
|
189
|
+
if (!('budget' in actionInternal)) return undefined;
|
|
190
|
+
if (actionInternal.budget === undefined) return undefined;
|
|
191
|
+
|
|
192
|
+
return Number(actionInternal.budget);
|
|
133
193
|
};
|
|
134
194
|
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
137
|
-
if ('
|
|
138
|
-
return undefined;
|
|
195
|
+
const extractTimeout = (actionInternal: MeasureAction): number | undefined => {
|
|
196
|
+
if (!isActionObject(actionInternal)) return undefined;
|
|
197
|
+
if (!('timeout' in actionInternal)) return undefined;
|
|
198
|
+
if (actionInternal.timeout === undefined) return undefined;
|
|
199
|
+
|
|
200
|
+
return Number(actionInternal.timeout);
|
|
139
201
|
};
|
|
140
202
|
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
203
|
+
const extractMaxResultLength = (actionInternal: MeasureAction): number | undefined => {
|
|
204
|
+
if (!isActionObject(actionInternal)) return undefined;
|
|
205
|
+
if (!('maxResultLength' in actionInternal)) return undefined;
|
|
206
|
+
if (actionInternal.maxResultLength === undefined) return undefined;
|
|
207
|
+
|
|
208
|
+
return Number(actionInternal.maxResultLength);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const extractResultMapper = <T>(
|
|
212
|
+
actionInternal: MeasureAction<T>,
|
|
213
|
+
): ((value: T) => unknown) | undefined => {
|
|
214
|
+
if (!isActionObject(actionInternal)) return undefined;
|
|
215
|
+
if (typeof actionInternal.result !== 'function') return undefined;
|
|
216
|
+
|
|
217
|
+
return actionInternal.result;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const extractMeta = (actionInternal: MeasureAction): Record<string, unknown> | undefined => {
|
|
221
|
+
if (!isActionObject(actionInternal)) return undefined;
|
|
222
|
+
|
|
223
|
+
const details: Record<string, unknown> = { ...actionInternal };
|
|
224
|
+
|
|
225
|
+
delete details.label;
|
|
226
|
+
delete details.budget;
|
|
227
|
+
delete details.timeout;
|
|
228
|
+
delete details.maxResultLength;
|
|
229
|
+
delete details.result;
|
|
230
|
+
|
|
231
|
+
const explicitMeta =
|
|
232
|
+
typeof details.meta === 'object' && details.meta !== null
|
|
233
|
+
? (details.meta as Record<string, unknown>)
|
|
234
|
+
: undefined;
|
|
235
|
+
|
|
236
|
+
delete details.meta;
|
|
237
|
+
|
|
238
|
+
const merged = {
|
|
239
|
+
...details,
|
|
240
|
+
...(explicitMeta ?? {}),
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
if (Object.keys(merged).length === 0) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return merged;
|
|
149
248
|
};
|
|
150
249
|
|
|
151
250
|
const formatMeta = (meta?: Record<string, unknown>): string => {
|
|
152
251
|
if (!meta) return '';
|
|
252
|
+
|
|
153
253
|
const params = Object.entries(meta)
|
|
154
|
-
.map(([key, value]) => `${key}=${
|
|
254
|
+
.map(([key, value]) => `${key}=${safeStringify(value, 0)}`)
|
|
155
255
|
.join(' ');
|
|
256
|
+
|
|
156
257
|
return ` (${params})`;
|
|
157
258
|
};
|
|
158
259
|
|
|
260
|
+
const mapResultForLog = <T>(actionInternal: MeasureAction<T>, result: T): unknown => {
|
|
261
|
+
const mapper = extractResultMapper(actionInternal);
|
|
262
|
+
|
|
263
|
+
if (!mapper) {
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
return mapper(result);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return {
|
|
271
|
+
resultMapperError:
|
|
272
|
+
error instanceof Error ? error.message : String(error),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
159
277
|
const emit = (event: MeasureEvent, prefix?: string) => {
|
|
160
278
|
if (silent) return;
|
|
279
|
+
|
|
161
280
|
if (logger) {
|
|
162
281
|
logger(event);
|
|
163
282
|
return;
|
|
164
283
|
}
|
|
284
|
+
|
|
165
285
|
defaultLogger(event, prefix);
|
|
166
286
|
};
|
|
167
287
|
|
|
@@ -171,58 +291,114 @@ const defaultLogger = (event: MeasureEvent, prefix?: string) => {
|
|
|
171
291
|
const t = ts();
|
|
172
292
|
|
|
173
293
|
switch (event.type) {
|
|
174
|
-
case 'start':
|
|
294
|
+
case 'start': {
|
|
175
295
|
console.log(`${t}${id} ... ${event.label}${formatMeta(event.meta)}`);
|
|
176
296
|
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
177
299
|
case 'success': {
|
|
178
|
-
const endLabel = dotEndLabel
|
|
179
|
-
|
|
300
|
+
const endLabel = dotEndLabel
|
|
301
|
+
? dotChar.repeat(event.label.length + 5)
|
|
302
|
+
: ` ${event.label}`;
|
|
303
|
+
|
|
304
|
+
const resultStr =
|
|
305
|
+
event.result !== undefined
|
|
306
|
+
? safeStringify(event.result, event.maxResultLength)
|
|
307
|
+
: '';
|
|
308
|
+
|
|
180
309
|
const arrow = resultStr ? ` → ${resultStr}` : '';
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
310
|
+
|
|
311
|
+
const budgetWarn =
|
|
312
|
+
event.budget && event.duration !== undefined && event.duration > event.budget
|
|
313
|
+
? ` ⚠ OVER BUDGET (${formatDuration(event.budget)})`
|
|
314
|
+
: '';
|
|
315
|
+
|
|
316
|
+
console.log(
|
|
317
|
+
`${t}${id} ${endLabel} ${formatDuration(event.duration ?? 0)}${arrow}${budgetWarn}`,
|
|
318
|
+
);
|
|
319
|
+
|
|
185
320
|
break;
|
|
186
321
|
}
|
|
322
|
+
|
|
187
323
|
case 'error': {
|
|
188
|
-
const endLabel = dotEndLabel
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
324
|
+
const endLabel = dotEndLabel
|
|
325
|
+
? dotChar.repeat(event.label.length + 3)
|
|
326
|
+
: ` ${event.label}`;
|
|
327
|
+
|
|
328
|
+
const errorMsg =
|
|
329
|
+
event.error instanceof Error ? event.error.message : String(event.error);
|
|
330
|
+
|
|
331
|
+
const budgetWarn =
|
|
332
|
+
event.budget && event.duration !== undefined && event.duration > event.budget
|
|
333
|
+
? ` ⚠ OVER BUDGET (${formatDuration(event.budget)})`
|
|
334
|
+
: '';
|
|
335
|
+
|
|
336
|
+
console.log(
|
|
337
|
+
`${t}${id} ✗ ${endLabel} ${formatDuration(event.duration ?? 0)} (${errorMsg})${budgetWarn}`,
|
|
338
|
+
);
|
|
339
|
+
|
|
194
340
|
if (event.error instanceof Error) {
|
|
195
341
|
console.error(`${id}`, event.error.stack ?? event.error.message);
|
|
342
|
+
|
|
196
343
|
if (event.error.cause) {
|
|
197
344
|
console.error(`${id} Cause:`, event.error.cause);
|
|
198
345
|
}
|
|
199
346
|
} else {
|
|
200
347
|
console.error(`${id}`, event.error);
|
|
201
348
|
}
|
|
349
|
+
|
|
202
350
|
break;
|
|
203
351
|
}
|
|
204
|
-
|
|
352
|
+
|
|
353
|
+
case 'annotation': {
|
|
205
354
|
console.log(`${t}${id} = ${event.label}${formatMeta(event.meta)}`);
|
|
206
355
|
break;
|
|
356
|
+
}
|
|
207
357
|
}
|
|
208
358
|
};
|
|
209
359
|
|
|
210
360
|
// ─── Types ───────────────────────────────────────────────────────────
|
|
211
361
|
|
|
212
362
|
export type MeasureFn = {
|
|
213
|
-
<U>(label:
|
|
214
|
-
|
|
215
|
-
<U>(
|
|
216
|
-
|
|
363
|
+
<U>(label: MeasureAction<U>, fn: () => Promise<U>): Promise<U | null>;
|
|
364
|
+
|
|
365
|
+
<U>(
|
|
366
|
+
label: MeasureAction<U>,
|
|
367
|
+
fn: (m: MeasureFn, ms: MeasureSyncFn) => Promise<U>,
|
|
368
|
+
): Promise<U | null>;
|
|
369
|
+
|
|
370
|
+
<U>(
|
|
371
|
+
label: MeasureAction<U>,
|
|
372
|
+
fn: (m: MeasureFn) => Promise<U>,
|
|
373
|
+
): Promise<U | null>;
|
|
374
|
+
|
|
375
|
+
<U>(
|
|
376
|
+
label: MeasureAction<U>,
|
|
377
|
+
fn: () => Promise<U>,
|
|
378
|
+
onError: (error: unknown) => U | null | Promise<U | null>,
|
|
379
|
+
): Promise<U | null>;
|
|
380
|
+
|
|
381
|
+
(label: MeasureAction): Promise<null>;
|
|
217
382
|
};
|
|
218
383
|
|
|
219
384
|
export type MeasureSyncFn = {
|
|
220
|
-
<U>(label:
|
|
221
|
-
|
|
222
|
-
(label:
|
|
385
|
+
<U>(label: MeasureAction<U>, fn: () => U): U | null;
|
|
386
|
+
|
|
387
|
+
<U>(label: MeasureAction<U>, fn: (m: MeasureSyncFn) => U): U | null;
|
|
388
|
+
|
|
389
|
+
<U>(
|
|
390
|
+
label: MeasureAction<U>,
|
|
391
|
+
fn: () => U,
|
|
392
|
+
onError: (error: unknown) => U | null,
|
|
393
|
+
): U | null;
|
|
394
|
+
|
|
395
|
+
(label: MeasureAction): null;
|
|
223
396
|
};
|
|
224
397
|
|
|
225
|
-
export type TimedResult<T> = {
|
|
398
|
+
export type TimedResult<T> = {
|
|
399
|
+
result: T | null;
|
|
400
|
+
duration: number;
|
|
401
|
+
};
|
|
226
402
|
|
|
227
403
|
export type RetryOpts = {
|
|
228
404
|
attempts?: number;
|
|
@@ -241,28 +417,47 @@ const createNestedResolver = (
|
|
|
241
417
|
fullIdChain: string[],
|
|
242
418
|
childCounterRef: { value: number },
|
|
243
419
|
depth: number,
|
|
244
|
-
resolver: <U>(
|
|
420
|
+
resolver: <U>(
|
|
421
|
+
fn: any,
|
|
422
|
+
action: MeasureAction<U>,
|
|
423
|
+
chain: (string | number)[],
|
|
424
|
+
depth: number,
|
|
425
|
+
onError?: (error: unknown) => any,
|
|
426
|
+
inheritedMaxLen?: number,
|
|
427
|
+
) => Promise<U | null> | (U | null),
|
|
245
428
|
prefix?: string,
|
|
246
|
-
inheritedMaxLen?: number
|
|
429
|
+
inheritedMaxLen?: number,
|
|
247
430
|
) => {
|
|
248
431
|
return (...args: any[]) => {
|
|
249
|
-
const label = args[0];
|
|
432
|
+
const label = args[0] as MeasureAction;
|
|
250
433
|
const fn = args[1];
|
|
251
434
|
const onError = args[2];
|
|
252
435
|
|
|
253
436
|
if (typeof fn === 'function') {
|
|
254
437
|
const childParentChain = [...fullIdChain, childCounterRef.value++];
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
438
|
+
|
|
439
|
+
return resolver(
|
|
440
|
+
fn,
|
|
441
|
+
label,
|
|
442
|
+
childParentChain,
|
|
443
|
+
depth + 1,
|
|
444
|
+
typeof onError === 'function' ? onError : undefined,
|
|
445
|
+
inheritedMaxLen,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
emit(
|
|
450
|
+
{
|
|
258
451
|
type: 'annotation',
|
|
259
452
|
id: fullIdChain.join('-'),
|
|
260
453
|
label: buildActionLabel(label),
|
|
261
454
|
depth: depth + 1,
|
|
262
455
|
meta: extractMeta(label),
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
|
|
456
|
+
},
|
|
457
|
+
prefix,
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
return isAsync ? Promise.resolve(null) : null;
|
|
266
461
|
};
|
|
267
462
|
};
|
|
268
463
|
|
|
@@ -278,18 +473,32 @@ export type ScopeOpts = {
|
|
|
278
473
|
maxResultLength?: number;
|
|
279
474
|
};
|
|
280
475
|
|
|
281
|
-
const createMeasureImpl = (
|
|
282
|
-
|
|
476
|
+
const createMeasureImpl = (
|
|
477
|
+
prefix?: string,
|
|
478
|
+
counterRef?: { value: number },
|
|
479
|
+
scopeOpts?: ScopeOpts,
|
|
480
|
+
) => {
|
|
481
|
+
const counter =
|
|
482
|
+
counterRef ??
|
|
483
|
+
{
|
|
484
|
+
get value() {
|
|
485
|
+
return globalRootCounter;
|
|
486
|
+
},
|
|
487
|
+
set value(value: number) {
|
|
488
|
+
globalRootCounter = value;
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
|
|
283
492
|
const scopeMaxLen = scopeOpts?.maxResultLength;
|
|
284
493
|
let _lastError: unknown = null;
|
|
285
494
|
|
|
286
495
|
const _measureInternal = async <U>(
|
|
287
496
|
fnInternal: (measure: MeasureFn, measureSync: MeasureSyncFn) => Promise<U>,
|
|
288
|
-
actionInternal:
|
|
497
|
+
actionInternal: MeasureAction<U>,
|
|
289
498
|
parentIdChain: (string | number)[],
|
|
290
499
|
depth: number,
|
|
291
|
-
onError?: (error: unknown) =>
|
|
292
|
-
inheritedMaxLen?: number
|
|
500
|
+
onError?: (error: unknown) => U | null | Promise<U | null>,
|
|
501
|
+
inheritedMaxLen?: number,
|
|
293
502
|
): Promise<U | null> => {
|
|
294
503
|
const start = performance.now();
|
|
295
504
|
const childCounterRef = { value: 0 };
|
|
@@ -299,316 +508,604 @@ const createMeasureImpl = (prefix?: string, counterRef?: { value: number }, scop
|
|
|
299
508
|
const localMaxLen = extractMaxResultLength(actionInternal);
|
|
300
509
|
const effectiveMaxLen = localMaxLen ?? inheritedMaxLen;
|
|
301
510
|
|
|
302
|
-
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0)
|
|
303
|
-
const fullIdChain: string[] = [
|
|
511
|
+
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0));
|
|
512
|
+
const fullIdChain: string[] = [
|
|
513
|
+
...parentIdChain.map((value) => String(value)),
|
|
514
|
+
currentId,
|
|
515
|
+
];
|
|
304
516
|
const idStr = fullIdChain.join('-');
|
|
305
517
|
|
|
306
|
-
emit(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
518
|
+
emit(
|
|
519
|
+
{
|
|
520
|
+
type: 'start',
|
|
521
|
+
id: idStr,
|
|
522
|
+
label,
|
|
523
|
+
depth,
|
|
524
|
+
meta: extractMeta(actionInternal),
|
|
525
|
+
},
|
|
526
|
+
prefix,
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
const measureForNextLevel = createNestedResolver(
|
|
530
|
+
true,
|
|
531
|
+
fullIdChain,
|
|
532
|
+
childCounterRef,
|
|
310
533
|
depth,
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
534
|
+
_measureInternal,
|
|
535
|
+
prefix,
|
|
536
|
+
effectiveMaxLen,
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
const measureSyncForNextLevel = createNestedResolver(
|
|
540
|
+
false,
|
|
541
|
+
fullIdChain,
|
|
542
|
+
childCounterRef,
|
|
543
|
+
depth,
|
|
544
|
+
_measureInternalSync,
|
|
545
|
+
prefix,
|
|
546
|
+
effectiveMaxLen,
|
|
547
|
+
);
|
|
316
548
|
|
|
317
549
|
try {
|
|
318
550
|
let result: U;
|
|
551
|
+
|
|
319
552
|
if (timeout && timeout > 0) {
|
|
320
553
|
result = await Promise.race([
|
|
321
|
-
fnInternal(
|
|
322
|
-
|
|
323
|
-
|
|
554
|
+
fnInternal(
|
|
555
|
+
measureForNextLevel as MeasureFn,
|
|
556
|
+
measureSyncForNextLevel as MeasureSyncFn,
|
|
324
557
|
),
|
|
558
|
+
new Promise<never>((_resolve, reject) => {
|
|
559
|
+
setTimeout(
|
|
560
|
+
() => reject(new Error(`Timeout (${formatDuration(timeout)})`)),
|
|
561
|
+
timeout,
|
|
562
|
+
);
|
|
563
|
+
}),
|
|
325
564
|
]);
|
|
326
565
|
} else {
|
|
327
|
-
result = await fnInternal(
|
|
566
|
+
result = await fnInternal(
|
|
567
|
+
measureForNextLevel as MeasureFn,
|
|
568
|
+
measureSyncForNextLevel as MeasureSyncFn,
|
|
569
|
+
);
|
|
328
570
|
}
|
|
571
|
+
|
|
329
572
|
const duration = performance.now() - start;
|
|
330
|
-
|
|
573
|
+
|
|
574
|
+
emit(
|
|
575
|
+
{
|
|
576
|
+
type: 'success',
|
|
577
|
+
id: idStr,
|
|
578
|
+
label,
|
|
579
|
+
depth,
|
|
580
|
+
duration,
|
|
581
|
+
result: mapResultForLog(actionInternal, result),
|
|
582
|
+
budget,
|
|
583
|
+
maxResultLength: effectiveMaxLen,
|
|
584
|
+
},
|
|
585
|
+
prefix,
|
|
586
|
+
);
|
|
587
|
+
|
|
331
588
|
return result;
|
|
332
589
|
} catch (error) {
|
|
333
590
|
const duration = performance.now() - start;
|
|
334
|
-
|
|
591
|
+
|
|
592
|
+
emit(
|
|
593
|
+
{
|
|
594
|
+
type: 'error',
|
|
595
|
+
id: idStr,
|
|
596
|
+
label,
|
|
597
|
+
depth,
|
|
598
|
+
duration,
|
|
599
|
+
error,
|
|
600
|
+
budget,
|
|
601
|
+
maxResultLength: effectiveMaxLen,
|
|
602
|
+
},
|
|
603
|
+
prefix,
|
|
604
|
+
);
|
|
605
|
+
|
|
335
606
|
_lastError = error;
|
|
607
|
+
|
|
336
608
|
if (onError) {
|
|
337
609
|
try {
|
|
338
|
-
return onError(error);
|
|
610
|
+
return await onError(error);
|
|
339
611
|
} catch (onErrorError) {
|
|
340
|
-
emit(
|
|
612
|
+
emit(
|
|
613
|
+
{
|
|
614
|
+
type: 'error',
|
|
615
|
+
id: idStr,
|
|
616
|
+
label: `${label} (onError)`,
|
|
617
|
+
depth,
|
|
618
|
+
duration: performance.now() - start,
|
|
619
|
+
error: onErrorError,
|
|
620
|
+
budget,
|
|
621
|
+
maxResultLength: effectiveMaxLen,
|
|
622
|
+
},
|
|
623
|
+
prefix,
|
|
624
|
+
);
|
|
625
|
+
|
|
341
626
|
_lastError = onErrorError;
|
|
342
627
|
return null;
|
|
343
628
|
}
|
|
344
629
|
}
|
|
630
|
+
|
|
345
631
|
return null;
|
|
346
632
|
}
|
|
347
633
|
};
|
|
348
634
|
|
|
349
635
|
const _measureInternalSync = <U>(
|
|
350
636
|
fnInternal: (measure: MeasureSyncFn) => U,
|
|
351
|
-
actionInternal:
|
|
637
|
+
actionInternal: MeasureAction<U>,
|
|
352
638
|
parentIdChain: (string | number)[],
|
|
353
639
|
depth: number,
|
|
354
|
-
|
|
355
|
-
inheritedMaxLen?: number
|
|
640
|
+
onError?: (error: unknown) => U | null,
|
|
641
|
+
inheritedMaxLen?: number,
|
|
356
642
|
): U | null => {
|
|
357
643
|
const start = performance.now();
|
|
358
644
|
const childCounterRef = { value: 0 };
|
|
359
645
|
const label = buildActionLabel(actionInternal);
|
|
360
|
-
const hasNested = fnInternal.length > 0;
|
|
361
646
|
const budget = extractBudget(actionInternal);
|
|
362
647
|
const localMaxLen = extractMaxResultLength(actionInternal);
|
|
363
648
|
const effectiveMaxLen = localMaxLen ?? inheritedMaxLen;
|
|
364
649
|
|
|
365
|
-
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0)
|
|
366
|
-
const fullIdChain: string[] = [
|
|
650
|
+
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0));
|
|
651
|
+
const fullIdChain: string[] = [
|
|
652
|
+
...parentIdChain.map((value) => String(value)),
|
|
653
|
+
currentId,
|
|
654
|
+
];
|
|
367
655
|
const idStr = fullIdChain.join('-');
|
|
368
656
|
|
|
369
|
-
|
|
370
|
-
|
|
657
|
+
emit(
|
|
658
|
+
{
|
|
371
659
|
type: 'start',
|
|
372
660
|
id: idStr,
|
|
373
661
|
label,
|
|
374
662
|
depth,
|
|
375
663
|
meta: extractMeta(actionInternal),
|
|
376
|
-
},
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
664
|
+
},
|
|
665
|
+
prefix,
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
const measureForNextLevel = createNestedResolver(
|
|
669
|
+
false,
|
|
670
|
+
fullIdChain,
|
|
671
|
+
childCounterRef,
|
|
672
|
+
depth,
|
|
673
|
+
_measureInternalSync,
|
|
674
|
+
prefix,
|
|
675
|
+
effectiveMaxLen,
|
|
676
|
+
);
|
|
380
677
|
|
|
381
678
|
try {
|
|
382
679
|
const result = fnInternal(measureForNextLevel as MeasureSyncFn);
|
|
383
680
|
const duration = performance.now() - start;
|
|
384
|
-
|
|
681
|
+
|
|
682
|
+
emit(
|
|
683
|
+
{
|
|
684
|
+
type: 'success',
|
|
685
|
+
id: idStr,
|
|
686
|
+
label,
|
|
687
|
+
depth,
|
|
688
|
+
duration,
|
|
689
|
+
result: mapResultForLog(actionInternal, result),
|
|
690
|
+
budget,
|
|
691
|
+
maxResultLength: effectiveMaxLen,
|
|
692
|
+
},
|
|
693
|
+
prefix,
|
|
694
|
+
);
|
|
695
|
+
|
|
385
696
|
return result;
|
|
386
697
|
} catch (error) {
|
|
387
698
|
const duration = performance.now() - start;
|
|
388
|
-
|
|
699
|
+
|
|
700
|
+
emit(
|
|
701
|
+
{
|
|
702
|
+
type: 'error',
|
|
703
|
+
id: idStr,
|
|
704
|
+
label,
|
|
705
|
+
depth,
|
|
706
|
+
duration,
|
|
707
|
+
error,
|
|
708
|
+
budget,
|
|
709
|
+
maxResultLength: effectiveMaxLen,
|
|
710
|
+
},
|
|
711
|
+
prefix,
|
|
712
|
+
);
|
|
713
|
+
|
|
389
714
|
_lastError = error;
|
|
715
|
+
|
|
716
|
+
if (onError) {
|
|
717
|
+
try {
|
|
718
|
+
return onError(error);
|
|
719
|
+
} catch (onErrorError) {
|
|
720
|
+
emit(
|
|
721
|
+
{
|
|
722
|
+
type: 'error',
|
|
723
|
+
id: idStr,
|
|
724
|
+
label: `${label} (onError)`,
|
|
725
|
+
depth,
|
|
726
|
+
duration: performance.now() - start,
|
|
727
|
+
error: onErrorError,
|
|
728
|
+
budget,
|
|
729
|
+
maxResultLength: effectiveMaxLen,
|
|
730
|
+
},
|
|
731
|
+
prefix,
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
_lastError = onErrorError;
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
390
739
|
return null;
|
|
391
740
|
}
|
|
392
741
|
};
|
|
393
742
|
|
|
394
|
-
// ─── measure
|
|
743
|
+
// ─── measure Async ─────────────────────────────────────────────
|
|
395
744
|
|
|
396
745
|
const measureFn = async <T = null>(
|
|
397
|
-
arg1:
|
|
398
|
-
arg2?:
|
|
399
|
-
|
|
746
|
+
arg1: MeasureAction<T>,
|
|
747
|
+
arg2?:
|
|
748
|
+
| ((measure: MeasureFn, measureSync: MeasureSyncFn) => Promise<T>)
|
|
749
|
+
| ((measure: MeasureFn) => Promise<T>)
|
|
750
|
+
| (() => Promise<T>),
|
|
751
|
+
arg3?: (error: unknown) => T | null | Promise<T | null>,
|
|
400
752
|
): Promise<T | null> => {
|
|
401
753
|
if (typeof arg2 === 'function') {
|
|
402
|
-
return _measureInternal(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
754
|
+
return _measureInternal(
|
|
755
|
+
arg2 as any,
|
|
756
|
+
arg1,
|
|
757
|
+
[counter.value++],
|
|
758
|
+
0,
|
|
759
|
+
arg3,
|
|
760
|
+
scopeMaxLen,
|
|
761
|
+
) as Promise<T | null>;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const currentId = toAlpha(counter.value++);
|
|
765
|
+
|
|
766
|
+
emit(
|
|
767
|
+
{
|
|
406
768
|
type: 'annotation',
|
|
407
769
|
id: currentId,
|
|
408
770
|
label: buildActionLabel(arg1),
|
|
409
771
|
depth: 0,
|
|
410
772
|
meta: extractMeta(arg1),
|
|
411
|
-
},
|
|
412
|
-
|
|
413
|
-
|
|
773
|
+
},
|
|
774
|
+
prefix,
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
return Promise.resolve(null);
|
|
414
778
|
};
|
|
415
779
|
|
|
416
780
|
measureFn.timed = async <T = null>(
|
|
417
|
-
arg1:
|
|
418
|
-
arg2?: ((measure: MeasureFn) => Promise<T>)
|
|
781
|
+
arg1: MeasureAction<T>,
|
|
782
|
+
arg2?: ((measure: MeasureFn) => Promise<T>) | (() => Promise<T>),
|
|
419
783
|
): Promise<TimedResult<T>> => {
|
|
420
784
|
const start = performance.now();
|
|
421
|
-
const result = await measureFn(arg1, arg2);
|
|
785
|
+
const result = await measureFn(arg1, arg2 as any);
|
|
422
786
|
const duration = performance.now() - start;
|
|
423
|
-
|
|
787
|
+
|
|
788
|
+
return {
|
|
789
|
+
result,
|
|
790
|
+
duration,
|
|
791
|
+
};
|
|
424
792
|
};
|
|
425
793
|
|
|
426
794
|
measureFn.retry = async <T = null>(
|
|
427
|
-
label:
|
|
795
|
+
label: MeasureAction<T>,
|
|
428
796
|
opts: RetryOpts,
|
|
429
|
-
fn: () => Promise<T
|
|
797
|
+
fn: () => Promise<T>,
|
|
430
798
|
): Promise<T | null> => {
|
|
431
799
|
const attempts = opts.attempts ?? 3;
|
|
432
800
|
const delay = opts.delay ?? 1000;
|
|
433
801
|
const backoff = opts.backoff ?? 1;
|
|
434
802
|
const lbl = buildActionLabel(label);
|
|
435
803
|
const budget = extractBudget(label);
|
|
804
|
+
const effectiveMaxLen = extractMaxResultLength(label) ?? scopeMaxLen;
|
|
436
805
|
|
|
437
|
-
for (let i = 0; i < attempts; i
|
|
806
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
438
807
|
const attempt = i + 1;
|
|
439
808
|
const attemptLabel = `${lbl} [${attempt}/${attempts}]`;
|
|
440
809
|
const start = performance.now();
|
|
441
810
|
const currentId = toAlpha(counter.value++);
|
|
442
811
|
|
|
443
|
-
emit(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
812
|
+
emit(
|
|
813
|
+
{
|
|
814
|
+
type: 'start',
|
|
815
|
+
id: currentId,
|
|
816
|
+
label: attemptLabel,
|
|
817
|
+
depth: 0,
|
|
818
|
+
meta: extractMeta(label),
|
|
819
|
+
},
|
|
820
|
+
prefix,
|
|
821
|
+
);
|
|
450
822
|
|
|
451
823
|
try {
|
|
452
824
|
const result = await fn();
|
|
453
825
|
const duration = performance.now() - start;
|
|
454
|
-
|
|
826
|
+
|
|
827
|
+
emit(
|
|
828
|
+
{
|
|
829
|
+
type: 'success',
|
|
830
|
+
id: currentId,
|
|
831
|
+
label: attemptLabel,
|
|
832
|
+
depth: 0,
|
|
833
|
+
duration,
|
|
834
|
+
result: mapResultForLog(label, result),
|
|
835
|
+
budget,
|
|
836
|
+
maxResultLength: effectiveMaxLen,
|
|
837
|
+
},
|
|
838
|
+
prefix,
|
|
839
|
+
);
|
|
840
|
+
|
|
455
841
|
return result;
|
|
456
842
|
} catch (error) {
|
|
457
843
|
const duration = performance.now() - start;
|
|
458
|
-
|
|
844
|
+
|
|
845
|
+
emit(
|
|
846
|
+
{
|
|
847
|
+
type: 'error',
|
|
848
|
+
id: currentId,
|
|
849
|
+
label: attemptLabel,
|
|
850
|
+
depth: 0,
|
|
851
|
+
duration,
|
|
852
|
+
error,
|
|
853
|
+
budget,
|
|
854
|
+
maxResultLength: effectiveMaxLen,
|
|
855
|
+
},
|
|
856
|
+
prefix,
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
_lastError = error;
|
|
860
|
+
|
|
459
861
|
if (attempt < attempts) {
|
|
460
|
-
await new Promise(
|
|
862
|
+
await new Promise((resolve) => {
|
|
863
|
+
setTimeout(resolve, delay * Math.pow(backoff, i));
|
|
864
|
+
});
|
|
461
865
|
}
|
|
462
866
|
}
|
|
463
867
|
}
|
|
868
|
+
|
|
464
869
|
return null;
|
|
465
870
|
};
|
|
466
871
|
|
|
467
872
|
measureFn.assert = async <T>(
|
|
468
|
-
arg1:
|
|
469
|
-
arg2: ((measure: MeasureFn) => Promise<T>) | (() => Promise<T>)
|
|
873
|
+
arg1: MeasureAction<T>,
|
|
874
|
+
arg2: ((measure: MeasureFn) => Promise<T>) | (() => Promise<T>),
|
|
470
875
|
): Promise<T> => {
|
|
471
876
|
const result = await measureFn(arg1, arg2 as any);
|
|
877
|
+
|
|
472
878
|
if (result === null) {
|
|
473
879
|
const cause = _lastError;
|
|
474
880
|
_lastError = null;
|
|
475
|
-
|
|
881
|
+
|
|
882
|
+
throw new Error(`measure.assert: "${buildActionLabel(arg1)}" failed`, {
|
|
883
|
+
cause,
|
|
884
|
+
});
|
|
476
885
|
}
|
|
886
|
+
|
|
477
887
|
return result;
|
|
478
888
|
};
|
|
479
889
|
|
|
480
890
|
measureFn.wrap = <A extends any[], R>(
|
|
481
|
-
label:
|
|
482
|
-
fn: (...args: A) => Promise<R
|
|
891
|
+
label: MeasureAction<R>,
|
|
892
|
+
fn: (...args: A) => Promise<R>,
|
|
483
893
|
): ((...args: A) => Promise<R | null>) => {
|
|
484
894
|
return (...args: A) => measureFn(label, () => fn(...args));
|
|
485
895
|
};
|
|
486
896
|
|
|
487
897
|
measureFn.batch = async <T, R>(
|
|
488
|
-
label:
|
|
898
|
+
label: MeasureAction<R>,
|
|
489
899
|
items: T[],
|
|
490
900
|
fn: (item: T, index: number) => Promise<R>,
|
|
491
|
-
opts?: BatchOpts
|
|
901
|
+
opts?: BatchOpts,
|
|
492
902
|
): Promise<(R | null)[]> => {
|
|
493
903
|
const lbl = buildActionLabel(label);
|
|
494
904
|
const total = items.length;
|
|
495
905
|
const every = opts?.every ?? Math.max(1, Math.ceil(total / 5));
|
|
496
906
|
const currentId = toAlpha(counter.value++);
|
|
497
907
|
const startTime = performance.now();
|
|
908
|
+
const budget = extractBudget(label);
|
|
498
909
|
|
|
499
|
-
emit(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
910
|
+
emit(
|
|
911
|
+
{
|
|
912
|
+
type: 'start',
|
|
913
|
+
id: currentId,
|
|
914
|
+
label: `${lbl} (${total} items)`,
|
|
915
|
+
depth: 0,
|
|
916
|
+
meta: extractMeta(label),
|
|
917
|
+
},
|
|
918
|
+
prefix,
|
|
919
|
+
);
|
|
506
920
|
|
|
507
921
|
const results: (R | null)[] = [];
|
|
508
|
-
|
|
922
|
+
|
|
923
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
509
924
|
try {
|
|
510
925
|
results.push(await fn(items[i]!, i));
|
|
511
|
-
} catch {
|
|
926
|
+
} catch (error) {
|
|
927
|
+
_lastError = error;
|
|
512
928
|
results.push(null);
|
|
513
929
|
}
|
|
930
|
+
|
|
514
931
|
if ((i + 1) % every === 0 && i + 1 < total) {
|
|
515
932
|
const elapsed = (performance.now() - startTime) / 1000;
|
|
516
933
|
const rate = ((i + 1) / elapsed).toFixed(0);
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
934
|
+
|
|
935
|
+
emit(
|
|
936
|
+
{
|
|
937
|
+
type: 'annotation',
|
|
938
|
+
id: currentId,
|
|
939
|
+
label: `${i + 1}/${total} (${elapsed.toFixed(1)}s, ${rate}/s)`,
|
|
940
|
+
depth: 0,
|
|
941
|
+
},
|
|
942
|
+
prefix,
|
|
943
|
+
);
|
|
523
944
|
}
|
|
524
945
|
}
|
|
525
946
|
|
|
526
947
|
const duration = performance.now() - startTime;
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
948
|
+
const okCount = results.filter((result) => result !== null).length;
|
|
949
|
+
|
|
950
|
+
emit(
|
|
951
|
+
{
|
|
952
|
+
type: 'success',
|
|
953
|
+
id: currentId,
|
|
954
|
+
label: `${lbl} (${total} items)`,
|
|
955
|
+
depth: 0,
|
|
956
|
+
duration,
|
|
957
|
+
result: `${okCount}/${total} ok`,
|
|
958
|
+
budget,
|
|
959
|
+
},
|
|
960
|
+
prefix,
|
|
961
|
+
);
|
|
962
|
+
|
|
537
963
|
return results;
|
|
538
964
|
};
|
|
539
965
|
|
|
540
966
|
// ─── measureSync ───────────────────────────────────────────────
|
|
541
967
|
|
|
542
968
|
const measureSyncFn = <T = null>(
|
|
543
|
-
arg1:
|
|
544
|
-
arg2?: ((measure: MeasureSyncFn) => T)
|
|
969
|
+
arg1: MeasureAction<T>,
|
|
970
|
+
arg2?: ((measure: MeasureSyncFn) => T) | (() => T),
|
|
971
|
+
arg3?: (error: unknown) => T | null,
|
|
545
972
|
): T | null => {
|
|
546
973
|
if (typeof arg2 === 'function') {
|
|
547
|
-
return _measureInternalSync(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
974
|
+
return _measureInternalSync(
|
|
975
|
+
arg2 as any,
|
|
976
|
+
arg1,
|
|
977
|
+
[counter.value++],
|
|
978
|
+
0,
|
|
979
|
+
arg3,
|
|
980
|
+
scopeMaxLen,
|
|
981
|
+
) as T | null;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const currentId = toAlpha(counter.value++);
|
|
985
|
+
|
|
986
|
+
emit(
|
|
987
|
+
{
|
|
551
988
|
type: 'annotation',
|
|
552
989
|
id: currentId,
|
|
553
990
|
label: buildActionLabel(arg1),
|
|
554
991
|
depth: 0,
|
|
555
992
|
meta: extractMeta(arg1),
|
|
556
|
-
},
|
|
557
|
-
|
|
558
|
-
|
|
993
|
+
},
|
|
994
|
+
prefix,
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
return null;
|
|
559
998
|
};
|
|
560
999
|
|
|
561
1000
|
measureSyncFn.timed = <T = null>(
|
|
562
|
-
arg1:
|
|
563
|
-
arg2?: ((measure: MeasureSyncFn) => T)
|
|
1001
|
+
arg1: MeasureAction<T>,
|
|
1002
|
+
arg2?: ((measure: MeasureSyncFn) => T) | (() => T),
|
|
564
1003
|
): TimedResult<T> => {
|
|
565
1004
|
const start = performance.now();
|
|
566
|
-
const result = measureSyncFn(arg1, arg2);
|
|
1005
|
+
const result = measureSyncFn(arg1, arg2 as any);
|
|
567
1006
|
const duration = performance.now() - start;
|
|
568
|
-
|
|
1007
|
+
|
|
1008
|
+
return {
|
|
1009
|
+
result,
|
|
1010
|
+
duration,
|
|
1011
|
+
};
|
|
569
1012
|
};
|
|
570
1013
|
|
|
571
1014
|
measureSyncFn.assert = <T>(
|
|
572
|
-
arg1:
|
|
573
|
-
arg2: ((measure: MeasureSyncFn) => T) | (() => T)
|
|
1015
|
+
arg1: MeasureAction<T>,
|
|
1016
|
+
arg2: ((measure: MeasureSyncFn) => T) | (() => T),
|
|
574
1017
|
): T => {
|
|
575
1018
|
const result = measureSyncFn(arg1, arg2 as any);
|
|
1019
|
+
|
|
576
1020
|
if (result === null) {
|
|
577
1021
|
const cause = _lastError;
|
|
578
1022
|
_lastError = null;
|
|
579
|
-
|
|
1023
|
+
|
|
1024
|
+
throw new Error(`measureSync.assert: "${buildActionLabel(arg1)}" failed`, {
|
|
1025
|
+
cause,
|
|
1026
|
+
});
|
|
580
1027
|
}
|
|
1028
|
+
|
|
581
1029
|
return result;
|
|
582
1030
|
};
|
|
583
1031
|
|
|
584
1032
|
measureSyncFn.wrap = <A extends any[], R>(
|
|
585
|
-
label:
|
|
586
|
-
fn: (...args: A) => R
|
|
1033
|
+
label: MeasureAction<R>,
|
|
1034
|
+
fn: (...args: A) => R,
|
|
587
1035
|
): ((...args: A) => R | null) => {
|
|
588
1036
|
return (...args: A) => measureSyncFn(label, () => fn(...args));
|
|
589
1037
|
};
|
|
590
1038
|
|
|
591
|
-
return {
|
|
1039
|
+
return {
|
|
1040
|
+
measure: measureFn as MeasureFn & {
|
|
1041
|
+
timed: <T = null>(
|
|
1042
|
+
arg1: MeasureAction<T>,
|
|
1043
|
+
arg2?: ((measure: MeasureFn) => Promise<T>) | (() => Promise<T>),
|
|
1044
|
+
) => Promise<TimedResult<T>>;
|
|
1045
|
+
|
|
1046
|
+
retry: <T = null>(
|
|
1047
|
+
label: MeasureAction<T>,
|
|
1048
|
+
opts: RetryOpts,
|
|
1049
|
+
fn: () => Promise<T>,
|
|
1050
|
+
) => Promise<T | null>;
|
|
1051
|
+
|
|
1052
|
+
assert: <T>(
|
|
1053
|
+
arg1: MeasureAction<T>,
|
|
1054
|
+
arg2: ((measure: MeasureFn) => Promise<T>) | (() => Promise<T>),
|
|
1055
|
+
) => Promise<T>;
|
|
1056
|
+
|
|
1057
|
+
wrap: <A extends any[], R>(
|
|
1058
|
+
label: MeasureAction<R>,
|
|
1059
|
+
fn: (...args: A) => Promise<R>,
|
|
1060
|
+
) => (...args: A) => Promise<R | null>;
|
|
1061
|
+
|
|
1062
|
+
batch: <T, R>(
|
|
1063
|
+
label: MeasureAction<R>,
|
|
1064
|
+
items: T[],
|
|
1065
|
+
fn: (item: T, index: number) => Promise<R>,
|
|
1066
|
+
opts?: BatchOpts,
|
|
1067
|
+
) => Promise<(R | null)[]>;
|
|
1068
|
+
},
|
|
1069
|
+
|
|
1070
|
+
measureSync: measureSyncFn as MeasureSyncFn & {
|
|
1071
|
+
timed: <T = null>(
|
|
1072
|
+
arg1: MeasureAction<T>,
|
|
1073
|
+
arg2?: ((measure: MeasureSyncFn) => T) | (() => T),
|
|
1074
|
+
) => TimedResult<T>;
|
|
1075
|
+
|
|
1076
|
+
assert: <T>(
|
|
1077
|
+
arg1: MeasureAction<T>,
|
|
1078
|
+
arg2: ((measure: MeasureSyncFn) => T) | (() => T),
|
|
1079
|
+
) => T;
|
|
1080
|
+
|
|
1081
|
+
wrap: <A extends any[], R>(
|
|
1082
|
+
label: MeasureAction<R>,
|
|
1083
|
+
fn: (...args: A) => R,
|
|
1084
|
+
) => (...args: A) => R | null;
|
|
1085
|
+
},
|
|
1086
|
+
};
|
|
592
1087
|
};
|
|
593
1088
|
|
|
594
|
-
// ─── Default
|
|
1089
|
+
// ─── Default Global Instance ─────────────────────────────────────────
|
|
595
1090
|
|
|
596
1091
|
const globalInstance = createMeasureImpl();
|
|
597
1092
|
|
|
598
1093
|
export const measure = globalInstance.measure;
|
|
599
1094
|
export const measureSync = globalInstance.measureSync;
|
|
600
1095
|
|
|
601
|
-
// ─── Scoped
|
|
1096
|
+
// ─── Scoped Instances ────────────────────────────────────────────────
|
|
602
1097
|
|
|
603
1098
|
export const createMeasure = (scopePrefix: string, opts?: ScopeOpts) => {
|
|
604
|
-
const scopeCounter = {
|
|
1099
|
+
const scopeCounter = {
|
|
1100
|
+
value: 0,
|
|
1101
|
+
};
|
|
1102
|
+
|
|
605
1103
|
const scoped = createMeasureImpl(scopePrefix, scopeCounter, opts);
|
|
1104
|
+
|
|
606
1105
|
return {
|
|
607
1106
|
...scoped,
|
|
608
|
-
resetCounter: () => {
|
|
1107
|
+
resetCounter: () => {
|
|
1108
|
+
scopeCounter.value = 0;
|
|
1109
|
+
},
|
|
609
1110
|
};
|
|
610
1111
|
};
|
|
611
|
-
|
|
612
|
-
// ─── Utility exports ─────────────────────────────────────────────────
|
|
613
|
-
|
|
614
|
-
export { formatDuration };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"main": "./index.ts",
|
|
5
5
|
"types": "./index.ts",
|
|
6
|
-
"version": "3.11.
|
|
6
|
+
"version": "3.11.2",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"private": false,
|
|
9
9
|
"description": "Zero-dependency function performance measurement with hierarchical logging",
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
"test": "bun test",
|
|
31
31
|
"example": "bun run example.ts"
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|
package/bun.lock
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"configVersion": 0,
|
|
4
|
-
"workspaces": {
|
|
5
|
-
"": {
|
|
6
|
-
"name": "ments-utils",
|
|
7
|
-
"devDependencies": {
|
|
8
|
-
"@types/bun": "latest",
|
|
9
|
-
},
|
|
10
|
-
"peerDependencies": {
|
|
11
|
-
"typescript": "^5",
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
"packages": {
|
|
16
|
-
"@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
|
|
17
|
-
|
|
18
|
-
"@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
|
|
19
|
-
|
|
20
|
-
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
|
21
|
-
|
|
22
|
-
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
|
23
|
-
|
|
24
|
-
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
|
25
|
-
}
|
|
26
|
-
}
|