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,585 @@
1
+ declare const RECORDING_SCHEMA = "react-perf-recorder/recording";
2
+ declare const GLOBAL_KEY = "__REACT_PERF_RECORDER__";
3
+ type Primitive = string | number | boolean | null;
4
+ type JsonValue = Primitive | JsonValue[] | {
5
+ [key: string]: JsonValue;
6
+ };
7
+ type Conditions = Record<string, Primitive>;
8
+ interface PluginInfo {
9
+ name: string;
10
+ sectionVersion: number;
11
+ error?: string;
12
+ }
13
+ interface PluginSection<Data = unknown> {
14
+ version: number;
15
+ /** false: the plugin's library is not on the page, and the report leaves the plugin out. */
16
+ active?: boolean;
17
+ highlights?: string[];
18
+ metrics?: Record<string, {
19
+ value: number;
20
+ kind: 'count' | 'gauge';
21
+ }>;
22
+ data?: Data;
23
+ }
24
+ /**
25
+ * One render on the way down: the root first (`root` indexes the recording's roots, inside then outside), the
26
+ * component itself last. `reason` indexes `RecordingV2.reasons`; `skipped` stands for links left out of a long chain.
27
+ */
28
+ /** A link of the commits' cascade trees: the link above it (-1 for a root), who rendered, and why. */
29
+ interface ChainNodeInfo {
30
+ up: number;
31
+ name: string;
32
+ reason?: number;
33
+ /** For a root's link: the root, as `ChainLink.root`. */
34
+ root?: number;
35
+ }
36
+ interface ChainLink {
37
+ name: string;
38
+ reason?: number;
39
+ root?: number;
40
+ skipped?: number;
41
+ }
42
+ /** A useMemo or useCallback that recomputed on at least half of its component's renders. */
43
+ interface MemoHookStat {
44
+ component: string;
45
+ source?: string;
46
+ /** Cell index in the component's hook list: the same `#N` the reasons use. */
47
+ hook: number;
48
+ kind: 'useMemo' | 'useCallback';
49
+ /** Renders in which the hook had a value to keep; `recomputed` of them made a new one. */
50
+ renders: number;
51
+ recomputed: number;
52
+ /** No dependency array: it recomputes on every render by definition. */
53
+ noDeps?: true;
54
+ /** Per dependency position: how often it changed, and how often into a value with the same content. */
55
+ deps: Array<{
56
+ index: number;
57
+ changed: number;
58
+ sameContent: number;
59
+ }>;
60
+ info?: HookInfo;
61
+ }
62
+ interface HookInfo {
63
+ /** Primitive hook type from `_debugHookTypes`, e.g. `useSyncExternalStore`. */
64
+ type?: string;
65
+ /** Custom hooks from the component down to the primitive, e.g. `['useActivePositionsList', 'useSelector', 'SyncExternalStore']`. */
66
+ path?: string[];
67
+ /** npm package the chain enters, e.g. `zustand`; absent when every custom hook is app code. */
68
+ library?: string;
69
+ /** Index in `path` of the first hook of `library`: the package API the app called, e.g. `useStore`. */
70
+ libraryAt?: number;
71
+ /** Call site in the generated code; the dev server maps it to `site` and `code`. */
72
+ generated?: {
73
+ url: string;
74
+ line: number;
75
+ column: number;
76
+ };
77
+ site?: string;
78
+ code?: string;
79
+ /** For a useMemo or useCallback: its dependency list as the code writes it, `['items', 'filter']`. */
80
+ deps?: string[];
81
+ }
82
+ /** What made a component render, as fields rather than a sentence; `text` is the sentence, built from them. */
83
+ type ReasonKind = 'state' | 'store' | 'context' | 'props' | 'parent' | 'bailout' | 'unknown';
84
+ interface ReasonInfo {
85
+ i: number;
86
+ kind: ReasonKind;
87
+ /** Index in the hook list, for `state` and `store`; the key into `RootStat.hooks`. */
88
+ hook?: number;
89
+ /** The store behind an external-store hook, and the selector it was read with. */
90
+ store?: string;
91
+ selector?: string;
92
+ /** The context that changed, for `context`. */
93
+ context?: string;
94
+ /** Props that really changed, and props that are a new reference with the same content. */
95
+ changed?: string[];
96
+ sameRef?: string[];
97
+ /** The children element changed by reference. */
98
+ children?: true;
99
+ /** The parent rendered and handed over equal props: `memo` would have skipped this render. */
100
+ equal?: true;
101
+ /** A new value with the same content: a subscription bug rather than new data. */
102
+ sameContent?: true;
103
+ /** Older recordings carry the sentence; it is built from the fields above by `reasonText` when it is not there. */
104
+ text?: string;
105
+ }
106
+ interface RootStat {
107
+ key: string;
108
+ name: string;
109
+ source: string;
110
+ /** Call site in the generated code, when the source alone has no line; the dev server maps it into `source`. */
111
+ generatedSource?: {
112
+ url: string;
113
+ line: number;
114
+ column: number;
115
+ };
116
+ path: string;
117
+ hits: number;
118
+ instances: number;
119
+ cascade: number;
120
+ perHit: number;
121
+ medianGapMs: number | null;
122
+ firstAtMs: number;
123
+ lastAtMs: number;
124
+ /** `[reason id, how many hits]`; the ids index `RecordingV2.reasons`. */
125
+ reasons: Array<[number, number]>;
126
+ causes: Array<[string, number]>;
127
+ lanes: Array<[string, number]>;
128
+ /** Hits in which nothing in the root's DOM changed: the render was wasted. */
129
+ noDomChange: number;
130
+ renderMs?: number;
131
+ /** Components mounted under the root in its hits: a component declared in render or an unstable key remounts. */
132
+ mounts?: number;
133
+ hooks?: Record<string, HookInfo>;
134
+ /** Only for outside roots: renders inside the scope this root caused. */
135
+ scopeRenders?: number;
136
+ }
137
+ interface ScopeInfo {
138
+ name: string;
139
+ source: string;
140
+ path: string[];
141
+ state: 'attached' | 'lost';
142
+ remounts: number;
143
+ lostAtMs: number[];
144
+ }
145
+ interface Totals {
146
+ commits: number;
147
+ commitsInScope: number;
148
+ renders: number;
149
+ mounts: number;
150
+ rendersPerCommit: number;
151
+ rendersPerScopeCommit: number;
152
+ rendersFromOutside: number;
153
+ rendersWithoutDom: number;
154
+ domTextChanges: number;
155
+ causesDropped: number;
156
+ lanes: Record<string, number>;
157
+ }
158
+ interface CauseStat {
159
+ /** Id: what a commit references instead of repeating the key. */
160
+ i: number;
161
+ key: string;
162
+ plugin: string;
163
+ type: string;
164
+ events: number;
165
+ commits: number;
166
+ keys?: Record<string, {
167
+ changed: number;
168
+ sameContent: number;
169
+ unknown: number;
170
+ }>;
171
+ }
172
+ type ActionKind = 'click' | 'typing' | 'change' | 'key' | 'submit' | 'scroll' | 'navigation';
173
+ interface ActionTarget {
174
+ testId?: string;
175
+ name?: string;
176
+ label?: string;
177
+ tag: string;
178
+ role?: string;
179
+ text?: string;
180
+ /** The component that rendered the element, where it was written, and the app's components above it. */
181
+ component?: string;
182
+ source?: string;
183
+ /** React 19: the built position of the component, which the dev server maps into `source` when saving. */
184
+ generatedSource?: {
185
+ url: string;
186
+ line: number;
187
+ column: number;
188
+ };
189
+ path?: string[];
190
+ /** Enough to find the element again: a selector, and which one it is among its like-named siblings. */
191
+ selector?: string;
192
+ nth?: number;
193
+ /** Where the click landed, and the box of the element at that moment. */
194
+ point?: {
195
+ x: number;
196
+ y: number;
197
+ };
198
+ box?: {
199
+ x: number;
200
+ y: number;
201
+ w: number;
202
+ h: number;
203
+ };
204
+ /** The element's own state when it was acted on. */
205
+ id?: string;
206
+ href?: string;
207
+ disabled?: true;
208
+ checked?: boolean;
209
+ inScope?: boolean;
210
+ }
211
+ interface ActionRecord {
212
+ id: number;
213
+ kind: ActionKind;
214
+ atMs: number;
215
+ endMs: number;
216
+ target?: ActionTarget;
217
+ key?: string;
218
+ /** Characters typed, for `typing`. */
219
+ chars?: number;
220
+ /** Value length after the action; the value itself only with `actions.values`. */
221
+ length?: number;
222
+ value?: string;
223
+ secret?: boolean;
224
+ scroll?: {
225
+ from: number;
226
+ to: number;
227
+ pixels: number;
228
+ };
229
+ url?: string;
230
+ /** The commits that followed it, by id; empty when the action changed nothing. */
231
+ commitIds?: number[];
232
+ }
233
+ interface LatencyEntry {
234
+ atMs: number;
235
+ type: string;
236
+ duration: number;
237
+ inputDelay: number;
238
+ processing: number;
239
+ presentation: number;
240
+ interactionId: number;
241
+ }
242
+ interface LongFrame {
243
+ atMs: number;
244
+ duration: number;
245
+ blocking: number;
246
+ commits: number;
247
+ scripts: Array<{
248
+ invoker: string;
249
+ source: string;
250
+ duration: number;
251
+ layout: number;
252
+ own?: boolean;
253
+ }>;
254
+ }
255
+ interface Segment {
256
+ action: number;
257
+ /** Internal wiring while a recording is built; the saved recording keeps these on the action itself. */
258
+ commitIds?: number[];
259
+ atMs: number;
260
+ durationMs: number;
261
+ commits: number;
262
+ renders: number;
263
+ reaction: {
264
+ commits: number;
265
+ renders: number;
266
+ };
267
+ background: {
268
+ commits: number;
269
+ renders: number;
270
+ };
271
+ perChar?: {
272
+ commits: number;
273
+ renders: number;
274
+ maxRenders: number;
275
+ };
276
+ topRoots: Array<[number, number]>;
277
+ latency?: LatencyEntry;
278
+ longFrames: number;
279
+ maxFrameMs: number;
280
+ }
281
+ /**
282
+ * One commit, with everything that points at it: the action and the causes that led to it, the roots that rendered
283
+ * in it and why. This is what a timeline is drawn from — a bar per commit, a marker per action.
284
+ */
285
+ interface CommitRecord {
286
+ i: number;
287
+ atMs: number;
288
+ /** Milliseconds since the previous commit. */
289
+ sinceMs?: number;
290
+ /** React render time of the commit's cascade roots, when the build has profile timings. */
291
+ ms?: number;
292
+ lane?: string;
293
+ /** The DOM event being dispatched when React was told, e.g. `click`. */
294
+ event?: string;
295
+ /** The action this commit answered, and the causes that claimed it. */
296
+ actionId?: number;
297
+ causeIds?: number[];
298
+ /** Renders in scope (or everywhere without a scope), and how many changed nothing in the DOM. */
299
+ renders: number;
300
+ noDom?: number;
301
+ outside?: number;
302
+ mounts?: number;
303
+ /** Cascade roots of this commit: which root, how many of its instances, and why each rendered. */
304
+ roots?: Array<{
305
+ i: number;
306
+ hits: number;
307
+ reasonIds: number[];
308
+ }>;
309
+ /**
310
+ * The commit's cascade as a tree: `[link, renders]` into `RecordingV2.chainNodes`, the busiest links and every link
311
+ * above them. Absent in fast recordings.
312
+ */
313
+ ways?: Array<[number, number]>;
314
+ }
315
+ interface Navigation {
316
+ type: 'push' | 'replace' | 'pop';
317
+ atMs: number;
318
+ url: string;
319
+ sameUrl?: true;
320
+ }
321
+ interface RecordingV2 {
322
+ schema: typeof RECORDING_SCHEMA;
323
+ version: 2;
324
+ id?: string;
325
+ createdAt: string;
326
+ label?: string;
327
+ partial?: boolean;
328
+ tool: {
329
+ version: string;
330
+ source: string;
331
+ plugins: PluginInfo[];
332
+ };
333
+ page: {
334
+ url: string;
335
+ title: string;
336
+ viewport: string;
337
+ dpr: number;
338
+ userAgent: string;
339
+ };
340
+ react: {
341
+ version: string | null;
342
+ roots: number;
343
+ profileTimings: boolean;
344
+ };
345
+ meta?: Record<string, Primitive>;
346
+ options: Record<string, JsonValue>;
347
+ startedAt: string;
348
+ durationMs: number;
349
+ scope: ScopeInfo | null;
350
+ totals: Totals;
351
+ roots: RootStat[];
352
+ outsideRoots: RootStat[];
353
+ /** Every rendered component; `reasons` covers renders caused by a parent too (`parent: props …`). */
354
+ components: Array<{
355
+ name: string;
356
+ renders: number;
357
+ mounts?: number;
358
+ /** A component of a package: the app's own come first in the list. */
359
+ library?: true;
360
+ /** An unnamed wrapper of the app or a component that only hands a context down: listed after the app's own. */
361
+ wrapper?: true;
362
+ withoutDom: number;
363
+ byParent: number;
364
+ memo?: true;
365
+ reasons: Array<[number, number]>;
366
+ /** Its most frequent ways down from a root, with how many renders came each way; only for renders a parent caused. */
367
+ chains?: Array<{
368
+ n: number;
369
+ links: ChainLink[];
370
+ }>;
371
+ /** Its parent-caused renders had their reasons worked out for a sample of the instances in a commit, not all. */
372
+ sampled?: true;
373
+ }>;
374
+ watch?: Record<string, {
375
+ mounted: number;
376
+ renders: number;
377
+ byRoot: Array<[number | null, number]>;
378
+ }>;
379
+ zones?: Record<string, {
380
+ renders: number;
381
+ mounted: number;
382
+ found: boolean;
383
+ }>;
384
+ causes: CauseStat[];
385
+ actions: ActionRecord[];
386
+ segments: Segment[];
387
+ /** Memo hooks that keep recomputing, worst first; absent when none do. */
388
+ memos?: MemoHookStat[];
389
+ latency: LatencyEntry[];
390
+ /** Every reason any root or component gave, once; everything else points here by id. */
391
+ reasons: ReasonInfo[];
392
+ commits: {
393
+ list: CommitRecord[];
394
+ truncated: boolean;
395
+ };
396
+ /** The links the commits' `ways` point at; only in the final recording. */
397
+ chainNodes?: ChainNodeInfo[];
398
+ bigCommits: number[];
399
+ frames: {
400
+ longTasks: {
401
+ count: number;
402
+ maxMs: number;
403
+ totalMs: number;
404
+ };
405
+ loaf: LongFrame[];
406
+ fps?: number;
407
+ };
408
+ dom: {
409
+ text: number;
410
+ attr?: number;
411
+ child?: number;
412
+ };
413
+ navigations: Navigation[];
414
+ hmr: Array<{
415
+ atMs: number;
416
+ type: string;
417
+ paths: string[];
418
+ }>;
419
+ conditions: Conditions;
420
+ conditionsChanged?: Record<string, [Primitive, Primitive]>;
421
+ plugins: Record<string, PluginSection>;
422
+ /** `highlight`: outlines were drawn during the recording, which adds to frame and long-task times. */
423
+ overhead: {
424
+ commitMs: number;
425
+ maxCommitMs: number;
426
+ overlayMs: number;
427
+ highlight?: boolean;
428
+ };
429
+ warnings: string[];
430
+ errors: string[];
431
+ }
432
+
433
+ interface ContextDependency {
434
+ context: {
435
+ displayName?: string;
436
+ };
437
+ memoizedValue: unknown;
438
+ next: ContextDependency | null;
439
+ }
440
+ interface Fiber {
441
+ tag: number;
442
+ key: string | null;
443
+ type: any;
444
+ elementType: any;
445
+ stateNode: any;
446
+ return: Fiber | null;
447
+ child: Fiber | null;
448
+ sibling: Fiber | null;
449
+ index: number;
450
+ alternate: Fiber | null;
451
+ memoizedProps: any;
452
+ memoizedState: any;
453
+ dependencies: {
454
+ firstContext: ContextDependency | null;
455
+ } | null;
456
+ mode: number;
457
+ flags: number;
458
+ lanes?: number;
459
+ childLanes?: number;
460
+ actualDuration?: number;
461
+ /** React 18 only: where the element was written. React 19.1 replaced it with `_debugStack`. */
462
+ _debugSource?: {
463
+ fileName: string;
464
+ lineNumber: number;
465
+ columnNumber?: number;
466
+ } | null;
467
+ /** React 19.1+: an owner stack whose second frame is where the element was written. */
468
+ _debugStack?: unknown;
469
+ _debugOwner?: Fiber | null;
470
+ _debugHookTypes?: string[] | null;
471
+ }
472
+ interface FiberRoot {
473
+ current: Fiber;
474
+ containerInfo: Element;
475
+ finishedLanes?: number;
476
+ pendingLanes?: number;
477
+ }
478
+
479
+ interface MemoStat {
480
+ name: string;
481
+ file: string;
482
+ kind: string;
483
+ size: number;
484
+ calls: number;
485
+ recomputes: number;
486
+ hitRate: number;
487
+ nestedCalls: number;
488
+ recomputesOnArgSwitch: number;
489
+ distinctArgs: number;
490
+ /**
491
+ * Recomputes for arguments whose answer was already pushed out: `size` other answers were stored since theirs.
492
+ * The cache is a ring, so this happens even with more slots than argument sets.
493
+ */
494
+ evictions: number;
495
+ /** A size-limited cache that keeps pushing out answers still in use: rows, cells or two forms sharing one selector. */
496
+ evicting: boolean;
497
+ }
498
+ interface MemoInstrumentation {
499
+ /** Wraps a memoizer factory (`memoize`, `memoizeWithArgs`, `createSelector`) so every memoized function it creates is counted. */
500
+ instrument<F extends (...args: any[]) => any>(factory: F, kind: string, fnArg: number | 'lastFunction'): F;
501
+ /** Names a memoized function; the build transform calls it after `const selectX = memoize(...)`. */
502
+ name(fn: unknown, name: string, file: string): void;
503
+ label(fn: Function): string | null;
504
+ /** Whether any memoized function made through `instrument` is still alive: the library is in use on the page. */
505
+ readonly used: boolean;
506
+ start(): void;
507
+ stop(): MemoStat[];
508
+ readonly recording: boolean;
509
+ }
510
+ /**
511
+ * Counting must not change memoization: arguments inside nested selector calls are proxy-compare proxies, and any
512
+ * property read would be recorded as a dependency. Only `typeof`, WeakMap lookups and `String()` of primitives touch them.
513
+ */
514
+ declare function createMemoInstrumentation(): MemoInstrumentation;
515
+
516
+ /**
517
+ * Called by the proxy of `react-dom/client` right after a root is created, before the app renders into it, with the
518
+ * root itself: it is found wherever the app mounted it, not only where a search of the page would look.
519
+ */
520
+ declare function noteRoot(root?: {
521
+ _internalRoot?: FiberRoot | null;
522
+ }): void;
523
+
524
+ interface CauseInput {
525
+ /** Shown as `<plugin>:<type>`, e.g. `markets/updateMarketsAmmState`. */
526
+ type: string;
527
+ /** Top-level keys the event changed; the core marks the ones with the same content and drops the values. */
528
+ changes?: Array<{
529
+ key: string;
530
+ prev: unknown;
531
+ next: unknown;
532
+ }>;
533
+ data?: Record<string, Primitive>;
534
+ /**
535
+ * Emitted after React was told of the update, so the recorder can see which components it woke.
536
+ * Leave it out when the event runs ahead of React, as a query cache or a navigation does.
537
+ */
538
+ aim?: true;
539
+ /**
540
+ * The library tells React later, from a timer of its own (react-query's notify batch): the event waits for that
541
+ * timer and goes to the components it updated. Needs the plugin's `packages`.
542
+ */
543
+ waitForTimer?: true;
544
+ /** Events with the same key still waiting are one: the later one's type replaces the earlier's. */
545
+ merge?: string;
546
+ }
547
+ interface PluginContext {
548
+ readonly recording: boolean;
549
+ /** Queues a cause for the next commit and returns it, so a later hook can refine `type`; null outside a recording. */
550
+ emitCause(event: CauseInput): {
551
+ type: string;
552
+ } | null;
553
+ /** Milliseconds since the recording started. */
554
+ now(): number;
555
+ warn(message: string): void;
556
+ }
557
+ interface SessionContext extends PluginContext {
558
+ scope: {
559
+ name: string;
560
+ source: string;
561
+ } | null;
562
+ /** One full walk of the committed tree; use for discovery at start, not per commit. */
563
+ findFibers(predicate: (fiber: Fiber) => boolean, limit?: number): Fiber[];
564
+ }
565
+ type DescribeKind = 'selector' | 'store';
566
+ interface RuntimePlugin<Data = unknown> {
567
+ name: string;
568
+ sectionVersion?: number;
569
+ /** npm packages whose timers deliver this plugin's `waitForTimer` events. */
570
+ packages?: string[];
571
+ /** Runs at page boot, before the app's modules. */
572
+ setup?(ctx: PluginContext): void;
573
+ /** Label for a store selector or a store (by its getSnapshot); `null` when the function is not the plugin's. */
574
+ describe?(fn: Function, kind: DescribeKind, next: (fn: Function) => string): string | null;
575
+ start?(session: SessionContext): void;
576
+ /** After each commit of a recording, for what mounts late (a lazy provider); keep it cheap. */
577
+ commit?(session: SessionContext): void;
578
+ stop?(session: SessionContext): PluginSection<Data> | void;
579
+ conditions?(): Conditions;
580
+ }
581
+ type RuntimePluginFactory<Options = any> = (options: Options) => RuntimePlugin;
582
+ /** Identity helper for typing; the module's default export is the factory. */
583
+ declare const definePlugin: <Options = void>(factory: (options: Options) => RuntimePlugin) => RuntimePluginFactory<Options>;
584
+
585
+ export { type ActionRecord as A, type CauseInput as C, type DescribeKind as D, type Fiber as F, GLOBAL_KEY as G, type HookInfo as H, type JsonValue as J, type MemoInstrumentation as M, type PluginInfo as P, type RuntimePlugin as R, type SessionContext as S, type RuntimePluginFactory as a, type PluginContext as b, type Primitive as c, type PluginSection as d, type Conditions as e, type RecordingV2 as f, type ReasonInfo as g, type RootStat as h, type MemoStat as i, createMemoInstrumentation as j, definePlugin as k, noteRoot as n };
@@ -0,0 +1,7 @@
1
+ import { a as RuntimePluginFactory } from '../index-BlkKhwHe.js';
2
+
3
+ declare const instrument: <F extends (...args: any[]) => any>(factory: F, kind: string, fnArg: number | "lastFunction") => F;
4
+ declare const nameMemoized: (fn: unknown, name: string, file: string) => void;
5
+ declare const _default: RuntimePluginFactory<void>;
6
+
7
+ export { _default as default, instrument, nameMemoized };
@@ -0,0 +1,41 @@
1
+ import {
2
+ createMemoInstrumentation,
3
+ definePlugin
4
+ } from "../chunk-NTY2W4HE.js";
5
+ import "../chunk-7DJUCCWG.js";
6
+
7
+ // src/plugins/proxy-memoize/runtime.ts
8
+ var memo = createMemoInstrumentation();
9
+ var instrument = memo.instrument;
10
+ var nameMemoized = memo.name;
11
+ var runtime_default = definePlugin(() => ({
12
+ name: "proxy-memoize",
13
+ describe: (fn, kind) => kind === "selector" ? memo.label(fn) : null,
14
+ start: () => memo.start(),
15
+ stop() {
16
+ const selectors = memo.stop();
17
+ const evicting = selectors.filter((s) => s.evicting);
18
+ return {
19
+ version: 1,
20
+ active: memo.used,
21
+ highlights: selectors.length ? [
22
+ ...evicting.slice(0, 5).map(
23
+ (s) => s.distinctArgs > s.size ? `${s.name}: ${s.recomputes}/${s.calls} recomputes, ${s.distinctArgs} argument sets > cache size ${s.size}` : `${s.name}: ${s.recomputes}/${s.calls} recomputes, ${s.evictions} after the answer was pushed out of cache size ${s.size} (${s.distinctArgs} argument sets)`
24
+ ),
25
+ ...selectors.filter((s) => !s.evicting).slice(0, 3).map((s) => `${s.name}: ${s.recomputes}/${s.calls} recomputes`)
26
+ ] : ["no selector calls during the recording"],
27
+ metrics: Object.fromEntries(
28
+ selectors.slice(0, 30).flatMap((s) => [
29
+ [`${s.name}.calls`, { value: s.calls, kind: "count" }],
30
+ [`${s.name}.recomputes`, { value: s.recomputes, kind: "count" }]
31
+ ])
32
+ ),
33
+ data: { selectors }
34
+ };
35
+ }
36
+ }));
37
+ export {
38
+ runtime_default as default,
39
+ instrument,
40
+ nameMemoized
41
+ };
@@ -0,0 +1,5 @@
1
+ import { a as RuntimePluginFactory } from '../index-BlkKhwHe.js';
2
+
3
+ declare const _default: RuntimePluginFactory<void>;
4
+
5
+ export { _default as default };