@tallyrow/safesignal 1.3.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.
Files changed (50) hide show
  1. package/README.md +329 -20
  2. package/dist/capture.cjs +77 -0
  3. package/dist/capture.cjs.map +1 -0
  4. package/dist/capture.d.cts +49 -0
  5. package/dist/capture.d.ts +49 -0
  6. package/dist/capture.mjs +75 -0
  7. package/dist/capture.mjs.map +1 -0
  8. package/dist/dev-console.cjs +90 -0
  9. package/dist/dev-console.cjs.map +1 -0
  10. package/dist/dev-console.d.cts +67 -0
  11. package/dist/dev-console.d.ts +67 -0
  12. package/dist/dev-console.mjs +88 -0
  13. package/dist/dev-console.mjs.map +1 -0
  14. package/dist/framework-react.cjs +92 -0
  15. package/dist/framework-react.cjs.map +1 -0
  16. package/dist/framework-react.d.cts +97 -0
  17. package/dist/framework-react.d.ts +97 -0
  18. package/dist/framework-react.mjs +87 -0
  19. package/dist/framework-react.mjs.map +1 -0
  20. package/dist/framework-vue.cjs +88 -0
  21. package/dist/framework-vue.cjs.map +1 -0
  22. package/dist/framework-vue.d.cts +101 -0
  23. package/dist/framework-vue.d.ts +101 -0
  24. package/dist/framework-vue.mjs +82 -0
  25. package/dist/framework-vue.mjs.map +1 -0
  26. package/dist/index.cjs +180 -40
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +2 -2
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.mjs +180 -40
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/stacks.cjs +81 -0
  33. package/dist/stacks.cjs.map +1 -0
  34. package/dist/stacks.d.cts +55 -0
  35. package/dist/stacks.d.ts +55 -0
  36. package/dist/stacks.mjs +77 -0
  37. package/dist/stacks.mjs.map +1 -0
  38. package/dist/testing.d.cts +1 -1
  39. package/dist/testing.d.ts +1 -1
  40. package/dist/transport-beacon.cjs.map +1 -1
  41. package/dist/transport-beacon.d.cts +1 -1
  42. package/dist/transport-beacon.d.ts +1 -1
  43. package/dist/transport-beacon.mjs.map +1 -1
  44. package/dist/transport-otlp.cjs.map +1 -1
  45. package/dist/transport-otlp.d.cts +1 -1
  46. package/dist/transport-otlp.d.ts +1 -1
  47. package/dist/transport-otlp.mjs.map +1 -1
  48. package/dist/{types-BiRyHi1e.d.cts → types-CZtSjgq5.d.cts} +53 -1
  49. package/dist/{types-BiRyHi1e.d.ts → types-CZtSjgq5.d.ts} +53 -1
  50. package/package.json +53 -6
