appdoctor-rn 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kelvin Ajayi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # App Doctor — React Native SDK
2
+
3
+ Lightweight performance and observability for React Native: screen timing, network latency, and a pluggable event pipeline with minimal setup.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @appdoctor/react-native
9
+ ```
10
+
11
+ Peer dependencies: `react`, `react-native`. Optional: `@react-navigation/native` for stack/tab navigation listeners.
12
+
13
+ ## Quick start
14
+
15
+ 1. Wrap your app with `AppDoctorProvider` and pass transports (for example `createConsoleTransport()`).
16
+ 2. For React Navigation, pass `createNavigationStateListener(client).listener` to `NavigationContainer` as `onStateChange` (see [Getting started](docs/getting-started.md)).
17
+ 3. Optionally use `useTrackScreen`, `useTrackRender`, `trackApi`, and `instrumentAxios` for extra coverage.
18
+
19
+ ```tsx
20
+ import { NavigationContainer } from "@react-navigation/native";
21
+ import {
22
+ AppDoctorProvider,
23
+ createConsoleTransport,
24
+ createNavigationStateListener,
25
+ useAppDoctor,
26
+ } from "@appdoctor/react-native";
27
+ import { useMemo, type ReactNode } from "react";
28
+
29
+ function AppShell({ children }: { children: ReactNode }) {
30
+ return (
31
+ <AppDoctorProvider
32
+ transports={[createConsoleTransport()]}
33
+ tags={{ env: __DEV__ ? "dev" : "prod" }}
34
+ context={{ platform: "react-native" }}
35
+ >
36
+ <NavWithListener>{children}</NavWithListener>
37
+ </AppDoctorProvider>
38
+ );
39
+ }
40
+
41
+ function NavWithListener({ children }: { children: ReactNode }) {
42
+ const client = useAppDoctor();
43
+ const onStateChange = useMemo(
44
+ () => createNavigationStateListener(client).listener,
45
+ [client],
46
+ );
47
+ return (
48
+ <NavigationContainer onStateChange={onStateChange}>
49
+ {children}
50
+ </NavigationContainer>
51
+ );
52
+ }
53
+ ```
54
+
55
+ ## Documentation
56
+
57
+ - [Getting started (Expo & bare RN)](docs/getting-started.md)
58
+ - [API reference](docs/api-reference.md)
59
+ - [Performance budget](docs/performance-budget.md)
60
+ - [v0.1 release checklist](docs/release-checklist-v0.1.md)
61
+
62
+ ## Scripts
63
+
64
+
65
+ | Script | Description |
66
+ | ------------------- | --------------------------------- |
67
+ | `npm run build` | Build `dist/` (CJS + ESM + types) |
68
+ | `npm test` | Run Vitest |
69
+ | `npm run lint` | ESLint |
70
+ | `npm run typecheck` | `tsc --noEmit` |
71
+
72
+
73
+ ## License
74
+
75
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,609 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/core/types.ts
7
+ var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
8
+ var DEFAULT_MAX_QUEUE_SIZE = 200;
9
+
10
+ // src/core/sampling.ts
11
+ function shouldSample(rate) {
12
+ if (rate >= 1) return true;
13
+ if (rate <= 0) return false;
14
+ return Math.random() < rate;
15
+ }
16
+
17
+ // src/core/client.ts
18
+ function now() {
19
+ return Date.now();
20
+ }
21
+ function newSessionId() {
22
+ return `${now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
23
+ }
24
+ var AppDoctorClient = class {
25
+ constructor(config = {}) {
26
+ this.queue = [];
27
+ this.fetchPatched = false;
28
+ this.sessionId = newSessionId();
29
+ this.config = {
30
+ enabled: config.enabled ?? true,
31
+ noop: config.noop ?? false,
32
+ sampleRate: config.sampleRate ?? 1,
33
+ flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
34
+ maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
35
+ instrumentFetch: config.instrumentFetch ?? true,
36
+ tags: config.tags,
37
+ context: config.context,
38
+ redactNetworkEvent: config.redactNetworkEvent
39
+ };
40
+ this.transports = [...config.transports ?? []];
41
+ if (this.config.enabled) {
42
+ this.startFlushTimer();
43
+ if (this.config.instrumentFetch && typeof globalThis.fetch === "function") {
44
+ this.patchFetch();
45
+ }
46
+ }
47
+ }
48
+ updateConfig(partial) {
49
+ if (partial.enabled !== void 0) {
50
+ this.config.enabled = partial.enabled;
51
+ }
52
+ if (partial.noop !== void 0) {
53
+ this.config.noop = partial.noop;
54
+ }
55
+ if (partial.sampleRate !== void 0) {
56
+ this.config.sampleRate = partial.sampleRate;
57
+ }
58
+ if (partial.flushIntervalMs !== void 0) {
59
+ this.config.flushIntervalMs = partial.flushIntervalMs;
60
+ this.restartFlushTimer();
61
+ }
62
+ if (partial.maxQueueSize !== void 0) {
63
+ this.config.maxQueueSize = partial.maxQueueSize;
64
+ }
65
+ if (partial.tags !== void 0) {
66
+ this.config.tags = partial.tags;
67
+ }
68
+ if (partial.context !== void 0) {
69
+ this.config.context = partial.context;
70
+ }
71
+ if (partial.instrumentFetch !== void 0) {
72
+ const next = partial.instrumentFetch;
73
+ this.config.instrumentFetch = next;
74
+ if (next && !this.fetchPatched) this.patchFetch();
75
+ if (!next && this.fetchPatched) this.restoreFetch();
76
+ }
77
+ if (partial.transports !== void 0) {
78
+ this.transports.length = 0;
79
+ this.transports.push(...partial.transports);
80
+ }
81
+ if (partial.redactNetworkEvent !== void 0) {
82
+ this.config.redactNetworkEvent = partial.redactNetworkEvent;
83
+ }
84
+ }
85
+ addTransport(transport) {
86
+ this.transports.push(transport);
87
+ }
88
+ emit(event) {
89
+ if (!this.config.enabled) return;
90
+ if (this.config.noop) return;
91
+ if (!shouldSample(this.config.sampleRate)) return;
92
+ const { tags: eventTags, ...payload } = event;
93
+ const tags = this.config.tags || eventTags ? { ...this.config.tags, ...eventTags } : void 0;
94
+ const full = {
95
+ ...payload,
96
+ timestamp: now(),
97
+ sessionId: this.sessionId,
98
+ tags,
99
+ context: this.config.context
100
+ };
101
+ this.enqueue(full);
102
+ }
103
+ enqueue(event) {
104
+ if (this.queue.length >= this.config.maxQueueSize) {
105
+ this.queue.shift();
106
+ }
107
+ this.queue.push(event);
108
+ }
109
+ captureError(message, context, err) {
110
+ const stack = err instanceof Error && typeof err.stack === "string" ? err.stack : void 0;
111
+ const sdkErr = {
112
+ name: "sdk_error",
113
+ message,
114
+ stack,
115
+ errorContext: context
116
+ };
117
+ this.emit(sdkErr);
118
+ }
119
+ async flush() {
120
+ if (this.queue.length === 0) return;
121
+ const batch = this.queue.splice(0, this.queue.length);
122
+ await Promise.all(
123
+ this.transports.map(async (t) => {
124
+ try {
125
+ await t.send(batch);
126
+ } catch (e) {
127
+ this.safeInternalError("transport.send failed", e);
128
+ }
129
+ })
130
+ );
131
+ }
132
+ async shutdown() {
133
+ if (this.flushTimer) {
134
+ clearInterval(this.flushTimer);
135
+ this.flushTimer = void 0;
136
+ }
137
+ this.restoreFetch();
138
+ await this.flush();
139
+ await Promise.all(
140
+ this.transports.map(async (t) => {
141
+ if (!t.shutdown) return;
142
+ try {
143
+ await t.shutdown();
144
+ } catch (e) {
145
+ this.safeInternalError("transport.shutdown failed", e);
146
+ }
147
+ })
148
+ );
149
+ }
150
+ startFlushTimer() {
151
+ this.flushTimer = setInterval(() => {
152
+ void this.flush();
153
+ }, this.config.flushIntervalMs);
154
+ if (typeof this.flushTimer === "object" && this.flushTimer && "unref" in this.flushTimer && typeof this.flushTimer.unref === "function") {
155
+ this.flushTimer.unref();
156
+ }
157
+ }
158
+ restartFlushTimer() {
159
+ if (this.flushTimer) clearInterval(this.flushTimer);
160
+ this.startFlushTimer();
161
+ }
162
+ safeInternalError(msg, err) {
163
+ const dev = typeof __DEV__ !== "undefined" ? __DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
164
+ if (dev) console.warn(`[AppDoctor] ${msg}`, err);
165
+ }
166
+ patchFetch() {
167
+ if (this.fetchPatched) return;
168
+ const g = globalThis;
169
+ this.originalFetch = g.fetch.bind(globalThis);
170
+ const originalFetch = this.originalFetch;
171
+ g.fetch = async (input, init) => {
172
+ const start = now();
173
+ let method = "GET";
174
+ let url = "";
175
+ try {
176
+ if (typeof input === "string") {
177
+ url = input;
178
+ } else if (input instanceof URL) {
179
+ url = input.href;
180
+ } else if (typeof Request !== "undefined" && input instanceof Request) {
181
+ url = input.url;
182
+ method = input.method;
183
+ }
184
+ if (init?.method) method = init.method;
185
+ } catch {
186
+ }
187
+ const urlForEvent = url || (typeof input === "string" ? input : input instanceof URL ? input.href : typeof Request !== "undefined" && input instanceof Request ? input.url : "[request]");
188
+ try {
189
+ const res = await originalFetch(input, init);
190
+ const durationMs = now() - start;
191
+ const event = this.applyNetworkRedaction({
192
+ method,
193
+ url: urlForEvent,
194
+ status: res.status,
195
+ durationMs,
196
+ success: true
197
+ });
198
+ this.emit({
199
+ name: "api_request",
200
+ ...event
201
+ });
202
+ return res;
203
+ } catch (e) {
204
+ const durationMs = now() - start;
205
+ const event = this.applyNetworkRedaction({
206
+ method,
207
+ url: urlForEvent,
208
+ durationMs,
209
+ success: false,
210
+ errorMessage: e instanceof Error ? e.message : String(e)
211
+ });
212
+ this.emit({
213
+ name: "api_request",
214
+ ...event
215
+ });
216
+ throw e;
217
+ }
218
+ };
219
+ this.fetchPatched = true;
220
+ }
221
+ restoreFetch() {
222
+ if (!this.fetchPatched || !this.originalFetch) return;
223
+ globalThis.fetch = this.originalFetch;
224
+ this.fetchPatched = false;
225
+ this.originalFetch = void 0;
226
+ }
227
+ applyNetworkRedaction(event) {
228
+ if (!this.config.redactNetworkEvent) return event;
229
+ const redacted = this.config.redactNetworkEvent(event);
230
+ return { ...event, ...redacted };
231
+ }
232
+ };
233
+
234
+ // src/transports/console-transport.ts
235
+ function createConsoleTransport(options = {}) {
236
+ const label = options.label ?? "AppDoctor";
237
+ const slowScreenThresholdMs = options.slowScreenThresholdMs ?? 1e3;
238
+ const slowApiThresholdMs = options.slowApiThresholdMs ?? 800;
239
+ function toHint(event) {
240
+ if (event.name === "screen_load" && event.phase === "ready" && event.durationMs >= slowScreenThresholdMs) {
241
+ return `Slow screen "${event.screen}" (${event.durationMs}ms). Check expensive effects and repeated renders.`;
242
+ }
243
+ if (event.name === "api_request" && event.durationMs >= slowApiThresholdMs) {
244
+ return `Slow API "${event.method} ${event.url}" (${event.durationMs}ms). Check server latency, payload size, and retry loops.`;
245
+ }
246
+ return void 0;
247
+ }
248
+ return {
249
+ send(events) {
250
+ if (events.length === 0) return;
251
+ for (const event of events) {
252
+ const hint = toHint(event);
253
+ if (hint) {
254
+ console.warn(`[${label}] ${hint}`);
255
+ }
256
+ }
257
+ console.log(`[${label}]`, events);
258
+ }
259
+ };
260
+ }
261
+
262
+ // src/transports/http-transport.ts
263
+ async function sleep(ms) {
264
+ await new Promise((r) => setTimeout(r, ms));
265
+ }
266
+ function createHttpTransport(options) {
267
+ const maxRetries = options.maxRetries ?? 3;
268
+ const initialBackoffMs = options.initialBackoffMs ?? 500;
269
+ return {
270
+ async send(events) {
271
+ if (events.length === 0) return;
272
+ const body = {
273
+ events: [...events],
274
+ ...options.getExtraBody?.() ?? {}
275
+ };
276
+ let attempt = 0;
277
+ let backoff = initialBackoffMs;
278
+ for (; ; ) {
279
+ try {
280
+ const res = await fetch(options.url, {
281
+ method: "POST",
282
+ headers: {
283
+ "Content-Type": "application/json",
284
+ ...options.headers
285
+ },
286
+ body: JSON.stringify(body)
287
+ });
288
+ if (res.ok) return;
289
+ if (res.status >= 400 && res.status < 500 && res.status !== 429) {
290
+ return;
291
+ }
292
+ } catch {
293
+ }
294
+ attempt += 1;
295
+ if (attempt > maxRetries) return;
296
+ await sleep(backoff);
297
+ backoff *= 2;
298
+ }
299
+ }
300
+ };
301
+ }
302
+
303
+ // src/navigation/route-name.ts
304
+ function getActiveRouteName(state) {
305
+ if (!state || typeof state !== "object") return void 0;
306
+ const s = state;
307
+ if (!Array.isArray(s.routes) || typeof s.index !== "number") return void 0;
308
+ const route = s.routes[s.index];
309
+ if (!route || typeof route !== "object") return void 0;
310
+ if (route.state !== void 0) return getActiveRouteName(route.state);
311
+ return typeof route.name === "string" ? route.name : void 0;
312
+ }
313
+
314
+ // src/navigation/create-navigation-listener.ts
315
+ function createNavigationStateListener(client) {
316
+ const timing = {};
317
+ return {
318
+ listener(state) {
319
+ const screen = getActiveRouteName(state);
320
+ if (!screen || screen === timing.lastScreen) return;
321
+ timing.lastScreen = screen;
322
+ const startedAt = Date.now();
323
+ client.emit({
324
+ name: "screen_load",
325
+ screen,
326
+ phase: "start",
327
+ durationMs: 0
328
+ });
329
+ queueMicrotask(() => {
330
+ if (timing.lastScreen !== screen) return;
331
+ client.emit({
332
+ name: "screen_load",
333
+ screen,
334
+ phase: "ready",
335
+ durationMs: Date.now() - startedAt
336
+ });
337
+ });
338
+ },
339
+ reset() {
340
+ timing.lastScreen = void 0;
341
+ }
342
+ };
343
+ }
344
+
345
+ // src/network/axios-instrumentation.ts
346
+ function getConfig(errorOrResponse) {
347
+ if (!errorOrResponse || typeof errorOrResponse !== "object") return void 0;
348
+ if ("config" in errorOrResponse && errorOrResponse.config && typeof errorOrResponse.config === "object") {
349
+ return errorOrResponse.config;
350
+ }
351
+ return void 0;
352
+ }
353
+ function getResponseMeta(response) {
354
+ if (!response || typeof response !== "object") return {};
355
+ const r = response;
356
+ const status = "status" in r && typeof r.status === "number" ? r.status : void 0;
357
+ const rawConfig = "config" in r ? r.config : void 0;
358
+ let method;
359
+ let url;
360
+ if (rawConfig !== null && typeof rawConfig === "object") {
361
+ if ("method" in rawConfig && typeof rawConfig.method === "string") {
362
+ method = rawConfig.method;
363
+ }
364
+ if ("url" in rawConfig && typeof rawConfig.url === "string") {
365
+ url = rawConfig.url;
366
+ }
367
+ }
368
+ return {
369
+ status,
370
+ method,
371
+ url
372
+ };
373
+ }
374
+ function instrumentAxios(axios, client, options = {}) {
375
+ function applyRedaction(event) {
376
+ const redacted = options.redactNetworkEvent?.(event);
377
+ return redacted ? { ...event, ...redacted } : event;
378
+ }
379
+ const starts = /* @__PURE__ */ new WeakMap();
380
+ const reqId = axios.interceptors.request.use((config) => {
381
+ if (config !== null && typeof config === "object") {
382
+ starts.set(config, Date.now());
383
+ }
384
+ return config;
385
+ });
386
+ const resId = axios.interceptors.response.use(
387
+ (response) => {
388
+ const cfg = getConfig(response);
389
+ if (cfg !== null && typeof cfg === "object") {
390
+ const start = starts.get(cfg);
391
+ if (start !== void 0) {
392
+ const { status, method, url } = getResponseMeta(response);
393
+ const event = applyRedaction({
394
+ method: (method ?? "GET").toUpperCase(),
395
+ url: url ?? "",
396
+ status,
397
+ durationMs: Date.now() - start,
398
+ success: true
399
+ });
400
+ client.emit({
401
+ name: "api_request",
402
+ ...event
403
+ });
404
+ starts.delete(cfg);
405
+ }
406
+ }
407
+ return response;
408
+ },
409
+ (error) => {
410
+ const cfg = getConfig(error);
411
+ if (cfg !== null && typeof cfg === "object") {
412
+ const start = starts.get(cfg);
413
+ if (start !== void 0) {
414
+ let method = "GET";
415
+ let url = "";
416
+ let status;
417
+ let errorMessage = String(error);
418
+ if (error !== null && typeof error === "object") {
419
+ if ("message" in error && typeof error.message === "string") {
420
+ errorMessage = error.message;
421
+ }
422
+ const resp = "response" in error && error.response !== null && typeof error.response === "object" ? error.response : void 0;
423
+ if (resp && "status" in resp && typeof resp.status === "number") {
424
+ status = resp.status;
425
+ }
426
+ const errCfg = "config" in error && error.config !== null && typeof error.config === "object" ? error.config : void 0;
427
+ if (errCfg) {
428
+ if ("method" in errCfg && typeof errCfg.method === "string") {
429
+ method = errCfg.method.toUpperCase();
430
+ }
431
+ if ("url" in errCfg && typeof errCfg.url === "string") {
432
+ url = errCfg.url;
433
+ }
434
+ }
435
+ }
436
+ const event = applyRedaction({
437
+ method,
438
+ url,
439
+ status,
440
+ durationMs: Date.now() - start,
441
+ success: false,
442
+ errorMessage
443
+ });
444
+ client.emit({
445
+ name: "api_request",
446
+ ...event
447
+ });
448
+ starts.delete(cfg);
449
+ }
450
+ }
451
+ const rejectReason = error instanceof Error ? error : new Error(String(error));
452
+ return Promise.reject(rejectReason);
453
+ }
454
+ );
455
+ return () => {
456
+ axios.interceptors.request.eject(reqId);
457
+ axios.interceptors.response.eject(resId);
458
+ };
459
+ }
460
+
461
+ // src/utils/track-api.ts
462
+ async function trackApi(client, label, fn) {
463
+ const start = Date.now();
464
+ try {
465
+ const result = await fn();
466
+ client.emit({
467
+ name: "api_request",
468
+ method: "TRACK",
469
+ url: label,
470
+ status: 200,
471
+ durationMs: Date.now() - start,
472
+ success: true
473
+ });
474
+ return result;
475
+ } catch (e) {
476
+ client.emit({
477
+ name: "api_request",
478
+ method: "TRACK",
479
+ url: label,
480
+ durationMs: Date.now() - start,
481
+ success: false,
482
+ errorMessage: e instanceof Error ? e.message : String(e)
483
+ });
484
+ throw e;
485
+ }
486
+ }
487
+ var AppDoctorContext = react.createContext(null);
488
+ function AppDoctorProvider({
489
+ children,
490
+ ...config
491
+ }) {
492
+ const clientRef = react.useRef(null);
493
+ if (!clientRef.current) {
494
+ clientRef.current = new AppDoctorClient(config);
495
+ }
496
+ const {
497
+ enabled,
498
+ noop,
499
+ sampleRate,
500
+ flushIntervalMs,
501
+ maxQueueSize,
502
+ instrumentFetch,
503
+ tags,
504
+ context,
505
+ transports,
506
+ redactNetworkEvent
507
+ } = config;
508
+ react.useEffect(() => {
509
+ clientRef.current?.updateConfig({
510
+ enabled,
511
+ noop,
512
+ sampleRate,
513
+ flushIntervalMs,
514
+ maxQueueSize,
515
+ instrumentFetch,
516
+ tags,
517
+ context,
518
+ transports,
519
+ redactNetworkEvent
520
+ });
521
+ }, [
522
+ enabled,
523
+ noop,
524
+ sampleRate,
525
+ flushIntervalMs,
526
+ maxQueueSize,
527
+ instrumentFetch,
528
+ tags,
529
+ context,
530
+ transports,
531
+ redactNetworkEvent
532
+ ]);
533
+ react.useEffect(() => {
534
+ return () => {
535
+ void clientRef.current?.shutdown();
536
+ clientRef.current = null;
537
+ };
538
+ }, []);
539
+ return /* @__PURE__ */ jsxRuntime.jsx(AppDoctorContext.Provider, { value: clientRef.current, children });
540
+ }
541
+ function useAppDoctor() {
542
+ const client = react.useContext(AppDoctorContext);
543
+ if (!client) {
544
+ throw new Error("useAppDoctor must be used within AppDoctorProvider");
545
+ }
546
+ return client;
547
+ }
548
+ function useTrackScreen(screen, options = {}) {
549
+ const fromContext = react.useContext(AppDoctorContext);
550
+ const client = options.client ?? fromContext;
551
+ if (!client) {
552
+ throw new Error(
553
+ "useTrackScreen requires AppDoctorProvider or options.client"
554
+ );
555
+ }
556
+ react.useEffect(() => {
557
+ const startedAt = Date.now();
558
+ client.emit({
559
+ name: "screen_load",
560
+ screen,
561
+ phase: "start",
562
+ durationMs: 0
563
+ });
564
+ queueMicrotask(() => {
565
+ client.emit({
566
+ name: "screen_load",
567
+ screen,
568
+ phase: "ready",
569
+ durationMs: Date.now() - startedAt
570
+ });
571
+ });
572
+ }, [client, screen]);
573
+ }
574
+ function useTrackRender(componentName, options = {}) {
575
+ const fromContext = react.useContext(AppDoctorContext);
576
+ const client = options.client ?? fromContext;
577
+ if (!client) {
578
+ throw new Error(
579
+ "useTrackRender requires AppDoctorProvider or options.client"
580
+ );
581
+ }
582
+ const every = options.every ?? 10;
583
+ const count = react.useRef(0);
584
+ count.current += 1;
585
+ react.useEffect(() => {
586
+ if (count.current % every !== 0) return;
587
+ client.emit({
588
+ name: "render_event",
589
+ component: componentName,
590
+ renderCount: count.current
591
+ });
592
+ });
593
+ }
594
+
595
+ exports.AppDoctorClient = AppDoctorClient;
596
+ exports.AppDoctorContext = AppDoctorContext;
597
+ exports.AppDoctorProvider = AppDoctorProvider;
598
+ exports.createConsoleTransport = createConsoleTransport;
599
+ exports.createHttpTransport = createHttpTransport;
600
+ exports.createNavigationStateListener = createNavigationStateListener;
601
+ exports.getActiveRouteName = getActiveRouteName;
602
+ exports.instrumentAxios = instrumentAxios;
603
+ exports.shouldSample = shouldSample;
604
+ exports.trackApi = trackApi;
605
+ exports.useAppDoctor = useAppDoctor;
606
+ exports.useTrackRender = useTrackRender;
607
+ exports.useTrackScreen = useTrackScreen;
608
+ //# sourceMappingURL=index.cjs.map
609
+ //# sourceMappingURL=index.cjs.map