Initial Commit
This commit is contained in:
133
packages/foundation/data/clipboard/src/errors/errors.ts
Normal file
133
packages/foundation/data/clipboard/src/errors/errors.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import type { SuaveplanErrorOptions } from "@suaveplan/error";
|
||||
import { defineCodes, SuaveplanError } from "@suaveplan/error";
|
||||
|
||||
/**
|
||||
* Stable error code registry for `@suaveplan/clipboard`. Every error path
|
||||
* the package can take surfaces one of these codes so consumers (loggers,
|
||||
* retry policies, UI surfaces, telemetry pipelines) can classify failures
|
||||
* programmatically without parsing message strings.
|
||||
*/
|
||||
export const CLIPBOARD_CODES = defineCodes("CLIPBOARD", [
|
||||
"API_UNAVAILABLE",
|
||||
"PERMISSION_DENIED",
|
||||
"FALLBACK_FAILED",
|
||||
"INSECURE_CONTEXT",
|
||||
"NO_CONTENT",
|
||||
"READ_FAILED",
|
||||
"DOCUMENT_UNAVAILABLE",
|
||||
"QUEUE_DRAINED",
|
||||
] as const);
|
||||
|
||||
/** Union of every error code emitted by `@suaveplan/clipboard`. */
|
||||
export type ClipboardErrorCode =
|
||||
(typeof CLIPBOARD_CODES)[keyof typeof CLIPBOARD_CODES];
|
||||
|
||||
/** Per-code metadata for HTTP / retry / severity classification. */
|
||||
export interface ClipboardCodeMetadata {
|
||||
readonly httpStatus: number;
|
||||
readonly severity: "info" | "warning" | "error";
|
||||
readonly retryable: boolean;
|
||||
readonly description: string;
|
||||
}
|
||||
|
||||
/** Mapping of every {@link ClipboardErrorCode} to its metadata. */
|
||||
export const CLIPBOARD_ERROR_METADATA: Readonly<
|
||||
Record<ClipboardErrorCode, ClipboardCodeMetadata>
|
||||
> = Object.freeze({
|
||||
[CLIPBOARD_CODES.API_UNAVAILABLE]: {
|
||||
httpStatus: 501,
|
||||
severity: "warning",
|
||||
retryable: false,
|
||||
description:
|
||||
"navigator.clipboard is undefined; the host environment does not expose the async Clipboard API.",
|
||||
},
|
||||
[CLIPBOARD_CODES.PERMISSION_DENIED]: {
|
||||
httpStatus: 403,
|
||||
severity: "warning",
|
||||
retryable: false,
|
||||
description:
|
||||
"The Permissions API reported state=denied for clipboard-read or clipboard-write.",
|
||||
},
|
||||
[CLIPBOARD_CODES.FALLBACK_FAILED]: {
|
||||
httpStatus: 500,
|
||||
severity: "error",
|
||||
retryable: false,
|
||||
description:
|
||||
"document.execCommand('copy') returned false or threw during the textarea fallback.",
|
||||
},
|
||||
[CLIPBOARD_CODES.INSECURE_CONTEXT]: {
|
||||
httpStatus: 400,
|
||||
severity: "error",
|
||||
retryable: false,
|
||||
description:
|
||||
"window.isSecureContext is false; clipboard operations require HTTPS or localhost when requireSecureContext is enabled.",
|
||||
},
|
||||
[CLIPBOARD_CODES.NO_CONTENT]: {
|
||||
httpStatus: 400,
|
||||
severity: "warning",
|
||||
retryable: false,
|
||||
description:
|
||||
"copyRichContent was called with empty ClipboardData (no text, html, image, or blobs).",
|
||||
},
|
||||
[CLIPBOARD_CODES.READ_FAILED]: {
|
||||
httpStatus: 500,
|
||||
severity: "error",
|
||||
retryable: true,
|
||||
description:
|
||||
"Clipboard read failed; the underlying API rejected or the paste-event fallback timed out.",
|
||||
},
|
||||
[CLIPBOARD_CODES.DOCUMENT_UNAVAILABLE]: {
|
||||
httpStatus: 501,
|
||||
severity: "error",
|
||||
retryable: false,
|
||||
description:
|
||||
"document is undefined; the fallback path cannot run outside a DOM environment.",
|
||||
},
|
||||
[CLIPBOARD_CODES.QUEUE_DRAINED]: {
|
||||
httpStatus: 503,
|
||||
severity: "warning",
|
||||
retryable: true,
|
||||
description:
|
||||
"ClipboardManager was disposed while a queued operation was still pending.",
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Base error class for every failure raised by `@suaveplan/clipboard`.
|
||||
* Extends {@link SuaveplanError} so consumers receive cause-chain traversal,
|
||||
* Zod-validated wire format, redaction hooks, and the OTel span-event
|
||||
* adapter for free.
|
||||
*/
|
||||
export class ClipboardError extends SuaveplanError<ClipboardErrorCode> {
|
||||
constructor(
|
||||
code: ClipboardErrorCode,
|
||||
message: string,
|
||||
options?: SuaveplanErrorOptions
|
||||
) {
|
||||
super(code, message, options);
|
||||
this.name = "ClipboardError";
|
||||
}
|
||||
|
||||
/** Metadata for this error's code. */
|
||||
get metadata(): ClipboardCodeMetadata {
|
||||
return CLIPBOARD_ERROR_METADATA[this.code];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lift an unknown thrown value into a {@link ClipboardError}. Used at every
|
||||
* `catch` site so call sites can rely on the typed error contract.
|
||||
*/
|
||||
export function asClipboardError(
|
||||
code: ClipboardErrorCode,
|
||||
cause: unknown,
|
||||
fallbackMessage: string
|
||||
): ClipboardError {
|
||||
const message =
|
||||
cause instanceof Error && cause.message
|
||||
? cause.message
|
||||
: fallbackMessage;
|
||||
const causeError =
|
||||
cause instanceof Error ? cause : new Error(String(cause));
|
||||
return new ClipboardError(code, message, { cause: causeError });
|
||||
}
|
||||
Reference in New Issue
Block a user