@beignet/core
Version:
Core framework primitives for Beignet
172 lines • 5.53 kB
JavaScript
/**
* @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