nuxt-devtools-observatory 0.1.18 → 0.1.19

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.
@@ -177,14 +177,19 @@ const activeRoute = ref('')
177
177
  // Separate thresholds per mode so switching modes doesn't produce nonsense results.
178
178
  // Count: flag components that rendered 3+ times (1 hydration mount is normal).
179
179
  // Time: flag components averaging 16ms+ (one animation frame budget).
180
- const countThreshold = ref(3)
181
- const timeThreshold = ref(16)
180
+ const COUNT_THRESHOLD = import.meta.env.VITE_OBSERVATORY_HEATMAP_THRESHOLD_COUNT ?? 3
181
+ const TIME_THRESHOLD = import.meta.env.VITE_OBSERVATORY_HEATMAP_THRESHOLD_TIME ?? 1600
182
+ const countThreshold = ref(Number(COUNT_THRESHOLD))
183
+ const timeThreshold = ref(Number(TIME_THRESHOLD))
182
184
  // Writable computed so the threshold slider can use v-model directly.
183
185
  const activeThreshold = computed({
184
186
  get: () => (activeMode.value === 'count' ? countThreshold.value : timeThreshold.value),
185
187
  set: (val: number) => {
186
- if (activeMode.value === 'count') countThreshold.value = val
187
- else timeThreshold.value = val
188
+ if (activeMode.value === 'count') {
189
+ countThreshold.value = val
190
+ } else {
191
+ timeThreshold.value = val
192
+ }
188
193
  },
189
194
  })
190
195
  const activeHotOnly = ref(false)
