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.
Files changed (36) hide show
  1. package/README.md +37 -1
  2. package/client/.env.example +2 -0
  3. package/client/dist/assets/index-BO7neKEi.css +1 -0
  4. package/client/dist/assets/index-fFBuk6M6.js +20 -0
  5. package/client/dist/index.html +2 -2
  6. package/client/src/App.vue +8 -0
  7. package/client/src/components/Flamegraph.vue +4 -4
  8. package/client/src/components/SpanInspector.vue +1 -1
  9. package/client/src/composables/composable-search.ts +3 -0
  10. package/client/src/composables/trace-render-aggregation.ts +11 -2
  11. package/client/src/composables/useVirtualizationConfig.ts +40 -0
  12. package/client/src/composables/useVirtualizationFlags.ts +129 -0
  13. package/client/src/stores/observatory.ts +20 -0
  14. package/client/src/views/ComposableTracker.vue +212 -71
  15. package/client/src/views/FetchDashboard.vue +181 -16
  16. package/client/src/views/PiniaStoreTracker.vue +343 -0
  17. package/client/src/views/ProvideInjectGraph.vue +66 -18
  18. package/client/src/views/RenderHeatmap.vue +329 -75
  19. package/client/src/views/TraceViewer.vue +190 -20
  20. package/client/src/views/TransitionTimeline.vue +112 -19
  21. package/dist/module.d.mts +15 -0
  22. package/dist/module.json +1 -1
  23. package/dist/module.mjs +28 -24
  24. package/dist/runtime/composables/pinia-store-registry.d.ts +44 -0
  25. package/dist/runtime/composables/pinia-store-registry.js +447 -0
  26. package/dist/runtime/composables/provide-inject-registry.js +13 -8
  27. package/dist/runtime/composables/render-registry.js +6 -4
  28. package/dist/runtime/instrumentation/asyncData.d.ts +1 -1
  29. package/dist/runtime/instrumentation/fetch.d.ts +7 -1
  30. package/dist/runtime/instrumentation/fetch.js +22 -1
  31. package/dist/runtime/plugin.js +39 -2
  32. package/dist/runtime/test-bridge.d.ts +18 -0
  33. package/dist/runtime/test-bridge.js +100 -0
  34. package/package.json +14 -3
  35. package/client/dist/assets/index-5Wl1XYRH.js +0 -17
  36. package/client/dist/assets/index-DT_QUiIh.css +0 -1
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addServerPlugin } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addVitePlugin, addImports, addPlugin, addServerPlugin } from '@nuxt/kit';
2
2
  import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
3
3
  import sirv from 'sirv';
4
4
  import { parse } from '@babel/parser';
@@ -209,10 +209,6 @@ function fetchInstrumentPlugin() {
209
209
  plugins: ["typescript"]
210
210
  });
211
211
  let modified = false;