@@ -0,0 +1,82 @@
1
+ import { inject, onErrorCaptured } from 'vue';
2
+
3
+ // src/framework-vue/index.ts
4
+ var SOURCE_HANDLER = "vue-error-handler";
5
+ var SOURCE_HOOK = "vue-use-log-error";
6
+ var SOURCE_CAPTURED = "vue-error-captured";
7
+ var loggerKey = /* @__PURE__ */ Symbol("safesignal.logger");
8
+ function readName(obj) {
9
+ if (obj && typeof obj === "object") {
10
+ const name = obj.name;
11
+ if (typeof name === "string" && name.length > 0) return name;
12
+ }
13
+ return void 0;
14
+ }
15
+ function componentNameOf(instance) {
16
+ try {
17
+ if (!instance || typeof instance !== "object") return void 0;
18
+ const inst = instance;
19
+ return readName(inst.$options) ?? readName(inst.$?.type) ?? readName(inst.type);
20
+ } catch {
21
+ return void 0;
22
+ }
23
+ }
24
+ function emit(logger, source, message, error, extra, instance, info) {
25
+ if (!logger) return;
26
+ try {
27
+ const attributes = { "safesignal.source": source };
28
+ if (typeof info === "string" && info.length > 0) {
29
+ attributes["safesignal.vue.info"] = info;
30
+ }
31
+ const name = componentNameOf(instance);
32
+ if (name) attributes["safesignal.vue.componentName"] = name;
33
+ if (extra) {
34
+ for (const key of Object.keys(extra)) {
35
+ attributes[key] = extra[key];
36
+ }
37
+ }
38
+ logger.error(message, attributes, error);
39
+ } catch {
40
+ }
41
+ }
42
+ function createErrorHandler(logger) {
43
+ return (err, instance, info) => {
44
+ emit(logger, SOURCE_HANDLER, "Vue error", err, void 0, instance, info);
45
+ };
46
+ }
47
+ var safesignalErrorHandler = {
48
+ install(app, options) {
49
+ const { logger } = options;
50
+ app.config.errorHandler = createErrorHandler(logger);
51
+ app.provide(loggerKey, logger);
52
+ }
53
+ };
54
+ function useLogError(loggerOverride) {
55
+ const logger = loggerOverride ?? inject(loggerKey);
56
+ return (error, attributes) => {
57
+ emit(logger, SOURCE_HOOK, "Reported error", error, attributes);
58
+ };
59
+ }
60
+ function useErrorCapture(options = {}) {
61
+ const logger = options.logger ?? inject(loggerKey);
62
+ onErrorCaptured((err, instance, info) => {
63
+ emit(
64
+ logger,
65
+ SOURCE_CAPTURED,
66
+ "Vue captured error",
67
+ err,
68
+ void 0,
69
+ instance,
70
+ info
71
+ );
72
+ try {
73
+ options.onError?.(err, info);
74
+ } catch {
75
+ }
76
+ return options.propagate ? void 0 : false;
77
+ });
78
+ }
79
+
80
+ export { createErrorHandler, loggerKey, safesignalErrorHandler, useErrorCapture, useLogError };
81
+ //# sourceMappingURL=framework-vue.mjs.map
82
+ //# sourceMappingURL=framework-vue.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/framework-vue/index.ts"],"names":[],"mappings":";;;AAmDA,IAAM,cAAA,GAAiB,mBAAA;AAEvB,IAAM,WAAA,GAAc,mBAAA;AAEpB,IAAM,eAAA,GAAkB,oBAAA;AAQjB,IAAM,SAAA,0BAAyC,mBAAmB;AAUzE,SAAS,SAAS,GAAA,EAAkC;AAClD,EAAA,IAAI,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,EAAU;AAClC,IAAA,MAAM,OAAQ,GAAA,CAA2B,IAAA;AACzC,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,CAAK,MAAA,GAAS,GAAG,OAAO,IAAA;AAAA,EAC1D;AACA,EAAA,OAAO,MAAA;AACT;AAOA,SAAS,gBAAgB,QAAA,EAAuC;AAC9D,EAAA,IAAI;AACF,IAAA,IAAI,CAAC,QAAA,IAAY,OAAO,QAAA,KAAa,UAAU,OAAO,KAAA,CAAA;AACtD,IAAA,MAAM,IAAA,GAAO,QAAA;AAKb,IAAA,OACE,QAAA,CAAS,IAAA,CAAK,QAAQ,CAAA,IAAK,QAAA,CAAS,IAAA,CAAK,CAAA,EAAG,IAAI,CAAA,IAAK,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA;AAAA,EAE3E,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAOA,SAAS,KACP,MAAA,EACA,MAAA,EACA,SACA,KAAA,EACA,KAAA,EACA,UACA,IAAA,EACM;AACN,EAAA,IAAI,CAAC,MAAA,EAAQ;AACb,EAAA,IAAI;AACF,IAAA,MAAM,UAAA,GAAyB,EAAE,mBAAA,EAAqB,MAAA,EAAO;AAC7D,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,CAAK,SAAS,CAAA,EAAG;AAC/C,MAAA,UAAA,CAAW,qBAAqB,CAAA,GAAI,IAAA;AAAA,IACtC;AACA,IAAA,MAAM,IAAA,GAAO,gBAAgB,QAAQ,CAAA;AACrC,IAAA,IAAI,IAAA,EAAM,UAAA,CAAW,8BAA8B,CAAA,GAAI,IAAA;AACvD,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,EAAG;AACpC,QAAA,UAAA,CAAW,GAAG,CAAA,GAAI,KAAA,CAAM,GAAG,CAAA;AAAA,MAC7B;AAAA,IACF;AACA,IAAA,MAAA,CAAO,KAAA,CAAM,OAAA,EAAS,UAAA,EAAY,KAAK,CAAA;AAAA,EACzC,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAQO,SAAS,mBAAmB,MAAA,EAAiC;AAClE,EAAA,OAAO,CAAC,GAAA,EAAK,QAAA,EAAU,IAAA,KAAS;AAC9B,IAAA,IAAA,CAAK,QAAQ,cAAA,EAAgB,WAAA,EAAa,GAAA,EAAK,MAAA,EAAW,UAAU,IAAI,CAAA;AAAA,EAC1E,CAAA;AACF;AAcO,IAAM,sBAAA,GAAgE;AAAA,EAC3E,OAAA,CAAQ,KAAU,OAAA,EAA8C;AAC9D,IAAA,MAAM,EAAE,QAAO,GAAI,OAAA;AACnB,IAAA,GAAA,CAAI,MAAA,CAAO,YAAA,GAAe,kBAAA,CAAmB,MAAM,CAAA;AACnD,IAAA,GAAA,CAAI,OAAA,CAAQ,WAAW,MAAM,CAAA;AAAA,EAC/B;AACF;AAUO,SAAS,YACd,cAAA,EACmD;AACnD,EAAA,MAAM,MAAA,GAAS,cAAA,IAAkB,MAAA,CAAO,SAAS,CAAA;AACjD,EAAA,OAAO,CAAC,OAAO,UAAA,KAAe;AAC5B,IAAA,IAAA,CAAK,MAAA,EAAQ,WAAA,EAAa,gBAAA,EAAkB,KAAA,EAAO,UAAU,CAAA;AAAA,EAC/D,CAAA;AACF;AAoBO,SAAS,eAAA,CAAgB,OAAA,GAAkC,EAAC,EAAS;AAC1E,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,MAAA,CAAO,SAAS,CAAA;AACjD,EAAA,eAAA,CAAgB,CAAC,GAAA,EAAK,QAAA,EAAU,IAAA,KAAS;AACvC,IAAA,IAAA;AAAA,MACE,MAAA;AAAA,MACA,eAAA;AAAA,MACA,oBAAA;AAAA,MACA,GAAA;AAAA,MACA,MAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,IAAI;AACF,MAAA,OAAA,CAAQ,OAAA,GAAU,KAAK,IAAI,CAAA;AAAA,IAC7B,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,OAAO,OAAA,CAAQ,YAAY,MAAA,GAAY,KAAA;AAAA,EACzC,CAAC,CAAA;AACH","file":"framework-vue.mjs","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"]}
package/dist/index.cjs CHANGED
@@ -1,5 +1,121 @@
1
1
  'use strict';
