nuxt-devtools-observatory 0.1.32 → 0.1.34
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 +37 -1
- package/client/.env.example +2 -0
- package/client/dist/assets/index-BO7neKEi.css +1 -0
- package/client/dist/assets/index-fFBuk6M6.js +20 -0
- package/client/dist/index.html +2 -2
- package/client/src/App.vue +8 -0
- package/client/src/components/Flamegraph.vue +4 -4
- package/client/src/components/SpanInspector.vue +1 -1
- package/client/src/composables/composable-search.ts +3 -0
- package/client/src/composables/trace-render-aggregation.ts +11 -2
- package/client/src/composables/useVirtualizationConfig.ts +40 -0
- package/client/src/composables/useVirtualizationFlags.ts +129 -0
- package/client/src/stores/observatory.ts +20 -0
- package/client/src/views/ComposableTracker.vue +212 -71
- package/client/src/views/FetchDashboard.vue +181 -16
- package/client/src/views/PiniaStoreTracker.vue +343 -0
- package/client/src/views/ProvideInjectGraph.vue +66 -18
- package/client/src/views/RenderHeatmap.vue +329 -75
- package/client/src/views/TraceViewer.vue +190 -20
- package/client/src/views/TransitionTimeline.vue +112 -19
- package/dist/module.d.mts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +28 -24
- package/dist/runtime/composables/pinia-store-registry.d.ts +44 -0
- package/dist/runtime/composables/pinia-store-registry.js +447 -0
- package/dist/runtime/composables/provide-inject-registry.js +13 -8
- package/dist/runtime/composables/render-registry.js +6 -4
- package/dist/runtime/instrumentation/asyncData.d.ts +1 -1
- package/dist/runtime/instrumentation/fetch.d.ts +7 -1
- package/dist/runtime/instrumentation/fetch.js +22 -1
- package/dist/runtime/plugin.js +39 -2
- package/dist/runtime/test-bridge.d.ts +18 -0
- package/dist/runtime/test-bridge.js +100 -0
- package/package.json +14 -3
- package/client/dist/assets/index-5Wl1XYRH.js +0 -17
- package/client/dist/assets/index-DT_QUiIh.css +0 -1
package/client/src/App.vue
CHANGED
|
@@ -4,14 +4,17 @@ import { useObservatoryData } from './stores/observatory'
|
|
|
4
4
|
import FetchDashboard from './views/FetchDashboard.vue'
|
|
5
5
|
import ProvideInjectGraph from './views/ProvideInjectGraph.vue'
|
|
6
6
|
import ComposableTracker from './views/ComposableTracker.vue'
|
|
7
|
+
import PiniaStoreTracker from './views/PiniaStoreTracker.vue'
|
|
7
8
|
import RenderHeatmap from './views/RenderHeatmap.vue'
|
|
8
9
|
import TransitionTimeline from './views/TransitionTimeline.vue'
|
|
9
10
|
import TraceViewer from './views/TraceViewer.vue'
|
|
11
|
+
import { useVirtualizationFlags } from './composables/useVirtualizationFlags'
|
|
10
12
|
|
|
11
13
|
const pathMap: Record<string, string> = {
|
|
12
14
|
fetch: 'fetch',
|
|
13
15
|
provide: 'provide',
|
|
14
16
|
composables: 'composable',
|
|
17
|
+
pinia: 'pinia',
|
|
15
18
|
heatmap: 'heatmap',
|
|
16
19
|
transitions: 'transitions',
|
|
17
20
|
traces: 'traces',
|
|
@@ -22,12 +25,16 @@ const activeTab = ref(pathMap[segment] ?? 'fetch')
|
|
|
22
25
|
|
|
23
26
|
const { features } = useObservatoryData()
|
|
24
27
|
|
|
28
|
+
// Initializes rollout flags from query params before view rendering.
|
|
29
|
+
useVirtualizationFlags()
|
|
30
|
+
|
|
25
31
|
const tabs = computed(() => {
|
|
26
32
|
const f = features.value || {}
|
|
27
33
|
return [
|
|
28
34
|
f.fetchDashboard && { id: 'fetch', label: 'useFetch', icon: '⬡' },
|
|
29
35
|
f.provideInjectGraph && { id: 'provide', label: 'provide/inject', icon: '⬡' },
|
|
30
36
|
f.composableTracker && { id: 'composable', label: 'Composables', icon: '⬡' },
|
|
37
|
+
f.piniaTracker && { id: 'pinia', label: 'Pinia', icon: '⬡' },
|
|
31
38
|
f.renderHeatmap && { id: 'heatmap', label: 'Heatmap', icon: '⬡' },
|
|
32
39
|
f.transitionTracker && { id: 'transitions', label: 'Transitions', icon: '⬡' },
|
|
33
40
|
f.traceViewer && { id: 'traces', label: 'Traces', icon: '⬡' },
|
|
@@ -49,6 +56,7 @@ const tabs = computed(() => {
|
|
|
49
56
|
<FetchDashboard v-if="activeTab === 'fetch'" />
|
|
50
57
|
<ProvideInjectGraph v-else-if="activeTab === 'provide'" />
|
|
51
58
|
<ComposableTracker v-else-if="activeTab === 'composable'" />
|
|
59
|
+
<PiniaStoreTracker v-else-if="activeTab === 'pinia'" />
|
|
52
60
|
<RenderHeatmap v-else-if="activeTab === 'heatmap'" />
|
|
53
61
|
<TransitionTimeline v-else-if="activeTab === 'transitions'" />
|
|
54
62
|
<TraceViewer v-else-if="activeTab === 'traces'" />
|
|
@@ -291,10 +291,6 @@ const flattenedTree = computed(() => {
|
|
|
291
291
|
background: var(--tracker-tint-purple-soft, rgb(127 119 221 / 8%));
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
.flamegraph__row--selected .flamegraph__span-name {
|
|
295
|
-
color: var(--purple, var(--accent));
|
|
296
|
-
}
|
|
297
|
-
|
|
298
294
|
.flamegraph__label {
|
|
299
295
|
display: flex;
|
|
300
296
|
align-items: center;
|
|
@@ -350,6 +346,10 @@ const flattenedTree = computed(() => {
|
|
|
350
346
|
color: var(--text);
|
|
351
347
|
}
|
|
352
348
|
|
|
349
|
+
.flamegraph__row--selected .flamegraph__span-name {
|
|
350
|
+
color: var(--purple, var(--accent));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
353
|
.flamegraph__span-type {
|
|
354
354
|
padding: 2px 6px;
|
|
355
355
|
background: var(--bg2, var(--bg));
|
|
@@ -91,6 +91,9 @@ function valueMatchesQuery(value: unknown, query: string, seen: WeakSet<object>,
|
|
|
91
91
|
/**
|
|
92
92
|
* Returns true when a composable entry matches the search query.
|
|
93
93
|
* Search scope includes name, file, ref keys, and nested reactive key/value content.
|
|
94
|
+
* @param {ComposableEntry} entry - Composable entry to inspect.
|
|
95
|
+
* @param {string} query - Case-insensitive search query.
|
|
96
|
+
* @returns {boolean} True when the entry matches the query.
|
|
94
97
|
*/
|
|
95
98
|
export function matchesComposableEntryQuery(entry: ComposableEntry, query: string): boolean {
|
|
96
99
|
const normalizedQuery = query.trim().toLowerCase()
|
|
@@ -68,7 +68,11 @@ function getComponentIdentity(metadata: SpanMetadata, fallbackId: string): { key
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Build per-trace render stats grouped by component UID.
|
|
73
|
+
* @param {TraceEntry | undefined} trace - Trace to summarize.
|
|
74
|
+
* @returns {TraceRenderStatsRow[]} Render metrics grouped by component uid.
|
|
75
|
+
*/
|
|
72
76
|
export function buildRenderSummaryForTrace(trace?: TraceEntry): TraceRenderStatsRow[] {
|
|
73
77
|
if (!trace) {
|
|
74
78
|
return []
|
|
@@ -135,7 +139,12 @@ export function buildRenderSummaryForTrace(trace?: TraceEntry): TraceRenderStats
|
|
|
135
139
|
return rows
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Build cross-trace render aggregation and comparison against a selected trace.
|
|
144
|
+
* @param {TraceEntry[]} traces - Traces included in the aggregation window.
|
|
145
|
+
* @param {string | undefined} selectedTraceId - Optional selected trace for baseline comparison.
|
|
146
|
+
* @returns {CrossTraceRenderSummaryRow[]} Aggregated comparison rows by component identity.
|
|
147
|
+
*/
|
|
139
148
|
export function buildCrossTraceRenderSummary(traces: TraceEntry[], selectedTraceId?: string): CrossTraceRenderSummaryRow[] {
|
|
140
149
|
if (traces.length === 0) {
|
|
141
150
|
return []
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
const DEFAULT_ROW_HEIGHT = 34
|
|
4
|
+
const DEFAULT_OVERSCAN = 10
|
|
5
|
+
const MIN_OVERSCAN = 2
|
|
6
|
+
const MAX_OVERSCAN = 40
|
|
7
|
+
|
|
8
|
+
function clampOverscan(value: number): number {
|
|
9
|
+
if (!Number.isFinite(value)) {
|
|
10
|
+
return DEFAULT_OVERSCAN
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return Math.max(MIN_OVERSCAN, Math.min(MAX_OVERSCAN, Math.round(value)))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type VirtualizationPreset = {
|
|
17
|
+
rowHeight: number
|
|
18
|
+
overscan: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useVirtualizationConfig(initial?: Partial<VirtualizationPreset>) {
|
|
22
|
+
const rowHeight = ref(initial?.rowHeight ?? DEFAULT_ROW_HEIGHT)
|
|
23
|
+
const overscan = ref(clampOverscan(initial?.overscan ?? DEFAULT_OVERSCAN))
|
|
24
|
+
|
|
25
|
+
const preset = computed<VirtualizationPreset>(() => ({
|
|
26
|
+
rowHeight: Math.max(20, rowHeight.value),
|
|
27
|
+
overscan: clampOverscan(overscan.value),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
rowHeight,
|
|
32
|
+
overscan,
|
|
33
|
+
preset,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const VIRTUALIZATION_DEFAULTS = {
|
|
38
|
+
rowHeight: DEFAULT_ROW_HEIGHT,
|
|
39
|
+
overscan: DEFAULT_OVERSCAN,
|
|
40
|
+
} as const
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
type VirtualizationScreen = 'heatmap' | 'traces' | 'composables' | 'fetch' | 'transitions'
|
|
4
|
+
|
|
5
|
+
type VirtualizationFlags = {
|
|
6
|
+
enabled: boolean
|
|
7
|
+
heatmap: boolean
|
|
8
|
+
traces: boolean
|
|
9
|
+
composables: boolean
|
|
10
|
+
fetch: boolean
|
|
11
|
+
transitions: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const defaultFlags: VirtualizationFlags = {
|
|
15
|
+
enabled: false,
|
|
16
|
+
heatmap: false,
|
|
17
|
+
traces: false,
|
|
18
|
+
composables: false,
|
|
19
|
+
fetch: false,
|
|
20
|
+
transitions: false,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const flags = ref<VirtualizationFlags>({ ...defaultFlags })
|
|
24
|
+
|
|
25
|
+
let initialized = false
|
|
26
|
+
|
|
27
|
+
function parseBooleanParam(value: string | null): boolean | null {
|
|
28
|
+
if (value == null) {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (value === '1' || value === 'true' || value === 'on') {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (value === '0' || value === 'false' || value === 'off') {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readFromStorage(): VirtualizationFlags {
|
|
44
|
+
return { ...defaultFlags }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function applyQueryOverrides(current: VirtualizationFlags): VirtualizationFlags {
|
|
48
|
+
if (typeof window === 'undefined') {
|
|
49
|
+
return current
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const params = new URLSearchParams(window.location.search)
|
|
53
|
+
const globalParam = parseBooleanParam(params.get('virt'))
|
|
54
|
+
|
|
55
|
+
const next = { ...current }
|
|
56
|
+
|
|
57
|
+
if (globalParam != null) {
|
|
58
|
+
next.enabled = globalParam
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const perScreen: Array<[VirtualizationScreen, string]> = [
|
|
62
|
+
['heatmap', 'virtHeatmap'],
|
|
63
|
+
['traces', 'virtTraces'],
|
|
64
|
+
['composables', 'virtComposables'],
|
|
65
|
+
['fetch', 'virtFetch'],
|
|
66
|
+
['transitions', 'virtTransitions'],
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
for (const [key, paramName] of perScreen) {
|
|
70
|
+
const override = parseBooleanParam(params.get(paramName))
|
|
71
|
+
|
|
72
|
+
if (override != null) {
|
|
73
|
+
next[key] = override
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return next
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function init() {
|
|
81
|
+
if (initialized) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
initialized = true
|
|
86
|
+
const stored = readFromStorage()
|
|
87
|
+
const merged = applyQueryOverrides(stored)
|
|
88
|
+
flags.value = merged
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function setAllEnabled(value: boolean) {
|
|
92
|
+
const next = {
|
|
93
|
+
...flags.value,
|
|
94
|
+
enabled: value,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
flags.value = next
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function setScreenEnabled(screen: VirtualizationScreen, value: boolean) {
|
|
101
|
+
const next = {
|
|
102
|
+
...flags.value,
|
|
103
|
+
[screen]: value,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
flags.value = next
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function useVirtualizationFlags() {
|
|
110
|
+
init()
|
|
111
|
+
|
|
112
|
+
const effective = computed(() => ({
|
|
113
|
+
enabled: flags.value.enabled,
|
|
114
|
+
heatmap: flags.value.enabled && flags.value.heatmap,
|
|
115
|
+
traces: flags.value.enabled && flags.value.traces,
|
|
116
|
+
composables: flags.value.enabled && flags.value.composables,
|
|
117
|
+
fetch: flags.value.enabled && flags.value.fetch,
|
|
118
|
+
transitions: flags.value.enabled && flags.value.transitions,
|
|
119
|
+
}))
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
flags,
|
|
123
|
+
effective,
|
|
124
|
+
setAllEnabled,
|
|
125
|
+
setScreenEnabled,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type { VirtualizationFlags, VirtualizationScreen }
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ProvideEntry,
|
|
7
7
|
InjectEntry,
|
|
8
8
|
ComposableEntry,
|
|
9
|
+
PiniaStoreEntry,
|
|
9
10
|
RenderEntry,
|
|
10
11
|
TransitionEntry,
|
|
11
12
|
TraceEntry,
|
|
@@ -16,6 +17,7 @@ type ProvideInjectSnapshot = { provides: ProvideEntry[]; injects: InjectEntry[]
|
|
|
16
17
|
const fetchEntries = ref<FetchEntry[]>([])
|
|
17
18
|
const provideInject = ref<ProvideInjectSnapshot>({ provides: [], injects: [] })
|
|
18
19
|
const composables = ref<ComposableEntry[]>([])
|
|
20
|
+
const piniaStores = ref<PiniaStoreEntry[]>([])
|
|
19
21
|
const renders = ref<RenderEntry[]>([])
|
|
20
22
|
const transitions = ref<TransitionEntry[]>([])
|
|
21
23
|
const traces = ref<TraceEntry[]>([])
|
|
@@ -57,6 +59,7 @@ function applySnapshot(data: ObservatorySnapshot) {
|
|
|
57
59
|
}
|
|
58
60
|
: { provides: [], injects: [] }
|
|
59
61
|
composables.value = cloneArray(data.composables as ComposableEntry[] | undefined)
|
|
62
|
+
piniaStores.value = cloneArray(data.piniaStores as PiniaStoreEntry[] | undefined)
|
|
60
63
|
renders.value = normalizeRenderEntries(data.renders as RenderEntry[] | undefined)
|
|
61
64
|
transitions.value = cloneArray(data.transitions as TransitionEntry[] | undefined)
|
|
62
65
|
traces.value = cloneArray(data.traces as TraceEntry[] | undefined)
|
|
@@ -87,6 +90,7 @@ function applySnapshot(data: ObservatorySnapshot) {
|
|
|
87
90
|
debugLog('first snapshot received', {
|
|
88
91
|
fetch: fetchEntries.value.length,
|
|
89
92
|
composables: composables.value.length,
|
|
93
|
+
piniaStores: piniaStores.value.length,
|
|
90
94
|
renders: renders.value.length,
|
|
91
95
|
transitions: transitions.value.length,
|
|
92
96
|
traces: traces.value.length,
|
|
@@ -200,6 +204,21 @@ export function editComposableValue(id: string, key: string, value: unknown) {
|
|
|
200
204
|
})
|
|
201
205
|
}
|
|
202
206
|
|
|
207
|
+
export function clearPiniaStores() {
|
|
208
|
+
piniaStores.value = []
|
|
209
|
+
rpc?.clearPiniaStores()
|
|
210
|
+
.then(() => rpc?.requestSnapshot())
|
|
211
|
+
.catch((error) => {
|
|
212
|
+
debugLog('clearPiniaStores failed', error)
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function editPiniaState(storeId: string, path: string, value: unknown) {
|
|
217
|
+
rpc?.editPiniaState(storeId, path, value).catch((error) => {
|
|
218
|
+
debugLog('editPiniaState failed', error)
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
203
222
|
export function openInEditor(file: string) {
|
|
204
223
|
if (!file || file === 'unknown') {
|
|
205
224
|
return
|
|
@@ -233,6 +252,7 @@ export function useObservatoryData() {
|
|
|
233
252
|
fetch: fetchEntries,
|
|
234
253
|
provideInject,
|
|
235
254
|
composables,
|
|
255
|
+
piniaStores,
|
|
236
256
|
renders,
|
|
237
257
|
transitions,
|
|
238
258
|
traces,
|