UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

172 lines 5.53 kB
/** * @beignet/core/error-reporting * * Provider-neutral error reporting primitives for Beignet applications. */ import { redactValue } from "../ports/redaction.js"; import { DEFAULT_ERROR_REPORTING_TIMEOUT_MS, errorReportingObserverTimeout, runErrorReportingOperation, } from "./internal.js"; export { DEFAULT_ERROR_REPORTING_TIMEOUT_MS, ErrorReportingTimeoutError, } from "./internal.js"; /** * Create a no-op reporter for apps that want to bind the port without sending * events. */ export function createNoopErrorReporter() { return { async captureException() { return {}; }, async captureMessage() { return {}; }, setUser() { }, setTags() { }, setContext() { }, async flush() { return true; }, }; } /** * Create an in-memory reporter for tests and local assertions. */ export function createMemoryErrorReporter(options = {}) { let nextId = 1; const reports = []; const contexts = new Map(); const port = { reports, user: null, tags: {}, contexts, async captureException(error, reportOptions) { const report = { type: "exception", error, options: withAmbientState(port, reportOptions), id: String(nextId++), }; reports.push(report); await options.onCapture?.(report); return { id: report.id }; }, async captureMessage(message, reportOptions) { const report = { type: "message", message, options: withAmbientState(port, reportOptions), id: String(nextId++), }; reports.push(report); await options.onCapture?.(report); return { id: report.id }; }, setUser(user) { port.user = user; }, setTags(tags) { port.tags = { ...port.tags, ...tags }; }, setContext(name, context) { if (context === null) { contexts.delete(name); return; } contexts.set(name, context); }, async flush() { return true; }, reset() { reports.length = 0; contexts.clear(); port.user = null; port.tags = {}; nextId = 1; }, }; return port; } /** * Report an exception through any `ErrorReporterPort`. */ export function reportException(reporter, error, options) { return reporter.captureException(error, options); } /** * Report a message through any `ErrorReporterPort`. */ export function reportMessage(reporter, message, options) { return reporter.captureMessage(message, options); } /** * Best-effort exception capture for runtime boundaries. * * Missing reporters, reporter failures, and reporter-failure observer errors * resolve to `undefined` so diagnostics cannot replace application behavior. */ export async function tryReportException(options) { const reporterSource = options.reporter; if (!reporterSource) return undefined; const timeoutMs = options.timeoutMs ?? DEFAULT_ERROR_REPORTING_TIMEOUT_MS; try { return await runErrorReportingOperation(async () => { const reporter = typeof reporterSource === "function" ? await reporterSource() : reporterSource; if (!reporter) return undefined; return reporter.captureException(options.error, options.reportOptions); }, timeoutMs); } catch (reportingError) { try { if (options.onReporterError) { await runErrorReportingOperation(() => options.onReporterError?.({ error: options.error, reportingError, }), errorReportingObserverTimeout(timeoutMs)); } } catch { // Reporter failure observers must not replace application behavior. } return undefined; } } /** * Redact structured error-report metadata with Beignet's shared sensitive-key * rules. The original exception is intentionally not part of this operation. */ export function redactErrorReportOptions(options, redactionOptions = {}) { return { ...options, user: options.user ? redactValue(options.user, redactionOptions) : options.user, tags: options.tags ? redactValue(options.tags, redactionOptions) : options.tags, contexts: options.contexts ? redactValue(options.contexts, redactionOptions) : options.contexts, extra: options.extra ? redactValue(options.extra, redactionOptions) : options.extra, }; } function withAmbientState(port, options) { const contexts = port.contexts.size > 0 ? Object.fromEntries(port.contexts) : undefined; const hasTags = Object.keys(port.tags).length > 0; if (!port.user && !hasTags && !contexts) return options; return { ...options, user: options?.user ?? port.user ?? undefined, tags: hasTags ? { ...port.tags, ...options?.tags } : options?.tags, contexts: contexts || options?.contexts ? { ...contexts, ...options?.contexts } : undefined, }; } //# sourceMappingURL=index.js.map