diff --git a/src/types/index.ts b/src/types/index.ts index 4a7ae4f..2e6374e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,8 +3,8 @@ * This is the main entry point for all type exports */ -export { TRANSACTION_STATUSES } from './transaction-status.ts'; -export type { TransactionStatus } from './transaction-status.ts'; +export { TRANSACTION_STATUSES, isTerminalTransactionStatus } from './transaction-status.ts'; +export type { TerminalTransactionStatus, TransactionStatus } from './transaction-status.ts'; export type { Customer } from './customer.ts'; diff --git a/src/types/transaction-status.ts b/src/types/transaction-status.ts index ccd3839..d750b1f 100644 --- a/src/types/transaction-status.ts +++ b/src/types/transaction-status.ts @@ -26,3 +26,28 @@ export const TRANSACTION_STATUSES = [ /** Union of all valid transaction statuses, pulled from the array above. */ export type TransactionStatus = (typeof TRANSACTION_STATUSES)[number]; + +const TERMINAL_TRANSACTION_STATUSES = [ + 'completed', + 'refunded', + 'expired', + 'error', + 'no_market', + 'too_small', + 'too_large', +] as const; + +/** Union of transaction statuses that cannot make further progress. */ +export type TerminalTransactionStatus = (typeof TERMINAL_TRANSACTION_STATUSES)[number]; + +/** + * Returns true when a transaction can no longer make progress. + * + * Terminal statuses: + * `completed`, `refunded`, `expired`, `error`, `no_market`, `too_small`, `too_large` + */ +export function isTerminalTransactionStatus( + status: TransactionStatus, +): status is TerminalTransactionStatus { + return (TERMINAL_TRANSACTION_STATUSES as readonly TransactionStatus[]).includes(status); +} diff --git a/tests/types/transaction-status.test.ts b/tests/types/transaction-status.test.ts index a693484..a14db6c 100644 --- a/tests/types/transaction-status.test.ts +++ b/tests/types/transaction-status.test.ts @@ -1,4 +1,9 @@ -import { TRANSACTION_STATUSES, type TransactionStatus } from '@/types/index.ts'; +import { + isTerminalTransactionStatus, + TRANSACTION_STATUSES, + type TerminalTransactionStatus, + type TransactionStatus, +} from '@/types/index.ts'; describe('TransactionStatus', () => { // -- runtime checks on the status array -- @@ -30,6 +35,38 @@ describe('TransactionStatus', () => { expect(unique.size).toBe(TRANSACTION_STATUSES.length); }); + it('returns the expected result for every valid status', () => { + const terminalStatuses = new Set([ + 'completed', + 'refunded', + 'expired', + 'error', + 'no_market', + 'too_small', + 'too_large', + ]); + + for (const status of TRANSACTION_STATUSES) { + expect(isTerminalTransactionStatus(status)).toBe(terminalStatuses.has(status)); + } + }); + + it('acts as a type guard for terminal statuses', () => { + const terminalStatuses = TRANSACTION_STATUSES.filter(isTerminalTransactionStatus); + + const narrowed: TerminalTransactionStatus[] = terminalStatuses; + + expect(narrowed).toEqual([ + 'completed', + 'refunded', + 'expired', + 'error', + 'no_market', + 'too_small', + 'too_large', + ] satisfies TerminalTransactionStatus[]); + }); + // -- compile-time checks (tsc catches these before tests even run) -- it('accepts every valid status', () => {