@goliapkg/sentori-vue 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.
@@ -0,0 +1,22 @@
1
+ export declare const SentoriErrorBoundary: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ /** Optional list of error names (`error.name`) to ignore — they
3
+ * pass through to upper boundaries unchanged. */
4
+ ignore: {
5
+ type: () => readonly string[];
6
+ default: () => never[];
7
+ };
8
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
9
+ [key: string]: any;
10
+ }> | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
11
+ [key: string]: any;
12
+ }>[] | undefined, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
13
+ /** Optional list of error names (`error.name`) to ignore — they
14
+ * pass through to upper boundaries unchanged. */
15
+ ignore: {
16
+ type: () => readonly string[];
17
+ default: () => never[];
18
+ };
19
+ }>> & Readonly<{}>, {
20
+ ignore: readonly string[];
21
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
22
+ //# sourceMappingURL=ErrorBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../src/ErrorBoundary.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,oBAAoB;IAG7B;sDACkD;;cACzB,MAAM,SAAS,MAAM,EAAE;;;;;;;;IAFhD;sDACkD;;cACzB,MAAM,SAAS,MAAM,EAAE;;;;;4EA+BlD,CAAA"}
@@ -0,0 +1,48 @@
1
+ // Phase 45 sub-B — Vue error boundary component.
2
+ //
3
+ // Vue 3 doesn't ship a built-in ErrorBoundary like React. The
4
+ // pattern is to use the `errorCaptured(err, instance, info)`
5
+ // lifecycle hook on a wrapper component that returns `false` to
6
+ // stop the error from propagating. Our wrapper captures into
7
+ // Sentori and renders either the slot's children or a `fallback`
8
+ // slot when the subtree threw.
9
+ import { captureException } from '@goliapkg/sentori-javascript';
10
+ import { defineComponent, h, ref } from 'vue';
11
+ export const SentoriErrorBoundary = defineComponent({
12
+ name: 'SentoriErrorBoundary',
13
+ props: {
14
+ /** Optional list of error names (`error.name`) to ignore — they
15
+ * pass through to upper boundaries unchanged. */
16
+ ignore: { type: Array, default: () => [] },
17
+ },
18
+ setup(_props, { slots }) {
19
+ const caughtError = ref(null);
20
+ const reset = () => {
21
+ caughtError.value = null;
22
+ };
23
+ return () => {
24
+ if (caughtError.value) {
25
+ if (slots.fallback) {
26
+ return slots.fallback({ error: caughtError.value, reset });
27
+ }
28
+ // Default fallback: a hidden span so Vue doesn't render a
29
+ // crashed subtree. Apps that don't pass a fallback opt in
30
+ // to that minimal behaviour.
31
+ return h('span', { 'data-sentori-boundary-error': 'true' });
32
+ }
33
+ return slots.default?.();
34
+ };
35
+ },
36
+ errorCaptured(err, _instance, info) {
37
+ const e = err instanceof Error ? err : new Error(String(err));
38
+ if (this.ignore.includes(e.name)) {
39
+ return true; // propagate further
40
+ }
41
+ captureException(e, { tags: { 'vue.errorInfo': info } });
42
+ // Switch to fallback render and stop propagation.
43
+ this.$forceUpdate();
44
+ this.caughtError = { value: e };
45
+ return false;
46
+ },
47
+ });
48
+ //# sourceMappingURL=ErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../src/ErrorBoundary.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,EAAE;AACF,8DAA8D;AAC9D,6DAA6D;AAC7D,gEAAgE;AAChE,6DAA6D;AAC7D,iEAAiE;AACjE,+BAA+B;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAE7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC;IAClD,IAAI,EAAE,sBAAsB;IAC5B,KAAK,EAAE;QACL;0DACkD;QAClD,MAAM,EAAE,EAAE,IAAI,EAAE,KAAgC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE;KACtE;IACD,KAAK,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE;QACrB,MAAM,WAAW,GAAG,GAAG,CAAe,IAAI,CAAC,CAAA;QAC3C,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,WAAW,CAAC,KAAK,GAAG,IAAI,CAAA;QAC1B,CAAC,CAAA;QACD,OAAO,GAAG,EAAE;YACV,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;gBACtB,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;oBACnB,OAAO,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;gBAC5D,CAAC;gBACD,0DAA0D;gBAC1D,0DAA0D;gBAC1D,6BAA6B;gBAC7B,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,6BAA6B,EAAE,MAAM,EAAE,CAAC,CAAA;YAC7D,CAAC;YACD,OAAO,KAAK,CAAC,OAAO,EAAE,EAAE,CAAA;QAC1B,CAAC,CAAA;IACH,CAAC;IACD,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI;QAChC,MAAM,CAAC,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC7D,IAAK,IAAI,CAAC,MAA4B,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACxD,OAAO,IAAI,CAAA,CAAC,oBAAoB;QAClC,CAAC;QACD,gBAAgB,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;QACxD,kDAAkD;QAClD,IAAI,CAAC,YAAY,EAAE,CAClB;QAAC,IAA4D,CAAC,WAAW,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAA;QACzF,OAAO,KAAK,CAAA;IACd,CAAC;CACF,CAAC,CAAA"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Phase 45 sub-B — Vue 3 adapter for Sentori.
3
+ *
4
+ * Plugin shape:
5
+ *
6
+ * import { createApp } from 'vue'
7
+ * import sentori from '@goliapkg/sentori-vue'
8
+ *
9
+ * const app = createApp(App)
10
+ * app.use(sentori, {
11
+ * token: 'st_pk_…',
12
+ * release: 'myapp@1.0.0',
13
+ * sampling: { errors: 1.0 },
14
+ * })
15
+ *
16
+ * What `app.use(sentori, opts)` does:
17
+ * 1. forwards `opts` to `@goliapkg/sentori-javascript`'s init
18
+ * 2. wires `app.config.errorHandler` so any error thrown inside
19
+ * a render / lifecycle bubbles into `captureException`
20
+ * 3. tags every Sentori event with `tags.vue.version` so the
21
+ * dashboard knows which framework is producing the data
22
+ *
23
+ * Router integration (Vue Router) lives in the `/router` subpath:
24
+ *
25
+ * import { setupTraceNavigation } from '@goliapkg/sentori-vue/router'
26
+ * setupTraceNavigation(router)
27
+ */
28
+ import type { Plugin } from 'vue';
29
+ import { type InitOptions } from '@goliapkg/sentori-javascript';
30
+ export type SentoriVueOptions = InitOptions;
31
+ declare const plugin: Plugin;
32
+ export default plugin;
33
+ export { plugin as sentori };
34
+ export { addBreadcrumb, captureException, captureException as captureError, captureStep, getUser, setUser, } from '@goliapkg/sentori-javascript';
35
+ export { SentoriErrorBoundary } from './ErrorBoundary.js';
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAO,MAAM,EAAE,MAAM,KAAK,CAAA;AACtC,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAA;AAErC,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAA;AAE3C,QAAA,MAAM,MAAM,EAAE,MAqBb,CAAA;AAED,eAAe,MAAM,CAAA;AACrB,OAAO,EAAE,MAAM,IAAI,OAAO,EAAE,CAAA;AAE5B,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,IAAI,YAAY,EAChC,WAAW,EACX,OAAO,EACP,OAAO,GACR,MAAM,8BAA8B,CAAA;AAErC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA"}
package/lib/index.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Phase 45 sub-B — Vue 3 adapter for Sentori.
3
+ *
4
+ * Plugin shape:
5
+ *
6
+ * import { createApp } from 'vue'
7
+ * import sentori from '@goliapkg/sentori-vue'
8
+ *
9
+ * const app = createApp(App)
10
+ * app.use(sentori, {
11
+ * token: 'st_pk_…',
12
+ * release: 'myapp@1.0.0',
13
+ * sampling: { errors: 1.0 },
14
+ * })
15
+ *
16
+ * What `app.use(sentori, opts)` does:
17
+ * 1. forwards `opts` to `@goliapkg/sentori-javascript`'s init
18
+ * 2. wires `app.config.errorHandler` so any error thrown inside
19
+ * a render / lifecycle bubbles into `captureException`
20
+ * 3. tags every Sentori event with `tags.vue.version` so the
21
+ * dashboard knows which framework is producing the data
22
+ *
23
+ * Router integration (Vue Router) lives in the `/router` subpath:
24
+ *
25
+ * import { setupTraceNavigation } from '@goliapkg/sentori-vue/router'
26
+ * setupTraceNavigation(router)
27
+ */
28
+ import { captureException as captureExceptionJs, initSentori as initSentoriJs, } from '@goliapkg/sentori-javascript';
29
+ const plugin = {
30
+ install(app, options) {
31
+ // 1. init the core JS SDK.
32
+ initSentoriJs(options);
33
+ // 2. Vue's global error handler. Sentori captureException
34
+ // accepts an Error; Vue's handler receives `unknown`. Wrap
35
+ // non-Error values so the SDK still gets a stack.
36
+ const previous = app.config.errorHandler;
37
+ app.config.errorHandler = (err, instance, info) => {
38
+ const e = err instanceof Error ? err : new Error(String(err));
39
+ captureExceptionJs(e, {
40
+ tags: {
41
+ 'vue.component': instance?.$options?.name ?? '<anonymous>',
42
+ 'vue.errorInfo': info,
43
+ },
44
+ });
45
+ // Chain to any previously installed handler so plugins layer.
46
+ if (previous)
47
+ previous(err, instance, info);
48
+ };
49
+ },
50
+ };
51
+ export default plugin;
52
+ export { plugin as sentori };
53
+ export { addBreadcrumb, captureException, captureException as captureError, captureStep, getUser, setUser, } from '@goliapkg/sentori-javascript';
54
+ export { SentoriErrorBoundary } from './ErrorBoundary.js';
55
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EACL,gBAAgB,IAAI,kBAAkB,EACtC,WAAW,IAAI,aAAa,GAE7B,MAAM,8BAA8B,CAAA;AAIrC,MAAM,MAAM,GAAW;IACrB,OAAO,CAAC,GAAQ,EAAE,OAA0B;QAC1C,2BAA2B;QAC3B,aAAa,CAAC,OAAO,CAAC,CAAA;QAEtB,0DAA0D;QAC1D,8DAA8D;QAC9D,qDAAqD;QACrD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,CAAA;QACxC,GAAG,CAAC,MAAM,CAAC,YAAY,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;YAChD,MAAM,CAAC,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;YAC7D,kBAAkB,CAAC,CAAC,EAAE;gBACpB,IAAI,EAAE;oBACJ,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,IAAI,aAAa;oBAC1D,eAAe,EAAE,IAAI;iBACtB;aACF,CAAC,CAAA;YACF,8DAA8D;YAC9D,IAAI,QAAQ;gBAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC7C,CAAC,CAAA;IACH,CAAC;CACF,CAAA;AAED,eAAe,MAAM,CAAA;AACrB,OAAO,EAAE,MAAM,IAAI,OAAO,EAAE,CAAA;AAE5B,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,IAAI,YAAY,EAChC,WAAW,EACX,OAAO,EACP,OAAO,GACR,MAAM,8BAA8B,CAAA;AAErC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,17 @@
1
+ type RouterLike = {
2
+ beforeEach: (cb: (to: {
3
+ path: string;
4
+ name?: unknown;
5
+ }, from: {
6
+ path: string;
7
+ }) => void) => void;
8
+ afterEach: (cb: (to: {
9
+ path: string;
10
+ name?: unknown;
11
+ }, from: {
12
+ path: string;
13
+ }) => void) => void;
14
+ };
15
+ export declare function setupTraceNavigation(router: RouterLike): void;
16
+ export {};
17
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAmBA,KAAK,UAAU,GAAG;IAChB,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,IAAI,CAAA;IAChG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,IAAI,CAAA;CAChG,CAAA;AAID,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CA4B7D"}
package/lib/router.js ADDED
@@ -0,0 +1,46 @@
1
+ // Phase 45 sub-B — Vue Router auto-trace navigation.
2
+ //
3
+ // import { createRouter } from 'vue-router'
4
+ // import { setupTraceNavigation } from '@goliapkg/sentori-vue/router'
5
+ //
6
+ // const router = createRouter({ ... })
7
+ // setupTraceNavigation(router)
8
+ //
9
+ // On every route push, we open a `vue.navigation` span keyed by the
10
+ // destination path, mark it active, and finish it on the next
11
+ // `afterEach`. Sentori spans / fetch / xhr instrumentation in
12
+ // `@goliapkg/sentori-javascript` automatically nest into it so each
13
+ // screen's network requests cluster into one trace.
14
+ import { setActiveSpan, startSpan } from '@goliapkg/sentori-core';
15
+ import { captureStep } from '@goliapkg/sentori-javascript';
16
+ let _active = null;
17
+ export function setupTraceNavigation(router) {
18
+ router.beforeEach((to, from) => {
19
+ // Finish any still-open span from the previous transition that
20
+ // afterEach didn't reach (route guard rejected, etc.).
21
+ if (_active) {
22
+ _active.finish({ status: 'ok' });
23
+ _active = null;
24
+ }
25
+ const name = `${from.path || '/'} → ${to.path || '/'}`;
26
+ const span = startSpan('vue.navigation', {
27
+ name,
28
+ parent: null,
29
+ tags: { 'nav.from': from.path || '/', 'nav.to': to.path || '/' },
30
+ });
31
+ _active = span;
32
+ setActiveSpan(span);
33
+ // Phase 46 — also record into the session-trail buffer; no-op
34
+ // unless `init({ capture: { sessionTrail: true } })`.
35
+ captureStep(`route:${to.path || '/'}`, {
36
+ breadcrumb: { type: 'navigation', message: name },
37
+ });
38
+ });
39
+ router.afterEach(() => {
40
+ if (_active) {
41
+ _active.finish({ status: 'ok' });
42
+ _active = null;
43
+ }
44
+ });
45
+ }
46
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,EAAE;AACF,gDAAgD;AAChD,0EAA0E;AAC1E,EAAE;AACF,2CAA2C;AAC3C,mCAAmC;AACnC,EAAE;AACF,oEAAoE;AACpE,8DAA8D;AAC9D,8DAA8D;AAC9D,oEAAoE;AACpE,oDAAoD;AAEpD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAmB,MAAM,wBAAwB,CAAA;AAClF,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAS1D,IAAI,OAAO,GAAsB,IAAI,CAAA;AAErC,MAAM,UAAU,oBAAoB,CAAC,MAAkB;IACrD,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE;QAC7B,+DAA+D;QAC/D,uDAAuD;QACvD,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;YAChC,OAAO,GAAG,IAAI,CAAA;QAChB,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,IAAI,GAAG,EAAE,CAAA;QACtD,MAAM,IAAI,GAAG,SAAS,CAAC,gBAAgB,EAAE;YACvC,IAAI;YACJ,MAAM,EAAE,IAAI;YACZ,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,EAAE;SACjE,CAAC,CAAA;QACF,OAAO,GAAG,IAAI,CAAA;QACd,aAAa,CAAC,IAAI,CAAC,CAAA;QACnB,8DAA8D;QAC9D,sDAAsD;QACtD,WAAW,CAAC,SAAS,EAAE,CAAC,IAAI,IAAI,GAAG,EAAE,EAAE;YACrC,UAAU,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE;SAClD,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE;QACpB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;YAChC,OAAO,GAAG,IAAI,CAAA;QAChB,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@goliapkg/sentori-vue",
3
+ "version": "0.1.0",
4
+ "description": "Vue 3 adapter for Sentori — plugin, errorHandler hook, Vue Router auto-trace navigation, <SentoriErrorBoundary>.",
5
+ "license": "MIT",
6
+ "homepage": "https://sentori.golia.jp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/goliajp/sentori.git",
10
+ "directory": "sdk/vue"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/goliajp/sentori/issues"
14
+ },
15
+ "keywords": [
16
+ "sentori",
17
+ "vue",
18
+ "vue3",
19
+ "error-tracking",
20
+ "error-boundary"
21
+ ],
22
+ "type": "module",
23
+ "main": "./lib/index.js",
24
+ "types": "./lib/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./lib/index.d.ts",
28
+ "default": "./lib/index.js"
29
+ },
30
+ "./router": {
31
+ "types": "./lib/router.d.ts",
32
+ "default": "./lib/router.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "lib/",
37
+ "src/",
38
+ "README.md"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc --noEmit",
43
+ "test": "bun test",
44
+ "prepack": "bun run build"
45
+ },
46
+ "peerDependencies": {
47
+ "vue": ">=3.4",
48
+ "vue-router": ">=4"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "vue-router": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "dependencies": {
56
+ "@goliapkg/sentori-core": "0.6.0",
57
+ "@goliapkg/sentori-javascript": "0.4.0"
58
+ },
59
+ "devDependencies": {
60
+ "@types/bun": "latest",
61
+ "typescript": "^5",
62
+ "vue": "^3.4"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ }
67
+ }
@@ -0,0 +1,49 @@
1
+ // Phase 45 sub-B — Vue error boundary component.
2
+ //
3
+ // Vue 3 doesn't ship a built-in ErrorBoundary like React. The
4
+ // pattern is to use the `errorCaptured(err, instance, info)`
5
+ // lifecycle hook on a wrapper component that returns `false` to
6
+ // stop the error from propagating. Our wrapper captures into
7
+ // Sentori and renders either the slot's children or a `fallback`
8
+ // slot when the subtree threw.
9
+
10
+ import { captureException } from '@goliapkg/sentori-javascript'
11
+ import { defineComponent, h, ref } from 'vue'
12
+
13
+ export const SentoriErrorBoundary = defineComponent({
14
+ name: 'SentoriErrorBoundary',
15
+ props: {
16
+ /** Optional list of error names (`error.name`) to ignore — they
17
+ * pass through to upper boundaries unchanged. */
18
+ ignore: { type: Array as () => readonly string[], default: () => [] },
19
+ },
20
+ setup(_props, { slots }) {
21
+ const caughtError = ref<Error | null>(null)
22
+ const reset = () => {
23
+ caughtError.value = null
24
+ }
25
+ return () => {
26
+ if (caughtError.value) {
27
+ if (slots.fallback) {
28
+ return slots.fallback({ error: caughtError.value, reset })
29
+ }
30
+ // Default fallback: a hidden span so Vue doesn't render a
31
+ // crashed subtree. Apps that don't pass a fallback opt in
32
+ // to that minimal behaviour.
33
+ return h('span', { 'data-sentori-boundary-error': 'true' })
34
+ }
35
+ return slots.default?.()
36
+ }
37
+ },
38
+ errorCaptured(err, _instance, info) {
39
+ const e = err instanceof Error ? err : new Error(String(err))
40
+ if ((this.ignore as readonly string[]).includes(e.name)) {
41
+ return true // propagate further
42
+ }
43
+ captureException(e, { tags: { 'vue.errorInfo': info } })
44
+ // Switch to fallback render and stop propagation.
45
+ this.$forceUpdate()
46
+ ;(this as unknown as { caughtError: { value: Error | null } }).caughtError = { value: e }
47
+ return false
48
+ },
49
+ })
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import plugin, {
4
+ SentoriErrorBoundary,
5
+ addBreadcrumb,
6
+ captureException,
7
+ sentori,
8
+ } from '../index.js'
9
+ import { setupTraceNavigation } from '../router.js'
10
+
11
+ describe('@goliapkg/sentori-vue exports', () => {
12
+ test('default export is a Vue plugin with an install function', () => {
13
+ expect(typeof plugin.install).toBe('function')
14
+ expect(plugin).toBe(sentori)
15
+ })
16
+
17
+ test('SentoriErrorBoundary is a defineComponent', () => {
18
+ expect(SentoriErrorBoundary).toBeDefined()
19
+ expect(typeof SentoriErrorBoundary).toBe('object')
20
+ })
21
+
22
+ test('Vue Router helper is exported', () => {
23
+ expect(typeof setupTraceNavigation).toBe('function')
24
+ })
25
+
26
+ test('re-exports common SDK helpers', () => {
27
+ expect(typeof captureException).toBe('function')
28
+ expect(typeof addBreadcrumb).toBe('function')
29
+ })
30
+ })
package/src/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Phase 45 sub-B — Vue 3 adapter for Sentori.
3
+ *
4
+ * Plugin shape:
5
+ *
6
+ * import { createApp } from 'vue'
7
+ * import sentori from '@goliapkg/sentori-vue'
8
+ *
9
+ * const app = createApp(App)
10
+ * app.use(sentori, {
11
+ * token: 'st_pk_…',
12
+ * release: 'myapp@1.0.0',
13
+ * sampling: { errors: 1.0 },
14
+ * })
15
+ *
16
+ * What `app.use(sentori, opts)` does:
17
+ * 1. forwards `opts` to `@goliapkg/sentori-javascript`'s init
18
+ * 2. wires `app.config.errorHandler` so any error thrown inside
19
+ * a render / lifecycle bubbles into `captureException`
20
+ * 3. tags every Sentori event with `tags.vue.version` so the
21
+ * dashboard knows which framework is producing the data
22
+ *
23
+ * Router integration (Vue Router) lives in the `/router` subpath:
24
+ *
25
+ * import { setupTraceNavigation } from '@goliapkg/sentori-vue/router'
26
+ * setupTraceNavigation(router)
27
+ */
28
+
29
+ import type { App, Plugin } from 'vue'
30
+ import {
31
+ captureException as captureExceptionJs,
32
+ initSentori as initSentoriJs,
33
+ type InitOptions,
34
+ } from '@goliapkg/sentori-javascript'
35
+
36
+ export type SentoriVueOptions = InitOptions
37
+
38
+ const plugin: Plugin = {
39
+ install(app: App, options: SentoriVueOptions) {
40
+ // 1. init the core JS SDK.
41
+ initSentoriJs(options)
42
+
43
+ // 2. Vue's global error handler. Sentori captureException
44
+ // accepts an Error; Vue's handler receives `unknown`. Wrap
45
+ // non-Error values so the SDK still gets a stack.
46
+ const previous = app.config.errorHandler
47
+ app.config.errorHandler = (err, instance, info) => {
48
+ const e = err instanceof Error ? err : new Error(String(err))
49
+ captureExceptionJs(e, {
50
+ tags: {
51
+ 'vue.component': instance?.$options?.name ?? '<anonymous>',
52
+ 'vue.errorInfo': info,
53
+ },
54
+ })
55
+ // Chain to any previously installed handler so plugins layer.
56
+ if (previous) previous(err, instance, info)
57
+ }
58
+ },
59
+ }
60
+
61
+ export default plugin
62
+ export { plugin as sentori }
63
+
64
+ export {
65
+ addBreadcrumb,
66
+ captureException,
67
+ captureException as captureError,
68
+ captureStep,
69
+ getUser,
70
+ setUser,
71
+ } from '@goliapkg/sentori-javascript'
72
+
73
+ export { SentoriErrorBoundary } from './ErrorBoundary.js'
package/src/router.ts ADDED
@@ -0,0 +1,55 @@
1
+ // Phase 45 sub-B — Vue Router auto-trace navigation.
2
+ //
3
+ // import { createRouter } from 'vue-router'
4
+ // import { setupTraceNavigation } from '@goliapkg/sentori-vue/router'
5
+ //
6
+ // const router = createRouter({ ... })
7
+ // setupTraceNavigation(router)
8
+ //
9
+ // On every route push, we open a `vue.navigation` span keyed by the
10
+ // destination path, mark it active, and finish it on the next
11
+ // `afterEach`. Sentori spans / fetch / xhr instrumentation in
12
+ // `@goliapkg/sentori-javascript` automatically nest into it so each
13
+ // screen's network requests cluster into one trace.
14
+
15
+ import { setActiveSpan, startSpan, type SpanHandle } from '@goliapkg/sentori-core'
16
+ import { captureStep } from '@goliapkg/sentori-javascript'
17
+
18
+ // Minimal duck type — accept anything that exposes `beforeEach` +
19
+ // `afterEach`. Avoids hard-coding a vue-router version.
20
+ type RouterLike = {
21
+ beforeEach: (cb: (to: { path: string; name?: unknown }, from: { path: string }) => void) => void
22
+ afterEach: (cb: (to: { path: string; name?: unknown }, from: { path: string }) => void) => void
23
+ }
24
+
25
+ let _active: SpanHandle | null = null
26
+
27
+ export function setupTraceNavigation(router: RouterLike): void {
28
+ router.beforeEach((to, from) => {
29
+ // Finish any still-open span from the previous transition that
30
+ // afterEach didn't reach (route guard rejected, etc.).
31
+ if (_active) {
32
+ _active.finish({ status: 'ok' })
33
+ _active = null
34
+ }
35
+ const name = `${from.path || '/'} → ${to.path || '/'}`
36
+ const span = startSpan('vue.navigation', {
37
+ name,
38
+ parent: null,
39
+ tags: { 'nav.from': from.path || '/', 'nav.to': to.path || '/' },
40
+ })
41
+ _active = span
42
+ setActiveSpan(span)
43
+ // Phase 46 — also record into the session-trail buffer; no-op
44
+ // unless `init({ capture: { sessionTrail: true } })`.
45
+ captureStep(`route:${to.path || '/'}`, {
46
+ breadcrumb: { type: 'navigation', message: name },
47
+ })
48
+ })
49
+ router.afterEach(() => {
50
+ if (_active) {
51
+ _active.finish({ status: 'ok' })
52
+ _active = null
53
+ }
54
+ })
55
+ }