@arms/rum-browser-nuxt 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-09-01)
4
+
5
+ 首发版本。`@arms/rum-browser` 的 Nuxt 插件(Nuxt 3 / 4):文件路由风格的视图追踪、Nuxt 双通道异常捕获、手动错误上报。纯客户端薄适配包 —— 独立实现(零兄弟包依赖),源码零 nuxt / vue / vue-router 导入(SSR-safe)。
6
+
7
+ ### 视图追踪(文件路由命名)
8
+
9
+ - `initNuxtPlugin(shell, { router, nuxtApp? })`:单入口初始化 API;注册 `nuxt` collector(`ICollector`)并无条件注入 `trackViewsManually`(router 必填)—— view 的创建、命名与计时完全由路由变化驱动(内部调用 `shell.startView`)
10
+ - **Nuxt 文件路由名归一化**(本包核心差异化能力):view.name 按 `to.matched` 尾向头取第一个非空 `path`,再反向归一化为 Nuxt 文件路由风格(`:id()` → `[id]`、`:id?` → `[[id]]`、`:slug(.*)*` → `[...slug]`、转义还原),与 `pages/` 目录心智一致(算法与正则从 Datadog browser-rum-nuxt 源码原样移植,14+ 参数化单测对齐)
11
+ - 初始视图补偿:`router.currentRoute.matched` 非空(插件晚于首个导航接入)时立即补报首屏 view;router 未就绪时由 afterEach 覆盖首个导航
12
+ - 导航语义:query-only 变化不开新视图、hash 变化不开新视图(afterEach 放行但视图名不变,经 `Shell.startView` 同名去重,净效果与 browser-vue 一致)、导航失败(afterEach 第三参 `failure`)不开新视图、同名 view 去重由 `Shell.startView` 内建
13
+ - `loading_type` 语义对齐 browser-vue:ctx 已有 view 时 `route_change`,否则 `initial_load`
14
+ - pending 队列兜底反向初始化时序:`initNuxtPlugin` 早于 `armsRum.init()` 完成时缓存最近一次 startView 参数,collector setup 末尾 flush 补发(强制 `initial_load`),首屏不丢失
15
+
16
+ ### 异常捕获(双通道 + 去重)
17
+
18
+ - **通道 1**:链式包装 `nuxtApp.vueApp.config.errorHandler` —— 先经 `addNuxtError` 上报再透传原 handler(保留 Nuxt 自带 `handleVueError` 驱动错误页的行为;无原 handler 时 `console.error` 保持 Vue 默认可见性,不吞错)
19
+ - **通道 2**:`nuxtApp.hook('app:error')` 捕获 Nuxt 启动 / SSR 传播错误(配合插件 `enforce: 'pre'` 可捕获后续插件启动错误)
20
+ - **WeakSet 按错误对象引用去重**:同一错误对象经双通道只报一次(非 object 错误值无引用语义,不去重)
21
+ - `addNuxtError(error, context?)`:手动上报(供 `onErrorCaptured` / 自定义 errorHandler 使用);`error` 参数可为任意值,内部自动 normalize 为 Error;事件以 `source: 'vue'` 上报,`snapshots`(JSON 字符串)内携带 `framework: 'nuxt'` 与 `handling: 'handled'` 及可得时的 `component_stack` / `component_name` / `lifecycle_hook`(snake_case 键,字段收敛模式对齐 browser-vue `addVueError`)
22
+ - init 前错误排队:插件已接入但 collector 未 setup 时排队(上限 20 条),setup 末尾统一补发;插件未接入时 warn + skip,不抛错
23
+
24
+ ### 工程特性
25
+
26
+ - `es` / `lib` 双产物(ESM / CJS 单入口,`exports` 四条件带 `.js` 扩展名)
27
+ - 零 `@babel/runtime` 运行时依赖;`es` / `lib` 产物经 terser 压缩管线:构建 fail-fast、压缩后复检、**产物零 nuxt / vue / vue-router 导入断言**(SSR-safe 纪律的构建期强制)、spec 产物清理、入口压缩形态断言
28
+ - ES5 语法兼容(源码 var + function 风格,无 class field)
29
+ - peer 依赖:`@arms/rum-core >=0.1.11`、`@arms/rum-browser >=0.1.16`(必选,下限与 registry 实际产物对齐);`nuxt` `3 || 4`、`vue ^3.5.0`、`vue-router ^4.0.0 || ^5.0.0` 为可选 peer(框架实例经 options 注入,包自身零框架导入)
30
+ - 插件自监控:关键失败场景(缺 shell / 缺有效 router、invalid nuxtApp、setup 失败、addNuxtError 失败)以 `logger.error` / `logger.warn` 分级触发 Telemetry 自监控上报
31
+ - 评审加固:collector setup 中 `trackViewsManually` 配置注入与 pending flush(首屏 view / 排队错误)解耦为独立 try-catch(注入失败仅降级不连坐补发);不同 rum 实例二次 `initNuxtPlugin` 时统一 `logger.warn` 告警(单实例约束提示,不改行为);e2e 新增版本矩阵断言(主工程 vue-router v5、冒烟 v4,漂移即 FAIL)
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # @arms/rum-browser-nuxt
2
+
3
+ Nuxt 插件 for `@arms/rum-browser` RUM SDK —— 支持 **Nuxt 3 / Nuxt 4**(Vue 3.5+、vue-router v4 / v5)。
4
+
5
+ 能力范围:
6
+
7
+ - **文件路由风格的视图追踪**:经 `router.afterEach` 驱动 `Shell.startView`(手动 view 模式),view.name 反向归一化为 Nuxt 文件路由风格(`/user/:id()` → `/user/[id]`),与 `pages/` 目录心智一致、聚合稳定
8
+ - **Nuxt 双通道异常捕获**:链式包装 `vueApp.config.errorHandler`(保留原 handler)+ `nuxtApp.hook('app:error')`(Nuxt 启动 / SSR 传播错误),同一错误对象 WeakSet 去重只报一次,自动上报 `source: 'vue'` 的异常事件(`snapshots` 内携带 `framework: 'nuxt'`)
9
+ - **手动错误上报**:`addNuxtError(error, context?)` 供 `onErrorCaptured` / 自定义 errorHandler 场景转发
10
+
11
+ > 不含:组件性能监控、hydration 错误分级、Nuxt Module 形态、Nitro 服务端集成(均为非目标,见 docs-dev/browser-nuxt-spec.md)。
12
+
13
+ ## 安装
14
+
15
+ 本插件不独立工作 —— 需与主包 `@arms/rum-browser`(必需 peer dependency)同时安装,并完成主包初始化:
16
+
17
+ ```bash
18
+ npm install @arms/rum-browser @arms/rum-browser-nuxt
19
+ ```
20
+
21
+ ## 发布前置条件
22
+
23
+ 发布顺序(严格按序,前一步发版在 registry 生效后,再发布下一步):
24
+
25
+ 1. **`@arms/rum-core@>=0.1.11`**:含 `Shell.startView` 与 `IConfiguration.trackViewsManually` 能力的发版(已在 npm registry 发布)
26
+ 2. **`@arms/rum-browser@>=0.1.16`**:含 PvCollector 手动模式防护(`trackViewsManually` 早退,避免双发 PV)的发版(已在 npm registry 发布)
27
+ 3. **`@arms/rum-browser-nuxt`**(本包)
28
+
29
+ 本包为独立实现(不依赖 `@arms/rum-browser-vue` / `@arms/rum-browser-react`,dependencies 为空),无兄弟包解锁约束;peerDependencies 下限与 registry 实际产物对齐,外部安装可正常解析。后续版本迭代仍需遵循上述发布顺序(依赖包发版在 registry 生效后,再发布本包)。仓库内开发期经 workspace 直链源码使用(无需等待发版),安装依赖请遵循仓库惯例使用 `yarn` 或 `scripts/bootstrap.sh`。
30
+
31
+ ## 快速开始
32
+
33
+ 在 Nuxt 项目中创建客户端插件文件(`.client.ts` 后缀保证仅在客户端执行):
34
+
35
+ ```ts
36
+ // plugins/arms-rum.client.ts
37
+ import ArmsRum from '@arms/rum-browser';
38
+ import { initNuxtPlugin } from '@arms/rum-browser-nuxt';
39
+
40
+ export default defineNuxtPlugin({
41
+ name: 'arms-rum',
42
+ // enforce: 'pre' 保证本插件先于其他插件执行,
43
+ // 错误双通道可捕获后续插件的启动错误
44
+ enforce: 'pre',
45
+ setup() {
46
+ ArmsRum.init({
47
+ endpoint: 'https://your-endpoint',
48
+ // pid: 'your-pid',
49
+ });
50
+ initNuxtPlugin(ArmsRum, {
51
+ router: useRouter(), // 必填:驱动视图采集
52
+ nuxtApp: useNuxtApp(), // 推荐:启用错误双通道
53
+ });
54
+ },
55
+ });
56
+ ```
57
+
58
+ > **时序说明**:`initNuxtPlugin` 不依赖 `armsRum.init()` 完成 —— 可在 init 前后任意时序同步调用,先后顺序可互换(反向时序经内部 pending 队列兜底,首屏 view 与排队错误不丢失)。`defineNuxtPlugin` / `useRouter` / `useNuxtApp` 均为 Nuxt 自动导入 —— 本包源码零 nuxt / vue / vue-router 导入(SSR-safe),router / nuxtApp 由用户在 `setup()` 中取好后传入。
59
+
60
+ ## 视图命名规则
61
+
62
+ view.name 按 `to.matched` **尾向头**取第一个非空 `path`(最深匹配段即页面本身的模板),再反向归一化为 Nuxt 文件路由风格:
63
+
64
+ | vue-router 路由模板(matched.path) | view.name(Nuxt 文件路由风格) | 对应 pages/ 目录 |
65
+ | -------------------------------------- | ------------------------------ | ------------------------------ |
66
+ | `/users` | `/users` | `pages/users.vue` |
67
+ | `/user/:id()` | `/user/[id]` | `pages/user/[id].vue` |
68
+ | `/blog/:slug?` | `/blog/[[slug]]` | `pages/blog/[[slug]].vue` |
69
+ | `/:slug(.*)*` | `/[...slug]` | `pages/[...slug].vue` |
70
+ | `/:pathMatch(.*)*` | `/[...pathMatch]` | `pages/[...pathMatch].vue` |
71
+ | `/users-:group()-:id()` | `/users-[group]-[id]` | `pages/users-[group]-[id].vue` |
72
+ | 嵌套路由(父 `''` + 子 `/user/:id()`) | `/user/[id]`(尾向头取最深段) | `pages/user/[id].vue` |
73
+
74
+ 静态路由原样保留;转义字符还原为字面量(`\:` → `:`)。
75
+
76
+ ## 路由语义
77
+
78
+ > **警告:`router` 必填 —— 传入后 view/PV 完全由 `afterEach` 驱动,主包自动 PV 被禁用**
79
+ >
80
+ > 插件在 `nuxtCollector.setup` 中注入 `trackViewsManually: true`:主包 PvCollector 跳过首屏 PV 与 history 拦截(避免双发),view 的创建、命名、计时**完全依赖** `router.afterEach` 驱动(内部调用 `shell.startView`)。
81
+
82
+ 导航语义(对齐 Nuxt 场景约定):
83
+
84
+ | 导航场景 | 是否开新视图 | 说明 |
85
+ | ------------------------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | 首次导航 / 首屏 | ✅ | `initial_load`;init 前触发经 pending 队列补发 |
87
+ | 路径变化 | ✅ | `route_change`(ctx 已有 view 时;否则 `initial_load`) |
88
+ | **query 变化**(path 与 hash 不变) | ❌ 跳过 | `/list?page=1` → `/list?page=2` 不算新视图 |
89
+ | **hash 变化**(path 不变) | ❌ 去重 | `/doc#section-a` → `/doc#section-b` afterEach 放行但视图名不变,经 `Shell.startView` 同名去重后无新 view 事件(与 browser-vue 行为一致) |
90
+ | 导航失败(afterEach 第三参 failure) | ❌ 跳过 | guard 拦截 / 取消等不算新视图 |
91
+ | 同名重复导航 | ❌ 去重 | `Shell.startView` 内建同名去重 |
92
+
93
+ ## 异常捕获说明
94
+
95
+ `nuxtApp` 传入时启用双通道自动接管:
96
+
97
+ - **通道 1**:链式包装 `nuxtApp.vueApp.config.errorHandler` —— 先经 `addNuxtError` 上报,再调用你已有的 handler(保留 Nuxt 自带 `handleVueError` 驱动错误页的行为;无原 handler 时保持 Vue 默认的 `console.error` 可见性,不吞错)
98
+ - **通道 2**:`nuxtApp.hook('app:error')` 捕获 Nuxt 启动 / SSR 传播错误(纯 Vue 场景不存在的 Nuxt 独有通道;插件 `enforce: 'pre'` 时可捕获后续插件启动错误)
99
+ - **去重**:同一错误对象(引用相等)经双通道只报一次(WeakSet);非 object 错误值无引用语义,不去重
100
+
101
+ 上报字段:
102
+
103
+ ```ts
104
+ {
105
+ event_type: 'exception',
106
+ type: 'error',
107
+ source: 'vue', // 复用家族 vue 错误存储/查询链路
108
+ name: 'TypeError', // error.name(非 Error 值自动 normalize)
109
+ message: "Cannot read properties of undefined",
110
+ stack: '...',
111
+ // 扩展信息收敛为单一 JSON 字符串字段(值为 undefined 的内部键不会出现)
112
+ snapshots: JSON.stringify({
113
+ handling: 'handled',
114
+ framework: 'nuxt', // 本包标识,恒定携带
115
+ component_stack: 'at <OrderList>\nat <UserPage>', // 当前组件在前,沿父链向上
116
+ component_name: 'OrderList',
117
+ lifecycle_hook: 'hook:mounted', // Vue errorHandler 的 info 参数
118
+ }),
119
+ times: 1,
120
+ }
121
+ ```
122
+
123
+ 也可在 `onErrorCaptured` / 自定义 errorHandler 中手动上报(双通道已接管时无需重复调用):
124
+
125
+ ```ts
126
+ import { addNuxtError } from '@arms/rum-browser-nuxt';
127
+
128
+ // 组件内
129
+ onErrorCaptured(function (err, instance, info) {
130
+ addNuxtError(err, { lifecycle_hook: info });
131
+ return false;
132
+ });
133
+ ```
134
+
135
+ ## API
136
+
137
+ ### 主入口 `@arms/rum-browser-nuxt`(单入口,无子入口)
138
+
139
+ | 导出 | 说明 |
140
+ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |
141
+ | `initNuxtPlugin(shell: Shell, options: NuxtPluginOptions)` | 初始化插件(collector 注册 + 路由视图采集 + 条件启用错误双通道) |
142
+ | `addNuxtError(error: unknown, context?: NuxtErrorContext)` | 手动上报 Nuxt 异常(`component_stack` / `component_name` / `lifecycle_hook` 可选) |
143
+ | `type NuxtPluginOptions` / `NuxtErrorContext` 等 | 公共类型(duck typing,版本无关) |
144
+
145
+ ### `NuxtPluginOptions`
146
+
147
+ `initNuxtPlugin(shell, options)` 的第二参数(源 `src/types/index.ts`):
148
+
149
+ | 字段 | 类型 | 必填 | 说明 |
150
+ | --------- | ------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
151
+ | `router` | `AnyRouter` | 是 | vue-router 实例(v4 / v5 均可,`useRouter()` 取得,仅依赖 `afterEach` / `currentRoute` 最小接口)。驱动 view 生命周期(手动 view 模式:注入 `trackViewsManually` + 调用 `Shell.startView`) |
152
+ | `nuxtApp` | `NuxtAppLike` | 否 | NuxtApp 实例(`useNuxtApp()` 取得)。提供时启用错误双通道自动接管;不提供时仅启用路由视图采集,错误可经 `addNuxtError` 手动上报 |
153
+
154
+ ### `NuxtErrorContext`
155
+
156
+ `addNuxtError` 的第二参数(字段对齐 `@arms/rum-browser-vue` 的 `VueErrorContext`):
157
+
158
+ | 字段 | 类型 | 说明 |
159
+ | ----------------- | -------- | ----------------------------------------------------------------------------------- |
160
+ | `component_stack` | `string` | 组件链栈,如 `'at <OrderList>\nat <UserPage>'`(当前组件在前) |
161
+ | `component_name` | `string` | 出错组件名 |
162
+ | `lifecycle_hook` | `string` | 生命周期钩子 / 错误来源信息(Vue errorHandler 的 `info` 参数,如 `'hook:mounted'`) |
163
+
164
+ ## 版本要求
165
+
166
+ | 依赖 | 版本要求 | 说明 |
167
+ | ------------------- | -------------------- | ------------------------------------------------------------------------------------------------ |
168
+ | `@arms/rum-core` | `>=0.1.11` | 含 `Shell.startView` 与 `IConfiguration.trackViewsManually`(已发布) |
169
+ | `@arms/rum-browser` | `>=0.1.16` | 含 PvCollector 手动模式防护(已发布) |
170
+ | `nuxt` | `3(>=3.7)\|\| 4` | optional peer;3.7 起 `defineNuxtPlugin` 对象语法与 `enforce` 稳定 |
171
+ | `vue` | `^3.5.0` | optional peer;本包经 `nuxtApp.vueApp.config.errorHandler` duck typing 访问,不直接依赖 vue 模块 |
172
+ | `vue-router` | `^4.0.0 \|\| ^5.0.0` | optional peer;仅依赖 `afterEach` / `currentRoute` 最小接口 |
173
+
174
+ > **版本口径说明**:peerDependencies 中 `nuxt` 声明为 `3 || 4`(对齐 Datadog 实践),实际支持基线为 Nuxt ≥3.7 —— 更早版本的 `defineNuxtPlugin` 对象语法与 `enforce` 不稳定。`vue` peer 为 `^3.5.0`,而 Nuxt 3.7-3.12 生态常配 vue 3.3 / 3.4 —— 三项框架 peer 均 optional(`peerDependenciesMeta` 标记),版本不满足仅产生安装告警、不阻塞安装(插件对框架实例 duck typing,实际兼容宽于声明口径)。
175
+
176
+ ### Nuxt 2 用户
177
+
178
+ Nuxt 2 / Vue 2 已 EOL(插件体系完全不同),本包不支持。请使用通用包 `@arms/rum-browser`(自动 PV + 全局异常采集,无 Nuxt 专属能力),或升级到 Nuxt 3 后接入本包。
179
+
180
+ ## 已知限制
181
+
182
+ > 以下限制是当前 MVP 设计所固有的,后续版本计划改进。
183
+
184
+ ### 单实例约束
185
+
186
+ 插件为 collector 级单例(模块级状态持有 shell / ctx 引用)。重复调用 `initNuxtPlugin` 传入**另一个** rum 实例时会打印 `logger.warn` 并跳过重复注册、仅刷新 shell 引用 —— 最后一次绑定的实例生效。微前端多子应用各自持有独立 RUM 实例的场景暂不支持。
187
+
188
+ ### `source='vue'` 错误不受 `collectors.jsError` 采样管控
189
+
190
+ 本包上报的错误携带 `source: 'vue'`,browser SDK 的 `SessionProcessor` 采样键映射当前不覆盖 `'vue'`(与 browser-vue 包同款已知约束)——这些错误会绕过 `collectors.jsError` 采样配置。如需禁用,请使用 `collectors.exception`(设置为 `false`)。另外,core reporter 对 exception 按 `getErrorID(message + stack)` 聚合且 key 不含 source —— 同一错误被全局通道与插件双通道捕获时会聚合合并;自动通道错误可经 `filters.exception` 过滤以保留富上下文事件。
191
+
192
+ ### errorHandler 覆盖边界
193
+
194
+ - **组件 `onErrorCaptured` 返回 `false`**:按 Vue 语义该错误被视为"已处理"并阻止传播,全局 `errorHandler` 不会触发 —— 插件捕获不到(Vue 框架行为);需观测时请在 `onErrorCaptured` 中手动调用 `addNuxtError`;
195
+ - **`warnHandler` 不覆盖**:Vue 运行时警告不进入 RUM 异常事件;
196
+ - **`unhandledrejection`**:由主包 `@arms/rum-browser` 的 jsError 采集器覆盖,无需本插件处理;
197
+ - **hydration mismatch 明细**:Vue errorHandler 不捕获(vuejs/core#13154),不做监控承诺。
198
+
199
+ ### 错误页为全新页面加载
200
+
201
+ Nuxt 渲染 `error.vue` 属全新页面加载,插件与会话上下文随之重置 —— 错误后首屏 view 重新计为 `initial_load`。
202
+
203
+ ### 无 `pages/` 目录的应用
204
+
205
+ 仅单 `app.vue` 的 Nuxt 应用没有路由实例,插件因缺少有效 `router` 整体禁用(`logger.error` 自监控上报),仅 `addNuxtError` 手动错误上报可用。
206
+
207
+ ### 错误去重窗口语义
208
+
209
+ 双通道去重按 app 生命周期(WeakSet 按错误对象引用)—— 同一 `Error` 实例二次抛出会被去重丢弃。
210
+
211
+ ### 初始化时序
212
+
213
+ `initNuxtPlugin` 可在 `armsRum.init()` 前后任意时序同步调用。初始化完成之前调用 `addNuxtError` 时安全降级:插件未接入则 warn + skip(不抛错);插件已接入但 init 未完成则排队,init 完成后自动补发。init 前触发的路由导航经 pending 队列兜底,collector setup 后自动补发首个 view(`loading_type: 'initial_load'`),首屏不丢失。
214
+
215
+ ## 兼容性
216
+
217
+ | 环境 | 支持情况 | 说明 |
218
+ | --------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
219
+ | Vite / webpack 5 等 | ✅ | 经 `exports` 字段解析单入口(types / import / require / default 四条件) |
220
+ | SSR(Nuxt 服务端) | ✅ 安全 | 源码零 nuxt / vue / vue-router 导入、零顶层 `window` / `document` 访问(唯一 DOM 触点 `getCurrentUrl` 在函数内 try-catch 防护);配合 `.client.ts` 后缀约定使用 |
221
+ | CDN / `<script>` 标签 | ❌ 不适用 | 本包不出 UMD dist 产物,仅经 bundler 消费 —— CDN 场景请直接使用主包 `@arms/rum-browser` |
222
+
223
+ > **产物压缩说明**:`es` / `lib` 产物已经过 terser 压缩与混淆,且**不附带 sourcemap**(与 browser-vue / browser-nextjs 包一致的体积/混淆 trade-off)。排查线上堆栈时请对照仓库源码与 [CHANGELOG](./CHANGELOG.md) 版本定位对应发布版本。
@@ -0,0 +1,73 @@
1
+ import type { SendEventFn } from '@arms/rum-core';
2
+ /**
3
+ * addNuxtError 的附加上下文(字段对齐 browser-vue 的 VueErrorContext)。
4
+ *
5
+ * component_stack / component_name / lifecycle_hook 通常由插件内部的
6
+ * 双通道接管自动构建(组件链栈 / 出错组件名 / errorHandler 的 info
7
+ * 参数),手动上报时可按需传入。
8
+ */
9
+ export interface NuxtErrorContext {
10
+ /** 组件链栈,格式如 'at <ComponentA>\nat <ComponentB>'(当前组件在前) */
11
+ component_stack?: string;
12
+ /** 出错组件名 */
13
+ component_name?: string;
14
+ /** 生命周期钩子 / 错误来源信息(Vue errorHandler 的 info 参数,如 'hook:mounted') */
15
+ lifecycle_hook?: string;
16
+ }
17
+ /**
18
+ * 手动上报 Nuxt 异常。
19
+ *
20
+ * 供两类场景调用:
21
+ * 1. 插件内部的双通道接管链(vueApp.config.errorHandler 包装 +
22
+ * nuxtApp.hook('app:error'),经 setupNuxtErrorHandling 自动构建上下文)
23
+ * 2. 用户在 onErrorCaptured / 已有自定义 errorHandler 中手动转发
24
+ *
25
+ * 事件形态(source='vue' + snapshots 收敛模式,对齐 browser-vue
26
+ * addVueError 与 browser-nextjs addNextjsError 的家族口径):
27
+ * - event_type: 'exception'、type: 'error'、source: 'vue'
28
+ * - name / message / stack:error normalize 后的 Error 字段
29
+ * - snapshots:JSON 字符串,内部携带 handling: 'handled'、
30
+ * framework: 'nuxt'(本包标识)与可得时的 component_stack /
31
+ * component_name / lifecycle_hook(snake_case 键)
32
+ * - times: 1
33
+ *
34
+ * 时序行为:
35
+ * - 插件未接入(initNuxtPlugin 未调用):logger.warn + skip(对齐家族)
36
+ * - 插件已接入但 init 未完成(sendEvent 未就绪):排队待
37
+ * collector.setup 时 flush 补发(对齐 Datadog onRumStart 订阅机制)
38
+ *
39
+ * 事件基础字段(session / view / user 等)由 core reporter 事件处理链路补齐。
40
+ *
41
+ * @param error Nuxt 异常(errorHandler 第一参数 / app:error hook 参数,
42
+ * 实际可为任意值,内部统一 normalize 为 Error)
43
+ * @param context 附加上下文(component_stack / component_name / lifecycle_hook)
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { addNuxtError } from '@arms/rum-browser-nuxt';
48
+ *
49
+ * // onErrorCaptured 场景手动转发
50
+ * onErrorCaptured(function (err, instance, info) {
51
+ * addNuxtError(err, { lifecycle_hook: info });
52
+ * return false;
53
+ * });
54
+ * ```
55
+ */
56
+ export declare function addNuxtError(error: unknown, context?: NuxtErrorContext): void;
57
+ /**
58
+ * 核心上报函数(normalize + snapshots 收敛 + 事件构造 + sendEvent 直发)。
59
+ *
60
+ * 由 addNuxtError(手动 / flush 补发)与 setupNuxtErrorHandling 的
61
+ * 自动通道(经 addNuxtError 间接调用)共用。
62
+ *
63
+ * 说明:不走 Shell.sendException —— 后者强制 type='custom',本包
64
+ * 事件口径为 type='error' + source='vue'(见事件构造注释)。
65
+ */
66
+ export declare function reportNuxtErrorEvent(error: unknown, context: NuxtErrorContext | undefined, sendEvent: SendEventFn): void;
67
+ /**
68
+ * flush 排队错误(nuxtCollector.setup 在 sendEvent 就绪后调用)。
69
+ *
70
+ * 经 addNuxtError 重入(此时 sendEvent 必然已就绪,直接上报),
71
+ * 逐条独立防护(单条失败不影响其余)。
72
+ */
73
+ export declare function flushPendingNuxtErrors(): void;
@@ -0,0 +1 @@
1
+ import{RumEventType as r,logger as e}from"@arms/rum-core";import{getSendEvent as t,getShell as n,pushPendingError as o,takePendingErrors as a}from"../state";export function addNuxtError(r,a){try{var c=t();if(!c)return n()?void o(r,a):void e.warn("nuxt-plugin","addNuxtError called before plugin initialization, skipping");reportNuxtErrorEvent(r,a,c)}catch(r){e.error("nuxt-plugin","addNuxtError failed",r)}}export function reportNuxtErrorEvent(e,t,n){var o,a;if(e instanceof Error)o=e;else if(e&&"object"==typeof e){var c,i=e;if("string"==typeof i.message&&i.message)c=i.message;else try{c=String(e)}catch(r){c="Unknown error"}o=new Error(c),"string"==typeof i.name&&i.name&&(o.name=i.name),"string"==typeof i.stack&&i.stack&&(o.stack=i.stack)}else try{o=new Error(String(e))}catch(r){o=new Error("Unknown error")}var s={handling:"handled",framework:"nuxt"};t&&(t.component_stack&&(s.component_stack=t.component_stack),t.component_name&&(s.component_name=t.component_name),t.lifecycle_hook&&(s.lifecycle_hook=t.lifecycle_hook));try{a=JSON.stringify(s)}catch(r){a=void 0}n({event_type:r.EXCEPTION,type:"error",source:"vue",name:o.name||"Error",message:o.message,stack:o.stack,snapshots:a,times:1})}export function flushPendingNuxtErrors(){for(var r=a(),t=0;t<r.length;t++)try{addNuxtError(r[t].error,r[t].context)}catch(r){e.error("nuxt-plugin","flushPendingNuxtErrors failed",r)}}
@@ -0,0 +1,16 @@
1
+ import type { NuxtAppLike } from '../../types';
2
+ /**
3
+ * 挂载错误双通道自动接管(initNuxtPlugin 在 options.nuxtApp 存在时调用)。
4
+ *
5
+ * @param nuxtApp NuxtApp 实例(duck typing:vueApp + hook('app:error'))
6
+ */
7
+ export declare function setupNuxtErrorHandling(nuxtApp: NuxtAppLike): void;
8
+ /**
9
+ * 重置错误通道接管标记(nuxtCollector.destroy / 测试复位时调用)。
10
+ *
11
+ * 仅重置 WeakSet 标记(允许 destroy 后重新接管),不还原
12
+ * vueApp.config.errorHandler —— 包装函数持有原 handler 引用,HMR /
13
+ * 外部改写场景下盲目还原会覆盖他人的 handler(browser-vue 家族同样
14
+ * 不做 errorHandler 还原)。
15
+ */
16
+ export declare function destroyErrorHandling(): void;
@@ -0,0 +1 @@
1
+ import{logger as r}from"@arms/rum-core";import{addNuxtError as n}from"./addNuxtError";var e=new WeakSet;export function setupNuxtErrorHandling(n){if(n&&n.vueApp&&n.vueApp.config){if(!e.has(n)){e.add(n);var t,a,i=(t=o,a=new WeakSet,function(r,n,e){if(null!==r&&"object"==typeof r){if(a.has(r))return;a.add(r)}t(r,n,e)}),u=n.vueApp.config,p=u.errorHandler;u.errorHandler=function(r,n,e){i(r,n,e),"function"==typeof p?p(r,n,e):console.error(r)};try{n.hook("app:error",function(r){i(r,null,"")})}catch(n){r.error("nuxt-plugin","app:error hook registration failed",n)}}}else r.error("nuxt-plugin","invalid nuxtApp instance, error handling disabled")}function o(r,e,o){n(r,function(r,n){var e={};try{if(n&&(e.lifecycle_hook=n),!r)return e;var o=r.$?r.$:r,a=t(o);a&&(e.component_name=a);for(var i=[],u=[],p=o,f=0;p&&f<30&&-1===u.indexOf(p);){u.push(p);var c=t(p);c&&i.push("at <"+c+">"),p=p.parent||null,f++}i.length>0&&(e.component_stack=i.join("\n"))}catch(r){}return e}(e,o))}function t(r){if(r){var n=r.type;if(n)return n.name||n.__name||void 0}}export function destroyErrorHandling(){e=new WeakSet}
@@ -0,0 +1,48 @@
1
+ import type { Shell } from '@arms/rum-core';
2
+ import type { NuxtPluginOptions } from '../types';
3
+ /**
4
+ * 初始化 Nuxt 插件。
5
+ *
6
+ * 在 armsRum.init() 完成(或 new ArmsRum(config) 构造即 auto-init)
7
+ * 之后调用最佳;反向时序(先 init 插件后完成 init)经 pending 队列
8
+ * 兜底同样安全。
9
+ *
10
+ * 内部通过 shell.useCollectors(nuxtCollector) 注册轻量 ICollector
11
+ * (name='nuxt'),在 collector.setup 中缓存引用、注入
12
+ * trackViewsManually(时序安全)并 flush 延迟补发;同时编排两个域:
13
+ * - 路由域:setupRouterTracking(router) —— afterEach 驱动 startView
14
+ * (含初始视图补偿、failure 守卫、query-only 跳过、WeakSet 幂等)
15
+ * - 错误域:nuxtApp 存在时 setupNuxtErrorHandling(nuxtApp) ——
16
+ * 双通道自动接管(errorHandler 链式包装 + app:error hook + 去重)
17
+ *
18
+ * 幂等性:HMR 或重复调用时,若 nuxt collector 已注册,仅刷新 shell
19
+ * 引用,不重复注册(路由域 / 错误域各有独立的 WeakSet 幂等守卫)。
20
+ *
21
+ * @param shell ArmsRum 实例(或任意 Shell 子类实例)
22
+ * @param options 插件选项(router 必填驱动视图采集;nuxtApp 可选启用
23
+ * 错误双通道)
24
+ *
25
+ * @example plugins/arms-rum.client.ts(用户手写)
26
+ * ```ts
27
+ * export default defineNuxtPlugin({
28
+ * name: 'arms-rum',
29
+ * enforce: 'pre',
30
+ * setup() {
31
+ * armsRum.init({ endpoint: 'https://...' });
32
+ * initNuxtPlugin(armsRum, {
33
+ * router: useRouter(), // 必填:驱动视图采集
34
+ * nuxtApp: useNuxtApp(), // 推荐:启用错误双通道
35
+ * });
36
+ * },
37
+ * });
38
+ * ```
39
+ */
40
+ export declare function initNuxtPlugin(shell: Shell, options: NuxtPluginOptions): void;
41
+ /**
42
+ * 复位模块级插件状态(测试 / HMR 场景使用,不构成公共 API)。
43
+ *
44
+ * 聚合调用各域的清理函数并重置 pluginShell,等效于 collector.destroy
45
+ * 的清理路径(供测试在模块状态隔离下复位,对齐 browser-nextjs
46
+ * navigation.ts 的 clearNavigationState 先例)。
47
+ */
48
+ export declare function resetNuxtPluginState(): void;
@@ -0,0 +1 @@
1
+ import{logger as t}from"@arms/rum-core";import{setShell as r,setState as e,clearState as n}from"./state";import{flushPendingStartView as i,setupRouterTracking as u,destroyRouterTracking as o}from"./router/nuxtRouter";import{setupNuxtErrorHandling as l,destroyErrorHandling as a}from"./error/setupNuxtErrorHandling";import{flushPendingNuxtErrors as s}from"./error/addNuxtError";var c=null,p={name:"nuxt",setup:function(r,n){e(r,n,c);try{var u=r.getConfig(),o=Object.assign({},u,{trackViewsManually:!0});r.setConfig(o)}catch(r){t.error("nuxt-plugin","trackViewsManually injection failed",r)}try{i()}catch(r){t.error("nuxt-plugin","flush pending start view failed",r)}try{s()}catch(r){t.error("nuxt-plugin","flush pending nuxt errors failed",r)}},destroy:function(){n(),o(),a(),c=null}};export function initNuxtPlugin(n,i){if(n)if(i&&i.router&&"function"==typeof i.router.afterEach){var o=c;c=n,o&&o!==n&&t.warn("nuxt-plugin","nuxt plugin is bound to another rum instance, only a single SDK instance is supported, refreshing shell reference"),r(n);var a=!1;try{a=n.getCollectors().some(function(t){return"nuxt"===t.name})}catch(t){}var s=n;if(s.client&&s.client.getContext){var g=s.client.getContext();g&&e(g,s.client.sendEvent,n)}u(i.router),i.nuxtApp&&l(i.nuxtApp),a?t.info("nuxt-plugin","nuxt collector already registered, skip re-register"):n.useCollectors(p)}else t.error("nuxt-plugin","initNuxtPlugin called without a valid router, plugin disabled");else t.error("nuxt-plugin","initNuxtPlugin called without shell, plugin disabled")}export function resetNuxtPluginState(){p.destroy()}
@@ -0,0 +1,23 @@
1
+ import type { AnyRouteRecord } from '../../types';
2
+ /**
3
+ * 根据 vue-router 的 matched 数组计算 Nuxt 文件路由风格的 view 名称。
4
+ *
5
+ * 两步:
6
+ * 1. matched **尾向头**取第一个非空 path(对齐 Datadog:嵌套路由的
7
+ * matched 链即文件路径层级,最深非空段即页面本身的模板;区别于
8
+ * browser-vue computeViewName 的「逐段拼接 + catch-all 展开」策略
9
+ * —— 本包以 Nuxt 文件心智为准,模板原样归一化)
10
+ * 2. 参数语法反向归一化为 Nuxt 文件路由风格
11
+ *
12
+ * @param matched vue-router to.matched 匹配记录链(父 → 子顺序)
13
+ * @returns Nuxt 文件路由风格的名称,如 `/user/[id]`;matched 为空或
14
+ * 全部段 path 为空时返回 ''(调用方以此跳过 startView)
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * computeNuxtViewName([{ path: '/user/:id()' }]); // => '/user/[id]'
19
+ * computeNuxtViewName([{ path: '/:slug(.*)*' }]); // => '/[...slug]'
20
+ * computeNuxtViewName([]); // => ''
21
+ * ```
22
+ */
23
+ export declare function computeNuxtViewName(matched?: AnyRouteRecord[]): string;
@@ -0,0 +1 @@
1
+ var r="(.*)*",t="([^/]*)*",e=/\\(.)|:([\w.]+)(\(\.\*\)\*|\(\[\^\/\]\*\)\*|\?|\(\))?/g;export function computeNuxtViewName(n){var u=function(r){if(!r||0===r.length)return"";for(var t=r.length-1;t>=0;t--){var e=r[t].path;if(e)return e}return""}(n);return u?u.replace(e,function(e,n,u,a){return void 0!==n?n:u?a===r||a===t?"[..."+u+"]":"?"===a?"[["+u+"]]":"["+u+"]":""}):u}
@@ -0,0 +1,27 @@
1
+ import type { AnyRouter } from '../../types';
2
+ /**
3
+ * 挂载 vue-router 全局后置钩子并做初始视图补偿(v4 / v5 通用)。
4
+ *
5
+ * @param router vue-router 实例(Nuxt 经 useRouter() 传入)
6
+ */
7
+ export declare function setupRouterTracking(router: AnyRouter): void;
8
+ /**
9
+ * 延迟补发 pending 的初始导航(init 前触发 afterEach / 初始补偿时
10
+ * shell 尚未就绪)。由 nuxtCollector.setup 在注入 trackViewsManually
11
+ * 后调用。
12
+ *
13
+ * pending 场景下 ctx 必然尚无 view(init 未完成),强制 initial_load。
14
+ */
15
+ export declare function flushPendingStartView(): void;
16
+ /**
17
+ * 清理 pending 队列(destroy 时调用)。
18
+ */
19
+ export declare function clearPendingStartView(): void;
20
+ /**
21
+ * 卸载路由追踪(nuxtCollector.destroy 时调用)。
22
+ *
23
+ * - 逐一调用 afterEach 返回的卸载函数,移除全部全局钩子
24
+ * - 清空 pending 队列、重置挂载标记(允许 destroy 后重新接入 router)
25
+ * - 置 destroyed 标记:此后 shell 未就绪的导航不再写入 pending
26
+ */
27
+ export declare function destroyRouterTracking(): void;
@@ -0,0 +1 @@
1
+ import{logger as t}from"@arms/rum-core";import{computeNuxtViewName as e}from"./computeNuxtViewName";import{getCtx as r,getShell as n}from"../state";var a=null,i=[],o=new WeakSet,u=!1;export function setupRouterTracking(e){if(e&&"function"==typeof e.afterEach){if(!o.has(e)){o.add(e),u=!1,function(t){try{var e=t.currentRoute,r=e&&e.value?e.value:e;r&&r.matched&&r.matched.length>0&&c(r)}catch(t){}}(e);var r=e.afterEach(function(t,e,r){r||e&&e.matched&&e.matched.length>0&&t&&t.path===e.path&&t.hash===e.hash||c(t)});"function"==typeof r&&i.push(r)}}else t.error("nuxt-plugin","invalid router instance, router view tracking disabled")}function c(t){var i=e(t.matched);if(i){var o=n(),c=r();if(o&&c&&"function"==typeof o.startView)o.startView(i,{url:l(),loading_type:f()});else{if(u)return;a={name:i,url:l()}}}}export function flushPendingStartView(){if(a){var t=a;a=null;var e=n();e&&"function"==typeof e.startView&&e.startView(t.name,{url:t.url,loading_type:"initial_load"})}}export function clearPendingStartView(){a=null}export function destroyRouterTracking(){for(var t=0;t<i.length;t++)try{i[t]()}catch(t){}i=[],o=new WeakSet,clearPendingStartView(),u=!0}function f(){try{var t=r();if(t&&"function"==typeof t.getViews){var e=t.getViews();if(e&&e.length>0)return"route_change"}}catch(t){}return"initial_load"}function l(){try{return window.location.href}catch(t){return""}}
@@ -0,0 +1,50 @@
1
+ import type { IContext, SendEventFn, Shell } from '@arms/rum-core';
2
+ import type { NuxtErrorContext } from './error/addNuxtError';
3
+ /**
4
+ * 模块级共享状态(单实例约束,与 browser-vue state.ts /
5
+ * browser-nextjs navigation.ts 的家族形态一致)。
6
+ *
7
+ * - cachedShell:initNuxtPlugin 调用时缓存(早于 collector.setup),
8
+ * 供路由域与错误域在 init 完成前判断「插件已接入」
9
+ * - cachedCtx / cachedSendEvent:nuxtCollector.setup 时缓存,
10
+ * 供 addNuxtError 与路由 startView 使用
11
+ * - pendingErrors:init 完成前(sendEvent 未就绪)的 addNuxtError 排队
12
+ * (对齐 Datadog nuxtPlugin.ts 的 onRumStart 订阅延迟机制,按 ARMS
13
+ * 家族形态实现为「shell 已缓存但 collector 未 setup」短窗口内的排队,
14
+ * setup 时统一 flush),仅保留最近 MAX_PENDING_ERRORS 条
15
+ */
16
+ interface PendingNuxtError {
17
+ error: unknown;
18
+ context?: NuxtErrorContext;
19
+ }
20
+ /**
21
+ * 缓存 shell 引用(initNuxtPlugin 调用时)。
22
+ *
23
+ * 调用即缓存(早于 collector.setup),覆盖 init 前后两种调用时序;
24
+ * 供 addNuxtError 判断插件是否已接入(未接入时 warn + skip)。
25
+ */
26
+ export declare function setShell(shell: Shell): void;
27
+ /**
28
+ * 由 nuxt-collector 的 setup 调用,缓存 ctx / sendEvent / shell 引用。
29
+ *
30
+ * 供 addNuxtError(sendEvent 直发)与路由域(getCtx / getShell)使用。
31
+ */
32
+ export declare function setState(ctx: IContext, sendEvent: SendEventFn, shell: Shell): void;
33
+ /**
34
+ * 清理模块级状态(destroy / 测试复位时调用)。
35
+ */
36
+ export declare function clearState(): void;
37
+ export declare function getCtx(): IContext | null;
38
+ export declare function getSendEvent(): SendEventFn | null;
39
+ export declare function getShell(): Shell | null;
40
+ /**
41
+ * 追加一条待 flush 的错误(addNuxtError 在「shell 已缓存但 sendEvent
42
+ * 未就绪」窗口内调用时)。超限时丢弃最旧的一条(仅保留最近
43
+ * MAX_PENDING_ERRORS 条,防异常时序下的无限增长)。
44
+ */
45
+ export declare function pushPendingError(error: unknown, context?: NuxtErrorContext): void;
46
+ /**
47
+ * 取出并清空排队错误(collector.setup 后的 flush 使用)。
48
+ */
49
+ export declare function takePendingErrors(): PendingNuxtError[];
50
+ export {};
@@ -0,0 +1 @@
1
+ var t=null,n=null,e=null,r=[];export function setShell(n){t=n}export function setState(r,o,u){n=r,e=o,t=u}export function clearState(){n=null,e=null,t=null,r=[]}export function getCtx(){return n}export function getSendEvent(){return e}export function getShell(){return t}export function pushPendingError(t,n){r.push({error:t,context:n}),r.length>20&&r.shift()}export function takePendingErrors(){var t=r;return r=[],t}
package/es/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @arms/rum-browser-nuxt 主入口(零 nuxt / vue / vue-router 导入,SSR-safe)。
3
+ *
4
+ * 导出 2 个运行时 API + 公共类型:
5
+ * - initNuxtPlugin:初始化插件(collector 注册 + 路由视图采集 + 错误双通道)
6
+ * - addNuxtError:Nuxt 异常手动上报(自动通道内部同链路复用)
7
+ *
8
+ * 单入口设计(无子入口):本包为纯客户端薄适配层,全部能力在
9
+ * plugins/arms-rum.client.ts 的 defineNuxtPlugin setup 内同步调用,
10
+ * 无 'use client' 边界 / 双版本入口等拆分诉求(区别于 browser-vue
11
+ * 的 vue2/vue3 子入口与 browser-nextjs 的 app/pages 子入口)。
12
+ *
13
+ * 刻意不导出:
14
+ * - setupNuxtErrorHandling / setupRouterTracking 等域内函数:仅由
15
+ * initNuxtPlugin 编排调用(最小 API 暴露原则)
16
+ * - computeNuxtViewName:内部归一化算法,非用户 API(单测经相对路径
17
+ * 覆盖;确有模板反推诉求的用户可参照 README 视图命名规则表自行实现)
18
+ */
19
+ export { initNuxtPlugin } from './domain/nuxtPlugin';
20
+ export { addNuxtError } from './domain/error/addNuxtError';
21
+ export type { NuxtErrorContext } from './domain/error/addNuxtError';
22
+ export type { NuxtPluginOptions, NuxtAppLike, NuxtVueAppLike, NuxtVueErrorHandler, AnyRouter, AnyRouteRecord, AnyRouteLocation, } from './types';
package/es/index.js ADDED
@@ -0,0 +1 @@
1
+ export{initNuxtPlugin}from"./domain/nuxtPlugin";export{addNuxtError}from"./domain/error/addNuxtError";
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Nuxt / vue-router 版本无关的结构化类型定义(duck typing)。
3
+ *
4
+ * 不导入 nuxt / vue / vue-router 官方类型(零框架依赖,构建期 / 类型期
5
+ * 天然兼容 Nuxt 3 / Nuxt 4),仅包含插件实现实际用到的最小属性集。
6
+ *
7
+ * 类型应:
8
+ * - 与 Nuxt 3(>=3.7)/ Nuxt 4、Vue 3.5+、vue-router v4 / v5 结构兼容
9
+ * - 仅包含插件需要的最小属性集
10
+ */
11
+ /**
12
+ * Vue errorHandler 函数签名(Vue 3)。
13
+ *
14
+ * Vue 3:`(err: unknown, instance: ComponentPublicInstance | null, info: string)`。
15
+ * 此处取参数的最宽联合形态(unknown / any / string),保证任一版本的
16
+ * 原生 errorHandler 都能赋值给该类型。
17
+ */
18
+ export type NuxtVueErrorHandler = (err: unknown, instance: any, info: string) => void;
19
+ /**
20
+ * Nuxt vueApp 的最小结构(nuxtApp.vueApp)。
21
+ *
22
+ * 仅依赖 config.errorHandler(错误通道 1 的链式包装点)。
23
+ */
24
+ export interface NuxtVueAppLike {
25
+ /** 应用配置(errorHandler 接管点) */
26
+ config?: {
27
+ errorHandler?: NuxtVueErrorHandler;
28
+ [key: string]: any;
29
+ };
30
+ [key: string]: any;
31
+ }
32
+ /**
33
+ * NuxtApp 的最小结构(duck typing,对齐 Datadog browser-rum-nuxt
34
+ * setupNuxtErrorHandling.ts 的 NuxtApp 接口)。
35
+ *
36
+ * 仅声明错误双通道实际用到的两个成员:
37
+ * - vueApp:通道 1 链式包装 vueApp.config.errorHandler
38
+ * - hook('app:error'):通道 2 捕获 Nuxt 启动 / SSR 传播错误
39
+ */
40
+ export interface NuxtAppLike {
41
+ /** Vue 应用实例(经 nuxtApp.vueApp 访问) */
42
+ vueApp: NuxtVueAppLike;
43
+ /** Nuxt 运行时 hook(本插件仅注册 'app:error') */
44
+ hook(name: 'app:error', callback: (err: unknown) => void): void;
45
+ [key: string]: any;
46
+ }
47
+ /**
48
+ * vue-router 路由记录的最小结构(v4 / v5 RouteLocationMatched 兼容)。
49
+ */
50
+ export interface AnyRouteRecord {
51
+ /** 路由模板路径(Nuxt 文件路由生成形态),如 '/user/:id()' */
52
+ path?: string;
53
+ [key: string]: any;
54
+ }
55
+ /**
56
+ * vue-router 路由地址的最小结构(v4 / v5 RouteLocationNormalized 兼容)。
57
+ */
58
+ export interface AnyRouteLocation {
59
+ /** 实际路径(含参数实际值,不含 query 与 hash),如 '/user/123' */
60
+ path?: string;
61
+ /** hash 部分(如 '#details'),query-only 跳过守卫的判定字段之一 */
62
+ hash?: string;
63
+ /** 匹配的路由记录链(父 → 子顺序) */
64
+ matched?: AnyRouteRecord[];
65
+ [key: string]: any;
66
+ }
67
+ /**
68
+ * vue-router 实例的最小结构(v4 / v5 兼容)。
69
+ *
70
+ * 仅依赖 afterEach(v4 / v5 均提供)与 currentRoute(初始视图补偿),
71
+ * 不包装 createRouter、不替换任何框架 import。
72
+ */
73
+ export interface AnyRouter {
74
+ /**
75
+ * 注册全局后置钩子。
76
+ * v4 / v5 的回调第三参为 failure(导航失败),签名天然兼容。
77
+ * 返回卸载函数(v3+ 均支持,非函数返回值防御性忽略)。
78
+ */
79
+ afterEach(callback: (to: AnyRouteLocation, from?: AnyRouteLocation, failure?: unknown) => void): unknown;
80
+ /**
81
+ * 当前路由(v4 / v5 为 shallowRef,经 .value 读取;实现中防御兼容
82
+ * 直接对象形态)。初始视图补偿的判定依据(matched 非空即已就绪)。
83
+ */
84
+ currentRoute?: unknown;
85
+ [key: string]: any;
86
+ }
87
+ /**
88
+ * Nuxt 插件初始化选项(initNuxtPlugin 的第二参数)。
89
+ */
90
+ export interface NuxtPluginOptions {
91
+ /**
92
+ * vue-router 实例(v4 / v5 均可),必填。
93
+ *
94
+ * Nuxt 应用经 useRouter() 在插件 setup() 中取好后传入(SDK 内零
95
+ * nuxt 导入)。传入后经 router.afterEach 驱动 view 生命周期(手动
96
+ * view 模式:无条件注入 trackViewsManually + 调用 Shell.startView)。
97
+ */
98
+ router: AnyRouter;
99
+ /**
100
+ * NuxtApp 实例(useNuxtApp() 取得),可选。
101
+ *
102
+ * 提供时启用错误双通道自动接管(链式包装 vueApp.config.errorHandler
103
+ * + nuxtApp.hook('app:error'),WeakSet 按错误对象引用去重);
104
+ * 不提供时仅启用路由视图采集,错误可经 addNuxtError 手动上报。
105
+ */
106
+ nuxtApp?: NuxtAppLike;
107
+ }
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,73 @@
1
+ import type { SendEventFn } from '@arms/rum-core';
2
+ /**
3
+ * addNuxtError 的附加上下文(字段对齐 browser-vue 的 VueErrorContext)。
4
+ *
5
+ * component_stack / component_name / lifecycle_hook 通常由插件内部的
6
+ * 双通道接管自动构建(组件链栈 / 出错组件名 / errorHandler 的 info
7
+ * 参数),手动上报时可按需传入。
8
+ */
9
+ export interface NuxtErrorContext {
10
+ /** 组件链栈,格式如 'at <ComponentA>\nat <ComponentB>'(当前组件在前) */
11
+ component_stack?: string;
12
+ /** 出错组件名 */
13
+ component_name?: string;
14
+ /** 生命周期钩子 / 错误来源信息(Vue errorHandler 的 info 参数,如 'hook:mounted') */
15
+ lifecycle_hook?: string;
16
+ }
17
+ /**
18
+ * 手动上报 Nuxt 异常。
19
+ *
20
+ * 供两类场景调用:
21
+ * 1. 插件内部的双通道接管链(vueApp.config.errorHandler 包装 +
22
+ * nuxtApp.hook('app:error'),经 setupNuxtErrorHandling 自动构建上下文)
23
+ * 2. 用户在 onErrorCaptured / 已有自定义 errorHandler 中手动转发
24
+ *
25
+ * 事件形态(source='vue' + snapshots 收敛模式,对齐 browser-vue
26
+ * addVueError 与 browser-nextjs addNextjsError 的家族口径):
27
+ * - event_type: 'exception'、type: 'error'、source: 'vue'
28
+ * - name / message / stack:error normalize 后的 Error 字段
29
+ * - snapshots:JSON 字符串,内部携带 handling: 'handled'、
30
+ * framework: 'nuxt'(本包标识)与可得时的 component_stack /
31
+ * component_name / lifecycle_hook(snake_case 键)
32
+ * - times: 1
33
+ *
34
+ * 时序行为:
35
+ * - 插件未接入(initNuxtPlugin 未调用):logger.warn + skip(对齐家族)
36
+ * - 插件已接入但 init 未完成(sendEvent 未就绪):排队待
37
+ * collector.setup 时 flush 补发(对齐 Datadog onRumStart 订阅机制)
38
+ *
39
+ * 事件基础字段(session / view / user 等)由 core reporter 事件处理链路补齐。
40
+ *
41
+ * @param error Nuxt 异常(errorHandler 第一参数 / app:error hook 参数,
42
+ * 实际可为任意值,内部统一 normalize 为 Error)
43
+ * @param context 附加上下文(component_stack / component_name / lifecycle_hook)
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { addNuxtError } from '@arms/rum-browser-nuxt';
48
+ *
49
+ * // onErrorCaptured 场景手动转发
50
+ * onErrorCaptured(function (err, instance, info) {
51
+ * addNuxtError(err, { lifecycle_hook: info });
52
+ * return false;
53
+ * });
54
+ * ```
55
+ */
56
+ export declare function addNuxtError(error: unknown, context?: NuxtErrorContext): void;
57
+ /**
58
+ * 核心上报函数(normalize + snapshots 收敛 + 事件构造 + sendEvent 直发)。
59
+ *
60
+ * 由 addNuxtError(手动 / flush 补发)与 setupNuxtErrorHandling 的
61
+ * 自动通道(经 addNuxtError 间接调用)共用。
62
+ *
63
+ * 说明:不走 Shell.sendException —— 后者强制 type='custom',本包
64
+ * 事件口径为 type='error' + source='vue'(见事件构造注释)。
65
+ */
66
+ export declare function reportNuxtErrorEvent(error: unknown, context: NuxtErrorContext | undefined, sendEvent: SendEventFn): void;
67
+ /**
68
+ * flush 排队错误(nuxtCollector.setup 在 sendEvent 就绪后调用)。
69
+ *
70
+ * 经 addNuxtError 重入(此时 sendEvent 必然已就绪,直接上报),
71
+ * 逐条独立防护(单条失败不影响其余)。
72
+ */
73
+ export declare function flushPendingNuxtErrors(): void;
@@ -0,0 +1 @@
1
+ "use strict";exports.__esModule=!0,exports.addNuxtError=addNuxtError,exports.flushPendingNuxtErrors=flushPendingNuxtErrors,exports.reportNuxtErrorEvent=reportNuxtErrorEvent;var _rumCore=require("@arms/rum-core"),_state=require("../state");function addNuxtError(r,e){try{var t=(0,_state.getSendEvent)();if(!t)return(0,_state.getShell)()?void(0,_state.pushPendingError)(r,e):void _rumCore.logger.warn("nuxt-plugin","addNuxtError called before plugin initialization, skipping");reportNuxtErrorEvent(r,e,t)}catch(r){_rumCore.logger.error("nuxt-plugin","addNuxtError failed",r)}}function reportNuxtErrorEvent(r,e,t){var o,n;if(r instanceof Error)o=r;else if(r&&"object"==typeof r){var a,s=r;if("string"==typeof s.message&&s.message)a=s.message;else try{a=String(r)}catch(r){a="Unknown error"}o=new Error(a),"string"==typeof s.name&&s.name&&(o.name=s.name),"string"==typeof s.stack&&s.stack&&(o.stack=s.stack)}else try{o=new Error(String(r))}catch(r){o=new Error("Unknown error")}var i={handling:"handled",framework:"nuxt"};e&&(e.component_stack&&(i.component_stack=e.component_stack),e.component_name&&(i.component_name=e.component_name),e.lifecycle_hook&&(i.lifecycle_hook=e.lifecycle_hook));try{n=JSON.stringify(i)}catch(r){n=void 0}t({event_type:_rumCore.RumEventType.EXCEPTION,type:"error",source:"vue",name:o.name||"Error",message:o.message,stack:o.stack,snapshots:n,times:1})}function flushPendingNuxtErrors(){for(var r=(0,_state.takePendingErrors)(),e=0;e<r.length;e++)try{addNuxtError(r[e].error,r[e].context)}catch(r){_rumCore.logger.error("nuxt-plugin","flushPendingNuxtErrors failed",r)}}
@@ -0,0 +1,16 @@
1
+ import type { NuxtAppLike } from '../../types';
2
+ /**
3
+ * 挂载错误双通道自动接管(initNuxtPlugin 在 options.nuxtApp 存在时调用)。
4
+ *
5
+ * @param nuxtApp NuxtApp 实例(duck typing:vueApp + hook('app:error'))
6
+ */
7
+ export declare function setupNuxtErrorHandling(nuxtApp: NuxtAppLike): void;
8
+ /**
9
+ * 重置错误通道接管标记(nuxtCollector.destroy / 测试复位时调用)。
10
+ *
11
+ * 仅重置 WeakSet 标记(允许 destroy 后重新接管),不还原
12
+ * vueApp.config.errorHandler —— 包装函数持有原 handler 引用,HMR /
13
+ * 外部改写场景下盲目还原会覆盖他人的 handler(browser-vue 家族同样
14
+ * 不做 errorHandler 还原)。
15
+ */
16
+ export declare function destroyErrorHandling(): void;
@@ -0,0 +1 @@
1
+ "use strict";exports.__esModule=!0,exports.destroyErrorHandling=destroyErrorHandling,exports.setupNuxtErrorHandling=setupNuxtErrorHandling;var _rumCore=require("@arms/rum-core"),_addNuxtError=require("./addNuxtError"),handledApps=new WeakSet,MAX_COMPONENT_STACK_DEPTH=30;function setupNuxtErrorHandling(r){if(r&&r.vueApp&&r.vueApp.config){if(!handledApps.has(r)){handledApps.add(r);var e=deduplicateByError(reportCapturedError),n=r.vueApp.config,o=n.errorHandler;n.errorHandler=function(r,n,t){e(r,n,t),"function"==typeof o?o(r,n,t):console.error(r)};try{r.hook("app:error",function(r){e(r,null,"")})}catch(r){_rumCore.logger.error("nuxt-plugin","app:error hook registration failed",r)}}}else _rumCore.logger.error("nuxt-plugin","invalid nuxtApp instance, error handling disabled")}function reportCapturedError(r,e,n){(0,_addNuxtError.addNuxtError)(r,buildNuxtErrorContext(e,n))}function deduplicateByError(r){var e=new WeakSet;return function(n,o,t){if(null!==n&&"object"==typeof n){if(e.has(n))return;e.add(n)}r(n,o,t)}}function buildNuxtErrorContext(r,e){var n={};try{if(e&&(n.lifecycle_hook=e),!r)return n;var o=r.$?r.$:r,t=getComponentName(o);t&&(n.component_name=t);for(var a=[],u=[],d=o,i=0;d&&i<MAX_COMPONENT_STACK_DEPTH&&-1===u.indexOf(d);){u.push(d);var p=getComponentName(d);p&&a.push("at <"+p+">"),d=d.parent||null,i++}a.length>0&&(n.component_stack=a.join("\n"))}catch(r){}return n}function getComponentName(r){if(r){var e=r.type;if(e)return e.name||e.__name||void 0}}function destroyErrorHandling(){handledApps=new WeakSet}
@@ -0,0 +1,48 @@
1
+ import type { Shell } from '@arms/rum-core';
2
+ import type { NuxtPluginOptions } from '../types';
3
+ /**
4
+ * 初始化 Nuxt 插件。
5
+ *
6
+ * 在 armsRum.init() 完成(或 new ArmsRum(config) 构造即 auto-init)
7
+ * 之后调用最佳;反向时序(先 init 插件后完成 init)经 pending 队列
8
+ * 兜底同样安全。
9
+ *
10
+ * 内部通过 shell.useCollectors(nuxtCollector) 注册轻量 ICollector
11
+ * (name='nuxt'),在 collector.setup 中缓存引用、注入
12
+ * trackViewsManually(时序安全)并 flush 延迟补发;同时编排两个域:
13
+ * - 路由域:setupRouterTracking(router) —— afterEach 驱动 startView
14
+ * (含初始视图补偿、failure 守卫、query-only 跳过、WeakSet 幂等)
15
+ * - 错误域:nuxtApp 存在时 setupNuxtErrorHandling(nuxtApp) ——
16
+ * 双通道自动接管(errorHandler 链式包装 + app:error hook + 去重)
17
+ *
18
+ * 幂等性:HMR 或重复调用时,若 nuxt collector 已注册,仅刷新 shell
19
+ * 引用,不重复注册(路由域 / 错误域各有独立的 WeakSet 幂等守卫)。
20
+ *
21
+ * @param shell ArmsRum 实例(或任意 Shell 子类实例)
22
+ * @param options 插件选项(router 必填驱动视图采集;nuxtApp 可选启用
23
+ * 错误双通道)
24
+ *
25
+ * @example plugins/arms-rum.client.ts(用户手写)
26
+ * ```ts
27
+ * export default defineNuxtPlugin({
28
+ * name: 'arms-rum',
29
+ * enforce: 'pre',
30
+ * setup() {
31
+ * armsRum.init({ endpoint: 'https://...' });
32
+ * initNuxtPlugin(armsRum, {
33
+ * router: useRouter(), // 必填:驱动视图采集
34
+ * nuxtApp: useNuxtApp(), // 推荐:启用错误双通道
35
+ * });
36
+ * },
37
+ * });
38
+ * ```
39
+ */
40
+ export declare function initNuxtPlugin(shell: Shell, options: NuxtPluginOptions): void;
41
+ /**
42
+ * 复位模块级插件状态(测试 / HMR 场景使用,不构成公共 API)。
43
+ *
44
+ * 聚合调用各域的清理函数并重置 pluginShell,等效于 collector.destroy
45
+ * 的清理路径(供测试在模块状态隔离下复位,对齐 browser-nextjs
46
+ * navigation.ts 的 clearNavigationState 先例)。
47
+ */
48
+ export declare function resetNuxtPluginState(): void;
@@ -0,0 +1 @@
1
+ "use strict";exports.__esModule=!0,exports.initNuxtPlugin=initNuxtPlugin,exports.resetNuxtPluginState=resetNuxtPluginState;var _rumCore=require("@arms/rum-core"),_state=require("./state"),_nuxtRouter=require("./router/nuxtRouter"),_setupNuxtErrorHandling=require("./error/setupNuxtErrorHandling"),_addNuxtError=require("./error/addNuxtError"),pluginShell=null,nuxtCollector={name:"nuxt",setup:function(e,r){(0,_state.setState)(e,r,pluginShell);try{var t=e.getConfig(),u=Object.assign({},t,{trackViewsManually:!0});e.setConfig(u)}catch(e){_rumCore.logger.error("nuxt-plugin","trackViewsManually injection failed",e)}try{(0,_nuxtRouter.flushPendingStartView)()}catch(e){_rumCore.logger.error("nuxt-plugin","flush pending start view failed",e)}try{(0,_addNuxtError.flushPendingNuxtErrors)()}catch(e){_rumCore.logger.error("nuxt-plugin","flush pending nuxt errors failed",e)}},destroy:function(){(0,_state.clearState)(),(0,_nuxtRouter.destroyRouterTracking)(),(0,_setupNuxtErrorHandling.destroyErrorHandling)(),pluginShell=null}};function initNuxtPlugin(e,r){if(e)if(r&&r.router&&"function"==typeof r.router.afterEach){var t=pluginShell;pluginShell=e,t&&t!==e&&_rumCore.logger.warn("nuxt-plugin","nuxt plugin is bound to another rum instance, only a single SDK instance is supported, refreshing shell reference"),(0,_state.setShell)(e);var u=!1;try{u=e.getCollectors().some(function(e){return"nuxt"===e.name})}catch(e){}var n=e;if(n.client&&n.client.getContext){var l=n.client.getContext();l&&(0,_state.setState)(l,n.client.sendEvent,e)}(0,_nuxtRouter.setupRouterTracking)(r.router),r.nuxtApp&&(0,_setupNuxtErrorHandling.setupNuxtErrorHandling)(r.nuxtApp),u?_rumCore.logger.info("nuxt-plugin","nuxt collector already registered, skip re-register"):e.useCollectors(nuxtCollector)}else _rumCore.logger.error("nuxt-plugin","initNuxtPlugin called without a valid router, plugin disabled");else _rumCore.logger.error("nuxt-plugin","initNuxtPlugin called without shell, plugin disabled")}function resetNuxtPluginState(){nuxtCollector.destroy()}
@@ -0,0 +1,23 @@
1
+ import type { AnyRouteRecord } from '../../types';
2
+ /**
3
+ * 根据 vue-router 的 matched 数组计算 Nuxt 文件路由风格的 view 名称。
4
+ *
5
+ * 两步:
6
+ * 1. matched **尾向头**取第一个非空 path(对齐 Datadog:嵌套路由的
7
+ * matched 链即文件路径层级,最深非空段即页面本身的模板;区别于
8
+ * browser-vue computeViewName 的「逐段拼接 + catch-all 展开」策略
9
+ * —— 本包以 Nuxt 文件心智为准,模板原样归一化)
10
+ * 2. 参数语法反向归一化为 Nuxt 文件路由风格
11
+ *
12
+ * @param matched vue-router to.matched 匹配记录链(父 → 子顺序)
13
+ * @returns Nuxt 文件路由风格的名称,如 `/user/[id]`;matched 为空或
14
+ * 全部段 path 为空时返回 ''(调用方以此跳过 startView)
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * computeNuxtViewName([{ path: '/user/:id()' }]); // => '/user/[id]'
19
+ * computeNuxtViewName([{ path: '/:slug(.*)*' }]); // => '/[...slug]'
20
+ * computeNuxtViewName([]); // => ''
21
+ * ```
22
+ */
23
+ export declare function computeNuxtViewName(matched?: AnyRouteRecord[]): string;
@@ -0,0 +1 @@
1
+ "use strict";exports.__esModule=!0,exports.computeNuxtViewName=computeNuxtViewName;var TERMINAL_CATCH_ALL_PARAM="(.*)*",NESTED_CATCH_ALL_PARAM="([^/]*)*",NORMALIZE_NUXT_PATH_REGEXP=/\\(.)|:([\w.]+)(\(\.\*\)\*|\(\[\^\/\]\*\)\*|\?|\(\))?/g;function computeNuxtViewName(e){var t=computeViewName(e);return t?normalizeNuxtPath(t):t}function computeViewName(e){if(!e||0===e.length)return"";for(var t=e.length-1;t>=0;t--){var r=e[t].path;if(r)return r}return""}function normalizeNuxtPath(e){return e.replace(NORMALIZE_NUXT_PATH_REGEXP,function(e,t,r,u){return void 0!==t?t:r?u===TERMINAL_CATCH_ALL_PARAM||u===NESTED_CATCH_ALL_PARAM?"[..."+r+"]":"?"===u?"[["+r+"]]":"["+r+"]":""})}
@@ -0,0 +1,27 @@
1
+ import type { AnyRouter } from '../../types';
2
+ /**
3
+ * 挂载 vue-router 全局后置钩子并做初始视图补偿(v4 / v5 通用)。
4
+ *
5
+ * @param router vue-router 实例(Nuxt 经 useRouter() 传入)
6
+ */
7
+ export declare function setupRouterTracking(router: AnyRouter): void;
8
+ /**
9
+ * 延迟补发 pending 的初始导航(init 前触发 afterEach / 初始补偿时
10
+ * shell 尚未就绪)。由 nuxtCollector.setup 在注入 trackViewsManually
11
+ * 后调用。
12
+ *
13
+ * pending 场景下 ctx 必然尚无 view(init 未完成),强制 initial_load。
14
+ */
15
+ export declare function flushPendingStartView(): void;
16
+ /**
17
+ * 清理 pending 队列(destroy 时调用)。
18
+ */
19
+ export declare function clearPendingStartView(): void;
20
+ /**
21
+ * 卸载路由追踪(nuxtCollector.destroy 时调用)。
22
+ *
23
+ * - 逐一调用 afterEach 返回的卸载函数,移除全部全局钩子
24
+ * - 清空 pending 队列、重置挂载标记(允许 destroy 后重新接入 router)
25
+ * - 置 destroyed 标记:此后 shell 未就绪的导航不再写入 pending
26
+ */
27
+ export declare function destroyRouterTracking(): void;
@@ -0,0 +1 @@
1
+ "use strict";exports.__esModule=!0,exports.clearPendingStartView=clearPendingStartView,exports.destroyRouterTracking=destroyRouterTracking,exports.flushPendingStartView=flushPendingStartView,exports.setupRouterTracking=setupRouterTracking;var _rumCore=require("@arms/rum-core"),_computeNuxtViewName=require("./computeNuxtViewName"),_state=require("../state"),pendingStartView=null,unregisterCallbacks=[],mountedRouters=new WeakSet,destroyed=!1;function setupRouterTracking(e){if(e&&"function"==typeof e.afterEach){if(!mountedRouters.has(e)){mountedRouters.add(e),destroyed=!1,startInitialView(e);var t=e.afterEach(function(e,t,r){r||t&&t.matched&&t.matched.length>0&&e&&e.path===t.path&&e.hash===t.hash||startNuxtRouterView(e)});"function"==typeof t&&unregisterCallbacks.push(t)}}else _rumCore.logger.error("nuxt-plugin","invalid router instance, router view tracking disabled")}function startInitialView(e){try{var t=e.currentRoute,r=t&&t.value?t.value:t;r&&r.matched&&r.matched.length>0&&startNuxtRouterView(r)}catch(e){}}function startNuxtRouterView(e){var t=(0,_computeNuxtViewName.computeNuxtViewName)(e.matched);if(t){var r=(0,_state.getShell)(),n=(0,_state.getCtx)();if(r&&n&&"function"==typeof r.startView)r.startView(t,{url:getCurrentUrl(),loading_type:computeLoadingType()});else{if(destroyed)return;pendingStartView={name:t,url:getCurrentUrl()}}}}function flushPendingStartView(){if(pendingStartView){var e=pendingStartView;pendingStartView=null;var t=(0,_state.getShell)();t&&"function"==typeof t.startView&&t.startView(e.name,{url:e.url,loading_type:"initial_load"})}}function clearPendingStartView(){pendingStartView=null}function destroyRouterTracking(){for(var e=0;e<unregisterCallbacks.length;e++)try{unregisterCallbacks[e]()}catch(e){}unregisterCallbacks=[],mountedRouters=new WeakSet,clearPendingStartView(),destroyed=!0}function computeLoadingType(){try{var e=(0,_state.getCtx)();if(e&&"function"==typeof e.getViews){var t=e.getViews();if(t&&t.length>0)return"route_change"}}catch(e){}return"initial_load"}function getCurrentUrl(){try{return window.location.href}catch(e){return""}}
@@ -0,0 +1,50 @@
1
+ import type { IContext, SendEventFn, Shell } from '@arms/rum-core';
2
+ import type { NuxtErrorContext } from './error/addNuxtError';
3
+ /**
4
+ * 模块级共享状态(单实例约束,与 browser-vue state.ts /
5
+ * browser-nextjs navigation.ts 的家族形态一致)。
6
+ *
7
+ * - cachedShell:initNuxtPlugin 调用时缓存(早于 collector.setup),
8
+ * 供路由域与错误域在 init 完成前判断「插件已接入」
9
+ * - cachedCtx / cachedSendEvent:nuxtCollector.setup 时缓存,
10
+ * 供 addNuxtError 与路由 startView 使用
11
+ * - pendingErrors:init 完成前(sendEvent 未就绪)的 addNuxtError 排队
12
+ * (对齐 Datadog nuxtPlugin.ts 的 onRumStart 订阅延迟机制,按 ARMS
13
+ * 家族形态实现为「shell 已缓存但 collector 未 setup」短窗口内的排队,
14
+ * setup 时统一 flush),仅保留最近 MAX_PENDING_ERRORS 条
15
+ */
16
+ interface PendingNuxtError {
17
+ error: unknown;
18
+ context?: NuxtErrorContext;
19
+ }
20
+ /**
21
+ * 缓存 shell 引用(initNuxtPlugin 调用时)。
22
+ *
23
+ * 调用即缓存(早于 collector.setup),覆盖 init 前后两种调用时序;
24
+ * 供 addNuxtError 判断插件是否已接入(未接入时 warn + skip)。
25
+ */
26
+ export declare function setShell(shell: Shell): void;
27
+ /**
28
+ * 由 nuxt-collector 的 setup 调用,缓存 ctx / sendEvent / shell 引用。
29
+ *
30
+ * 供 addNuxtError(sendEvent 直发)与路由域(getCtx / getShell)使用。
31
+ */
32
+ export declare function setState(ctx: IContext, sendEvent: SendEventFn, shell: Shell): void;
33
+ /**
34
+ * 清理模块级状态(destroy / 测试复位时调用)。
35
+ */
36
+ export declare function clearState(): void;
37
+ export declare function getCtx(): IContext | null;
38
+ export declare function getSendEvent(): SendEventFn | null;
39
+ export declare function getShell(): Shell | null;
40
+ /**
41
+ * 追加一条待 flush 的错误(addNuxtError 在「shell 已缓存但 sendEvent
42
+ * 未就绪」窗口内调用时)。超限时丢弃最旧的一条(仅保留最近
43
+ * MAX_PENDING_ERRORS 条,防异常时序下的无限增长)。
44
+ */
45
+ export declare function pushPendingError(error: unknown, context?: NuxtErrorContext): void;
46
+ /**
47
+ * 取出并清空排队错误(collector.setup 后的 flush 使用)。
48
+ */
49
+ export declare function takePendingErrors(): PendingNuxtError[];
50
+ export {};
@@ -0,0 +1 @@
1
+ "use strict";exports.__esModule=!0,exports.clearState=clearState,exports.getCtx=getCtx,exports.getSendEvent=getSendEvent,exports.getShell=getShell,exports.pushPendingError=pushPendingError,exports.setShell=setShell,exports.setState=setState,exports.takePendingErrors=takePendingErrors;var MAX_PENDING_ERRORS=20,cachedShell=null,cachedCtx=null,cachedSendEvent=null,pendingErrors=[];function setShell(e){cachedShell=e}function setState(e,t,n){cachedCtx=e,cachedSendEvent=t,cachedShell=n}function clearState(){cachedCtx=null,cachedSendEvent=null,cachedShell=null,pendingErrors=[]}function getCtx(){return cachedCtx}function getSendEvent(){return cachedSendEvent}function getShell(){return cachedShell}function pushPendingError(e,t){pendingErrors.push({error:e,context:t}),pendingErrors.length>MAX_PENDING_ERRORS&&pendingErrors.shift()}function takePendingErrors(){var e=pendingErrors;return pendingErrors=[],e}
package/lib/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @arms/rum-browser-nuxt 主入口(零 nuxt / vue / vue-router 导入,SSR-safe)。
3
+ *
4
+ * 导出 2 个运行时 API + 公共类型:
5
+ * - initNuxtPlugin:初始化插件(collector 注册 + 路由视图采集 + 错误双通道)
6
+ * - addNuxtError:Nuxt 异常手动上报(自动通道内部同链路复用)
7
+ *
8
+ * 单入口设计(无子入口):本包为纯客户端薄适配层,全部能力在
9
+ * plugins/arms-rum.client.ts 的 defineNuxtPlugin setup 内同步调用,
10
+ * 无 'use client' 边界 / 双版本入口等拆分诉求(区别于 browser-vue
11
+ * 的 vue2/vue3 子入口与 browser-nextjs 的 app/pages 子入口)。
12
+ *
13
+ * 刻意不导出:
14
+ * - setupNuxtErrorHandling / setupRouterTracking 等域内函数:仅由
15
+ * initNuxtPlugin 编排调用(最小 API 暴露原则)
16
+ * - computeNuxtViewName:内部归一化算法,非用户 API(单测经相对路径
17
+ * 覆盖;确有模板反推诉求的用户可参照 README 视图命名规则表自行实现)
18
+ */
19
+ export { initNuxtPlugin } from './domain/nuxtPlugin';
20
+ export { addNuxtError } from './domain/error/addNuxtError';
21
+ export type { NuxtErrorContext } from './domain/error/addNuxtError';
22
+ export type { NuxtPluginOptions, NuxtAppLike, NuxtVueAppLike, NuxtVueErrorHandler, AnyRouter, AnyRouteRecord, AnyRouteLocation, } from './types';
package/lib/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";exports.__esModule=!0,exports.initNuxtPlugin=exports.addNuxtError=void 0;var _nuxtPlugin=require("./domain/nuxtPlugin");exports.initNuxtPlugin=_nuxtPlugin.initNuxtPlugin;var _addNuxtError=require("./domain/error/addNuxtError");exports.addNuxtError=_addNuxtError.addNuxtError;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Nuxt / vue-router 版本无关的结构化类型定义(duck typing)。
3
+ *
4
+ * 不导入 nuxt / vue / vue-router 官方类型(零框架依赖,构建期 / 类型期
5
+ * 天然兼容 Nuxt 3 / Nuxt 4),仅包含插件实现实际用到的最小属性集。
6
+ *
7
+ * 类型应:
8
+ * - 与 Nuxt 3(>=3.7)/ Nuxt 4、Vue 3.5+、vue-router v4 / v5 结构兼容
9
+ * - 仅包含插件需要的最小属性集
10
+ */
11
+ /**
12
+ * Vue errorHandler 函数签名(Vue 3)。
13
+ *
14
+ * Vue 3:`(err: unknown, instance: ComponentPublicInstance | null, info: string)`。
15
+ * 此处取参数的最宽联合形态(unknown / any / string),保证任一版本的
16
+ * 原生 errorHandler 都能赋值给该类型。
17
+ */
18
+ export type NuxtVueErrorHandler = (err: unknown, instance: any, info: string) => void;
19
+ /**
20
+ * Nuxt vueApp 的最小结构(nuxtApp.vueApp)。
21
+ *
22
+ * 仅依赖 config.errorHandler(错误通道 1 的链式包装点)。
23
+ */
24
+ export interface NuxtVueAppLike {
25
+ /** 应用配置(errorHandler 接管点) */
26
+ config?: {
27
+ errorHandler?: NuxtVueErrorHandler;
28
+ [key: string]: any;
29
+ };
30
+ [key: string]: any;
31
+ }
32
+ /**
33
+ * NuxtApp 的最小结构(duck typing,对齐 Datadog browser-rum-nuxt
34
+ * setupNuxtErrorHandling.ts 的 NuxtApp 接口)。
35
+ *
36
+ * 仅声明错误双通道实际用到的两个成员:
37
+ * - vueApp:通道 1 链式包装 vueApp.config.errorHandler
38
+ * - hook('app:error'):通道 2 捕获 Nuxt 启动 / SSR 传播错误
39
+ */
40
+ export interface NuxtAppLike {
41
+ /** Vue 应用实例(经 nuxtApp.vueApp 访问) */
42
+ vueApp: NuxtVueAppLike;
43
+ /** Nuxt 运行时 hook(本插件仅注册 'app:error') */
44
+ hook(name: 'app:error', callback: (err: unknown) => void): void;
45
+ [key: string]: any;
46
+ }
47
+ /**
48
+ * vue-router 路由记录的最小结构(v4 / v5 RouteLocationMatched 兼容)。
49
+ */
50
+ export interface AnyRouteRecord {
51
+ /** 路由模板路径(Nuxt 文件路由生成形态),如 '/user/:id()' */
52
+ path?: string;
53
+ [key: string]: any;
54
+ }
55
+ /**
56
+ * vue-router 路由地址的最小结构(v4 / v5 RouteLocationNormalized 兼容)。
57
+ */
58
+ export interface AnyRouteLocation {
59
+ /** 实际路径(含参数实际值,不含 query 与 hash),如 '/user/123' */
60
+ path?: string;
61
+ /** hash 部分(如 '#details'),query-only 跳过守卫的判定字段之一 */
62
+ hash?: string;
63
+ /** 匹配的路由记录链(父 → 子顺序) */
64
+ matched?: AnyRouteRecord[];
65
+ [key: string]: any;
66
+ }
67
+ /**
68
+ * vue-router 实例的最小结构(v4 / v5 兼容)。
69
+ *
70
+ * 仅依赖 afterEach(v4 / v5 均提供)与 currentRoute(初始视图补偿),
71
+ * 不包装 createRouter、不替换任何框架 import。
72
+ */
73
+ export interface AnyRouter {
74
+ /**
75
+ * 注册全局后置钩子。
76
+ * v4 / v5 的回调第三参为 failure(导航失败),签名天然兼容。
77
+ * 返回卸载函数(v3+ 均支持,非函数返回值防御性忽略)。
78
+ */
79
+ afterEach(callback: (to: AnyRouteLocation, from?: AnyRouteLocation, failure?: unknown) => void): unknown;
80
+ /**
81
+ * 当前路由(v4 / v5 为 shallowRef,经 .value 读取;实现中防御兼容
82
+ * 直接对象形态)。初始视图补偿的判定依据(matched 非空即已就绪)。
83
+ */
84
+ currentRoute?: unknown;
85
+ [key: string]: any;
86
+ }
87
+ /**
88
+ * Nuxt 插件初始化选项(initNuxtPlugin 的第二参数)。
89
+ */
90
+ export interface NuxtPluginOptions {
91
+ /**
92
+ * vue-router 实例(v4 / v5 均可),必填。
93
+ *
94
+ * Nuxt 应用经 useRouter() 在插件 setup() 中取好后传入(SDK 内零
95
+ * nuxt 导入)。传入后经 router.afterEach 驱动 view 生命周期(手动
96
+ * view 模式:无条件注入 trackViewsManually + 调用 Shell.startView)。
97
+ */
98
+ router: AnyRouter;
99
+ /**
100
+ * NuxtApp 实例(useNuxtApp() 取得),可选。
101
+ *
102
+ * 提供时启用错误双通道自动接管(链式包装 vueApp.config.errorHandler
103
+ * + nuxtApp.hook('app:error'),WeakSet 按错误对象引用去重);
104
+ * 不提供时仅启用路由视图采集,错误可经 addNuxtError 手动上报。
105
+ */
106
+ nuxtApp?: NuxtAppLike;
107
+ }
@@ -0,0 +1 @@
1
+ "use strict";exports.__esModule=!0;
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@arms/rum-browser-nuxt",
3
+ "version": "0.1.0",
4
+ "description": "Nuxt plugin for @arms/rum-browser SDK (Nuxt 3/4 file-route view tracking + dual-channel error capture, zero nuxt/vue/vue-router imports)",
5
+ "author": "guangli.fj <guangli.fj@alibaba-inc.com>",
6
+ "license": "ISC",
7
+ "main": "lib/index.js",
8
+ "module": "es/index.js",
9
+ "types": "es/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./es/index.d.ts",
13
+ "import": "./es/index.js",
14
+ "require": "./lib/index.js",
15
+ "default": "./es/index.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "lib",
21
+ "es",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "keywords": [
25
+ "rum",
26
+ "real-user-monitoring",
27
+ "arms",
28
+ "nuxt",
29
+ "nuxt3",
30
+ "nuxt4",
31
+ "vue",
32
+ "vue-router",
33
+ "error-tracking",
34
+ "monitoring"
35
+ ],
36
+ "sideEffects": false,
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "build-scripts build --skip-demo",
42
+ "test": "jest --config jest.config.js --no-watchman",
43
+ "prepublishOnly": "npm run build"
44
+ },
45
+ "dependencies": {},
46
+ "peerDependencies": {
47
+ "@arms/rum-core": ">=0.1.11",
48
+ "@arms/rum-browser": ">=0.1.16",
49
+ "nuxt": "3 || 4",
50
+ "vue": "^3.5.0",
51
+ "vue-router": "^4.0.0 || ^5.0.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "nuxt": {
55
+ "optional": true
56
+ },
57
+ "vue": {
58
+ "optional": true
59
+ },
60
+ "vue-router": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "devDependencies": {
65
+ "typescript": "^4.9.4"
66
+ },
67
+ "browserslist": [
68
+ "last 2 Chrome versions",
69
+ "last 2 Safari versions",
70
+ "last 2 Firefox versions",
71
+ "last 2 Edge versions",
72
+ "iOS >= 14",
73
+ "Android >= 8"
74
+ ]
75
+ }