2
2
 
3
+ // src/internal/errors/internal-errors.ts
4
+ var PACKAGE_ERROR_MARKER = /* @__PURE__ */ Symbol("frontend-logging-sdk/package-error");
5
+ var PackageError = class extends Error {
6
+ constructor(code, message, options = {}) {
7
+ super(message);
8
+ this.name = "PackageError";
9
+ this.code = code;
10
+ if (options.cause !== void 0) {
11
+ this.cause = options.cause;
12
+ }
13
+ if (options.transportName !== void 0) {
14
+ this.transportName = options.transportName;
15
+ }
16
+ Object.defineProperty(this, PACKAGE_ERROR_MARKER, {
17
+ value: true,
18
+ enumerable: false,
19
+ writable: false,
20
+ configurable: false
21
+ });
22
+ }
23
+ };
24
+ function isPackageError(value) {
25
+ if (typeof value !== "object" || value === null) return false;
26
+ return value[PACKAGE_ERROR_MARKER] === true;
27
+ }
28
+ function wrapAsPackageError(code, message, cause, transportName) {
29
+ if (isPackageError(cause)) return cause;
30
+ const options = { cause };
31
+ if (transportName !== void 0) {
32
+ options.transportName = transportName;
33
+ }
34
+ return new PackageError(code, message, options);
35
+ }
36
+ function safeNotify(onInternalError, err) {
37
+ try {
38
+ onInternalError(err);
39
+ } catch {
40
+ }
41
+ }
42
+
43
+ // src/breadcrumbs/breadcrumb-buffer.ts
44
+ function breadcrumbFail(onInternalError, err) {
45
+ safeNotify(
46
+ onInternalError,
47
+ wrapAsPackageError("breadcrumb_failed", "breadcrumb failed", err)
48
+ );
49
+ }
50
+ var DEFAULT_MAX_EVENTS = 20;
51
+ var MAX_EVENTS_BOUND = 100;
52
+ var BREADCRUMBS_KEY = "safesignal.breadcrumbs";
53
+ function buildSnapshot(event) {
54
+ const snap = {
55
+ ts: event.timestamp,
56
+ level: event.level,
57
+ message: event.message
58
+ };
59
+ const app = event.context.application?.name;
60
+ if (app !== void 0) snap.app = app;
61
+ const mod = event.context.module?.name;
62
+ if (mod !== void 0) snap.module = mod;
63
+ const attrs = {};
64
+ let hasAttrs = false;
65
+ for (const key of Object.keys(event.attributes)) {
66
+ const value = event.attributes[key];
67
+ if (key === BREADCRUMBS_KEY || value === void 0) continue;
68
+ attrs[key] = value;
69
+ hasAttrs = true;
70
+ }
71
+ if (hasAttrs) snap.attributes = attrs;
72
+ return snap;
73
+ }
74
+ var BreadcrumbBuffer = class {
75
+ constructor(maxEvents) {
76
+ this.ring = [];
77
+ this.capacity = maxEvents;
78
+ }
79
+ /** Record a compact snapshot of a post-pipeline event; oldest evicted. */
80
+ record(event) {
81
+ this.ring.push(buildSnapshot(event));
82
+ if (this.ring.length > this.capacity) this.ring.shift();
83
+ }
84
+ /**
85
+ * Attach the current trail to an error event as `attributes[BREADCRUMBS_KEY]`
86
+ * (ordered oldest→newest). No-op when the buffer is empty (no placeholder).
87
+ */
88
+ attachTrailTo(event) {
89
+ if (this.ring.length === 0) return;
90
+ event.attributes[BREADCRUMBS_KEY] = this.ring.slice();
91
+ }
92
+ };
93
+ var MAX_CAUSE_DEPTH = 8;
94
+ var CAUSES_KEY = "safesignal.errorCauses";
95
+ function getCause(value) {
96
+ if (value !== null && typeof value === "object" && "cause" in value) {
97
+ return value.cause;
98
+ }
99
+ return void 0;
100
+ }
101
+ function extractCauseChain(value, maxDepth) {
102
+ const chain = [];
103
+ const seen = /* @__PURE__ */ new Set();
104
+ if (value !== null && typeof value === "object") seen.add(value);
105
+ let current = getCause(value);
106
+ while (current !== void 0 && current !== null && chain.length < maxDepth) {
107
+ if (typeof current === "object") {
108
+ if (seen.has(current)) break;
109
+ seen.add(current);
110
+ }
111
+ chain.push(
112
+ current instanceof Error ? { name: current.name, message: current.message } : { name: "NonError", message: String(current) }
113
+ );
114
+ current = getCause(current);
115
+ }
116
+ return chain;
117
+ }
118
+
3
119
  // src/context/context-merge.ts
