@flotrace/runtime-core 2.0.0 → 2.2.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/dist/index.d.mts +237 -4
- package/dist/index.d.ts +237 -4
- package/dist/index.js +413 -12
- package/dist/index.mjs +398 -12
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -176,6 +176,7 @@ function getChangedKeys(prev, next) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
// src/websocketClient.ts
|
|
179
|
+
import React from "react";
|
|
179
180
|
var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
|
|
180
181
|
constructor(config = {}) {
|
|
181
182
|
this.ws = null;
|
|
@@ -223,7 +224,10 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
|
|
|
223
224
|
appUrl: this.config.getAppUrl?.(),
|
|
224
225
|
platform: this.config.platform,
|
|
225
226
|
appId: this.config.appId,
|
|
226
|
-
appVersion: this.config.appVersion
|
|
227
|
+
appVersion: this.config.appVersion,
|
|
228
|
+
frameworkName: this.config.frameworkName,
|
|
229
|
+
frameworkVersion: this.config.frameworkVersion,
|
|
230
|
+
reactNativeVersion: this.config.reactNativeVersion
|
|
227
231
|
});
|
|
228
232
|
this.flush();
|
|
229
233
|
};
|
|
@@ -407,12 +411,17 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
|
|
|
407
411
|
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
408
412
|
}
|
|
409
413
|
/**
|
|
410
|
-
* Get React version if available
|
|
414
|
+
* Get React version if available.
|
|
415
|
+
*
|
|
416
|
+
* Historical note: an earlier implementation read `globalThis.React?.version` —
|
|
417
|
+
* but React is an ES-module import in modern bundles (Vite/webpack/Next.js) and
|
|
418
|
+
* is never placed on `globalThis`, so the probe returned undefined for every
|
|
419
|
+
* typical bundled app. Reading `React.version` via a direct import is
|
|
420
|
+
* authoritative across web (both CJS and ESM bundles), React Native, and SSR.
|
|
411
421
|
*/
|
|
412
422
|
getReactVersion() {
|
|
413
423
|
try {
|
|
414
|
-
|
|
415
|
-
return React?.version;
|
|
424
|
+
return React.version;
|
|
416
425
|
} catch {
|
|
417
426
|
return void 0;
|
|
418
427
|
}
|
|
@@ -1542,8 +1551,8 @@ function runAnalysis(tree, fiberRefMap2) {
|
|
|
1542
1551
|
}
|
|
1543
1552
|
function schedulePropDrillingAnalysis(tree, fiberRefMap2, client2) {
|
|
1544
1553
|
if (analyzeTimer) clearTimeout(analyzeTimer);
|
|
1545
|
-
const
|
|
1546
|
-
const elapsed =
|
|
1554
|
+
const now2 = Date.now();
|
|
1555
|
+
const elapsed = now2 - lastAnalysisTime;
|
|
1547
1556
|
const delay = elapsed >= ANALYZE_INTERVAL_MS ? 0 : ANALYZE_INTERVAL_MS - elapsed;
|
|
1548
1557
|
analyzeTimer = setTimeout(() => {
|
|
1549
1558
|
analyzeTimer = null;
|
|
@@ -2570,7 +2579,7 @@ function executeSnapshot(root) {
|
|
|
2570
2579
|
}
|
|
2571
2580
|
previousFlatTree = currentFlatTree;
|
|
2572
2581
|
if (pendingLocalStateCorrelations.length > 0) {
|
|
2573
|
-
const
|
|
2582
|
+
const now2 = Date.now();
|
|
2574
2583
|
const toSend = pendingLocalStateCorrelations.splice(0);
|
|
2575
2584
|
for (const corr of toSend) {
|
|
2576
2585
|
try {
|
|
@@ -2579,7 +2588,7 @@ function executeSnapshot(root) {
|
|
|
2579
2588
|
requestId: corr.requestId,
|
|
2580
2589
|
componentName: corr.componentName,
|
|
2581
2590
|
hookIndex: corr.hookIndex,
|
|
2582
|
-
timestamp:
|
|
2591
|
+
timestamp: now2
|
|
2583
2592
|
});
|
|
2584
2593
|
} catch {
|
|
2585
2594
|
}
|
|
@@ -3024,6 +3033,7 @@ var activeUnsubscribers = [];
|
|
|
3024
3033
|
var isInstalled4 = false;
|
|
3025
3034
|
var debounceTimers = /* @__PURE__ */ new Map();
|
|
3026
3035
|
var DEBOUNCE_MS = 200;
|
|
3036
|
+
var trackedStores = /* @__PURE__ */ new Map();
|
|
3027
3037
|
function installZustandTracker(stores, client2) {
|
|
3028
3038
|
if (isInstalled4) {
|
|
3029
3039
|
console.warn("[FloTrace] Zustand tracker already installed, reinstalling");
|
|
@@ -3039,6 +3049,7 @@ function installZustandTracker(stores, client2) {
|
|
|
3039
3049
|
continue;
|
|
3040
3050
|
}
|
|
3041
3051
|
try {
|
|
3052
|
+
trackedStores.set(storeName, store);
|
|
3042
3053
|
const initialState = store.getState();
|
|
3043
3054
|
sendStoreUpdate(storeName, initialState, Object.keys(initialState), client2);
|
|
3044
3055
|
const unsubscribe = store.subscribe((newState, prevState) => {
|
|
@@ -3068,9 +3079,20 @@ function uninstallZustandTracker() {
|
|
|
3068
3079
|
}
|
|
3069
3080
|
}
|
|
3070
3081
|
activeUnsubscribers = [];
|
|
3082
|
+
trackedStores.clear();
|
|
3071
3083
|
isInstalled4 = false;
|
|
3072
3084
|
console.log("[FloTrace] Zustand tracker uninstalled");
|
|
3073
3085
|
}
|
|
3086
|
+
function getZustandSnapshot() {
|
|
3087
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
3088
|
+
for (const [name, store] of trackedStores) {
|
|
3089
|
+
try {
|
|
3090
|
+
snapshot.set(name, store.getState());
|
|
3091
|
+
} catch {
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
return snapshot;
|
|
3095
|
+
}
|
|
3074
3096
|
function scheduleStoreUpdate(storeName, prevState, newState, client2) {
|
|
3075
3097
|
let changedKeys;
|
|
3076
3098
|
try {
|
|
@@ -3109,6 +3131,7 @@ var isInstalled5 = false;
|
|
|
3109
3131
|
var debounceTimer = null;
|
|
3110
3132
|
var previousState = null;
|
|
3111
3133
|
var DEBOUNCE_MS2 = 200;
|
|
3134
|
+
var trackedStore = null;
|
|
3112
3135
|
function isReduxStore(obj) {
|
|
3113
3136
|
return typeof obj === "object" && obj !== null && typeof obj.getState === "function" && typeof obj.subscribe === "function" && typeof obj.dispatch === "function";
|
|
3114
3137
|
}
|
|
@@ -3120,6 +3143,7 @@ function installReduxTracker(store, client2) {
|
|
|
3120
3143
|
isInstalled5 = true;
|
|
3121
3144
|
console.log("[FloTrace] Installing Redux tracker");
|
|
3122
3145
|
try {
|
|
3146
|
+
trackedStore = store;
|
|
3123
3147
|
const initialState = store.getState();
|
|
3124
3148
|
previousState = initialState;
|
|
3125
3149
|
sendReduxUpdate(initialState, Object.keys(initialState), client2);
|
|
@@ -3151,9 +3175,18 @@ function uninstallReduxTracker() {
|
|
|
3151
3175
|
activeUnsubscribe = null;
|
|
3152
3176
|
}
|
|
3153
3177
|
previousState = null;
|
|
3178
|
+
trackedStore = null;
|
|
3154
3179
|
isInstalled5 = false;
|
|
3155
3180
|
console.log("[FloTrace] Redux tracker uninstalled");
|
|
3156
3181
|
}
|
|
3182
|
+
function getReduxSnapshot() {
|
|
3183
|
+
if (!trackedStore) return null;
|
|
3184
|
+
try {
|
|
3185
|
+
return trackedStore.getState();
|
|
3186
|
+
} catch {
|
|
3187
|
+
return null;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3157
3190
|
function scheduleReduxUpdate(newState, client2) {
|
|
3158
3191
|
let changedKeys;
|
|
3159
3192
|
try {
|
|
@@ -3191,6 +3224,7 @@ var queryUnsubscribe = null;
|
|
|
3191
3224
|
var mutationUnsubscribe = null;
|
|
3192
3225
|
var debounceTimer2 = null;
|
|
3193
3226
|
var DEBOUNCE_MS3 = 300;
|
|
3227
|
+
var trackedClient = null;
|
|
3194
3228
|
var MAX_EVENTS_PER_QUERY = 50;
|
|
3195
3229
|
var queryTracking = /* @__PURE__ */ new Map();
|
|
3196
3230
|
var CORRELATION_WINDOW_MS = 500;
|
|
@@ -3213,6 +3247,7 @@ function installTanStackQueryTracker(queryClient, client2) {
|
|
|
3213
3247
|
isInstalled6 = true;
|
|
3214
3248
|
console.log("[FloTrace] Installing TanStack Query tracker");
|
|
3215
3249
|
try {
|
|
3250
|
+
trackedClient = queryClient;
|
|
3216
3251
|
const queryCache = queryClient.getQueryCache();
|
|
3217
3252
|
const mutationCache = queryClient.getMutationCache();
|
|
3218
3253
|
for (const query of queryCache.getAll()) {
|
|
@@ -3277,9 +3312,24 @@ function uninstallTanStackQueryTracker() {
|
|
|
3277
3312
|
clearTimeout(pending.timeoutId);
|
|
3278
3313
|
}
|
|
3279
3314
|
pendingCorrelations.clear();
|
|
3315
|
+
trackedClient = null;
|
|
3280
3316
|
isInstalled6 = false;
|
|
3281
3317
|
console.log("[FloTrace] TanStack Query tracker uninstalled");
|
|
3282
3318
|
}
|
|
3319
|
+
function getTanstackSnapshot() {
|
|
3320
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
3321
|
+
if (!trackedClient) return snapshot;
|
|
3322
|
+
try {
|
|
3323
|
+
const queryCache = trackedClient.getQueryCache();
|
|
3324
|
+
for (const query of queryCache.getAll()) {
|
|
3325
|
+
if (query.state.data !== void 0) {
|
|
3326
|
+
snapshot.set(query.queryHash, { queryKey: query.queryKey, data: query.state.data });
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
} catch {
|
|
3330
|
+
}
|
|
3331
|
+
return snapshot;
|
|
3332
|
+
}
|
|
3283
3333
|
function computeDataHash(data) {
|
|
3284
3334
|
if (data === null || data === void 0) return "__null__";
|
|
3285
3335
|
try {
|
|
@@ -3342,11 +3392,11 @@ function updateQueryTracking(query, eventType) {
|
|
|
3342
3392
|
}
|
|
3343
3393
|
}
|
|
3344
3394
|
if (tracking.prevFetchStatus === "idle" && currentFetchStatus === "fetching") {
|
|
3345
|
-
const
|
|
3395
|
+
const now2 = Date.now();
|
|
3346
3396
|
for (const pending of pendingCorrelations.values()) {
|
|
3347
3397
|
if (pending.idleQueryHashes.has(query.queryHash)) {
|
|
3348
3398
|
pending.affectedQueries.set(query.queryHash, {
|
|
3349
|
-
fetchStartedAt:
|
|
3399
|
+
fetchStartedAt: now2,
|
|
3350
3400
|
queryKey: query.queryKey
|
|
3351
3401
|
});
|
|
3352
3402
|
}
|
|
@@ -3358,7 +3408,7 @@ function updateQueryTracking(query, eventType) {
|
|
|
3358
3408
|
}
|
|
3359
3409
|
function openCorrelationWindow(mutation, queryCache, mutationCache, client2) {
|
|
3360
3410
|
const correlationId = `corr-${++correlationCounter}`;
|
|
3361
|
-
const
|
|
3411
|
+
const now2 = Date.now();
|
|
3362
3412
|
const idleQueryHashes = /* @__PURE__ */ new Set();
|
|
3363
3413
|
for (const query of queryCache.getAll()) {
|
|
3364
3414
|
if (query.state.fetchStatus === "idle") {
|
|
@@ -3372,7 +3422,7 @@ function openCorrelationWindow(mutation, queryCache, mutationCache, client2) {
|
|
|
3372
3422
|
correlationId,
|
|
3373
3423
|
mutationId: mutation.mutationId,
|
|
3374
3424
|
mutationKey: mutation.options.mutationKey,
|
|
3375
|
-
completedAt:
|
|
3425
|
+
completedAt: now2,
|
|
3376
3426
|
idleQueryHashes,
|
|
3377
3427
|
affectedQueries: /* @__PURE__ */ new Map(),
|
|
3378
3428
|
timeoutId
|
|
@@ -3571,12 +3621,344 @@ function safeCall(fn, fallback) {
|
|
|
3571
3621
|
return fallback;
|
|
3572
3622
|
}
|
|
3573
3623
|
}
|
|
3624
|
+
|
|
3625
|
+
// src/valueTraceResolver.ts
|
|
3626
|
+
var FIBER_TAG_CONTEXT_PROVIDER = 10;
|
|
3627
|
+
var BUDGET_MS = 50;
|
|
3628
|
+
var SCAN_DEPTH = 3;
|
|
3629
|
+
var MAX_PROP_CHAIN_DEPTH = 30;
|
|
3630
|
+
function now() {
|
|
3631
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
3632
|
+
}
|
|
3633
|
+
function walkPath(root, path) {
|
|
3634
|
+
let cur = root;
|
|
3635
|
+
for (const key of path) {
|
|
3636
|
+
if (cur === null || cur === void 0 || typeof cur !== "object") return void 0;
|
|
3637
|
+
cur = cur[key];
|
|
3638
|
+
}
|
|
3639
|
+
return cur;
|
|
3640
|
+
}
|
|
3641
|
+
function getHookValueAt(fiber, hookIndex) {
|
|
3642
|
+
let hook = fiber.memoizedState;
|
|
3643
|
+
let i = 0;
|
|
3644
|
+
while (hook && i < hookIndex) {
|
|
3645
|
+
hook = hook.next;
|
|
3646
|
+
i++;
|
|
3647
|
+
}
|
|
3648
|
+
if (!hook) return void 0;
|
|
3649
|
+
return hook.memoizedState;
|
|
3650
|
+
}
|
|
3651
|
+
function valuesMatch(target, targetFp, candidate) {
|
|
3652
|
+
const targetIsObject = target !== null && typeof target === "object";
|
|
3653
|
+
const candidateIsObject = candidate !== null && typeof candidate === "object";
|
|
3654
|
+
if (targetIsObject && candidateIsObject && target === candidate) return "exact";
|
|
3655
|
+
if (!shouldFlagRename(target) || !shouldFlagRename(candidate)) return null;
|
|
3656
|
+
if (valueFingerprint(candidate) === targetFp) return "fingerprint-match";
|
|
3657
|
+
return null;
|
|
3658
|
+
}
|
|
3659
|
+
function findMatchingPathInObject(target, targetFp, container, currentPath, depth, deadline) {
|
|
3660
|
+
if (now() > deadline) return null;
|
|
3661
|
+
if (depth > SCAN_DEPTH) return null;
|
|
3662
|
+
if (container === null || typeof container !== "object") return null;
|
|
3663
|
+
const selfMatch = valuesMatch(target, targetFp, container);
|
|
3664
|
+
if (selfMatch) return { path: [...currentPath], confidence: selfMatch };
|
|
3665
|
+
if (Array.isArray(container)) {
|
|
3666
|
+
for (let i = 0; i < Math.min(container.length, 50); i++) {
|
|
3667
|
+
const child = container[i];
|
|
3668
|
+
const directMatch = valuesMatch(target, targetFp, child);
|
|
3669
|
+
if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
|
|
3670
|
+
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, String(i)], depth + 1, deadline);
|
|
3671
|
+
if (nested) return nested;
|
|
3672
|
+
}
|
|
3673
|
+
} else {
|
|
3674
|
+
for (const key of Object.keys(container)) {
|
|
3675
|
+
const child = container[key];
|
|
3676
|
+
const directMatch = valuesMatch(target, targetFp, child);
|
|
3677
|
+
if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
|
|
3678
|
+
const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, key], depth + 1, deadline);
|
|
3679
|
+
if (nested) return nested;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
return null;
|
|
3683
|
+
}
|
|
3684
|
+
function buildFiberToNodeIdMap() {
|
|
3685
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
3686
|
+
for (const [nodeId, fiber] of getFiberRefMap()) {
|
|
3687
|
+
reverse.set(fiber, nodeId);
|
|
3688
|
+
}
|
|
3689
|
+
return reverse;
|
|
3690
|
+
}
|
|
3691
|
+
function resolveValueTrace(input) {
|
|
3692
|
+
const startedAt = now();
|
|
3693
|
+
const deadline = startedAt + BUDGET_MS;
|
|
3694
|
+
const steps = [];
|
|
3695
|
+
const base = {
|
|
3696
|
+
rootNodeId: input.nodeId,
|
|
3697
|
+
rootPropPath: input.propPath,
|
|
3698
|
+
rootHookPath: input.hookPath,
|
|
3699
|
+
steps,
|
|
3700
|
+
resolvedAtMs: now()
|
|
3701
|
+
};
|
|
3702
|
+
const fiber = getFiberRefMap().get(input.nodeId);
|
|
3703
|
+
if (!fiber) {
|
|
3704
|
+
return { ...base, error: "no-fiber", resolvedAtMs: now() };
|
|
3705
|
+
}
|
|
3706
|
+
let rootValue;
|
|
3707
|
+
if (input.propPath && input.propPath.length > 0) {
|
|
3708
|
+
if (!fiber.memoizedProps) return { ...base, error: "value-not-found", resolvedAtMs: now() };
|
|
3709
|
+
rootValue = walkPath(fiber.memoizedProps, input.propPath);
|
|
3710
|
+
} else if (input.hookPath) {
|
|
3711
|
+
const hookValue = getHookValueAt(fiber, input.hookPath.hookIndex);
|
|
3712
|
+
rootValue = input.hookPath.subPath && input.hookPath.subPath.length > 0 ? walkPath(hookValue, input.hookPath.subPath) : hookValue;
|
|
3713
|
+
} else {
|
|
3714
|
+
return { ...base, error: "value-not-found", resolvedAtMs: now() };
|
|
3715
|
+
}
|
|
3716
|
+
if (rootValue === void 0) {
|
|
3717
|
+
return { ...base, error: "value-not-found", resolvedAtMs: now() };
|
|
3718
|
+
}
|
|
3719
|
+
const rootFp = valueFingerprint(rootValue);
|
|
3720
|
+
const fiberToNodeId = buildFiberToNodeIdMap();
|
|
3721
|
+
const rootComponentName = getComponentNameFromFiber(fiber) ?? "Unknown";
|
|
3722
|
+
if (input.propPath) {
|
|
3723
|
+
steps.push({
|
|
3724
|
+
kind: "prop",
|
|
3725
|
+
nodeId: input.nodeId,
|
|
3726
|
+
componentName: rootComponentName,
|
|
3727
|
+
propPath: input.propPath,
|
|
3728
|
+
confidence: "exact"
|
|
3729
|
+
});
|
|
3730
|
+
} else if (input.hookPath) {
|
|
3731
|
+
steps.push({
|
|
3732
|
+
kind: "hook-state",
|
|
3733
|
+
nodeId: input.nodeId,
|
|
3734
|
+
componentName: rootComponentName,
|
|
3735
|
+
hookIndex: input.hookPath.hookIndex,
|
|
3736
|
+
hookType: "unknown",
|
|
3737
|
+
// Cheap: full hook classification is expensive; caller can fetch separately.
|
|
3738
|
+
subPath: input.hookPath.subPath,
|
|
3739
|
+
confidence: "exact"
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
if (input.propPath) {
|
|
3743
|
+
let current = fiber.return;
|
|
3744
|
+
let hops = 0;
|
|
3745
|
+
while (current && hops < MAX_PROP_CHAIN_DEPTH) {
|
|
3746
|
+
if (now() > deadline) return { ...base, steps, truncated: true, resolvedAtMs: now() };
|
|
3747
|
+
if (current.tag !== FIBER_TAG_CONTEXT_PROVIDER) {
|
|
3748
|
+
const props = current.memoizedProps;
|
|
3749
|
+
if (props) {
|
|
3750
|
+
const match = findMatchingPathInObject(rootValue, rootFp, props, [], 0, deadline);
|
|
3751
|
+
if (match) {
|
|
3752
|
+
const ancestorNodeId = fiberToNodeId.get(current);
|
|
3753
|
+
const ancestorName = getComponentNameFromFiber(current) ?? "Unknown";
|
|
3754
|
+
if (ancestorNodeId) {
|
|
3755
|
+
steps.push({
|
|
3756
|
+
kind: "prop",
|
|
3757
|
+
nodeId: ancestorNodeId,
|
|
3758
|
+
componentName: ancestorName,
|
|
3759
|
+
propPath: match.path,
|
|
3760
|
+
confidence: match.confidence
|
|
3761
|
+
});
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
current = current.return;
|
|
3767
|
+
hops++;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
if (input.hookPath) {
|
|
3771
|
+
const origin = findFetchOrigin(rootValue);
|
|
3772
|
+
if (origin) {
|
|
3773
|
+
steps.push({
|
|
3774
|
+
kind: "api",
|
|
3775
|
+
requestId: origin,
|
|
3776
|
+
method: "UNKNOWN",
|
|
3777
|
+
urlPath: "",
|
|
3778
|
+
ageMs: 0
|
|
3779
|
+
});
|
|
3780
|
+
return { ...base, steps, resolvedAtMs: now() };
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
const derivedMatch = findDerivationMatch(fiber, rootValue, rootFp, rootComponentName);
|
|
3784
|
+
if (derivedMatch) {
|
|
3785
|
+
steps.push({ ...derivedMatch, nodeId: input.nodeId });
|
|
3786
|
+
return { ...base, steps, resolvedAtMs: now() };
|
|
3787
|
+
}
|
|
3788
|
+
const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId);
|
|
3789
|
+
if (contextMatch) {
|
|
3790
|
+
steps.push(contextMatch.step);
|
|
3791
|
+
const providerStoreMatch = findStoreMatch(contextMatch.providerValue, valueFingerprint(contextMatch.providerValue), deadline);
|
|
3792
|
+
if (providerStoreMatch) {
|
|
3793
|
+
steps.push({
|
|
3794
|
+
kind: "store",
|
|
3795
|
+
source: providerStoreMatch.source,
|
|
3796
|
+
storeName: providerStoreMatch.storeName,
|
|
3797
|
+
keyPath: providerStoreMatch.keyPath,
|
|
3798
|
+
confidence: providerStoreMatch.confidence
|
|
3799
|
+
});
|
|
3800
|
+
const origin = findFetchOrigin(providerStoreMatch.matchedValue);
|
|
3801
|
+
if (origin) {
|
|
3802
|
+
steps.push({ kind: "api", requestId: origin, method: "UNKNOWN", urlPath: "", ageMs: 0 });
|
|
3803
|
+
}
|
|
3804
|
+
} else {
|
|
3805
|
+
const origin = findFetchOrigin(contextMatch.providerValue);
|
|
3806
|
+
if (origin) {
|
|
3807
|
+
steps.push({ kind: "api", requestId: origin, method: "UNKNOWN", urlPath: "", ageMs: 0 });
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
return { ...base, steps, resolvedAtMs: now() };
|
|
3811
|
+
}
|
|
3812
|
+
const storeMatch = findStoreMatch(rootValue, rootFp, deadline);
|
|
3813
|
+
if (storeMatch) {
|
|
3814
|
+
steps.push({
|
|
3815
|
+
kind: "store",
|
|
3816
|
+
source: storeMatch.source,
|
|
3817
|
+
storeName: storeMatch.storeName,
|
|
3818
|
+
keyPath: storeMatch.keyPath,
|
|
3819
|
+
confidence: storeMatch.confidence
|
|
3820
|
+
});
|
|
3821
|
+
const origin = findFetchOrigin(storeMatch.matchedValue);
|
|
3822
|
+
if (origin) {
|
|
3823
|
+
steps.push({
|
|
3824
|
+
kind: "api",
|
|
3825
|
+
requestId: origin,
|
|
3826
|
+
method: "UNKNOWN",
|
|
3827
|
+
urlPath: "",
|
|
3828
|
+
ageMs: 0
|
|
3829
|
+
});
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
return { ...base, steps, resolvedAtMs: now() };
|
|
3833
|
+
}
|
|
3834
|
+
function findContextMatch(consumer, target, targetFp, fiberToNodeId) {
|
|
3835
|
+
const deps = consumer.dependencies?.firstContext;
|
|
3836
|
+
if (!deps) return null;
|
|
3837
|
+
let dep = deps;
|
|
3838
|
+
while (dep) {
|
|
3839
|
+
const match = valuesMatch(target, targetFp, dep.memoizedValue);
|
|
3840
|
+
if (match) {
|
|
3841
|
+
const provider = findNearestProvider(consumer, dep.context);
|
|
3842
|
+
const step = {
|
|
3843
|
+
kind: "context",
|
|
3844
|
+
contextName: dep.context.displayName || "Context",
|
|
3845
|
+
providerNodeId: provider ? fiberToNodeId.get(provider) : void 0,
|
|
3846
|
+
confidence: match
|
|
3847
|
+
};
|
|
3848
|
+
const providerValue = provider?.memoizedProps?.value ?? dep.memoizedValue;
|
|
3849
|
+
return { step, providerValue };
|
|
3850
|
+
}
|
|
3851
|
+
dep = dep.next;
|
|
3852
|
+
}
|
|
3853
|
+
return null;
|
|
3854
|
+
}
|
|
3855
|
+
function findDerivationMatch(fiber, target, targetFp, componentName) {
|
|
3856
|
+
let hook = fiber.memoizedState;
|
|
3857
|
+
let index = 0;
|
|
3858
|
+
while (hook) {
|
|
3859
|
+
const ms = hook.memoizedState;
|
|
3860
|
+
if (Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1])) {
|
|
3861
|
+
const [computed, deps] = ms;
|
|
3862
|
+
const match = valuesMatch(target, targetFp, computed);
|
|
3863
|
+
if (match) {
|
|
3864
|
+
const hookType = typeof computed === "function" ? "useCallback" : "useMemo";
|
|
3865
|
+
return {
|
|
3866
|
+
kind: "derived",
|
|
3867
|
+
nodeId: "",
|
|
3868
|
+
componentName,
|
|
3869
|
+
hookIndex: index,
|
|
3870
|
+
hookType,
|
|
3871
|
+
depCount: deps.length,
|
|
3872
|
+
confidence: match
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
hook = hook.next;
|
|
3877
|
+
index++;
|
|
3878
|
+
}
|
|
3879
|
+
return null;
|
|
3880
|
+
}
|
|
3881
|
+
function findNearestProvider(consumer, contextObj) {
|
|
3882
|
+
let current = consumer.return;
|
|
3883
|
+
let hops = 0;
|
|
3884
|
+
while (current && hops < MAX_PROP_CHAIN_DEPTH) {
|
|
3885
|
+
if (current.tag === FIBER_TAG_CONTEXT_PROVIDER) {
|
|
3886
|
+
const providerType = current.type;
|
|
3887
|
+
if (providerType && providerType._context === contextObj) return current;
|
|
3888
|
+
}
|
|
3889
|
+
current = current.return;
|
|
3890
|
+
hops++;
|
|
3891
|
+
}
|
|
3892
|
+
return null;
|
|
3893
|
+
}
|
|
3894
|
+
function findStoreMatch(target, targetFp, deadline) {
|
|
3895
|
+
for (const [storeName, state] of getZustandSnapshot()) {
|
|
3896
|
+
if (now() > deadline) return null;
|
|
3897
|
+
const hit = findMatchingPathInObject(target, targetFp, state, [], 0, deadline);
|
|
3898
|
+
if (hit) {
|
|
3899
|
+
return {
|
|
3900
|
+
source: "zustand",
|
|
3901
|
+
storeName,
|
|
3902
|
+
keyPath: hit.path,
|
|
3903
|
+
confidence: hit.confidence,
|
|
3904
|
+
matchedValue: walkPath(state, hit.path)
|
|
3905
|
+
};
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
const redux = getReduxSnapshot();
|
|
3909
|
+
if (redux) {
|
|
3910
|
+
if (now() > deadline) return null;
|
|
3911
|
+
const hit = findMatchingPathInObject(target, targetFp, redux, [], 0, deadline);
|
|
3912
|
+
if (hit) {
|
|
3913
|
+
return {
|
|
3914
|
+
source: "redux",
|
|
3915
|
+
storeName: "redux",
|
|
3916
|
+
keyPath: hit.path,
|
|
3917
|
+
confidence: hit.confidence,
|
|
3918
|
+
matchedValue: walkPath(redux, hit.path)
|
|
3919
|
+
};
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
for (const [queryHash, entry] of getTanstackSnapshot()) {
|
|
3923
|
+
if (now() > deadline) return null;
|
|
3924
|
+
const hit = findMatchingPathInObject(target, targetFp, entry.data, [], 0, deadline);
|
|
3925
|
+
if (hit) {
|
|
3926
|
+
return {
|
|
3927
|
+
source: "tanstack-query",
|
|
3928
|
+
storeName: queryHash,
|
|
3929
|
+
keyPath: hit.path,
|
|
3930
|
+
confidence: hit.confidence,
|
|
3931
|
+
matchedValue: walkPath(entry.data, hit.path)
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
return null;
|
|
3936
|
+
}
|
|
3937
|
+
|
|
3938
|
+
// src/frameworkDetect.ts
|
|
3939
|
+
function detectWebFramework() {
|
|
3940
|
+
if (typeof document === "undefined") return {};
|
|
3941
|
+
const hasNextData = !!globalThis.__NEXT_DATA__ || !!document.querySelector("script#__NEXT_DATA__");
|
|
3942
|
+
if (!hasNextData) return { frameworkName: "plain-react" };
|
|
3943
|
+
let version;
|
|
3944
|
+
try {
|
|
3945
|
+
const req = globalThis.require;
|
|
3946
|
+
if (typeof req === "function") {
|
|
3947
|
+
const id = "next/package.json";
|
|
3948
|
+
const pkg = req(id);
|
|
3949
|
+
version = pkg?.version;
|
|
3950
|
+
}
|
|
3951
|
+
} catch {
|
|
3952
|
+
}
|
|
3953
|
+
return { frameworkName: "next", frameworkVersion: version };
|
|
3954
|
+
}
|
|
3574
3955
|
export {
|
|
3575
3956
|
DEFAULT_CONFIG,
|
|
3576
3957
|
FloTraceWebSocketClient,
|
|
3577
3958
|
buildAncestorChain,
|
|
3578
3959
|
clearFetchOriginTags,
|
|
3579
3960
|
detectServerComponent,
|
|
3961
|
+
detectWebFramework,
|
|
3580
3962
|
disposeWebSocketClient,
|
|
3581
3963
|
findFetchOrigin,
|
|
3582
3964
|
getChangedKeys,
|
|
@@ -3587,8 +3969,11 @@ export {
|
|
|
3587
3969
|
getNodeEffects,
|
|
3588
3970
|
getNodeHooks,
|
|
3589
3971
|
getNodeProps,
|
|
3972
|
+
getReduxSnapshot,
|
|
3973
|
+
getTanstackSnapshot,
|
|
3590
3974
|
getTimeline,
|
|
3591
3975
|
getWebSocketClient,
|
|
3976
|
+
getZustandSnapshot,
|
|
3592
3977
|
hasActiveTags,
|
|
3593
3978
|
inspectEffects,
|
|
3594
3979
|
inspectHooks,
|
|
@@ -3605,6 +3990,7 @@ export {
|
|
|
3605
3990
|
requestFullSnapshot,
|
|
3606
3991
|
requestTreeSnapshot,
|
|
3607
3992
|
resetNextjsDetection,
|
|
3993
|
+
resolveValueTrace,
|
|
3608
3994
|
serializeProps,
|
|
3609
3995
|
serializeValue,
|
|
3610
3996
|
tagFetchData,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flotrace/runtime-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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",
|