From 0433a034afae6e0d55f0848b7c970ac0bf56c5d4 Mon Sep 17 00:00:00 2001
From: Steven Luscher <steven.luscher@anza.xyz>
Date: Thu, 16 Jan 2025 22:49:50 +0000
Subject: [PATCH] Document `@solana/errors` with TypeDoc

---
 packages/errors/src/codes.ts             |  6 ++-
 packages/errors/src/context.ts           | 14 +++--
 packages/errors/src/error.ts             | 59 +++++++++++++++++++++
 packages/errors/src/index.ts             | 65 ++++++++++++++++++++++++
 packages/errors/src/instruction-error.ts |  3 ++
 packages/errors/src/json-rpc-error.ts    |  5 +-
 packages/errors/src/messages.ts          | 14 +++--
 packages/errors/typedoc.json             |  3 +-
 8 files changed, 156 insertions(+), 13 deletions(-)

diff --git a/packages/errors/src/codes.ts b/packages/errors/src/codes.ts
index 3a6c104cd..e94ffcfec 100644
--- a/packages/errors/src/codes.ts
+++ b/packages/errors/src/codes.ts
@@ -2,6 +2,8 @@
  * To add a new error, follow the instructions at
  * https://github.com/anza-xyz/solana-web3.js/tree/main/packages/errors/#adding-a-new-error
  *
+ * @module
+ * @privateRemarks
  * WARNING:
  *   - Don't remove error codes
  *   - Don't change or reorder error codes.
@@ -302,6 +304,7 @@ export const SOLANA_ERROR__INVARIANT_VIOLATION__DATA_PUBLISHER_CHANNEL_UNIMPLEME
 /**
  * A union of every Solana error code
  *
+ * @privateRemarks
  * You might be wondering why this is not a TypeScript enum or const enum.
  *
  * One of the goals of this library is to enable people to use some or none of it without having to
@@ -541,6 +544,7 @@ export type SolanaErrorCode =
     | typeof SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_VOTE_COST_LIMIT;
 
 /**
- * Errors of this type are understood to have an optional `SolanaError` nested inside as `cause`.
+ * Errors of this type are understood to have an optional {@link SolanaError} nested inside as
+ * `cause`.
  */
 export type SolanaErrorCodeWithCause = typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE;
diff --git a/packages/errors/src/context.ts b/packages/errors/src/context.ts
index e134fa49d..78d54bb01 100644
--- a/packages/errors/src/context.ts
+++ b/packages/errors/src/context.ts
@@ -1,3 +1,11 @@
+/**
+ * To add a new error, follow the instructions at
+ * https://github.com/anza-xyz/solana-web3.js/tree/main/packages/errors/#adding-a-new-error
+ *
+ * @privateRemarks
+ * WARNING:
+ *   - Don't change or remove members of an error's context.
+ */
 import {
     SOLANA_ERROR__ACCOUNTS__ACCOUNT_NOT_FOUND,
     SOLANA_ERROR__ACCOUNTS__EXPECTED_ALL_ACCOUNTS_TO_BE_DECODED,
@@ -167,11 +175,7 @@ interface ReadonlyUint8Array extends Omit<Uint8Array, TypedArrayMutablePropertie
 }
 
 /**
- * To add a new error, follow the instructions at
- * https://github.com/anza-xyz/solana-web3.js/tree/main/packages/errors/#adding-a-new-error
- *
- * WARNING:
- *   - Don't change or remove members of an error's context.
+ * A map of every {@link SolanaError} code to the type of its `context` property.
  */
 export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
     BasicInstructionErrorContext<
diff --git a/packages/errors/src/error.ts b/packages/errors/src/error.ts
index 230b402ed..a0a9c1bb8 100644
--- a/packages/errors/src/error.ts
+++ b/packages/errors/src/error.ts
@@ -2,8 +2,54 @@ import { SolanaErrorCode, SolanaErrorCodeWithCause } from './codes';
 import { SolanaErrorContext } from './context';
 import { getErrorMessage } from './message-formatter';
 