4
120
  function mergeContexts(...sources) {
5
121
  const merged = {};
@@ -50,46 +166,6 @@ function isPlainAttributeObject(value) {
50
166
  return value !== null && value !== void 0 && typeof value === "object" && !Array.isArray(value);
51
167
  }
52
168
 
53
- // src/internal/errors/internal-errors.ts
54
- var PACKAGE_ERROR_MARKER = /* @__PURE__ */ Symbol("frontend-logging-sdk/package-error");
55
- var PackageError = class extends Error {
56
- constructor(code, message, options = {}) {
57
- super(message);
58
- this.name = "PackageError";
59
- this.code = code;
60
- if (options.cause !== void 0) {
61
- this.cause = options.cause;
62
- }
63
- if (options.transportName !== void 0) {
64
- this.transportName = options.transportName;
65
- }
66
- Object.defineProperty(this, PACKAGE_ERROR_MARKER, {
67
- value: true,
68
- enumerable: false,
69
- writable: false,
70
- configurable: false
71
- });
72
- }
73
- };
74
- function isPackageError(value) {
75
- if (typeof value !== "object" || value === null) return false;
76
- return value[PACKAGE_ERROR_MARKER] === true;
77
- }
78
- function wrapAsPackageError(code, message, cause, transportName) {
79
- if (isPackageError(cause)) return cause;
80
- const options = { cause };
81
- if (transportName !== void 0) {
82
- options.transportName = transportName;
83
- }
84
- return new PackageError(code, message, options);
85
- }
86
- function safeNotify(onInternalError, err) {
87
- try {
88
- onInternalError(err);
89
- } catch {
90
- }
91
- }
92
-
93
169
  // src/pipeline/control-char-guard.ts
94
170
  var CONTROL_CHAR_CLASS = "[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u2028\\u2029]";
95
171
  var HAS_CONTROL_CHAR = new RegExp(CONTROL_CHAR_CLASS);
@@ -847,6 +923,13 @@ function dispatch(event, config) {
847
923
  if (current === null) return;
848
924
  current = controlCharGuard(current, config);
849
925
  if (current === null) return;
926
+ if (config.breadcrumbs !== void 0 && current.level === "error") {
927
+ try {
928
+ config.breadcrumbs.attachTrailTo(current);
929
+ } catch (err) {
930
+ breadcrumbFail(config.onInternalError, err);
931
+ }
932
+ }
850
933
  current = freezeInDev(current, config);
851
934
  if (current === null) return;
852
935
  } catch (err) {
@@ -869,6 +952,13 @@ function dispatch(event, config) {
869
952
  } catch {
870
953
  }
871
954
  }
955
+ if (config.breadcrumbs !== void 0) {
956
+ try {
957
+ config.breadcrumbs.record(current);
958
+ } catch (err) {
959
+ breadcrumbFail(config.onInternalError, err);
960
+ }
961
+ }
872
962
  }
