/** * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright (c) Infonomic Company Limited * * Structured error handling for Byline CMS. * * Follows the Modulus CoreError pattern: every error carries a machine-readable * `code`, optional `details` (included in API responses), and optional `logExtra` * (included only in logs). The `.log(logger)` method logs the error exactly once * and then sets the log level to 'silent' to prevent double-logging up the stack. * * Errors are created via factory functions produced by `createErrorType()`: * * throw ERR_NOT_FOUND({ * message: 'document not found', * details: { documentId }, * }).log(logger) */ import type { DocumentStaleDetails } from '../@types/document-revision.js'; import type { BylineLogger, LogLevel, LogLevelWithSilent } from './logger.js'; export type ErrorReport = { code: string; message: string; details?: Record; }; export type BylineErrorOptions = { /** Short description of the error, for internal consumption. */ message: string; /** Optional underlying cause (e.g. a 3rd-party error). */ cause?: unknown; /** Details to be included in the error report (API responses) AND logs. */ details?: Record; /** If true, stack trace will be captured. */ captureStack?: boolean; /** Log level for this error. Defaults to 'error'. */ logLevel?: LogLevelWithSilent; /** Extra data to include only in logs (never in API responses). */ logExtra?: Record; }; export declare class BylineError extends Error { /** Machine-readable error code (e.g. 'ERR_NOT_FOUND'). */ readonly code: string; /** Additional details included in both error reports and logs. */ readonly details?: Record; /** Extra values included only when logging this error. */ readonly logExtra?: Record; /** * Level at which this error should be logged when caught. Will always be * 'silent' after `.log()` has been called. */ private logLevel; constructor(code: string, options: BylineErrorOptions, errorConstructor?: any); /** * Log this error via the given logger. Sets `logLevel` to 'silent' after * the first call to prevent double-logging when the error is re-thrown. * * Returns `this` for chaining: `throw ERR_X({ ... }).log(logger)` */ log(logger: BylineLogger): typeof this; /** * Serialize this error for API responses. Includes `code`, `message`, * and `details` but deliberately excludes `logExtra`. */ report(): ErrorReport; } /** * Create a reusable error factory for a given error code and default log level. * * ```ts * export const ERR_NOT_FOUND = createErrorType('ERR_NOT_FOUND', 'warn') * * throw ERR_NOT_FOUND({ message: 'document not found' }).log(logger) * ``` */ export declare const createErrorType: (code: string, logLevel?: LogLevel) => (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ErrorCodes: { readonly UNHANDLED: 'ERR_UNHANDLED'; readonly NOT_FOUND: 'ERR_NOT_FOUND'; readonly CONFLICT: 'ERR_CONFLICT'; readonly VALIDATION: 'ERR_VALIDATION'; readonly INVALID_TRANSITION: 'ERR_INVALID_TRANSITION'; readonly PATCH_FAILED: 'ERR_PATCH_FAILED'; readonly DATABASE: 'ERR_DATABASE'; readonly LOCK_CONFLICT: 'ERR_LOCK_CONFLICT'; readonly STORAGE: 'ERR_STORAGE'; readonly READ_BUDGET_EXCEEDED: 'ERR_READ_BUDGET_EXCEEDED'; readonly READ_RECURSION: 'ERR_READ_RECURSION'; readonly PATH_CONFLICT: 'ERR_PATH_CONFLICT'; readonly AUDIT_UNSUPPORTED: 'ERR_AUDIT_UNSUPPORTED'; readonly DOCUMENT_HOOK_COMMITTED: 'ERR_DOCUMENT_HOOK_COMMITTED'; readonly DOCUMENT_STALE: 'ERR_DOCUMENT_STALE'; readonly TREE_HOOK_COMMITTED: 'ERR_TREE_HOOK_COMMITTED'; }; /** Stable message fallback for stale tree-placement `ERR_CONFLICT` errors. */ export declare const TREE_PLACEMENT_STALE_MARKER = "[ERR_CONFLICT:TREE_PLACEMENT_STALE]"; /** Stable message fallback for `ERR_TREE_HOOK_COMMITTED` errors. */ export declare const TREE_HOOK_COMMITTED_MARKER = "[ERR_TREE_HOOK_COMMITTED]"; /** Stable message fallback for `ERR_DOCUMENT_HOOK_COMMITTED` errors. */ export declare const DOCUMENT_HOOK_COMMITTED_MARKER = "[ERR_DOCUMENT_HOOK_COMMITTED]"; export declare const ERR_UNHANDLED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ERR_NOT_FOUND: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ERR_CONFLICT: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; /** Editorial stale state has its own code; other conflicts must not trigger reload. */ export declare const ERR_DOCUMENT_STALE: (options: Omit & { details: DocumentStaleDetails; }) => BylineError; export declare const ERR_VALIDATION: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ERR_INVALID_TRANSITION: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ERR_PATCH_FAILED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ERR_DATABASE: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ERR_LOCK_CONFLICT: (options: BylineErrorOptions) => BylineError; /** Safe, small contract for live errors, reports, and message-only Error transports. */ export declare function getLockConflictDetails(error: unknown): { reason: 'lock_conflict'; rolledBack: true; retryable: true; } | null; export declare const ERR_STORAGE: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ERR_READ_BUDGET_EXCEEDED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; export declare const ERR_READ_RECURSION: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; /** * Thrown when a write attempts to set `path` to a value already used by * another document in the same `(collection, locale)` scope. Surfaces the * underlying Postgres unique-constraint violation on * `byline_document_paths(collection_id, locale, path)`. */ export declare const ERR_PATH_CONFLICT: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; /** * Thrown when an audited write (a document-grain change that must be recorded * atomically — path / available-locales / status / delete) runs against a db * adapter that does not provide both the `withTransaction` capability and the * `commands.audit` / `queries.audit` surfaces. A misconfiguration, not a * user error: an auditability guarantee cannot be honoured non-atomically, so * the write is refused loudly rather than recorded with a gap. See * docs/03-architecture/03-transactions.md and docs/07-auth-and-security/02-auditability.md. */ export declare const ERR_AUDIT_UNSUPPORTED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; /** * A document version committed, but its post-commit `afterCreate` or * `afterUpdate` hook failed. Callers still receive a rejection so existing * SDK error handling keeps working, while hosts can distinguish this from a * rolled-back write and reconcile their UI without replaying the mutation. */ export declare const ERR_DOCUMENT_HOOK_COMMITTED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; /** * A tree mutation and its audit row committed, but its post-commit * `afterTreeChange` work failed. Callers still receive a rejection so they can * reconcile, while transports can distinguish it from a rolled-back mutation. */ export declare const ERR_TREE_HOOK_COMMITTED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError; /** * Code-based classification of a raw database driver error, produced by * `IDbAdapter.classifyError`. The error-side analogue of the storage * `normalizeRow` seam: the adapter canonicalises driver anatomy into these * codes so `@byline/core` can map DB failures to domain errors (e.g. * `ERR_PATH_CONFLICT`) without knowing any driver's error shape. * * Distinct from `ErrorCodes` above: those are thrown `BylineError` codes; * these are returned classification values. Extend with new codes (e.g. a * future `STALE_RECORD` for optimistic-concurrency failures) as consumers * need them. */ export declare const DbErrorCodes: { readonly UNIQUE_VIOLATION: 'DB_UNIQUE_VIOLATION'; readonly FOREIGN_KEY_VIOLATION: 'DB_FOREIGN_KEY_VIOLATION'; readonly LOCK_CONFLICT: 'DB_LOCK_CONFLICT'; readonly UNKNOWN: 'DB_UNKNOWN'; }; export type DbErrorCode = (typeof DbErrorCodes)[keyof typeof DbErrorCodes]; export interface DbErrorClassification { code: DbErrorCode; /** * For `DB_UNIQUE_VIOLATION` and `DB_FOREIGN_KEY_VIOLATION`: the violated * constraint / index name when the driver exposes it (Postgres carries it * structurally; MySQL parses it from the error message). Absent when the * driver surfaces no name. */ constraint?: string; }