nuxt-devtools-observatory 0.1.33 → 0.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/client/.env.example +1 -0
- package/client/dist/assets/index-BO7neKEi.css +1 -0
- package/client/dist/assets/index-fFBuk6M6.js +20 -0
- package/client/dist/index.html +2 -2
- package/client/src/App.vue +4 -0
- package/client/src/stores/observatory.ts +20 -0
- package/client/src/views/PiniaStoreTracker.vue +343 -0
- package/client/src/views/ProvideInjectGraph.vue +25 -0
- package/dist/module.d.mts +10 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +17 -2
- package/dist/runtime/composables/pinia-store-registry.d.ts +44 -0
- package/dist/runtime/composables/pinia-store-registry.js +447 -0
- package/dist/runtime/composables/provide-inject-registry.js +13 -8
- package/dist/runtime/plugin.js +35 -1
- package/dist/runtime/test-bridge.js +18 -4
- package/package.json +1 -1
- package/client/dist/assets/index-BqKYgjVB.js +0 -20
- package/client/dist/assets/index-bs1JBJ2u.css +0 -1
|
@@ -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
|
+
}
|
|
@@ -2,15 +2,18 @@ import { isRef, isReactive, unref, getCurrentInstance, provide, inject } from "v
|
|
|
2
2
|
export function setupProvideInjectRegistry() {
|
|
3
3
|
let dirty = true;
|
|
4
4
|
let cachedSnapshot = '{"provides":[],"injects":[]}';
|
|
5
|
+
let hasLiveProvides = false;
|
|
5
6
|
function markDirty() {
|
|
6
7
|
dirty = true;
|
|
7
8
|
}
|
|
8
9
|
const provides = /* @__PURE__ */ new Map();
|
|
9
10
|
const injects = /* @__PURE__ */ new Map();
|
|
10
11
|
function registerProvide(entry) {
|
|
11
|
-
|
|
12
|
+
const internal = entry;
|
|
13
|
+
provides.set(`${entry.key}:${entry.componentUid}`, internal);
|
|
14
|
+
hasLiveProvides = hasLiveProvides || internal.__valueSource !== void 0;
|
|
12
15
|
markDirty();
|
|
13
|
-
emit("provide:register",
|
|
16
|
+
emit("provide:register", sanitizeProvide(internal));
|
|
14
17
|
}
|
|
15
18
|
function registerInject(entry) {
|
|
16
19
|
injects.set(`${entry.key}:${entry.componentUid}`, entry);
|
|
@@ -20,6 +23,7 @@ export function setupProvideInjectRegistry() {
|
|
|
20
23
|
function clear() {
|
|
21
24
|
provides.clear();
|
|
22
25
|
injects.clear();
|
|
26
|
+
hasLiveProvides = false;
|
|
23
27
|
markDirty();
|
|
24
28
|
emit("provide:clear", {});
|
|
25
29
|
}
|
|
@@ -32,9 +36,8 @@ export function setupProvideInjectRegistry() {
|
|
|
32
36
|
parentUid: entry.parentUid,
|
|
33
37
|
parentFile: entry.parentFile,
|
|
34
38
|
isReactive: entry.isReactive,
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
valueSnapshot: entry.valueSnapshot,
|
|
39
|
+
// Reactive values are materialized from their live source on every read.
|
|
40
|
+
valueSnapshot: entry.__valueSource !== void 0 ? safeSnapshot(unref(entry.__valueSource)) : entry.valueSnapshot,
|
|
38
41
|
line: entry.line,
|
|
39
42
|
scope: entry.scope,
|
|
40
43
|
isShadowing: entry.isShadowing
|
|
@@ -61,7 +64,7 @@ export function setupProvideInjectRegistry() {
|
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
66
|
function getSnapshot() {
|
|
64
|
-
if (!dirty) {
|
|
67
|
+
if (!dirty && !hasLiveProvides) {
|
|
65
68
|
return cachedSnapshot;
|
|
66
69
|
}
|
|
67
70
|
try {
|
|
@@ -103,6 +106,7 @@ export function __devProvide(key, value, meta) {
|
|
|
103
106
|
scope = "layout";
|
|
104
107
|
}
|
|
105
108
|
const isShadowing = findProvider(keyStr, instance) !== null;
|
|
109
|
+
const reactiveValue = isRef(value) || isReactive(value);
|
|
106
110
|
registry.registerProvide({
|
|
107
111
|
key: keyStr,
|
|
108
112
|
componentName: instance?.type?.__name ?? "unknown",
|
|
@@ -110,11 +114,12 @@ export function __devProvide(key, value, meta) {
|
|
|
110
114
|
componentUid: instance?.uid ?? -1,
|
|
111
115
|
parentUid: instance?.parent?.uid,
|
|
112
116
|
parentFile: instance?.parent?.type?.__file,
|
|
113
|
-
isReactive:
|
|
117
|
+
isReactive: reactiveValue,
|
|
114
118
|
valueSnapshot: safeSnapshot(unref(value)),
|
|
115
119
|
line: meta.line,
|
|
116
120
|
scope,
|
|
117
|
-
isShadowing
|
|
121
|
+
isShadowing,
|
|
122
|
+
...reactiveValue ? { __valueSource: value } : {}
|
|
118
123
|
});
|
|
119
124
|
}
|
|
120
125
|
export function __devInject(key, defaultValue, meta) {
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -3,11 +3,13 @@ import { nextTick } from "vue";
|
|
|
3
3
|
import { setupComposableRegistry } from "./composables/composable-registry.js";
|
|
4
4
|
import { setupFetchRegistry } from "./composables/fetch-registry.js";
|
|
5
5
|
import { setupProvideInjectRegistry } from "./composables/provide-inject-registry.js";
|
|
6
|
+
import { setupPiniaStoreRegistry } from "./composables/pinia-store-registry.js";
|
|
6
7
|
import { setupRenderRegistry } from "./composables/render-registry.js";
|
|
7
8
|
import { setupTransitionRegistry } from "./composables/transition-registry.js";
|
|
8
9
|
import { setupComponentInstrumentation } from "./instrumentation/component.js";
|
|
9
10
|
import { setupFetchInstrumentation } from "./instrumentation/fetch.js";
|
|
10
11
|
import { setupRouteInstrumentation } from "./instrumentation/route.js";
|
|
12
|
+
import { injectTestBridge } from "./test-bridge.js";
|
|
11
13
|
import { traceStore } from "./tracing/traceStore.js";
|
|
12
14
|
export default defineNuxtPlugin(() => {
|
|
13
15
|
if (!import.meta.dev) {
|
|
@@ -37,6 +39,13 @@ export default defineNuxtPlugin(() => {
|
|
|
37
39
|
if (config.composableTracker) {
|
|
38
40
|
registries.composable = setupComposableRegistry();
|
|
39
41
|
}
|
|
42
|
+
if (config.piniaTracker) {
|
|
43
|
+
registries.pinia = setupPiniaStoreRegistry({
|
|
44
|
+
pinia: nuxtApp.$pinia,
|
|
45
|
+
nuxtPayload: nuxtApp.payload,
|
|
46
|
+
maxTimeline: config.maxPiniaTimeline
|
|
47
|
+
});
|
|
48
|
+
}
|
|
40
49
|
if (config.renderHeatmap) {
|
|
41
50
|
registries.render = setupRenderRegistry(nuxtApp, {
|
|
42
51
|
isHydrating: () => (nuxtApp.isHydrating ?? false) && nuxtApp.payload?.serverRendered === true
|
|
@@ -98,12 +107,19 @@ export default defineNuxtPlugin(() => {
|
|
|
98
107
|
}
|
|
99
108
|
delete window.__observatory__;
|
|
100
109
|
window.__observatory__ = registries;
|
|
110
|
+
injectTestBridge();
|
|
101
111
|
const composableRegistry = registries.composable;
|
|
112
|
+
const piniaRegistry = registries.pinia;
|
|
102
113
|
if (composableRegistry && composableRegistry.onComposableChange) {
|
|
103
114
|
composableRegistry.onComposableChange(() => {
|
|
104
115
|
broadcastAll("composable:onChange");
|
|
105
116
|
});
|
|
106
117
|
}
|
|
118
|
+
if (piniaRegistry?.onChange) {
|
|
119
|
+
piniaRegistry.onChange(() => {
|
|
120
|
+
broadcastAll("pinia:onChange");
|
|
121
|
+
});
|
|
122
|
+
}
|
|
107
123
|
import.meta.hot?.on("observatory:command", (rawPayload) => {
|
|
108
124
|
if (!rawPayload || typeof rawPayload !== "object") {
|
|
109
125
|
return;
|
|
@@ -137,10 +153,24 @@ export default defineNuxtPlugin(() => {
|
|
|
137
153
|
if (payload.cmd === "edit-composable") {
|
|
138
154
|
debugLog("received command: edit-composable", { id: payload.id, key: payload.key });
|
|
139
155
|
composableRegistry?.editValue(payload.id, payload.key, payload.value);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (payload.cmd === "clear-pinia") {
|
|
159
|
+
debugLog("received command: clear-pinia");
|
|
160
|
+
piniaRegistry?.clear();
|
|
161
|
+
broadcastAll("command:clear-pinia");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (payload.cmd === "edit-pinia") {
|
|
165
|
+
debugLog("received command: edit-pinia", { storeId: payload.storeId, path: payload.path });
|
|
166
|
+
piniaRegistry?.editState(payload.storeId, payload.path, payload.value);
|
|
167
|
+
broadcastAll("command:edit-pinia");
|
|
140
168
|
}
|
|
141
169
|
});
|
|
142
170
|
nuxtApp.hook("app:beforeUnmount", () => {
|
|
143
171
|
import.meta.hot?.off("observatory:command");
|
|
172
|
+
const pinia = registries.pinia;
|
|
173
|
+
pinia?.teardown?.();
|
|
144
174
|
if (heartbeatId !== null) {
|
|
145
175
|
window.clearInterval(heartbeatId);
|
|
146
176
|
heartbeatId = null;
|
|
@@ -233,6 +263,7 @@ export default defineNuxtPlugin(() => {
|
|
|
233
263
|
reason,
|
|
234
264
|
fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
|
|
235
265
|
composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
|
|
266
|
+
piniaStores: Array.isArray(snapshot.piniaStores) ? snapshot.piniaStores.length : 0,
|
|
236
267
|
renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
|
|
237
268
|
transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0,
|
|
238
269
|
traces: Array.isArray(snapshot.traces) ? snapshot.traces.length : 0
|
|
@@ -258,6 +289,7 @@ export default defineNuxtPlugin(() => {
|
|
|
258
289
|
{ key: "fetch", fallback: [] },
|
|
259
290
|
{ key: "provideInject", fallback: { provides: [], injects: [] } },
|
|
260
291
|
{ key: "composable", fallback: [] },
|
|
292
|
+
{ key: "pinia", fallback: [] },
|
|
261
293
|
{ key: "render", fallback: {} },
|
|
262
294
|
{ key: "transition", fallback: {} }
|
|
263
295
|
];
|
|
@@ -265,7 +297,8 @@ export default defineNuxtPlugin(() => {
|
|
|
265
297
|
for (const { key, fallback } of trackerDefs) {
|
|
266
298
|
const reg = registries[key];
|
|
267
299
|
const hasGetSnapshot = reg && typeof reg.getSnapshot === "function";
|
|
268
|
-
|
|
300
|
+
const snapshotKey = key === "composable" ? "composables" : key === "pinia" ? "piniaStores" : key === "render" ? "renders" : key === "transition" ? "transitions" : key;
|
|
301
|
+
snapshot[snapshotKey] = hasGetSnapshot ? safeParse(reg.getSnapshot(), fallback) : fallback;
|
|
269
302
|
}
|
|
270
303
|
snapshot.traces = config.traceViewer ? traceStore.getAllTraces().map((trace) => ({
|
|
271
304
|
id: trace.id,
|
|
@@ -292,6 +325,7 @@ export default defineNuxtPlugin(() => {
|
|
|
292
325
|
fetchDashboard: !!registries.fetch,
|
|
293
326
|
provideInjectGraph: !!registries.provideInject,
|
|
294
327
|
composableTracker: !!registries.composable,
|
|
328
|
+
piniaTracker: !!registries.pinia,
|
|
295
329
|
composableNavigationMode,
|
|
296
330
|
fetchPageSize: typeof config.fetchPageSize === "number" ? config.fetchPageSize : 20,
|
|
297
331
|
renderHeatmap: !!registries.render,
|
|
@@ -40,6 +40,14 @@ export function injectTestBridge() {
|
|
|
40
40
|
const { transitionRegistry } = await import("./composables/transition-registry.js");
|
|
41
41
|
return transitionRegistry.getEntries();
|
|
42
42
|
},
|
|
43
|
+
async getPiniaStores() {
|
|
44
|
+
const observatory = window.__observatory__;
|
|
45
|
+
const registry = observatory?.pinia;
|
|
46
|
+
if (!registry?.getAll) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return registry.getAll();
|
|
50
|
+
},
|
|
43
51
|
async getInternalCounts() {
|
|
44
52
|
const counts = {
|
|
45
53
|
componentMounts: {},
|
|
@@ -58,10 +66,15 @@ export function injectTestBridge() {
|
|
|
58
66
|
const { renderRegistry } = await import("./composables/render-registry.js");
|
|
59
67
|
const { composableRegistry } = await import("./composables/composable-registry.js");
|
|
60
68
|
const { fetchRegistry } = await import("./composables/fetch-registry.js");
|
|
69
|
+
const observatory = window.__observatory__;
|
|
70
|
+
const piniaRegistry = observatory?.pinia;
|
|
61
71
|
traceStore.clear();
|
|
62
|
-
renderRegistry
|
|
63
|
-
composableRegistry
|
|
64
|
-
fetchRegistry
|
|
72
|
+
renderRegistry?.clear?.();
|
|
73
|
+
composableRegistry?.clear?.();
|
|
74
|
+
fetchRegistry?.clear?.();
|
|
75
|
+
if (piniaRegistry?.clear) {
|
|
76
|
+
piniaRegistry.clear();
|
|
77
|
+
}
|
|
65
78
|
},
|
|
66
79
|
async startRecording() {
|
|
67
80
|
const { traceStore } = await import("./tracing/traceStore.js");
|
|
@@ -77,7 +90,8 @@ export function injectTestBridge() {
|
|
|
77
90
|
heatmap: await this.getHeatmapData(),
|
|
78
91
|
composables: await this.getComposableEntries(),
|
|
79
92
|
fetches: await this.getFetchEntries(),
|
|
80
|
-
transitions: await this.getTransitionEntries()
|
|
93
|
+
transitions: await this.getTransitionEntries(),
|
|
94
|
+
piniaStores: await this.getPiniaStores()
|
|
81
95
|
};
|
|
82
96
|
return JSON.stringify(snapshot, null, 2);
|
|
83
97
|
}
|