873
963
 
874
964
  // src/pipeline/event-builder.ts
@@ -956,9 +1046,32 @@ function normalizeConfig(config) {
956
1046
  transports,
957
1047
  redactor: config.redactor,
958
1048
  sanitizerLimits,
1049
+ breadcrumbs: resolveBreadcrumbs(config.breadcrumbs, onInternalError),
1050
+ normalizeStack: config.normalizeStack,
959
1051
  onInternalError
960
1052
  };
961
1053
  }
1054
+ function resolveBreadcrumbs(option, onInternalError) {
1055
+ if (!option) return void 0;
1056
+ const requested = option === true ? void 0 : option.maxEvents;
1057
+ if (requested === void 0 || !Number.isFinite(requested)) {
1058
+ return new BreadcrumbBuffer(DEFAULT_MAX_EVENTS);
1059
+ }
1060
+ const maxEvents = Math.min(
1061
+ MAX_EVENTS_BOUND,
1062
+ Math.max(1, Math.floor(requested))
1063
+ );
1064
+ if (maxEvents !== requested) {
1065
+ safeNotify(
1066
+ onInternalError,
1067
+ new PackageError(
1068
+ "breadcrumbs_max_clamped",
1069
+ `breadcrumbs.maxEvents clamped to ${maxEvents}`
1070
+ )
1071
+ );
1072
+ }
1073
+ return new BreadcrumbBuffer(maxEvents);
1074
+ }
962
1075
  function resolveConfigLevel(level, environment) {
963
1076
  if (level === void 0) {
964
1077
  return defaultLevelForEnvironment(environment);
@@ -1236,6 +1349,33 @@ function makeLogger(options, chainedContexts) {
1236
1349
  context,
1237
1350
  errorValue
1238
1351
  });
1352
+ if (cfg.breadcrumbs !== void 0 && level === "error" && errorValue !== void 0) {
1353
+ try {
1354
+ const causes = extractCauseChain(errorValue, MAX_CAUSE_DEPTH);
1355
+ if (causes.length > 0) {
1356
+ event.attributes[CAUSES_KEY] = causes;
1357
+ }
1358
+ } catch (err) {
1359
+ breadcrumbFail(cfg.onInternalError, err);
1360
+ }
1361
+ }
1362
+ if (cfg.normalizeStack !== void 0 && level === "error" && event.error?.stack !== void 0) {
1363
+ try {
1364
+ const frames = cfg.normalizeStack(event.error.stack);
1365
+ if (frames !== null && frames.length > 0) {
1366
+ event.attributes["safesignal.stack"] = frames;
1367
+ }
1368
+ } catch (err) {
1369
+ safeNotify(
1370
+ cfg.onInternalError,
1371
+ wrapAsPackageError(
1372
+ "stack_normalize_failed",
1373
+ "stack normalize failed",
1374
+ err
1375
+ )
1376
+ );
1377
+ }
1378
+ }
1239
1379
  dispatch(event, cfg);
1240
1380
  }
1241
1381
  return {