From 81da3bcf6922758dc0c41fedfa5091d99baa8178 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Fri, 30 Jan 2026 11:27:46 +0000 Subject: [PATCH] Add missing type parameters to TransactionPlanResult types and helpers --- .changeset/slick-rooms-guess.md | 5 + .../transaction-plan-result-typetest.ts | 92 +++++ .../src/transaction-plan-result.ts | 352 ++++++++++++++---- 3 files changed, 368 insertions(+), 81 deletions(-) create mode 100644 .changeset/slick-rooms-guess.md diff --git a/.changeset/slick-rooms-guess.md b/.changeset/slick-rooms-guess.md new file mode 100644 index 000000000..6fd41e01f --- /dev/null +++ b/.changeset/slick-rooms-guess.md @@ -0,0 +1,5 @@ +--- +'@solana/instruction-plans': minor +--- + +Add missing `TContext`, `TTransactionMessage` and/or `TSingle` type parameters to `TransactionPlanResult` types and helper functions to better preserve type information through narrowing operations. diff --git a/packages/instruction-plans/src/__typetests__/transaction-plan-result-typetest.ts b/packages/instruction-plans/src/__typetests__/transaction-plan-result-typetest.ts index ca65572a2..2b8db5b17 100644 --- a/packages/instruction-plans/src/__typetests__/transaction-plan-result-typetest.ts +++ b/packages/instruction-plans/src/__typetests__/transaction-plan-result-typetest.ts @@ -221,6 +221,15 @@ type CustomContext = { customData: string }; plan satisfies SingleTransactionPlanResult; } } + + // It keeps TSingle information. + { + const plan = null as unknown as SuccessfulTransactionPlanResult; + if (isSingleTransactionPlanResult(plan)) { + plan satisfies SingleTransactionPlanResult; + plan satisfies SuccessfulSingleTransactionPlanResult; + } + } } // [DESCRIBE] assertIsSingleTransactionPlanResult @@ -231,6 +240,14 @@ type CustomContext = { customData: string }; assertIsSingleTransactionPlanResult(plan); plan satisfies SingleTransactionPlanResult; } + + // It keeps TSingle information. + { + const plan = null as unknown as SuccessfulTransactionPlanResult; + assertIsSingleTransactionPlanResult(plan); + plan satisfies SingleTransactionPlanResult; + plan satisfies SuccessfulSingleTransactionPlanResult; + } } // [DESCRIBE] isSuccessfulSingleTransactionPlanResult @@ -305,6 +322,19 @@ type CustomContext = { customData: string }; plan satisfies SequentialTransactionPlanResult; } } + + // It keeps TSingle information. + { + const plan = null as unknown as SuccessfulTransactionPlanResult; + if (isSequentialTransactionPlanResult(plan)) { + plan satisfies SequentialTransactionPlanResult; + plan satisfies SequentialTransactionPlanResult< + TransactionPlanResultContext, + TransactionMessage & TransactionMessageWithFeePayer, + SuccessfulSingleTransactionPlanResult + >; + } + } } // [DESCRIBE] assertIsSequentialTransactionPlanResult @@ -315,6 +345,18 @@ type CustomContext = { customData: string }; assertIsSequentialTransactionPlanResult(plan); plan satisfies SequentialTransactionPlanResult; } + + // It keeps TSingle information. + { + const plan = null as unknown as SuccessfulTransactionPlanResult; + assertIsSequentialTransactionPlanResult(plan); + plan satisfies SequentialTransactionPlanResult; + plan satisfies SequentialTransactionPlanResult< + TransactionPlanResultContext, + TransactionMessage & TransactionMessageWithFeePayer, + SuccessfulSingleTransactionPlanResult + >; + } } // [DESCRIBE] isNonDivisibleTransactionPlanResult @@ -326,6 +368,19 @@ type CustomContext = { customData: string }; plan satisfies SequentialTransactionPlanResult & { divisible: false }; } } + + // It keeps TSingle information. + { + const plan = null as unknown as SuccessfulTransactionPlanResult; + if (isNonDivisibleSequentialTransactionPlanResult(plan)) { + plan satisfies SequentialTransactionPlanResult & { divisible: false }; + plan satisfies SequentialTransactionPlanResult< + TransactionPlanResultContext, + TransactionMessage & TransactionMessageWithFeePayer, + SuccessfulSingleTransactionPlanResult + > & { divisible: false }; + } + } } // [DESCRIBE] assertIsNonDivisibleSequentialTransactionPlanResult @@ -336,6 +391,18 @@ type CustomContext = { customData: string }; assertIsNonDivisibleSequentialTransactionPlanResult(plan); plan satisfies SequentialTransactionPlanResult & { divisible: false }; } + + // It keeps TSingle information. + { + const plan = null as unknown as SuccessfulTransactionPlanResult; + assertIsNonDivisibleSequentialTransactionPlanResult(plan); + plan satisfies SequentialTransactionPlanResult & { divisible: false }; + plan satisfies SequentialTransactionPlanResult< + TransactionPlanResultContext, + TransactionMessage & TransactionMessageWithFeePayer, + SuccessfulSingleTransactionPlanResult + > & { divisible: false }; + } } // [DESCRIBE] isParallelTransactionPlanResult @@ -347,6 +414,19 @@ type CustomContext = { customData: string }; plan satisfies ParallelTransactionPlanResult; } } + + // It keeps TSingle information. + { + const plan = null as unknown as SuccessfulTransactionPlanResult; + if (isParallelTransactionPlanResult(plan)) { + plan satisfies ParallelTransactionPlanResult; + plan satisfies ParallelTransactionPlanResult< + TransactionPlanResultContext, + TransactionMessage & TransactionMessageWithFeePayer, + SuccessfulSingleTransactionPlanResult + >; + } + } } // [DESCRIBE] assertIsParallelTransactionPlanResult @@ -357,6 +437,18 @@ type CustomContext = { customData: string }; assertIsParallelTransactionPlanResult(plan); plan satisfies ParallelTransactionPlanResult; } + + // It keeps TSingle information. + { + const plan = null as unknown as SuccessfulTransactionPlanResult; + assertIsParallelTransactionPlanResult(plan); + plan satisfies ParallelTransactionPlanResult; + plan satisfies ParallelTransactionPlanResult< + TransactionPlanResultContext, + TransactionMessage & TransactionMessageWithFeePayer, + SuccessfulSingleTransactionPlanResult + >; + } } // [DESCRIBE] isSuccessfulTransactionPlanResult diff --git a/packages/instruction-plans/src/transaction-plan-result.ts b/packages/instruction-plans/src/transaction-plan-result.ts index c403ea61e..8c62f215e 100644 --- a/packages/instruction-plans/src/transaction-plan-result.ts +++ b/packages/instruction-plans/src/transaction-plan-result.ts @@ -24,6 +24,7 @@ import { getSignatureFromTransaction, Transaction } from '@solana/transactions'; * original plan. * * @template TContext - The type of the context object that may be passed along with successful results + * @template TTransactionMessage - The type of the transaction message * @template TSingle - The type of single transaction plan results in this tree * * @see {@link SingleTransactionPlanResult} @@ -33,8 +34,16 @@ import { getSignatureFromTransaction, Transaction } from '@solana/transactions'; */ export type TransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContext, - TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult, -> = ParallelTransactionPlanResult | SequentialTransactionPlanResult | TSingle; + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +> = + | ParallelTransactionPlanResult + | SequentialTransactionPlanResult + | TSingle; /** * A {@link TransactionPlanResult} where all single transaction results are successful. @@ -49,6 +58,7 @@ export type TransactionPlanResult< * leaf nodes are successful. * * @template TContext - The type of the context object that may be passed along with successful results + * @template TTransactionMessage - The type of the transaction message * * @see {@link isSuccessfulTransactionPlanResult} * @see {@link assertIsSuccessfulTransactionPlanResult} @@ -56,7 +66,13 @@ export type TransactionPlanResult< */ export type SuccessfulTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContext, -> = TransactionPlanResult>; + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +> = TransactionPlanResult< + TContext, + TTransactionMessage, + SuccessfulSingleTransactionPlanResult +>; /** A context object that may be passed along with successful results. */ export type TransactionPlanResultContext = Record; @@ -72,6 +88,7 @@ export type TransactionPlanResultContext = Record = SingleTransactionPlanResult, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, > = Readonly<{ divisible: boolean; kind: 'sequential'; - plans: TransactionPlanResult[]; + plans: TransactionPlanResult[]; }>; /** @@ -114,6 +136,7 @@ export type SequentialTransactionPlanResult< * You may use the {@link parallelTransactionPlanResult} helper to create objects of this type. * * @template TContext - The type of the context object that may be passed along with successful results + * @template TTransactionMessage - The type of the transaction message * @template TSingle - The type of single transaction plan results in this tree * * @example @@ -129,10 +152,15 @@ export type SequentialTransactionPlanResult< */ export type ParallelTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContext, - TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, > = Readonly<{ kind: 'parallel'; - plans: TransactionPlanResult[]; + plans: TransactionPlanResult[]; }>; /** @@ -317,7 +345,7 @@ export function successfulSingleTransactionPlanResult< transactionMessage: TTransactionMessage, transaction: Transaction, context?: TContext, -): SingleTransactionPlanResult { +): SuccessfulSingleTransactionPlanResult { return Object.freeze({ kind: 'single', message: transactionMessage, @@ -362,7 +390,7 @@ export function successfulSingleTransactionPlanResultFromSignature< transactionMessage: TTransactionMessage, signature: Signature, context?: TContext, -): SingleTransactionPlanResult { +): SuccessfulSingleTransactionPlanResult { return Object.freeze({ kind: 'single', message: transactionMessage, @@ -400,7 +428,10 @@ export function failedSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContext, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, ->(transactionMessage: TTransactionMessage, error: Error): SingleTransactionPlanResult { +>( + transactionMessage: TTransactionMessage, + error: Error, +): FailedSingleTransactionPlanResult { return Object.freeze({ kind: 'single', message: transactionMessage, @@ -430,7 +461,7 @@ export function canceledSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContext, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, ->(transactionMessage: TTransactionMessage): SingleTransactionPlanResult { +>(transactionMessage: TTransactionMessage): CanceledSingleTransactionPlanResult { return Object.freeze({ kind: 'single', message: transactionMessage, @@ -456,7 +487,15 @@ export function canceledSingleTransactionPlanResult< * @see {@link SingleTransactionPlanResult} * @see {@link assertIsSingleTransactionPlanResult} */ -export function isSingleTransactionPlanResult(plan: TransactionPlanResult): plan is SingleTransactionPlanResult { +export function isSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>(plan: TransactionPlanResult): plan is TSingle { return plan.kind === 'single'; } @@ -478,9 +517,15 @@ export function isSingleTransactionPlanResult(plan: TransactionPlanResult): plan * @see {@link SingleTransactionPlanResult} * @see {@link isSingleTransactionPlanResult} */ -export function assertIsSingleTransactionPlanResult( - plan: TransactionPlanResult, -): asserts plan is SingleTransactionPlanResult { +export function assertIsSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>(plan: TransactionPlanResult): asserts plan is TSingle { if (!isSingleTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind, @@ -508,9 +553,13 @@ export function assertIsSingleTransactionPlanResult( * @see {@link SuccessfulSingleTransactionPlanResult} * @see {@link assertIsSuccessfulSingleTransactionPlanResult} */ -export function isSuccessfulSingleTransactionPlanResult( - plan: TransactionPlanResult, -): plan is SuccessfulSingleTransactionPlanResult { +export function isSuccessfulSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + plan: TransactionPlanResult, +): plan is SuccessfulSingleTransactionPlanResult { return plan.kind === 'single' && plan.status.kind === 'successful'; } @@ -532,9 +581,13 @@ export function isSuccessfulSingleTransactionPlanResult( * @see {@link SuccessfulSingleTransactionPlanResult} * @see {@link isSuccessfulSingleTransactionPlanResult} */ -export function assertIsSuccessfulSingleTransactionPlanResult( - plan: TransactionPlanResult, -): asserts plan is SuccessfulSingleTransactionPlanResult { +export function assertIsSuccessfulSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + plan: TransactionPlanResult, +): asserts plan is SuccessfulSingleTransactionPlanResult { if (!isSuccessfulSingleTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind === 'single' ? `${plan.status.kind} single` : plan.kind, @@ -562,9 +615,13 @@ export function assertIsSuccessfulSingleTransactionPlanResult( * @see {@link FailedSingleTransactionPlanResult} * @see {@link assertIsFailedSingleTransactionPlanResult} */ -export function isFailedSingleTransactionPlanResult( - plan: TransactionPlanResult, -): plan is FailedSingleTransactionPlanResult { +export function isFailedSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + plan: TransactionPlanResult, +): plan is FailedSingleTransactionPlanResult { return plan.kind === 'single' && plan.status.kind === 'failed'; } @@ -586,9 +643,13 @@ export function isFailedSingleTransactionPlanResult( * @see {@link FailedSingleTransactionPlanResult} * @see {@link isFailedSingleTransactionPlanResult} */ -export function assertIsFailedSingleTransactionPlanResult( - plan: TransactionPlanResult, -): asserts plan is FailedSingleTransactionPlanResult { +export function assertIsFailedSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + plan: TransactionPlanResult, +): asserts plan is FailedSingleTransactionPlanResult { if (!isFailedSingleTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind === 'single' ? `${plan.status.kind} single` : plan.kind, @@ -616,9 +677,13 @@ export function assertIsFailedSingleTransactionPlanResult( * @see {@link CanceledSingleTransactionPlanResult} * @see {@link assertIsCanceledSingleTransactionPlanResult} */ -export function isCanceledSingleTransactionPlanResult( - plan: TransactionPlanResult, -): plan is CanceledSingleTransactionPlanResult { +export function isCanceledSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + plan: TransactionPlanResult, +): plan is CanceledSingleTransactionPlanResult { return plan.kind === 'single' && plan.status.kind === 'canceled'; } @@ -640,9 +705,13 @@ export function isCanceledSingleTransactionPlanResult( * @see {@link CanceledSingleTransactionPlanResult} * @see {@link isCanceledSingleTransactionPlanResult} */ -export function assertIsCanceledSingleTransactionPlanResult( - plan: TransactionPlanResult, -): asserts plan is CanceledSingleTransactionPlanResult { +export function assertIsCanceledSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + plan: TransactionPlanResult, +): asserts plan is CanceledSingleTransactionPlanResult { if (!isCanceledSingleTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind === 'single' ? `${plan.status.kind} single` : plan.kind, @@ -670,9 +739,17 @@ export function assertIsCanceledSingleTransactionPlanResult( * @see {@link SequentialTransactionPlanResult} * @see {@link assertIsSequentialTransactionPlanResult} */ -export function isSequentialTransactionPlanResult( - plan: TransactionPlanResult, -): plan is SequentialTransactionPlanResult { +export function isSequentialTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>( + plan: TransactionPlanResult, +): plan is SequentialTransactionPlanResult { return plan.kind === 'sequential'; } @@ -694,9 +771,17 @@ export function isSequentialTransactionPlanResult( * @see {@link SequentialTransactionPlanResult} * @see {@link isSequentialTransactionPlanResult} */ -export function assertIsSequentialTransactionPlanResult( - plan: TransactionPlanResult, -): asserts plan is SequentialTransactionPlanResult { +export function assertIsSequentialTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>( + plan: TransactionPlanResult, +): asserts plan is SequentialTransactionPlanResult { if (!isSequentialTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind, @@ -727,9 +812,17 @@ export function assertIsSequentialTransactionPlanResult( * @see {@link SequentialTransactionPlanResult} * @see {@link assertIsNonDivisibleSequentialTransactionPlanResult} */ -export function isNonDivisibleSequentialTransactionPlanResult( - plan: TransactionPlanResult, -): plan is SequentialTransactionPlanResult & { divisible: false } { +export function isNonDivisibleSequentialTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>( + plan: TransactionPlanResult, +): plan is SequentialTransactionPlanResult & { divisible: false } { return plan.kind === 'sequential' && plan.divisible === false; } @@ -754,9 +847,17 @@ export function isNonDivisibleSequentialTransactionPlanResult( * @see {@link SequentialTransactionPlanResult} * @see {@link isNonDivisibleSequentialTransactionPlanResult} */ -export function assertIsNonDivisibleSequentialTransactionPlanResult( - plan: TransactionPlanResult, -): asserts plan is SequentialTransactionPlanResult & { divisible: false } { +export function assertIsNonDivisibleSequentialTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>( + plan: TransactionPlanResult, +): asserts plan is SequentialTransactionPlanResult & { divisible: false } { if (!isNonDivisibleSequentialTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind === 'sequential' ? 'divisible sequential' : plan.kind, @@ -784,7 +885,17 @@ export function assertIsNonDivisibleSequentialTransactionPlanResult( * @see {@link ParallelTransactionPlanResult} * @see {@link assertIsParallelTransactionPlanResult} */ -export function isParallelTransactionPlanResult(plan: TransactionPlanResult): plan is ParallelTransactionPlanResult { +export function isParallelTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>( + plan: TransactionPlanResult, +): plan is ParallelTransactionPlanResult { return plan.kind === 'parallel'; } @@ -806,9 +917,17 @@ export function isParallelTransactionPlanResult(plan: TransactionPlanResult): pl * @see {@link ParallelTransactionPlanResult} * @see {@link isParallelTransactionPlanResult} */ -export function assertIsParallelTransactionPlanResult( - plan: TransactionPlanResult, -): asserts plan is ParallelTransactionPlanResult { +export function assertIsParallelTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>( + plan: TransactionPlanResult, +): asserts plan is ParallelTransactionPlanResult { if (!isParallelTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind, @@ -850,9 +969,13 @@ export function assertIsParallelTransactionPlanResult( * @see {@link assertIsSuccessfulTransactionPlanResult} * @see {@link isSuccessfulSingleTransactionPlanResult} */ -export function isSuccessfulTransactionPlanResult( - plan: TransactionPlanResult, -): plan is SuccessfulTransactionPlanResult { +export function isSuccessfulTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + plan: TransactionPlanResult, +): plan is SuccessfulTransactionPlanResult { return everyTransactionPlanResult( plan, r => !isSingleTransactionPlanResult(r) || isSuccessfulSingleTransactionPlanResult(r), @@ -892,9 +1015,13 @@ export function isSuccessfulTransactionPlanResult( * @see {@link isSuccessfulTransactionPlanResult} * @see {@link assertIsSuccessfulSingleTransactionPlanResult} */ -export function assertIsSuccessfulTransactionPlanResult( - plan: TransactionPlanResult, -): asserts plan is SuccessfulTransactionPlanResult { +export function assertIsSuccessfulTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + plan: TransactionPlanResult, +): asserts plan is SuccessfulTransactionPlanResult { if (!isSuccessfulTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__EXPECTED_SUCCESSFUL_TRANSACTION_PLAN_RESULT, { transactionPlanResult: plan, @@ -909,6 +1036,9 @@ export function assertIsSuccessfulTransactionPlanResult( * returning the first result that satisfies the predicate. It checks the root result * first, then recursively searches through nested results. * + * @template TContext - The type of the context object that may be passed along with successful results + * @template TTransactionMessage - The type of the transaction message + * @template TSingle - The type of single transaction plan results in this tree * @param transactionPlanResult - The transaction plan result tree to search. * @param predicate - A function that returns `true` for the result to find. * @returns The first matching transaction plan result, or `undefined` if no match is found. @@ -933,10 +1063,18 @@ export function assertIsSuccessfulTransactionPlanResult( * @see {@link transformTransactionPlanResult} * @see {@link flattenTransactionPlanResult} */ -export function findTransactionPlanResult( - transactionPlanResult: TransactionPlanResult, - predicate: (result: TransactionPlanResult) => boolean, -): TransactionPlanResult | undefined { +export function findTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>( + transactionPlanResult: TransactionPlanResult, + predicate: (result: TransactionPlanResult) => boolean, +): TransactionPlanResult | undefined { if (predicate(transactionPlanResult)) { return transactionPlanResult; } @@ -959,6 +1097,8 @@ export function findTransactionPlanResult( + transactionPlanResult: TransactionPlanResult, +): FailedSingleTransactionPlanResult { const result = findTransactionPlanResult( transactionPlanResult, r => r.kind === 'single' && r.status.kind === 'failed', @@ -1007,7 +1151,7 @@ export function getFirstFailedSingleTransactionPlanResult( ); } - return result as FailedSingleTransactionPlanResult; + return result as FailedSingleTransactionPlanResult; } /** @@ -1017,6 +1161,9 @@ export function getFirstFailedSingleTransactionPlanResult( * returning `true` only if the predicate returns `true` for every result in the tree * (including the root result and all nested results). * + * @template TContext - The type of the context object that may be passed along with successful results + * @template TTransactionMessage - The type of the transaction message + * @template TSingle - The type of single transaction plan results in this tree * @param transactionPlanResult - The transaction plan result tree to check. * @param predicate - A function that returns `true` if the result satisfies the condition. * @return `true` if every result in the tree satisfies the predicate, `false` otherwise. @@ -1052,9 +1199,17 @@ export function getFirstFailedSingleTransactionPlanResult( * @see {@link transformTransactionPlanResult} * @see {@link flattenTransactionPlanResult} */ -export function everyTransactionPlanResult( - transactionPlanResult: TransactionPlanResult, - predicate: (plan: TransactionPlanResult) => boolean, +export function everyTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>( + transactionPlanResult: TransactionPlanResult, + predicate: (plan: TransactionPlanResult) => boolean, ): boolean { if (!predicate(transactionPlanResult)) { return false; @@ -1124,6 +1279,9 @@ export function transformTransactionPlanResult( * all the single results they contain. It's useful when you need to access all the individual * transaction results, regardless of their organization in the result tree (parallel or sequential). * + * @template TContext - The type of the context object that may be passed along with successful results + * @template TTransactionMessage - The type of the transaction message + * @template TSingle - The type of single transaction plan results in this tree * @param result - The transaction plan result to extract single results from * @returns An array of all single transaction plan results contained in the tree * @@ -1145,7 +1303,15 @@ export function transformTransactionPlanResult( * @see {@link everyTransactionPlanResult} * @see {@link transformTransactionPlanResult} */ -export function flattenTransactionPlanResult(result: TransactionPlanResult): SingleTransactionPlanResult[] { +export function flattenTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, + TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< + TContext, + TTransactionMessage + >, +>(result: TransactionPlanResult): TSingle[] { if (result.kind === 'single') { return [result]; } @@ -1157,17 +1323,27 @@ export function flattenTransactionPlanResult(result: TransactionPlanResult): Sin */ export type SuccessfulSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContext, -> = SingleTransactionPlanResult & { status: { kind: 'successful' } }; + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +> = SingleTransactionPlanResult & { status: { kind: 'successful' } }; /** * A {@link SingleTransactionPlanResult} with 'failed' status. */ -export type FailedSingleTransactionPlanResult = SingleTransactionPlanResult & { status: { kind: 'failed' } }; +export type FailedSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +> = SingleTransactionPlanResult & { status: { kind: 'failed' } }; /** * A {@link SingleTransactionPlanResult} with 'canceled' status. */ -export type CanceledSingleTransactionPlanResult = SingleTransactionPlanResult & { status: { kind: 'canceled' } }; +export type CanceledSingleTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +> = SingleTransactionPlanResult & { status: { kind: 'canceled' } }; /** * A summary of a {@link TransactionPlanResult}, categorizing transactions by their execution status. @@ -1176,37 +1352,51 @@ export type CanceledSingleTransactionPlanResult = SingleTransactionPlanResult & * - `failedTransactions`: An array of failed transactions, each including the error that caused the failure. * - `canceledTransactions`: An array of canceled transactions. */ -export type TransactionPlanResultSummary = Readonly<{ - canceledTransactions: CanceledSingleTransactionPlanResult[]; - failedTransactions: FailedSingleTransactionPlanResult[]; +export type TransactionPlanResultSummary< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +> = Readonly<{ + canceledTransactions: CanceledSingleTransactionPlanResult[]; + failedTransactions: FailedSingleTransactionPlanResult[]; successful: boolean; - successfulTransactions: SuccessfulSingleTransactionPlanResult[]; + successfulTransactions: SuccessfulSingleTransactionPlanResult[]; }>; /** * Summarize a {@link TransactionPlanResult} into a {@link TransactionPlanResultSummary}. + * + * @template TContext - The type of the context object that may be passed along with successful results + * @template TTransactionMessage - The type of the transaction message * @param result The transaction plan result to summarize * @returns A summary of the transaction plan result */ -export function summarizeTransactionPlanResult(result: TransactionPlanResult): TransactionPlanResultSummary { - const successfulTransactions: TransactionPlanResultSummary['successfulTransactions'] = []; - const failedTransactions: TransactionPlanResultSummary['failedTransactions'] = []; - const canceledTransactions: TransactionPlanResultSummary['canceledTransactions'] = []; +export function summarizeTransactionPlanResult< + TContext extends TransactionPlanResultContext = TransactionPlanResultContext, + TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & + TransactionMessageWithFeePayer, +>( + result: TransactionPlanResult, +): TransactionPlanResultSummary { + type Out = TransactionPlanResultSummary; + const successfulTransactions: Out['successfulTransactions'] = []; + const failedTransactions: Out['failedTransactions'] = []; + const canceledTransactions: Out['canceledTransactions'] = []; const flattenedResults = flattenTransactionPlanResult(result); for (const singleResult of flattenedResults) { switch (singleResult.status.kind) { case 'successful': { - successfulTransactions.push(singleResult as SuccessfulSingleTransactionPlanResult); + successfulTransactions.push(singleResult as Out['successfulTransactions'][number]); break; } case 'failed': { - failedTransactions.push(singleResult as FailedSingleTransactionPlanResult); + failedTransactions.push(singleResult as Out['failedTransactions'][number]); break; } case 'canceled': { - canceledTransactions.push(singleResult as CanceledSingleTransactionPlanResult); + canceledTransactions.push(singleResult as Out['canceledTransactions'][number]); break; } }