nuxt-devtools-observatory 0.1.31 → 0.1.33

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.
Files changed (44) hide show
  1. package/README.md +79 -46
  2. package/client/.env.example +1 -0
  3. package/client/dist/assets/index-BqKYgjVB.js +20 -0
  4. package/client/dist/assets/index-bs1JBJ2u.css +1 -0
  5. package/client/dist/index.html +2 -2
  6. package/client/src/App.vue +4 -0
  7. package/client/src/components/Flamegraph.vue +7 -8
  8. package/client/src/components/SpanInspector.vue +1 -1
  9. package/client/src/components/TraceFilter.vue +0 -2
  10. package/client/src/components/WaterfallView.vue +1 -1
  11. package/client/src/composables/composable-search.ts +127 -0
  12. package/client/src/composables/trace-render-aggregation.ts +263 -0
  13. package/client/src/composables/useExportImport.ts +63 -0
  14. package/client/src/composables/useTraceFilter.ts +1 -5
  15. package/client/src/composables/useVirtualizationConfig.ts +40 -0
  16. package/client/src/composables/useVirtualizationFlags.ts +129 -0
  17. package/client/src/stores/observatory.ts +9 -1
  18. package/client/src/views/ComposableTracker.vue +273 -97
  19. package/client/src/views/FetchDashboard.vue +181 -16
  20. package/client/src/views/ProvideInjectGraph.vue +41 -18
  21. package/client/src/views/RenderHeatmap.vue +392 -76
  22. package/client/src/views/TraceViewer.vue +797 -14
  23. package/client/src/views/TransitionTimeline.vue +112 -19
  24. package/dist/module.d.mts +5 -0
  25. package/dist/module.json +1 -1
  26. package/dist/module.mjs +12 -23
  27. package/dist/runtime/composables/composable-registry.d.ts +19 -0
  28. package/dist/runtime/composables/composable-registry.js +63 -5
  29. package/dist/runtime/composables/render-registry.js +23 -13
  30. package/dist/runtime/instrumentation/asyncData.d.ts +1 -1
  31. package/dist/runtime/instrumentation/fetch.d.ts +7 -1
  32. package/dist/runtime/instrumentation/fetch.js +22 -1
  33. package/dist/runtime/nitro/fetch-capture.d.ts +1 -2
  34. package/dist/runtime/nitro/fetch-capture.js +85 -7
  35. package/dist/runtime/nitro/ssr-trace-store.d.ts +85 -0
  36. package/dist/runtime/nitro/ssr-trace-store.js +84 -0
  37. package/dist/runtime/plugin.js +48 -1
  38. package/dist/runtime/test-bridge.d.ts +18 -0
  39. package/dist/runtime/test-bridge.js +86 -0
  40. package/dist/runtime/tracing/trace.d.ts +1 -1
  41. package/package.json +18 -3
  42. package/client/.env +0 -17
  43. package/client/dist/assets/index-BuMXDBO9.js +0 -17
  44. package/client/dist/assets/index-CwcspZ6w.css +0 -1
@@ -0,0 +1,63 @@
1
+ export interface ObservatoryExportFile<T> {
2
+ type: 'observatory-traces' | 'observatory-renders'
3
+ version: '1'
4
+ exportedAt: number
5
+ count: number
6
+ data: T[]
7
+ }
8
+
9
+ export function exportJson(filename: string, envelope: ObservatoryExportFile<unknown>): void {
10
+ const json = JSON.stringify(envelope, null, 2)
11
+ const blob = new Blob([json], { type: 'application/json' })
12
+ const url = URL.createObjectURL(blob)
13
+ const anchor = document.createElement('a')
14
+ anchor.href = url
15
+ anchor.download = filename
16
+ anchor.click()
17
+ URL.revokeObjectURL(url)
18
+ }
19
+
20
+ export function importJson(): Promise<unknown> {
21
+ return new Promise((resolve, reject) => {
22
+ const input = document.createElement('input')
23
+ input.type = 'file'
24
+ input.accept = '.json,application/json'
25
+
26
+ input.addEventListener('change', () => {
27
+ const file = input.files?.[0]
28
+
29
+ if (!file) {
30
+ reject(new Error('No file selected'))
31
+ return
32
+ }
33
+
34
+ const reader = new FileReader()
35
+
36
+ reader.addEventListener('load', () => {
37
+ try {
38
+ resolve(JSON.parse(reader.result as string))
39
+ } catch {
40
+ reject(new Error('Invalid JSON file'))
41
+ }
42
+ })
43
+
44
+ reader.addEventListener('error', () => reject(new Error('Failed to read file')))
45
+ reader.readAsText(file)
46
+ })
47
+
48
+ // Reject if the dialog is cancelled (focus returns without a change event)
49
+ window.addEventListener(
50
+ 'focus',
51
+ () => {
52
+ setTimeout(() => {
53
+ if (!input.files?.length) {
54
+ reject(new Error('cancelled'))
55
+ }
56
+ }, 300)
57
+ },
58
+ { once: true }
59
+ )
60
+
61
+ input.click()
62
+ })
63
+ }
@@ -65,11 +65,7 @@ export function useTraceFilter() {
65
65
  return false
66
66
  }
67
67
 
68
- function matchesDurationFilter(
69
- trace: TraceEntry,
70
- min: number,
71
- max: number
72
- ): boolean {
68
+ function matchesDurationFilter(trace: TraceEntry, min: number, max: number): boolean {
73
69
  const hasExplicitDurationFilter = min > 0 || max < Infinity
74
70
 
75
71
  if (trace.durationMs === undefined) {
@@ -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 }
@@ -1,7 +1,15 @@
1
1
  import { ref } from 'vue'
2
2
  import { useDevtoolsClient, onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'
3
3
  import type { ObservatorySnapshot, ObservatoryServerFunctions, ObservatoryClientFunctions } from '@observatory/types/rpc'
4
- import type { FetchEntry, ProvideEntry, InjectEntry, ComposableEntry, RenderEntry, TransitionEntry, TraceEntry } from '@observatory/types/snapshot'
4
+ import type {
5
+ FetchEntry,
6
+ ProvideEntry,
7
+ InjectEntry,
8
+ ComposableEntry,
9
+ RenderEntry,
10
+ TransitionEntry,
11
+ TraceEntry,
12
+ } from '@observatory/types/snapshot'
5
13
 
6
14
  type ProvideInjectSnapshot = { provides: ProvideEntry[]; injects: InjectEntry[] }
7
15