+/**
+ * A type guard that returns `true` if the input is a {@link SolanaError}, optionally with a
+ * particular error code.
+ *
+ * When the `code` argument is supplied and the input is a {@link SolanaError}, TypeScript will
+ * refine the error's {@link SolanaError#context | `context`} property to the type associated with
+ * that error code. You can use that context to render useful error messages, or to make
+ * context-aware decisions that help your application to recover from the error.
+ *
+ * @example
+ * ```ts
+ * import {
+ *     SOLANA_ERROR__TRANSACTION__MISSING_SIGNATURE,
+ *     SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING,
+ *     isSolanaError,
+ * } from '@solana/errors';
+ * import { assertTransactionIsFullySigned, getSignatureFromTransaction } from '@solana/transactions';
+ *
+ * try {
+ *     const transactionSignature = getSignatureFromTransaction(tx);
+ *     assertTransactionIsFullySigned(tx);
+ *     /* ... *\/
+ * } catch (e) {
+ *     if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING)) {
+ *         displayError(
+ *             "We can't send this transaction without signatures for these addresses:\n- %s",
+ *             // The type of the `context` object is now refined to contain `addresses`.
+ *             e.context.addresses.join('\n- '),
+ *         );
+ *         return;
+ *     } else if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING)) {
+ *         if (!tx.feePayer) {
+ *             displayError('Choose a fee payer for this transaction before sending it');
+ *         } else {
+ *             displayError('The fee payer still needs to sign for this transaction');
+ *         }
+ *         return;
+ *     }
+ *     throw e;
+ * }
+ * ```
+ */
 export function isSolanaError<TErrorCode extends SolanaErrorCode>(
     e: unknown,
+    /**
+     * When supplied, this function will require that the input is a {@link SolanaError} _and_ that
+     * its error code is exactly this value.
+     */
     code?: TErrorCode,
 ): e is SolanaError<TErrorCode> {
     const isSolanaError = e instanceof Error && e.name === 'SolanaError';
@@ -22,8 +68,21 @@ type SolanaErrorCodedContext = Readonly<{
     };
 }>;
 