@@ -299,6 +304,7 @@ function countSubtree(node: ComponentNode): number {
299
304
  function collectIds(node: ComponentNode, target = new Set<string>()) {
300
305
  target.add(node.id)
301
306
  node.children.forEach((child) => collectIds(child, target))
307
+
302
308
  return target
303
309
  }
304
310
 
@@ -344,13 +350,16 @@ function defaultExpandedIds(root: ComponentNode | null) {
344
350
  // Expand all nodes that have children — gives a fully-open tree on first load.
345
351
  // The user can collapse individual branches as needed.
346
352
  const expanded = new Set<string>()
353
+
347
354
  function expandAll(node: ComponentNode) {
348
355
  if (node.children.length > 0) {
349
356
  expanded.add(node.id)
350
357
  node.children.forEach(expandAll)
351
358
  }
352
359
  }
360
+
353
361
  expandAll(root)
362
+
354
363
  return expanded
355
364
  }
356
365
 
@@ -413,10 +422,16 @@ function subtreeHasHotNode(node: ComponentNode): boolean {
413
422
  }
414
423
 
415
424
  function nodeMatchesRoute(node: ComponentNode): boolean {
416
- if (!activeRoute.value) return true
425
+ if (!activeRoute.value) {
426
+ return true
427
+ }
428
+
417
429
  // A component is visible for a route if it was first seen on that route
418
430
  // OR if any of its timeline events happened on that route.
419
- if (node.route === activeRoute.value) return true
431
+ if (node.route === activeRoute.value) {
432
+ return true
433
+ }
434
+
420
435
  return node.timeline.some((e) => e.route === activeRoute.value)
421
436
  }
422
437
 
@@ -495,8 +510,12 @@ const appEntries = computed(() =>
495
510
 
496
511
  const knownRoutes = computed(() => {
497
512
  const routes = new Set<string>()
513
+
498
514
  for (const node of allComponents.value) {
499
- if (node.route) routes.add(node.route)
515
+ if (node.route) {
516
+ routes.add(node.route)
517
+ }
518
+
500
519
  for (const event of node.timeline) {
501
520
  if (event.route) routes.add(event.route)
502
521
  }
@@ -525,6 +544,7 @@ watch(
525
544
  activeSelectedId.value = null
526
545
  expandedIds.value = new Set()
527
546
  expansionReady.value = false
547
+
528
548
  return
529
549
  }
530
550
 
@@ -544,12 +564,12 @@ watch(
544
564
  if (!expansionReady.value) {
545
565
  expandedIds.value = defaultExpandedIds(activeRoot.value)
546
566
  expansionReady.value = true
567
+
547
568
  return
548
569
  }
549
570
 
550
571
  if (!search.value.trim() && activeSelectedId.value && activeRoot.value) {
551
572
  const selectedPath = pathToNode(activeRoot.value, activeSelectedId.value) ?? []
552
-
553
573
  selectedPath.forEach((id) => preserved.add(id))
554
574
  }
555
575
 
@@ -567,13 +587,14 @@ watch(search, (term) => {
567
587
 
568
588
  if (normalized) {
569
589
  expandedIds.value = searchExpandedIds(activeRoot.value, normalized)
590
+
570
591
  return
571
592
  }
572
593
 
573
594
  if (activeSelectedId.value) {
574
595
  const selectedPath = pathToNode(activeRoot.value, activeSelectedId.value)
575
-
576
596
  expandedIds.value = selectedPath ? new Set(selectedPath) : defaultExpandedIds(activeRoot.value)
597
+
577
598
  return
578
599
  }
579
600
 
@@ -589,6 +610,7 @@ watch([activeHotOnly, activeThreshold, activeMode, filteredRoots], () => {
589
610
 
590
611
  if (!topLevelRoot) {
591
612
  activeSelectedId.value = null
613
+
592
614
  return
593
615
  }
594
616
 
@@ -596,6 +618,7 @@ watch([activeHotOnly, activeThreshold, activeMode, filteredRoots], () => {
596
618
 
597
619
  if (!firstHot) {
598
620
  activeSelectedId.value = null
621
+
599
622
  return
600
623
  }
601
624
 
@@ -646,6 +669,7 @@ function toggleFreeze() {
646
669
  if (frozen.value) {
647
670
  frozen.value = false
648
671
  frozenSnapshot.value = []
672
+
649
673
  return
650
674
  }
651
675
 
@@ -663,9 +687,16 @@ function basename(file: string) {
663
687
  }
664
688
 
665
689
  function openInEditor(file: string) {
666
- if (!file || file === 'unknown') return
690
+ if (!file || file === 'unknown') {
691
+ return
692
+ }
693
+
667
694
  const origin = getObservatoryOrigin()
668
- if (!origin) return
695
+
696
+ if (!origin) {
697
+ return
698
+ }
699
+
669
700
  window.top?.postMessage({ type: 'observatory:open-in-editor', file }, origin)
670
701
  }
671
702
 
package/dist/module.d.mts CHANGED
@@ -1,6 +1,36 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface ModuleOptions {
4
+ /**
5
+ * Maximum number of fetch entries to keep in memory
6
+ * @default 200
7
+ */
8
+ maxFetchEntries?: number;
9
+ /**
10
+ * Maximum payload size (bytes) to store per fetch entry
11
+ * @default 10000
12
+ */
13
+ maxPayloadBytes?: number;
14
+ /**
15
+ * Maximum number of transition entries to keep in memory
16
+ * @default 500
17
+ */
18
+ maxTransitions?: number;
19
+ /**
20
+ * Maximum number of composable history events per entry
21
+ * @default 50
22
+ */
23
+ maxComposableHistory?: number;
24
+ /**
25
+ * Maximum number of composable entries to keep in memory
26
+ * @default 300
27
+ */
28
+ maxComposableEntries?: number;
29
+ /**
30
+ * Maximum number of render timeline events per entry
31
+ * @default 100
32
+ */
33
+ maxRenderTimeline?: number;
4
34
  /**
5
35
  * Enable the useFetch / useAsyncData dashboard tab
6
36
  * @default true
@@ -28,9 +58,14 @@ interface ModuleOptions {
28
58
  transitionTracker?: boolean;
29
59
  /**
30
60
  * Minimum render count / ms threshold to highlight in the heatmap
31
- * @default 5
61
+ * @default 3
62
+ */
63
+ heatmapThresholdCount?: number;
64
+ /**
65
+ * Minimum render count / ms threshold to highlight in the heatmap
66
+ * @default 1600
32
67
  */
33
- heatmapThreshold?: number;
68
+ heatmapThresholdTime?: number;
34
69
  }
35
70
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
36
71
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0 || ^4.0.0"
6
6
  },
7
- "version": "0.1.18",
7
+ "version": "0.1.19",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -524,7 +524,14 @@ const module$1 = defineNuxtModule({
524
524
  composableTracker: true,
525
525
  renderHeatmap: true,
526
526
  transitionTracker: true,
527
- heatmapThreshold: 5
527
+ heatmapThresholdCount: process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT) : 3,
528
+ heatmapThresholdTime: process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME) : 1600,
529
+ maxFetchEntries: process.env.OBSERVATORY_MAX_FETCH_ENTRIES ? Number(process.env.OBSERVATORY_MAX_FETCH_ENTRIES) : 200,
530
+ maxPayloadBytes: process.env.OBSERVATORY_MAX_PAYLOAD_BYTES ? Number(process.env.OBSERVATORY_MAX_PAYLOAD_BYTES) : 1e4,
531
+ maxTransitions: process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500,
532
+ maxComposableHistory: process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50,
533
+ maxComposableEntries: process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300,
534
+ maxRenderTimeline: process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100
528
535
  },
529
536
  setup(options, nuxt) {
530
537
  if (!nuxt.options.dev) {
@@ -534,6 +541,21 @@ const module$1 = defineNuxtModule({
534
541
  process.env.LAUNCH_EDITOR = "code";
535
542
  }
536
543
  const resolver = createResolver(import.meta.url);
544
+ const resolved = {
545
+ fetchDashboard: options.fetchDashboard ?? (process.env.OBSERVATORY_FETCH_DASHBOARD ? process.env.OBSERVATORY_FETCH_DASHBOARD === "true" : true),
546
+ provideInjectGraph: options.provideInjectGraph ?? (process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH ? process.env.OBSERVATORY_PROVIDE_INJECT_GRAPH === "true" : true),
547
+ composableTracker: options.composableTracker ?? (process.env.OBSERVATORY_COMPOSABLE_TRACKER ? process.env.OBSERVATORY_COMPOSABLE_TRACKER === "true" : true),
548
+ renderHeatmap: options.renderHeatmap ?? (process.env.OBSERVATORY_RENDER_HEATMAP ? process.env.OBSERVATORY_RENDER_HEATMAP === "true" : true),
549
+ transitionTracker: options.transitionTracker ?? (process.env.OBSERVATORY_TRANSITION_TRACKER ? process.env.OBSERVATORY_TRANSITION_TRACKER === "true" : true),
550
+ heatmapThresholdCount: options.heatmapThresholdCount ?? (process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_COUNT) : 3),
551
+ heatmapThresholdTime: options.heatmapThresholdTime ?? (process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME ? Number(process.env.OBSERVATORY_HEATMAP_THRESHOLD_TIME) : 1600),
552
+ maxFetchEntries: options.maxFetchEntries ?? (process.env.OBSERVATORY_MAX_FETCH_ENTRIES ? Number(process.env.OBSERVATORY_MAX_FETCH_ENTRIES) : 200),
553
+ maxPayloadBytes: options.maxPayloadBytes ?? (process.env.OBSERVATORY_MAX_PAYLOAD_BYTES ? Number(process.env.OBSERVATORY_MAX_PAYLOAD_BYTES) : 1e4),
554
+ maxTransitions: options.maxTransitions ?? (process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500),
555
+ maxComposableHistory: options.maxComposableHistory ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50),
556
+ maxComposableEntries: options.maxComposableEntries ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300),
557
+ maxRenderTimeline: options.maxRenderTimeline ?? (process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100)
558
+ };
537
559
  nuxt.hook("vite:extendConfig", (config) => {
538
560
  const alias = config.resolve?.alias;
539
561
  const aliases = Array.isArray(alias) ? {} : alias ?? {};
@@ -544,22 +566,22 @@ const module$1 = defineNuxtModule({
544
566
  aliases["nuxt-devtools-observatory/runtime/fetch-registry"] = resolver.resolve("./runtime/composables/fetch-registry");
545
567
  config.resolve = { ...config.resolve, alias: aliases };
546
568
  });
547
- if (options.fetchDashboard) {
569
+ if (resolved.fetchDashboard) {
548
570
  addVitePlugin(fetchInstrumentPlugin());
549
571
  }
550
- if (options.provideInjectGraph) {
572
+ if (resolved.provideInjectGraph) {
551
573
  addVitePlugin(provideInjectPlugin());
552
574
  }
553
- if (options.composableTracker) {
575
+ if (resolved.composableTracker) {
554
576
  addVitePlugin(composableTrackerPlugin());
555
577
  }
556
- if (options.transitionTracker) {
578
+ if (resolved.transitionTracker) {
557
579
  addVitePlugin(transitionTrackerPlugin());
558
580
  }
559
- if (options.fetchDashboard || options.provideInjectGraph || options.composableTracker || options.renderHeatmap || options.transitionTracker) {
581
+ if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.renderHeatmap || resolved.transitionTracker) {
560
582
  addPlugin(resolver.resolve("./runtime/plugin"));
561
583
  }
562
- if (options.fetchDashboard) {
584
+ if (resolved.fetchDashboard) {
563
585
  addServerPlugin(resolver.resolve("./runtime/nitro/fetch-capture"));
564
586
  }
565
587
  const CLIENT_PORT = 4949;
@@ -590,56 +612,38 @@ const module$1 = defineNuxtModule({
590
612
  });
591
613
  });
592
614
  const base = clientOrigin;
593
- nuxt.hook("devtools:customTabs", (tabs) => {
594
- if (options.fetchDashboard) {
595
- tabs.push({
596
- name: "observatory-fetch",
597
- title: "useFetch",
598
- icon: "carbon:radio-button",
599
- view: { type: "iframe", src: `${base}/fetch` }
600
- });
615
+ nuxt.hook("render:response", (response, { url }) => {
616
+ if (url.startsWith("/trackers") || url === "/" || url.startsWith("/index.html")) {
617
+ const configScript = `<script>window.__observatoryConfig = ${JSON.stringify(nuxt.options.runtimeConfig.public.observatory)};<\/script>`;
618
+ response.body = response.body.replace("<head>", `<head>
619
+ ${configScript}`);
601
620
  }
602
- if (options.provideInjectGraph) {
603
- tabs.push({
604
- name: "observatory-provide",
605
- title: "provide/inject",
606
- icon: "carbon:branch",
607
- view: { type: "iframe", src: `${base}/provide` }
608
- });
609
- }
610
- if (options.composableTracker) {
611
- tabs.push({
612
- name: "observatory-composables",
613
- title: "Composables",
614
- icon: "carbon:function",
615
- view: { type: "iframe", src: `${base}/composables` }
616
- });
617
- }
618
- if (options.renderHeatmap) {
621
+ });
622
+ nuxt.hook("devtools:customTabs", (tabs) => {
623
+ if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.renderHeatmap || resolved.transitionTracker) {
619
624
  tabs.push({
620
- name: "observatory-heatmap",
621
- title: "Heatmap",
625
+ name: "observatory-trackers",
626
+ title: "Observatory Trackers",
622
627
  icon: "carbon:heat-map",
623
- view: { type: "iframe", src: `${base}/heatmap` }
624
- });
625
- }
626
- if (options.transitionTracker) {
627
- tabs.push({
628
- name: "observatory-transitions",
629
- title: "Transitions",
630
- icon: "carbon:movement",
631
- view: { type: "iframe", src: `${base}/transitions` }
628
+ view: { type: "iframe", src: `${base}/trackers` }
632
629
  });
633
630
  }
634
631
  });
635
632
  nuxt.options.runtimeConfig.public.observatory = {
636
- heatmapThreshold: options.heatmapThreshold ?? 5,
633
+ heatmapThresholdCount: resolved.heatmapThresholdCount,
634
+ heatmapThresholdTime: resolved.heatmapThresholdTime,
637
635
  clientOrigin,
638
- fetchDashboard: options.fetchDashboard,
639
- provideInjectGraph: options.provideInjectGraph,
640
- composableTracker: options.composableTracker,
641
- renderHeatmap: options.renderHeatmap,
642
- transitionTracker: options.transitionTracker
636
+ fetchDashboard: resolved.fetchDashboard,
637
+ provideInjectGraph: resolved.provideInjectGraph,
638
+ composableTracker: resolved.composableTracker,
639
+ renderHeatmap: resolved.renderHeatmap,
640
+ transitionTracker: resolved.transitionTracker,
641
+ maxFetchEntries: resolved.maxFetchEntries,
642
+ maxPayloadBytes: resolved.maxPayloadBytes,
643
+ maxTransitions: resolved.maxTransitions,
644
+ maxComposableHistory: resolved.maxComposableHistory,
645
+ maxComposableEntries: resolved.maxComposableEntries,
646
+ maxRenderTimeline: resolved.maxRenderTimeline
643
647
  };
644
648
  }
645
649
  });
@@ -41,7 +41,20 @@ export interface ComposableEntry {
41
41
  * - `register`: Registers a new composable entry.
42
42
  * - `update`: Updates an existing composable entry.
43
43
  * - `getAll`: Retrieves all composable entries.
44
- * @returns {{ register: (entry: ComposableEntry) => void, update: (id: string, patch: Partial<ComposableEntry>) => void, getAll: () => ComposableEntry[] }} An object with `register`, `update`, and `getAll` methods.
44
+ * - `getSnapshot`: Returns a cached pre-serialized JSON string, rebuilt only when dirty.
45
+ * @returns {{
46
+ * register: (entry: ComposableEntry) => void,
47
+ * registerLiveRefs: (id: string, refs: Record<string, import('vue').Ref<unknown>>) => void,
48
+ * registerRawRefs: (id: string, refs: Record<string, unknown>) => void,
49
+ * onComposableChange: (cb: () => void) => void,
50
+ * clear: () => void,
51
+ * setRoute: (path: string) => void,
52
+ * getRoute: () => string,
53
+ * update: (id: string, patch: Partial<ComposableEntry>) => void,
54
+ * getAll: () => ComposableEntry[],
55
+ * getSnapshot: () => string,
56
+ * editValue: (id: string, key: string, value: unknown) => void
57
+ * }} An object with `register`, `update`, `getAll`, `getSnapshot`, and related methods.
45
58
  */
46
59
  export declare function setupComposableRegistry(): {
47
60
  register: (entry: ComposableEntry) => void;
@@ -53,6 +66,7 @@ export declare function setupComposableRegistry(): {
53
66
  getRoute: () => string;
54
67
  update: (id: string, patch: Partial<ComposableEntry>) => void;
55
68
  getAll: () => ComposableEntry[];
69
+ getSnapshot: () => string;
56
70
  editValue: (id: string, key: string, value: unknown) => void;
57
71
  };
58
72
  export declare function __trackComposable<T>(name: string, callFn: () => T, meta: {
@@ -1,16 +1,22 @@
1
- import { ref, isRef, isReactive, isReadonly, unref, computed, watchEffect, getCurrentInstance, onUnmounted } from "vue";
1
+ import { isRef, isReactive, isReadonly, unref, computed, watchEffect, getCurrentInstance, onUnmounted } from "vue";
2
2
  export function setupComposableRegistry() {
3
- const entries = ref(/* @__PURE__ */ new Map());
3
+ const entries = /* @__PURE__ */ new Map();
4
4
  const liveRefs = /* @__PURE__ */ new Map();
5
5
  const liveRefWatchers = /* @__PURE__ */ new Map();
6
- const MAX_HISTORY = 50;
7
- const MAX_COMPOSABLE_ENTRIES = 300;
8
- const entryHistory2 = /* @__PURE__ */ new Map();
6
+ const MAX_HISTORY = typeof process !== "undefined" && process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50;
7
+ const MAX_COMPOSABLE_ENTRIES = typeof process !== "undefined" && process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300;
8
+ const entryHistory = /* @__PURE__ */ new Map();
9
9
  const prevValues = /* @__PURE__ */ new Map();
10
10
  const rawRefs = /* @__PURE__ */ new Map();
11
11
  const sharedKeysCache = /* @__PURE__ */ new Map();
12
+ let dirty = true;
13
+ let cachedSnapshot = "[]";
14
+ function markDirty() {
15
+ dirty = true;
16
+ }
12
17
  function invalidateSharedKeysForName(name) {
13
18
  sharedKeysCache.delete(name);
19
+ markDirty();
14
20
  }
15
21
  function deleteEntry(entryId, entryName) {
16
22
  const stop = liveRefWatchers.get(entryId);
@@ -21,8 +27,8 @@ export function setupComposableRegistry() {
21
27
  liveRefs.delete(entryId);
22
28
  rawRefs.delete(entryId);
23
29
  prevValues.delete(entryId);
24
- entryHistory2.delete(entryId);
25
- entries.value.delete(entryId);
30
+ entryHistory.delete(entryId);
31
+ entries.delete(entryId);
26
32
  invalidateSharedKeysForName(entryName);
27
33
  }
28
34
  function getSharedKeys(id, name) {
@@ -32,7 +38,7 @@ export function setupComposableRegistry() {
32
38
  }
33
39
  nameCache = /* @__PURE__ */ new Map();
34
40
  sharedKeysCache.set(name, nameCache);
35
- const peers = [...entries.value.entries()].filter(([, e]) => e.name === name);
41
+ const peers = [...entries.entries()].filter(([, e]) => e.name === name);
36
42
  for (const [eid] of peers) {
37
43
  const ownRaw = rawRefs.get(eid);
38
44
  if (!ownRaw) {
@@ -66,16 +72,17 @@ export function setupComposableRegistry() {
66
72
  return currentRoute;
67
73
  }
68
74
  function register(entry) {
69
- if (entries.value.size >= MAX_COMPOSABLE_ENTRIES) {
70
- const unmountedId = [...entries.value.entries()].find(([, e]) => e.status === "unmounted")?.[0];
71
- const evictId = unmountedId ?? entries.value.keys().next().value;
75
+ if (entries.size >= MAX_COMPOSABLE_ENTRIES) {
76
+ const unmountedId = [...entries.entries()].find(([, e]) => e.status === "unmounted")?.[0];
77
+ const evictId = unmountedId ?? entries.keys().next().value;
72
78
  if (evictId !== void 0) {
73
- const evictName = entries.value.get(evictId)?.name ?? "";
79
+ const evictName = entries.get(evictId)?.name ?? "";
74
80
  deleteEntry(evictId, evictName);
75
81
  }
76
82
  }
77
- entries.value.set(entry.id, entry);
83
+ entries.set(entry.id, entry);
78
84
  invalidateSharedKeysForName(entry.name);
85
+ markDirty();
79
86
  emit("composable:register", entry);
80
87
  }
81
88
  function registerLiveRefs(id, refs) {
@@ -111,13 +118,14 @@ export function setupComposableRegistry() {
111
118
  const prev = prevValues.get(id);
112
119
  if (prev && serialised !== prev[k]) {
113
120
  const t = typeof performance !== "undefined" ? performance.now() : Date.now();
114
- const history = entryHistory2.get(id) ?? [];
121
+ const history = entryHistory.get(id) ?? [];
115
122
  history.push({ t, key: k, value: safeValue(val) });
116
123
  if (history.length > MAX_HISTORY) {
117
124
  history.shift();
118
125
  }
119
- entryHistory2.set(id, history);
126
+ entryHistory.set(id, history);
120
127
  prev[k] = serialised;
128
+ markDirty();
121
129
  _scheduleOnChange();
122
130
  }
123
131
  });
@@ -144,12 +152,13 @@ export function setupComposableRegistry() {
144
152
  _onChange = cb;
145
153
  }
146
154
  function update(id, patch) {
147
- const existing = entries.value.get(id);
155
+ const existing = entries.get(id);
148
156
  if (!existing) {
149
157
  return;
150
158
  }
151
159
  const updated = { ...existing, ...patch };
152
- entries.value.set(id, updated);
160
+ entries.set(id, updated);
161
+ markDirty();
153
162
  emit("composable:update", updated);
154
163
  }
155
164
  function safeValue(val) {
@@ -197,7 +206,7 @@ export function setupComposableRegistry() {
197
206
  leak: entry.leak,
198
207
  leakReason: entry.leakReason,
199
208
  refs: freshRefs,
200
- history: entryHistory2.get(entry.id) ?? [],
209
+ history: entryHistory.get(entry.id) ?? [],
201
210
  sharedKeys: getSharedKeys(entry.id, entry.name),
202
211
  watcherCount: entry.watcherCount,
203
212
  intervalCount: entry.intervalCount,
@@ -208,7 +217,19 @@ export function setupComposableRegistry() {
208
217
  };
209
218
  }
210
219
  function getAll() {
211
- return [...entries.value.values()].map(sanitize);
220
+ return [...entries.values()].map(sanitize);
221
+ }
222
+ function getSnapshot() {
223
+ if (!dirty) {
224
+ return cachedSnapshot;
225
+ }
226
+ try {
227
+ cachedSnapshot = JSON.stringify([...entries.values()].map(sanitize)) ?? "[]";
228
+ } catch {
229
+ cachedSnapshot = "[]";
230
+ }
231
+ dirty = false;
232
+ return cachedSnapshot;
212
233
  }
213
234
  function emit(event, data) {
214
235
  if (!import.meta.client) {
@@ -227,9 +248,10 @@ export function setupComposableRegistry() {
227
248
  liveRefs.clear();
228
249
  rawRefs.clear();
229
250
  prevValues.clear();
230
- entryHistory2.clear();
251
+ entryHistory.clear();
231
252
  sharedKeysCache.clear();
232
- entries.value.clear();
253
+ entries.clear();
254
+ markDirty();
233
255
  emit("composable:clear", {});
234
256
  }
235
257
  function editValue(id, key, value) {
@@ -241,7 +263,7 @@ export function setupComposableRegistry() {
241
263
  if (!r) {
242
264
  return;
243
265
  }
244
- const entry = entries.value.get(id);
266
+ const entry = entries.get(id);
245
267
  if (!entry) {
246
268
  return;
247
269
  }
@@ -250,7 +272,19 @@ export function setupComposableRegistry() {
250
272
  }
251
273
  r.value = value;
252
274
  }
253
- return { register, registerLiveRefs, registerRawRefs, onComposableChange, clear, setRoute, getRoute, update, getAll, editValue };
275
+ return {
276
+ register,
277
+ registerLiveRefs,
278
+ registerRawRefs,
279
+ onComposableChange,
280
+ clear,
281
+ setRoute,
282
+ getRoute,
283
+ update,
284
+ getAll,
285
+ getSnapshot,
286
+ editValue
287
+ };
254
288
  }
255
289
  export function __trackComposable(name, callFn, meta) {
256
290
  if (!import.meta.dev) {
@@ -367,7 +401,6 @@ export function __trackComposable(name, callFn, meta) {
367
401
  }
368
402
  });
369
403
  registry.registerLiveRefs(id, {});
370
- entryHistory.delete(id);
371
404
  });
372
405
  }
373
406
  return result;
@@ -1,5 +1,5 @@
1
1
  export interface FetchEntry {
2
- id: string;
2
+ id: number | string;
3
3
  key: string;
4
4
  url: string;
5
5
  status: 'pending' | 'ok' | 'error' | 'cached';
@@ -45,9 +45,10 @@ export declare function setupFetchRegistry(): {
45
45
  register: (entry: FetchEntry) => void;
46
46
  update: (id: string, patch: Partial<FetchEntry>) => void;
47
47
  getAll: () => FetchEntry[];
48
+ getSnapshot: () => FetchEntry[];
48
49
  clear: () => void;
49
- entries: Readonly<import("vue").Ref<ReadonlyMap<string, {
50
- readonly id: string;
50
+ entries: ReadonlyMap<string | number, {
51
+ readonly id: number | string;
51
52
  readonly key: string;
52
53
  readonly url: string;
53
54
  readonly status: "pending" | "ok" | "error" | "cached";
@@ -61,22 +62,7 @@ export declare function setupFetchRegistry(): {
61
62
  readonly error?: Readonly<unknown> | undefined;
62
63
  readonly file?: string | undefined;
63
64
  readonly line?: number | undefined;
64
- }>, ReadonlyMap<string, {
65
- readonly id: string;
66
- readonly key: string;
67
- readonly url: string;
68
- readonly status: "pending" | "ok" | "error" | "cached";
69
- readonly origin: "ssr" | "csr";
70
- readonly startTime: number;
71
- readonly endTime?: number | undefined;
72
- readonly ms?: number | undefined;
73
- readonly size?: number | undefined;
74
- readonly cached: boolean;
75
- readonly payload?: Readonly<unknown> | undefined;
76
- readonly error?: Readonly<unknown> | undefined;
77
- readonly file?: string | undefined;
78
- readonly line?: number | undefined;
79
- }>>>;
65
+ }>;
80
66
  };
81
67
  export declare function __devFetchHandler(handler: (...args: unknown[]) => unknown, key: unknown, meta: FetchMeta): (...args: unknown[]) => Promise<unknown>;
82
68
  export declare function __devFetchCall(originalFn: (url: string, opts: FetchOptions) => FetchResult, url: string, opts: FetchOptions, meta: FetchMeta): FetchResult;