nuxt-devtools-observatory 0.1.17 → 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.
- package/README.md +1 -1
- package/client/.env +14 -0
- package/client/.env.example +14 -0
- package/client/dist/assets/index-BUdwtn9n.js +17 -0
- package/client/dist/assets/index-PBRECUGt.css +1 -0
- package/client/dist/index.html +2 -2
- package/client/env.d.ts +10 -0
- package/client/src/App.vue +7 -4
- package/client/src/stores/observatory.ts +19 -0
- package/client/src/views/ComposableTracker.vue +2 -2
- package/client/src/views/FetchDashboard.vue +27 -8
- package/client/src/views/RenderHeatmap.vue +45 -12
- package/client/src/views/ValueInspector.vue +10 -0
- package/dist/module.d.mts +37 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +118 -99
- package/dist/runtime/composables/composable-registry.d.ts +15 -1
- package/dist/runtime/composables/composable-registry.js +142 -52
- package/dist/runtime/composables/fetch-registry.d.ts +15 -25
- package/dist/runtime/composables/fetch-registry.js +162 -59
- package/dist/runtime/composables/provide-inject-registry.d.ts +1 -0
- package/dist/runtime/composables/provide-inject-registry.js +36 -37
- package/dist/runtime/composables/render-registry.d.ts +2 -1
- package/dist/runtime/composables/render-registry.js +78 -31
- package/dist/runtime/composables/transition-registry.d.ts +2 -0
- package/dist/runtime/composables/transition-registry.js +41 -9
- package/dist/runtime/plugin.js +65 -84
- package/package.json +8 -7
- package/client/dist/assets/index-DDPIR7nA.js +0 -17
- package/client/dist/assets/index-DzKvjd0B.css +0 -1
package/dist/module.mjs
CHANGED
|
@@ -5,17 +5,19 @@ import _generate from '@babel/generator';
|
|
|
5
5
|
import * as t from '@babel/types';
|
|
6
6
|
|
|
7
7
|
function extractScriptBlock(code) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
try {
|
|
9
|
+
const { parse } = require("@vue/compiler-sfc");
|
|
10
|
+
const { descriptor } = parse(code, { ignoreEmpty: false });
|
|
11
|
+
const block = descriptor.scriptSetup ?? descriptor.script ?? null;
|
|
12
|
+
if (!block) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const start = block.loc.start.offset;
|
|
16
|
+
const end = block.loc.end.offset;
|
|
17
|
+
return { content: block.content, start, end };
|
|
18
|
+
} catch {
|
|
16
19
|
return null;
|
|
17
20
|
}
|
|
18
|
-
return { content: code.slice(start, end), start, end };
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
const traverse$2 = _traverse.default ?? _traverse;
|
|
@@ -90,9 +92,7 @@ function fetchInstrumentPlugin() {
|
|
|
90
92
|
handlerArg = getExpr(args[1]);
|
|
91
93
|
optsArg = getExpr(args[2]) ?? t.objectExpression([]);
|
|
92
94
|
} else {
|
|
93
|
-
|
|
94
|
-
optsArg = getExpr(args[1]) ?? t.objectExpression([]);
|
|
95
|
-
handlerArg = void 0;
|
|
95
|
+
return;
|
|
96
96
|
}
|
|
97
97
|
} else {
|
|
98
98
|
keyArg = getExpr(args[0]) ?? t.stringLiteral("");
|
|
@@ -121,39 +121,41 @@ function fetchInstrumentPlugin() {
|
|
|
121
121
|
t.objectProperty(t.identifier("originalFn"), t.stringLiteral(originalName))
|
|
122
122
|
]);
|
|
123
123
|
if ((originalName === "useAsyncData" || originalName === "useLazyAsyncData") && handlerArg) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
t.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
t.callExpression(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
124
|
+
const wrappedHandler = t.arrowFunctionExpression(
|
|
125
|
+
[t.restElement(t.identifier("args"))],
|
|
126
|
+
t.conditionalExpression(
|
|
127
|
+
t.logicalExpression(
|
|
128
|
+
"&&",
|
|
129
|
+
t.memberExpression(t.identifier("process"), t.identifier("dev")),
|
|
130
|
+
t.memberExpression(t.identifier("process"), t.identifier("client"))
|
|
131
|
+
),
|
|
132
|
+
t.callExpression(
|
|
133
|
+
t.callExpression(t.identifier("__devFetchHandler"), [
|
|
134
|
+
handlerArg,
|
|
135
|
+
keyArg ?? t.stringLiteral(key),
|
|
136
|
+
meta
|
|
137
|
+
]),
|
|
138
|
+
[t.spreadElement(t.identifier("args"))]
|
|
139
|
+
),
|
|
140
|
+
t.callExpression(handlerArg, [t.spreadElement(t.identifier("args"))])
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
wrappedHandler.__observatoryTransformed = true;
|
|
144
|
+
needsFetchHandlerHelper = true;
|
|
145
|
+
if (keyArg) {
|
|
146
|
+
const newCall = t.callExpression(t.identifier(originalName), [
|
|
147
|
+
keyArg,
|
|
148
|
+
wrappedHandler,
|
|
149
|
+
optsArg ?? t.objectExpression([])
|
|
150
|
+
]);
|
|
151
|
+
newCall.__observatoryTransformed = true;
|
|
152
|
+
path.replaceWith(newCall);
|
|
153
|
+
} else {
|
|
154
|
+
const newCall = t.callExpression(t.identifier(originalName), [wrappedHandler]);
|
|
155
|
+
newCall.__observatoryTransformed = true;
|
|
156
|
+
path.replaceWith(newCall);
|
|
156
157
|
}
|
|
158
|
+
modified = true;
|
|
157
159
|
} else {
|
|
158
160
|
const newCall = t.callExpression(t.identifier("__devFetchCall"), [
|
|
159
161
|
t.identifier(originalName),
|
|
@@ -244,8 +246,9 @@ function provideInjectPlugin() {
|
|
|
244
246
|
}
|
|
245
247
|
const args = path.node.arguments;
|
|
246
248
|
const loc = path.node.loc;
|
|
249
|
+
const fileName = id.split(/[\\/]/).pop() || id;
|
|
247
250
|
const meta = t.objectExpression([
|
|
248
|
-
t.objectProperty(t.identifier("file"), t.stringLiteral(
|
|
251
|
+
t.objectProperty(t.identifier("file"), t.stringLiteral(fileName)),
|
|
249
252
|
t.objectProperty(t.identifier("line"), t.numericLiteral(loc?.start.line ?? 0))
|
|
250
253
|
]);
|
|
251
254
|
if (name === "provide") {
|
|
@@ -416,6 +419,11 @@ function _obsMergeHook(original, fn) {
|
|
|
416
419
|
return function(el) { fn(el); if (original) original(el) }
|
|
417
420
|
}
|
|
418
421
|
|
|
422
|
+
// Monotonically increasing counter used to make transition IDs unique even
|
|
423
|
+
// when multiple transitions fire within the same performance.now() millisecond
|
|
424
|
+
// (e.g. rapid-toggle stress tests, or simultaneous enter + leave on a swap).
|
|
425
|
+
let _obsSeq = 0
|
|
426
|
+
|
|
419
427
|
const _ObservedTransition = _obsDefineComponent({
|
|
420
428
|
name: 'Transition',
|
|
421
429
|
inheritAttrs: false,
|
|
@@ -442,7 +450,7 @@ const _ObservedTransition = _obsDefineComponent({
|
|
|
442
450
|
const hookedAttrs = Object.assign({}, attrs, {
|
|
443
451
|
onBeforeEnter: _obsMergeHook(attrs.onBeforeEnter, function() {
|
|
444
452
|
const t = performance.now()
|
|
445
|
-
const id = transitionName + '::enter::' + t
|
|
453
|
+
const id = transitionName + '::enter::' + t + '::' + (++_obsSeq)
|
|
446
454
|
enterEntryId = id
|
|
447
455
|
r.register({ id, transitionName, parentComponent, direction: 'enter', phase: 'entering', startTime: t, cancelled: false, appear: isAppear, mode })
|
|
448
456
|
}),
|
|
@@ -454,7 +462,7 @@ const _ObservedTransition = _obsDefineComponent({
|
|
|
454
462
|
}),
|
|
455
463
|
onBeforeLeave: _obsMergeHook(attrs.onBeforeLeave, function() {
|
|
456
464
|
const t = performance.now()
|
|
457
|
-
const id = transitionName + '::leave::' + t
|
|
465
|
+
const id = transitionName + '::leave::' + t + '::' + (++_obsSeq)
|
|
458
466
|
leaveEntryId = id
|
|
459
467
|
r.register({ id, transitionName, parentComponent, direction: 'leave', phase: 'leaving', startTime: t, cancelled: false, appear: false, mode })
|
|
460
468
|
}),
|
|
@@ -516,7 +524,14 @@ const module$1 = defineNuxtModule({
|
|
|
516
524
|
composableTracker: true,
|
|
517
525
|
renderHeatmap: true,
|
|
518
526
|
transitionTracker: true,
|
|
519
|
-
|
|
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
|
|
520
535
|
},
|
|
521
536
|
setup(options, nuxt) {
|
|
522
537
|
if (!nuxt.options.dev) {
|
|
@@ -526,6 +541,21 @@ const module$1 = defineNuxtModule({
|
|
|
526
541
|
process.env.LAUNCH_EDITOR = "code";
|
|
527
542
|
}
|
|
528
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
|
+
};
|
|
529
559
|
nuxt.hook("vite:extendConfig", (config) => {
|
|
530
560
|
const alias = config.resolve?.alias;
|
|
531
561
|
const aliases = Array.isArray(alias) ? {} : alias ?? {};
|
|
@@ -536,33 +566,37 @@ const module$1 = defineNuxtModule({
|
|
|
536
566
|
aliases["nuxt-devtools-observatory/runtime/fetch-registry"] = resolver.resolve("./runtime/composables/fetch-registry");
|
|
537
567
|
config.resolve = { ...config.resolve, alias: aliases };
|
|
538
568
|
});
|
|
539
|
-
if (
|
|
569
|
+
if (resolved.fetchDashboard) {
|
|
540
570
|
addVitePlugin(fetchInstrumentPlugin());
|
|
541
571
|
}
|
|
542
|
-
if (
|
|
572
|
+
if (resolved.provideInjectGraph) {
|
|
543
573
|
addVitePlugin(provideInjectPlugin());
|
|
544
574
|
}
|
|
545
|
-
if (
|
|
575
|
+
if (resolved.composableTracker) {
|
|
546
576
|
addVitePlugin(composableTrackerPlugin());
|
|
547
577
|
}
|
|
548
|
-
if (
|
|
578
|
+
if (resolved.transitionTracker) {
|
|
549
579
|
addVitePlugin(transitionTrackerPlugin());
|
|
550
580
|
}
|
|
551
|
-
if (
|
|
581
|
+
if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.renderHeatmap || resolved.transitionTracker) {
|
|
552
582
|
addPlugin(resolver.resolve("./runtime/plugin"));
|
|
553
583
|
}
|
|
554
|
-
if (
|
|
584
|
+
if (resolved.fetchDashboard) {
|
|
555
585
|
addServerPlugin(resolver.resolve("./runtime/nitro/fetch-capture"));
|
|
556
586
|
}
|
|
557
587
|
const CLIENT_PORT = 4949;
|
|
558
588
|
const clientOrigin = `http://localhost:${CLIENT_PORT}`;
|
|
589
|
+
let innerServer = null;
|
|
559
590
|
nuxt.hook("vite:serverCreated", async (_viteServer, env) => {
|
|
560
591
|
if (!env.isClient) {
|
|
561
592
|
return;
|
|
562
593
|
}
|
|
594
|
+
if (innerServer) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
563
597
|
const { createServer } = await import('vite');
|
|
564
598
|
const { default: vue } = await import('@vitejs/plugin-vue');
|
|
565
|
-
|
|
599
|
+
innerServer = await createServer({
|
|
566
600
|
root: resolver.resolve("../client"),
|
|
567
601
|
base: "/",
|
|
568
602
|
server: { port: CLIENT_PORT, strictPort: true, cors: true },
|
|
@@ -571,60 +605,45 @@ const module$1 = defineNuxtModule({
|
|
|
571
605
|
plugins: [vue()],
|
|
572
606
|
logLevel: "warn"
|
|
573
607
|
});
|
|
574
|
-
await
|
|
575
|
-
nuxt.hook("close", () =>
|
|
608
|
+
await innerServer.listen();
|
|
609
|
+
nuxt.hook("close", () => {
|
|
610
|
+
innerServer?.close();
|
|
611
|
+
innerServer = null;
|
|
612
|
+
});
|
|
576
613
|
});
|
|
577
614
|
const base = clientOrigin;
|
|
578
|
-
nuxt.hook("
|
|
579
|
-
if (
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
icon: "carbon:radio-button",
|
|
584
|
-
view: { type: "iframe", src: `${base}/fetch` }
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
if (options.provideInjectGraph) {
|
|
588
|
-
tabs.push({
|
|
589
|
-
name: "observatory-provide",
|
|
590
|
-
title: "provide/inject",
|
|
591
|
-
icon: "carbon:branch",
|
|
592
|
-
view: { type: "iframe", src: `${base}/provide` }
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
if (options.composableTracker) {
|
|
596
|
-
tabs.push({
|
|
597
|
-
name: "observatory-composables",
|
|
598
|
-
title: "Composables",
|
|
599
|
-
icon: "carbon:function",
|
|
600
|
-
view: { type: "iframe", src: `${base}/composables` }
|
|
601
|
-
});
|
|
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}`);
|
|
602
620
|
}
|
|
603
|
-
|
|
621
|
+
});
|
|
622
|
+
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
623
|
+
if (resolved.fetchDashboard || resolved.provideInjectGraph || resolved.composableTracker || resolved.renderHeatmap || resolved.transitionTracker) {
|
|
604
624
|
tabs.push({
|
|
605
|
-
name: "observatory-
|
|
606
|
-
title: "
|
|
625
|
+
name: "observatory-trackers",
|
|
626
|
+
title: "Observatory Trackers",
|
|
607
627
|
icon: "carbon:heat-map",
|
|
608
|
-
view: { type: "iframe", src: `${base}/
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
if (options.transitionTracker) {
|
|
612
|
-
tabs.push({
|
|
613
|
-
name: "observatory-transitions",
|
|
614
|
-
title: "Transitions",
|
|
615
|
-
icon: "carbon:movement",
|
|
616
|
-
view: { type: "iframe", src: `${base}/transitions` }
|
|
628
|
+
view: { type: "iframe", src: `${base}/trackers` }
|
|
617
629
|
});
|
|
618
630
|
}
|
|
619
631
|
});
|
|
620
632
|
nuxt.options.runtimeConfig.public.observatory = {
|
|
621
|
-
|
|
633
|
+
heatmapThresholdCount: resolved.heatmapThresholdCount,
|
|
634
|
+
heatmapThresholdTime: resolved.heatmapThresholdTime,
|
|
622
635
|
clientOrigin,
|
|
623
|
-
fetchDashboard:
|
|
624
|
-
provideInjectGraph:
|
|
625
|
-
composableTracker:
|
|
626
|
-
renderHeatmap:
|
|
627
|
-
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
|
|
628
647
|
};
|
|
629
648
|
}
|
|
630
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
|
-
*
|
|
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,33 +1,68 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isRef, isReactive, isReadonly, unref, computed, watchEffect, getCurrentInstance, onUnmounted } from "vue";
|
|
2
2
|
export function setupComposableRegistry() {
|
|
3
|
-
const entries =
|
|
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;
|
|
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;
|
|
7
8
|
const entryHistory = /* @__PURE__ */ new Map();
|
|
8
9
|
const prevValues = /* @__PURE__ */ new Map();
|
|
9
10
|
const rawRefs = /* @__PURE__ */ new Map();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const sharedKeysCache = /* @__PURE__ */ new Map();
|
|
12
|
+
let dirty = true;
|
|
13
|
+
let cachedSnapshot = "[]";
|
|
14
|
+
function markDirty() {
|
|
15
|
+
dirty = true;
|
|
16
|
+
}
|
|
17
|
+
function invalidateSharedKeysForName(name) {
|
|
18
|
+
sharedKeysCache.delete(name);
|
|
19
|
+
markDirty();
|
|
20
|
+
}
|
|
21
|
+
function deleteEntry(entryId, entryName) {
|
|
22
|
+
const stop = liveRefWatchers.get(entryId);
|
|
23
|
+
if (stop) {
|
|
24
|
+
stop();
|
|
25
|
+
liveRefWatchers.delete(entryId);
|
|
14
26
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
liveRefs.delete(entryId);
|
|
28
|
+
rawRefs.delete(entryId);
|
|
29
|
+
prevValues.delete(entryId);
|
|
30
|
+
entryHistory.delete(entryId);
|
|
31
|
+
entries.delete(entryId);
|
|
32
|
+
invalidateSharedKeysForName(entryName);
|
|
33
|
+
}
|
|
34
|
+
function getSharedKeys(id, name) {
|
|
35
|
+
let nameCache = sharedKeysCache.get(name);
|
|
36
|
+
if (nameCache && nameCache.has(id)) {
|
|
37
|
+
return nameCache.get(id);
|
|
38
|
+
}
|
|
39
|
+
nameCache = /* @__PURE__ */ new Map();
|
|
40
|
+
sharedKeysCache.set(name, nameCache);
|
|
41
|
+
const peers = [...entries.entries()].filter(([, e]) => e.name === name);
|
|
42
|
+
for (const [eid] of peers) {
|
|
43
|
+
const ownRaw = rawRefs.get(eid);
|
|
44
|
+
if (!ownRaw) {
|
|
45
|
+
nameCache.set(eid, []);
|
|
22
46
|
continue;
|
|
23
47
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
48
|
+
const shared = /* @__PURE__ */ new Set();
|
|
49
|
+
for (const [otherId] of peers) {
|
|
50
|
+
if (otherId === eid) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const otherRaw = rawRefs.get(otherId);
|
|
54
|
+
if (!otherRaw) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
for (const [key, obj] of Object.entries(ownRaw)) {
|
|
58
|
+
if (key in otherRaw && otherRaw[key] === obj) {
|
|
59
|
+
shared.add(key);
|
|
60
|
+
}
|
|
27
61
|
}
|
|
28
62
|
}
|
|
63
|
+
nameCache.set(eid, [...shared]);
|
|
29
64
|
}
|
|
30
|
-
return [
|
|
65
|
+
return nameCache.get(id) ?? [];
|
|
31
66
|
}
|
|
32
67
|
let currentRoute = "/";
|
|
33
68
|
function setRoute(path) {
|
|
@@ -37,7 +72,17 @@ export function setupComposableRegistry() {
|
|
|
37
72
|
return currentRoute;
|
|
38
73
|
}
|
|
39
74
|
function register(entry) {
|
|
40
|
-
entries.
|
|
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;
|
|
78
|
+
if (evictId !== void 0) {
|
|
79
|
+
const evictName = entries.get(evictId)?.name ?? "";
|
|
80
|
+
deleteEntry(evictId, evictName);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
entries.set(entry.id, entry);
|
|
84
|
+
invalidateSharedKeysForName(entry.name);
|
|
85
|
+
markDirty();
|
|
41
86
|
emit("composable:register", entry);
|
|
42
87
|
}
|
|
43
88
|
function registerLiveRefs(id, refs) {
|
|
@@ -53,54 +98,67 @@ export function setupComposableRegistry() {
|
|
|
53
98
|
return;
|
|
54
99
|
}
|
|
55
100
|
liveRefs.set(id, refs);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
);
|
|
68
|
-
const stop = watchEffect(() => {
|
|
69
|
-
const prev = prevValues.get(id) ?? {};
|
|
70
|
-
const now = {};
|
|
71
|
-
const t = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
72
|
-
for (const [k, r] of Object.entries(refs)) {
|
|
101
|
+
const stopFns = [];
|
|
102
|
+
for (const [k, r] of Object.entries(refs)) {
|
|
103
|
+
if (!prevValues.has(id)) {
|
|
104
|
+
prevValues.set(id, {});
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
prevValues.get(id)[k] = JSON.stringify(unref(r)) ?? "";
|
|
108
|
+
} catch {
|
|
109
|
+
prevValues.get(id)[k] = "";
|
|
110
|
+
}
|
|
111
|
+
const stopK = watchEffect(() => {
|
|
73
112
|
const val = unref(r);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
113
|
+
let serialised = "";
|
|
114
|
+
try {
|
|
115
|
+
serialised = JSON.stringify(val) ?? "";
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
const prev = prevValues.get(id);
|
|
119
|
+
if (prev && serialised !== prev[k]) {
|
|
120
|
+
const t = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
77
121
|
const history = entryHistory.get(id) ?? [];
|
|
78
122
|
history.push({ t, key: k, value: safeValue(val) });
|
|
79
123
|
if (history.length > MAX_HISTORY) {
|
|
80
124
|
history.shift();
|
|
81
125
|
}
|
|
82
126
|
entryHistory.set(id, history);
|
|
127
|
+
prev[k] = serialised;
|
|
128
|
+
markDirty();
|
|
129
|
+
_scheduleOnChange();
|
|
83
130
|
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
131
|
+
});
|
|
132
|
+
stopFns.push(stopK);
|
|
133
|
+
}
|
|
134
|
+
const stop = () => stopFns.forEach((s) => s());
|
|
88
135
|
liveRefWatchers.set(id, stop);
|
|
89
136
|
}
|
|
90
137
|
function registerRawRefs(id, refs) {
|
|
91
138
|
rawRefs.set(id, refs);
|
|
92
139
|
}
|
|
93
140
|
let _onChange = null;
|
|
141
|
+
let _pendingFrame = null;
|
|
142
|
+
function _scheduleOnChange() {
|
|
143
|
+
if (_pendingFrame !== null) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
_pendingFrame = requestAnimationFrame(() => {
|
|
147
|
+
_pendingFrame = null;
|
|
148
|
+
_onChange?.();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
94
151
|
function onComposableChange(cb) {
|
|
95
152
|
_onChange = cb;
|
|
96
153
|
}
|
|
97
154
|
function update(id, patch) {
|
|
98
|
-
const existing = entries.
|
|
155
|
+
const existing = entries.get(id);
|
|
99
156
|
if (!existing) {
|
|
100
157
|
return;
|
|
101
158
|
}
|
|
102
159
|
const updated = { ...existing, ...patch };
|
|
103
|
-
entries.
|
|
160
|
+
entries.set(id, updated);
|
|
161
|
+
markDirty();
|
|
104
162
|
emit("composable:update", updated);
|
|
105
163
|
}
|
|
106
164
|
function safeValue(val) {
|
|
@@ -149,7 +207,7 @@ export function setupComposableRegistry() {
|
|
|
149
207
|
leakReason: entry.leakReason,
|
|
150
208
|
refs: freshRefs,
|
|
151
209
|
history: entryHistory.get(entry.id) ?? [],
|
|
152
|
-
sharedKeys:
|
|
210
|
+
sharedKeys: getSharedKeys(entry.id, entry.name),
|
|
153
211
|
watcherCount: entry.watcherCount,
|
|
154
212
|
intervalCount: entry.intervalCount,
|
|
155
213
|
lifecycle: entry.lifecycle,
|
|
@@ -159,7 +217,19 @@ export function setupComposableRegistry() {
|
|
|
159
217
|
};
|
|
160
218
|
}
|
|
161
219
|
function getAll() {
|
|
162
|
-
return [...entries.
|
|
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;
|
|
163
233
|
}
|
|
164
234
|
function emit(event, data) {
|
|
165
235
|
if (!import.meta.client) {
|
|
@@ -169,13 +239,19 @@ export function setupComposableRegistry() {
|
|
|
169
239
|
channel?.send(event, data);
|
|
170
240
|
}
|
|
171
241
|
function clear() {
|
|
242
|
+
if (_pendingFrame !== null) {
|
|
243
|
+
cancelAnimationFrame(_pendingFrame);
|
|
244
|
+
_pendingFrame = null;
|
|
245
|
+
}
|
|
172
246
|
for (const stop of liveRefWatchers.values()) stop();
|
|
173
247
|
liveRefWatchers.clear();
|
|
174
248
|
liveRefs.clear();
|
|
175
249
|
rawRefs.clear();
|
|
176
250
|
prevValues.clear();
|
|
177
251
|
entryHistory.clear();
|
|
178
|
-
|
|
252
|
+
sharedKeysCache.clear();
|
|
253
|
+
entries.clear();
|
|
254
|
+
markDirty();
|
|
179
255
|
emit("composable:clear", {});
|
|
180
256
|
}
|
|
181
257
|
function editValue(id, key, value) {
|
|
@@ -187,7 +263,7 @@ export function setupComposableRegistry() {
|
|
|
187
263
|
if (!r) {
|
|
188
264
|
return;
|
|
189
265
|
}
|
|
190
|
-
const entry = entries.
|
|
266
|
+
const entry = entries.get(id);
|
|
191
267
|
if (!entry) {
|
|
192
268
|
return;
|
|
193
269
|
}
|
|
@@ -196,7 +272,19 @@ export function setupComposableRegistry() {
|
|
|
196
272
|
}
|
|
197
273
|
r.value = value;
|
|
198
274
|
}
|
|
199
|
-
return {
|
|
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
|
+
};
|
|
200
288
|
}
|
|
201
289
|
export function __trackComposable(name, callFn, meta) {
|
|
202
290
|
if (!import.meta.dev) {
|
|
@@ -286,8 +374,10 @@ export function __trackComposable(name, callFn, meta) {
|
|
|
286
374
|
route: registry.getRoute()
|
|
287
375
|
};
|
|
288
376
|
registry.register(entry);
|
|
289
|
-
|
|
290
|
-
|
|
377
|
+
if (instance) {
|
|
378
|
+
registry.registerLiveRefs(id, liveRefMap);
|
|
379
|
+
registry.registerRawRefs(id, rawRefMap);
|
|
380
|
+
}
|
|
291
381
|
if (instance) {
|
|
292
382
|
onUnmounted(() => {
|
|
293
383
|
const leakedWatchers = trackedWatchers.filter((w) => w.effect.active);
|