measure-fn 3.11.1 → 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 -198
- package/package.json +1 -1
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,118 +172,233 @@ 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
|
|
|
168
|
-
|
|
169
288
|
const defaultLogger = (event: MeasureEvent, prefix?: string) => {
|
|
170
289
|
const pfx = prefix ? `${prefix}:` : '';
|
|
171
290
|
const id = `[${pfx}${event.id}]`;
|
|
172
291
|
const t = ts();
|
|
173
292
|
|
|
174
293
|
switch (event.type) {
|
|
175
|
-
case 'start':
|
|
294
|
+
case 'start': {
|
|
176
295
|
console.log(`${t}${id} ... ${event.label}${formatMeta(event.meta)}`);
|
|
177
296
|
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
178
299
|
case 'success': {
|
|
179
|
-
const endLabel = dotEndLabel
|
|
180
|
-
|
|
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
|
+
|
|
181
309
|
const arrow = resultStr ? ` → ${resultStr}` : '';
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
|
|
186
320
|
break;
|
|
187
321
|
}
|
|
322
|
+
|
|
188
323
|
case 'error': {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
:
|
|
195
|
-
|
|
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
|
+
|
|
196
340
|
if (event.error instanceof Error) {
|
|
197
341
|
console.error(`${id}`, event.error.stack ?? event.error.message);
|
|
342
|
+
|
|
198
343
|
if (event.error.cause) {
|
|
199
344
|
console.error(`${id} Cause:`, event.error.cause);
|
|
200
345
|
}
|
|
201
346
|
} else {
|
|
202
347
|
console.error(`${id}`, event.error);
|
|
203
348
|
}
|
|
349
|
+
|
|
204
350
|
break;
|
|
205
351
|
}
|
|
206
|
-
|
|
352
|
+
|
|
353
|
+
case 'annotation': {
|
|
207
354
|
console.log(`${t}${id} = ${event.label}${formatMeta(event.meta)}`);
|
|
208
355
|
break;
|
|
356
|
+
}
|
|
209
357
|
}
|
|
210
358
|
};
|
|
211
359
|
|
|
212
360
|
// ─── Types ───────────────────────────────────────────────────────────
|
|
213
361
|
|
|
214
|
-
|
|
215
362
|
export type MeasureFn = {
|
|
216
|
-
<U>(label:
|
|
217
|
-
|
|
218
|
-
<U>(
|
|
219
|
-
|
|
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>;
|
|
220
382
|
};
|
|
221
383
|
|
|
222
384
|
export type MeasureSyncFn = {
|
|
223
|
-
<U>(label:
|
|
224
|
-
|
|
225
|
-
(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;
|
|
226
396
|
};
|
|
227
397
|
|
|
228
|
-
export type TimedResult<T> = {
|
|
398
|
+
export type TimedResult<T> = {
|
|
399
|
+
result: T | null;
|
|
400
|
+
duration: number;
|
|
401
|
+
};
|
|
229
402
|
|
|
230
403
|
export type RetryOpts = {
|
|
231
404
|
attempts?: number;
|
|
@@ -244,28 +417,47 @@ const createNestedResolver = (
|
|
|
244
417
|
fullIdChain: string[],
|
|
245
418
|
childCounterRef: { value: number },
|
|
246
419
|
depth: number,
|
|
247
|
-
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),
|
|
248
428
|
prefix?: string,
|
|
249
|
-
inheritedMaxLen?: number
|
|
429
|
+
inheritedMaxLen?: number,
|
|
250
430
|
) => {
|
|
251
431
|
return (...args: any[]) => {
|
|
252
|
-
const label = args[0];
|
|
432
|
+
const label = args[0] as MeasureAction;
|
|
253
433
|
const fn = args[1];
|
|
254
434
|
const onError = args[2];
|
|
255
435
|
|
|
256
436
|
if (typeof fn === 'function') {
|
|
257
437
|
const childParentChain = [...fullIdChain, childCounterRef.value++];
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
{
|
|
261
451
|
type: 'annotation',
|
|
262
452
|
id: fullIdChain.join('-'),
|
|
263
453
|
label: buildActionLabel(label),
|
|
264
454
|
depth: depth + 1,
|
|
265
455
|
meta: extractMeta(label),
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
|
|
456
|
+
},
|
|
457
|
+
prefix,
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
return isAsync ? Promise.resolve(null) : null;
|
|
269
461
|
};
|
|
270
462
|
};
|
|
271
463
|
|
|
@@ -281,18 +473,32 @@ export type ScopeOpts = {
|
|
|
281
473
|
maxResultLength?: number;
|
|
282
474
|
};
|
|
283
475
|
|
|
284
|
-
const createMeasureImpl = (
|
|
285
|
-
|
|
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
|
+
|
|
286
492
|
const scopeMaxLen = scopeOpts?.maxResultLength;
|
|
287
493
|
let _lastError: unknown = null;
|
|
288
494
|
|
|
289
495
|
const _measureInternal = async <U>(
|
|
290
496
|
fnInternal: (measure: MeasureFn, measureSync: MeasureSyncFn) => Promise<U>,
|
|
291
|
-
actionInternal:
|
|
497
|
+
actionInternal: MeasureAction<U>,
|
|
292
498
|
parentIdChain: (string | number)[],
|
|
293
499
|
depth: number,
|
|
294
|
-
onError?: (error: unknown) =>
|
|
295
|
-
inheritedMaxLen?: number
|
|
500
|
+
onError?: (error: unknown) => U | null | Promise<U | null>,
|
|
501
|
+
inheritedMaxLen?: number,
|
|
296
502
|
): Promise<U | null> => {
|
|
297
503
|
const start = performance.now();
|
|
298
504
|
const childCounterRef = { value: 0 };
|
|
@@ -302,316 +508,604 @@ const createMeasureImpl = (prefix?: string, counterRef?: { value: number }, scop
|
|
|
302
508
|
const localMaxLen = extractMaxResultLength(actionInternal);
|
|
303
509
|
const effectiveMaxLen = localMaxLen ?? inheritedMaxLen;
|
|
304
510
|
|
|
305
|
-
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0)
|
|
306
|
-
const fullIdChain: string[] = [
|
|
511
|
+
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0));
|
|
512
|
+
const fullIdChain: string[] = [
|
|
513
|
+
...parentIdChain.map((value) => String(value)),
|
|
514
|
+
currentId,
|
|
515
|
+
];
|
|
307
516
|
const idStr = fullIdChain.join('-');
|
|
308
517
|
|
|
309
|
-
emit(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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,
|
|
313
533
|
depth,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
+
);
|
|
319
548
|
|
|
320
549
|
try {
|
|
321
550
|
let result: U;
|
|
551
|
+
|
|
322
552
|
if (timeout && timeout > 0) {
|
|
323
553
|
result = await Promise.race([
|
|
324
|
-
fnInternal(
|
|
325
|
-
|
|
326
|
-
|
|
554
|
+
fnInternal(
|
|
555
|
+
measureForNextLevel as MeasureFn,
|
|
556
|
+
measureSyncForNextLevel as MeasureSyncFn,
|
|
327
557
|
),
|
|
558
|
+
new Promise<never>((_resolve, reject) => {
|
|
559
|
+
setTimeout(
|
|
560
|
+
() => reject(new Error(`Timeout (${formatDuration(timeout)})`)),
|
|
561
|
+
timeout,
|
|
562
|
+
);
|
|
563
|
+
}),
|
|
328
564
|
]);
|
|
329
565
|
} else {
|
|
330
|
-
result = await fnInternal(
|
|
566
|
+
result = await fnInternal(
|
|
567
|
+
measureForNextLevel as MeasureFn,
|
|
568
|
+
measureSyncForNextLevel as MeasureSyncFn,
|
|
569
|
+
);
|
|
331
570
|
}
|
|
571
|
+
|
|
332
572
|
const duration = performance.now() - start;
|
|
333
|
-
|
|
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
|
+
|
|
334
588
|
return result;
|
|
335
589
|
} catch (error) {
|
|
336
590
|
const duration = performance.now() - start;
|
|
337
|
-
|
|
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
|
+
|
|
338
606
|
_lastError = error;
|
|
607
|
+
|
|
339
608
|
if (onError) {
|
|
340
609
|
try {
|
|
341
|
-
return onError(error);
|
|
610
|
+
return await onError(error);
|
|
342
611
|
} catch (onErrorError) {
|
|
343
|
-
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
|
+
|
|
344
626
|
_lastError = onErrorError;
|
|
345
627
|
return null;
|
|
346
628
|
}
|
|
347
629
|
}
|
|
630
|
+
|
|
348
631
|
return null;
|
|
349
632
|
}
|
|
350
633
|
};
|
|
351
634
|
|
|
352
635
|
const _measureInternalSync = <U>(
|
|
353
636
|
fnInternal: (measure: MeasureSyncFn) => U,
|
|
354
|
-
actionInternal:
|
|
637
|
+
actionInternal: MeasureAction<U>,
|
|
355
638
|
parentIdChain: (string | number)[],
|
|
356
639
|
depth: number,
|
|
357
|
-
|
|
358
|
-
inheritedMaxLen?: number
|
|
640
|
+
onError?: (error: unknown) => U | null,
|
|
641
|
+
inheritedMaxLen?: number,
|
|
359
642
|
): U | null => {
|
|
360
643
|
const start = performance.now();
|
|
361
644
|
const childCounterRef = { value: 0 };
|
|
362
645
|
const label = buildActionLabel(actionInternal);
|
|
363
|
-
const hasNested = fnInternal.length > 0;
|
|
364
646
|
const budget = extractBudget(actionInternal);
|
|
365
647
|
const localMaxLen = extractMaxResultLength(actionInternal);
|
|
366
648
|
const effectiveMaxLen = localMaxLen ?? inheritedMaxLen;
|
|
367
649
|
|
|
368
|
-
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0)
|
|
369
|
-
const fullIdChain: string[] = [
|
|
650
|
+
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0));
|
|
651
|
+
const fullIdChain: string[] = [
|
|
652
|
+
...parentIdChain.map((value) => String(value)),
|
|
653
|
+
currentId,
|
|
654
|
+
];
|
|
370
655
|
const idStr = fullIdChain.join('-');
|
|
371
656
|
|
|
372
|
-
|
|
373
|
-
|
|
657
|
+
emit(
|
|
658
|
+
{
|
|
374
659
|
type: 'start',
|
|
375
660
|
id: idStr,
|
|
376
661
|
label,
|
|
377
662
|
depth,
|
|
378
663
|
meta: extractMeta(actionInternal),
|
|
379
|
-
},
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
664
|
+
},
|
|
665
|
+
prefix,
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
const measureForNextLevel = createNestedResolver(
|
|
669
|
+
false,
|
|
670
|
+
fullIdChain,
|
|
671
|
+
childCounterRef,
|
|
672
|
+
depth,
|
|
673
|
+
_measureInternalSync,
|
|
674
|
+
prefix,
|
|
675
|
+
effectiveMaxLen,
|
|
676
|
+
);
|
|
383
677
|
|
|
384
678
|
try {
|
|
385
679
|
const result = fnInternal(measureForNextLevel as MeasureSyncFn);
|
|
386
680
|
const duration = performance.now() - start;
|
|
387
|
-
|
|
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
|
+
|
|
388
696
|
return result;
|
|
389
697
|
} catch (error) {
|
|
390
698
|
const duration = performance.now() - start;
|
|
391
|
-
|
|
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
|
+
|
|
392
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
|
+
|
|
393
739
|
return null;
|
|
394
740
|
}
|
|
395
741
|
};
|
|
396
742
|
|
|
397
|
-
// ─── measure
|
|
743
|
+
// ─── measure Async ─────────────────────────────────────────────
|
|
398
744
|
|
|
399
745
|
const measureFn = async <T = null>(
|
|
400
|
-
arg1:
|
|
401
|
-
arg2?:
|
|
402
|
-
|
|
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>,
|
|
403
752
|
): Promise<T | null> => {
|
|
404
753
|
if (typeof arg2 === 'function') {
|
|
405
|
-
return _measureInternal(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
{
|
|
409
768
|
type: 'annotation',
|
|
410
769
|
id: currentId,
|
|
411
770
|
label: buildActionLabel(arg1),
|
|
412
771
|
depth: 0,
|
|
413
772
|
meta: extractMeta(arg1),
|
|
414
|
-
},
|
|
415
|
-
|
|
416
|
-
|
|
773
|
+
},
|
|
774
|
+
prefix,
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
return Promise.resolve(null);
|
|
417
778
|
};
|
|
418
779
|
|
|
419
780
|
measureFn.timed = async <T = null>(
|
|
420
|
-
arg1:
|
|
421
|
-
arg2?: ((measure: MeasureFn) => Promise<T>)
|
|
781
|
+
arg1: MeasureAction<T>,
|
|
782
|
+
arg2?: ((measure: MeasureFn) => Promise<T>) | (() => Promise<T>),
|
|
422
783
|
): Promise<TimedResult<T>> => {
|
|
423
784
|
const start = performance.now();
|
|
424
|
-
const result = await measureFn(arg1, arg2);
|
|
785
|
+
const result = await measureFn(arg1, arg2 as any);
|
|
425
786
|
const duration = performance.now() - start;
|
|
426
|
-
|
|
787
|
+
|
|
788
|
+
return {
|
|
789
|
+
result,
|
|
790
|
+
duration,
|
|
791
|
+
};
|
|
427
792
|
};
|
|
428
793
|
|
|
429
794
|
measureFn.retry = async <T = null>(
|
|
430
|
-
label:
|
|
795
|
+
label: MeasureAction<T>,
|
|
431
796
|
opts: RetryOpts,
|
|
432
|
-
fn: () => Promise<T
|
|
797
|
+
fn: () => Promise<T>,
|
|
433
798
|
): Promise<T | null> => {
|
|
434
799
|
const attempts = opts.attempts ?? 3;
|
|
435
800
|
const delay = opts.delay ?? 1000;
|
|
436
801
|
const backoff = opts.backoff ?? 1;
|
|
437
802
|
const lbl = buildActionLabel(label);
|
|
438
803
|
const budget = extractBudget(label);
|
|
804
|
+
const effectiveMaxLen = extractMaxResultLength(label) ?? scopeMaxLen;
|
|
439
805
|
|
|
440
|
-
for (let i = 0; i < attempts; i
|
|
806
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
441
807
|
const attempt = i + 1;
|
|
442
808
|
const attemptLabel = `${lbl} [${attempt}/${attempts}]`;
|
|
443
809
|
const start = performance.now();
|
|
444
810
|
const currentId = toAlpha(counter.value++);
|
|
445
811
|
|
|
446
|
-
emit(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
812
|
+
emit(
|
|
813
|
+
{
|
|
814
|
+
type: 'start',
|
|
815
|
+
id: currentId,
|
|
816
|
+
label: attemptLabel,
|
|
817
|
+
depth: 0,
|
|
818
|
+
meta: extractMeta(label),
|
|
819
|
+
},
|
|
820
|
+
prefix,
|
|
821
|
+
);
|
|
453
822
|
|
|
454
823
|
try {
|
|
455
824
|
const result = await fn();
|
|
456
825
|
const duration = performance.now() - start;
|
|
457
|
-
|
|
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
|
+
|
|
458
841
|
return result;
|
|
459
842
|
} catch (error) {
|
|
460
843
|
const duration = performance.now() - start;
|
|
461
|
-
|
|
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
|
+
|
|
462
861
|
if (attempt < attempts) {
|
|
463
|
-
await new Promise(
|
|
862
|
+
await new Promise((resolve) => {
|
|
863
|
+
setTimeout(resolve, delay * Math.pow(backoff, i));
|
|
864
|
+
});
|
|
464
865
|
}
|
|
465
866
|
}
|
|
466
867
|
}
|
|
868
|
+
|
|
467
869
|
return null;
|
|
468
870
|
};
|
|
469
871
|
|
|
470
872
|
measureFn.assert = async <T>(
|
|
471
|
-
arg1:
|
|
472
|
-
arg2: ((measure: MeasureFn) => Promise<T>) | (() => Promise<T>)
|
|
873
|
+
arg1: MeasureAction<T>,
|
|
874
|
+
arg2: ((measure: MeasureFn) => Promise<T>) | (() => Promise<T>),
|
|
473
875
|
): Promise<T> => {
|
|
474
876
|
const result = await measureFn(arg1, arg2 as any);
|
|
877
|
+
|
|
475
878
|
if (result === null) {
|
|
476
879
|
const cause = _lastError;
|
|
477
880
|
_lastError = null;
|
|
478
|
-
|
|
881
|
+
|
|
882
|
+
throw new Error(`measure.assert: "${buildActionLabel(arg1)}" failed`, {
|
|
883
|
+
cause,
|
|
884
|
+
});
|
|
479
885
|
}
|
|
886
|
+
|
|
480
887
|
return result;
|
|
481
888
|
};
|
|
482
889
|
|
|
483
890
|
measureFn.wrap = <A extends any[], R>(
|
|
484
|
-
label:
|
|
485
|
-
fn: (...args: A) => Promise<R
|
|
891
|
+
label: MeasureAction<R>,
|
|
892
|
+
fn: (...args: A) => Promise<R>,
|
|
486
893
|
): ((...args: A) => Promise<R | null>) => {
|
|
487
894
|
return (...args: A) => measureFn(label, () => fn(...args));
|
|
488
895
|
};
|
|
489
896
|
|
|
490
897
|
measureFn.batch = async <T, R>(
|
|
491
|
-
label:
|
|
898
|
+
label: MeasureAction<R>,
|
|
492
899
|
items: T[],
|
|
493
900
|
fn: (item: T, index: number) => Promise<R>,
|
|
494
|
-
opts?: BatchOpts
|
|
901
|
+
opts?: BatchOpts,
|
|
495
902
|
): Promise<(R | null)[]> => {
|
|
496
903
|
const lbl = buildActionLabel(label);
|
|
497
904
|
const total = items.length;
|
|
498
905
|
const every = opts?.every ?? Math.max(1, Math.ceil(total / 5));
|
|
499
906
|
const currentId = toAlpha(counter.value++);
|
|
500
907
|
const startTime = performance.now();
|
|
908
|
+
const budget = extractBudget(label);
|
|
501
909
|
|
|
502
|
-
emit(
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
+
);
|
|
509
920
|
|
|
510
921
|
const results: (R | null)[] = [];
|
|
511
|
-
|
|
922
|
+
|
|
923
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
512
924
|
try {
|
|
513
925
|
results.push(await fn(items[i]!, i));
|
|
514
|
-
} catch {
|
|
926
|
+
} catch (error) {
|
|
927
|
+
_lastError = error;
|
|
515
928
|
results.push(null);
|
|
516
929
|
}
|
|
930
|
+
|
|
517
931
|
if ((i + 1) % every === 0 && i + 1 < total) {
|
|
518
932
|
const elapsed = (performance.now() - startTime) / 1000;
|
|
519
933
|
const rate = ((i + 1) / elapsed).toFixed(0);
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
+
);
|
|
526
944
|
}
|
|
527
945
|
}
|
|
528
946
|
|
|
529
947
|
const duration = performance.now() - startTime;
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
+
|
|
540
963
|
return results;
|
|
541
964
|
};
|
|
542
965
|
|
|
543
966
|
// ─── measureSync ───────────────────────────────────────────────
|
|
544
967
|
|
|
545
968
|
const measureSyncFn = <T = null>(
|
|
546
|
-
arg1:
|
|
547
|
-
arg2?: ((measure: MeasureSyncFn) => T)
|
|
969
|
+
arg1: MeasureAction<T>,
|
|
970
|
+
arg2?: ((measure: MeasureSyncFn) => T) | (() => T),
|
|
971
|
+
arg3?: (error: unknown) => T | null,
|
|
548
972
|
): T | null => {
|
|
549
973
|
if (typeof arg2 === 'function') {
|
|
550
|
-
return _measureInternalSync(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
+
{
|
|
554
988
|
type: 'annotation',
|
|
555
989
|
id: currentId,
|
|
556
990
|
label: buildActionLabel(arg1),
|
|
557
991
|
depth: 0,
|
|
558
992
|
meta: extractMeta(arg1),
|
|
559
|
-
},
|
|
560
|
-
|
|
561
|
-
|
|
993
|
+
},
|
|
994
|
+
prefix,
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
return null;
|
|
562
998
|
};
|
|
563
999
|
|
|
564
1000
|
measureSyncFn.timed = <T = null>(
|
|
565
|
-
arg1:
|
|
566
|
-
arg2?: ((measure: MeasureSyncFn) => T)
|
|
1001
|
+
arg1: MeasureAction<T>,
|
|
1002
|
+
arg2?: ((measure: MeasureSyncFn) => T) | (() => T),
|
|
567
1003
|
): TimedResult<T> => {
|
|
568
1004
|
const start = performance.now();
|
|
569
|
-
const result = measureSyncFn(arg1, arg2);
|
|
1005
|
+
const result = measureSyncFn(arg1, arg2 as any);
|
|
570
1006
|
const duration = performance.now() - start;
|
|
571
|
-
|
|
1007
|
+
|
|
1008
|
+
return {
|
|
1009
|
+
result,
|
|
1010
|
+
duration,
|
|
1011
|
+
};
|
|
572
1012
|
};
|
|
573
1013
|
|
|
574
1014
|
measureSyncFn.assert = <T>(
|
|
575
|
-
arg1:
|
|
576
|
-
arg2: ((measure: MeasureSyncFn) => T) | (() => T)
|
|
1015
|
+
arg1: MeasureAction<T>,
|
|
1016
|
+
arg2: ((measure: MeasureSyncFn) => T) | (() => T),
|
|
577
1017
|
): T => {
|
|
578
1018
|
const result = measureSyncFn(arg1, arg2 as any);
|
|
1019
|
+
|
|
579
1020
|
if (result === null) {
|
|
580
1021
|
const cause = _lastError;
|
|
581
1022
|
_lastError = null;
|
|
582
|
-
|
|
1023
|
+
|
|
1024
|
+
throw new Error(`measureSync.assert: "${buildActionLabel(arg1)}" failed`, {
|
|
1025
|
+
cause,
|
|
1026
|
+
});
|
|
583
1027
|
}
|
|
1028
|
+
|
|
584
1029
|
return result;
|
|
585
1030
|
};
|
|
586
1031
|
|
|
587
1032
|
measureSyncFn.wrap = <A extends any[], R>(
|
|
588
|
-
label:
|
|
589
|
-
fn: (...args: A) => R
|
|
1033
|
+
label: MeasureAction<R>,
|
|
1034
|
+
fn: (...args: A) => R,
|
|
590
1035
|
): ((...args: A) => R | null) => {
|
|
591
1036
|
return (...args: A) => measureSyncFn(label, () => fn(...args));
|
|
592
1037
|
};
|
|
593
1038
|
|
|
594
|
-
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
|
+
};
|
|
595
1087
|
};
|
|
596
1088
|
|
|
597
|
-
// ─── Default
|
|
1089
|
+
// ─── Default Global Instance ─────────────────────────────────────────
|
|
598
1090
|
|
|
599
1091
|
const globalInstance = createMeasureImpl();
|
|
600
1092
|
|
|
601
1093
|
export const measure = globalInstance.measure;
|
|
602
1094
|
export const measureSync = globalInstance.measureSync;
|
|
603
1095
|
|
|
604
|
-
// ─── Scoped
|
|
1096
|
+
// ─── Scoped Instances ────────────────────────────────────────────────
|
|
605
1097
|
|
|
606
1098
|
export const createMeasure = (scopePrefix: string, opts?: ScopeOpts) => {
|
|
607
|
-
const scopeCounter = {
|
|
1099
|
+
const scopeCounter = {
|
|
1100
|
+
value: 0,
|
|
1101
|
+
};
|
|
1102
|
+
|
|
608
1103
|
const scoped = createMeasureImpl(scopePrefix, scopeCounter, opts);
|
|
1104
|
+
|
|
609
1105
|
return {
|
|
610
1106
|
...scoped,
|
|
611
|
-
resetCounter: () => {
|
|
1107
|
+
resetCounter: () => {
|
|
1108
|
+
scopeCounter.value = 0;
|
|
1109
|
+
},
|
|
612
1110
|
};
|
|
613
1111
|
};
|
|
614
|
-
|
|
615
|
-
// ─── Utility exports ─────────────────────────────────────────────────
|
|
616
|
-
|
|
617
|
-
export { formatDuration };
|
package/package.json
CHANGED