@flareapp/vue 2.7.0 → 2.8.0

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 CHANGED
@@ -54,6 +54,53 @@ See the [JavaScript identifying-users docs](https://flareapp.io/docs/javascript/
54
54
 
55
55
  Full documentation on the Vue error handler and its options is available at [flareapp.io/docs/vue/general/installation](https://flareapp.io/docs/vue/general/installation).
56
56
 
57
+ ## Component profiling
58
+
59
+ Record a span per component mount, nested under the active page-load or navigation trace. Requires tracing
60
+ (`enableTracing: true`) and an allowlist:
61
+
62
+ ```js
63
+ app.use(flareVue, {
64
+ router,
65
+ profileComponents: ['ProductPage', 'CartPage', /^Checkout/],
66
+ });
67
+ ```
68
+
69
+ `app.use(flareVue, …)` and `flare.configure({ enableTracing: true })` can run in either order. Installing
70
+ the plugin first is fine: the router guards and the profiler hook stay idle until tracing is on.
71
+
72
+ Strings match the component name exactly. Regular expressions match by `test()`. `true` profiles every named
73
+ component, which is useful when exploring but will hit the 1024 span per trace cap on a real page and bury the
74
+ spans you care about.
75
+
76
+ Names come from the same resolution the error reports use: the name the SFC compiler derives from the filename,
77
+ then an explicit `name` option, then `AnonymousComponent`. Renaming a component silently stops profiling it.
78
+
79
+ Only mounts are recorded. Updates, `<KeepAlive>` reactivation and unmounts are not.
80
+
81
+ A component that mounts later inside an already-mounted profiled ancestor still nests under that ancestor, whose
82
+ own span closed when it finished mounting. The tree is correct, but the waterfall shows the child starting after
83
+ its parent ended. A page body swapped inside a persistent layout is the usual way to see this.
84
+
85
+ **Async components and `<Suspense>`:** Vue treats a component as mounted once its _synchronous_ children
86
+ are mounted. A component with an async `setup()`, or one inside a `<Suspense>` boundary, therefore mounts
87
+ after its profiled ancestor's span has closed, so it appears after its parent in the waterfall. If the
88
+ whole trace closed by then, the span is dropped rather than attached to a finished trace.
89
+
90
+ ### Naming with Inertia
91
+
92
+ Inertia names navigation spans after the page component from its page object, so a root reads `Products/Show`.
93
+ Vue names components after the file, so `./Pages/Products/Show.vue` is `Show`, and `./Pages/Orders/Show.vue` is
94
+ also `Show`. Set the name explicitly on page components if you want the two to read the same:
95
+
96
+ ```vue
97
+ <script setup>
98
+ defineOptions({ name: 'Products/Show' });
99
+ </script>
100
+ ```
101
+
102
+ Without it, spans use the bare filename, which is usually still readable because the root span disambiguates.
103
+
57
104
  ## Compatibility
58
105
 
59
106
  - Vue 3
@@ -1,31 +1,14 @@
1
+ let _flareapp_js_browser = require("@flareapp/js/browser");
1
2
  let _flareapp_core = require("@flareapp/core");
2
3
  let vue = require("vue");
4
+ let _flareapp_core_util = require("@flareapp/core/util");
3
5
 
4
6
  //#region src/resolveFlare.ts
5
- let defaultProvider = null;
6
- function isDevMode() {
7
- try {
8
- return process.env.NODE_ENV !== "production";
9
- } catch {
10
- return false;
11
- }
12
- }
13
- function registerDefaultFlare(provider) {
14
- if (typeof window !== "undefined" && window.__flare) {
15
- const message = "[flare] @flareapp/vue (web root) was imported in a renderer where the Electron bridge is present, pulling the keyed @flareapp/js singleton into the renderer. Import @flareapp/vue/inject and pass the @flareapp/electron/renderer instance instead.";
16
- if (isDevMode()) throw new Error(message);
17
- console.warn(message);
18
- }
19
- defaultProvider = provider;
20
- }
21
- function resolveFlare(explicit) {
22
- if (explicit) return explicit;
23
- if (defaultProvider) return defaultProvider();
24
- throw new Error("[flare] No Flare instance available. Pass `flare` (e.g. from @flareapp/electron/renderer), or import @flareapp/vue (the package root) to use the @flareapp/js default singleton.");
25
- }
7
+ const { registerDefaultFlare, resolveFlare } = (0, _flareapp_js_browser.createFlareResolver)({ packageName: "@flareapp/vue" });
26
8
 
27
9
  //#endregion
28
10
  //#region src/constants.ts
11
+ /** Injected at build time via tsdown --env.PACKAGE_VERSION (reads package.json version). */
29
12
  const PACKAGE_VERSION = typeof process !== "undefined" && typeof process.env?.PACKAGE_VERSION !== "undefined" ? process.env.PACKAGE_VERSION : "?";
30
13
  const MAX_HIERARCHY_DEPTH = 50;
31
14
  const DEFAULT_PROPS_DENYLIST = /password|passwd|pwd|token|secret|authorization|\bauth\b|bearer|oauth|credentials?|cookie|api[-_]?key|private[-_]?key|session|csrf|xsrf|\bpin\b|\bssn\b|card[-_]?number|\bcvv\b/i;
@@ -122,51 +105,19 @@ function buildComponentHierarchy(instance) {
122
105
 
123
106
  //#endregion
124
107
  //#region src/serializeProps.ts
108
+ /**
109
+ * JSON-safe, redacted, size-bounded copy of a Vue component's props for the report payload. Delegates
110
+ * to core safeClone (display mode) so the cycle / BigInt / throwing-getter safety is shared with core.
111
+ */
125
112
  function serializeProps(value, maxDepth, denylist = DEFAULT_PROPS_DENYLIST) {
126
- return serialize(value, 0, maxDepth, /* @__PURE__ */ new WeakSet(), denylist);
127
- }
128
- function serialize(value, depth, maxDepth, seen, denylist) {
129
- if (value === null) return null;
130
- const type = typeof value;
131
- if (type === "function") return "[Function]";
132
- if (type === "symbol") return "[Symbol]";
133
- if (type === "bigint") return value.toString();
134
- if (type === "string") return truncateString(value);
135
- if (type !== "object") return value;
136
- if (seen.has(value)) return "[Circular]";
137
- if (Array.isArray(value)) {
138
- if (depth > maxDepth) return "[Array]";
139
- seen.add(value);
140
- const out = (value.length > MAX_PROP_ARRAY_LENGTH ? value.slice(0, MAX_PROP_ARRAY_LENGTH) : value).map((item) => serialize(item, depth + 1, maxDepth, seen, denylist));
141
- if (value.length > MAX_PROP_ARRAY_LENGTH) out.push(`[… ${value.length - MAX_PROP_ARRAY_LENGTH} more items]`);
142
- seen.delete(value);
143
- return out;
144
- }
145
- if (!isPlainObject(value)) return "[Object]";
146
- if (depth > maxDepth) return "[Object]";
147
- seen.add(value);
148
- const out = {};
149
- const keys = Object.keys(value);
150
- const limitedKeys = keys.length > MAX_PROP_OBJECT_KEYS ? keys.slice(0, MAX_PROP_OBJECT_KEYS) : keys;
151
- for (const key of limitedKeys) {
152
- if (denylist.test(key)) {
153
- out[key] = "[redacted]";
154
- continue;
155
- }
156
- out[key] = serialize(value[key], depth + 1, maxDepth, seen, denylist);
157
- }
158
- if (keys.length > MAX_PROP_OBJECT_KEYS) out["…"] = `[${keys.length - MAX_PROP_OBJECT_KEYS} more keys]`;
159
- seen.delete(value);
160
- return out;
161
- }
162
- function truncateString(value) {
163
- if (value.length <= MAX_PROP_STRING_LENGTH) return value;
164
- return `${value.slice(0, MAX_PROP_STRING_LENGTH)}…[truncated ${value.length - MAX_PROP_STRING_LENGTH} chars]`;
165
- }
166
- function isPlainObject(value) {
167
- if (value === null || typeof value !== "object") return false;
168
- const prototype = Object.getPrototypeOf(value);
169
- return prototype === null || prototype === Object.prototype;
113
+ return (0, _flareapp_core.safeClone)(value, {
114
+ mode: "display",
115
+ maxDepth,
116
+ arrayCap: MAX_PROP_ARRAY_LENGTH,
117
+ objectKeyCap: MAX_PROP_OBJECT_KEYS,
118
+ stringCap: MAX_PROP_STRING_LENGTH,
119
+ denylist
120
+ });
170
121
  }
171
122
 
172
123
  //#endregion
@@ -189,6 +140,10 @@ function buildComponentHierarchyFrames(instance, options) {
189
140
 
190
141
  //#endregion
191
142
  //#region src/getErrorOrigin.ts
143
+ /**
144
+ * Vue's `info` is a human-readable string in dev (e.g. "render function") but a numeric/short code
145
+ * in production. INFO_TO_ORIGIN maps both forms to a stable origin category.
146
+ */
192
147
  function getErrorOrigin(info) {
193
148
  return INFO_TO_ORIGIN[info] ?? "unknown";
194
149
  }
@@ -196,6 +151,29 @@ function getErrorOrigin(info) {
196
151
  //#endregion
197
152
  //#region src/getRouteContext.ts
198
153
  const ROUTE_PARAMS_DEPTH = 2;
154
+ /** vue-router allows a symbol route name; neither shape survives JSON, so convert both to a string. */
155
+ function routeNameOf(value) {
156
+ if (typeof value === "string") return value;
157
+ if (typeof value === "symbol") return value.toString();
158
+ return null;
159
+ }
160
+ function matchedNames(matched) {
161
+ if (!Array.isArray(matched)) return [];
162
+ return matched.map((record) => {
163
+ if (!record || typeof record !== "object") return "unknown";
164
+ return routeNameOf(record.name) ?? "unknown";
165
+ });
166
+ }
167
+ function recordOrEmpty(value) {
168
+ if (!value || typeof value !== "object") return {};
169
+ return value;
170
+ }
171
+ /**
172
+ * `router` is typed `unknown` because vue-router is an optional peer (importing it would force every
173
+ * consumer to install it, and the runtime shape may differ across v4.x patches). The chained
174
+ * type-guards read defensively so a missing or shimmed router yields `null` rather than throwing
175
+ * inside an error handler.
176
+ */
199
177
  function getRouteContext(router, options = {}) {
200
178
  if (!router || typeof router !== "object" || !("currentRoute" in router)) return null;
201
179
  const currentRouteRef = router.currentRoute;
@@ -203,44 +181,180 @@ function getRouteContext(router, options = {}) {
203
181
  const route = currentRouteRef.value;
204
182
  if (!route || typeof route !== "object") return null;
205
183
  const r = route;
206
- const name = r.name;
207
184
  const denylist = options.denylist ?? DEFAULT_PROPS_DENYLIST;
208
- const params = r.params && typeof r.params === "object" ? r.params : {};
209
- const query = r.query && typeof r.query === "object" ? r.query : {};
185
+ const redactedParams = (0, _flareapp_core.redactObjectValues)(recordOrEmpty(r.params), denylist);
186
+ const redactedQuery = (0, _flareapp_core.redactObjectValues)(recordOrEmpty(r.query), denylist);
210
187
  return {
211
- name: typeof name === "string" ? name : typeof name === "symbol" ? name.toString() : null,
188
+ name: routeNameOf(r.name),
212
189
  path: typeof r.path === "string" ? r.path : "",
213
190
  fullPath: typeof r.fullPath === "string" ? (0, _flareapp_core.redactUrlQuery)(r.fullPath, denylist) : "",
214
- params: serializeProps(params, ROUTE_PARAMS_DEPTH, denylist),
215
- query: serializeProps(query, ROUTE_PARAMS_DEPTH, denylist),
191
+ params: serializeProps(redactedParams, ROUTE_PARAMS_DEPTH, denylist),
192
+ query: serializeProps(redactedQuery, ROUTE_PARAMS_DEPTH, denylist),
216
193
  hash: typeof r.hash === "string" ? r.hash : "",
217
- matched: Array.isArray(r.matched) ? r.matched.map((record) => {
218
- if (!record || typeof record !== "object") return "unknown";
219
- const n = record.name;
220
- return typeof n === "string" ? n : typeof n === "symbol" ? n.toString() : "unknown";
221
- }) : []
194
+ matched: matchedNames(r.matched)
222
195
  };
223
196
  }
224
197
 
225
198
  //#endregion
226
199
  //#region src/identify.ts
227
- const sdkTagged = /* @__PURE__ */ new WeakSet();
228
- const frameworkTagged = /* @__PURE__ */ new WeakSet();
200
+ const tagger = (0, _flareapp_core.createIdentityTagger)({
201
+ sdkName: "@flareapp/vue",
202
+ sdkVersion: PACKAGE_VERSION,
203
+ frameworkName: _flareapp_core.FrameworkName.Vue
204
+ });
205
+ /** Web path: SDK identity only; the framework version (app.version) is only known at install time. */
229
206
  function registerVueSdkInfo(flare) {
230
- if (sdkTagged.has(flare)) return;
231
- sdkTagged.add(flare);
232
- flare.setSdkInfo({
233
- name: "@flareapp/vue",
234
- version: PACKAGE_VERSION
235
- });
207
+ tagger.registerSdkIdentity(flare);
236
208
  }
209
+ /** Both paths tag the framework; never touches sdkInfo (would clobber an injected SDK name). */
237
210
  function tagVueFramework(flare, appVersion) {
238
- if (frameworkTagged.has(flare)) return;
239
- frameworkTagged.add(flare);
240
- flare.setFramework({
241
- name: "Vue",
242
- version: appVersion
243
- });
211
+ tagger.tagFramework(flare, appVersion);
212
+ }
213
+
214
+ //#endregion
215
+ //#region src/profileVueComponents.ts
216
+ const PROFILE = Symbol("flareComponentProfile");
217
+ /** Only matched components store a marker, so unmatched ones, and functional components, which get
218
+ * no lifecycle hooks at all, need no code of their own. */
219
+ function nearestMarker(instance) {
220
+ for (let node = instance.parent; node; node = node.parent) {
221
+ const state = node[PROFILE];
222
+ if (state) return state.marker;
223
+ }
224
+ return null;
225
+ }
226
+ /**
227
+ * Record one `browser_component` span per matched component mount. `beforeMount` reserves the span id
228
+ * and captures the start; `mounted` records it. Vue runs `beforeMount` top-down and `mounted` bottom-up,
229
+ * so a parent's span encloses its SYNCHRONOUS descendants in time. Async components and anything under
230
+ * `<Suspense>` are outside that contract: their span can start after the parent's ended, or be dropped
231
+ * entirely when the root closed in the meantime. Nesting by parent id holds while the trace is the
232
+ * same; a trace change re-homes a descendant to the live root instead of its dead-trace ancestor.
233
+ */
234
+ function createComponentProfilerMixin(matches) {
235
+ return {
236
+ beforeMount() {
237
+ try {
238
+ const live = (0, _flareapp_js_browser.activeComponentRoot)();
239
+ if (!live) return;
240
+ const name = getComponentName(this);
241
+ if (!matches(name)) return;
242
+ const internal = this.$;
243
+ const parent = (0, _flareapp_js_browser.resolveComponentParent)(nearestMarker(internal), live) ?? live;
244
+ const spanId = (0, _flareapp_js_browser.reserveSpanId)(parent.traceId);
245
+ if (!spanId) return;
246
+ internal[PROFILE] = {
247
+ marker: {
248
+ traceId: parent.traceId,
249
+ parentSpanId: spanId
250
+ },
251
+ pending: {
252
+ name,
253
+ spanId,
254
+ startNano: (0, _flareapp_js_browser.nowNano)(),
255
+ parent
256
+ }
257
+ };
258
+ } catch {}
259
+ },
260
+ mounted() {
261
+ try {
262
+ const state = this.$[PROFILE];
263
+ const pending = state?.pending;
264
+ if (!state || !pending) return;
265
+ state.pending = null;
266
+ (0, _flareapp_js_browser.recordComponentSpan)({
267
+ name: pending.name,
268
+ spanId: pending.spanId,
269
+ parent: pending.parent,
270
+ startTimeUnixNano: pending.startNano,
271
+ endTimeUnixNano: (0, _flareapp_js_browser.nowNano)()
272
+ });
273
+ } catch {}
274
+ }
275
+ };
276
+ }
277
+
278
+ //#endregion
279
+ //#region src/traceVueRouter.ts
280
+ const NAVIGATION_CANCELLED = 8;
281
+ /** Internal, wired through `flareVue({ router })`. Opens a held navigation root per route change,
282
+ * settled when the navigation confirms. */
283
+ function traceVueRouter(router) {
284
+ if (!isVueRouter(router)) return () => {};
285
+ return (0, _flareapp_js_browser.instrumentOnce)(router, (track) => install(router, track));
286
+ }
287
+ /** Guards only what the integration calls unconditionally; `resolve` and `onError` stay optional. */
288
+ function isVueRouter(router) {
289
+ if (typeof router !== "object" || router === null) return false;
290
+ return "beforeEach" in router && typeof router.beforeEach === "function" && "afterEach" in router && typeof router.afterEach === "function";
291
+ }
292
+ function install(router, track) {
293
+ const nav = (0, _flareapp_js_browser.registerNavigationSource)();
294
+ track(() => nav.unregister());
295
+ function routeNameFor(loc) {
296
+ return (0, _flareapp_js_browser.routeName)(() => loc.matched?.[loc.matched.length - 1]?.path, loc.path, hrefOf(loc));
297
+ }
298
+ function hrefOf(loc) {
299
+ const path = loc.fullPath ?? loc.path;
300
+ if (!path) return;
301
+ return (0, _flareapp_js_browser.resolveHref)(() => router.resolve?.(path)?.href, path);
302
+ }
303
+ function isInitial(from) {
304
+ return !from || !from.matched || from.matched.length === 0;
305
+ }
306
+ let sawInitial = false;
307
+ let inFlight = false;
308
+ try {
309
+ const current = router.currentRoute?.value;
310
+ if (current && current.matched && current.matched.length > 0) {
311
+ nav.setActiveRouteName(routeNameFor(current));
312
+ sawInitial = true;
313
+ }
314
+ } catch {}
315
+ track(router.beforeEach((0, _flareapp_js_browser.insulate)((to, from) => {
316
+ if (!sawInitial && isInitial(from)) {
317
+ nav.setActiveRouteName(routeNameFor(to));
318
+ return;
319
+ }
320
+ if (to.fullPath && from?.fullPath && to.fullPath === from.fullPath) return;
321
+ if (!inFlight) {
322
+ inFlight = true;
323
+ nav.startNavigation({
324
+ path: to.path,
325
+ url: hrefOf(to),
326
+ hold: true
327
+ });
328
+ }
329
+ nav.setActiveRouteName(routeNameFor(to));
330
+ })));
331
+ track(router.afterEach((0, _flareapp_js_browser.insulate)((to, from, failure) => {
332
+ if (!sawInitial && isInitial(from)) {
333
+ if (!failure) {
334
+ sawInitial = true;
335
+ nav.setActiveRouteName(routeNameFor(to));
336
+ }
337
+ return;
338
+ }
339
+ if (!inFlight) return;
340
+ if (!failure) {
341
+ inFlight = false;
342
+ nav.settleNavigation(routeNameFor(to));
343
+ return;
344
+ }
345
+ if (failure.type === NAVIGATION_CANCELLED) return;
346
+ inFlight = false;
347
+ nav.settleNavigation(routeNameFor(from));
348
+ })));
349
+ if (typeof router.onError === "function") track(router.onError((0, _flareapp_js_browser.insulate)(() => {
350
+ if (!inFlight) return;
351
+ inFlight = false;
352
+ const current = router.currentRoute?.value;
353
+ nav.settleNavigation(current ? routeNameFor(current) : {
354
+ name: "",
355
+ source: "url"
356
+ });
357
+ })));
244
358
  }
245
359
 
246
360
  //#endregion
@@ -255,7 +369,7 @@ function vueContextToAttributes(context) {
255
369
  };
256
370
  if (context.vue.componentProps) vue.componentProps = context.vue.componentProps;
257
371
  if (context.vue.route) vue.route = context.vue.route;
258
- return { "context.custom": { vue } };
372
+ return (0, _flareapp_core.toCustomContext)("vue", vue);
259
373
  }
260
374
  function vueWarningContextToAttributes(context) {
261
375
  const vue = {
@@ -265,7 +379,7 @@ function vueWarningContextToAttributes(context) {
265
379
  componentTrace: context.vue.componentTrace
266
380
  };
267
381
  if (context.vue.route) vue.route = context.vue.route;
268
- return { "context.custom": { vue } };
382
+ return (0, _flareapp_core.toCustomContext)("vue", vue);
269
383
  }
270
384
  const installedApps = /* @__PURE__ */ new WeakSet();
271
385
  const flareVue = (app, options) => {
@@ -339,6 +453,15 @@ const flareVue = (app, options) => {
339
453
  if (typeof initialWarnHandler === "function") initialWarnHandler(msg, instance, trace);
340
454
  };
341
455
  }
456
+ if (options?.router) try {
457
+ const stopRouterTracing = traceVueRouter(options.router);
458
+ if (typeof app.onUnmount === "function") app.onUnmount(stopRouterTracing);
459
+ } catch {}
460
+ const profile = options?.profileComponents;
461
+ if (!(profile === true || Array.isArray(profile) && profile.length > 0)) return;
462
+ try {
463
+ app.mixin(createComponentProfilerMixin((0, _flareapp_core_util.createComponentMatcher)(profile)));
464
+ } catch {}
342
465
  };
343
466
 
344
467
  //#endregion
@@ -1,31 +1,14 @@
1
- import { convertToError, redactUrlQuery, resolveDenylist } from "@flareapp/core";
1
+ import { activeComponentRoot, createFlareResolver, instrumentOnce, insulate, nowNano, recordComponentSpan, registerNavigationSource, reserveSpanId, resolveComponentParent, resolveHref, routeName } from "@flareapp/js/browser";
2
+ import { FrameworkName, convertToError, createIdentityTagger, redactObjectValues, redactUrlQuery, resolveDenylist, safeClone, toCustomContext } from "@flareapp/core";
2
3
  import { defineComponent, getCurrentInstance, onErrorCaptured, ref, watch } from "vue";
4
+ import { createComponentMatcher } from "@flareapp/core/util";
3
5
 
4
6
  //#region src/resolveFlare.ts
5
- let defaultProvider = null;
6
- function isDevMode() {
7
- try {
8
- return process.env.NODE_ENV !== "production";
9
- } catch {
10
- return false;
11
- }
12
- }
13
- function registerDefaultFlare(provider) {
14
- if (typeof window !== "undefined" && window.__flare) {
15
- const message = "[flare] @flareapp/vue (web root) was imported in a renderer where the Electron bridge is present, pulling the keyed @flareapp/js singleton into the renderer. Import @flareapp/vue/inject and pass the @flareapp/electron/renderer instance instead.";
16
- if (isDevMode()) throw new Error(message);
17
- console.warn(message);
18
- }
19
- defaultProvider = provider;
20
- }
21
- function resolveFlare(explicit) {
22
- if (explicit) return explicit;
23
- if (defaultProvider) return defaultProvider();
24
- throw new Error("[flare] No Flare instance available. Pass `flare` (e.g. from @flareapp/electron/renderer), or import @flareapp/vue (the package root) to use the @flareapp/js default singleton.");
25
- }
7
+ const { registerDefaultFlare, resolveFlare } = createFlareResolver({ packageName: "@flareapp/vue" });
26
8
 
27
9
  //#endregion
28
10
  //#region src/constants.ts
11
+ /** Injected at build time via tsdown --env.PACKAGE_VERSION (reads package.json version). */
29
12
  const PACKAGE_VERSION = typeof process !== "undefined" && typeof process.env?.PACKAGE_VERSION !== "undefined" ? process.env.PACKAGE_VERSION : "?";
30
13
  const MAX_HIERARCHY_DEPTH = 50;
31
14
  const DEFAULT_PROPS_DENYLIST = /password|passwd|pwd|token|secret|authorization|\bauth\b|bearer|oauth|credentials?|cookie|api[-_]?key|private[-_]?key|session|csrf|xsrf|\bpin\b|\bssn\b|card[-_]?number|\bcvv\b/i;
@@ -122,51 +105,19 @@ function buildComponentHierarchy(instance) {
122
105
 
123
106
  //#endregion
124
107
  //#region src/serializeProps.ts
108
+ /**
109
+ * JSON-safe, redacted, size-bounded copy of a Vue component's props for the report payload. Delegates
110
+ * to core safeClone (display mode) so the cycle / BigInt / throwing-getter safety is shared with core.
111
+ */
125
112
  function serializeProps(value, maxDepth, denylist = DEFAULT_PROPS_DENYLIST) {
126
- return serialize(value, 0, maxDepth, /* @__PURE__ */ new WeakSet(), denylist);
127
- }
128
- function serialize(value, depth, maxDepth, seen, denylist) {
129
- if (value === null) return null;
130
- const type = typeof value;
131
- if (type === "function") return "[Function]";
132
- if (type === "symbol") return "[Symbol]";
133
- if (type === "bigint") return value.toString();
134
- if (type === "string") return truncateString(value);
135
- if (type !== "object") return value;
136
- if (seen.has(value)) return "[Circular]";
137
- if (Array.isArray(value)) {
138
- if (depth > maxDepth) return "[Array]";
139
- seen.add(value);
140
- const out = (value.length > MAX_PROP_ARRAY_LENGTH ? value.slice(0, MAX_PROP_ARRAY_LENGTH) : value).map((item) => serialize(item, depth + 1, maxDepth, seen, denylist));
141
- if (value.length > MAX_PROP_ARRAY_LENGTH) out.push(`[… ${value.length - MAX_PROP_ARRAY_LENGTH} more items]`);
142
- seen.delete(value);
143
- return out;
144
- }
145
- if (!isPlainObject(value)) return "[Object]";
146
- if (depth > maxDepth) return "[Object]";
147
- seen.add(value);
148
- const out = {};
149
- const keys = Object.keys(value);
150
- const limitedKeys = keys.length > MAX_PROP_OBJECT_KEYS ? keys.slice(0, MAX_PROP_OBJECT_KEYS) : keys;
151
- for (const key of limitedKeys) {
152
- if (denylist.test(key)) {
153
- out[key] = "[redacted]";
154
- continue;
155
- }
156
- out[key] = serialize(value[key], depth + 1, maxDepth, seen, denylist);
157
- }
158
- if (keys.length > MAX_PROP_OBJECT_KEYS) out["…"] = `[${keys.length - MAX_PROP_OBJECT_KEYS} more keys]`;
159
- seen.delete(value);
160
- return out;
161
- }
162
- function truncateString(value) {
163
- if (value.length <= MAX_PROP_STRING_LENGTH) return value;
164
- return `${value.slice(0, MAX_PROP_STRING_LENGTH)}…[truncated ${value.length - MAX_PROP_STRING_LENGTH} chars]`;
165
- }
166
- function isPlainObject(value) {
167
- if (value === null || typeof value !== "object") return false;
168
- const prototype = Object.getPrototypeOf(value);
169
- return prototype === null || prototype === Object.prototype;
113
+ return safeClone(value, {
114
+ mode: "display",
115
+ maxDepth,
116
+ arrayCap: MAX_PROP_ARRAY_LENGTH,
117
+ objectKeyCap: MAX_PROP_OBJECT_KEYS,
118
+ stringCap: MAX_PROP_STRING_LENGTH,
119
+ denylist
120
+ });
170
121
  }
171
122
 
172
123
  //#endregion
@@ -189,6 +140,10 @@ function buildComponentHierarchyFrames(instance, options) {
189
140
 
190
141
  //#endregion
191
142
  //#region src/getErrorOrigin.ts
143
+ /**
144
+ * Vue's `info` is a human-readable string in dev (e.g. "render function") but a numeric/short code
145
+ * in production. INFO_TO_ORIGIN maps both forms to a stable origin category.
146
+ */
192
147
  function getErrorOrigin(info) {
193
148
  return INFO_TO_ORIGIN[info] ?? "unknown";
194
149
  }
@@ -196,6 +151,29 @@ function getErrorOrigin(info) {
196
151
  //#endregion
197
152
  //#region src/getRouteContext.ts
198
153
  const ROUTE_PARAMS_DEPTH = 2;
154
+ /** vue-router allows a symbol route name; neither shape survives JSON, so convert both to a string. */
155
+ function routeNameOf(value) {
156
+ if (typeof value === "string") return value;
157
+ if (typeof value === "symbol") return value.toString();
158
+ return null;
159
+ }
160
+ function matchedNames(matched) {
161
+ if (!Array.isArray(matched)) return [];
162
+ return matched.map((record) => {
163
+ if (!record || typeof record !== "object") return "unknown";
164
+ return routeNameOf(record.name) ?? "unknown";
165
+ });
166
+ }
167
+ function recordOrEmpty(value) {
168
+ if (!value || typeof value !== "object") return {};
169
+ return value;
170
+ }
171
+ /**
172
+ * `router` is typed `unknown` because vue-router is an optional peer (importing it would force every
173
+ * consumer to install it, and the runtime shape may differ across v4.x patches). The chained
174
+ * type-guards read defensively so a missing or shimmed router yields `null` rather than throwing
175
+ * inside an error handler.
176
+ */
199
177
  function getRouteContext(router, options = {}) {
200
178
  if (!router || typeof router !== "object" || !("currentRoute" in router)) return null;
201
179
  const currentRouteRef = router.currentRoute;
@@ -203,44 +181,180 @@ function getRouteContext(router, options = {}) {
203
181
  const route = currentRouteRef.value;
204
182
  if (!route || typeof route !== "object") return null;
205
183
  const r = route;
206
- const name = r.name;
207
184
  const denylist = options.denylist ?? DEFAULT_PROPS_DENYLIST;
208
- const params = r.params && typeof r.params === "object" ? r.params : {};
209
- const query = r.query && typeof r.query === "object" ? r.query : {};
185
+ const redactedParams = redactObjectValues(recordOrEmpty(r.params), denylist);
186
+ const redactedQuery = redactObjectValues(recordOrEmpty(r.query), denylist);
210
187
  return {
211
- name: typeof name === "string" ? name : typeof name === "symbol" ? name.toString() : null,
188
+ name: routeNameOf(r.name),
212
189
  path: typeof r.path === "string" ? r.path : "",
213
190
  fullPath: typeof r.fullPath === "string" ? redactUrlQuery(r.fullPath, denylist) : "",
214
- params: serializeProps(params, ROUTE_PARAMS_DEPTH, denylist),
215
- query: serializeProps(query, ROUTE_PARAMS_DEPTH, denylist),
191
+ params: serializeProps(redactedParams, ROUTE_PARAMS_DEPTH, denylist),
192
+ query: serializeProps(redactedQuery, ROUTE_PARAMS_DEPTH, denylist),
216
193
  hash: typeof r.hash === "string" ? r.hash : "",
217
- matched: Array.isArray(r.matched) ? r.matched.map((record) => {
218
- if (!record || typeof record !== "object") return "unknown";
219
- const n = record.name;
220
- return typeof n === "string" ? n : typeof n === "symbol" ? n.toString() : "unknown";
221
- }) : []
194
+ matched: matchedNames(r.matched)
222
195
  };
223
196
  }
224
197
 
225
198
  //#endregion
226
199
  //#region src/identify.ts
227
- const sdkTagged = /* @__PURE__ */ new WeakSet();
228
- const frameworkTagged = /* @__PURE__ */ new WeakSet();
200
+ const tagger = createIdentityTagger({
201
+ sdkName: "@flareapp/vue",
202
+ sdkVersion: PACKAGE_VERSION,
203
+ frameworkName: FrameworkName.Vue
204
+ });
205
+ /** Web path: SDK identity only; the framework version (app.version) is only known at install time. */
229
206
  function registerVueSdkInfo(flare) {
230
- if (sdkTagged.has(flare)) return;
231
- sdkTagged.add(flare);
232
- flare.setSdkInfo({
233
- name: "@flareapp/vue",
234
- version: PACKAGE_VERSION
235
- });
207
+ tagger.registerSdkIdentity(flare);
236
208
  }
209
+ /** Both paths tag the framework; never touches sdkInfo (would clobber an injected SDK name). */
237
210
  function tagVueFramework(flare, appVersion) {
238
- if (frameworkTagged.has(flare)) return;
239
- frameworkTagged.add(flare);
240
- flare.setFramework({
241
- name: "Vue",
242
- version: appVersion
243
- });
211
+ tagger.tagFramework(flare, appVersion);
212
+ }
213
+
214
+ //#endregion
215
+ //#region src/profileVueComponents.ts
216
+ const PROFILE = Symbol("flareComponentProfile");
217
+ /** Only matched components store a marker, so unmatched ones, and functional components, which get
218
+ * no lifecycle hooks at all, need no code of their own. */
219
+ function nearestMarker(instance) {
220
+ for (let node = instance.parent; node; node = node.parent) {
221
+ const state = node[PROFILE];
222
+ if (state) return state.marker;
223
+ }
224
+ return null;
225
+ }
226
+ /**
227
+ * Record one `browser_component` span per matched component mount. `beforeMount` reserves the span id
228
+ * and captures the start; `mounted` records it. Vue runs `beforeMount` top-down and `mounted` bottom-up,
229
+ * so a parent's span encloses its SYNCHRONOUS descendants in time. Async components and anything under
230
+ * `<Suspense>` are outside that contract: their span can start after the parent's ended, or be dropped
231
+ * entirely when the root closed in the meantime. Nesting by parent id holds while the trace is the
232
+ * same; a trace change re-homes a descendant to the live root instead of its dead-trace ancestor.
233
+ */
234
+ function createComponentProfilerMixin(matches) {
235
+ return {
236
+ beforeMount() {
237
+ try {
238
+ const live = activeComponentRoot();
239
+ if (!live) return;
240
+ const name = getComponentName(this);
241
+ if (!matches(name)) return;
242
+ const internal = this.$;
243
+ const parent = resolveComponentParent(nearestMarker(internal), live) ?? live;
244
+ const spanId = reserveSpanId(parent.traceId);
245
+ if (!spanId) return;
246
+ internal[PROFILE] = {
247
+ marker: {
248
+ traceId: parent.traceId,
249
+ parentSpanId: spanId
250
+ },
251
+ pending: {
252
+ name,
253
+ spanId,
254
+ startNano: nowNano(),
255
+ parent
256
+ }
257
+ };
258
+ } catch {}
259
+ },
260
+ mounted() {
261
+ try {
262
+ const state = this.$[PROFILE];
263
+ const pending = state?.pending;
264
+ if (!state || !pending) return;
265
+ state.pending = null;
266
+ recordComponentSpan({
267
+ name: pending.name,
268
+ spanId: pending.spanId,
269
+ parent: pending.parent,
270
+ startTimeUnixNano: pending.startNano,
271
+ endTimeUnixNano: nowNano()
272
+ });
273
+ } catch {}
274
+ }
275
+ };
276
+ }
277
+
278
+ //#endregion
279
+ //#region src/traceVueRouter.ts
280
+ const NAVIGATION_CANCELLED = 8;
281
+ /** Internal, wired through `flareVue({ router })`. Opens a held navigation root per route change,
282
+ * settled when the navigation confirms. */
283
+ function traceVueRouter(router) {
284
+ if (!isVueRouter(router)) return () => {};
285
+ return instrumentOnce(router, (track) => install(router, track));
286
+ }
287
+ /** Guards only what the integration calls unconditionally; `resolve` and `onError` stay optional. */
288
+ function isVueRouter(router) {
289
+ if (typeof router !== "object" || router === null) return false;
290
+ return "beforeEach" in router && typeof router.beforeEach === "function" && "afterEach" in router && typeof router.afterEach === "function";
291
+ }
292
+ function install(router, track) {
293
+ const nav = registerNavigationSource();
294
+ track(() => nav.unregister());
295
+ function routeNameFor(loc) {
296
+ return routeName(() => loc.matched?.[loc.matched.length - 1]?.path, loc.path, hrefOf(loc));
297
+ }
298
+ function hrefOf(loc) {
299
+ const path = loc.fullPath ?? loc.path;
300
+ if (!path) return;
301
+ return resolveHref(() => router.resolve?.(path)?.href, path);
302
+ }
303
+ function isInitial(from) {
304
+ return !from || !from.matched || from.matched.length === 0;
305
+ }
306
+ let sawInitial = false;
307
+ let inFlight = false;
308
+ try {
309
+ const current = router.currentRoute?.value;
310
+ if (current && current.matched && current.matched.length > 0) {
311
+ nav.setActiveRouteName(routeNameFor(current));
312
+ sawInitial = true;
313
+ }
314
+ } catch {}
315
+ track(router.beforeEach(insulate((to, from) => {
316
+ if (!sawInitial && isInitial(from)) {
317
+ nav.setActiveRouteName(routeNameFor(to));
318
+ return;
319
+ }
320
+ if (to.fullPath && from?.fullPath && to.fullPath === from.fullPath) return;
321
+ if (!inFlight) {
322
+ inFlight = true;
323
+ nav.startNavigation({
324
+ path: to.path,
325
+ url: hrefOf(to),
326
+ hold: true
327
+ });
328
+ }
329
+ nav.setActiveRouteName(routeNameFor(to));
330
+ })));
331
+ track(router.afterEach(insulate((to, from, failure) => {
332
+ if (!sawInitial && isInitial(from)) {
333
+ if (!failure) {
334
+ sawInitial = true;
335
+ nav.setActiveRouteName(routeNameFor(to));
336
+ }
337
+ return;
338
+ }
339
+ if (!inFlight) return;
340
+ if (!failure) {
341
+ inFlight = false;
342
+ nav.settleNavigation(routeNameFor(to));
343
+ return;
344
+ }
345
+ if (failure.type === NAVIGATION_CANCELLED) return;
346
+ inFlight = false;
347
+ nav.settleNavigation(routeNameFor(from));
348
+ })));
349
+ if (typeof router.onError === "function") track(router.onError(insulate(() => {
350
+ if (!inFlight) return;
351
+ inFlight = false;
352
+ const current = router.currentRoute?.value;
353
+ nav.settleNavigation(current ? routeNameFor(current) : {
354
+ name: "",
355
+ source: "url"
356
+ });
357
+ })));
244
358
  }
245
359
 
246
360
  //#endregion
@@ -255,7 +369,7 @@ function vueContextToAttributes(context) {
255
369
  };
256
370
  if (context.vue.componentProps) vue.componentProps = context.vue.componentProps;
257
371
  if (context.vue.route) vue.route = context.vue.route;
258
- return { "context.custom": { vue } };
372
+ return toCustomContext("vue", vue);
259
373
  }
260
374
  function vueWarningContextToAttributes(context) {
261
375
  const vue = {
@@ -265,7 +379,7 @@ function vueWarningContextToAttributes(context) {
265
379
  componentTrace: context.vue.componentTrace
266
380
  };
267
381
  if (context.vue.route) vue.route = context.vue.route;
268
- return { "context.custom": { vue } };
382
+ return toCustomContext("vue", vue);
269
383
  }
270
384
  const installedApps = /* @__PURE__ */ new WeakSet();
271
385
  const flareVue = (app, options) => {
@@ -339,6 +453,15 @@ const flareVue = (app, options) => {
339
453
  if (typeof initialWarnHandler === "function") initialWarnHandler(msg, instance, trace);
340
454
  };
341
455
  }
456
+ if (options?.router) try {
457
+ const stopRouterTracing = traceVueRouter(options.router);
458
+ if (typeof app.onUnmount === "function") app.onUnmount(stopRouterTracing);
459
+ } catch {}
460
+ const profile = options?.profileComponents;
461
+ if (!(profile === true || Array.isArray(profile) && profile.length > 0)) return;
462
+ try {
463
+ app.mixin(createComponentProfilerMixin(createComponentMatcher(profile)));
464
+ } catch {}
342
465
  };
343
466
 
344
467
  //#endregion
@@ -1,6 +1,7 @@
1
1
  import * as vue from "vue";
2
2
  import { ComponentPublicInstance, Plugin, PropType } from "vue";
3
3
  import { Flare } from "@flareapp/js/browser";
4
+ import { ProfileComponentsOption } from "@flareapp/core/util";
4
5
 
5
6
  //#region src/types.d.ts
6
7
  type ErrorOrigin = 'setup' | 'render' | 'lifecycle' | 'event' | 'watcher' | 'unknown';
@@ -46,7 +47,14 @@ type FlareErrorBoundaryHookParams = {
46
47
  info: string;
47
48
  };
48
49
  type FlareVueOptions = {
49
- flare?: Flare;
50
+ flare?: Flare; /** A vue-router Router instance. When set, enables navigation/pageload performance tracing. */
51
+ router?: unknown;
52
+ /**
53
+ * Record a span per component mount. An array matches component names exactly (string) or by
54
+ * `test()` (RegExp). `true` profiles every named component, which is a debugging aid: a real page
55
+ * will hit `maxSpansPerTrace` and bury the useful spans. Requires `enableTracing`.
56
+ */
57
+ profileComponents?: ProfileComponentsOption;
50
58
  captureWarnings?: boolean;
51
59
  attachProps?: boolean;
52
60
  propsMaxDepth?: number;
@@ -1,6 +1,7 @@
1
+ import { Flare } from "@flareapp/js/browser";
1
2
  import * as vue from "vue";
2
3
  import { ComponentPublicInstance, Plugin, PropType } from "vue";
3
- import { Flare } from "@flareapp/js/browser";
4
+ import { ProfileComponentsOption } from "@flareapp/core/util";
4
5
 
5
6
  //#region src/types.d.ts
6
7
  type ErrorOrigin = 'setup' | 'render' | 'lifecycle' | 'event' | 'watcher' | 'unknown';
@@ -46,7 +47,14 @@ type FlareErrorBoundaryHookParams = {
46
47
  info: string;
47
48
  };
48
49
  type FlareVueOptions = {
49
- flare?: Flare;
50
+ flare?: Flare; /** A vue-router Router instance. When set, enables navigation/pageload performance tracing. */
51
+ router?: unknown;
52
+ /**
53
+ * Record a span per component mount. An array matches component names exactly (string) or by
54
+ * `test()` (RegExp). `true` profiles every named component, which is a debugging aid: a real page
55
+ * will hit `maxSpansPerTrace` and bury the useful spans. Requires `enableTracing`.
56
+ */
57
+ profileComponents?: ProfileComponentsOption;
50
58
  captureWarnings?: boolean;
51
59
  attachProps?: boolean;
52
60
  propsMaxDepth?: number;
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_FlareErrorBoundary = require('./FlareErrorBoundary-G0Jqt8zN.cjs');
2
+ const require_FlareErrorBoundary = require('./FlareErrorBoundary-BndkpJFI.cjs');
3
3
  let _flareapp_js = require("@flareapp/js");
4
4
 
5
5
  //#region src/index.ts
package/dist/index.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ErrorOrigin, c as FlareVueContext, d as RouteContext, f as RouteParamValue, i as ComponentHierarchyFrame, l as FlareVueOptions, n as flareVue, o as FlareErrorBoundaryFallbackProps, p as RouteQueryValue, r as FlareErrorBoundary, s as FlareErrorBoundaryHookParams, t as DEFAULT_PROPS_DENYLIST, u as FlareVueWarningContext } from "./constants-B6O_8KY6.cjs";
1
+ import { a as ErrorOrigin, c as FlareVueContext, d as RouteContext, f as RouteParamValue, i as ComponentHierarchyFrame, l as FlareVueOptions, n as flareVue, o as FlareErrorBoundaryFallbackProps, p as RouteQueryValue, r as FlareErrorBoundary, s as FlareErrorBoundaryHookParams, t as DEFAULT_PROPS_DENYLIST, u as FlareVueWarningContext } from "./constants-BFiYPDOl.cjs";
2
2
  export { type ComponentHierarchyFrame, DEFAULT_PROPS_DENYLIST, type ErrorOrigin, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryHookParams, type FlareVueContext, type FlareVueOptions, type FlareVueWarningContext, type RouteContext, type RouteParamValue, type RouteQueryValue, flareVue };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ErrorOrigin, c as FlareVueContext, d as RouteContext, f as RouteParamValue, i as ComponentHierarchyFrame, l as FlareVueOptions, n as flareVue, o as FlareErrorBoundaryFallbackProps, p as RouteQueryValue, r as FlareErrorBoundary, s as FlareErrorBoundaryHookParams, t as DEFAULT_PROPS_DENYLIST, u as FlareVueWarningContext } from "./constants-DCI9dIQ_.mjs";
1
+ import { a as ErrorOrigin, c as FlareVueContext, d as RouteContext, f as RouteParamValue, i as ComponentHierarchyFrame, l as FlareVueOptions, n as flareVue, o as FlareErrorBoundaryFallbackProps, p as RouteQueryValue, r as FlareErrorBoundary, s as FlareErrorBoundaryHookParams, t as DEFAULT_PROPS_DENYLIST, u as FlareVueWarningContext } from "./constants-CFWxnFRO.mjs";
2
2
  export { type ComponentHierarchyFrame, DEFAULT_PROPS_DENYLIST, type ErrorOrigin, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryHookParams, type FlareVueContext, type FlareVueOptions, type FlareVueWarningContext, type RouteContext, type RouteParamValue, type RouteQueryValue, flareVue };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as registerDefaultFlare, n as flareVue, r as DEFAULT_PROPS_DENYLIST, t as FlareErrorBoundary } from "./FlareErrorBoundary-9LmNTGFB.mjs";
1
+ import { i as registerDefaultFlare, n as flareVue, r as DEFAULT_PROPS_DENYLIST, t as FlareErrorBoundary } from "./FlareErrorBoundary-CO0ZV9lZ.mjs";
2
2
  import { flare } from "@flareapp/js";
3
3
 
4
4
  //#region src/index.ts
package/dist/inject.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_FlareErrorBoundary = require('./FlareErrorBoundary-G0Jqt8zN.cjs');
2
+ const require_FlareErrorBoundary = require('./FlareErrorBoundary-BndkpJFI.cjs');
3
3
 
4
4
  exports.DEFAULT_PROPS_DENYLIST = require_FlareErrorBoundary.DEFAULT_PROPS_DENYLIST;
5
5
  exports.FlareErrorBoundary = require_FlareErrorBoundary.FlareErrorBoundary;
package/dist/inject.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ErrorOrigin, c as FlareVueContext, d as RouteContext, f as RouteParamValue, i as ComponentHierarchyFrame, l as FlareVueOptions, n as flareVue, o as FlareErrorBoundaryFallbackProps, p as RouteQueryValue, r as FlareErrorBoundary, s as FlareErrorBoundaryHookParams, t as DEFAULT_PROPS_DENYLIST, u as FlareVueWarningContext } from "./constants-B6O_8KY6.cjs";
1
+ import { a as ErrorOrigin, c as FlareVueContext, d as RouteContext, f as RouteParamValue, i as ComponentHierarchyFrame, l as FlareVueOptions, n as flareVue, o as FlareErrorBoundaryFallbackProps, p as RouteQueryValue, r as FlareErrorBoundary, s as FlareErrorBoundaryHookParams, t as DEFAULT_PROPS_DENYLIST, u as FlareVueWarningContext } from "./constants-BFiYPDOl.cjs";
2
2
  export { type ComponentHierarchyFrame, DEFAULT_PROPS_DENYLIST, type ErrorOrigin, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryHookParams, type FlareVueContext, type FlareVueOptions, type FlareVueWarningContext, type RouteContext, type RouteParamValue, type RouteQueryValue, flareVue };
package/dist/inject.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ErrorOrigin, c as FlareVueContext, d as RouteContext, f as RouteParamValue, i as ComponentHierarchyFrame, l as FlareVueOptions, n as flareVue, o as FlareErrorBoundaryFallbackProps, p as RouteQueryValue, r as FlareErrorBoundary, s as FlareErrorBoundaryHookParams, t as DEFAULT_PROPS_DENYLIST, u as FlareVueWarningContext } from "./constants-DCI9dIQ_.mjs";
1
+ import { a as ErrorOrigin, c as FlareVueContext, d as RouteContext, f as RouteParamValue, i as ComponentHierarchyFrame, l as FlareVueOptions, n as flareVue, o as FlareErrorBoundaryFallbackProps, p as RouteQueryValue, r as FlareErrorBoundary, s as FlareErrorBoundaryHookParams, t as DEFAULT_PROPS_DENYLIST, u as FlareVueWarningContext } from "./constants-CFWxnFRO.mjs";
2
2
  export { type ComponentHierarchyFrame, DEFAULT_PROPS_DENYLIST, type ErrorOrigin, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryHookParams, type FlareVueContext, type FlareVueOptions, type FlareVueWarningContext, type RouteContext, type RouteParamValue, type RouteQueryValue, flareVue };
package/dist/inject.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { n as flareVue, r as DEFAULT_PROPS_DENYLIST, t as FlareErrorBoundary } from "./FlareErrorBoundary-9LmNTGFB.mjs";
1
+ import { n as flareVue, r as DEFAULT_PROPS_DENYLIST, t as FlareErrorBoundary } from "./FlareErrorBoundary-CO0ZV9lZ.mjs";
2
2
 
3
3
  export { DEFAULT_PROPS_DENYLIST, FlareErrorBoundary, flareVue };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flareapp/vue",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "Vue client for flareapp.io",
5
5
  "homepage": "https://flareapp.io",
6
6
  "bugs": "https://github.com/spatie/flare-client-js/issues",
@@ -62,11 +62,12 @@
62
62
  "verify:inject": "node scripts/verify-inject-no-root.mjs"
63
63
  },
64
64
  "dependencies": {
65
- "@flareapp/core": "2.7.0"
65
+ "@flareapp/core": "2.8.0"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@flareapp/electron": "file:../electron",
69
69
  "@flareapp/js": "file:../js",
70
+ "@flareapp/test-helpers": "*",
70
71
  "@vue/test-utils": "^2.4.0",
71
72
  "jsdom": "^26.1.0",
72
73
  "tsdown": "^0.20.3",
@@ -76,7 +77,7 @@
76
77
  "vue-router": "^5.0.0"
77
78
  },
78
79
  "peerDependencies": {
79
- "@flareapp/js": "^2.7.0",
80
+ "@flareapp/js": "^2.8.0",
80
81
  "vue": "^3.0.0",
81
82
  "vue-router": "^4.0.0 || ^5.0.0"
82
83
  },