nuxt-devtools-observatory 0.1.33 → 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 +32 -1
- package/client/.env.example +1 -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 +4 -0
- package/client/src/stores/observatory.ts +20 -0
- package/client/src/views/PiniaStoreTracker.vue +343 -0
- package/client/src/views/ProvideInjectGraph.vue +25 -0
- package/dist/module.d.mts +10 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +17 -2
- 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/plugin.js +35 -1
- package/dist/runtime/test-bridge.js +18 -4
- package/package.json +1 -1
- package/client/dist/assets/index-BqKYgjVB.js +0 -20
- package/client/dist/assets/index-bs1JBJ2u.css +0 -1
package/client/src/App.vue
CHANGED
|
@@ -4,6 +4,7 @@ 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'
|
|
@@ -13,6 +14,7 @@ const pathMap: Record<string, string> = {
|
|
|
13
14
|
fetch: 'fetch',
|
|
14
15
|
provide: 'provide',
|
|
15
16
|
composables: 'composable',
|
|
17
|
+
pinia: 'pinia',
|
|
16
18
|
heatmap: 'heatmap',
|
|
17
19
|
transitions: 'transitions',
|
|
18
20
|
traces: 'traces',
|
|
@@ -32,6 +34,7 @@ const tabs = computed(() => {
|
|
|
32
34
|
f.fetchDashboard && { id: 'fetch', label: 'useFetch', icon: '⬡' },
|
|
33
35
|
f.provideInjectGraph && { id: 'provide', label: 'provide/inject', icon: '⬡' },
|
|
34
36
|
f.composableTracker && { id: 'composable', label: 'Composables', icon: '⬡' },
|
|
37
|
+
f.piniaTracker && { id: 'pinia', label: 'Pinia', icon: '⬡' },
|
|
35
38
|
f.renderHeatmap && { id: 'heatmap', label: 'Heatmap', icon: '⬡' },
|
|
36
39
|
f.transitionTracker && { id: 'transitions', label: 'Transitions', icon: '⬡' },
|
|
37
40
|
f.traceViewer && { id: 'traces', label: 'Traces', icon: '⬡' },
|
|
@@ -53,6 +56,7 @@ const tabs = computed(() => {
|
|
|
53
56
|
<FetchDashboard v-if="activeTab === 'fetch'" />
|
|
54
57
|
<ProvideInjectGraph v-else-if="activeTab === 'provide'" />
|
|
55
58
|
<ComposableTracker v-else-if="activeTab === 'composable'" />
|
|
59
|
+
<PiniaStoreTracker v-else-if="activeTab === 'pinia'" />
|
|
56
60
|
<RenderHeatmap v-else-if="activeTab === 'heatmap'" />
|
|
57
61
|
<TransitionTimeline v-else-if="activeTab === 'transitions'" />
|
|
58
62
|
<TraceViewer v-else-if="activeTab === 'traces'" />
|
|
@@ -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,
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// No store is accessed or created here, so Pinia Tracker will show 'no store available' by default.
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
import { useObservatoryData, clearPiniaStores /* editPiniaState */ } from '@observatory-client/stores/observatory'
|
|
5
|
+
import type { PiniaMutationEvent, PiniaStoreEntry } from '@observatory/types/snapshot'
|
|
6
|
+
|
|
7
|
+
const { piniaStores, connected } = useObservatoryData()
|
|
8
|
+
|
|
9
|
+
const selectedStoreId = ref<string | null>(null)
|
|
10
|
+
const selectedEventId = ref<string | null>(null)
|
|
11
|
+
// Disabled until we can get it working properly. See comments in the template and applyEdit function for details.
|
|
12
|
+
// const editPath = ref('')
|
|
13
|
+
// const editValue = ref('')
|
|
14
|
+
const editError = ref('')
|
|
15
|
+
|
|
16
|
+
const stores = computed(() => [...piniaStores.value].sort((a, b) => a.id.localeCompare(b.id)))
|
|
17
|
+
|
|
18
|
+
const selectedStore = computed<PiniaStoreEntry | null>(() => {
|
|
19
|
+
if (!selectedStoreId.value) {
|
|
20
|
+
return stores.value[0] ?? null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return stores.value.find((item) => item.id === selectedStoreId.value) ?? null
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const timeline = computed<PiniaMutationEvent[]>(() => {
|
|
27
|
+
return selectedStore.value ? [...selectedStore.value.timeline].slice().reverse() : []
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const selectedEvent = computed<PiniaMutationEvent | null>(() => {
|
|
31
|
+
if (!selectedEventId.value) {
|
|
32
|
+
return timeline.value[0] ?? null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return timeline.value.find((event) => event.id === selectedEventId.value) ?? null
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
function selectStore(id: string) {
|
|
39
|
+
selectedStoreId.value = id
|
|
40
|
+
selectedEventId.value = null
|
|
41
|
+
editError.value = ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function pretty(value: unknown) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.stringify(value, null, 2)
|
|
47
|
+
} catch {
|
|
48
|
+
return String(value)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function renderDuration(event: PiniaMutationEvent) {
|
|
53
|
+
if (typeof event.durationMs !== 'number') {
|
|
54
|
+
return '-'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return `${event.durationMs.toFixed(2)}ms`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Edit functionality is temporarily disabled due to issues with state updates not being reflected in the UI. This is likely related to how the editPiniaState function applies changes and how the store's reactivity system detects those changes. Further investigation is needed to determine the root cause and implement a proper fix.
|
|
61
|
+
function applyEdit() {
|
|
62
|
+
const store = selectedStore.value
|
|
63
|
+
|
|
64
|
+
if (!store) {
|
|
65
|
+
editError.value = 'Select a store first.'
|
|
66
|
+
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!editPath.value.trim()) {
|
|
71
|
+
editError.value = 'Path is required (for example: preferences.theme).'
|
|
72
|
+
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let nextValue: unknown = editValue.value
|
|
77
|
+
|
|
78
|
+
if (editValue.value.trim()) {
|
|
79
|
+
try {
|
|
80
|
+
nextValue = JSON.parse(editValue.value)
|
|
81
|
+
} catch {
|
|
82
|
+
nextValue = editValue.value
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
editError.value = ''
|
|
87
|
+
editPiniaState(store.id, editPath.value.trim(), nextValue)
|
|
88
|
+
} */
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<template>
|
|
92
|
+
<section class="pinia-tracker">
|
|
93
|
+
<header class="toolbar">
|
|
94
|
+
<h2>Pinia State and Mutations</h2>
|
|
95
|
+
<div class="toolbar-actions">
|
|
96
|
+
<span v-if="connected" class="status connected">Connected</span>
|
|
97
|
+
<span v-else class="status">Waiting for app</span>
|
|
98
|
+
<button class="danger" @click="clearPiniaStores">Clear timeline</button>
|
|
99
|
+
</div>
|
|
100
|
+
</header>
|
|
101
|
+
|
|
102
|
+
<div v-if="stores.length === 0" class="empty">No Pinia stores detected yet. Trigger store usage in the app.</div>
|
|
103
|
+
|
|
104
|
+
<div v-else class="content-grid">
|
|
105
|
+
<aside class="panel stores">
|
|
106
|
+
<h3>Stores</h3>
|
|
107
|
+
<button
|
|
108
|
+
v-for="store in stores"
|
|
109
|
+
:key="store.id"
|
|
110
|
+
class="store-row"
|
|
111
|
+
:class="{ active: selectedStore?.id === store.id }"
|
|
112
|
+
@click="selectStore(store.id)"
|
|
113
|
+
>
|
|
114
|
+
<span>{{ store.name }}</span>
|
|
115
|
+
<small>{{ store.timeline.length }} events</small>
|
|
116
|
+
</button>
|
|
117
|
+
</aside>
|
|
118
|
+
|
|
119
|
+
<section class="panel timeline">
|
|
120
|
+
<h3>Timeline</h3>
|
|
121
|
+
<div class="list">
|
|
122
|
+
<button
|
|
123
|
+
v-for="event in timeline"
|
|
124
|
+
:key="event.id"
|
|
125
|
+
class="event-row"
|
|
126
|
+
:class="{ active: selectedEvent?.id === event.id }"
|
|
127
|
+
@click="selectedEventId = event.id"
|
|
128
|
+
>
|
|
129
|
+
<strong>{{ event.kind }}</strong>
|
|
130
|
+
<span>{{ event.name }}</span>
|
|
131
|
+
<small>{{ renderDuration(event) }}</small>
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</section>
|
|
135
|
+
|
|
136
|
+
<section class="panel inspector">
|
|
137
|
+
<h3>Inspector</h3>
|
|
138
|
+
|
|
139
|
+
<div v-if="selectedStore" class="block">
|
|
140
|
+
<h4>Current state</h4>
|
|
141
|
+
<pre>{{ pretty(selectedStore.state) }}</pre>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<!-- Edit functionality is temporarily disabled due to issues with state updates not being reflected in the UI. This is likely related to how the editPiniaState function applies changes and how the store's reactivity system detects those changes. Further investigation is needed to determine the root cause and implement a proper fix.
|
|
145
|
+
<div v-if="selectedStore" class="block edit-box">
|
|
146
|
+
<h4>Edit state path</h4>
|
|
147
|
+
<input v-model="editPath" placeholder="preferences.theme" />
|
|
148
|
+
<textarea v-model="editValue" rows="4" placeholder='"dark" or {"enabled":true}' />
|
|
149
|
+
<div class="actions">
|
|
150
|
+
<button @click="applyEdit">Apply edit</button>
|
|
151
|
+
<small v-if="editError" class="error">{{ editError }}</small>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
-->
|
|
155
|
+
|
|
156
|
+
<div v-if="selectedEvent" class="block">
|
|
157
|
+
<h4>Selected event</h4>
|
|
158
|
+
<p class="meta">
|
|
159
|
+
{{ selectedEvent.kind }} · {{ selectedEvent.name }} · {{ selectedEvent.status }} ·
|
|
160
|
+
{{ renderDuration(selectedEvent) }}
|
|
161
|
+
</p>
|
|
162
|
+
<h5>Diff</h5>
|
|
163
|
+
<ul class="diff-list">
|
|
164
|
+
<li v-for="item in selectedEvent.diff" :key="item.path">
|
|
165
|
+
<code>{{ item.path }}</code>
|
|
166
|
+
</li>
|
|
167
|
+
</ul>
|
|
168
|
+
<h5>Before</h5>
|
|
169
|
+
<pre>{{ pretty(selectedEvent.beforeState) }}</pre>
|
|
170
|
+
<h5>After</h5>
|
|
171
|
+
<pre>{{ pretty(selectedEvent.afterState) }}</pre>
|
|
172
|
+
</div>
|
|
173
|
+
</section>
|
|
174
|
+
</div>
|
|
175
|
+
</section>
|
|
176
|
+
</template>
|
|
177
|
+
|
|
178
|
+
<style scoped>
|
|
179
|
+
.pinia-tracker {
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: column;
|
|
182
|
+
height: 100%;
|
|
183
|
+
gap: 12px;
|
|
184
|
+
padding: 12px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.toolbar {
|
|
188
|
+
display: flex;
|
|
189
|
+
justify-content: space-between;
|
|
190
|
+
align-items: center;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.toolbar h2 {
|
|
194
|
+
margin: 0;
|
|
195
|
+
font-size: 14px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.toolbar-actions {
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
gap: 8px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.status {
|
|
205
|
+
color: var(--text3);
|
|
206
|
+
font-size: 12px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.status.connected {
|
|
210
|
+
color: var(--green);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.content-grid {
|
|
214
|
+
display: grid;
|
|
215
|
+
grid-template-columns: 220px 280px 1fr;
|
|
216
|
+
gap: 10px;
|
|
217
|
+
min-height: 0;
|
|
218
|
+
flex: 1;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.panel {
|
|
222
|
+
border: 1px solid var(--border);
|
|
223
|
+
border-radius: 8px;
|
|
224
|
+
background: var(--bg2);
|
|
225
|
+
padding: 10px;
|
|
226
|
+
min-height: 0;
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
gap: 8px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.panel h3 {
|
|
233
|
+
margin: 0;
|
|
234
|
+
font-size: 12px;
|
|
235
|
+
color: var(--text2);
|
|
236
|
+
letter-spacing: 0.02em;
|
|
237
|
+
text-transform: uppercase;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.stores,
|
|
241
|
+
.timeline,
|
|
242
|
+
.inspector,
|
|
243
|
+
.list {
|
|
244
|
+
overflow: auto;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.store-row,
|
|
248
|
+
.event-row {
|
|
249
|
+
width: 100%;
|
|
250
|
+
border: 1px solid var(--border);
|
|
251
|
+
border-radius: 6px;
|
|
252
|
+
background: var(--bg3);
|
|
253
|
+
color: var(--text);
|
|
254
|
+
text-align: left;
|
|
255
|
+
padding: 8px;
|
|
256
|
+
display: flex;
|
|
257
|
+
flex-direction: column;
|
|
258
|
+
gap: 2px;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.store-row.active,
|
|
263
|
+
.event-row.active {
|
|
264
|
+
border-color: var(--purple);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.store-row small,
|
|
268
|
+
.event-row small {
|
|
269
|
+
color: var(--text3);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.block {
|
|
273
|
+
border-top: 1px solid var(--border);
|
|
274
|
+
padding-top: 8px;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.block h4,
|
|
278
|
+
.block h5 {
|
|
279
|
+
margin: 0 0 6px;
|
|
280
|
+
font-size: 12px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.meta {
|
|
284
|
+
margin: 0 0 6px;
|
|
285
|
+
color: var(--text2);
|
|
286
|
+
font-size: 12px;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
pre {
|
|
290
|
+
margin: 0;
|
|
291
|
+
background: var(--bg3);
|
|
292
|
+
border: 1px solid var(--border);
|
|
293
|
+
border-radius: 6px;
|
|
294
|
+
padding: 8px;
|
|
295
|
+
white-space: pre-wrap;
|
|
296
|
+
word-break: break-word;
|
|
297
|
+
font-size: 11px;
|
|
298
|
+
max-height: 200px;
|
|
299
|
+
overflow: auto;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.edit-box input,
|
|
303
|
+
.edit-box textarea {
|
|
304
|
+
width: 100%;
|
|
305
|
+
background: var(--bg3);
|
|
306
|
+
border: 1px solid var(--border);
|
|
307
|
+
border-radius: 6px;
|
|
308
|
+
color: var(--text);
|
|
309
|
+
padding: 6px;
|
|
310
|
+
font-size: 12px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.actions {
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.error {
|
|
320
|
+
color: var(--red);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.empty {
|
|
324
|
+
padding: 24px;
|
|
325
|
+
border: 1px dashed var(--border);
|
|
326
|
+
border-radius: 8px;
|
|
327
|
+
color: var(--text2);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.diff-list {
|
|
331
|
+
margin: 0;
|
|
332
|
+
padding-left: 16px;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.danger {
|
|
336
|
+
border: 1px solid var(--border);
|
|
337
|
+
border-radius: 6px;
|
|
338
|
+
background: var(--bg2);
|
|
339
|
+
color: var(--text);
|
|
340
|
+
padding: 6px 10px;
|
|
341
|
+
cursor: pointer;
|
|
342
|
+
}
|
|
343
|
+
</style>
|
|
@@ -380,6 +380,22 @@ const visibleNodes = computed<TreeNodeData[]>(() => {
|
|
|
380
380
|
|
|
381
381
|
const visibleLeafCountById = computed(() => buildLeafCountMap(visibleNodes.value))
|
|
382
382
|
|
|
383
|
+
function findNodeById(roots: TreeNodeData[], id: string): TreeNodeData | null {
|
|
384
|
+
const stack = [...roots]
|
|
385
|
+
|
|
386
|
+
while (stack.length) {
|
|
387
|
+
const node = stack.pop()!
|
|
388
|
+
|
|
389
|
+
if (node.id === id) {
|
|
390
|
+
return node
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
stack.push(...node.children)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
|
|
383
399
|
watch([visibleNodes, selectedNode], ([currentNodes, currentSelected]) => {
|
|
384
400
|
if (!currentSelected) {
|
|
385
401
|
return
|
|
@@ -396,6 +412,15 @@ watch([visibleNodes, selectedNode], ([currentNodes, currentSelected]) => {
|
|
|
396
412
|
|
|
397
413
|
if (!ids.has(currentSelected.id)) {
|
|
398
414
|
selectedNode.value = null
|
|
415
|
+
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Keep details reactive: remap to the latest node instance after snapshots refresh.
|
|
420
|
+
const latest = findNodeById(currentNodes, currentSelected.id)
|
|
421
|
+
|
|
422
|
+
if (latest && latest !== currentSelected) {
|
|
423
|
+
selectedNode.value = latest
|
|
399
424
|
}
|
|
400
425
|
})
|
|
401
426
|
|
package/dist/module.d.mts
CHANGED
|
@@ -39,6 +39,11 @@ interface ModuleOptions {
|
|
|
39
39
|
* @default 300
|
|
40
40
|
*/
|
|
41
41
|
maxComposableEntries?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Maximum number of Pinia timeline events to keep per store
|
|
44
|
+
* @default 100
|
|
45
|
+
*/
|
|
46
|
+
maxPiniaTimeline?: number;
|
|
42
47
|
/**
|
|
43
48
|
* Maximum number of render timeline events per entry
|
|
44
49
|
* @default 100
|
|
@@ -66,6 +71,11 @@ interface ModuleOptions {
|
|
|
66
71
|
* @default true
|
|
67
72
|
*/
|
|
68
73
|
composableTracker?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Enable the Pinia state tracker tab
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
piniaTracker?: boolean;
|
|
69
79
|
/**
|
|
70
80
|
* Enable the render heatmap tab
|
|
71
81
|
* @default true
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -518,6 +518,7 @@ const defaults = {
|
|
|
518
518
|
fetchDashboard: process.env.OBSERVATORY_FETCH_DASHBOARD === "true",
|
|
519
519
|
provideInjectGraph: process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true",
|
|
520
520
|
composableTracker: process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true",
|
|
521
|
+
piniaTracker: process.env.OBSERVATORY_PINIA_TRACKER === "true",
|
|
521
522
|
renderHeatmap: process.env.OBSERVATORY_RENDER_HEATMAP === "true",
|
|
522
523
|
transitionTracker: process.env.OBSERVATORY_TRANSITION_TRACKER === "true",
|
|
523
524
|
traceViewer: process.env.OBSERVATORY_TRACE_VIEWER === "true",
|
|
@@ -529,6 +530,7 @@ const defaults = {
|
|
|
529
530
|
maxTransitions: process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500,
|
|
530
531
|
maxComposableHistory: process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50,
|
|
531
532
|
maxComposableEntries: process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300,
|
|
533
|
+
maxPiniaTimeline: process.env.OBSERVATORY_MAX_PINIA_TIMELINE ? Number(process.env.OBSERVATORY_MAX_PINIA_TIMELINE) : 100,
|
|
532
534
|
maxRenderTimeline: process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100,
|
|
533
535
|
composableNavigationMode: process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route",
|
|
534
536
|
heatmapHideInternals: process.env.OBSERVATORY_HEATMAP_HIDE_INTERNALS === "true",
|
|
@@ -557,6 +559,7 @@ const module$1 = defineNuxtModule({
|
|
|
557
559
|
fetchDashboard: options.fetchDashboard ?? (process.env.OBSERVATORY_FETCH_DASHBOARD ? process.env.OBSERVATORY_FETCH_DASHBOARD === "true" : true),
|
|
558
560
|
provideInjectGraph: options.provideInjectGraph ?? (process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH ? process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true" : true),
|
|
559
561
|
composableTracker: options.composableTracker ?? (process.env.OBSERVATORY_COMPOSABLE_TRACKER ? process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true" : true),
|
|
562
|
+
piniaTracker: options.piniaTracker ?? (process.env.OBSERVATORY_PINIA_TRACKER ? process.env.OBSERVATORY_PINIA_TRACKER === "true" : true),
|
|
560
563
|
renderHeatmap: options.renderHeatmap ?? (process.env.OBSERVATORY_RENDER_HEATMAP ? process.env.OBSERVATORY_RENDER_HEATMAP === "true" : true),
|
|
561
564
|
transitionTracker: options.transitionTracker ?? (process.env.OBSERVATORY_TRANSITION_TRACKER ? process.env.OBSERVATORY_TRANSITION_TRACKER === "true" : true),
|
|
562
565
|
traceViewer: options.traceViewer ?? (process.env.OBSERVATORY_TRACE_VIEWER ? process.env.OBSERVATORY_TRACE_VIEWER === "true" : true),
|
|
@@ -569,6 +572,7 @@ const module$1 = defineNuxtModule({
|
|
|
569
572
|
maxTransitions: options.maxTransitions ?? (process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500),
|
|
570
573
|
maxComposableHistory: options.maxComposableHistory ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50),
|
|
571
574
|
maxComposableEntries: options.maxComposableEntries ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300),
|
|
575
|
+
maxPiniaTimeline: options.maxPiniaTimeline ?? (process.env.OBSERVATORY_MAX_PINIA_TIMELINE ? Number(process.env.OBSERVATORY_MAX_PINIA_TIMELINE) : 100),
|
|
572
576
|
maxRenderTimeline: options.maxRenderTimeline ?? (process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100),
|
|
573
577
|
composableNavigationMode: options.composableNavigationMode ?? (process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route"),
|
|
574
578
|
debugRpc: options.debugRpc ?? (process.env.OBSERVATORY_DEBUG_RPC ? process.env.OBSERVATORY_DEBUG_RPC === "true" : false)
|
|
@@ -603,7 +607,7 @@ const module$1 = defineNuxtModule({
|
|
|
603
607
|
if (resolved.transitionTracker) {
|
|
604
608
|
addVitePlugin(transitionTrackerPlugin(), vitePluginScope);
|
|
605
609
|
}
|
|
606
|
-
if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.renderHeatmap || resolved.transitionTracker) {
|
|
610
|
+
if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.piniaTracker || resolved.renderHeatmap || resolved.transitionTracker) {
|
|
607
611
|
addPlugin(resolver.resolve("./runtime/plugin"));
|
|
608
612
|
}
|
|
609
613
|
if (resolved.fetchDashboard || resolved.traceViewer && resolved.instrumentServer) {
|
|
@@ -620,6 +624,7 @@ const module$1 = defineNuxtModule({
|
|
|
620
624
|
fetch: [],
|
|
621
625
|
provideInject: { provides: [], injects: [] },
|
|
622
626
|
composables: [],
|
|
627
|
+
piniaStores: [],
|
|
623
628
|
renders: [],
|
|
624
629
|
transitions: [],
|
|
625
630
|
traces: [],
|
|
@@ -627,6 +632,7 @@ const module$1 = defineNuxtModule({
|
|
|
627
632
|
fetchDashboard: !!resolved.fetchDashboard,
|
|
628
633
|
provideInjectGraph: !!resolved.provideInjectGraph,
|
|
629
634
|
composableTracker: !!resolved.composableTracker,
|
|
635
|
+
piniaTracker: !!resolved.piniaTracker,
|
|
630
636
|
composableNavigationMode: resolved.composableNavigationMode,
|
|
631
637
|
fetchPageSize: resolved.fetchPageSize,
|
|
632
638
|
renderHeatmap: !!resolved.renderHeatmap,
|
|
@@ -655,6 +661,7 @@ const module$1 = defineNuxtModule({
|
|
|
655
661
|
debugLog("received host snapshot", {
|
|
656
662
|
fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
|
|
657
663
|
composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
|
|
664
|
+
piniaStores: Array.isArray(snapshot.piniaStores) ? snapshot.piniaStores.length : 0,
|
|
658
665
|
renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
|
|
659
666
|
transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0
|
|
660
667
|
});
|
|
@@ -680,6 +687,12 @@ const module$1 = defineNuxtModule({
|
|
|
680
687
|
},
|
|
681
688
|
async editComposableValue(id, key, value) {
|
|
682
689
|
emitCommand({ cmd: "edit-composable", id, key, value });
|
|
690
|
+
},
|
|
691
|
+
async clearPiniaStores() {
|
|
692
|
+
emitCommand({ cmd: "clear-pinia" });
|
|
693
|
+
},
|
|
694
|
+
async editPiniaState(storeId, path, value) {
|
|
695
|
+
emitCommand({ cmd: "edit-pinia", storeId, path, value });
|
|
683
696
|
}
|
|
684
697
|
},
|
|
685
698
|
nuxt
|
|
@@ -687,7 +700,7 @@ const module$1 = defineNuxtModule({
|
|
|
687
700
|
rpc.broadcast.onSnapshot.asEvent(latestSnapshot);
|
|
688
701
|
}, nuxt);
|
|
689
702
|
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
690
|
-
if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.renderHeatmap || resolved.transitionTracker) {
|
|
703
|
+
if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.piniaTracker || resolved.renderHeatmap || resolved.transitionTracker) {
|
|
691
704
|
tabs.push({
|
|
692
705
|
name: "observatory-trackers",
|
|
693
706
|
title: "Observatory Trackers",
|
|
@@ -701,6 +714,7 @@ const module$1 = defineNuxtModule({
|
|
|
701
714
|
fetchDashboard: resolved.fetchDashboard,
|
|
702
715
|
provideInjectGraph: resolved.provideInjectGraph,
|
|
703
716
|
composableTracker: resolved.composableTracker,
|
|
717
|
+
piniaTracker: resolved.piniaTracker,
|
|
704
718
|
renderHeatmap: resolved.renderHeatmap,
|
|
705
719
|
transitionTracker: resolved.transitionTracker,
|
|
706
720
|
traceViewer: resolved.traceViewer,
|
|
@@ -710,6 +724,7 @@ const module$1 = defineNuxtModule({
|
|
|
710
724
|
maxTransitions: resolved.maxTransitions,
|
|
711
725
|
maxComposableHistory: resolved.maxComposableHistory,
|
|
712
726
|
maxComposableEntries: resolved.maxComposableEntries,
|
|
727
|
+
maxPiniaTimeline: resolved.maxPiniaTimeline,
|
|
713
728
|
maxRenderTimeline: resolved.maxRenderTimeline,
|
|
714
729
|
composableNavigationMode: resolved.composableNavigationMode,
|
|
715
730
|
heatmapHideInternals: resolved.heatmapHideInternals,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { PiniaHydrationEvent, PiniaStateDiff, PiniaStoreDependency } from '../../types/snapshot.js';
|
|
2
|
+
export declare function setupPiniaStoreRegistry(options: {
|
|
3
|
+
pinia?: unknown;
|
|
4
|
+
nuxtPayload?: unknown;
|
|
5
|
+
maxTimeline?: number;
|
|
6
|
+
stackProvider?: () => string[];
|
|
7
|
+
}): {
|
|
8
|
+
clear: () => void;
|
|
9
|
+
editState: (storeId: string, path: string, value: unknown) => void;
|
|
10
|
+
getAll: () => {
|
|
11
|
+
state: unknown;
|
|
12
|
+
dependencies: PiniaStoreDependency[];
|
|
13
|
+
hydrationTimeline: {
|
|
14
|
+
at: number;
|
|
15
|
+
source: "nuxt-payload" | "persistedstate" | "runtime" | "unknown";
|
|
16
|
+
details?: string;
|
|
17
|
+
}[];
|
|
18
|
+
timeline: {
|
|
19
|
+
beforeState: unknown;
|
|
20
|
+
afterState: unknown;
|
|
21
|
+
diff: PiniaStateDiff[];
|
|
22
|
+
id: string;
|
|
23
|
+
storeId: string;
|
|
24
|
+
storeName: string;
|
|
25
|
+
kind: "action" | "mutation";
|
|
26
|
+
name: string;
|
|
27
|
+
startTime: number;
|
|
28
|
+
endTime?: number;
|
|
29
|
+
durationMs?: number;
|
|
30
|
+
status: "active" | "ok" | "error";
|
|
31
|
+
callerStack?: string[];
|
|
32
|
+
payload?: unknown;
|
|
33
|
+
error?: string;
|
|
34
|
+
}[];
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
lastMutationAt?: number;
|
|
38
|
+
lastActionAt?: number;
|
|
39
|
+
hydration?: PiniaHydrationEvent;
|
|
40
|
+
}[];
|
|
41
|
+
getSnapshot: () => string;
|
|
42
|
+
onChange: (cb: () => void) => () => void;
|
|
43
|
+
teardown: () => void;
|
|
44
|
+
};
|