From 0433a034afae6e0d55f0848b7c970ac0bf56c5d4 Mon Sep 17 00:00:00 2001 From: Steven Luscher 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( 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 { 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 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" }