Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View 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 });
}