+/**
+ * Encapsulates an error's stacktrace, a Solana-specific numeric code that indicates what went
+ * wrong, and optional context if the type of error indicated by the code supports it.
+ */
 export class SolanaError<TErrorCode extends SolanaErrorCode = SolanaErrorCode> extends Error {
+    /**
+     * Indicates the root cause of this {@link SolanaError}, if any.
+     *
+     * For example, a transaction error might have an instruction error as its root cause. In this
+     * case, you will be able to access the instruction error on the transaction error as `cause`.
+     */
     readonly cause?: TErrorCode extends SolanaErrorCodeWithCause ? SolanaError : unknown = this.cause;
+    /**
+     * Contains context that can assist in understanding or recovering from a {@link SolanaError}.
+     */
     readonly context: SolanaErrorCodedContext[TErrorCode];
     constructor(
         ...[code, contextAndErrorOptions]: SolanaErrorContext[TErrorCode] extends undefined
diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts
index 9498a2f37..67982f686 100644
--- a/packages/errors/src/index.ts
+++ b/packages/errors/src/index.ts
@@ -1,3 +1,68 @@
+/**
+ * This package brings together every error message across all Solana JavaScript modules.
+ *
+ * # Reading error messages
+ *
+ * ## In development mode
+ *
+ * When your bundler sets the constant `__DEV__` to `true`, every error message will be included in
+ * the bundle. As such, you will be able to read them in plain language wherever they appear.
+ *
+ * > [!WARNING]
+ * > The size of your JavaScript bundle will increase significantly with the inclusion of every
+ * > error message in development mode. Be sure to build your bundle with `__DEV__` set to `false`
+ * > when you go to production.
+ *
+ * ## In production mode
+ *
+ * When your bundler sets the constant `__DEV__` to `false`, error messages will be stripped from
+ * the bundle to save space. Only the error code will appear when an error is encountered. Follow
+ * the instructions in the error message to convert the error code back to the human-readable error
+ * message.
+ *
+ * For instance, to recover the error text for the error with code `123`:
+ *
+ * ```package-install
+ * npx @solana/errors decode -- 123
+ * ```
+ *
+ * > [!IMPORTANT]
+ * > The string representation of a {@link SolanaError} should not be shown to users. Developers
+ * > should use {@link isSolanaError} to distinguish the type of a thrown error, then show a custom,
+ * > localized error message appropriate for their application's audience. Custom error messages
+ * > should use the error's {@link SolanaError#context | `context`} if it would help the reader
+ * > understand what happened and/or what to do next.
+ *
+ * # Adding a new error
+ *
+ * 1. Add a new exported error code constant to `src/codes.ts`.
+ * 2. Add that new constant to the {@link SolanaErrorCode} union in `src/codes.ts`.
+ * 3. If you would like the new error to encapsulate context about the error itself (eg. the public
+ *    keys for which a transaction is missing signatures) define the shape of that context in
+ *    `src/context.ts`.
+ * 4. Add the error's message to `src/messages.ts`. Any context values that you defined above will
+ *    be interpolated into the message wherever you write `$key`, where `key` is the index of a
+ *    value in the context (eg. ``'Missing a signature for account `$address`'``).
+ * 5. Publish a new version of `@solana/errors`.
+ * 6. Bump the version of `@solana/errors` in the package from which the error is thrown.
+ *
+ * # Removing an error message
+ *
+ * -   Don't remove errors.
+ * -   Don't change the meaning of an error message.
+ * -   Don't change or reorder error codes.
+ * -   Don't change or remove members of an error's context.
+ *
+ * When an older client throws an error, we want to make sure that they can always decode the error.
+ * If you make any of the changes above, old clients will, by definition, not have received your
+ * changes. This could make the errors that they throw impossible to decode going forward.
+ *
+ * # Catching errors
+ *
+ * See {@link isSolanaError} for an example of how to handle a caught {@link SolanaError}.
+ *
+ * @packageDocumentation
+ */
 export * from './codes';
 export * from './error';
 export * from './json-rpc-error';
diff --git a/packages/errors/src/instruction-error.ts b/packages/errors/src/instruction-error.ts
index 8108f7ce0..1a18110ec 100644
--- a/packages/errors/src/instruction-error.ts
+++ b/packages/errors/src/instruction-error.ts
@@ -67,6 +67,9 @@ const ORDERED_ERROR_NAMES = [
 ];
 
 export function getSolanaErrorFromInstructionError(
+    /**
+     * The index of the instruction inside the transaction.
+     */
     index: bigint | number,
     instructionError: string | { [key: string]: unknown },
 ): SolanaError {
diff --git a/packages/errors/src/json-rpc-error.ts b/packages/errors/src/json-rpc-error.ts
index 8e7997d75..42b139b0e 100644
--- a/packages/errors/src/json-rpc-error.ts
+++ b/packages/errors/src/json-rpc-error.ts
@@ -29,7 +29,10 @@ interface RpcErrorResponse {
 
 type TransactionError = string | { [key: string]: unknown };
 
-// Keep in sync with https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/response.rs
+/**
+ * Keep in sync with https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/response.rs
+ * @hidden
+ */
 export interface RpcSimulateTransactionResult {
     accounts:
         | ({
diff --git a/packages/errors/src/messages.ts b/packages/errors/src/messages.ts
index eaa2e5694..c32776f1e 100644
--- a/packages/errors/src/messages.ts
+++ b/packages/errors/src/messages.ts
@@ -1,3 +1,10 @@
+/**
+ * To add a new error, follow the instructions at
+ * https://github.com/anza-xyz/solana-web3.js/tree/main/packages/errors#adding-a-new-error
+ *
+ * WARNING:
+ *   - Don't change the meaning of an error message.
+ */
 import {
     SOLANA_ERROR__ACCOUNTS__ACCOUNT_NOT_FOUND,
     SOLANA_ERROR__ACCOUNTS__EXPECTED_ALL_ACCOUNTS_TO_BE_DECODED,
@@ -227,11 +234,8 @@ import {
 } from './codes';
 
 /**
- * To add a new error, follow the instructions at
- * https://github.com/anza-xyz/solana-web3.js/tree/main/packages/errors#adding-a-new-error
- *
- * WARNING:
- *   - Don't change the meaning of an error message.
+ * A map of every {@link SolanaError} code to the error message shown to developers in development
+ * mode.
  */
 export const SolanaErrorMessages: Readonly<{
     // This type makes this data structure exhaustive with respect to `SolanaErrorCode`.
diff --git a/packages/errors/typedoc.json b/packages/errors/typedoc.json
index d99f37ef3..3a90a65cc 100644
--- a/packages/errors/typedoc.json
+++ b/packages/errors/typedoc.json
@@ -1,5 +1,6 @@
 {
     "$schema": "https://typedoc.org/schema.json",
     "extends": ["../typedoc.base.json"],
-    "entryPoints": ["src/index.ts"]
+    "entryPoints": ["src/index.ts"],
+    "readme": "none"
 }