react-perf-recorder 0.1.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/claude/README.md +10 -0
  4. package/claude/agents/perf-recorder.md +44 -0
  5. package/claude/mcp.json +8 -0
  6. package/claude/skills/react-perf-recorder/SKILL.md +52 -0
  7. package/claude/skills/react-perf-recorder/references/causes-and-actions.md +47 -0
  8. package/claude/skills/react-perf-recorder/references/from-scripts.md +36 -0
  9. package/claude/skills/react-perf-recorder/references/getting-a-recording.md +41 -0
  10. package/claude/skills/react-perf-recorder/references/measuring-a-fix.md +42 -0
  11. package/claude/skills/react-perf-recorder/references/panel.md +21 -0
  12. package/claude/skills/react-perf-recorder/references/reading-a-recording.md +55 -0
  13. package/dist/browser/chunk-7DJUCCWG.js +447 -0
  14. package/dist/browser/chunk-NTY2W4HE.js +182 -0
  15. package/dist/browser/client.d.ts +589 -0
  16. package/dist/browser/client.js +7161 -0
  17. package/dist/browser/index-BlkKhwHe.d.ts +585 -0
  18. package/dist/browser/plugins/proxy-memoize.d.ts +7 -0
  19. package/dist/browser/plugins/proxy-memoize.js +41 -0
  20. package/dist/browser/plugins/react-query.d.ts +5 -0
  21. package/dist/browser/plugins/react-query.js +74 -0
  22. package/dist/browser/plugins/zustand.d.ts +11 -0
  23. package/dist/browser/plugins/zustand.js +171 -0
  24. package/dist/browser/runtime.d.ts +1 -0
  25. package/dist/browser/runtime.js +12 -0
  26. package/dist/cli.js +23119 -0
  27. package/dist/engine.iife.js +3661 -0
  28. package/dist/node/chunk-HS2BJBJX.js +170 -0
  29. package/dist/node/plugin-api-zXFxjYba.d.cts +61 -0
  30. package/dist/node/plugin-api-zXFxjYba.d.ts +61 -0
  31. package/dist/node/plugins/proxy-memoize.cjs +214 -0
  32. package/dist/node/plugins/proxy-memoize.d.cts +16 -0
  33. package/dist/node/plugins/proxy-memoize.d.ts +16 -0
  34. package/dist/node/plugins/proxy-memoize.js +51 -0
  35. package/dist/node/plugins/react-query.cjs +32 -0
  36. package/dist/node/plugins/react-query.d.cts +6 -0
  37. package/dist/node/plugins/react-query.d.ts +6 -0
  38. package/dist/node/plugins/react-query.js +7 -0
  39. package/dist/node/plugins/zustand.cjs +247 -0
  40. package/dist/node/plugins/zustand.d.cts +17 -0
  41. package/dist/node/plugins/zustand.d.ts +17 -0
  42. package/dist/node/plugins/zustand.js +61 -0
  43. package/dist/node/vite.cjs +1262 -0
  44. package/dist/node/vite.d.cts +103 -0
  45. package/dist/node/vite.d.ts +103 -0
  46. package/dist/node/vite.js +1072 -0
  47. package/docs/contributing.md +24 -0
  48. package/docs/how-it-works.md +34 -0
  49. package/docs/mcp.md +62 -0
  50. package/docs/measuring-a-fix.md +65 -0
  51. package/docs/options.md +22 -0
  52. package/docs/panel.md +59 -0
  53. package/docs/plugins.md +57 -0
  54. package/docs/recording.md +44 -0
  55. package/package.json +139 -0
