@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
package/dist/capture.mjs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/capture/index.ts
|
|
2
|
+
var SOURCE = "global-error-capture";
|
|
3
|
+
function noop() {
|
|
4
|
+
}
|
|
5
|
+
function safeNotify(hook, cause) {
|
|
6
|
+
if (!hook) return;
|
|
7
|
+
try {
|
|
8
|
+
hook(cause instanceof Error ? cause : new Error(String(cause)));
|
|
9
|
+
} catch {
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function installGlobalErrorCapture(logger, options = {}) {
|
|
13
|
+
const target = options.target ?? globalThis;
|
|
14
|
+
const canListen = typeof target.addEventListener === "function" && typeof target.removeEventListener === "function";
|
|
15
|
+
if (!canListen) return noop;
|
|
16
|
+
let disposed = false;
|
|
17
|
+
let inFlight = false;
|
|
18
|
+
const emit = (message, errorType, errorValue, extra) => {
|
|
19
|
+
if (inFlight) return;
|
|
20
|
+
inFlight = true;
|
|
21
|
+
try {
|
|
22
|
+
const attributes = {
|
|
23
|
+
"safesignal.source": SOURCE,
|
|
24
|
+
"safesignal.errorType": errorType,
|
|
25
|
+
...extra ?? {}
|
|
26
|
+
};
|
|
27
|
+
logger.error(message, attributes, errorValue);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
safeNotify(options.onInternalError, err);
|
|
30
|
+
} finally {
|
|
31
|
+
inFlight = false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const handleError = (event) => {
|
|
35
|
+
try {
|
|
36
|
+
const e = event;
|
|
37
|
+
const hasError = e.error !== void 0 && e.error !== null;
|
|
38
|
+
const extra = {};
|
|
39
|
+
if (!hasError) {
|
|
40
|
+
if (typeof e.filename === "string") extra.filename = e.filename;
|
|
41
|
+
if (typeof e.lineno === "number") extra.lineno = e.lineno;
|
|
42
|
+
if (typeof e.colno === "number") extra.colno = e.colno;
|
|
43
|
+
}
|
|
44
|
+
const errorValue = hasError ? e.error : typeof e.message === "string" ? e.message : "Uncaught exception";
|
|
45
|
+
emit(
|
|
46
|
+
"Uncaught exception",
|
|
47
|
+
"uncaught-exception",
|
|
48
|
+
errorValue,
|
|
49
|
+
Object.keys(extra).length > 0 ? extra : void 0
|
|
50
|
+
);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
safeNotify(options.onInternalError, err);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const handleRejection = (event) => {
|
|
56
|
+
try {
|
|
57
|
+
const e = event;
|
|
58
|
+
emit("Unhandled promise rejection", "unhandled-rejection", e.reason);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
safeNotify(options.onInternalError, err);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
target.addEventListener("error", handleError);
|
|
64
|
+
target.addEventListener("unhandledrejection", handleRejection);
|
|
65
|
+
return () => {
|
|
66
|
+
if (disposed) return;
|
|
67
|
+
disposed = true;
|
|
68
|
+
target.removeEventListener("error", handleError);
|
|
69
|
+
target.removeEventListener("unhandledrejection", handleRejection);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { installGlobalErrorCapture };
|
|
74
|
+
//# sourceMappingURL=capture.mjs.map
|
|
75
|
+
//# sourceMappingURL=capture.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/capture/index.ts"],"names":[],"mappings":"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","file":"capture.mjs","sourcesContent":["/**\n * Opt-in global error capture — the `./capture` subpath.\n *\n * A **host-level**, opt-in install that routes **uncaught exceptions** and\n * **unhandled promise rejections** through the host `Logger`'s existing secure\n * pipeline (sanitize → URL-scrub → redact → guard → transport). It is the single\n * explicit host-installed runtime-level global handler Principle VIII v1.5.0\n * sanctions — never a side effect of `createLogger()`, never installed by a\n * federated module.\n *\n * Properties (see `specs/013-global-error-capture/contracts/capture-api.md`):\n * - Fail-closed: emits via `logger.error`, so stacks/messages are redacted +\n * sanitized (drop-on-failure) before any transport sees them.\n * - Fail-safe: never throws/rejects into the page; internal failures are\n * swallowed (routed to `options.onInternalError`).\n * - Additive: attaches via `addEventListener` — never assigns `window.onerror`\n * and never calls `preventDefault()`, so existing handlers keep firing.\n * - Loop-safe: a re-entrancy guard stops an error raised during emit from\n * re-capturing.\n * - Errors only: attaches ONLY `error` + `unhandledrejection` (not RUM).\n *\n * The only `src/` import is **type-only** from `../api/types.js`, so this bundle\n * shares no runtime state with the core (it operates through the passed\n * `Logger`, which closes over the host's configured runtime).\n */\n\nimport type { Attributes, Logger } from '../api/types.js';\n\n/** Options for {@link installGlobalErrorCapture}. */\nexport interface GlobalErrorCaptureOptions {\n /** Where to attach listeners. Default: `globalThis`. */\n target?: EventTarget;\n /**\n * Diagnostics hook for the capturer's OWN failures (event-build/dispatch\n * throw). Invoked fail-safe — its own throw is swallowed. Distinct from the\n * runtime's `onInternalError`.\n */\n onInternalError?: (err: Error) => void;\n}\n\n/** Removes the installed listeners and stops capture. Idempotent. */\nexport type GlobalErrorCaptureDisposer = () => void;\n\nconst SOURCE = 'global-error-capture';\n\nfunction noop(): void {\n /* no-op disposer */\n}\n\nfunction safeNotify(\n hook: ((err: Error) => void) | undefined,\n cause: unknown,\n): void {\n if (!hook) return;\n try {\n hook(cause instanceof Error ? cause : new Error(String(cause)));\n } catch {\n // A diagnostics hook that itself throws must not break capture.\n }\n}\n\n/**\n * Install host-level capture of uncaught exceptions + unhandled promise\n * rejections, routed through `logger`'s configured pipeline. Returns a disposer.\n * Opt-in, host-owned; never a side effect of `createLogger()`. Never throws.\n */\nexport function installGlobalErrorCapture(\n logger: Logger,\n options: GlobalErrorCaptureOptions = {},\n): GlobalErrorCaptureDisposer {\n const target: EventTarget =\n options.target ?? (globalThis as unknown as EventTarget);\n\n // Safe no-op where the target cannot register listeners (SSR / worker).\n const canListen =\n typeof (target as { addEventListener?: unknown }).addEventListener ===\n 'function' &&\n typeof (target as { removeEventListener?: unknown }).removeEventListener ===\n 'function';\n if (!canListen) return noop;\n\n let disposed = false;\n let inFlight = false;\n\n const emit = (\n message: string,\n errorType: 'uncaught-exception' | 'unhandled-rejection',\n errorValue: unknown,\n extra?: Attributes,\n ): void => {\n if (inFlight) return; // loop-safety: drop a capture raised during emit.\n inFlight = true;\n try {\n const attributes: Attributes = {\n 'safesignal.source': SOURCE,\n 'safesignal.errorType': errorType,\n ...(extra ?? {}),\n };\n logger.error(message, attributes, errorValue);\n } catch (err) {\n safeNotify(options.onInternalError, err);\n } finally {\n inFlight = false;\n }\n };\n\n const handleError = (event: Event): void => {\n try {\n const e = event as ErrorEvent;\n const hasError = e.error !== undefined && e.error !== null;\n const extra: Attributes = {};\n if (!hasError) {\n // Cross-origin \"Script error.\" cases carry no error object.\n if (typeof e.filename === 'string') extra.filename = e.filename;\n if (typeof e.lineno === 'number') extra.lineno = e.lineno;\n if (typeof e.colno === 'number') extra.colno = e.colno;\n }\n const errorValue = hasError\n ? e.error\n : typeof e.message === 'string'\n ? e.message\n : 'Uncaught exception';\n emit(\n 'Uncaught exception',\n 'uncaught-exception',\n errorValue,\n Object.keys(extra).length > 0 ? extra : undefined,\n );\n } catch (err) {\n safeNotify(options.onInternalError, err);\n }\n };\n\n const handleRejection = (event: Event): void => {\n try {\n const e = event as PromiseRejectionEvent;\n emit('Unhandled promise rejection', 'unhandled-rejection', e.reason);\n } catch (err) {\n safeNotify(options.onInternalError, err);\n }\n };\n\n target.addEventListener('error', handleError);\n target.addEventListener('unhandledrejection', handleRejection);\n\n return (): void => {\n if (disposed) return;\n disposed = true;\n target.removeEventListener('error', handleError);\n target.removeEventListener('unhandledrejection', handleRejection);\n };\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/dev-console/index.ts
|
|
4
|
+
var LEVEL_STYLE = {
|
|
5
|
+
debug: { icon: "\u{1F41B}", color: "#6b7280" },
|
|
6
|
+
info: { icon: "\u2139\uFE0F", color: "#2563eb" },
|
|
7
|
+
warn: { icon: "\u26A0\uFE0F", color: "#d97706" },
|
|
8
|
+
error: { icon: "\u26D4", color: "#dc2626" }
|
|
9
|
+
};
|
|
10
|
+
var DIM_STYLE = "color:#9ca3af";
|
|
11
|
+
function resolveConsoleMethod(level) {
|
|
12
|
+
const slot = console[level];
|
|
13
|
+
if (typeof slot === "function") {
|
|
14
|
+
return slot.bind(console);
|
|
15
|
+
}
|
|
16
|
+
return console.log.bind(console);
|
|
17
|
+
}
|
|
18
|
+
function richConsoleAvailable() {
|
|
19
|
+
const c = console;
|
|
20
|
+
return typeof c.groupCollapsed === "function" && typeof c.groupEnd === "function";
|
|
21
|
+
}
|
|
22
|
+
function structuredFallback(event) {
|
|
23
|
+
resolveConsoleMethod(event.level)(event.message, event);
|
|
24
|
+
}
|
|
25
|
+
function contextSummary(event) {
|
|
26
|
+
const app = event.context.application?.name ?? "\u2014";
|
|
27
|
+
const mod = event.context.module?.name ?? "\u2014";
|
|
28
|
+
const env = event.context.environment ?? "\u2014";
|
|
29
|
+
return `${app} \xB7 ${mod} \xB7 ${env}`;
|
|
30
|
+
}
|
|
31
|
+
function traceLink(event, traceUrl) {
|
|
32
|
+
const trace = event.context.trace;
|
|
33
|
+
if (!trace) return void 0;
|
|
34
|
+
const ids = { traceId: trace.traceId, spanId: trace.spanId };
|
|
35
|
+
if (traceUrl) {
|
|
36
|
+
try {
|
|
37
|
+
return traceUrl(ids);
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return `${ids.traceId}/${ids.spanId}`;
|
|
42
|
+
}
|
|
43
|
+
function renderGroup(event, options) {
|
|
44
|
+
const useColors = options.colors ?? true;
|
|
45
|
+
const { icon, color } = LEVEL_STYLE[event.level];
|
|
46
|
+
const level = event.level.toUpperCase();
|
|
47
|
+
const summary = contextSummary(event);
|
|
48
|
+
if (useColors) {
|
|
49
|
+
console.groupCollapsed(
|
|
50
|
+
`%c${icon} ${level}%c ${event.message}%c ${summary}`,
|
|
51
|
+
`color:${color};font-weight:bold`,
|
|
52
|
+
"",
|
|
53
|
+
DIM_STYLE
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
console.groupCollapsed(`${icon} ${level} ${event.message} ${summary}`);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
if (Object.keys(event.attributes).length > 0) {
|
|
60
|
+
console.log(event.attributes);
|
|
61
|
+
}
|
|
62
|
+
if (event.error) {
|
|
63
|
+
console.log(`${event.error.name}: ${event.error.message}`);
|
|
64
|
+
if (event.error.stack) console.log(event.error.stack);
|
|
65
|
+
}
|
|
66
|
+
const link = traceLink(event, options.traceUrl);
|
|
67
|
+
if (link !== void 0) {
|
|
68
|
+
console.log(`\u21B3 trace ${link}`);
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
console.groupEnd();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
var DevConsoleTransport = (options = {}) => ({
|
|
75
|
+
name: options.name ?? "dev-console",
|
|
76
|
+
send(event) {
|
|
77
|
+
try {
|
|
78
|
+
if (event.context.environment !== "development" || !richConsoleAvailable()) {
|
|
79
|
+
structuredFallback(event);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
renderGroup(event, options);
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
exports.DevConsoleTransport = DevConsoleTransport;
|
|
89
|
+
//# sourceMappingURL=dev-console.cjs.map
|
|
90
|
+
//# sourceMappingURL=dev-console.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dev-console/index.ts"],"names":[],"mappings":"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","file":"dev-console.cjs","sourcesContent":["/**\n * Developer-friendly dev-mode console rendering — the `./dev-console` subpath.\n *\n * `DevConsoleTransport` is a **sibling** of the built-in `ConsoleTransport` (it\n * does NOT modify it): a pretty-rendering alternative for **development**. The\n * consumer selects it only in development so their production bundler\n * tree-shakes it out entirely:\n *\n * transports: [ import.meta.env.DEV ? DevConsoleTransport() : ConsoleTransport() ]\n *\n * In `environment: 'development'` (read from the event, a runtime decision —\n * never SafeSignal's build-time `__DEV__`) with rich console support, `send`\n * renders a collapsed group per entry: a level icon/color header (message +\n * `app · module · env`), the attributes, the error (name/message + stack), and\n * a trace link when a trace context is present. In any non-development\n * environment — or where rich console features are absent — it behaves exactly\n * like `ConsoleTransport` (`console[level](event.message, event)`).\n *\n * Safety (mirrors the package's guarantees — see\n * `specs/015-dev-console-rendering/contracts/dev-console.md`):\n * - Structured-only: renders ONLY the post-pipeline (sanitized + redacted +\n * bounded) event; the attributes object is logged by reference, never\n * re-serialized or re-walked (Principle IV/V; DC-5).\n * - No globals / no ambient: attaches no listeners and reads no ambient host\n * state — it operates solely on the event passed to `send()` (Principle\n * VIII; DC-6).\n * - Fail-safe: the whole `send` body is wrapped; a throwing console method or\n * a throwing `traceUrl` is swallowed and never reaches the page (Principle\n * III; DC-4/DC-7).\n * - Carry-only trace: links are built only from the event's existing trace\n * ids; none are minted (DC-8).\n *\n * The only `src/` import is **type-only** from `../api/types.js`, so this bundle\n * shares no runtime state with the core and stays vendor-neutral.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\n\n/** Options for {@link DevConsoleTransport}. */\nexport interface DevConsoleTransportOptions {\n /** `Transport.name` for diagnostics. Default `'dev-console'`. */\n name?: string;\n /**\n * Format a clickable trace URL from the event's existing trace ids (devtools\n * auto-linkifies URLs). Invoked with **only** `{ traceId, spanId }` —\n * carry-only, no ids minted. When omitted, the ids are rendered as text. A\n * throw from it is swallowed (fail-safe) and the ids are rendered instead.\n */\n traceUrl?: (trace: { traceId: string; spanId: string }) => string;\n /**\n * Force `%c` styling on/off. Default: auto — on, since the pretty path is\n * only taken when grouping is supported (`%c` is a harmless no-op in Node).\n */\n colors?: boolean;\n}\n\ntype ConsoleMethod = (message?: unknown, ...optional: unknown[]) => void;\n\n/** Per-level icon + devtools color. Presentation only. */\nconst LEVEL_STYLE: Record<LogEvent['level'], { icon: string; color: string }> =\n {\n debug: { icon: '🐛', color: '#6b7280' },\n info: { icon: 'ℹ️', color: '#2563eb' },\n warn: { icon: '⚠️', color: '#d97706' },\n error: { icon: '⛔', color: '#dc2626' },\n };\n\nconst DIM_STYLE = 'color:#9ca3af';\n\n/** Resolve `console[level]`, mirroring the built-in `ConsoleTransport`. */\nfunction resolveConsoleMethod(level: LogEvent['level']): ConsoleMethod {\n const slot = (console as unknown as Record<string, unknown>)[level];\n if (typeof slot === 'function') {\n return (slot as ConsoleMethod).bind(console);\n }\n return console.log.bind(console);\n}\n\n/** True only when the grouping primitives the pretty path needs are functions. */\nfunction richConsoleAvailable(): boolean {\n const c = console as unknown as Record<string, unknown>;\n return (\n typeof c.groupCollapsed === 'function' && typeof c.groupEnd === 'function'\n );\n}\n\n/** The current value (the structured-only form) — identical to `ConsoleTransport`. */\nfunction structuredFallback(event: LogEvent): void {\n resolveConsoleMethod(event.level)(event.message, event);\n}\n\n/** `app · module · env` summary; absent parts render as an em dash. */\nfunction contextSummary(event: LogEvent): string {\n const app = event.context.application?.name ?? '—';\n const mod = event.context.module?.name ?? '—';\n const env = event.context.environment ?? '—';\n return `${app} · ${mod} · ${env}`;\n}\n\n/**\n * Render the trace link from the event's existing trace context (carry-only).\n * Returns the formatted URL via `traceUrl`, or the raw ids when no formatter is\n * given (or when it throws — fail-safe).\n */\nfunction traceLink(\n event: LogEvent,\n traceUrl: DevConsoleTransportOptions['traceUrl'],\n): string | undefined {\n const trace = event.context.trace;\n if (!trace) return undefined;\n const ids = { traceId: trace.traceId, spanId: trace.spanId };\n if (traceUrl) {\n try {\n return traceUrl(ids);\n } catch {\n // A throwing formatter must not break rendering — fall back to the ids.\n }\n }\n return `${ids.traceId}/${ids.spanId}`;\n}\n\n/** Pretty-render one dev event as a collapsed group. */\nfunction renderGroup(\n event: LogEvent,\n options: DevConsoleTransportOptions,\n): void {\n const useColors = options.colors ?? true;\n const { icon, color } = LEVEL_STYLE[event.level];\n const level = event.level.toUpperCase();\n const summary = contextSummary(event);\n\n if (useColors) {\n console.groupCollapsed(\n `%c${icon} ${level}%c ${event.message}%c ${summary}`,\n `color:${color};font-weight:bold`,\n '',\n DIM_STYLE,\n );\n } else {\n console.groupCollapsed(`${icon} ${level} ${event.message} ${summary}`);\n }\n\n try {\n // Attributes: log the object BY REFERENCE — devtools stays interactive and\n // the renderer never re-serializes or re-walks the bounded event (DC-5).\n if (Object.keys(event.attributes).length > 0) {\n console.log(event.attributes);\n }\n if (event.error) {\n console.log(`${event.error.name}: ${event.error.message}`);\n if (event.error.stack) console.log(event.error.stack);\n }\n const link = traceLink(event, options.traceUrl);\n if (link !== undefined) {\n console.log(`↳ trace ${link}`);\n }\n } finally {\n console.groupEnd();\n }\n}\n\n/**\n * A pretty, dev-only console transport. Renders the post-pipeline event\n * beautifully in development; in non-development environments (or without rich\n * console support) it behaves exactly like the built-in `ConsoleTransport`.\n * Never throws.\n */\nexport const DevConsoleTransport: (\n options?: DevConsoleTransportOptions,\n) => Transport = (options: DevConsoleTransportOptions = {}): Transport => ({\n name: options.name ?? 'dev-console',\n send(event: LogEvent): void {\n try {\n if (\n event.context.environment !== 'development' ||\n !richConsoleAvailable()\n ) {\n structuredFallback(event);\n return;\n }\n renderGroup(event, options);\n } catch {\n // Fail-safe (Principle III): rendering MUST NEVER throw into the page.\n }\n },\n});\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { l as Transport } from './types-CZtSjgq5.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Developer-friendly dev-mode console rendering — the `./dev-console` subpath.
|
|
5
|
+
*
|
|
6
|
+
* `DevConsoleTransport` is a **sibling** of the built-in `ConsoleTransport` (it
|
|
7
|
+
* does NOT modify it): a pretty-rendering alternative for **development**. The
|
|
8
|
+
* consumer selects it only in development so their production bundler
|
|
9
|
+
* tree-shakes it out entirely:
|
|
10
|
+
*
|
|
11
|
+
* transports: [ import.meta.env.DEV ? DevConsoleTransport() : ConsoleTransport() ]
|
|
12
|
+
*
|
|
13
|
+
* In `environment: 'development'` (read from the event, a runtime decision —
|
|
14
|
+
* never SafeSignal's build-time `__DEV__`) with rich console support, `send`
|
|
15
|
+
* renders a collapsed group per entry: a level icon/color header (message +
|
|
16
|
+
* `app · module · env`), the attributes, the error (name/message + stack), and
|
|
17
|
+
* a trace link when a trace context is present. In any non-development
|
|
18
|
+
* environment — or where rich console features are absent — it behaves exactly
|
|
19
|
+
* like `ConsoleTransport` (`console[level](event.message, event)`).
|
|
20
|
+
*
|
|
21
|
+
* Safety (mirrors the package's guarantees — see
|
|
22
|
+
* `specs/015-dev-console-rendering/contracts/dev-console.md`):
|
|
23
|
+
* - Structured-only: renders ONLY the post-pipeline (sanitized + redacted +
|
|
24
|
+
* bounded) event; the attributes object is logged by reference, never
|
|
25
|
+
* re-serialized or re-walked (Principle IV/V; DC-5).
|
|
26
|
+
* - No globals / no ambient: attaches no listeners and reads no ambient host
|
|
27
|
+
* state — it operates solely on the event passed to `send()` (Principle
|
|
28
|
+
* VIII; DC-6).
|
|
29
|
+
* - Fail-safe: the whole `send` body is wrapped; a throwing console method or
|
|
30
|
+
* a throwing `traceUrl` is swallowed and never reaches the page (Principle
|
|
31
|
+
* III; DC-4/DC-7).
|
|
32
|
+
* - Carry-only trace: links are built only from the event's existing trace
|
|
33
|
+
* ids; none are minted (DC-8).
|
|
34
|
+
*
|
|
35
|
+
* The only `src/` import is **type-only** from `../api/types.js`, so this bundle
|
|
36
|
+
* shares no runtime state with the core and stays vendor-neutral.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** Options for {@link DevConsoleTransport}. */
|
|
40
|
+
interface DevConsoleTransportOptions {
|
|
41
|
+
/** `Transport.name` for diagnostics. Default `'dev-console'`. */
|
|
42
|
+
name?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Format a clickable trace URL from the event's existing trace ids (devtools
|
|
45
|
+
* auto-linkifies URLs). Invoked with **only** `{ traceId, spanId }` —
|
|
46
|
+
* carry-only, no ids minted. When omitted, the ids are rendered as text. A
|
|
47
|
+
* throw from it is swallowed (fail-safe) and the ids are rendered instead.
|
|
48
|
+
*/
|
|
49
|
+
traceUrl?: (trace: {
|
|
50
|
+
traceId: string;
|
|
51
|
+
spanId: string;
|
|
52
|
+
}) => string;
|
|
53
|
+
/**
|
|
54
|
+
* Force `%c` styling on/off. Default: auto — on, since the pretty path is
|
|
55
|
+
* only taken when grouping is supported (`%c` is a harmless no-op in Node).
|
|
56
|
+
*/
|
|
57
|
+
colors?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A pretty, dev-only console transport. Renders the post-pipeline event
|
|
61
|
+
* beautifully in development; in non-development environments (or without rich
|
|
62
|
+
* console support) it behaves exactly like the built-in `ConsoleTransport`.
|
|
63
|
+
* Never throws.
|
|
64
|
+
*/
|
|
65
|
+
declare const DevConsoleTransport: (options?: DevConsoleTransportOptions) => Transport;
|
|
66
|
+
|
|
67
|
+
export { DevConsoleTransport, type DevConsoleTransportOptions };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { l as Transport } from './types-CZtSjgq5.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Developer-friendly dev-mode console rendering — the `./dev-console` subpath.
|
|
5
|
+
*
|
|
6
|
+
* `DevConsoleTransport` is a **sibling** of the built-in `ConsoleTransport` (it
|
|
7
|
+
* does NOT modify it): a pretty-rendering alternative for **development**. The
|
|
8
|
+
* consumer selects it only in development so their production bundler
|
|
9
|
+
* tree-shakes it out entirely:
|
|
10
|
+
*
|
|
11
|
+
* transports: [ import.meta.env.DEV ? DevConsoleTransport() : ConsoleTransport() ]
|
|
12
|
+
*
|
|
13
|
+
* In `environment: 'development'` (read from the event, a runtime decision —
|
|
14
|
+
* never SafeSignal's build-time `__DEV__`) with rich console support, `send`
|
|
15
|
+
* renders a collapsed group per entry: a level icon/color header (message +
|
|
16
|
+
* `app · module · env`), the attributes, the error (name/message + stack), and
|
|
17
|
+
* a trace link when a trace context is present. In any non-development
|
|
18
|
+
* environment — or where rich console features are absent — it behaves exactly
|
|
19
|
+
* like `ConsoleTransport` (`console[level](event.message, event)`).
|
|
20
|
+
*
|
|
21
|
+
* Safety (mirrors the package's guarantees — see
|
|
22
|
+
* `specs/015-dev-console-rendering/contracts/dev-console.md`):
|
|
23
|
+
* - Structured-only: renders ONLY the post-pipeline (sanitized + redacted +
|
|
24
|
+
* bounded) event; the attributes object is logged by reference, never
|
|
25
|
+
* re-serialized or re-walked (Principle IV/V; DC-5).
|
|
26
|
+
* - No globals / no ambient: attaches no listeners and reads no ambient host
|
|
27
|
+
* state — it operates solely on the event passed to `send()` (Principle
|
|
28
|
+
* VIII; DC-6).
|
|
29
|
+
* - Fail-safe: the whole `send` body is wrapped; a throwing console method or
|
|
30
|
+
* a throwing `traceUrl` is swallowed and never reaches the page (Principle
|
|
31
|
+
* III; DC-4/DC-7).
|
|
32
|
+
* - Carry-only trace: links are built only from the event's existing trace
|
|
33
|
+
* ids; none are minted (DC-8).
|
|
34
|
+
*
|
|
35
|
+
* The only `src/` import is **type-only** from `../api/types.js`, so this bundle
|
|
36
|
+
* shares no runtime state with the core and stays vendor-neutral.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** Options for {@link DevConsoleTransport}. */
|
|
40
|
+
interface DevConsoleTransportOptions {
|
|
41
|
+
/** `Transport.name` for diagnostics. Default `'dev-console'`. */
|
|
42
|
+
name?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Format a clickable trace URL from the event's existing trace ids (devtools
|
|
45
|
+
* auto-linkifies URLs). Invoked with **only** `{ traceId, spanId }` —
|
|
46
|
+
* carry-only, no ids minted. When omitted, the ids are rendered as text. A
|
|
47
|
+
* throw from it is swallowed (fail-safe) and the ids are rendered instead.
|
|
48
|
+
*/
|
|
49
|
+
traceUrl?: (trace: {
|
|
50
|
+
traceId: string;
|
|
51
|
+
spanId: string;
|
|
52
|
+
}) => string;
|
|
53
|
+
/**
|
|
54
|
+
* Force `%c` styling on/off. Default: auto — on, since the pretty path is
|
|
55
|
+
* only taken when grouping is supported (`%c` is a harmless no-op in Node).
|
|
56
|
+
*/
|
|
57
|
+
colors?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A pretty, dev-only console transport. Renders the post-pipeline event
|
|
61
|
+
* beautifully in development; in non-development environments (or without rich
|
|
62
|
+
* console support) it behaves exactly like the built-in `ConsoleTransport`.
|
|
63
|
+
* Never throws.
|
|
64
|
+
*/
|
|
65
|
+
declare const DevConsoleTransport: (options?: DevConsoleTransportOptions) => Transport;
|
|
66
|
+
|
|
67
|
+
export { DevConsoleTransport, type DevConsoleTransportOptions };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/dev-console/index.ts
|
|
2
|
+
var LEVEL_STYLE = {
|
|
3
|
+
debug: { icon: "\u{1F41B}", color: "#6b7280" },
|
|
4
|
+
info: { icon: "\u2139\uFE0F", color: "#2563eb" },
|
|
5
|
+
warn: { icon: "\u26A0\uFE0F", color: "#d97706" },
|
|
6
|
+
error: { icon: "\u26D4", color: "#dc2626" }
|
|
7
|
+
};
|
|
8
|
+
var DIM_STYLE = "color:#9ca3af";
|
|
9
|
+
function resolveConsoleMethod(level) {
|
|
10
|
+
const slot = console[level];
|
|
11
|
+
if (typeof slot === "function") {
|
|
12
|
+
return slot.bind(console);
|
|
13
|
+
}
|
|
14
|
+
return console.log.bind(console);
|
|
15
|
+
}
|
|
16
|
+
function richConsoleAvailable() {
|
|
17
|
+
const c = console;
|
|
18
|
+
return typeof c.groupCollapsed === "function" && typeof c.groupEnd === "function";
|
|
19
|
+
}
|
|
20
|
+
function structuredFallback(event) {
|
|
21
|
+
resolveConsoleMethod(event.level)(event.message, event);
|
|
22
|
+
}
|
|
23
|
+
function contextSummary(event) {
|
|
24
|
+
const app = event.context.application?.name ?? "\u2014";
|
|
25
|
+
const mod = event.context.module?.name ?? "\u2014";
|
|
26
|
+
const env = event.context.environment ?? "\u2014";
|
|
27
|
+
return `${app} \xB7 ${mod} \xB7 ${env}`;
|
|
28
|
+
}
|
|
29
|
+
function traceLink(event, traceUrl) {
|
|
30
|
+
const trace = event.context.trace;
|
|
31
|
+
if (!trace) return void 0;
|
|
32
|
+
const ids = { traceId: trace.traceId, spanId: trace.spanId };
|
|
33
|
+
if (traceUrl) {
|
|
34
|
+
try {
|
|
35
|
+
return traceUrl(ids);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return `${ids.traceId}/${ids.spanId}`;
|
|
40
|
+
}
|
|
41
|
+
function renderGroup(event, options) {
|
|
42
|
+
const useColors = options.colors ?? true;
|
|
43
|
+
const { icon, color } = LEVEL_STYLE[event.level];
|
|
44
|
+
const level = event.level.toUpperCase();
|
|
45
|
+
const summary = contextSummary(event);
|
|
46
|
+
if (useColors) {
|
|
47
|
+
console.groupCollapsed(
|
|
48
|
+
`%c${icon} ${level}%c ${event.message}%c ${summary}`,
|
|
49
|
+
`color:${color};font-weight:bold`,
|
|
50
|
+
"",
|
|
51
|
+
DIM_STYLE
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
console.groupCollapsed(`${icon} ${level} ${event.message} ${summary}`);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
if (Object.keys(event.attributes).length > 0) {
|
|
58
|
+
console.log(event.attributes);
|
|
59
|
+
}
|
|
60
|
+
if (event.error) {
|
|
61
|
+
console.log(`${event.error.name}: ${event.error.message}`);
|
|
62
|
+
if (event.error.stack) console.log(event.error.stack);
|
|
63
|
+
}
|
|
64
|
+
const link = traceLink(event, options.traceUrl);
|
|
65
|
+
if (link !== void 0) {
|
|
66
|
+
console.log(`\u21B3 trace ${link}`);
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
console.groupEnd();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
var DevConsoleTransport = (options = {}) => ({
|
|
73
|
+
name: options.name ?? "dev-console",
|
|
74
|
+
send(event) {
|
|
75
|
+
try {
|
|
76
|
+
if (event.context.environment !== "development" || !richConsoleAvailable()) {
|
|
77
|
+
structuredFallback(event);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
renderGroup(event, options);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export { DevConsoleTransport };
|
|
87
|
+
//# sourceMappingURL=dev-console.mjs.map
|
|
88
|
+
//# sourceMappingURL=dev-console.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dev-console/index.ts"],"names":[],"mappings":"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","file":"dev-console.mjs","sourcesContent":["/**\n * Developer-friendly dev-mode console rendering — the `./dev-console` subpath.\n *\n * `DevConsoleTransport` is a **sibling** of the built-in `ConsoleTransport` (it\n * does NOT modify it): a pretty-rendering alternative for **development**. The\n * consumer selects it only in development so their production bundler\n * tree-shakes it out entirely:\n *\n * transports: [ import.meta.env.DEV ? DevConsoleTransport() : ConsoleTransport() ]\n *\n * In `environment: 'development'` (read from the event, a runtime decision —\n * never SafeSignal's build-time `__DEV__`) with rich console support, `send`\n * renders a collapsed group per entry: a level icon/color header (message +\n * `app · module · env`), the attributes, the error (name/message + stack), and\n * a trace link when a trace context is present. In any non-development\n * environment — or where rich console features are absent — it behaves exactly\n * like `ConsoleTransport` (`console[level](event.message, event)`).\n *\n * Safety (mirrors the package's guarantees — see\n * `specs/015-dev-console-rendering/contracts/dev-console.md`):\n * - Structured-only: renders ONLY the post-pipeline (sanitized + redacted +\n * bounded) event; the attributes object is logged by reference, never\n * re-serialized or re-walked (Principle IV/V; DC-5).\n * - No globals / no ambient: attaches no listeners and reads no ambient host\n * state — it operates solely on the event passed to `send()` (Principle\n * VIII; DC-6).\n * - Fail-safe: the whole `send` body is wrapped; a throwing console method or\n * a throwing `traceUrl` is swallowed and never reaches the page (Principle\n * III; DC-4/DC-7).\n * - Carry-only trace: links are built only from the event's existing trace\n * ids; none are minted (DC-8).\n *\n * The only `src/` import is **type-only** from `../api/types.js`, so this bundle\n * shares no runtime state with the core and stays vendor-neutral.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\n\n/** Options for {@link DevConsoleTransport}. */\nexport interface DevConsoleTransportOptions {\n /** `Transport.name` for diagnostics. Default `'dev-console'`. */\n name?: string;\n /**\n * Format a clickable trace URL from the event's existing trace ids (devtools\n * auto-linkifies URLs). Invoked with **only** `{ traceId, spanId }` —\n * carry-only, no ids minted. When omitted, the ids are rendered as text. A\n * throw from it is swallowed (fail-safe) and the ids are rendered instead.\n */\n traceUrl?: (trace: { traceId: string; spanId: string }) => string;\n /**\n * Force `%c` styling on/off. Default: auto — on, since the pretty path is\n * only taken when grouping is supported (`%c` is a harmless no-op in Node).\n */\n colors?: boolean;\n}\n\ntype ConsoleMethod = (message?: unknown, ...optional: unknown[]) => void;\n\n/** Per-level icon + devtools color. Presentation only. */\nconst LEVEL_STYLE: Record<LogEvent['level'], { icon: string; color: string }> =\n {\n debug: { icon: '🐛', color: '#6b7280' },\n info: { icon: 'ℹ️', color: '#2563eb' },\n warn: { icon: '⚠️', color: '#d97706' },\n error: { icon: '⛔', color: '#dc2626' },\n };\n\nconst DIM_STYLE = 'color:#9ca3af';\n\n/** Resolve `console[level]`, mirroring the built-in `ConsoleTransport`. */\nfunction resolveConsoleMethod(level: LogEvent['level']): ConsoleMethod {\n const slot = (console as unknown as Record<string, unknown>)[level];\n if (typeof slot === 'function') {\n return (slot as ConsoleMethod).bind(console);\n }\n return console.log.bind(console);\n}\n\n/** True only when the grouping primitives the pretty path needs are functions. */\nfunction richConsoleAvailable(): boolean {\n const c = console as unknown as Record<string, unknown>;\n return (\n typeof c.groupCollapsed === 'function' && typeof c.groupEnd === 'function'\n );\n}\n\n/** The current value (the structured-only form) — identical to `ConsoleTransport`. */\nfunction structuredFallback(event: LogEvent): void {\n resolveConsoleMethod(event.level)(event.message, event);\n}\n\n/** `app · module · env` summary; absent parts render as an em dash. */\nfunction contextSummary(event: LogEvent): string {\n const app = event.context.application?.name ?? '—';\n const mod = event.context.module?.name ?? '—';\n const env = event.context.environment ?? '—';\n return `${app} · ${mod} · ${env}`;\n}\n\n/**\n * Render the trace link from the event's existing trace context (carry-only).\n * Returns the formatted URL via `traceUrl`, or the raw ids when no formatter is\n * given (or when it throws — fail-safe).\n */\nfunction traceLink(\n event: LogEvent,\n traceUrl: DevConsoleTransportOptions['traceUrl'],\n): string | undefined {\n const trace = event.context.trace;\n if (!trace) return undefined;\n const ids = { traceId: trace.traceId, spanId: trace.spanId };\n if (traceUrl) {\n try {\n return traceUrl(ids);\n } catch {\n // A throwing formatter must not break rendering — fall back to the ids.\n }\n }\n return `${ids.traceId}/${ids.spanId}`;\n}\n\n/** Pretty-render one dev event as a collapsed group. */\nfunction renderGroup(\n event: LogEvent,\n options: DevConsoleTransportOptions,\n): void {\n const useColors = options.colors ?? true;\n const { icon, color } = LEVEL_STYLE[event.level];\n const level = event.level.toUpperCase();\n const summary = contextSummary(event);\n\n if (useColors) {\n console.groupCollapsed(\n `%c${icon} ${level}%c ${event.message}%c ${summary}`,\n `color:${color};font-weight:bold`,\n '',\n DIM_STYLE,\n );\n } else {\n console.groupCollapsed(`${icon} ${level} ${event.message} ${summary}`);\n }\n\n try {\n // Attributes: log the object BY REFERENCE — devtools stays interactive and\n // the renderer never re-serializes or re-walks the bounded event (DC-5).\n if (Object.keys(event.attributes).length > 0) {\n console.log(event.attributes);\n }\n if (event.error) {\n console.log(`${event.error.name}: ${event.error.message}`);\n if (event.error.stack) console.log(event.error.stack);\n }\n const link = traceLink(event, options.traceUrl);\n if (link !== undefined) {\n console.log(`↳ trace ${link}`);\n }\n } finally {\n console.groupEnd();\n }\n}\n\n/**\n * A pretty, dev-only console transport. Renders the post-pipeline event\n * beautifully in development; in non-development environments (or without rich\n * console support) it behaves exactly like the built-in `ConsoleTransport`.\n * Never throws.\n */\nexport const DevConsoleTransport: (\n options?: DevConsoleTransportOptions,\n) => Transport = (options: DevConsoleTransportOptions = {}): Transport => ({\n name: options.name ?? 'dev-console',\n send(event: LogEvent): void {\n try {\n if (\n event.context.environment !== 'development' ||\n !richConsoleAvailable()\n ) {\n structuredFallback(event);\n return;\n }\n renderGroup(event, options);\n } catch {\n // Fail-safe (Principle III): rendering MUST NEVER throw into the page.\n }\n },\n});\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/framework-react/index.ts
|
|
6
|
+
var SOURCE_BOUNDARY = "react-error-boundary";
|
|
7
|
+
var SOURCE_HOOK = "react-use-log-error";
|
|
8
|
+
var LoggerContext = react.createContext(void 0);
|
|
9
|
+
function LoggerProvider(props) {
|
|
10
|
+
return react.createElement(
|
|
11
|
+
LoggerContext.Provider,
|
|
12
|
+
{ value: props.logger },
|
|
13
|
+
props.children
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
function resetKeysChanged(prev, next) {
|
|
17
|
+
if (prev === next) return false;
|
|
18
|
+
if (prev === void 0 || next === void 0) return true;
|
|
19
|
+
if (prev.length !== next.length) return true;
|
|
20
|
+
for (let i = 0; i < prev.length; i += 1) {
|
|
21
|
+
if (!Object.is(prev[i], next[i])) return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
var LogErrorBoundary = class extends react.Component {
|
|
26
|
+
constructor(props) {
|
|
27
|
+
super(props);
|
|
28
|
+
this.state = { caught: false, error: void 0 };
|
|
29
|
+
this.reset = this.reset.bind(this);
|
|
30
|
+
}
|
|
31
|
+
static getDerivedStateFromError(error) {
|
|
32
|
+
return { caught: true, error };
|
|
33
|
+
}
|
|
34
|
+
componentDidCatch(error, info) {
|
|
35
|
+
const componentStack = typeof info.componentStack === "string" ? info.componentStack : "";
|
|
36
|
+
const logger = this.props.logger ?? this.context;
|
|
37
|
+
try {
|
|
38
|
+
const attributes = { "safesignal.source": SOURCE_BOUNDARY };
|
|
39
|
+
if (componentStack) {
|
|
40
|
+
attributes["safesignal.react.componentStack"] = componentStack;
|
|
41
|
+
}
|
|
42
|
+
logger?.error("React render error", attributes, error);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
this.props.onError?.(error, { componentStack });
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
componentDidUpdate(prevProps) {
|
|
51
|
+
if (!this.state.caught) return;
|
|
52
|
+
if (resetKeysChanged(prevProps.resetKeys, this.props.resetKeys)) {
|
|
53
|
+
this.reset();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
reset() {
|
|
57
|
+
this.setState({ caught: false, error: void 0 });
|
|
58
|
+
}
|
|
59
|
+
render() {
|
|
60
|
+
if (!this.state.caught) return this.props.children;
|
|
61
|
+
const { fallback } = this.props;
|
|
62
|
+
if (typeof fallback === "function") {
|
|
63
|
+
return fallback(this.state.error, this.reset);
|
|
64
|
+
}
|
|
65
|
+
return fallback ?? null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
LogErrorBoundary.contextType = LoggerContext;
|
|
69
|
+
function useLogError(loggerOverride) {
|
|
70
|
+
const contextLogger = react.useContext(LoggerContext);
|
|
71
|
+
const logger = loggerOverride ?? contextLogger;
|
|
72
|
+
return react.useCallback(
|
|
73
|
+
(error, attributes) => {
|
|
74
|
+
try {
|
|
75
|
+
const attrs = {
|
|
76
|
+
"safesignal.source": SOURCE_HOOK,
|
|
77
|
+
...attributes ?? {}
|
|
78
|
+
};
|
|
79
|
+
logger?.error("Reported error", attrs, error);
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[logger]
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
exports.LogErrorBoundary = LogErrorBoundary;
|
|
88
|
+
exports.LoggerContext = LoggerContext;
|
|
89
|
+
exports.LoggerProvider = LoggerProvider;
|
|
90
|
+
exports.useLogError = useLogError;
|
|
91
|
+
//# sourceMappingURL=framework-react.cjs.map
|
|
92
|
+
//# sourceMappingURL=framework-react.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/framework-react/index.ts"],"names":["createContext","createElement","Component","useContext","useCallback"],"mappings":"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","file":"framework-react.cjs","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"]}
|