212
- let needsFetchCallHelper = false;
213
- let needsTracedAsyncDataHelper = false;
214
- const hasFetchCallImport = scriptCode.includes("__devFetchCall");
215
- const hasTracedAsyncDataImport = scriptCode.includes("useTracedAsyncData");
216
212
  traverse$1(ast, {
217
213
  CallExpression(path) {
218
214
  if (path.node.__observatoryTransformed) {
@@ -284,7 +280,6 @@ function fetchInstrumentPlugin() {
284
280
  ]);
285
281
  newCall.__observatoryTransformed = true;
286
282
  path.replaceWith(newCall);
287
- needsTracedAsyncDataHelper = true;
288
283
  modified = true;
289
284
  } else {
290
285
  const newCall = t.callExpression(t.identifier("__devFetchCall"), [
@@ -294,7 +289,6 @@ function fetchInstrumentPlugin() {
294
289
  meta
295
290
  ]);
296
291
  newCall.__observatoryTransformed = true;
297
- needsFetchCallHelper = true;
298
292
  path.replaceWith(newCall);
299
293
  modified = true;
300
294
  }
@@ -303,18 +297,12 @@ function fetchInstrumentPlugin() {
303
297
  if (!modified) {
304
298
  return null;
305
299
  }
306
- const fetchImportNames = [needsFetchCallHelper && !hasFetchCallImport ? "__devFetchCall" : ""].filter(Boolean);
307
- const fetchImportStatement = fetchImportNames.length ? `import { ${fetchImportNames.join(", ")} } from 'nuxt-devtools-observatory/runtime/fetch-registry';
308
- ` : "";
309
- const asyncDataImportStatement = needsTracedAsyncDataHelper && !hasTracedAsyncDataImport ? `import { useTracedAsyncData } from 'nuxt-devtools-observatory/runtime/async-data-instrumentation';
310
- ` : "";
311
- const importStatement = fetchImportStatement + asyncDataImportStatement;
312
300
  const output = generate$1(ast, { retainLines: true }, scriptCode);
313
301
  let finalCode;
314
302
  if (isVue) {
315
- finalCode = code.slice(0, scriptStart) + importStatement + output.code + code.slice(scriptStart + scriptCode.length);
303
+ finalCode = code.slice(0, scriptStart) + output.code + code.slice(scriptStart + scriptCode.length);
316
304
  } else {
317
- finalCode = importStatement + output.code;
305
+ finalCode = output.code;
318
306
  }
319
307
  return {
320
308
  code: finalCode,
@@ -530,6 +518,7 @@ const defaults = {
530
518
  fetchDashboard: process.env.OBSERVATORY_FETCH_DASHBOARD === "true",
531
519
  provideInjectGraph: process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true",
532
520
  composableTracker: process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true",
521
+ piniaTracker: process.env.OBSERVATORY_PINIA_TRACKER === "true",
533
522
  renderHeatmap: process.env.OBSERVATORY_RENDER_HEATMAP === "true",
534
523
  transitionTracker: process.env.OBSERVATORY_TRANSITION_TRACKER === "true",
535
524
  traceViewer: process.env.OBSERVATORY_TRACE_VIEWER === "true",
@@ -537,9 +526,11 @@ const defaults = {
537
526
  heatmapThresholdTime: process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME) : 1600,
538
527
  maxFetchEntries: process.env.OBSERVATORY_MAX_FETCH_ENTRIES ? Number(process.env.OBSERVATORY_MAX_FETCH_ENTRIES) : 200,
539
528
  maxPayloadBytes: process.env.OBSERVATORY_MAX_PAYLOAD_BYTES ? Number(process.env.OBSERVATORY_MAX_PAYLOAD_BYTES) : 1e4,
529
+ fetchPageSize: process.env.OBSERVATORY_FETCH_PAGE_SIZE ? Number(process.env.OBSERVATORY_FETCH_PAGE_SIZE) : 20,
540
530
  maxTransitions: process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500,
541
531
  maxComposableHistory: process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50,
542
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,
543
534
  maxRenderTimeline: process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100,
544
535
  composableNavigationMode: process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route",
545
536
  heatmapHideInternals: process.env.OBSERVATORY_HEATMAP_HIDE_INTERNALS === "true",
@@ -568,6 +559,7 @@ const module$1 = defineNuxtModule({
568
559
  fetchDashboard: options.fetchDashboard ?? (process.env.OBSERVATORY_FETCH_DASHBOARD ? process.env.OBSERVATORY_FETCH_DASHBOARD === "true" : true),
569
560
  provideInjectGraph: options.provideInjectGraph ?? (process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH ? process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true" : true),
570
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),
571
563
  renderHeatmap: options.renderHeatmap ?? (process.env.OBSERVATORY_RENDER_HEATMAP ? process.env.OBSERVATORY_RENDER_HEATMAP === "true" : true),
572
564
  transitionTracker: options.transitionTracker ?? (process.env.OBSERVATORY_TRANSITION_TRACKER ? process.env.OBSERVATORY_TRANSITION_TRACKER === "true" : true),
573
565
  traceViewer: options.traceViewer ?? (process.env.OBSERVATORY_TRACE_VIEWER ? process.env.OBSERVATORY_TRACE_VIEWER === "true" : true),
@@ -576,9 +568,11 @@ const module$1 = defineNuxtModule({
576
568
  heatmapThresholdTime: options.heatmapThresholdTime ?? (process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME) : 1600),
577
569
  maxFetchEntries: options.maxFetchEntries ?? (process.env.OBSERVATORY_MAX_FETCH_ENTRIES ? Number(process.env.OBSERVATORY_MAX_FETCH_ENTRIES) : 200),
578
570
  maxPayloadBytes: options.maxPayloadBytes ?? (process.env.OBSERVATORY_MAX_PAYLOAD_BYTES ? Number(process.env.OBSERVATORY_MAX_PAYLOAD_BYTES) : 1e4),
571
+ fetchPageSize: options.fetchPageSize ?? (process.env.OBSERVATORY_FETCH_PAGE_SIZE ? Number(process.env.OBSERVATORY_FETCH_PAGE_SIZE) : 20),
579
572
  maxTransitions: options.maxTransitions ?? (process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500),
580
573
  maxComposableHistory: options.maxComposableHistory ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50),
581
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),
582
576
  maxRenderTimeline: options.maxRenderTimeline ?? (process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100),
583
577
  composableNavigationMode: options.composableNavigationMode ?? (process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route"),
584
578
  debugRpc: options.debugRpc ?? (process.env.OBSERVATORY_DEBUG_RPC ? process.env.OBSERVATORY_DEBUG_RPC === "true" : false)
@@ -599,6 +593,10 @@ const module$1 = defineNuxtModule({
599
593
  const vitePluginScope = resolved.instrumentServer ? { server: true, client: true } : { server: false, client: true };
600
594
  if (resolved.fetchDashboard) {
601
595
  addVitePlugin(fetchInstrumentPlugin(), vitePluginScope);
596
+ addImports([
597
+ { name: "__devFetchCall", from: resolver.resolve("./runtime/composables/fetch-registry") },
598
+ { name: "useTracedAsyncData", from: resolver.resolve("./runtime/instrumentation/asyncData") }
599
+ ]);
602
600
  }
603
601
  if (resolved.provideInjectGraph) {
604
602
  addVitePlugin(provideInjectPlugin(), vitePluginScope);
@@ -609,7 +607,7 @@ const module$1 = defineNuxtModule({
609
607
  if (resolved.transitionTracker) {
610
608
  addVitePlugin(transitionTrackerPlugin(), vitePluginScope);
611
609
  }
612
- 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) {
613
611
  addPlugin(resolver.resolve("./runtime/plugin"));
614
612
  }
615
613
  if (resolved.fetchDashboard || resolved.traceViewer && resolved.instrumentServer) {
@@ -626,6 +624,7 @@ const module$1 = defineNuxtModule({
626
624
  fetch: [],
627
625
  provideInject: { provides: [], injects: [] },
628
626
  composables: [],
627
+ piniaStores: [],
629
628
  renders: [],
630
629
  transitions: [],
631
630
  traces: [],
@@ -633,7 +632,9 @@ const module$1 = defineNuxtModule({
633
632
  fetchDashboard: !!resolved.fetchDashboard,
634
633
  provideInjectGraph: !!resolved.provideInjectGraph,
635
634
  composableTracker: !!resolved.composableTracker,
635
+ piniaTracker: !!resolved.piniaTracker,
636
636
  composableNavigationMode: resolved.composableNavigationMode,
637
+ fetchPageSize: resolved.fetchPageSize,
637
638
  renderHeatmap: !!resolved.renderHeatmap,
638
639
  transitionTracker: !!resolved.transitionTracker,
639
640
  traceViewer: !!resolved.traceViewer
@@ -660,6 +661,7 @@ const module$1 = defineNuxtModule({
660
661
  debugLog("received host snapshot", {
661
662
  fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
662
663
  composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
664
+ piniaStores: Array.isArray(snapshot.piniaStores) ? snapshot.piniaStores.length : 0,
663
665
  renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
664
666
  transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0
665
667
  });
@@ -685,21 +687,20 @@ const module$1 = defineNuxtModule({
685
687
  },
686
688
  async editComposableValue(id, key, value) {
687
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 });
688
696
  }
689
697
  },
690
698
  nuxt
691
699
  );
692
700
  rpc.broadcast.onSnapshot.asEvent(latestSnapshot);
693
701
  }, nuxt);
694
- nuxt.hook("render:response", (response, { url }) => {
695
- if (url.startsWith("/trackers") || url === "/" || url.startsWith("/index.html")) {
696
- const configScript = `<script>window.__observatoryConfig = ${JSON.stringify(nuxt.options.runtimeConfig.public.observatory)};<\/script>`;
697
- response.body = response.body.replace("<head>", `<head>
698
- ${configScript}`);
699
- }
700
- });
701
702
  nuxt.hook("devtools:customTabs", (tabs) => {
702
- 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) {
703
704
  tabs.push({
704
705
  name: "observatory-trackers",
705
706
  title: "Observatory Trackers",
@@ -713,14 +714,17 @@ ${configScript}`);
713
714
  fetchDashboard: resolved.fetchDashboard,
714
715
  provideInjectGraph: resolved.provideInjectGraph,
715
716
  composableTracker: resolved.composableTracker,
717
+ piniaTracker: resolved.piniaTracker,
716
718
  renderHeatmap: resolved.renderHeatmap,
717
719
  transitionTracker: resolved.transitionTracker,
718
720
  traceViewer: resolved.traceViewer,
719
721
  maxFetchEntries: resolved.maxFetchEntries,
720
722
  maxPayloadBytes: resolved.maxPayloadBytes,
723
+ fetchPageSize: resolved.fetchPageSize,
721
724
  maxTransitions: resolved.maxTransitions,
722
725
  maxComposableHistory: resolved.maxComposableHistory,
723
726
  maxComposableEntries: resolved.maxComposableEntries,
727
+ maxPiniaTimeline: resolved.maxPiniaTimeline,
724
728
  maxRenderTimeline: resolved.maxRenderTimeline,
725
729
  composableNavigationMode: resolved.composableNavigationMode,
726
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
+ };
@@ -0,0 +1,447 @@
1
+ import { getCurrentInstance } from "vue";
2
+ function nowMs() {
3
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4
+ }
5
+ function safeSnapshot(value) {
6
+ try {
7
+ return JSON.parse(JSON.stringify(value));
8
+ } catch {
9
+ return value;
10
+ }
11
+ }
12
+ function parsePath(path) {
13
+ return path.replace(/\[(\d+)\]/g, ".$1").split(".").map((part) => part.trim()).filter(Boolean).map((part) => /^\d+$/.test(part) ? Number(part) : part);
14
+ }
15
+ function setAtPath(target, path, value) {
16
+ const parts = parsePath(path);
17
+ if (parts.length === 0) {
18
+ return;
19
+ }
20
+ let cursor = target;
21
+ for (let i = 0; i < parts.length - 1; i++) {
22
+ const key = parts[i];
23
+ if (!cursor || typeof cursor !== "object") {
24
+ return;
25
+ }
26
+ const objectCursor = cursor;
27
+ const next = objectCursor[String(key)];
28
+ if (next === void 0 || next === null || typeof next !== "object") {
29
+ objectCursor[String(key)] = typeof parts[i + 1] === "number" ? [] : {};
30
+ }
31
+ cursor = objectCursor[String(key)];
32
+ }
33
+ if (!cursor || typeof cursor !== "object") {
34
+ return;
35
+ }
36
+ ;
37
+ cursor[String(parts[parts.length - 1])] = value;
38
+ }
39
+ function collectDiff(before, after, path = "", depth = 0, out = []) {
40
+ if (out.length >= 80 || depth > 6) {
41
+ return out;
42
+ }
43
+ if (Object.is(before, after)) {
44
+ return out;
45
+ }
46
+ const beforeIsObj = before !== null && typeof before === "object";
47
+ const afterIsObj = after !== null && typeof after === "object";
48
+ if (!beforeIsObj || !afterIsObj) {
49
+ out.push({ path: path || "$", before, after });
50
+ return out;
51
+ }
52
+ const beforeRec = before;
53
+ const afterRec = after;
54
+ const keys = /* @__PURE__ */ new Set([...Object.keys(beforeRec), ...Object.keys(afterRec)]);
55
+ for (const key of keys) {
56
+ const nextPath = path ? `${path}.${key}` : key;
57
+ collectDiff(beforeRec[key], afterRec[key], nextPath, depth + 1, out);
58
+ if (out.length >= 80) {
59
+ break;
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+ function stackFromError() {
65
+ const stack = new Error().stack;
66
+ if (!stack) {
67
+ return [];
68
+ }
69
+ return stack.split("\n").slice(2, 10).map((line) => line.trim());
70
+ }
71
+ function inferDependencyFromInstance() {
72
+ const instance = getCurrentInstance();
73
+ if (!instance) {
74
+ return null;
75
+ }
76
+ const file = instance.type?.__file;
77
+ const name = instance.type?.__name || instance.type?.name || (file?.split("/").pop() ?? `component:${instance.uid}`);
78
+ return {
79
+ id: `component:${instance.uid}`,
80
+ kind: "component",
81
+ name,
82
+ file
83
+ };
84
+ }
85
+ function parseStackLine(line) {
86
+ const callsiteMatch = line.match(/^at\s+(.+?)\s+\((.+?):\d+:\d+\)$/);
87
+ if (callsiteMatch) {
88
+ return {
89
+ name: callsiteMatch[1]?.trim(),
90
+ file: callsiteMatch[2]?.trim()
91
+ };
92
+ }
93
+ const fileOnlyMatch = line.match(/^at\s+(.+?):\d+:\d+$/);
94
+ if (fileOnlyMatch) {
95
+ return {
96
+ file: fileOnlyMatch[1]?.trim()
97
+ };
98
+ }
99
+ return {};
100
+ }
101
+ function inferDependenciesFromStack(stack) {
102
+ const result = [];
103
+ for (const line of stack) {
104
+ const parsed = parseStackLine(line);
105
+ const file = parsed.file;
106
+ const rawName = parsed.name;
107
+ const name = rawName?.includes(".") ? rawName.split(".").pop() : rawName;
108
+ if (name && /^use[A-Z]/.test(name)) {
109
+ result.push({
110
+ id: `composable:${file ?? name}`,
111
+ kind: "composable",
112
+ name,
113
+ file
114
+ });
115
+ }
116
+ if (file?.includes("/composables/")) {
117
+ const fileName = file.split("/").pop() ?? "";
118
+ const composableName = fileName.replace(/\.(mjs|cjs|js|ts|tsx|vue)$/i, "");
119
+ if (/^use[A-Z]/.test(composableName)) {
120
+ result.push({
121
+ id: `composable-file:${file}`,
122
+ kind: "composable",
123
+ name: composableName,
124
+ file
125
+ });
126
+ }
127
+ }
128
+ if (file?.endsWith(".vue")) {
129
+ const fileName = file.split("/").pop() ?? file;
130
+ const componentName = fileName.replace(/\.vue$/i, "");
131
+ result.push({
132
+ id: `component-file:${file}`,
133
+ kind: "component",
134
+ name: componentName,
135
+ file
136
+ });
137
+ }
138
+ }
139
+ return result;
140
+ }
141
+ function pushUniqueDependency(deps, dependency) {
142
+ if (!dependency.id) {
143
+ return;
144
+ }
145
+ if (deps.some((item) => item.id === dependency.id)) {
146
+ return;
147
+ }
148
+ deps.push(dependency);
149
+ }
150
+ export function setupPiniaStoreRegistry(options) {
151
+ const pinia = options.pinia;
152
+ const maxTimeline = typeof options.maxTimeline === "number" ? options.maxTimeline : 100;
153
+ const stackProvider = options.stackProvider ?? stackFromError;
154
+ const entries = /* @__PURE__ */ new Map();
155
+ const stores = /* @__PURE__ */ new Map();
156
+ const stopHandles = /* @__PURE__ */ new Map();
157
+ const activeActions = /* @__PURE__ */ new Map();
158
+ const listeners = /* @__PURE__ */ new Set();
159
+ let dirty = true;
160
+ let cached = "[]";
161
+ function notifyChange() {
162
+ dirty = true;
163
+ for (const listener of listeners) {
164
+ listener();
165
+ }
166
+ }
167
+ function inferPersistedStorageDetails(store) {
168
+ const persistConfig = store.$persist ?? store.$options?.persist ?? store.persist;
169
+ if (!persistConfig) {
170
+ return "Persist plugin detected";
171
+ }
172
+ const list = Array.isArray(persistConfig) ? persistConfig : [persistConfig];
173
+ const storageLabels = /* @__PURE__ */ new Set();
174
+ for (const item of list) {
175
+ if (!item || typeof item !== "object") {
176
+ continue;
177
+ }
178
+ const storage = item.storage;
179
+ if (!storage || typeof storage !== "object") {
180
+ continue;
181
+ }
182
+ const candidate = storage;
183
+ if (typeof localStorage !== "undefined" && candidate.getItem === localStorage.getItem) {
184
+ storageLabels.add("localStorage");
185
+ } else if (typeof sessionStorage !== "undefined" && candidate.getItem === sessionStorage.getItem) {
186
+ storageLabels.add("sessionStorage");
187
+ } else {
188
+ storageLabels.add("custom storage");
189
+ }
190
+ }
191
+ if (storageLabels.size === 0) {
192
+ return "Persist plugin detected";
193
+ }
194
+ return `Persist plugin detected (${[...storageLabels].join(", ")})`;
195
+ }
196
+ function inferHydrationTimeline(store) {
197
+ const payload = options.nuxtPayload;
198
+ const fromPayload = !!payload?.pinia?.[store.$id];
199
+ const hasPersist = !!store.$persist || !!store.$options?.persist || !!store.persist;
200
+ const events = [];
201
+ let at = nowMs();
202
+ if (fromPayload) {
203
+ events.push({
204
+ at,
205
+ source: "nuxt-payload",
206
+ details: "Nuxt payload state detected"
207
+ });
208
+ at += 0.01;
209
+ }
210
+ if (hasPersist) {
211
+ events.push({
212
+ at,
213
+ source: "persistedstate",
214
+ details: inferPersistedStorageDetails(store)
215
+ });
216
+ at += 0.01;
217
+ }
218
+ if (events.length === 0) {
219
+ events.push({
220
+ at,
221
+ source: "runtime",
222
+ details: void 0
223
+ });
224
+ }
225
+ return events;
226
+ }
227
+ function trimTimeline(entry) {
228
+ if (entry.timeline.length <= maxTimeline) {
229
+ return;
230
+ }
231
+ entry.timeline.splice(0, entry.timeline.length - maxTimeline);
232
+ }
233
+ function ensureStore(store) {
234
+ if (!store?.$id || stores.has(store.$id)) {
235
+ return;
236
+ }
237
+ stores.set(store.$id, store);
238
+ const entry = {
239
+ id: store.$id,
240
+ name: store.$id,
241
+ state: safeSnapshot(store.$state),
242
+ dependencies: [],
243
+ timeline: [],
244
+ hydrationTimeline: inferHydrationTimeline(store),
245
+ hydration: void 0
246
+ };
247
+ entry.hydration = entry.hydrationTimeline[0];
248
+ entries.set(store.$id, entry);
249
+ const offAction = store.$onAction(({ name, args, after, onError }) => {
250
+ const actionId = `${store.$id}:action:${name}:${nowMs()}:${Math.random().toString(36).slice(2, 8)}`;
251
+ const start = nowMs();
252
+ const startSnapshot = safeSnapshot(store.$state);
253
+ const stack = stackProvider();
254
+ const dependenciesFromStack = inferDependenciesFromStack(stack);
255
+ const dependencyFromInstance = inferDependencyFromInstance();
256
+ const current = entries.get(store.$id);
257
+ if (!current) {
258
+ return;
259
+ }
260
+ if (dependencyFromInstance) {
261
+ pushUniqueDependency(current.dependencies, dependencyFromInstance);
262
+ }
263
+ for (const dependency of dependenciesFromStack) {
264
+ pushUniqueDependency(current.dependencies, dependency);
265
+ }
266
+ const event = {
267
+ id: actionId,
268
+ storeId: store.$id,
269
+ storeName: store.$id,
270
+ kind: "action",
271
+ name,
272
+ startTime: start,
273
+ status: "active",
274
+ beforeState: startSnapshot,
275
+ afterState: startSnapshot,
276
+ diff: [],
277
+ payload: safeSnapshot(args),
278
+ callerStack: stack
279
+ };
280
+ current.timeline.push(event);
281
+ trimTimeline(current);
282
+ current.lastActionAt = start;
283
+ activeActions.set(actionId, event);
284
+ notifyChange();
285
+ after(() => {
286
+ const end = nowMs();
287
+ const done = entries.get(store.$id);
288
+ if (!done) {
289
+ return;
290
+ }
291
+ const afterState = safeSnapshot(store.$state);
292
+ event.endTime = end;
293
+ event.durationMs = end - start;
294
+ event.status = "ok";
295
+ event.afterState = afterState;
296
+ event.diff = collectDiff(event.beforeState, afterState);
297
+ done.state = afterState;
298
+ done.lastMutationAt = end;
299
+ activeActions.delete(actionId);
300
+ notifyChange();
301
+ });
302
+ onError((error) => {
303
+ const end = nowMs();
304
+ const done = entries.get(store.$id);
305
+ if (!done) {
306
+ return;
307
+ }
308
+ const afterState = safeSnapshot(store.$state);
309
+ event.endTime = end;
310
+ event.durationMs = end - start;
311
+ event.status = "error";
312
+ event.afterState = afterState;
313
+ event.diff = collectDiff(event.beforeState, afterState);
314
+ event.error = error instanceof Error ? error.message : String(error);
315
+ done.state = afterState;
316
+ done.lastMutationAt = end;
317
+ activeActions.delete(actionId);
318
+ notifyChange();
319
+ });
320
+ });
321
+ const offSub = store.$subscribe(
322
+ (mutation, state) => {
323
+ const current = entries.get(store.$id);
324
+ if (!current) {
325
+ return;
326
+ }
327
+ const beforeState = safeSnapshot(current.state);
328
+ const afterState = safeSnapshot(state);
329
+ const at = nowMs();
330
+ const event = {
331
+ id: `${store.$id}:mutation:${at}:${Math.random().toString(36).slice(2, 8)}`,
332
+ storeId: store.$id,
333
+ storeName: store.$id,
334
+ kind: "mutation",
335
+ name: mutation.type,
336
+ startTime: at,
337
+ endTime: at,
338
+ durationMs: 0,
339
+ status: "ok",
340
+ beforeState,
341
+ afterState,
342
+ diff: collectDiff(beforeState, afterState),
343
+ payload: safeSnapshot(mutation.payload),
344
+ callerStack: stackProvider()
345
+ };
346
+ current.timeline.push(event);
347
+ trimTimeline(current);
348
+ current.state = afterState;
349
+ current.lastMutationAt = at;
350
+ notifyChange();
351
+ },
352
+ { detached: true, flush: "sync" }
353
+ );
354
+ stopHandles.set(store.$id, [offAction, offSub]);
355
+ notifyChange();
356
+ }
357
+ function clear() {
358
+ for (const [id, entry] of entries) {
359
+ const store = stores.get(id);
360
+ entry.timeline = [];
361
+ entry.dependencies = [];
362
+ entry.state = safeSnapshot(store?.$state ?? entry.state);
363
+ entry.lastActionAt = void 0;
364
+ entry.lastMutationAt = void 0;
365
+ }
366
+ notifyChange();
367
+ }
368
+ function editState(storeId, path, value) {
369
+ const store = stores.get(storeId);
370
+ if (!store || !path) {
371
+ return;
372
+ }
373
+ if (typeof store.$patch === "function") {
374
+ ;
375
+ store.$patch((state) => {
376
+ setAtPath(state, path, value);
377
+ });
378
+ } else {
379
+ setAtPath(store.$state, path, value);
380
+ }
381
+ notifyChange();
382
+ }
383
+ function getAll() {
384
+ return [...entries.values()].map((entry) => ({
385
+ ...entry,
386
+ state: safeSnapshot(entry.state),
387
+ dependencies: [...entry.dependencies],
388
+ hydrationTimeline: entry.hydrationTimeline.map((event) => ({ ...event })),
389
+ timeline: entry.timeline.map((event) => ({
390
+ ...event,
391
+ beforeState: safeSnapshot(event.beforeState),
392
+ afterState: safeSnapshot(event.afterState),
393
+ diff: [...event.diff]
394
+ }))
395
+ }));
396
+ }
397
+ function getSnapshot() {
398
+ if (!dirty) {
399
+ return cached;
400
+ }
401
+ try {
402
+ cached = JSON.stringify(getAll());
403
+ } catch {
404
+ cached = "[]";
405
+ }
406
+ dirty = false;
407
+ return cached;
408
+ }
409
+ function onChange(cb) {
410
+ listeners.add(cb);
411
+ return () => {
412
+ listeners.delete(cb);
413
+ };
414
+ }
415
+ function teardown() {
416
+ for (const handlers of stopHandles.values()) {
417
+ for (const off of handlers) {
418
+ off();
419
+ }
420
+ }
421
+ stopHandles.clear();
422
+ stores.clear();
423
+ entries.clear();
424
+ activeActions.clear();
425
+ listeners.clear();
426
+ dirty = true;
427
+ cached = "[]";
428
+ }
429
+ if (pinia?._s) {
430
+ for (const store of pinia._s.values()) {
431
+ ensureStore(store);
432
+ }
433
+ }
434
+ if (typeof pinia?.use === "function") {
435
+ pinia.use(({ store }) => {
436
+ ensureStore(store);
437
+ });
438
+ }
439
+ return {
440
+ clear,
441
+ editState,
442
+ getAll,
443
+ getSnapshot,
444
+ onChange,
445
+ teardown
446
+ };
447
+ }