@@ -0,0 +1,447 @@
1
+ // src/core/stack.ts
2
+ var V8_FRAME = /^\s*at (?:(?:async )?(.+?) \()?(.+?):(\d+):(\d+)\)?\s*$/;
3
+ var GECKO_FRAME = /^\s*(.*?)@(.+?):(\d+):(\d+)\s*$/;
4
+ var servedPath = (url) => url.replace(/^[a-z]+:\/\/[^/]+/, "").split(/[?#]/)[0].replace(/^\//, "");
5
+ function parseStack(stack) {
6
+ const frames = [];
7
+ for (const line of stack.split("\n")) {
8
+ const m = V8_FRAME.exec(line) ?? GECKO_FRAME.exec(line);
9
+ if (!m) continue;
10
+ frames.push({ fn: (m[1] ?? "").replace(/^Object\./, "").replace(/ \[as .+\]$/, ""), url: m[2], line: Number(m[3]), column: Number(m[4]) });
11
+ }
12
+ return frames;
13
+ }
14
+ var OWN = (() => {
15
+ const url = (parseStack(new Error().stack ?? "")[0]?.url ?? "").split(/[?#]/)[0];
16
+ const at = Math.max(url.lastIndexOf("/dist/"), url.lastIndexOf("/src/"));
17
+ return at >= 0 ? [`${url.slice(0, at)}/dist/`, `${url.slice(0, at)}/src/`] : [];
18
+ })();
19
+ var packageOf = (path) => {
20
+ const parts = path.split("/").filter(Boolean);
21
+ return parts[0]?.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0] ?? "";
22
+ };
23
+ function libraryOf(url) {
24
+ const path = url.split(/[?#]/)[0];
25
+ if (OWN.some((prefix) => path.startsWith(prefix))) return "react-perf-recorder";
26
+ const deps = /\/deps\/([^/]+)\.js$/.exec(path);
27
+ if (deps && (url.includes("?v=") || path.includes("/.vite"))) return deps[1].startsWith("chunk-") ? "" : packageOf(deps[1].replace(/_/g, "/"));
28
+ const at = path.lastIndexOf("/node_modules/");
29
+ if (at >= 0) return packageOf(path.slice(at + "/node_modules/".length));
30
+ return null;
31
+ }
32
+
33
+ // src/core/react-compat.ts
34
+ var BOTTOM_FRAME = /react_stack_bottom_frame/;
35
+ var ownerStackSites = /* @__PURE__ */ new WeakMap();
36
+ var sawSite = false;
37
+ var sawFiberWithoutSite = false;
38
+ function siteOf(f) {
39
+ const legacy = f._debugSource;
40
+ if (legacy?.fileName) {
41
+ sawSite = true;
42
+ return { url: legacy.fileName, line: legacy.lineNumber, column: legacy.columnNumber ?? 0, exact: true };
43
+ }
44
+ const stack = f._debugStack;
45
+ if (stack instanceof Error) {
46
+ const site = ownerStackSite(stack);
47
+ if (site) sawSite = true;
48
+ else sawFiberWithoutSite = true;
49
+ return site;
50
+ }
51
+ sawFiberWithoutSite = true;
52
+ return null;
53
+ }
54
+ function ownerStackSite(error) {
55
+ const known2 = ownerStackSites.get(error);
56
+ if (known2 !== void 0) return known2;
57
+ const holder = Error;
58
+ const previous = holder.prepareStackTrace;
59
+ let text = "";
60
+ try {
61
+ holder.prepareStackTrace = void 0;
62
+ text = error.stack ?? "";
63
+ } catch {
64
+ text = "";
65
+ } finally {
66
+ holder.prepareStackTrace = previous;
67
+ }
68
+ const frames = parseStack(text);
69
+ const frame = frames[1];
70
+ const site = frame && !BOTTOM_FRAME.test(frame.fn) ? { url: frame.url, line: frame.line, column: frame.column, exact: false } : null;
71
+ ownerStackSites.set(error, site);
72
+ return site;
73
+ }
74
+ function sourcesUnavailable() {
75
+ return sawFiberWithoutSite && !sawSite;
76
+ }
77
+ var ContextProviderTag = 10;
78
+ var ContextConsumerTag = 9;
79
+ var isProviderTag = (tag) => tag === ContextProviderTag;
80
+ var isConsumerTag = (tag) => tag === ContextConsumerTag;
81
+ function contextOf(f) {
82
+ const type = f.type;
83
+ return type?._context ?? type ?? null;
84
+ }
85
+ var HOOKS_WITHOUT_CELLS = /* @__PURE__ */ new Set(["useContext", "useDebugValue", "use", "useMemoCache", "useHostTransitionStatus", "useFormStatus"]);
86
+ var HOOK_CELLS = {
87
+ useSyncExternalStore: 2,
88
+ useTransition: 2,
89
+ useActionState: 3,
90
+ useFormState: 3
91
+ };
92
+ var hookCells = (type) => HOOKS_WITHOUT_CELLS.has(type) ? 0 : HOOK_CELLS[type] ?? 1;
93
+ var LANES_18 = [
94
+ [1, "Sync"],
95
+ [2, "InputContinuousHydration"],
96
+ [4, "InputContinuous"],
97
+ [8, "DefaultHydration"],
98
+ [16, "Default"],
99
+ [32, "TransitionHydration"],
100
+ [4194240, "Transition"],
101
+ [31 << 22, "Retry"],
102
+ [1 << 27, "SelectiveHydration"],
103
+ [1 << 28, "IdleHydration"],
104
+ [1 << 29, "Idle"],
105
+ [1 << 30, "Offscreen"]
106
+ ];
107
+ var LANES_19 = [
108
+ [1, "SyncHydration"],
109
+ [2, "Sync"],
110
+ [4, "InputContinuousHydration"],
111
+ [8, "InputContinuous"],
112
+ [16, "DefaultHydration"],
113
+ [32, "Default"],
114
+ [64, "TransitionHydration"],
115
+ [261888, "Transition"],
116
+ [3932160, "TransitionDeferred"],
117
+ [62914560, "Retry"],
118
+ [1 << 26, "SelectiveHydration"],
119
+ [1 << 27, "IdleHydration"],
120
+ [1 << 28, "Idle"],
121
+ [1 << 29, "Offscreen"],
122
+ [1 << 30, "Deferred"]
123
+ ];
124
+ function laneLabel(lanes) {
125
+ if (!lanes) return void 0;
126
+ const lowest = lanes & -lanes;
127
+ for (const [mask, label] of majorVersion() >= 19 ? LANES_19 : LANES_18) if (lowest & mask) return label;
128
+ return `lane:${lowest}`;
129
+ }
130
+ var captured = /* @__PURE__ */ new Set();
131
+ function captureRenderers() {
132
+ const target = globalThis;
133
+ let hook = target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
134
+ if (!hook) {
135
+ const renderers = /* @__PURE__ */ new Map();
136
+ const noop = () => {
137
+ };
138
+ hook = {
139
+ renderers,
140
+ supportsFiber: true,
141
+ isDisabled: false,
142
+ inject(renderer2) {
143
+ const id = renderers.size + 1;
144
+ renderers.set(id, renderer2);
145
+ return id;
146
+ },
147
+ onCommitFiberRoot: noop,
148
+ onCommitFiberUnmount: noop,
149
+ onPostCommitFiberRoot: noop,
150
+ onScheduleFiberRoot: noop,
151
+ checkDCE: noop,
152
+ on: noop,
153
+ off: noop,
154
+ emit: noop,
155
+ sub: () => noop
156
+ };
157
+ target.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;
158
+ }
159
+ for (const renderer2 of hook.renderers?.values() ?? []) captured.add(renderer2);
160
+ const original = hook.inject;
161
+ if (typeof original === "function" && !original.rprCaptured) {
162
+ const inject = function(renderer2) {
163
+ captured.add(renderer2);
164
+ return original.call(this, renderer2);
165
+ };
166
+ inject.rprCaptured = true;
167
+ hook.inject = inject;
168
+ }
169
+ }
170
+ function knownRenderers() {
171
+ const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
172
+ return [...captured, ...hook?.renderers?.values() ?? []];
173
+ }
174
+ function reactVersion() {
175
+ return knownRenderers().find((r) => r.version)?.version ?? null;
176
+ }
177
+ function renderer() {
178
+ return knownRenderers().find((r) => r.currentDispatcherRef) ?? null;
179
+ }
180
+ function majorVersion() {
181
+ return Number.parseInt(reactVersion() ?? "", 10) || 0;
182
+ }
183
+
184
+ // src/core/sites.ts
185
+ var positionKey = (p) => `${p.url}:${p.line}:${p.column}`;
186
+ var known = /* @__PURE__ */ new Map();
187
+ var asked = /* @__PURE__ */ new Set();
188
+ var queue = /* @__PURE__ */ new Map();
189
+ var listeners = /* @__PURE__ */ new Set();
190
+ var mapper = null;
191
+ var inFlight = null;
192
+ var scheduled = null;
193
+ function setSiteMapper(fn) {
194
+ mapper = fn;
195
+ }
196
+ function onSitesMapped(listener) {
197
+ listeners.add(listener);
198
+ return () => listeners.delete(listener);
199
+ }
200
+ function mappedSite(position) {
201
+ const key = positionKey(position);
202
+ const site = known.get(key);
203
+ if (site) return site;
204
+ if (site === "" || !mapper) return void 0;
205
+ if (!asked.has(key)) {
206
+ asked.add(key);
207
+ queue.set(key, position);
208
+ schedule();
209
+ }
210
+ return void 0;
211
+ }
212
+ function schedule() {
213
+ if (scheduled || inFlight) return;
214
+ scheduled = setTimeout(() => {
215
+ scheduled = null;
216
+ void flush();
217
+ }, 0);
218
+ }
219
+ function flush() {
220
+ if (!mapper || !queue.size) return Promise.resolve();
221
+ const positions = [...queue.values()];
222
+ queue.clear();
223
+ const request = mapper(positions).then((sites) => {
224
+ for (const [key, site] of Object.entries(sites)) known.set(key, site);
225
+ for (const position of positions) if (!known.has(positionKey(position))) known.set(positionKey(position), "");
226
+ if (Object.keys(sites).length) for (const listener of listeners) listener();
227
+ }).catch(() => {
228
+ for (const position of positions) asked.delete(positionKey(position));
229
+ }).finally(() => {
230
+ inFlight = null;
231
+ if (queue.size) schedule();
232
+ });
233
+ inFlight = request;
234
+ return request;
235
+ }
236
+
237
+ // src/core/fiber.ts
238
+ var Tag = {
239
+ FunctionComponent: 0,
240
+ ClassComponent: 1,
241
+ IndeterminateComponent: 2,
242
+ HostRoot: 3,
243
+ HostPortal: 4,
244
+ HostComponent: 5,
245
+ HostText: 6,
246
+ ForwardRef: 11,
247
+ MemoComponent: 14,
248
+ SimpleMemoComponent: 15,
249
+ HostHoistable: 26,
250
+ HostSingleton: 27
251
+ };
252
+ var ProfileMode = 2;
253
+ var isHost = (f) => f.tag === Tag.HostComponent || f.tag === Tag.HostHoistable || f.tag === Tag.HostSingleton;
254
+ var isComposite = (f) => typeof f.type === "function" || Boolean(f.type?.render || f.type?.type);
255
+ var mountedInPlace = (f) => f.alternate === null && isComposite(f) && f.return !== null && f.return.alternate !== null && f.return.tag !== Tag.HostRoot;
256
+ var hasHooks = (f) => f.tag === Tag.FunctionComponent || f.tag === Tag.ForwardRef || f.tag === Tag.SimpleMemoComponent || f.tag === Tag.IndeterminateComponent;
257
+ var hasProfileTimings = (f) => (f.mode & ProfileMode) !== 0 && typeof f.actualDuration === "number";
258
+ function nameOf(f) {
259
+ if (isProviderTag(f.tag)) return `Provider(${contextOf(f)?.displayName || "context"})`;
260
+ if (isConsumerTag(f.tag)) return `Consumer(${contextOf(f)?.displayName || "context"})`;
261
+ const t = f.type;
262
+ if (t == null || typeof t === "string") return null;
263
+ if (typeof t === "function") return t.displayName || f.elementType?.displayName || t.name || "Anonymous";
264
+ if (typeof t === "object") {
265
+ if (t.displayName) return t.displayName;
266
+ if (t.render) return t.render.displayName || t.render.name || "ForwardRef";
267
+ if (t.type) return t.type.displayName || t.type.name || "Memo";
268
+ }
269
+ return null;
270
+ }
271
+ var isProvider = (name) => name.startsWith("Provider(");
272
+ function wrapsProvider(f) {
273
+ const child = currentOf(f).child;
274
+ return Boolean(child && !child.sibling && isProvider(nameOf(child) ?? ""));
275
+ }
276
+ var libraryByType = /* @__PURE__ */ new WeakMap();
277
+ function isLibraryFiber(f) {
278
+ const type = typeof f.type === "function" || f.type && typeof f.type === "object" ? f.type : null;
279
+ const known2 = type ? libraryByType.get(type) : void 0;
280
+ if (known2 !== void 0) return known2;
281
+ const library = definedInPackage(f);
282
+ if (type) libraryByType.set(type, library);
283
+ return library;
284
+ }
285
+ function definedInPackage(f) {
286
+ const site = siteOf(f.child ?? f);
287
+ return !site || libraryOf(site.url) !== null;
288
+ }
289
+ function sourceOf(f, root = "") {
290
+ const site = siteOf(f);
291
+ if (!site) return "";
292
+ const file = relativeFile(site.url, root);
293
+ if (site.exact) return `${file}:${site.line}`;
294
+ return mappedSite(site) || file;
295
+ }
296
+ function generatedSourceOf(f) {
297
+ const site = siteOf(f);
298
+ return site && !site.exact ? { url: site.url, line: site.line, column: site.column } : void 0;
299
+ }
300
+ function siteKeyOf(f, root = "") {
301
+ const site = siteOf(f);
302
+ return site ? `${relativeFile(site.url, root)}:${site.line}:${site.column}` : "";
303
+ }
304
+ function relativeFile(fileName, root = "") {
305
+ const file = fileName.replace(/^[a-z]+:\/\/[^/]+/, "").replace(/[?#].*$/, "");
306
+ if (root && file.startsWith(root)) return file.slice(root.length).replace(/^\/+/, "");
307
+ const i = file.lastIndexOf("/src/");
308
+ return i >= 0 ? file.slice(i + 1) : file.replace(/^\/+/, "").split("/").slice(-3).join("/");
309
+ }
310
+ var fiberKey = null;
311
+ function fiberFromNode(node) {
312
+ for (let el = node; el; el = el.parentNode) {
313
+ if (!fiberKey) fiberKey = Object.keys(el).find((k) => k.startsWith("__reactFiber$")) ?? null;
314
+ const fiber = fiberKey ? el[fiberKey] : void 0;
315
+ if (fiber) return fiber;
316
+ }
317
+ return null;
318
+ }
319
+ function eachFiber(visit) {
320
+ for (const root of findRoots()) {
321
+ const stack = [root.current];
322
+ while (stack.length) {
323
+ const f = stack.pop();
324
+ if (visit(f) === false) return;
325
+ if (f.sibling) stack.push(f.sibling);
326
+ if (f.child) stack.push(f.child);
327
+ }
328
+ }
329
+ }
330
+ function hostRootOf(f) {
331
+ let node = f;
332
+ while (node?.return) node = node.return;
333
+ return node?.tag === Tag.HostRoot ? node.stateNode : null;
334
+ }
335
+ var created = /* @__PURE__ */ new Set();
336
+ function registerRoot(root) {
337
+ created.add(new WeakRef(root));
338
+ }
339
+ function findRoots(doc = document) {
340
+ const roots = /* @__PURE__ */ new Set();
341
+ for (const ref of created) {
342
+ const root = ref.deref();
343
+ if (!root) created.delete(ref);
344
+ else if (root.containerInfo?.isConnected && root.current) roots.add(root);
345
+ }
346
+ const candidates = [doc.getElementById("root"), ...Array.from(doc.body?.children ?? [])];
347
+ for (const el of Array.from(doc.body?.children ?? [])) candidates.push(...Array.from(el.children));
348
+ for (const el of candidates) {
349
+ if (!el) continue;
350
+ const key = Object.keys(el).find((k) => k.startsWith("__reactContainer$"));
351
+ const container = key ? el[key] : void 0;
352
+ if (container?.stateNode) roots.add(container.stateNode);
353
+ }
354
+ return [...roots];
355
+ }
356
+ function nearestHosts(f, limit = 500) {
357
+ const out = [];
358
+ const stack = f.child ? [f.child] : [];
359
+ if (isHost(f)) return [f.stateNode];
360
+ while (stack.length && out.length < limit) {
361
+ const node = stack.pop();
362
+ if (node.sibling) stack.push(node.sibling);
363
+ if (isHost(node)) {
364
+ out.push(node.stateNode);
365
+ } else if (node.tag === Tag.HostText) {
366
+ const parent = node.stateNode.parentElement;
367
+ if (parent && !out.includes(parent)) out.push(parent);
368
+ } else if (node.child) {
369
+ stack.push(node.child);
370
+ }
371
+ }
372
+ return out;
373
+ }
374
+ function compositeChain(f) {
375
+ const chain = [];
376
+ for (let node = f; node; node = node.return) if (isComposite(node)) chain.push(node);
377
+ return chain.reverse();
378
+ }
379
+ function currentOf(f) {
380
+ let top = f;
381
+ while (top.return) top = top.return;
382
+ if (top.tag !== Tag.HostRoot || top.stateNode?.current === top) return f;
383
+ return f.alternate ?? f;
384
+ }
385
+ function compositeChildren(f, skip, limit = 100) {
386
+ const out = [];
387
+ const stack = [];
388
+ const first = currentOf(f).child;
389
+ if (first) stack.push(first);
390
+ while (stack.length && out.length < limit) {
391
+ const node = stack.pop();
392
+ if (node.sibling) stack.push(node.sibling);
393
+ if (isComposite(node) && !skip(node)) out.push(node);
394
+ else if (node.child) stack.push(node.child);
395
+ }
396
+ return out;
397
+ }
398
+
399
+ // src/core/roots-notify.ts
400
+ var listeners2 = /* @__PURE__ */ new Set();
401
+ function noteRoot(root) {
402
+ if (root?._internalRoot) registerRoot(root._internalRoot);
403
+ listeners2.forEach((listener) => listener());
404
+ }
405
+ function onRootCreated(listener) {
406
+ listeners2.add(listener);
407
+ return () => listeners2.delete(listener);
408
+ }
409
+
410
+ export {
411
+ servedPath,
412
+ parseStack,
413
+ libraryOf,
414
+ sourcesUnavailable,
415
+ isProviderTag,
416
+ isConsumerTag,
417
+ hookCells,
418
+ laneLabel,
419
+ captureRenderers,
420
+ reactVersion,
421
+ renderer,
422
+ setSiteMapper,
423
+ onSitesMapped,
424
+ Tag,
425
+ isHost,
426
+ isComposite,
427
+ mountedInPlace,
428
+ hasHooks,
429
+ hasProfileTimings,
430
+ nameOf,
431
+ isProvider,
432
+ wrapsProvider,
433
+ isLibraryFiber,
434
+ sourceOf,
435
+ generatedSourceOf,
436
+ siteKeyOf,
437
+ fiberFromNode,
438
+ eachFiber,
439
+ hostRootOf,
440
+ findRoots,
441
+ nearestHosts,
442
+ compositeChain,
443
+ currentOf,
444
+ compositeChildren,
445
+ noteRoot,
446
+ onRootCreated
447
+ };
@@ -0,0 +1,182 @@
1
+ import {
2
+ libraryOf,
3
+ parseStack,
4
+ servedPath
5
+ } from "./chunk-7DJUCCWG.js";
6
+
7
+ // src/runtime/memo.ts
8
+ var MAX_DISTINCT = 64;
9
+ function createdAt(rec) {
10
+ const frame = parseStack(rec.created?.stack ?? "").slice(1).find((f) => libraryOf(f.url) === null);
11
+ if (!frame) return { name: rec.kind, file: "" };
12
+ const file = servedPath(frame.url).replace(/^src\//, "");
13
+ const fn = /^[A-Za-z_$][\w$]*$/.test(frame.fn) ? frame.fn : "";
14
+ return { name: `${rec.kind} in ${fn ? `${fn} \xB7 ` : ""}${file.split("/").pop()}`, file };
15
+ }
16
+ function createMemoInstrumentation() {
17
+ const state = { on: false, depth: 0 };
18
+ const byFn = /* @__PURE__ */ new WeakMap();
19
+ const live = /* @__PURE__ */ new Set();
20
+ const objectIds = /* @__PURE__ */ new WeakMap();
21
+ let nextId = 1;
22
+ const keyOf = (value) => {
23
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
24
+ const text = String(value);
25
+ return typeof value === "bigint" ? `${text}n` : text.slice(0, 40);
26
+ }
27
+ let id = objectIds.get(value);
28
+ if (!id) objectIds.set(value, id = nextId++);
29
+ return `#${id}`;
30
+ };
31
+ const argsKey = (args) => {
32
+ let key = "";
33
+ for (let i = 1; i < args.length; i++) key += `${i > 1 ? "|" : ""}${keyOf(args[i])}`;
34
+ return key;
35
+ };
36
+ const instrument = (factory, kind, fnArg) => function(...args) {
37
+ let index = typeof fnArg === "number" ? fnArg : -1;
38
+ if (fnArg === "lastFunction") {
39
+ for (let i = args.length - 1; i >= 0 && index < 0; i--) if (typeof args[i] === "function") index = i;
40
+ }
41
+ const fn = args[index];
42
+ if (typeof fn !== "function") return factory.apply(this, args);
43
+ const options = args[index + 1];
44
+ const size = options && typeof options === "object" && typeof options.size === "number" ? options.size : 1;
45
+ const rec = {
46
+ name: "",
47
+ file: "",
48
+ kind,
49
+ size,
50
+ calls: 0,
51
+ recomputes: 0,
52
+ nestedCalls: 0,
53
+ recomputesOnArgSwitch: 0,
54
+ distinct: /* @__PURE__ */ new Set(),
55
+ lastKey: null,
56
+ evictions: 0,
57
+ storedAt: /* @__PURE__ */ new Map(),
58
+ created: new Error()
59
+ };
60
+ const inner = function(...innerArgs) {
61
+ if (state.on) rec.recomputes++;
62
+ return fn.apply(this, innerArgs);
63
+ };
64
+ const wrappedArgs = args.slice();
65
+ wrappedArgs[index] = inner;
66
+ const memo = factory.apply(this, wrappedArgs);
67
+ if (typeof memo !== "function") return memo;
68
+ const outer = function(...callArgs) {
69
+ if (!state.on) return memo.apply(this, callArgs);
70
+ rec.calls++;
71
+ const nested = state.depth > 0;
72
+ if (nested) rec.nestedCalls++;
73
+ const before = rec.recomputes;
74
+ state.depth++;
75
+ try {
76
+ return memo.apply(this, callArgs);
77
+ } finally {
78
+ state.depth--;
79
+ if (!nested) {
80
+ const key = argsKey(callArgs);
81
+ if (rec.recomputes > before) {
82
+ if (rec.lastKey !== null && key !== rec.lastKey) rec.recomputesOnArgSwitch++;
83
+ const stored = rec.storedAt.get(key);
84
+ if (stored !== void 0 && before - stored >= rec.size) rec.evictions++;
85
+ if (rec.storedAt.size < MAX_DISTINCT || rec.storedAt.has(key)) rec.storedAt.set(key, rec.recomputes);
86
+ }
87
+ rec.lastKey = key;
88
+ if (rec.distinct.size < MAX_DISTINCT) rec.distinct.add(key);
89
+ }
90
+ }
91
+ };
92
+ Object.assign(outer, memo);
93
+ byFn.set(outer, rec);
94
+ live.add(new WeakRef(outer));
95
+ return outer;
96
+ };
97
+ const records = () => {
98
+ const out = [];
99
+ for (const ref of live) {
100
+ const fn = ref.deref();
101
+ if (!fn) live.delete(ref);
102
+ else out.push(byFn.get(fn));
103
+ }
104
+ return out;
105
+ };
106
+ return {
107
+ instrument,
108
+ get recording() {
109
+ return state.on;
110
+ },
111
+ get used() {
112
+ return records().length > 0;
113
+ },
114
+ name(fn, name, file) {
115
+ const rec = typeof fn === "function" ? byFn.get(fn) : void 0;
116
+ if (rec) Object.assign(rec, { name, file });
117
+ },
118
+ label(fn) {
119
+ return byFn.get(fn)?.name || null;
120
+ },
121
+ start() {
122
+ for (const rec of records())
123
+ Object.assign(rec, {
124
+ calls: 0,
125
+ recomputes: 0,
126
+ nestedCalls: 0,
127
+ recomputesOnArgSwitch: 0,
128
+ distinct: /* @__PURE__ */ new Set(),
129
+ lastKey: null,
130
+ evictions: 0,
131
+ storedAt: /* @__PURE__ */ new Map()
132
+ });
133
+ state.on = true;
134
+ state.depth = 0;
135
+ },
136
+ stop() {
137
+ state.on = false;
138
+ const merged = /* @__PURE__ */ new Map();
139
+ for (const rec of records()) {
140
+ if (!rec.calls) continue;
141
+ const { name, file } = rec.name ? rec : createdAt(rec);
142
+ const key = `${name}|${file}`;
143
+ const stat = merged.get(key) ?? {
144
+ name,
145
+ file,
146
+ kind: rec.kind,
147
+ size: rec.size,
148
+ calls: 0,
149
+ recomputes: 0,
150
+ hitRate: 0,
151
+ nestedCalls: 0,
152
+ recomputesOnArgSwitch: 0,
153
+ distinctArgs: 0,
154
+ evictions: 0,
155
+ evicting: false
156
+ };
157
+ stat.calls += rec.calls;
158
+ stat.recomputes += rec.recomputes;
159
+ stat.nestedCalls += rec.nestedCalls;
160
+ stat.recomputesOnArgSwitch += rec.recomputesOnArgSwitch;
161
+ stat.evictions += rec.evictions;
162
+ stat.distinctArgs = Math.max(stat.distinctArgs, rec.distinct.size);
163
+ merged.set(key, stat);
164
+ }
165
+ return [...merged.values()].map((stat) => ({
166
+ ...stat,
167
+ hitRate: stat.calls ? +(1 - stat.recomputes / stat.calls).toFixed(3) : 0,
168
+ // More argument sets than slots, recomputing as they alternate; or answers pushed out while still in use,
169
+ // which a ring with room for every argument set does too.
170
+ evicting: stat.recomputes >= 10 && (stat.recomputesOnArgSwitch / stat.recomputes >= 0.5 && stat.distinctArgs > stat.size || stat.evictions / stat.recomputes >= 0.25)
171
+ })).sort((a, b) => b.recomputes - a.recomputes);
172
+ }
173
+ };
174
+ }
175
+
176
+ // src/runtime/index.ts
177
+ var definePlugin = (factory) => factory;
178
+
179
+ export {
180
+ createMemoInstrumentation,
181
+ definePlugin
182
+ };