@tallyrow/safesignal 1.2.0 → 1.4.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 +359 -22
- package/dist/capture.cjs +77 -0
- package/dist/capture.cjs.map +1 -0
- package/dist/capture.d.cts +49 -0
- package/dist/capture.d.ts +49 -0
- package/dist/capture.mjs +75 -0
- package/dist/capture.mjs.map +1 -0
- package/dist/dev-console.cjs +90 -0
- package/dist/dev-console.cjs.map +1 -0
- package/dist/dev-console.d.cts +67 -0
- package/dist/dev-console.d.ts +67 -0
- package/dist/dev-console.mjs +88 -0
- package/dist/dev-console.mjs.map +1 -0
- package/dist/framework-react.cjs +92 -0
- package/dist/framework-react.cjs.map +1 -0
- package/dist/framework-react.d.cts +97 -0
- package/dist/framework-react.d.ts +97 -0
- package/dist/framework-react.mjs +87 -0
- package/dist/framework-react.mjs.map +1 -0
- package/dist/framework-vue.cjs +88 -0
- package/dist/framework-vue.cjs.map +1 -0
- package/dist/framework-vue.d.cts +101 -0
- package/dist/framework-vue.d.ts +101 -0
- package/dist/framework-vue.mjs +82 -0
- package/dist/framework-vue.mjs.map +1 -0
- package/dist/index.cjs +180 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +180 -40
- package/dist/index.mjs.map +1 -1
- package/dist/stacks.cjs +81 -0
- package/dist/stacks.cjs.map +1 -0
- package/dist/stacks.d.cts +55 -0
- package/dist/stacks.d.ts +55 -0
- package/dist/stacks.mjs +77 -0
- package/dist/stacks.mjs.map +1 -0
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/transport-beacon.cjs.map +1 -1
- package/dist/transport-beacon.d.cts +1 -1
- package/dist/transport-beacon.d.ts +1 -1
- package/dist/transport-beacon.mjs.map +1 -1
- package/dist/transport-otlp.cjs +84 -4
- package/dist/transport-otlp.cjs.map +1 -1
- package/dist/transport-otlp.d.cts +10 -1
- package/dist/transport-otlp.d.ts +10 -1
- package/dist/transport-otlp.mjs +84 -4
- package/dist/transport-otlp.mjs.map +1 -1
- package/dist/{types-BiRyHi1e.d.cts → types-CZtSjgq5.d.cts} +53 -1
- package/dist/{types-BiRyHi1e.d.ts → types-CZtSjgq5.d.ts} +53 -1
- package/package.json +58 -7
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Component, ReactNode, Context, ErrorInfo, ReactElement } from 'react';
|
|
2
|
+
import { f as Logger, b as Attributes } from './types-CZtSjgq5.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React error handling — the `./framework-react` subpath.
|
|
6
|
+
*
|
|
7
|
+
* The **no-globals, React-native counterpart** to `./capture`: a per-component
|
|
8
|
+
* `<LogErrorBoundary>` plus a `useLogError()` hook that route React errors
|
|
9
|
+
* through a consumer-provided `Logger`'s existing secure pipeline (sanitize →
|
|
10
|
+
* URL-scrub → redact → guard → transport) by calling `logger.error(...)`. Where
|
|
11
|
+
* `./capture` is a single host-level *global* install, this is explicit,
|
|
12
|
+
* per-subtree, and side-effect-free — it patches nothing and attaches no global
|
|
13
|
+
* listeners.
|
|
14
|
+
*
|
|
15
|
+
* Properties (see `specs/018-react-error-boundary/contracts/framework-react.md`):
|
|
16
|
+
* - Fail-closed: emits via `logger.error`, so messages / stacks / component
|
|
17
|
+
* stacks are redacted + sanitized (drop-on-failure) before any transport.
|
|
18
|
+
* - Fail-safe: a logging (or `onError`) throw is swallowed; the fallback still
|
|
19
|
+
* renders and nothing propagates to the page (Principle III). React's own
|
|
20
|
+
* semantics keep it loop-free (a boundary does not catch errors thrown while
|
|
21
|
+
* rendering its own fallback).
|
|
22
|
+
* - No-globals: no `window.onerror`, no `addEventListener`, no monkey-patching,
|
|
23
|
+
* no timers, no ambient reads (Principle VIII).
|
|
24
|
+
* - Framework-neutral-preserving: `react` is an externalized **peer** import,
|
|
25
|
+
* so the core entry and every other subpath stay React-free (Principle IV).
|
|
26
|
+
*
|
|
27
|
+
* The only intra-package `src/` import is **type-only** from `../api/types.js`;
|
|
28
|
+
* the single runtime external is `react` (the consumer-provided peer). No
|
|
29
|
+
* runtime state is shared with the core — the helpers operate solely through the
|
|
30
|
+
* passed/contextual `Logger`.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* React context carrying the consumer's `Logger` for a subtree. Default
|
|
35
|
+
* `undefined` — when no provider/override resolves a logger the helpers are a
|
|
36
|
+
* safe no-op (they never mint a fallback logger, which would couple this bundle
|
|
37
|
+
* to the core runtime). React-scoped, never a global registry.
|
|
38
|
+
*/
|
|
39
|
+
declare const LoggerContext: Context<Logger | undefined>;
|
|
40
|
+
/** Props for {@link LoggerProvider}. */
|
|
41
|
+
interface LoggerProviderProps {
|
|
42
|
+
/** The consumer's configured `Logger`, shared with the subtree. */
|
|
43
|
+
logger: Logger;
|
|
44
|
+
children?: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Supplies a `Logger` to {@link LogErrorBoundary} / {@link useLogError} within a
|
|
48
|
+
* subtree. Pure context plumbing — no globals, no side effects.
|
|
49
|
+
*/
|
|
50
|
+
declare function LoggerProvider(props: LoggerProviderProps): ReactElement;
|
|
51
|
+
/** A node, or a render-prop given the caught error + a reset callback. */
|
|
52
|
+
type FallbackRender = ReactNode | ((error: unknown, reset: () => void) => ReactNode);
|
|
53
|
+
/** Props for {@link LogErrorBoundary}. */
|
|
54
|
+
interface LogErrorBoundaryProps {
|
|
55
|
+
children?: ReactNode;
|
|
56
|
+
/** Explicit logger override; falls back to {@link LoggerContext}. */
|
|
57
|
+
logger?: Logger;
|
|
58
|
+
/** Rendered when an error is caught. Default: `null` (render nothing). */
|
|
59
|
+
fallback?: FallbackRender;
|
|
60
|
+
/** Optional consumer hook, invoked fail-safe AFTER logging. */
|
|
61
|
+
onError?: (error: unknown, info: {
|
|
62
|
+
componentStack: string;
|
|
63
|
+
}) => void;
|
|
64
|
+
/** Changing any key (shallow compare) clears caught state + re-mounts. */
|
|
65
|
+
resetKeys?: ReadonlyArray<unknown>;
|
|
66
|
+
}
|
|
67
|
+
interface LogErrorBoundaryState {
|
|
68
|
+
caught: boolean;
|
|
69
|
+
error: unknown;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Catches descendant render / lifecycle / constructor errors, logs them through
|
|
73
|
+
* the resolved `Logger` (with the React component stack), and renders a fallback
|
|
74
|
+
* in place of the crashed subtree. Errors React cannot catch this way (event
|
|
75
|
+
* handlers, async) belong to {@link useLogError}.
|
|
76
|
+
*/
|
|
77
|
+
declare class LogErrorBoundary extends Component<LogErrorBoundaryProps, LogErrorBoundaryState> {
|
|
78
|
+
static contextType: Context<Logger | undefined>;
|
|
79
|
+
context: Logger | undefined;
|
|
80
|
+
constructor(props: LogErrorBoundaryProps);
|
|
81
|
+
static getDerivedStateFromError(error: unknown): LogErrorBoundaryState;
|
|
82
|
+
componentDidCatch(error: unknown, info: ErrorInfo): void;
|
|
83
|
+
componentDidUpdate(prevProps: LogErrorBoundaryProps): void;
|
|
84
|
+
reset(): void;
|
|
85
|
+
render(): ReactNode;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns a **stable** callback that logs an error through the resolved logger
|
|
89
|
+
* (`loggerOverride` ?? {@link LoggerContext}) as an `error`-level event — for the
|
|
90
|
+
* errors a boundary cannot catch (event handlers, async/`Promise` callbacks,
|
|
91
|
+
* effects). Fail-safe; a **safe no-op** when no logger resolves. The callback
|
|
92
|
+
* identity is stable across re-renders for a fixed resolved logger (safe in
|
|
93
|
+
* dependency arrays).
|
|
94
|
+
*/
|
|
95
|
+
declare function useLogError(loggerOverride?: Logger): (error: unknown, attributes?: Attributes) => void;
|
|
96
|
+
|
|
97
|
+
export { LogErrorBoundary, type LogErrorBoundaryProps, LoggerContext, LoggerProvider, type LoggerProviderProps, useLogError };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Component, ReactNode, Context, ErrorInfo, ReactElement } from 'react';
|
|
2
|
+
import { f as Logger, b as Attributes } from './types-CZtSjgq5.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React error handling — the `./framework-react` subpath.
|
|
6
|
+
*
|
|
7
|
+
* The **no-globals, React-native counterpart** to `./capture`: a per-component
|
|
8
|
+
* `<LogErrorBoundary>` plus a `useLogError()` hook that route React errors
|
|
9
|
+
* through a consumer-provided `Logger`'s existing secure pipeline (sanitize →
|
|
10
|
+
* URL-scrub → redact → guard → transport) by calling `logger.error(...)`. Where
|
|
11
|
+
* `./capture` is a single host-level *global* install, this is explicit,
|
|
12
|
+
* per-subtree, and side-effect-free — it patches nothing and attaches no global
|
|
13
|
+
* listeners.
|
|
14
|
+
*
|
|
15
|
+
* Properties (see `specs/018-react-error-boundary/contracts/framework-react.md`):
|
|
16
|
+
* - Fail-closed: emits via `logger.error`, so messages / stacks / component
|
|
17
|
+
* stacks are redacted + sanitized (drop-on-failure) before any transport.
|
|
18
|
+
* - Fail-safe: a logging (or `onError`) throw is swallowed; the fallback still
|
|
19
|
+
* renders and nothing propagates to the page (Principle III). React's own
|
|
20
|
+
* semantics keep it loop-free (a boundary does not catch errors thrown while
|
|
21
|
+
* rendering its own fallback).
|
|
22
|
+
* - No-globals: no `window.onerror`, no `addEventListener`, no monkey-patching,
|
|
23
|
+
* no timers, no ambient reads (Principle VIII).
|
|
24
|
+
* - Framework-neutral-preserving: `react` is an externalized **peer** import,
|
|
25
|
+
* so the core entry and every other subpath stay React-free (Principle IV).
|
|
26
|
+
*
|
|
27
|
+
* The only intra-package `src/` import is **type-only** from `../api/types.js`;
|
|
28
|
+
* the single runtime external is `react` (the consumer-provided peer). No
|
|
29
|
+
* runtime state is shared with the core — the helpers operate solely through the
|
|
30
|
+
* passed/contextual `Logger`.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* React context carrying the consumer's `Logger` for a subtree. Default
|
|
35
|
+
* `undefined` — when no provider/override resolves a logger the helpers are a
|
|
36
|
+
* safe no-op (they never mint a fallback logger, which would couple this bundle
|
|
37
|
+
* to the core runtime). React-scoped, never a global registry.
|
|
38
|
+
*/
|
|
39
|
+
declare const LoggerContext: Context<Logger | undefined>;
|
|
40
|
+
/** Props for {@link LoggerProvider}. */
|
|
41
|
+
interface LoggerProviderProps {
|
|
42
|
+
/** The consumer's configured `Logger`, shared with the subtree. */
|
|
43
|
+
logger: Logger;
|
|
44
|
+
children?: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Supplies a `Logger` to {@link LogErrorBoundary} / {@link useLogError} within a
|
|
48
|
+
* subtree. Pure context plumbing — no globals, no side effects.
|
|
49
|
+
*/
|
|
50
|
+
declare function LoggerProvider(props: LoggerProviderProps): ReactElement;
|
|
51
|
+
/** A node, or a render-prop given the caught error + a reset callback. */
|
|
52
|
+
type FallbackRender = ReactNode | ((error: unknown, reset: () => void) => ReactNode);
|
|
53
|
+
/** Props for {@link LogErrorBoundary}. */
|
|
54
|
+
interface LogErrorBoundaryProps {
|
|
55
|
+
children?: ReactNode;
|
|
56
|
+
/** Explicit logger override; falls back to {@link LoggerContext}. */
|
|
57
|
+
logger?: Logger;
|
|
58
|
+
/** Rendered when an error is caught. Default: `null` (render nothing). */
|
|
59
|
+
fallback?: FallbackRender;
|
|
60
|
+
/** Optional consumer hook, invoked fail-safe AFTER logging. */
|
|
61
|
+
onError?: (error: unknown, info: {
|
|
62
|
+
componentStack: string;
|
|
63
|
+
}) => void;
|
|
64
|
+
/** Changing any key (shallow compare) clears caught state + re-mounts. */
|
|
65
|
+
resetKeys?: ReadonlyArray<unknown>;
|
|
66
|
+
}
|
|
67
|
+
interface LogErrorBoundaryState {
|
|
68
|
+
caught: boolean;
|
|
69
|
+
error: unknown;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Catches descendant render / lifecycle / constructor errors, logs them through
|
|
73
|
+
* the resolved `Logger` (with the React component stack), and renders a fallback
|
|
74
|
+
* in place of the crashed subtree. Errors React cannot catch this way (event
|
|
75
|
+
* handlers, async) belong to {@link useLogError}.
|
|
76
|
+
*/
|
|
77
|
+
declare class LogErrorBoundary extends Component<LogErrorBoundaryProps, LogErrorBoundaryState> {
|
|
78
|
+
static contextType: Context<Logger | undefined>;
|
|
79
|
+
context: Logger | undefined;
|
|
80
|
+
constructor(props: LogErrorBoundaryProps);
|
|
81
|
+
static getDerivedStateFromError(error: unknown): LogErrorBoundaryState;
|
|
82
|
+
componentDidCatch(error: unknown, info: ErrorInfo): void;
|
|
83
|
+
componentDidUpdate(prevProps: LogErrorBoundaryProps): void;
|
|
84
|
+
reset(): void;
|
|
85
|
+
render(): ReactNode;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns a **stable** callback that logs an error through the resolved logger
|
|
89
|
+
* (`loggerOverride` ?? {@link LoggerContext}) as an `error`-level event — for the
|
|
90
|
+
* errors a boundary cannot catch (event handlers, async/`Promise` callbacks,
|
|
91
|
+
* effects). Fail-safe; a **safe no-op** when no logger resolves. The callback
|
|
92
|
+
* identity is stable across re-renders for a fixed resolved logger (safe in
|
|
93
|
+
* dependency arrays).
|
|
94
|
+
*/
|
|
95
|
+
declare function useLogError(loggerOverride?: Logger): (error: unknown, attributes?: Attributes) => void;
|
|
96
|
+
|
|
97
|
+
export { LogErrorBoundary, type LogErrorBoundaryProps, LoggerContext, LoggerProvider, type LoggerProviderProps, useLogError };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createContext, Component, createElement, useContext, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/framework-react/index.ts
|
|
4
|
+
var SOURCE_BOUNDARY = "react-error-boundary";
|
|
5
|
+
var SOURCE_HOOK = "react-use-log-error";
|
|
6
|
+
var LoggerContext = createContext(void 0);
|
|
7
|
+
function LoggerProvider(props) {
|
|
8
|
+
return createElement(
|
|
9
|
+
LoggerContext.Provider,
|
|
10
|
+
{ value: props.logger },
|
|
11
|
+
props.children
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
function resetKeysChanged(prev, next) {
|
|
15
|
+
if (prev === next) return false;
|
|
16
|
+
if (prev === void 0 || next === void 0) return true;
|
|
17
|
+
if (prev.length !== next.length) return true;
|
|
18
|
+
for (let i = 0; i < prev.length; i += 1) {
|
|
19
|
+
if (!Object.is(prev[i], next[i])) return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
var LogErrorBoundary = class extends Component {
|
|
24
|
+
constructor(props) {
|
|
25
|
+
super(props);
|
|
26
|
+
this.state = { caught: false, error: void 0 };
|
|
27
|
+
this.reset = this.reset.bind(this);
|
|
28
|
+
}
|
|
29
|
+
static getDerivedStateFromError(error) {
|
|
30
|
+
return { caught: true, error };
|
|
31
|
+
}
|
|
32
|
+
componentDidCatch(error, info) {
|
|
33
|
+
const componentStack = typeof info.componentStack === "string" ? info.componentStack : "";
|
|
34
|
+
const logger = this.props.logger ?? this.context;
|
|
35
|
+
try {
|
|
36
|
+
const attributes = { "safesignal.source": SOURCE_BOUNDARY };
|
|
37
|
+
if (componentStack) {
|
|
38
|
+
attributes["safesignal.react.componentStack"] = componentStack;
|
|
39
|
+
}
|
|
40
|
+
logger?.error("React render error", attributes, error);
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
this.props.onError?.(error, { componentStack });
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
componentDidUpdate(prevProps) {
|
|
49
|
+
if (!this.state.caught) return;
|
|
50
|
+
if (resetKeysChanged(prevProps.resetKeys, this.props.resetKeys)) {
|
|
51
|
+
this.reset();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
reset() {
|
|
55
|
+
this.setState({ caught: false, error: void 0 });
|
|
56
|
+
}
|
|
57
|
+
render() {
|
|
58
|
+
if (!this.state.caught) return this.props.children;
|
|
59
|
+
const { fallback } = this.props;
|
|
60
|
+
if (typeof fallback === "function") {
|
|
61
|
+
return fallback(this.state.error, this.reset);
|
|
62
|
+
}
|
|
63
|
+
return fallback ?? null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
LogErrorBoundary.contextType = LoggerContext;
|
|
67
|
+
function useLogError(loggerOverride) {
|
|
68
|
+
const contextLogger = useContext(LoggerContext);
|
|
69
|
+
const logger = loggerOverride ?? contextLogger;
|
|
70
|
+
return useCallback(
|
|
71
|
+
(error, attributes) => {
|
|
72
|
+
try {
|
|
73
|
+
const attrs = {
|
|
74
|
+
"safesignal.source": SOURCE_HOOK,
|
|
75
|
+
...attributes ?? {}
|
|
76
|
+
};
|
|
77
|
+
logger?.error("Reported error", attrs, error);
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
[logger]
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { LogErrorBoundary, LoggerContext, LoggerProvider, useLogError };
|
|
86
|
+
//# sourceMappingURL=framework-react.mjs.map
|
|
87
|
+
//# sourceMappingURL=framework-react.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/framework-react/index.ts"],"names":[],"mappings":"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","file":"framework-react.mjs","sourcesContent":["/**\n * React error handling — the `./framework-react` subpath.\n *\n * The **no-globals, React-native counterpart** to `./capture`: a per-component\n * `<LogErrorBoundary>` plus a `useLogError()` hook that route React errors\n * through a consumer-provided `Logger`'s existing secure pipeline (sanitize →\n * URL-scrub → redact → guard → transport) by calling `logger.error(...)`. Where\n * `./capture` is a single host-level *global* install, this is explicit,\n * per-subtree, and side-effect-free — it patches nothing and attaches no global\n * listeners.\n *\n * Properties (see `specs/018-react-error-boundary/contracts/framework-react.md`):\n * - Fail-closed: emits via `logger.error`, so messages / stacks / component\n * stacks are redacted + sanitized (drop-on-failure) before any transport.\n * - Fail-safe: a logging (or `onError`) throw is swallowed; the fallback still\n * renders and nothing propagates to the page (Principle III). React's own\n * semantics keep it loop-free (a boundary does not catch errors thrown while\n * rendering its own fallback).\n * - No-globals: no `window.onerror`, no `addEventListener`, no monkey-patching,\n * no timers, no ambient reads (Principle VIII).\n * - Framework-neutral-preserving: `react` is an externalized **peer** import,\n * so the core entry and every other subpath stay React-free (Principle IV).\n *\n * The only intra-package `src/` import is **type-only** from `../api/types.js`;\n * the single runtime external is `react` (the consumer-provided peer). No\n * runtime state is shared with the core — the helpers operate solely through the\n * passed/contextual `Logger`.\n */\n\nimport {\n Component,\n createContext,\n createElement,\n useCallback,\n useContext,\n} from 'react';\nimport type { Context, ErrorInfo, ReactElement, ReactNode } from 'react';\nimport type { Attributes, Logger } from '../api/types.js';\n\n/** `safesignal.source` marker for boundary-caught render errors. */\nconst SOURCE_BOUNDARY = 'react-error-boundary';\n/** `safesignal.source` marker for errors reported via {@link useLogError}. */\nconst SOURCE_HOOK = 'react-use-log-error';\n\n/**\n * React context carrying the consumer's `Logger` for a subtree. Default\n * `undefined` — when no provider/override resolves a logger the helpers are a\n * safe no-op (they never mint a fallback logger, which would couple this bundle\n * to the core runtime). React-scoped, never a global registry.\n */\nexport const LoggerContext: Context<Logger | undefined> = createContext<\n Logger | undefined\n>(undefined);\n\n/** Props for {@link LoggerProvider}. */\nexport interface LoggerProviderProps {\n /** The consumer's configured `Logger`, shared with the subtree. */\n logger: Logger;\n children?: ReactNode;\n}\n\n/**\n * Supplies a `Logger` to {@link LogErrorBoundary} / {@link useLogError} within a\n * subtree. Pure context plumbing — no globals, no side effects.\n */\nexport function LoggerProvider(props: LoggerProviderProps): ReactElement {\n return createElement(\n LoggerContext.Provider,\n { value: props.logger },\n props.children,\n );\n}\n\n/** A node, or a render-prop given the caught error + a reset callback. */\ntype FallbackRender =\n | ReactNode\n | ((error: unknown, reset: () => void) => ReactNode);\n\n/** Props for {@link LogErrorBoundary}. */\nexport interface LogErrorBoundaryProps {\n children?: ReactNode;\n /** Explicit logger override; falls back to {@link LoggerContext}. */\n logger?: Logger;\n /** Rendered when an error is caught. Default: `null` (render nothing). */\n fallback?: FallbackRender;\n /** Optional consumer hook, invoked fail-safe AFTER logging. */\n onError?: (error: unknown, info: { componentStack: string }) => void;\n /** Changing any key (shallow compare) clears caught state + re-mounts. */\n resetKeys?: ReadonlyArray<unknown>;\n}\n\ninterface LogErrorBoundaryState {\n caught: boolean;\n error: unknown;\n}\n\n/** Shallow, order-sensitive comparison of two reset-key arrays. */\nfunction resetKeysChanged(\n prev: ReadonlyArray<unknown> | undefined,\n next: ReadonlyArray<unknown> | undefined,\n): boolean {\n if (prev === next) return false;\n if (prev === undefined || next === undefined) return true;\n if (prev.length !== next.length) return true;\n for (let i = 0; i < prev.length; i += 1) {\n if (!Object.is(prev[i], next[i])) return true;\n }\n return false;\n}\n\n/**\n * Catches descendant render / lifecycle / constructor errors, logs them through\n * the resolved `Logger` (with the React component stack), and renders a fallback\n * in place of the crashed subtree. Errors React cannot catch this way (event\n * handlers, async) belong to {@link useLogError}.\n */\nexport class LogErrorBoundary extends Component<\n LogErrorBoundaryProps,\n LogErrorBoundaryState\n> {\n static override contextType = LoggerContext;\n declare context: Logger | undefined;\n\n constructor(props: LogErrorBoundaryProps) {\n super(props);\n this.state = { caught: false, error: undefined };\n this.reset = this.reset.bind(this);\n }\n\n static getDerivedStateFromError(error: unknown): LogErrorBoundaryState {\n return { caught: true, error };\n }\n\n override componentDidCatch(error: unknown, info: ErrorInfo): void {\n const componentStack =\n typeof info.componentStack === 'string' ? info.componentStack : '';\n const logger = this.props.logger ?? this.context;\n try {\n const attributes: Attributes = { 'safesignal.source': SOURCE_BOUNDARY };\n if (componentStack) {\n attributes['safesignal.react.componentStack'] = componentStack;\n }\n logger?.error('React render error', attributes, error);\n } catch {\n // Fail-safe: a logging failure must never escalate the original crash.\n }\n try {\n this.props.onError?.(error, { componentStack });\n } catch {\n // Fail-safe: a consumer onError that throws must not break the fallback.\n }\n }\n\n override componentDidUpdate(prevProps: LogErrorBoundaryProps): void {\n if (!this.state.caught) return;\n if (resetKeysChanged(prevProps.resetKeys, this.props.resetKeys)) {\n this.reset();\n }\n }\n\n reset(): void {\n this.setState({ caught: false, error: undefined });\n }\n\n override render(): ReactNode {\n if (!this.state.caught) return this.props.children;\n const { fallback } = this.props;\n if (typeof fallback === 'function') {\n return fallback(this.state.error, this.reset);\n }\n return fallback ?? null;\n }\n}\n\n/**\n * Returns a **stable** callback that logs an error through the resolved logger\n * (`loggerOverride` ?? {@link LoggerContext}) as an `error`-level event — for the\n * errors a boundary cannot catch (event handlers, async/`Promise` callbacks,\n * effects). Fail-safe; a **safe no-op** when no logger resolves. The callback\n * identity is stable across re-renders for a fixed resolved logger (safe in\n * dependency arrays).\n */\nexport function useLogError(\n loggerOverride?: Logger,\n): (error: unknown, attributes?: Attributes) => void {\n const contextLogger = useContext(LoggerContext);\n const logger = loggerOverride ?? contextLogger;\n return useCallback(\n (error: unknown, attributes?: Attributes): void => {\n try {\n const attrs: Attributes = {\n 'safesignal.source': SOURCE_HOOK,\n ...(attributes ?? {}),\n };\n logger?.error('Reported error', attrs, error);\n } catch {\n // Fail-safe: reporting an error must never throw into the caller.\n }\n },\n [logger],\n );\n}\n"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var vue = require('vue');
|
|
4
|
+
|
|
5
|
+
// src/framework-vue/index.ts
|
|
6
|
+
var SOURCE_HANDLER = "vue-error-handler";
|
|
7
|
+
var SOURCE_HOOK = "vue-use-log-error";
|
|
8
|
+
var SOURCE_CAPTURED = "vue-error-captured";
|
|
9
|
+
var loggerKey = /* @__PURE__ */ Symbol("safesignal.logger");
|
|
10
|
+
function readName(obj) {
|
|
11
|
+
if (obj && typeof obj === "object") {
|
|
12
|
+
const name = obj.name;
|
|
13
|
+
if (typeof name === "string" && name.length > 0) return name;
|
|
14
|
+
}
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
function componentNameOf(instance) {
|
|
18
|
+
try {
|
|
19
|
+
if (!instance || typeof instance !== "object") return void 0;
|
|
20
|
+
const inst = instance;
|
|
21
|
+
return readName(inst.$options) ?? readName(inst.$?.type) ?? readName(inst.type);
|
|
22
|
+
} catch {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function emit(logger, source, message, error, extra, instance, info) {
|
|
27
|
+
if (!logger) return;
|
|
28
|
+
try {
|
|
29
|
+
const attributes = { "safesignal.source": source };
|
|
30
|
+
if (typeof info === "string" && info.length > 0) {
|
|
31
|
+
attributes["safesignal.vue.info"] = info;
|
|
32
|
+
}
|
|
33
|
+
const name = componentNameOf(instance);
|
|
34
|
+
if (name) attributes["safesignal.vue.componentName"] = name;
|
|
35
|
+
if (extra) {
|
|
36
|
+
for (const key of Object.keys(extra)) {
|
|
37
|
+
attributes[key] = extra[key];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
logger.error(message, attributes, error);
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function createErrorHandler(logger) {
|
|
45
|
+
return (err, instance, info) => {
|
|
46
|
+
emit(logger, SOURCE_HANDLER, "Vue error", err, void 0, instance, info);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
var safesignalErrorHandler = {
|
|
50
|
+
install(app, options) {
|
|
51
|
+
const { logger } = options;
|
|
52
|
+
app.config.errorHandler = createErrorHandler(logger);
|
|
53
|
+
app.provide(loggerKey, logger);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function useLogError(loggerOverride) {
|
|
57
|
+
const logger = loggerOverride ?? vue.inject(loggerKey);
|
|
58
|
+
return (error, attributes) => {
|
|
59
|
+
emit(logger, SOURCE_HOOK, "Reported error", error, attributes);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function useErrorCapture(options = {}) {
|
|
63
|
+
const logger = options.logger ?? vue.inject(loggerKey);
|
|
64
|
+
vue.onErrorCaptured((err, instance, info) => {
|
|
65
|
+
emit(
|
|
66
|
+
logger,
|
|
67
|
+
SOURCE_CAPTURED,
|
|
68
|
+
"Vue captured error",
|
|
69
|
+
err,
|
|
70
|
+
void 0,
|
|
71
|
+
instance,
|
|
72
|
+
info
|
|
73
|
+
);
|
|
74
|
+
try {
|
|
75
|
+
options.onError?.(err, info);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
return options.propagate ? void 0 : false;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
exports.createErrorHandler = createErrorHandler;
|
|
83
|
+
exports.loggerKey = loggerKey;
|
|
84
|
+
exports.safesignalErrorHandler = safesignalErrorHandler;
|
|
85
|
+
exports.useErrorCapture = useErrorCapture;
|
|
86
|
+
exports.useLogError = useLogError;
|
|
87
|
+
//# sourceMappingURL=framework-vue.cjs.map
|
|
88
|
+
//# sourceMappingURL=framework-vue.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/framework-vue/index.ts"],"names":["inject","onErrorCaptured"],"mappings":"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","file":"framework-vue.cjs","sourcesContent":["/**\n * Vue error handling — the `./framework-vue` subpath.\n *\n * The **no-globals, Vue-native counterpart** to `./capture` (and the sibling of\n * `./framework-react`): an `app.config.errorHandler` adapter plus composables\n * that route Vue component-tree errors through a consumer-provided `Logger`'s\n * existing secure pipeline (sanitize → URL-scrub → redact → guard → transport)\n * by calling `logger.error(...)`. Where `./capture` is a single host-level\n * *global* install, this is explicit, per-app / per-subtree, and\n * side-effect-free — it patches nothing and attaches no global listeners.\n *\n * Surface:\n * - `createErrorHandler(logger)` — a side-effect-free factory returning a\n * handler for `app.config.errorHandler`.\n * - `safesignalErrorHandler` — a thin Vue plugin that wires the handler AND\n * `app.provide(loggerKey, logger)`.\n * - `loggerKey` — the `InjectionKey<Logger>` by which the plugin provides, and\n * the composables inject, the logger (the Vue parallel of React's context).\n * - `useLogError(loggerOverride?)` — a stable manual-report callback for errors\n * Vue's handler cannot catch (async/try-catch, native listeners).\n * - `useErrorCapture(options?)` — a subtree boundary wrapping `onErrorCaptured`\n * (parallel of React's `<LogErrorBoundary>`) that logs descendant errors and,\n * by default, stops propagation so the app-level handler does not double-log.\n *\n * Properties (see `specs/020-vue-error-handler/contracts/framework-vue.md`):\n * - Fail-closed: emits via `logger.error`, so messages / stacks / the Vue info\n * string are redacted + sanitized (drop-on-failure) before any transport.\n * - Fail-safe: a logging (or `onError`) throw is swallowed; the original error\n * is never escalated and the app keeps running (Principle III).\n * - No-globals: no `window.onerror`, no `addEventListener`, no monkey-patching,\n * no timers, no ambient reads (Principle VIII). Errors flow only through the\n * resolved logger and Vue's own per-app / per-component error hooks.\n * - Framework-neutral-preserving: `vue` is an externalized **peer** import, so\n * the core entry and every other subpath stay Vue-free (Principle IV).\n *\n * The only intra-package `src/` import is **type-only** from `../api/types.js`;\n * the single runtime external is `vue` (the consumer-provided peer). No runtime\n * state is shared with the core — the helpers operate solely through the\n * passed/injected `Logger`.\n */\n\nimport {\n type App,\n type InjectionKey,\n inject,\n onErrorCaptured,\n type Plugin,\n} from 'vue';\nimport type { Attributes, Logger } from '../api/types.js';\n\n/** `safesignal.source` marker for app-level (`app.config.errorHandler`) errors. */\nconst SOURCE_HANDLER = 'vue-error-handler';\n/** `safesignal.source` marker for errors reported via {@link useLogError}. */\nconst SOURCE_HOOK = 'vue-use-log-error';\n/** `safesignal.source` marker for subtree errors captured by {@link useErrorCapture}. */\nconst SOURCE_CAPTURED = 'vue-error-captured';\n\n/**\n * Vue injection key carrying the consumer's `Logger` for an app/subtree. When no\n * provider/override resolves a logger the helpers are a safe no-op (they never\n * mint a fallback logger, which would couple this bundle to the core runtime).\n * Vue-scoped, never a global registry.\n */\nexport const loggerKey: InjectionKey<Logger> = Symbol('safesignal.logger');\n\n/** The Vue app-level error-handler signature (matches `app.config.errorHandler`). */\nexport type VueErrorHandler = (\n err: unknown,\n instance: unknown,\n info: string,\n) => void;\n\n/** Read a non-empty string `name` from an unknown object, else `undefined`. */\nfunction readName(obj: unknown): string | undefined {\n if (obj && typeof obj === 'object') {\n const name = (obj as { name?: unknown }).name;\n if (typeof name === 'string' && name.length > 0) return name;\n }\n return undefined;\n}\n\n/**\n * Best-effort component name from a Vue instance (public or internal shape).\n * Wrapped + tolerant: returns `undefined` rather than throwing when the instance\n * is null, exotic, or nameless. Never reads props/state.\n */\nfunction componentNameOf(instance: unknown): string | undefined {\n try {\n if (!instance || typeof instance !== 'object') return undefined;\n const inst = instance as {\n $options?: unknown;\n $?: { type?: unknown };\n type?: unknown;\n };\n return (\n readName(inst.$options) ?? readName(inst.$?.type) ?? readName(inst.type)\n );\n } catch {\n return undefined;\n }\n}\n\n/**\n * Emit one `error`-level event through the resolved logger. Safe no-op when no\n * logger resolves; fail-safe — any throw in the logging path is swallowed so the\n * original error is never escalated.\n */\nfunction emit(\n logger: Logger | undefined,\n source: string,\n message: string,\n error: unknown,\n extra?: Attributes,\n instance?: unknown,\n info?: string,\n): void {\n if (!logger) return;\n try {\n const attributes: Attributes = { 'safesignal.source': source };\n if (typeof info === 'string' && info.length > 0) {\n attributes['safesignal.vue.info'] = info;\n }\n const name = componentNameOf(instance);\n if (name) attributes['safesignal.vue.componentName'] = name;\n if (extra) {\n for (const key of Object.keys(extra)) {\n attributes[key] = extra[key] as Attributes[string];\n }\n }\n logger.error(message, attributes, error);\n } catch {\n // Fail-safe: a logging failure must never escalate the original error.\n }\n}\n\n/**\n * Side-effect-free factory: given a `Logger`, returns a handler suitable for\n * `app.config.errorHandler`. Each invocation emits one `error`-level event\n * (`safesignal.source: 'vue-error-handler'`) via that logger; it attaches\n * nothing at creation and never throws.\n */\nexport function createErrorHandler(logger: Logger): VueErrorHandler {\n return (err, instance, info) => {\n emit(logger, SOURCE_HANDLER, 'Vue error', err, undefined, instance, info);\n };\n}\n\n/** Options for the {@link safesignalErrorHandler} plugin. */\nexport interface SafesignalErrorHandlerOptions {\n /** The consumer's configured `Logger`, used by the app handler and provided to descendants. */\n logger: Logger;\n}\n\n/**\n * Vue plugin: `app.use(safesignalErrorHandler, { logger })` sets\n * `app.config.errorHandler = createErrorHandler(logger)` **and**\n * `app.provide(loggerKey, logger)`. No other side effects (no globals, no\n * timers, no listeners).\n */\nexport const safesignalErrorHandler: Plugin<SafesignalErrorHandlerOptions> = {\n install(app: App, options: SafesignalErrorHandlerOptions): void {\n const { logger } = options;\n app.config.errorHandler = createErrorHandler(logger);\n app.provide(loggerKey, logger);\n },\n};\n\n/**\n * Returns a callback that logs an error through the resolved logger\n * (`loggerOverride` ?? injected {@link loggerKey}) as an `error`-level event —\n * for the errors a framework handler cannot catch (async/`Promise` callbacks,\n * `try/catch`, native `addEventListener`). Fail-safe; a **safe no-op** when no\n * logger resolves. Call this in `setup()`; the callback identity is stable for\n * the component's lifetime (Vue runs `setup` once).\n */\nexport function useLogError(\n loggerOverride?: Logger,\n): (error: unknown, attributes?: Attributes) => void {\n const logger = loggerOverride ?? inject(loggerKey);\n return (error, attributes) => {\n emit(logger, SOURCE_HOOK, 'Reported error', error, attributes);\n };\n}\n\n/** Options for {@link useErrorCapture}. */\nexport interface UseErrorCaptureOptions {\n /** Explicit logger override; falls back to the injected {@link loggerKey}. */\n logger?: Logger;\n /** Optional consumer hook, invoked fail-safe AFTER logging, with Vue's info string. */\n onError?: (error: unknown, info: string) => void;\n /** Keep propagating to ancestor/app handlers after logging. Default: false (stop). */\n propagate?: boolean;\n}\n\n/**\n * Subtree boundary: registers `onErrorCaptured` in the calling component so a\n * descendant error is logged once via the resolved logger\n * (`safesignal.source: 'vue-error-captured'`). By **default it stops\n * propagation** (returns `false`) so the app-level handler does not also log it;\n * pass `{ propagate: true }` to keep it bubbling. The optional `onError` callback\n * is invoked fail-safe after logging. A safe no-op when no logger resolves.\n */\nexport function useErrorCapture(options: UseErrorCaptureOptions = {}): void {\n const logger = options.logger ?? inject(loggerKey);\n onErrorCaptured((err, instance, info) => {\n emit(\n logger,\n SOURCE_CAPTURED,\n 'Vue captured error',\n err,\n undefined,\n instance,\n info,\n );\n try {\n options.onError?.(err, info);\n } catch {\n // Fail-safe: a consumer onError that throws must not disrupt the app.\n }\n return options.propagate ? undefined : false;\n });\n}\n"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { InjectionKey, Plugin } from 'vue';
|
|
2
|
+
import { f as Logger, b as Attributes } from './types-CZtSjgq5.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vue error handling — the `./framework-vue` subpath.
|
|
6
|
+
*
|
|
7
|
+
* The **no-globals, Vue-native counterpart** to `./capture` (and the sibling of
|
|
8
|
+
* `./framework-react`): an `app.config.errorHandler` adapter plus composables
|
|
9
|
+
* that route Vue component-tree errors through a consumer-provided `Logger`'s
|
|
10
|
+
* existing secure pipeline (sanitize → URL-scrub → redact → guard → transport)
|
|
11
|
+
* by calling `logger.error(...)`. Where `./capture` is a single host-level
|
|
12
|
+
* *global* install, this is explicit, per-app / per-subtree, and
|
|
13
|
+
* side-effect-free — it patches nothing and attaches no global listeners.
|
|
14
|
+
*
|
|
15
|
+
* Surface:
|
|
16
|
+
* - `createErrorHandler(logger)` — a side-effect-free factory returning a
|
|
17
|
+
* handler for `app.config.errorHandler`.
|
|
18
|
+
* - `safesignalErrorHandler` — a thin Vue plugin that wires the handler AND
|
|
19
|
+
* `app.provide(loggerKey, logger)`.
|
|
20
|
+
* - `loggerKey` — the `InjectionKey<Logger>` by which the plugin provides, and
|
|
21
|
+
* the composables inject, the logger (the Vue parallel of React's context).
|
|
22
|
+
* - `useLogError(loggerOverride?)` — a stable manual-report callback for errors
|
|
23
|
+
* Vue's handler cannot catch (async/try-catch, native listeners).
|
|
24
|
+
* - `useErrorCapture(options?)` — a subtree boundary wrapping `onErrorCaptured`
|
|
25
|
+
* (parallel of React's `<LogErrorBoundary>`) that logs descendant errors and,
|
|
26
|
+
* by default, stops propagation so the app-level handler does not double-log.
|
|
27
|
+
*
|
|
28
|
+
* Properties (see `specs/020-vue-error-handler/contracts/framework-vue.md`):
|
|
29
|
+
* - Fail-closed: emits via `logger.error`, so messages / stacks / the Vue info
|
|
30
|
+
* string are redacted + sanitized (drop-on-failure) before any transport.
|
|
31
|
+
* - Fail-safe: a logging (or `onError`) throw is swallowed; the original error
|
|
32
|
+
* is never escalated and the app keeps running (Principle III).
|
|
33
|
+
* - No-globals: no `window.onerror`, no `addEventListener`, no monkey-patching,
|
|
34
|
+
* no timers, no ambient reads (Principle VIII). Errors flow only through the
|
|
35
|
+
* resolved logger and Vue's own per-app / per-component error hooks.
|
|
36
|
+
* - Framework-neutral-preserving: `vue` is an externalized **peer** import, so
|
|
37
|
+
* the core entry and every other subpath stay Vue-free (Principle IV).
|
|
38
|
+
*
|
|
39
|
+
* The only intra-package `src/` import is **type-only** from `../api/types.js`;
|
|
40
|
+
* the single runtime external is `vue` (the consumer-provided peer). No runtime
|
|
41
|
+
* state is shared with the core — the helpers operate solely through the
|
|
42
|
+
* passed/injected `Logger`.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Vue injection key carrying the consumer's `Logger` for an app/subtree. When no
|
|
47
|
+
* provider/override resolves a logger the helpers are a safe no-op (they never
|
|
48
|
+
* mint a fallback logger, which would couple this bundle to the core runtime).
|
|
49
|
+
* Vue-scoped, never a global registry.
|
|
50
|
+
*/
|
|
51
|
+
declare const loggerKey: InjectionKey<Logger>;
|
|
52
|
+
/** The Vue app-level error-handler signature (matches `app.config.errorHandler`). */
|
|
53
|
+
type VueErrorHandler = (err: unknown, instance: unknown, info: string) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Side-effect-free factory: given a `Logger`, returns a handler suitable for
|
|
56
|
+
* `app.config.errorHandler`. Each invocation emits one `error`-level event
|
|
57
|
+
* (`safesignal.source: 'vue-error-handler'`) via that logger; it attaches
|
|
58
|
+
* nothing at creation and never throws.
|
|
59
|
+
*/
|
|
60
|
+
declare function createErrorHandler(logger: Logger): VueErrorHandler;
|
|
61
|
+
/** Options for the {@link safesignalErrorHandler} plugin. */
|
|
62
|
+
interface SafesignalErrorHandlerOptions {
|
|
63
|
+
/** The consumer's configured `Logger`, used by the app handler and provided to descendants. */
|
|
64
|
+
logger: Logger;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Vue plugin: `app.use(safesignalErrorHandler, { logger })` sets
|
|
68
|
+
* `app.config.errorHandler = createErrorHandler(logger)` **and**
|
|
69
|
+
* `app.provide(loggerKey, logger)`. No other side effects (no globals, no
|
|
70
|
+
* timers, no listeners).
|
|
71
|
+
*/
|
|
72
|
+
declare const safesignalErrorHandler: Plugin<SafesignalErrorHandlerOptions>;
|
|
73
|
+
/**
|
|
74
|
+
* Returns a callback that logs an error through the resolved logger
|
|
75
|
+
* (`loggerOverride` ?? injected {@link loggerKey}) as an `error`-level event —
|
|
76
|
+
* for the errors a framework handler cannot catch (async/`Promise` callbacks,
|
|
77
|
+
* `try/catch`, native `addEventListener`). Fail-safe; a **safe no-op** when no
|
|
78
|
+
* logger resolves. Call this in `setup()`; the callback identity is stable for
|
|
79
|
+
* the component's lifetime (Vue runs `setup` once).
|
|
80
|
+
*/
|
|
81
|
+
declare function useLogError(loggerOverride?: Logger): (error: unknown, attributes?: Attributes) => void;
|
|
82
|
+
/** Options for {@link useErrorCapture}. */
|
|
83
|
+
interface UseErrorCaptureOptions {
|
|
84
|
+
/** Explicit logger override; falls back to the injected {@link loggerKey}. */
|
|
85
|
+
logger?: Logger;
|
|
86
|
+
/** Optional consumer hook, invoked fail-safe AFTER logging, with Vue's info string. */
|
|
87
|
+
onError?: (error: unknown, info: string) => void;
|
|
88
|
+
/** Keep propagating to ancestor/app handlers after logging. Default: false (stop). */
|
|
89
|
+
propagate?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Subtree boundary: registers `onErrorCaptured` in the calling component so a
|
|
93
|
+
* descendant error is logged once via the resolved logger
|
|
94
|
+
* (`safesignal.source: 'vue-error-captured'`). By **default it stops
|
|
95
|
+
* propagation** (returns `false`) so the app-level handler does not also log it;
|
|
96
|
+
* pass `{ propagate: true }` to keep it bubbling. The optional `onError` callback
|
|
97
|
+
* is invoked fail-safe after logging. A safe no-op when no logger resolves.
|
|
98
|
+
*/
|
|
99
|
+
declare function useErrorCapture(options?: UseErrorCaptureOptions): void;
|
|
100
|
+
|
|
101
|
+
export { type SafesignalErrorHandlerOptions, type UseErrorCaptureOptions, type VueErrorHandler, createErrorHandler, loggerKey, safesignalErrorHandler, useErrorCapture, useLogError };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { InjectionKey, Plugin } from 'vue';
|
|
2
|
+
import { f as Logger, b as Attributes } from './types-CZtSjgq5.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vue error handling — the `./framework-vue` subpath.
|
|
6
|
+
*
|
|
7
|
+
* The **no-globals, Vue-native counterpart** to `./capture` (and the sibling of
|
|
8
|
+
* `./framework-react`): an `app.config.errorHandler` adapter plus composables
|
|
9
|
+
* that route Vue component-tree errors through a consumer-provided `Logger`'s
|
|
10
|
+
* existing secure pipeline (sanitize → URL-scrub → redact → guard → transport)
|
|
11
|
+
* by calling `logger.error(...)`. Where `./capture` is a single host-level
|
|
12
|
+
* *global* install, this is explicit, per-app / per-subtree, and
|
|
13
|
+
* side-effect-free — it patches nothing and attaches no global listeners.
|
|
14
|
+
*
|
|
15
|
+
* Surface:
|
|
16
|
+
* - `createErrorHandler(logger)` — a side-effect-free factory returning a
|
|
17
|
+
* handler for `app.config.errorHandler`.
|
|
18
|
+
* - `safesignalErrorHandler` — a thin Vue plugin that wires the handler AND
|
|
19
|
+
* `app.provide(loggerKey, logger)`.
|
|
20
|
+
* - `loggerKey` — the `InjectionKey<Logger>` by which the plugin provides, and
|
|
21
|
+
* the composables inject, the logger (the Vue parallel of React's context).
|
|
22
|
+
* - `useLogError(loggerOverride?)` — a stable manual-report callback for errors
|
|
23
|
+
* Vue's handler cannot catch (async/try-catch, native listeners).
|
|
24
|
+
* - `useErrorCapture(options?)` — a subtree boundary wrapping `onErrorCaptured`
|
|
25
|
+
* (parallel of React's `<LogErrorBoundary>`) that logs descendant errors and,
|
|
26
|
+
* by default, stops propagation so the app-level handler does not double-log.
|
|
27
|
+
*
|
|
28
|
+
* Properties (see `specs/020-vue-error-handler/contracts/framework-vue.md`):
|
|
29
|
+
* - Fail-closed: emits via `logger.error`, so messages / stacks / the Vue info
|
|
30
|
+
* string are redacted + sanitized (drop-on-failure) before any transport.
|
|
31
|
+
* - Fail-safe: a logging (or `onError`) throw is swallowed; the original error
|
|
32
|
+
* is never escalated and the app keeps running (Principle III).
|
|
33
|
+
* - No-globals: no `window.onerror`, no `addEventListener`, no monkey-patching,
|
|
34
|
+
* no timers, no ambient reads (Principle VIII). Errors flow only through the
|
|
35
|
+
* resolved logger and Vue's own per-app / per-component error hooks.
|
|
36
|
+
* - Framework-neutral-preserving: `vue` is an externalized **peer** import, so
|
|
37
|
+
* the core entry and every other subpath stay Vue-free (Principle IV).
|
|
38
|
+
*
|
|
39
|
+
* The only intra-package `src/` import is **type-only** from `../api/types.js`;
|
|
40
|
+
* the single runtime external is `vue` (the consumer-provided peer). No runtime
|
|
41
|
+
* state is shared with the core — the helpers operate solely through the
|
|
42
|
+
* passed/injected `Logger`.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Vue injection key carrying the consumer's `Logger` for an app/subtree. When no
|
|
47
|
+
* provider/override resolves a logger the helpers are a safe no-op (they never
|
|
48
|
+
* mint a fallback logger, which would couple this bundle to the core runtime).
|
|
49
|
+
* Vue-scoped, never a global registry.
|
|
50
|
+
*/
|
|
51
|
+
declare const loggerKey: InjectionKey<Logger>;
|
|
52
|
+
/** The Vue app-level error-handler signature (matches `app.config.errorHandler`). */
|
|
53
|
+
type VueErrorHandler = (err: unknown, instance: unknown, info: string) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Side-effect-free factory: given a `Logger`, returns a handler suitable for
|
|
56
|
+
* `app.config.errorHandler`. Each invocation emits one `error`-level event
|
|
57
|
+
* (`safesignal.source: 'vue-error-handler'`) via that logger; it attaches
|
|
58
|
+
* nothing at creation and never throws.
|
|
59
|
+
*/
|
|
60
|
+
declare function createErrorHandler(logger: Logger): VueErrorHandler;
|
|
61
|
+
/** Options for the {@link safesignalErrorHandler} plugin. */
|
|
62
|
+
interface SafesignalErrorHandlerOptions {
|
|
63
|
+
/** The consumer's configured `Logger`, used by the app handler and provided to descendants. */
|
|
64
|
+
logger: Logger;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Vue plugin: `app.use(safesignalErrorHandler, { logger })` sets
|
|
68
|
+
* `app.config.errorHandler = createErrorHandler(logger)` **and**
|
|
69
|
+
* `app.provide(loggerKey, logger)`. No other side effects (no globals, no
|
|
70
|
+
* timers, no listeners).
|
|
71
|
+
*/
|
|
72
|
+
declare const safesignalErrorHandler: Plugin<SafesignalErrorHandlerOptions>;
|
|
73
|
+
/**
|
|
74
|
+
* Returns a callback that logs an error through the resolved logger
|
|
75
|
+
* (`loggerOverride` ?? injected {@link loggerKey}) as an `error`-level event —
|
|
76
|
+
* for the errors a framework handler cannot catch (async/`Promise` callbacks,
|
|
77
|
+
* `try/catch`, native `addEventListener`). Fail-safe; a **safe no-op** when no
|
|
78
|
+
* logger resolves. Call this in `setup()`; the callback identity is stable for
|
|
79
|
+
* the component's lifetime (Vue runs `setup` once).
|
|
80
|
+
*/
|
|
81
|
+
declare function useLogError(loggerOverride?: Logger): (error: unknown, attributes?: Attributes) => void;
|
|
82
|
+
/** Options for {@link useErrorCapture}. */
|
|
83
|
+
interface UseErrorCaptureOptions {
|
|
84
|
+
/** Explicit logger override; falls back to the injected {@link loggerKey}. */
|
|
85
|
+
logger?: Logger;
|
|
86
|
+
/** Optional consumer hook, invoked fail-safe AFTER logging, with Vue's info string. */
|
|
87
|
+
onError?: (error: unknown, info: string) => void;
|
|
88
|
+
/** Keep propagating to ancestor/app handlers after logging. Default: false (stop). */
|
|
89
|
+
propagate?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Subtree boundary: registers `onErrorCaptured` in the calling component so a
|
|
93
|
+
* descendant error is logged once via the resolved logger
|
|
94
|
+
* (`safesignal.source: 'vue-error-captured'`). By **default it stops
|
|
95
|
+
* propagation** (returns `false`) so the app-level handler does not also log it;
|
|
96
|
+
* pass `{ propagate: true }` to keep it bubbling. The optional `onError` callback
|
|
97
|
+
* is invoked fail-safe after logging. A safe no-op when no logger resolves.
|
|
98
|
+
*/
|
|
99
|
+
declare function useErrorCapture(options?: UseErrorCaptureOptions): void;
|
|
100
|
+
|
|
101
|
+
export { type SafesignalErrorHandlerOptions, type UseErrorCaptureOptions, type VueErrorHandler, createErrorHandler, loggerKey, safesignalErrorHandler, useErrorCapture, useLogError };
|