@flotrace/runtime-core 2.2.0 → 2.2.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/README.md +28 -16
- package/dist/index.d.mts +29 -8
- package/dist/index.d.ts +29 -8
- package/dist/index.js +190 -45
- package/dist/index.mjs +190 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,33 +1,45 @@
|
|
|
1
1
|
# @flotrace/runtime-core
|
|
2
2
|
|
|
3
|
-
Platform-agnostic core for
|
|
3
|
+
Platform-agnostic core for [FloTrace](https://flotrace.dev) — fiber walker, hook/effect inspectors, state-store trackers (Zustand / Redux / TanStack Query), serializer, and WebSocket client. Shared between [`@flotrace/runtime`](https://www.npmjs.com/package/@flotrace/runtime) (web) and [`@flotrace/runtime-native`](https://www.npmjs.com/package/@flotrace/runtime-native) (React Native).
|
|
4
4
|
|
|
5
|
-
> **You
|
|
6
|
-
>
|
|
5
|
+
> **You almost certainly want one of the adapter packages instead.**
|
|
6
|
+
>
|
|
7
|
+
> - **Web React app?** → [`@flotrace/runtime`](https://www.npmjs.com/package/@flotrace/runtime)
|
|
8
|
+
> - **React Native (Expo / bare)?** → [`@flotrace/runtime-native`](https://www.npmjs.com/package/@flotrace/runtime-native)
|
|
9
|
+
>
|
|
10
|
+
> The adapters depend on `runtime-core` and provide the wiring (provider component, network tracker, platform-specific hooks) you actually need. This package on its own does nothing useful at runtime.
|
|
7
11
|
|
|
8
|
-
`runtime-core` is published
|
|
12
|
+
`runtime-core` is published publicly so adapters can pin a compatible version and so users can audit the open-source half of FloTrace. It has zero runtime dependency on `window` / `document` / `XMLHttpRequest` — all platform-specific features live in the adapters.
|
|
9
13
|
|
|
10
14
|
## What's inside
|
|
11
15
|
|
|
12
16
|
| Module | Purpose |
|
|
13
17
|
|---|---|
|
|
14
|
-
| `fiberTreeWalker` | Incremental fiber walk, diffed tree emission
|
|
15
|
-
| `hookInspector` / `effectInspector` | Classify hooks and effects from a fiber; diff deps between commits. |
|
|
16
|
-
| `zustandTracker` / `reduxTracker` / `tanstackQueryTracker` | Duck-typed subscribers for the major state libraries. |
|
|
17
|
-
| `timelineTracker` | Per-component lifecycle events. |
|
|
18
|
-
| `cascadeAnalyzer` / `propDrillingAnalyzer` | Render-cascade tracing + prop-drilling chain detection. |
|
|
19
|
-
| `serializer` | Safe JSON serialization (depth 5, circular
|
|
20
|
-
| `websocketClient` | Singleton WS client with reconnect, batching, auth
|
|
18
|
+
| `fiberTreeWalker` | Incremental fiber walk, diffed tree emission. Pluggable `pruneSubtree` / `frameworkComponentNames` / `hostComponentSkipPrefixes` for platform adapters. |
|
|
19
|
+
| `hookInspector` / `effectInspector` | Classify hooks (14 types) and effects from a fiber; diff deps between commits. |
|
|
20
|
+
| `zustandTracker` / `reduxTracker` / `tanstackQueryTracker` | Duck-typed subscribers for the major state libraries — no peer-dep bloat. |
|
|
21
|
+
| `timelineTracker` | Per-component lifecycle events (mount, unmount, update, prop diff). |
|
|
22
|
+
| `cascadeAnalyzer` / `propDrillingAnalyzer` | Render-cascade tracing + prop-drilling DFS chain detection with severity scoring. |
|
|
23
|
+
| `serializer` | Safe JSON serialization (depth 5, circular-ref guard, truncation). |
|
|
24
|
+
| `websocketClient` | Singleton WS client with exponential backoff reconnect, message batching, optional auth token. |
|
|
21
25
|
|
|
22
26
|
## Version compatibility
|
|
23
27
|
|
|
24
|
-
`@flotrace/runtime-core@
|
|
28
|
+
`@flotrace/runtime-core@2.x` is the companion release for:
|
|
25
29
|
|
|
26
|
-
- `@flotrace/runtime@
|
|
27
|
-
- `@flotrace/runtime-native@
|
|
30
|
+
- `@flotrace/runtime@2.x`
|
|
31
|
+
- `@flotrace/runtime-native@2.x`
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
All three are released in lockstep — pin the same major.minor across the trio. The desktop app and runtime versions are independent (the WebSocket protocol is versioned).
|
|
34
|
+
|
|
35
|
+
## Why open?
|
|
36
|
+
|
|
37
|
+
The runtime is what lives inside your app. Open-source means you can read every byte of the code that touches your fibers, audit the WebSocket payloads, and fork if FloTrace ever disappears. The desktop app is closed-source commercial — that's the bit we charge for. See [flotrace.dev/security](https://flotrace.dev/security) for the full threat model.
|
|
38
|
+
|
|
39
|
+
## Contributing
|
|
40
|
+
|
|
41
|
+
Issues and PRs welcome at [github.com/flotrace](https://github.com/flotrace). The runtime packages target Hermes, V8 (Chromium), and JavaScriptCore — please test against all three when changing fiber-walker or serializer code.
|
|
30
42
|
|
|
31
43
|
## License
|
|
32
44
|
|
|
33
|
-
MIT
|
|
45
|
+
MIT.
|
package/dist/index.d.mts
CHANGED
|
@@ -48,6 +48,13 @@ interface RuntimeReadyMessage {
|
|
|
48
48
|
/** React Native version from `Platform.constants.reactNativeVersion`, formatted
|
|
49
49
|
* as "major.minor.patch". Native-only; web adapter leaves this undefined. */
|
|
50
50
|
reactNativeVersion?: string;
|
|
51
|
+
/** Version of the @flotrace/runtime or @flotrace/runtime-native package the
|
|
52
|
+
* user installed in their app. Lets the desktop diagnose runtime/desktop
|
|
53
|
+
* drift (e.g., user pinned an older runtime that lacks a new feature).
|
|
54
|
+
* Read from the adapter's own package.json at build time. The lockstep
|
|
55
|
+
* release script keeps runtime-core pinned identically, so a separate
|
|
56
|
+
* core-version field would be redundant. */
|
|
57
|
+
runtimeVersion?: string;
|
|
51
58
|
}
|
|
52
59
|
interface RuntimeRenderMessage {
|
|
53
60
|
type: 'runtime:render';
|
|
@@ -867,10 +874,14 @@ interface FloTraceConfig {
|
|
|
867
874
|
/** React Native version from `Platform.constants.reactNativeVersion`, formatted
|
|
868
875
|
* "major.minor.patch". Native adapter only. */
|
|
869
876
|
reactNativeVersion?: string;
|
|
877
|
+
/** Version of the active runtime adapter (`@flotrace/runtime` on web,
|
|
878
|
+
* `@flotrace/runtime-native` on RN). Adapters auto-populate from their
|
|
879
|
+
* own package.json. Surfaced on `runtime:ready` for diagnostics. */
|
|
880
|
+
runtimeVersion?: string;
|
|
870
881
|
}
|
|
871
882
|
/** Keys that stay optional in DEFAULT_CONFIG. These are populated by adapters (web/native)
|
|
872
883
|
* at call-time — the default object should not pretend to know a platform or LAN token. */
|
|
873
|
-
type OptionalConfigKeys = 'getAppUrl' | 'platform' | 'appId' | 'appVersion' | 'host' | 'authToken' | 'userOnlyStrict' | 'userAllowPatterns' | 'frameworkName' | 'frameworkVersion' | 'reactNativeVersion';
|
|
884
|
+
type OptionalConfigKeys = 'getAppUrl' | 'platform' | 'appId' | 'appVersion' | 'host' | 'authToken' | 'userOnlyStrict' | 'userAllowPatterns' | 'frameworkName' | 'frameworkVersion' | 'reactNativeVersion' | 'runtimeVersion';
|
|
874
885
|
type ResolvedFloTraceConfig = Required<Omit<FloTraceConfig, OptionalConfigKeys>> & Pick<FloTraceConfig, OptionalConfigKeys>;
|
|
875
886
|
/**
|
|
876
887
|
* Default configuration
|
|
@@ -1589,7 +1600,7 @@ declare function getChangedKeys(prev: Record<string, unknown> | undefined, next:
|
|
|
1589
1600
|
* State lives here — in platform-agnostic core — so both the web and native
|
|
1590
1601
|
* network trackers can write to the same registry that the analyzers read.
|
|
1591
1602
|
*/
|
|
1592
|
-
/** Tag an object and its nested children (depth ≤
|
|
1603
|
+
/** Tag an object and its nested children (depth ≤ FETCH_ORIGIN_SCAN_DEPTH) with the requestId. */
|
|
1593
1604
|
declare function tagFetchData(obj: unknown, requestId: string, depth?: number): void;
|
|
1594
1605
|
/** Returns true if any API request's response data is currently tagged (within TTL window). */
|
|
1595
1606
|
declare function hasActiveTags(): boolean;
|
|
@@ -1600,13 +1611,23 @@ declare function hasActiveTags(): boolean;
|
|
|
1600
1611
|
*/
|
|
1601
1612
|
declare function clearFetchOriginTags(): void;
|
|
1602
1613
|
/**
|
|
1603
|
-
* Scan an object (and nested children up to
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
1606
|
-
*
|
|
1607
|
-
*
|
|
1614
|
+
* Scan an object (and nested children up to FETCH_ORIGIN_SCAN_DEPTH) for a
|
|
1615
|
+
* WeakMap-tagged fetch origin. Returns the requestId if this object was the
|
|
1616
|
+
* result of a tracked fetch, else undefined.
|
|
1617
|
+
*
|
|
1618
|
+
* By default the result must be within FETCH_ORIGIN_TTL_MS of the original
|
|
1619
|
+
* fetch. Network-panel features (live API → Store correlation in
|
|
1620
|
+
* `storeUtils.buildCorrelatedRequests`, `tanstackQueryTracker.updateQueryTracking`,
|
|
1621
|
+
* `fiberTreeWalker.scanFiberStateForOrigin`) rely on this gate to avoid
|
|
1622
|
+
* mis-attributing later mutations.
|
|
1623
|
+
*
|
|
1624
|
+
* Pass `{ ignoreTTL: true }` to bypass the gate. Used by the value-lineage
|
|
1625
|
+
* resolver, which wants the *causal* origin regardless of how long ago the
|
|
1626
|
+
* fetch resolved (the trace UI displays an "expired" hint for old origins).
|
|
1608
1627
|
*/
|
|
1609
|
-
declare function findFetchOrigin(obj: unknown,
|
|
1628
|
+
declare function findFetchOrigin(obj: unknown, options?: {
|
|
1629
|
+
ignoreTTL?: boolean;
|
|
1630
|
+
}): string | undefined;
|
|
1610
1631
|
|
|
1611
1632
|
/**
|
|
1612
1633
|
* Next.js App Router client-side detection.
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,13 @@ interface RuntimeReadyMessage {
|
|
|
48
48
|
/** React Native version from `Platform.constants.reactNativeVersion`, formatted
|
|
49
49
|
* as "major.minor.patch". Native-only; web adapter leaves this undefined. */
|
|
50
50
|
reactNativeVersion?: string;
|
|
51
|
+
/** Version of the @flotrace/runtime or @flotrace/runtime-native package the
|
|
52
|
+
* user installed in their app. Lets the desktop diagnose runtime/desktop
|
|
53
|
+
* drift (e.g., user pinned an older runtime that lacks a new feature).
|
|
54
|
+
* Read from the adapter's own package.json at build time. The lockstep
|
|
55
|
+
* release script keeps runtime-core pinned identically, so a separate
|
|
56
|
+
* core-version field would be redundant. */
|
|
57
|
+
runtimeVersion?: string;
|
|
51
58
|
}
|
|
52
59
|
interface RuntimeRenderMessage {
|
|
53
60
|
type: 'runtime:render';
|
|
@@ -867,10 +874,14 @@ interface FloTraceConfig {
|
|
|
867
874
|
/** React Native version from `Platform.constants.reactNativeVersion`, formatted
|
|
868
875
|
* "major.minor.patch". Native adapter only. */
|
|
869
876
|
reactNativeVersion?: string;
|
|
877
|
+
/** Version of the active runtime adapter (`@flotrace/runtime` on web,
|
|
878
|
+
* `@flotrace/runtime-native` on RN). Adapters auto-populate from their
|
|
879
|
+
* own package.json. Surfaced on `runtime:ready` for diagnostics. */
|
|
880
|
+
runtimeVersion?: string;
|
|
870
881
|
}
|
|
871
882
|
/** Keys that stay optional in DEFAULT_CONFIG. These are populated by adapters (web/native)
|
|
872
883
|
* at call-time — the default object should not pretend to know a platform or LAN token. */
|
|
873
|
-
type OptionalConfigKeys = 'getAppUrl' | 'platform' | 'appId' | 'appVersion' | 'host' | 'authToken' | 'userOnlyStrict' | 'userAllowPatterns' | 'frameworkName' | 'frameworkVersion' | 'reactNativeVersion';
|
|
884
|
+
type OptionalConfigKeys = 'getAppUrl' | 'platform' | 'appId' | 'appVersion' | 'host' | 'authToken' | 'userOnlyStrict' | 'userAllowPatterns' | 'frameworkName' | 'frameworkVersion' | 'reactNativeVersion' | 'runtimeVersion';
|
|
874
885
|
type ResolvedFloTraceConfig = Required<Omit<FloTraceConfig, OptionalConfigKeys>> & Pick<FloTraceConfig, OptionalConfigKeys>;
|
|
875
886
|
/**
|
|
876
887
|
* Default configuration
|
|
@@ -1589,7 +1600,7 @@ declare function getChangedKeys(prev: Record<string, unknown> | undefined, next:
|
|
|
1589
1600
|
* State lives here — in platform-agnostic core — so both the web and native
|
|
1590
1601
|
* network trackers can write to the same registry that the analyzers read.
|
|
1591
1602
|
*/
|
|
1592
|
-
/** Tag an object and its nested children (depth ≤
|
|
1603
|
+
/** Tag an object and its nested children (depth ≤ FETCH_ORIGIN_SCAN_DEPTH) with the requestId. */
|
|
1593
1604
|
declare function tagFetchData(obj: unknown, requestId: string, depth?: number): void;
|
|
1594
1605
|
/** Returns true if any API request's response data is currently tagged (within TTL window). */
|
|
1595
1606
|
declare function hasActiveTags(): boolean;
|
|
@@ -1600,13 +1611,23 @@ declare function hasActiveTags(): boolean;
|
|
|
1600
1611
|
*/
|
|
1601
1612
|
declare function clearFetchOriginTags(): void;
|
|
1602
1613
|
/**
|
|
1603
|
-
* Scan an object (and nested children up to
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
1606
|
-
*
|
|
1607
|
-
*
|
|
1614
|
+
* Scan an object (and nested children up to FETCH_ORIGIN_SCAN_DEPTH) for a
|
|
1615
|
+
* WeakMap-tagged fetch origin. Returns the requestId if this object was the
|
|
1616
|
+
* result of a tracked fetch, else undefined.
|
|
1617
|
+
*
|
|
1618
|
+
* By default the result must be within FETCH_ORIGIN_TTL_MS of the original
|
|
1619
|
+
* fetch. Network-panel features (live API → Store correlation in
|
|
1620
|
+
* `storeUtils.buildCorrelatedRequests`, `tanstackQueryTracker.updateQueryTracking`,
|
|
1621
|
+
* `fiberTreeWalker.scanFiberStateForOrigin`) rely on this gate to avoid
|
|
1622
|
+
* mis-attributing later mutations.
|
|
1623
|
+
*
|
|
1624
|
+
* Pass `{ ignoreTTL: true }` to bypass the gate. Used by the value-lineage
|
|
1625
|
+
* resolver, which wants the *causal* origin regardless of how long ago the
|
|
1626
|
+
* fetch resolved (the trace UI displays an "expired" hint for old origins).
|
|
1608
1627
|
*/
|
|
1609
|
-
declare function findFetchOrigin(obj: unknown,
|
|
1628
|
+
declare function findFetchOrigin(obj: unknown, options?: {
|
|
1629
|
+
ignoreTTL?: boolean;
|
|
1630
|
+
}): string | undefined;
|
|
1610
1631
|
|
|
1611
1632
|
/**
|
|
1612
1633
|
* Next.js App Router client-side detection.
|
package/dist/index.js
CHANGED
|
@@ -84,7 +84,13 @@ module.exports = __toCommonJS(index_exports);
|
|
|
84
84
|
var DEFAULT_CONFIG = {
|
|
85
85
|
port: 3457,
|
|
86
86
|
appName: "React App",
|
|
87
|
-
|
|
87
|
+
// Default-on unless an explicit `process.env.NODE_ENV === 'production'` is
|
|
88
|
+
// detected. The previous heuristic (`=== 'development'`) silently disabled
|
|
89
|
+
// the runtime in any browser context that doesn't shim `process` (e.g. Vite,
|
|
90
|
+
// Webpack 5 with `node: false`, Rsbuild) — making the README quickstart fail
|
|
91
|
+
// for everyone. Production safety is handled by users gating the import via
|
|
92
|
+
// the dynamic-import pattern documented in the runtime READMEs.
|
|
93
|
+
enabled: globalThis.process?.env?.NODE_ENV !== "production",
|
|
88
94
|
autoReconnect: true,
|
|
89
95
|
reconnectInterval: 2e3,
|
|
90
96
|
trackAllRenders: true,
|
|
@@ -309,7 +315,8 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
|
|
|
309
315
|
appVersion: this.config.appVersion,
|
|
310
316
|
frameworkName: this.config.frameworkName,
|
|
311
317
|
frameworkVersion: this.config.frameworkVersion,
|
|
312
|
-
reactNativeVersion: this.config.reactNativeVersion
|
|
318
|
+
reactNativeVersion: this.config.reactNativeVersion,
|
|
319
|
+
runtimeVersion: this.config.runtimeVersion
|
|
313
320
|
});
|
|
314
321
|
this.flush();
|
|
315
322
|
};
|
|
@@ -1878,12 +1885,16 @@ function uninstallRscPayloadInterceptor() {
|
|
|
1878
1885
|
var fetchDataOrigin = /* @__PURE__ */ new WeakMap();
|
|
1879
1886
|
var requestTagTimestamps = /* @__PURE__ */ new Map();
|
|
1880
1887
|
var FETCH_ORIGIN_TTL_MS = 3e3;
|
|
1888
|
+
var FETCH_ORIGIN_SCAN_DEPTH = 4;
|
|
1889
|
+
var FETCH_ORIGIN_TAG_ARRAY_LIMIT = 50;
|
|
1890
|
+
var FETCH_ORIGIN_SCAN_ARRAY_LIMIT = 20;
|
|
1881
1891
|
function tagFetchData(obj, requestId, depth = 0) {
|
|
1882
|
-
if (depth >
|
|
1892
|
+
if (depth > FETCH_ORIGIN_SCAN_DEPTH || obj === null || typeof obj !== "object") return;
|
|
1883
1893
|
fetchDataOrigin.set(obj, requestId);
|
|
1884
1894
|
if (depth === 0) requestTagTimestamps.set(requestId, Date.now());
|
|
1885
1895
|
if (Array.isArray(obj)) {
|
|
1886
|
-
|
|
1896
|
+
const limit = Math.min(obj.length, FETCH_ORIGIN_TAG_ARRAY_LIMIT);
|
|
1897
|
+
for (let i = 0; i < limit; i++) tagFetchData(obj[i], requestId, depth + 1);
|
|
1887
1898
|
} else {
|
|
1888
1899
|
for (const val of Object.values(obj)) tagFetchData(val, requestId, depth + 1);
|
|
1889
1900
|
}
|
|
@@ -1894,24 +1905,29 @@ function hasActiveTags() {
|
|
|
1894
1905
|
function clearFetchOriginTags() {
|
|
1895
1906
|
requestTagTimestamps.clear();
|
|
1896
1907
|
}
|
|
1897
|
-
function findFetchOrigin(obj,
|
|
1898
|
-
|
|
1908
|
+
function findFetchOrigin(obj, options) {
|
|
1909
|
+
return scanForOrigin(obj, 0, options?.ignoreTTL === true);
|
|
1910
|
+
}
|
|
1911
|
+
function scanForOrigin(obj, depth, ignoreTTL) {
|
|
1912
|
+
if (depth > FETCH_ORIGIN_SCAN_DEPTH || obj === null || typeof obj !== "object") return void 0;
|
|
1899
1913
|
const rid = fetchDataOrigin.get(obj);
|
|
1900
1914
|
if (rid) {
|
|
1915
|
+
if (ignoreTTL) return rid;
|
|
1901
1916
|
const tagTime = requestTagTimestamps.get(rid);
|
|
1902
1917
|
if (tagTime && Date.now() - tagTime <= FETCH_ORIGIN_TTL_MS) return rid;
|
|
1903
1918
|
requestTagTimestamps.delete(rid);
|
|
1904
1919
|
}
|
|
1905
1920
|
if (Array.isArray(obj)) {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
}
|
|
1910
|
-
} else {
|
|
1911
|
-
for (const val of Object.values(obj)) {
|
|
1912
|
-
const found = findFetchOrigin(val, depth + 1);
|
|
1921
|
+
const limit = Math.min(obj.length, FETCH_ORIGIN_SCAN_ARRAY_LIMIT);
|
|
1922
|
+
for (let i = 0; i < limit; i++) {
|
|
1923
|
+
const found = scanForOrigin(obj[i], depth + 1, ignoreTTL);
|
|
1913
1924
|
if (found) return found;
|
|
1914
1925
|
}
|
|
1926
|
+
return void 0;
|
|
1927
|
+
}
|
|
1928
|
+
for (const val of Object.values(obj)) {
|
|
1929
|
+
const found = scanForOrigin(val, depth + 1, ignoreTTL);
|
|
1930
|
+
if (found) return found;
|
|
1915
1931
|
}
|
|
1916
1932
|
return void 0;
|
|
1917
1933
|
}
|
|
@@ -3706,7 +3722,7 @@ function safeCall(fn, fallback) {
|
|
|
3706
3722
|
|
|
3707
3723
|
// src/valueTraceResolver.ts
|
|
3708
3724
|
var FIBER_TAG_CONTEXT_PROVIDER = 10;
|
|
3709
|
-
var BUDGET_MS =
|
|
3725
|
+
var BUDGET_MS = 100;
|
|
3710
3726
|
var SCAN_DEPTH = 3;
|
|
3711
3727
|
var MAX_PROP_CHAIN_DEPTH = 30;
|
|
3712
3728
|
function now() {
|
|
@@ -3730,34 +3746,49 @@ function getHookValueAt(fiber, hookIndex) {
|
|
|
3730
3746
|
if (!hook) return void 0;
|
|
3731
3747
|
return hook.memoizedState;
|
|
3732
3748
|
}
|
|
3733
|
-
function
|
|
3749
|
+
function cachedFp(value, cache) {
|
|
3750
|
+
if (value === null || typeof value !== "object") return valueFingerprint(value);
|
|
3751
|
+
const cached = cache.get(value);
|
|
3752
|
+
if (cached !== void 0) return cached;
|
|
3753
|
+
const fp = valueFingerprint(value);
|
|
3754
|
+
cache.set(value, fp);
|
|
3755
|
+
return fp;
|
|
3756
|
+
}
|
|
3757
|
+
function valuesMatch(target, targetFp, candidate, cache) {
|
|
3734
3758
|
const targetIsObject = target !== null && typeof target === "object";
|
|
3735
3759
|
const candidateIsObject = candidate !== null && typeof candidate === "object";
|
|
3736
3760
|
if (targetIsObject && candidateIsObject && target === candidate) return "exact";
|
|
3737
3761
|
if (!shouldFlagRename(target) || !shouldFlagRename(candidate)) return null;
|
|
3738
|
-
if (
|
|
3762
|
+
if (cachedFp(candidate, cache) === targetFp) return "fingerprint-match";
|
|
3739
3763
|
return null;
|
|
3740
3764
|
}
|
|
3741
|
-
function
|
|
3765
|
+
function findReferenceMatchAtTopLevel(target, container) {
|
|
3766
|
+
if (target === null || typeof target !== "object") return null;
|
|
3767
|
+
for (const key of Object.keys(container)) {
|
|
3768
|
+
if (container[key] === target) return key;
|
|
3769
|
+
}
|
|
3770
|
+
return null;
|
|
3771
|
+
}
|
|
3772
|
+
function findMatchingPathInObject(target, targetFp, container, currentPath, depth, deadline, cache) {
|
|
3742
3773
|
if (now() > deadline) return null;
|
|
3743
3774
|
if (depth > SCAN_DEPTH) return null;
|
|
3744
3775
|
if (container === null || typeof container !== "object") return null;
|
|
3745
|
-
const selfMatch = valuesMatch(target, targetFp, container);
|
|
3776
|
+
const selfMatch = valuesMatch(target, targetFp, container, cache);
|
|
3746
3777
|
if (selfMatch) return { path: [...currentPath], confidence: selfMatch };
|
|
3747
3778
|
if (Array.isArray(container)) {
|
|
3748
3779
|
for (let i = 0; i < Math.min(container.length, 50); i++) {
|
|
3749
3780
|
const child = container[i];
|
|
3750
|
-
const directMatch = valuesMatch(target, targetFp, child);
|
|
3781
|
+
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3751
3782
|
if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
|
|
3752
|
-
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, String(i)], depth + 1, deadline);
|
|
3783
|
+
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, String(i)], depth + 1, deadline, cache);
|
|
3753
3784
|
if (nested) return nested;
|
|
3754
3785
|
}
|
|
3755
3786
|
} else {
|
|
3756
3787
|
for (const key of Object.keys(container)) {
|
|
3757
3788
|
const child = container[key];
|
|
3758
|
-
const directMatch = valuesMatch(target, targetFp, child);
|
|
3789
|
+
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3759
3790
|
if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
|
|
3760
|
-
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, key], depth + 1, deadline);
|
|
3791
|
+
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, key], depth + 1, deadline, cache);
|
|
3761
3792
|
if (nested) return nested;
|
|
3762
3793
|
}
|
|
3763
3794
|
}
|
|
@@ -3770,6 +3801,59 @@ function buildFiberToNodeIdMap() {
|
|
|
3770
3801
|
}
|
|
3771
3802
|
return reverse;
|
|
3772
3803
|
}
|
|
3804
|
+
function retreatToEnclosingObject(fiber, propPath, rootValue) {
|
|
3805
|
+
const isObject = rootValue !== null && typeof rootValue === "object";
|
|
3806
|
+
if (isObject || propPath.length <= 1 || !fiber.memoizedProps) {
|
|
3807
|
+
return { rootValue, trailingSubPath: [] };
|
|
3808
|
+
}
|
|
3809
|
+
let path = propPath.slice();
|
|
3810
|
+
let value = rootValue;
|
|
3811
|
+
const trail = [];
|
|
3812
|
+
while (path.length > 1 && (value === null || typeof value !== "object")) {
|
|
3813
|
+
trail.unshift(path[path.length - 1]);
|
|
3814
|
+
path = path.slice(0, -1);
|
|
3815
|
+
value = walkPath(fiber.memoizedProps, path);
|
|
3816
|
+
}
|
|
3817
|
+
if (value === null || typeof value !== "object") {
|
|
3818
|
+
return { rootValue, trailingSubPath: [] };
|
|
3819
|
+
}
|
|
3820
|
+
return { rootValue: value, trailingSubPath: trail };
|
|
3821
|
+
}
|
|
3822
|
+
function isDebugEnabled() {
|
|
3823
|
+
try {
|
|
3824
|
+
return !!globalThis.__FLOTRACE_DEBUG__;
|
|
3825
|
+
} catch {
|
|
3826
|
+
return false;
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
function findFetchOriginUpKeyPath(stateRoot, keyPath) {
|
|
3830
|
+
const ancestors = [stateRoot];
|
|
3831
|
+
let cursor = stateRoot;
|
|
3832
|
+
for (const segment of keyPath) {
|
|
3833
|
+
if (cursor === null || typeof cursor !== "object") break;
|
|
3834
|
+
cursor = cursor[segment];
|
|
3835
|
+
ancestors.push(cursor);
|
|
3836
|
+
}
|
|
3837
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
3838
|
+
const value = ancestors[i];
|
|
3839
|
+
if (value === null || typeof value !== "object") continue;
|
|
3840
|
+
const rid = findFetchOrigin(value, { ignoreTTL: true });
|
|
3841
|
+
if (rid) {
|
|
3842
|
+
if (isDebugEnabled()) {
|
|
3843
|
+
console.debug("[FloTrace] origin via keyPath retreat", {
|
|
3844
|
+
keyPath,
|
|
3845
|
+
depthHit: i,
|
|
3846
|
+
requestId: rid
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
return rid;
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
return void 0;
|
|
3853
|
+
}
|
|
3854
|
+
function resolveOriginViaTagOrKeyPath(matchedValue, stateRoot, keyPath) {
|
|
3855
|
+
return findFetchOrigin(matchedValue, { ignoreTTL: true }) ?? findFetchOriginUpKeyPath(stateRoot, keyPath);
|
|
3856
|
+
}
|
|
3773
3857
|
function resolveValueTrace(input) {
|
|
3774
3858
|
const startedAt = now();
|
|
3775
3859
|
const deadline = startedAt + BUDGET_MS;
|
|
@@ -3798,9 +3882,13 @@ function resolveValueTrace(input) {
|
|
|
3798
3882
|
if (rootValue === void 0) {
|
|
3799
3883
|
return { ...base, error: "value-not-found", resolvedAtMs: now() };
|
|
3800
3884
|
}
|
|
3885
|
+
const retreated = input.propPath ? retreatToEnclosingObject(fiber, input.propPath, rootValue) : { rootValue, trailingSubPath: [] };
|
|
3886
|
+
rootValue = retreated.rootValue;
|
|
3887
|
+
const trailingSubPath = retreated.trailingSubPath;
|
|
3801
3888
|
const rootFp = valueFingerprint(rootValue);
|
|
3802
3889
|
const fiberToNodeId = buildFiberToNodeIdMap();
|
|
3803
3890
|
const rootComponentName = getComponentNameFromFiber(fiber) ?? "Unknown";
|
|
3891
|
+
const fpCache = /* @__PURE__ */ new WeakMap();
|
|
3804
3892
|
if (input.propPath) {
|
|
3805
3893
|
steps.push({
|
|
3806
3894
|
kind: "prop",
|
|
@@ -3829,8 +3917,17 @@ function resolveValueTrace(input) {
|
|
|
3829
3917
|
if (current.tag !== FIBER_TAG_CONTEXT_PROVIDER) {
|
|
3830
3918
|
const props = current.memoizedProps;
|
|
3831
3919
|
if (props) {
|
|
3832
|
-
const
|
|
3833
|
-
|
|
3920
|
+
const refKey = findReferenceMatchAtTopLevel(rootValue, props);
|
|
3921
|
+
let matchPath = refKey !== null ? [refKey] : null;
|
|
3922
|
+
let matchConfidence = "exact";
|
|
3923
|
+
if (matchPath === null) {
|
|
3924
|
+
const match = findMatchingPathInObject(rootValue, rootFp, props, [], 0, deadline, fpCache);
|
|
3925
|
+
if (match) {
|
|
3926
|
+
matchPath = match.path;
|
|
3927
|
+
matchConfidence = match.confidence;
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
if (matchPath !== null) {
|
|
3834
3931
|
const ancestorNodeId = fiberToNodeId.get(current);
|
|
3835
3932
|
const ancestorName = getComponentNameFromFiber(current) ?? "Unknown";
|
|
3836
3933
|
if (ancestorNodeId) {
|
|
@@ -3838,10 +3935,28 @@ function resolveValueTrace(input) {
|
|
|
3838
3935
|
kind: "prop",
|
|
3839
3936
|
nodeId: ancestorNodeId,
|
|
3840
3937
|
componentName: ancestorName,
|
|
3841
|
-
propPath:
|
|
3842
|
-
confidence:
|
|
3938
|
+
propPath: trailingSubPath.length > 0 ? [...matchPath, ...trailingSubPath] : matchPath,
|
|
3939
|
+
confidence: matchConfidence
|
|
3843
3940
|
});
|
|
3844
3941
|
}
|
|
3942
|
+
} else {
|
|
3943
|
+
const hookMatch = findMatchingHookState(current, rootValue, rootFp, fpCache);
|
|
3944
|
+
if (hookMatch) {
|
|
3945
|
+
const ancestorNodeId = fiberToNodeId.get(current);
|
|
3946
|
+
const ancestorName = getComponentNameFromFiber(current) ?? "Unknown";
|
|
3947
|
+
if (ancestorNodeId) {
|
|
3948
|
+
steps.push({
|
|
3949
|
+
kind: "hook-state",
|
|
3950
|
+
nodeId: ancestorNodeId,
|
|
3951
|
+
componentName: ancestorName,
|
|
3952
|
+
hookIndex: hookMatch.hookIndex,
|
|
3953
|
+
hookType: hookMatch.hookType,
|
|
3954
|
+
subPath: trailingSubPath.length > 0 ? trailingSubPath : void 0,
|
|
3955
|
+
confidence: hookMatch.confidence
|
|
3956
|
+
});
|
|
3957
|
+
return { ...base, steps, resolvedAtMs: now() };
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3845
3960
|
}
|
|
3846
3961
|
}
|
|
3847
3962
|
}
|
|
@@ -3850,7 +3965,7 @@ function resolveValueTrace(input) {
|
|
|
3850
3965
|
}
|
|
3851
3966
|
}
|
|
3852
3967
|
if (input.hookPath) {
|
|
3853
|
-
const origin = findFetchOrigin(rootValue);
|
|
3968
|
+
const origin = findFetchOrigin(rootValue, { ignoreTTL: true });
|
|
3854
3969
|
if (origin) {
|
|
3855
3970
|
steps.push({
|
|
3856
3971
|
kind: "api",
|
|
@@ -3862,15 +3977,15 @@ function resolveValueTrace(input) {
|
|
|
3862
3977
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3863
3978
|
}
|
|
3864
3979
|
}
|
|
3865
|
-
const derivedMatch = findDerivationMatch(fiber, rootValue, rootFp, rootComponentName);
|
|
3980
|
+
const derivedMatch = findDerivationMatch(fiber, rootValue, rootFp, rootComponentName, fpCache);
|
|
3866
3981
|
if (derivedMatch) {
|
|
3867
3982
|
steps.push({ ...derivedMatch, nodeId: input.nodeId });
|
|
3868
3983
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3869
3984
|
}
|
|
3870
|
-
const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId);
|
|
3985
|
+
const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId, fpCache);
|
|
3871
3986
|
if (contextMatch) {
|
|
3872
3987
|
steps.push(contextMatch.step);
|
|
3873
|
-
const providerStoreMatch = findStoreMatch(contextMatch.providerValue,
|
|
3988
|
+
const providerStoreMatch = findStoreMatch(contextMatch.providerValue, cachedFp(contextMatch.providerValue, fpCache), deadline, fpCache);
|
|
3874
3989
|
if (providerStoreMatch) {
|
|
3875
3990
|
steps.push({
|
|
3876
3991
|
kind: "store",
|
|
@@ -3879,19 +3994,23 @@ function resolveValueTrace(input) {
|
|
|
3879
3994
|
keyPath: providerStoreMatch.keyPath,
|
|
3880
3995
|
confidence: providerStoreMatch.confidence
|
|
3881
3996
|
});
|
|
3882
|
-
const origin =
|
|
3997
|
+
const origin = resolveOriginViaTagOrKeyPath(
|
|
3998
|
+
providerStoreMatch.matchedValue,
|
|
3999
|
+
providerStoreMatch.stateRoot,
|
|
4000
|
+
providerStoreMatch.keyPath
|
|
4001
|
+
);
|
|
3883
4002
|
if (origin) {
|
|
3884
4003
|
steps.push({ kind: "api", requestId: origin, method: "UNKNOWN", urlPath: "", ageMs: 0 });
|
|
3885
4004
|
}
|
|
3886
4005
|
} else {
|
|
3887
|
-
const origin = findFetchOrigin(contextMatch.providerValue);
|
|
4006
|
+
const origin = findFetchOrigin(contextMatch.providerValue, { ignoreTTL: true });
|
|
3888
4007
|
if (origin) {
|
|
3889
4008
|
steps.push({ kind: "api", requestId: origin, method: "UNKNOWN", urlPath: "", ageMs: 0 });
|
|
3890
4009
|
}
|
|
3891
4010
|
}
|
|
3892
4011
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3893
4012
|
}
|
|
3894
|
-
const storeMatch = findStoreMatch(rootValue, rootFp, deadline);
|
|
4013
|
+
const storeMatch = findStoreMatch(rootValue, rootFp, deadline, fpCache);
|
|
3895
4014
|
if (storeMatch) {
|
|
3896
4015
|
steps.push({
|
|
3897
4016
|
kind: "store",
|
|
@@ -3900,7 +4019,11 @@ function resolveValueTrace(input) {
|
|
|
3900
4019
|
keyPath: storeMatch.keyPath,
|
|
3901
4020
|
confidence: storeMatch.confidence
|
|
3902
4021
|
});
|
|
3903
|
-
const origin =
|
|
4022
|
+
const origin = resolveOriginViaTagOrKeyPath(
|
|
4023
|
+
storeMatch.matchedValue,
|
|
4024
|
+
storeMatch.stateRoot,
|
|
4025
|
+
storeMatch.keyPath
|
|
4026
|
+
);
|
|
3904
4027
|
if (origin) {
|
|
3905
4028
|
steps.push({
|
|
3906
4029
|
kind: "api",
|
|
@@ -3913,12 +4036,12 @@ function resolveValueTrace(input) {
|
|
|
3913
4036
|
}
|
|
3914
4037
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3915
4038
|
}
|
|
3916
|
-
function findContextMatch(consumer, target, targetFp, fiberToNodeId) {
|
|
4039
|
+
function findContextMatch(consumer, target, targetFp, fiberToNodeId, cache) {
|
|
3917
4040
|
const deps = consumer.dependencies?.firstContext;
|
|
3918
4041
|
if (!deps) return null;
|
|
3919
4042
|
let dep = deps;
|
|
3920
4043
|
while (dep) {
|
|
3921
|
-
const match = valuesMatch(target, targetFp, dep.memoizedValue);
|
|
4044
|
+
const match = valuesMatch(target, targetFp, dep.memoizedValue, cache);
|
|
3922
4045
|
if (match) {
|
|
3923
4046
|
const provider = findNearestProvider(consumer, dep.context);
|
|
3924
4047
|
const step = {
|
|
@@ -3934,14 +4057,14 @@ function findContextMatch(consumer, target, targetFp, fiberToNodeId) {
|
|
|
3934
4057
|
}
|
|
3935
4058
|
return null;
|
|
3936
4059
|
}
|
|
3937
|
-
function findDerivationMatch(fiber, target, targetFp, componentName) {
|
|
4060
|
+
function findDerivationMatch(fiber, target, targetFp, componentName, cache) {
|
|
3938
4061
|
let hook = fiber.memoizedState;
|
|
3939
4062
|
let index = 0;
|
|
3940
4063
|
while (hook) {
|
|
3941
4064
|
const ms = hook.memoizedState;
|
|
3942
4065
|
if (Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1])) {
|
|
3943
4066
|
const [computed, deps] = ms;
|
|
3944
|
-
const match = valuesMatch(target, targetFp, computed);
|
|
4067
|
+
const match = valuesMatch(target, targetFp, computed, cache);
|
|
3945
4068
|
if (match) {
|
|
3946
4069
|
const hookType = typeof computed === "function" ? "useCallback" : "useMemo";
|
|
3947
4070
|
return {
|
|
@@ -3960,6 +4083,25 @@ function findDerivationMatch(fiber, target, targetFp, componentName) {
|
|
|
3960
4083
|
}
|
|
3961
4084
|
return null;
|
|
3962
4085
|
}
|
|
4086
|
+
function findMatchingHookState(fiber, target, targetFp, cache) {
|
|
4087
|
+
let hook = fiber.memoizedState;
|
|
4088
|
+
let index = 0;
|
|
4089
|
+
while (hook) {
|
|
4090
|
+
const ms = hook.memoizedState;
|
|
4091
|
+
const isMemoTuple = Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1]);
|
|
4092
|
+
const isEffectShape2 = ms !== null && typeof ms === "object" && "create" in ms && "deps" in ms;
|
|
4093
|
+
const isRefShape = ms !== null && typeof ms === "object" && Object.keys(ms).length === 1 && "current" in ms;
|
|
4094
|
+
if (!isMemoTuple && !isEffectShape2 && !isRefShape) {
|
|
4095
|
+
const match = valuesMatch(target, targetFp, ms, cache);
|
|
4096
|
+
if (match) {
|
|
4097
|
+
return { hookIndex: index, hookType: "useState", confidence: match };
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
hook = hook.next;
|
|
4101
|
+
index++;
|
|
4102
|
+
}
|
|
4103
|
+
return null;
|
|
4104
|
+
}
|
|
3963
4105
|
function findNearestProvider(consumer, contextObj) {
|
|
3964
4106
|
let current = consumer.return;
|
|
3965
4107
|
let hops = 0;
|
|
@@ -3973,44 +4115,47 @@ function findNearestProvider(consumer, contextObj) {
|
|
|
3973
4115
|
}
|
|
3974
4116
|
return null;
|
|
3975
4117
|
}
|
|
3976
|
-
function findStoreMatch(target, targetFp, deadline) {
|
|
4118
|
+
function findStoreMatch(target, targetFp, deadline, cache) {
|
|
3977
4119
|
for (const [storeName, state] of getZustandSnapshot()) {
|
|
3978
4120
|
if (now() > deadline) return null;
|
|
3979
|
-
const hit = findMatchingPathInObject(target, targetFp, state, [], 0, deadline);
|
|
4121
|
+
const hit = findMatchingPathInObject(target, targetFp, state, [], 0, deadline, cache);
|
|
3980
4122
|
if (hit) {
|
|
3981
4123
|
return {
|
|
3982
4124
|
source: "zustand",
|
|
3983
4125
|
storeName,
|
|
3984
4126
|
keyPath: hit.path,
|
|
3985
4127
|
confidence: hit.confidence,
|
|
3986
|
-
matchedValue: walkPath(state, hit.path)
|
|
4128
|
+
matchedValue: walkPath(state, hit.path),
|
|
4129
|
+
stateRoot: state
|
|
3987
4130
|
};
|
|
3988
4131
|
}
|
|
3989
4132
|
}
|
|
3990
4133
|
const redux = getReduxSnapshot();
|
|
3991
4134
|
if (redux) {
|
|
3992
4135
|
if (now() > deadline) return null;
|
|
3993
|
-
const hit = findMatchingPathInObject(target, targetFp, redux, [], 0, deadline);
|
|
4136
|
+
const hit = findMatchingPathInObject(target, targetFp, redux, [], 0, deadline, cache);
|
|
3994
4137
|
if (hit) {
|
|
3995
4138
|
return {
|
|
3996
4139
|
source: "redux",
|
|
3997
4140
|
storeName: "redux",
|
|
3998
4141
|
keyPath: hit.path,
|
|
3999
4142
|
confidence: hit.confidence,
|
|
4000
|
-
matchedValue: walkPath(redux, hit.path)
|
|
4143
|
+
matchedValue: walkPath(redux, hit.path),
|
|
4144
|
+
stateRoot: redux
|
|
4001
4145
|
};
|
|
4002
4146
|
}
|
|
4003
4147
|
}
|
|
4004
4148
|
for (const [queryHash, entry] of getTanstackSnapshot()) {
|
|
4005
4149
|
if (now() > deadline) return null;
|
|
4006
|
-
const hit = findMatchingPathInObject(target, targetFp, entry.data, [], 0, deadline);
|
|
4150
|
+
const hit = findMatchingPathInObject(target, targetFp, entry.data, [], 0, deadline, cache);
|
|
4007
4151
|
if (hit) {
|
|
4008
4152
|
return {
|
|
4009
4153
|
source: "tanstack-query",
|
|
4010
4154
|
storeName: queryHash,
|
|
4011
4155
|
keyPath: hit.path,
|
|
4012
4156
|
confidence: hit.confidence,
|
|
4013
|
-
matchedValue: walkPath(entry.data, hit.path)
|
|
4157
|
+
matchedValue: walkPath(entry.data, hit.path),
|
|
4158
|
+
stateRoot: entry.data
|
|
4014
4159
|
};
|
|
4015
4160
|
}
|
|
4016
4161
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
var DEFAULT_CONFIG = {
|
|
3
3
|
port: 3457,
|
|
4
4
|
appName: "React App",
|
|
5
|
-
|
|
5
|
+
// Default-on unless an explicit `process.env.NODE_ENV === 'production'` is
|
|
6
|
+
// detected. The previous heuristic (`=== 'development'`) silently disabled
|
|
7
|
+
// the runtime in any browser context that doesn't shim `process` (e.g. Vite,
|
|
8
|
+
// Webpack 5 with `node: false`, Rsbuild) — making the README quickstart fail
|
|
9
|
+
// for everyone. Production safety is handled by users gating the import via
|
|
10
|
+
// the dynamic-import pattern documented in the runtime READMEs.
|
|
11
|
+
enabled: globalThis.process?.env?.NODE_ENV !== "production",
|
|
6
12
|
autoReconnect: true,
|
|
7
13
|
reconnectInterval: 2e3,
|
|
8
14
|
trackAllRenders: true,
|
|
@@ -227,7 +233,8 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
|
|
|
227
233
|
appVersion: this.config.appVersion,
|
|
228
234
|
frameworkName: this.config.frameworkName,
|
|
229
235
|
frameworkVersion: this.config.frameworkVersion,
|
|
230
|
-
reactNativeVersion: this.config.reactNativeVersion
|
|
236
|
+
reactNativeVersion: this.config.reactNativeVersion,
|
|
237
|
+
runtimeVersion: this.config.runtimeVersion
|
|
231
238
|
});
|
|
232
239
|
this.flush();
|
|
233
240
|
};
|
|
@@ -1796,12 +1803,16 @@ function uninstallRscPayloadInterceptor() {
|
|
|
1796
1803
|
var fetchDataOrigin = /* @__PURE__ */ new WeakMap();
|
|
1797
1804
|
var requestTagTimestamps = /* @__PURE__ */ new Map();
|
|
1798
1805
|
var FETCH_ORIGIN_TTL_MS = 3e3;
|
|
1806
|
+
var FETCH_ORIGIN_SCAN_DEPTH = 4;
|
|
1807
|
+
var FETCH_ORIGIN_TAG_ARRAY_LIMIT = 50;
|
|
1808
|
+
var FETCH_ORIGIN_SCAN_ARRAY_LIMIT = 20;
|
|
1799
1809
|
function tagFetchData(obj, requestId, depth = 0) {
|
|
1800
|
-
if (depth >
|
|
1810
|
+
if (depth > FETCH_ORIGIN_SCAN_DEPTH || obj === null || typeof obj !== "object") return;
|
|
1801
1811
|
fetchDataOrigin.set(obj, requestId);
|
|
1802
1812
|
if (depth === 0) requestTagTimestamps.set(requestId, Date.now());
|
|
1803
1813
|
if (Array.isArray(obj)) {
|
|
1804
|
-
|
|
1814
|
+
const limit = Math.min(obj.length, FETCH_ORIGIN_TAG_ARRAY_LIMIT);
|
|
1815
|
+
for (let i = 0; i < limit; i++) tagFetchData(obj[i], requestId, depth + 1);
|
|
1805
1816
|
} else {
|
|
1806
1817
|
for (const val of Object.values(obj)) tagFetchData(val, requestId, depth + 1);
|
|
1807
1818
|
}
|
|
@@ -1812,24 +1823,29 @@ function hasActiveTags() {
|
|
|
1812
1823
|
function clearFetchOriginTags() {
|
|
1813
1824
|
requestTagTimestamps.clear();
|
|
1814
1825
|
}
|
|
1815
|
-
function findFetchOrigin(obj,
|
|
1816
|
-
|
|
1826
|
+
function findFetchOrigin(obj, options) {
|
|
1827
|
+
return scanForOrigin(obj, 0, options?.ignoreTTL === true);
|
|
1828
|
+
}
|
|
1829
|
+
function scanForOrigin(obj, depth, ignoreTTL) {
|
|
1830
|
+
if (depth > FETCH_ORIGIN_SCAN_DEPTH || obj === null || typeof obj !== "object") return void 0;
|
|
1817
1831
|
const rid = fetchDataOrigin.get(obj);
|
|
1818
1832
|
if (rid) {
|
|
1833
|
+
if (ignoreTTL) return rid;
|
|
1819
1834
|
const tagTime = requestTagTimestamps.get(rid);
|
|
1820
1835
|
if (tagTime && Date.now() - tagTime <= FETCH_ORIGIN_TTL_MS) return rid;
|
|
1821
1836
|
requestTagTimestamps.delete(rid);
|
|
1822
1837
|
}
|
|
1823
1838
|
if (Array.isArray(obj)) {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
}
|
|
1828
|
-
} else {
|
|
1829
|
-
for (const val of Object.values(obj)) {
|
|
1830
|
-
const found = findFetchOrigin(val, depth + 1);
|
|
1839
|
+
const limit = Math.min(obj.length, FETCH_ORIGIN_SCAN_ARRAY_LIMIT);
|
|
1840
|
+
for (let i = 0; i < limit; i++) {
|
|
1841
|
+
const found = scanForOrigin(obj[i], depth + 1, ignoreTTL);
|
|
1831
1842
|
if (found) return found;
|
|
1832
1843
|
}
|
|
1844
|
+
return void 0;
|
|
1845
|
+
}
|
|
1846
|
+
for (const val of Object.values(obj)) {
|
|
1847
|
+
const found = scanForOrigin(val, depth + 1, ignoreTTL);
|
|
1848
|
+
if (found) return found;
|
|
1833
1849
|
}
|
|
1834
1850
|
return void 0;
|
|
1835
1851
|
}
|
|
@@ -3624,7 +3640,7 @@ function safeCall(fn, fallback) {
|
|
|
3624
3640
|
|
|
3625
3641
|
// src/valueTraceResolver.ts
|
|
3626
3642
|
var FIBER_TAG_CONTEXT_PROVIDER = 10;
|
|
3627
|
-
var BUDGET_MS =
|
|
3643
|
+
var BUDGET_MS = 100;
|
|
3628
3644
|
var SCAN_DEPTH = 3;
|
|
3629
3645
|
var MAX_PROP_CHAIN_DEPTH = 30;
|
|
3630
3646
|
function now() {
|
|
@@ -3648,34 +3664,49 @@ function getHookValueAt(fiber, hookIndex) {
|
|
|
3648
3664
|
if (!hook) return void 0;
|
|
3649
3665
|
return hook.memoizedState;
|
|
3650
3666
|
}
|
|
3651
|
-
function
|
|
3667
|
+
function cachedFp(value, cache) {
|
|
3668
|
+
if (value === null || typeof value !== "object") return valueFingerprint(value);
|
|
3669
|
+
const cached = cache.get(value);
|
|
3670
|
+
if (cached !== void 0) return cached;
|
|
3671
|
+
const fp = valueFingerprint(value);
|
|
3672
|
+
cache.set(value, fp);
|
|
3673
|
+
return fp;
|
|
3674
|
+
}
|
|
3675
|
+
function valuesMatch(target, targetFp, candidate, cache) {
|
|
3652
3676
|
const targetIsObject = target !== null && typeof target === "object";
|
|
3653
3677
|
const candidateIsObject = candidate !== null && typeof candidate === "object";
|
|
3654
3678
|
if (targetIsObject && candidateIsObject && target === candidate) return "exact";
|
|
3655
3679
|
if (!shouldFlagRename(target) || !shouldFlagRename(candidate)) return null;
|
|
3656
|
-
if (
|
|
3680
|
+
if (cachedFp(candidate, cache) === targetFp) return "fingerprint-match";
|
|
3657
3681
|
return null;
|
|
3658
3682
|
}
|
|
3659
|
-
function
|
|
3683
|
+
function findReferenceMatchAtTopLevel(target, container) {
|
|
3684
|
+
if (target === null || typeof target !== "object") return null;
|
|
3685
|
+
for (const key of Object.keys(container)) {
|
|
3686
|
+
if (container[key] === target) return key;
|
|
3687
|
+
}
|
|
3688
|
+
return null;
|
|
3689
|
+
}
|
|
3690
|
+
function findMatchingPathInObject(target, targetFp, container, currentPath, depth, deadline, cache) {
|
|
3660
3691
|
if (now() > deadline) return null;
|
|
3661
3692
|
if (depth > SCAN_DEPTH) return null;
|
|
3662
3693
|
if (container === null || typeof container !== "object") return null;
|
|
3663
|
-
const selfMatch = valuesMatch(target, targetFp, container);
|
|
3694
|
+
const selfMatch = valuesMatch(target, targetFp, container, cache);
|
|
3664
3695
|
if (selfMatch) return { path: [...currentPath], confidence: selfMatch };
|
|
3665
3696
|
if (Array.isArray(container)) {
|
|
3666
3697
|
for (let i = 0; i < Math.min(container.length, 50); i++) {
|
|
3667
3698
|
const child = container[i];
|
|
3668
|
-
const directMatch = valuesMatch(target, targetFp, child);
|
|
3699
|
+
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3669
3700
|
if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
|
|
3670
|
-
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, String(i)], depth + 1, deadline);
|
|
3701
|
+
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, String(i)], depth + 1, deadline, cache);
|
|
3671
3702
|
if (nested) return nested;
|
|
3672
3703
|
}
|
|
3673
3704
|
} else {
|
|
3674
3705
|
for (const key of Object.keys(container)) {
|
|
3675
3706
|
const child = container[key];
|
|
3676
|
-
const directMatch = valuesMatch(target, targetFp, child);
|
|
3707
|
+
const directMatch = valuesMatch(target, targetFp, child, cache);
|
|
3677
3708
|
if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
|
|
3678
|
-
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, key], depth + 1, deadline);
|
|
3709
|
+
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, key], depth + 1, deadline, cache);
|
|
3679
3710
|
if (nested) return nested;
|
|
3680
3711
|
}
|
|
3681
3712
|
}
|
|
@@ -3688,6 +3719,59 @@ function buildFiberToNodeIdMap() {
|
|
|
3688
3719
|
}
|
|
3689
3720
|
return reverse;
|
|
3690
3721
|
}
|
|
3722
|
+
function retreatToEnclosingObject(fiber, propPath, rootValue) {
|
|
3723
|
+
const isObject = rootValue !== null && typeof rootValue === "object";
|
|
3724
|
+
if (isObject || propPath.length <= 1 || !fiber.memoizedProps) {
|
|
3725
|
+
return { rootValue, trailingSubPath: [] };
|
|
3726
|
+
}
|
|
3727
|
+
let path = propPath.slice();
|
|
3728
|
+
let value = rootValue;
|
|
3729
|
+
const trail = [];
|
|
3730
|
+
while (path.length > 1 && (value === null || typeof value !== "object")) {
|
|
3731
|
+
trail.unshift(path[path.length - 1]);
|
|
3732
|
+
path = path.slice(0, -1);
|
|
3733
|
+
value = walkPath(fiber.memoizedProps, path);
|
|
3734
|
+
}
|
|
3735
|
+
if (value === null || typeof value !== "object") {
|
|
3736
|
+
return { rootValue, trailingSubPath: [] };
|
|
3737
|
+
}
|
|
3738
|
+
return { rootValue: value, trailingSubPath: trail };
|
|
3739
|
+
}
|
|
3740
|
+
function isDebugEnabled() {
|
|
3741
|
+
try {
|
|
3742
|
+
return !!globalThis.__FLOTRACE_DEBUG__;
|
|
3743
|
+
} catch {
|
|
3744
|
+
return false;
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
function findFetchOriginUpKeyPath(stateRoot, keyPath) {
|
|
3748
|
+
const ancestors = [stateRoot];
|
|
3749
|
+
let cursor = stateRoot;
|
|
3750
|
+
for (const segment of keyPath) {
|
|
3751
|
+
if (cursor === null || typeof cursor !== "object") break;
|
|
3752
|
+
cursor = cursor[segment];
|
|
3753
|
+
ancestors.push(cursor);
|
|
3754
|
+
}
|
|
3755
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
3756
|
+
const value = ancestors[i];
|
|
3757
|
+
if (value === null || typeof value !== "object") continue;
|
|
3758
|
+
const rid = findFetchOrigin(value, { ignoreTTL: true });
|
|
3759
|
+
if (rid) {
|
|
3760
|
+
if (isDebugEnabled()) {
|
|
3761
|
+
console.debug("[FloTrace] origin via keyPath retreat", {
|
|
3762
|
+
keyPath,
|
|
3763
|
+
depthHit: i,
|
|
3764
|
+
requestId: rid
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
return rid;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
return void 0;
|
|
3771
|
+
}
|
|
3772
|
+
function resolveOriginViaTagOrKeyPath(matchedValue, stateRoot, keyPath) {
|
|
3773
|
+
return findFetchOrigin(matchedValue, { ignoreTTL: true }) ?? findFetchOriginUpKeyPath(stateRoot, keyPath);
|
|
3774
|
+
}
|
|
3691
3775
|
function resolveValueTrace(input) {
|
|
3692
3776
|
const startedAt = now();
|
|
3693
3777
|
const deadline = startedAt + BUDGET_MS;
|
|
@@ -3716,9 +3800,13 @@ function resolveValueTrace(input) {
|
|
|
3716
3800
|
if (rootValue === void 0) {
|
|
3717
3801
|
return { ...base, error: "value-not-found", resolvedAtMs: now() };
|
|
3718
3802
|
}
|
|
3803
|
+
const retreated = input.propPath ? retreatToEnclosingObject(fiber, input.propPath, rootValue) : { rootValue, trailingSubPath: [] };
|
|
3804
|
+
rootValue = retreated.rootValue;
|
|
3805
|
+
const trailingSubPath = retreated.trailingSubPath;
|
|
3719
3806
|
const rootFp = valueFingerprint(rootValue);
|
|
3720
3807
|
const fiberToNodeId = buildFiberToNodeIdMap();
|
|
3721
3808
|
const rootComponentName = getComponentNameFromFiber(fiber) ?? "Unknown";
|
|
3809
|
+
const fpCache = /* @__PURE__ */ new WeakMap();
|
|
3722
3810
|
if (input.propPath) {
|
|
3723
3811
|
steps.push({
|
|
3724
3812
|
kind: "prop",
|
|
@@ -3747,8 +3835,17 @@ function resolveValueTrace(input) {
|
|
|
3747
3835
|
if (current.tag !== FIBER_TAG_CONTEXT_PROVIDER) {
|
|
3748
3836
|
const props = current.memoizedProps;
|
|
3749
3837
|
if (props) {
|
|
3750
|
-
const
|
|
3751
|
-
|
|
3838
|
+
const refKey = findReferenceMatchAtTopLevel(rootValue, props);
|
|
3839
|
+
let matchPath = refKey !== null ? [refKey] : null;
|
|
3840
|
+
let matchConfidence = "exact";
|
|
3841
|
+
if (matchPath === null) {
|
|
3842
|
+
const match = findMatchingPathInObject(rootValue, rootFp, props, [], 0, deadline, fpCache);
|
|
3843
|
+
if (match) {
|
|
3844
|
+
matchPath = match.path;
|
|
3845
|
+
matchConfidence = match.confidence;
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
if (matchPath !== null) {
|
|
3752
3849
|
const ancestorNodeId = fiberToNodeId.get(current);
|
|
3753
3850
|
const ancestorName = getComponentNameFromFiber(current) ?? "Unknown";
|
|
3754
3851
|
if (ancestorNodeId) {
|
|
@@ -3756,10 +3853,28 @@ function resolveValueTrace(input) {
|
|
|
3756
3853
|
kind: "prop",
|
|
3757
3854
|
nodeId: ancestorNodeId,
|
|
3758
3855
|
componentName: ancestorName,
|
|
3759
|
-
propPath:
|
|
3760
|
-
confidence:
|
|
3856
|
+
propPath: trailingSubPath.length > 0 ? [...matchPath, ...trailingSubPath] : matchPath,
|
|
3857
|
+
confidence: matchConfidence
|
|
3761
3858
|
});
|
|
3762
3859
|
}
|
|
3860
|
+
} else {
|
|
3861
|
+
const hookMatch = findMatchingHookState(current, rootValue, rootFp, fpCache);
|
|
3862
|
+
if (hookMatch) {
|
|
3863
|
+
const ancestorNodeId = fiberToNodeId.get(current);
|
|
3864
|
+
const ancestorName = getComponentNameFromFiber(current) ?? "Unknown";
|
|
3865
|
+
if (ancestorNodeId) {
|
|
3866
|
+
steps.push({
|
|
3867
|
+
kind: "hook-state",
|
|
3868
|
+
nodeId: ancestorNodeId,
|
|
3869
|
+
componentName: ancestorName,
|
|
3870
|
+
hookIndex: hookMatch.hookIndex,
|
|
3871
|
+
hookType: hookMatch.hookType,
|
|
3872
|
+
subPath: trailingSubPath.length > 0 ? trailingSubPath : void 0,
|
|
3873
|
+
confidence: hookMatch.confidence
|
|
3874
|
+
});
|
|
3875
|
+
return { ...base, steps, resolvedAtMs: now() };
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3763
3878
|
}
|
|
3764
3879
|
}
|
|
3765
3880
|
}
|
|
@@ -3768,7 +3883,7 @@ function resolveValueTrace(input) {
|
|
|
3768
3883
|
}
|
|
3769
3884
|
}
|
|
3770
3885
|
if (input.hookPath) {
|
|
3771
|
-
const origin = findFetchOrigin(rootValue);
|
|
3886
|
+
const origin = findFetchOrigin(rootValue, { ignoreTTL: true });
|
|
3772
3887
|
if (origin) {
|
|
3773
3888
|
steps.push({
|
|
3774
3889
|
kind: "api",
|
|
@@ -3780,15 +3895,15 @@ function resolveValueTrace(input) {
|
|
|
3780
3895
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3781
3896
|
}
|
|
3782
3897
|
}
|
|
3783
|
-
const derivedMatch = findDerivationMatch(fiber, rootValue, rootFp, rootComponentName);
|
|
3898
|
+
const derivedMatch = findDerivationMatch(fiber, rootValue, rootFp, rootComponentName, fpCache);
|
|
3784
3899
|
if (derivedMatch) {
|
|
3785
3900
|
steps.push({ ...derivedMatch, nodeId: input.nodeId });
|
|
3786
3901
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3787
3902
|
}
|
|
3788
|
-
const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId);
|
|
3903
|
+
const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId, fpCache);
|
|
3789
3904
|
if (contextMatch) {
|
|
3790
3905
|
steps.push(contextMatch.step);
|
|
3791
|
-
const providerStoreMatch = findStoreMatch(contextMatch.providerValue,
|
|
3906
|
+
const providerStoreMatch = findStoreMatch(contextMatch.providerValue, cachedFp(contextMatch.providerValue, fpCache), deadline, fpCache);
|
|
3792
3907
|
if (providerStoreMatch) {
|
|
3793
3908
|
steps.push({
|
|
3794
3909
|
kind: "store",
|
|
@@ -3797,19 +3912,23 @@ function resolveValueTrace(input) {
|
|
|
3797
3912
|
keyPath: providerStoreMatch.keyPath,
|
|
3798
3913
|
confidence: providerStoreMatch.confidence
|
|
3799
3914
|
});
|
|
3800
|
-
const origin =
|
|
3915
|
+
const origin = resolveOriginViaTagOrKeyPath(
|
|
3916
|
+
providerStoreMatch.matchedValue,
|
|
3917
|
+
providerStoreMatch.stateRoot,
|
|
3918
|
+
providerStoreMatch.keyPath
|
|
3919
|
+
);
|
|
3801
3920
|
if (origin) {
|
|
3802
3921
|
steps.push({ kind: "api", requestId: origin, method: "UNKNOWN", urlPath: "", ageMs: 0 });
|
|
3803
3922
|
}
|
|
3804
3923
|
} else {
|
|
3805
|
-
const origin = findFetchOrigin(contextMatch.providerValue);
|
|
3924
|
+
const origin = findFetchOrigin(contextMatch.providerValue, { ignoreTTL: true });
|
|
3806
3925
|
if (origin) {
|
|
3807
3926
|
steps.push({ kind: "api", requestId: origin, method: "UNKNOWN", urlPath: "", ageMs: 0 });
|
|
3808
3927
|
}
|
|
3809
3928
|
}
|
|
3810
3929
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3811
3930
|
}
|
|
3812
|
-
const storeMatch = findStoreMatch(rootValue, rootFp, deadline);
|
|
3931
|
+
const storeMatch = findStoreMatch(rootValue, rootFp, deadline, fpCache);
|
|
3813
3932
|
if (storeMatch) {
|
|
3814
3933
|
steps.push({
|
|
3815
3934
|
kind: "store",
|
|
@@ -3818,7 +3937,11 @@ function resolveValueTrace(input) {
|
|
|
3818
3937
|
keyPath: storeMatch.keyPath,
|
|
3819
3938
|
confidence: storeMatch.confidence
|
|
3820
3939
|
});
|
|
3821
|
-
const origin =
|
|
3940
|
+
const origin = resolveOriginViaTagOrKeyPath(
|
|
3941
|
+
storeMatch.matchedValue,
|
|
3942
|
+
storeMatch.stateRoot,
|
|
3943
|
+
storeMatch.keyPath
|
|
3944
|
+
);
|
|
3822
3945
|
if (origin) {
|
|
3823
3946
|
steps.push({
|
|
3824
3947
|
kind: "api",
|
|
@@ -3831,12 +3954,12 @@ function resolveValueTrace(input) {
|
|
|
3831
3954
|
}
|
|
3832
3955
|
return { ...base, steps, resolvedAtMs: now() };
|
|
3833
3956
|
}
|
|
3834
|
-
function findContextMatch(consumer, target, targetFp, fiberToNodeId) {
|
|
3957
|
+
function findContextMatch(consumer, target, targetFp, fiberToNodeId, cache) {
|
|
3835
3958
|
const deps = consumer.dependencies?.firstContext;
|
|
3836
3959
|
if (!deps) return null;
|
|
3837
3960
|
let dep = deps;
|
|
3838
3961
|
while (dep) {
|
|
3839
|
-
const match = valuesMatch(target, targetFp, dep.memoizedValue);
|
|
3962
|
+
const match = valuesMatch(target, targetFp, dep.memoizedValue, cache);
|
|
3840
3963
|
if (match) {
|
|
3841
3964
|
const provider = findNearestProvider(consumer, dep.context);
|
|
3842
3965
|
const step = {
|
|
@@ -3852,14 +3975,14 @@ function findContextMatch(consumer, target, targetFp, fiberToNodeId) {
|
|
|
3852
3975
|
}
|
|
3853
3976
|
return null;
|
|
3854
3977
|
}
|
|
3855
|
-
function findDerivationMatch(fiber, target, targetFp, componentName) {
|
|
3978
|
+
function findDerivationMatch(fiber, target, targetFp, componentName, cache) {
|
|
3856
3979
|
let hook = fiber.memoizedState;
|
|
3857
3980
|
let index = 0;
|
|
3858
3981
|
while (hook) {
|
|
3859
3982
|
const ms = hook.memoizedState;
|
|
3860
3983
|
if (Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1])) {
|
|
3861
3984
|
const [computed, deps] = ms;
|
|
3862
|
-
const match = valuesMatch(target, targetFp, computed);
|
|
3985
|
+
const match = valuesMatch(target, targetFp, computed, cache);
|
|
3863
3986
|
if (match) {
|
|
3864
3987
|
const hookType = typeof computed === "function" ? "useCallback" : "useMemo";
|
|
3865
3988
|
return {
|
|
@@ -3878,6 +4001,25 @@ function findDerivationMatch(fiber, target, targetFp, componentName) {
|
|
|
3878
4001
|
}
|
|
3879
4002
|
return null;
|
|
3880
4003
|
}
|
|
4004
|
+
function findMatchingHookState(fiber, target, targetFp, cache) {
|
|
4005
|
+
let hook = fiber.memoizedState;
|
|
4006
|
+
let index = 0;
|
|
4007
|
+
while (hook) {
|
|
4008
|
+
const ms = hook.memoizedState;
|
|
4009
|
+
const isMemoTuple = Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1]);
|
|
4010
|
+
const isEffectShape2 = ms !== null && typeof ms === "object" && "create" in ms && "deps" in ms;
|
|
4011
|
+
const isRefShape = ms !== null && typeof ms === "object" && Object.keys(ms).length === 1 && "current" in ms;
|
|
4012
|
+
if (!isMemoTuple && !isEffectShape2 && !isRefShape) {
|
|
4013
|
+
const match = valuesMatch(target, targetFp, ms, cache);
|
|
4014
|
+
if (match) {
|
|
4015
|
+
return { hookIndex: index, hookType: "useState", confidence: match };
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
hook = hook.next;
|
|
4019
|
+
index++;
|
|
4020
|
+
}
|
|
4021
|
+
return null;
|
|
4022
|
+
}
|
|
3881
4023
|
function findNearestProvider(consumer, contextObj) {
|
|
3882
4024
|
let current = consumer.return;
|
|
3883
4025
|
let hops = 0;
|
|
@@ -3891,44 +4033,47 @@ function findNearestProvider(consumer, contextObj) {
|
|
|
3891
4033
|
}
|
|
3892
4034
|
return null;
|
|
3893
4035
|
}
|
|
3894
|
-
function findStoreMatch(target, targetFp, deadline) {
|
|
4036
|
+
function findStoreMatch(target, targetFp, deadline, cache) {
|
|
3895
4037
|
for (const [storeName, state] of getZustandSnapshot()) {
|
|
3896
4038
|
if (now() > deadline) return null;
|
|
3897
|
-
const hit = findMatchingPathInObject(target, targetFp, state, [], 0, deadline);
|
|
4039
|
+
const hit = findMatchingPathInObject(target, targetFp, state, [], 0, deadline, cache);
|
|
3898
4040
|
if (hit) {
|
|
3899
4041
|
return {
|
|
3900
4042
|
source: "zustand",
|
|
3901
4043
|
storeName,
|
|
3902
4044
|
keyPath: hit.path,
|
|
3903
4045
|
confidence: hit.confidence,
|
|
3904
|
-
matchedValue: walkPath(state, hit.path)
|
|
4046
|
+
matchedValue: walkPath(state, hit.path),
|
|
4047
|
+
stateRoot: state
|
|
3905
4048
|
};
|
|
3906
4049
|
}
|
|
3907
4050
|
}
|
|
3908
4051
|
const redux = getReduxSnapshot();
|
|
3909
4052
|
if (redux) {
|
|
3910
4053
|
if (now() > deadline) return null;
|
|
3911
|
-
const hit = findMatchingPathInObject(target, targetFp, redux, [], 0, deadline);
|
|
4054
|
+
const hit = findMatchingPathInObject(target, targetFp, redux, [], 0, deadline, cache);
|
|
3912
4055
|
if (hit) {
|
|
3913
4056
|
return {
|
|
3914
4057
|
source: "redux",
|
|
3915
4058
|
storeName: "redux",
|
|
3916
4059
|
keyPath: hit.path,
|
|
3917
4060
|
confidence: hit.confidence,
|
|
3918
|
-
matchedValue: walkPath(redux, hit.path)
|
|
4061
|
+
matchedValue: walkPath(redux, hit.path),
|
|
4062
|
+
stateRoot: redux
|
|
3919
4063
|
};
|
|
3920
4064
|
}
|
|
3921
4065
|
}
|
|
3922
4066
|
for (const [queryHash, entry] of getTanstackSnapshot()) {
|
|
3923
4067
|
if (now() > deadline) return null;
|
|
3924
|
-
const hit = findMatchingPathInObject(target, targetFp, entry.data, [], 0, deadline);
|
|
4068
|
+
const hit = findMatchingPathInObject(target, targetFp, entry.data, [], 0, deadline, cache);
|
|
3925
4069
|
if (hit) {
|
|
3926
4070
|
return {
|
|
3927
4071
|
source: "tanstack-query",
|
|
3928
4072
|
storeName: queryHash,
|
|
3929
4073
|
keyPath: hit.path,
|
|
3930
4074
|
confidence: hit.confidence,
|
|
3931
|
-
matchedValue: walkPath(entry.data, hit.path)
|
|
4075
|
+
matchedValue: walkPath(entry.data, hit.path),
|
|
4076
|
+
stateRoot: entry.data
|
|
3932
4077
|
};
|
|
3933
4078
|
}
|
|
3934
4079
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flotrace/runtime-core",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Platform-agnostic core for FloTrace runtime — fiber walker, analyzers, trackers. Shared by @flotrace/runtime (web) and @flotrace/runtime-native (React Native).",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|