diff --git a/.changeset/khaki-tables-give.md b/.changeset/khaki-tables-give.md deleted file mode 100644 index 8000eb0fe..000000000 --- a/.changeset/khaki-tables-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@penumbra-zone/getters': major ---- - -don't export optional getters diff --git a/.changeset/slow-seas-fix.md b/.changeset/slow-seas-fix.md deleted file mode 100644 index 3ab6685d6..000000000 --- a/.changeset/slow-seas-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@penumbra-zone/getters': major ---- - -improve getter type diff --git a/apps/minifront/src/components/dashboard/assets-table/equivalent-values.tsx b/apps/minifront/src/components/dashboard/assets-table/equivalent-values.tsx index 994efe2cd..0ce194a8e 100644 --- a/apps/minifront/src/components/dashboard/assets-table/equivalent-values.tsx +++ b/apps/minifront/src/components/dashboard/assets-table/equivalent-values.tsx @@ -4,7 +4,7 @@ import { getDisplayDenomFromView, getEquivalentValues } from '@penumbra-zone/get import { ValueViewComponent } from '@penumbra-zone/ui/components/ui/value'; export const EquivalentValues = ({ valueView }: { valueView?: ValueView }) => { - const equivalentValuesAsValueViews = (getEquivalentValues.optional(valueView) ?? []).map( + const equivalentValuesAsValueViews = (getEquivalentValues.optional()(valueView) ?? []).map( asValueView, ); diff --git a/apps/minifront/src/components/dashboard/assets-table/index.tsx b/apps/minifront/src/components/dashboard/assets-table/index.tsx index 6688f2240..6c7658472 100644 --- a/apps/minifront/src/components/dashboard/assets-table/index.tsx +++ b/apps/minifront/src/components/dashboard/assets-table/index.tsx @@ -14,7 +14,7 @@ import { EquivalentValues } from './equivalent-values'; import { Fragment } from 'react'; import { PagePath } from '../../metadata/paths'; import { Link } from 'react-router-dom'; -import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; +import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response'; import { getAddressIndex } from '@penumbra-zone/getters/address-view'; import { BalancesByAccount, groupByAccount, useBalancesResponses } from '../../../state/shared'; import { AbridgedZQueryState } from '@penumbra-zone/zquery/src/types'; @@ -24,7 +24,7 @@ import { LineWave } from 'react-loader-spinner'; import { cn } from '@penumbra-zone/ui/lib/utils'; const getTradeLink = (balance: BalancesResponse): string => { - const metadata = getMetadataFromBalancesResponse.optional(balance); + const metadata = getMetadataFromBalancesResponseOptional(balance); const accountIndex = getAddressIndex(balance.accountAddress).account; const accountQuery = accountIndex ? `&account=${accountIndex}` : ''; return metadata ? `${PagePath.SWAP}?from=${metadata.symbol}${accountQuery}` : PagePath.SWAP; diff --git a/apps/minifront/src/components/shared/input-token.tsx b/apps/minifront/src/components/shared/input-token.tsx index 9e3232d8b..2ce3608bf 100644 --- a/apps/minifront/src/components/shared/input-token.tsx +++ b/apps/minifront/src/components/shared/input-token.tsx @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; import { getDisplayDenomExponent } from '@penumbra-zone/getters/metadata'; -import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; +import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response'; import { BalanceValueView } from '@penumbra-zone/ui/components/ui/balance-value-view'; import { cn } from '@penumbra-zone/ui/lib/utils'; import BalanceSelector from './selectors/balance-selector'; @@ -38,7 +38,7 @@ export default function InputToken({ loading, }: InputTokenProps) { const tokenExponent = useMemo(() => { - return getDisplayDenomExponent.optional(getMetadataFromBalancesResponse.optional(selection)); + return getDisplayDenomExponent.optional()(getMetadataFromBalancesResponseOptional(selection)); }, [selection]); const setInputToBalanceMax = () => { diff --git a/apps/minifront/src/components/shared/non-native-fee-warning.tsx b/apps/minifront/src/components/shared/non-native-fee-warning.tsx index ffc6bde0a..4c150c749 100644 --- a/apps/minifront/src/components/shared/non-native-fee-warning.tsx +++ b/apps/minifront/src/components/shared/non-native-fee-warning.tsx @@ -24,7 +24,7 @@ const hasTokenBalance = ({ gasPrices: GasPrices[]; stakingAssetMetadata?: Metadata; }): boolean => { - const account = getAddressIndex.optional(source)?.account; + const account = getAddressIndex.optional()(source)?.account; if (typeof account === 'undefined') { return false; } @@ -33,9 +33,9 @@ const hasTokenBalance = ({ const hasStakingToken = balancesResponses.some( asset => getAssetIdFromValueView - .optional(asset.balanceView) - ?.equals(getAssetId.optional(stakingAssetMetadata)) && - getAddressIndex.optional(asset)?.account === account, + .optional()(asset.balanceView) + ?.equals(getAssetId.optional()(stakingAssetMetadata)) && + getAddressIndex.optional()(asset)?.account === account, ); if (hasStakingToken) { @@ -43,7 +43,7 @@ const hasTokenBalance = ({ } const accountAssets = balancesResponses.filter( - balance => getAddressIndex.optional(balance)?.account === account, + balance => getAddressIndex.optional()(balance)?.account === account, ); // Finds the alt tokens in the user's account balances that can be used for fees const hasAltTokens = accountAssets.some(balance => { @@ -54,7 +54,7 @@ const hasTokenBalance = ({ } return gasPrices.some(price => - price.assetId?.equals(getAssetIdFromBalancesResponse.optional(balance)), + price.assetId?.equals(getAssetIdFromBalancesResponse.optional()(balance)), ); }); diff --git a/apps/minifront/src/components/shared/selectors/balance-item.tsx b/apps/minifront/src/components/shared/selectors/balance-item.tsx index d68f3ef6b..fa2bd665a 100644 --- a/apps/minifront/src/components/shared/selectors/balance-item.tsx +++ b/apps/minifront/src/components/shared/selectors/balance-item.tsx @@ -1,6 +1,9 @@ import { BalanceOrMetadata, isBalance, isMetadata } from './helpers'; import { getAddressIndex } from '@penumbra-zone/getters/address-view'; -import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; +import { + getMetadataFromBalancesResponse, + getMetadataFromBalancesResponseOptional, +} from '@penumbra-zone/getters/balances-response'; import { useMemo } from 'react'; import { DialogClose } from '@penumbra-zone/ui/components/ui/dialog'; import { cn } from '@penumbra-zone/ui/lib/utils'; @@ -19,8 +22,8 @@ export const BalanceItem = ({ asset, value, onSelect }: BalanceItemProps) => { const account = isBalance(asset) ? getAddressIndex(asset.accountAddress).account : undefined; const metadataFromAsset = isMetadata(asset) ? asset - : getMetadataFromBalancesResponse.optional(asset); - const metadataFromValue = getMetadataFromBalancesResponse.optional(value); + : getMetadataFromBalancesResponseOptional(asset); + const metadataFromValue = getMetadataFromBalancesResponse.optional()(value); const isSelected = useMemo(() => { if (!value) { diff --git a/apps/minifront/src/components/shared/selectors/helpers.ts b/apps/minifront/src/components/shared/selectors/helpers.ts index 639531802..94f7c0ae4 100644 --- a/apps/minifront/src/components/shared/selectors/helpers.ts +++ b/apps/minifront/src/components/shared/selectors/helpers.ts @@ -2,7 +2,7 @@ import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_ import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; import { getAddressIndex, - getMetadataFromBalancesResponse, + getMetadataFromBalancesResponseOptional, } from '@penumbra-zone/getters/balances-response'; import { useEffect } from 'react'; @@ -22,7 +22,7 @@ export const mergeBalancesAndAssets = ( ): BalanceOrMetadata[] => { const filteredAssets = assets.filter(asset => { return !balances.some(balance => { - const balanceMetadata = getMetadataFromBalancesResponse.optional(balance); + const balanceMetadata = getMetadataFromBalancesResponseOptional(balance); return balanceMetadata?.equals(asset); }); }); @@ -43,10 +43,10 @@ export const useSyncSelectedBalance = ({ if (value) { const matchedValue = balances?.find(balance => { return ( - getAddressIndex.optional(balance)?.equals(getAddressIndex.optional(value)) && - getMetadataFromBalancesResponse - .optional(balance) - ?.equals(getMetadataFromBalancesResponse.optional(value)) + getAddressIndex.optional()(balance)?.equals(getAddressIndex.optional()(value)) && + getMetadataFromBalancesResponseOptional(balance)?.equals( + getMetadataFromBalancesResponseOptional(value), + ) ); }); if (matchedValue && !matchedValue.equals(value)) { diff --git a/apps/minifront/src/components/shared/selectors/search-filters.ts b/apps/minifront/src/components/shared/selectors/search-filters.ts index 91f3f4ae9..cb9a879e8 100644 --- a/apps/minifront/src/components/shared/selectors/search-filters.ts +++ b/apps/minifront/src/components/shared/selectors/search-filters.ts @@ -7,7 +7,7 @@ import { getValueViewCaseFromBalancesResponse } from '@penumbra-zone/getters/bal export const balanceBySearch = (search: string) => (balancesResponse: BalancesResponse): boolean => - getValueViewCaseFromBalancesResponse.optional(balancesResponse) === 'knownAssetId' && + getValueViewCaseFromBalancesResponse.optional()(balancesResponse) === 'knownAssetId' && (getDisplayDenomFromView(balancesResponse.balanceView) .toLocaleLowerCase() .includes(search.toLocaleLowerCase()) || diff --git a/apps/minifront/src/components/staking/account/delegation-value-view/validator-info-component.tsx b/apps/minifront/src/components/staking/account/delegation-value-view/validator-info-component.tsx index c217018f4..fa2d450bf 100644 --- a/apps/minifront/src/components/staking/account/delegation-value-view/validator-info-component.tsx +++ b/apps/minifront/src/components/staking/account/delegation-value-view/validator-info-component.tsx @@ -35,7 +35,7 @@ export const ValidatorInfoComponent = ({ const showTooltips = useStore(state => !state.staking.loading); const validator = getValidator(validatorInfo); const identityKey = getIdentityKeyFromValidatorInfo(validatorInfo); - const state = getValidatorState.optional(validatorInfo); + const state = getValidatorState.optional()(validatorInfo); return ( diff --git a/apps/minifront/src/components/swap/swap-form/output/estimated-output-explanation.tsx b/apps/minifront/src/components/swap/swap-form/output/estimated-output-explanation.tsx index cd7399e5f..54ad934a5 100644 --- a/apps/minifront/src/components/swap/swap-form/output/estimated-output-explanation.tsx +++ b/apps/minifront/src/components/swap/swap-form/output/estimated-output-explanation.tsx @@ -24,9 +24,9 @@ export const EstimatedOutputExplanation = () => { const formattedAmount = formatAmount({ amount: estimatedOutput, - exponent: getDisplayDenomExponent.optional(assetOut), + exponent: getDisplayDenomExponent.optional()(assetOut), }); - const assetInSymbol = getSymbolFromValueView.optional(assetIn?.balanceView); + const assetInSymbol = getSymbolFromValueView.optional()(assetIn?.balanceView); return (
diff --git a/apps/minifront/src/components/swap/swap-form/price-history.tsx b/apps/minifront/src/components/swap/swap-form/price-history.tsx index d143245b9..d5fab7f6b 100644 --- a/apps/minifront/src/components/swap/swap-form/price-history.tsx +++ b/apps/minifront/src/components/swap/swap-form/price-history.tsx @@ -1,4 +1,4 @@ -import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; +import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response'; import { AbridgedZQueryState } from '@penumbra-zone/zquery/src/types'; import { Box } from '@penumbra-zone/ui/components/ui/box'; import { CandlestickPlot } from '@penumbra-zone/ui/components/ui/candlestick-plot'; @@ -12,7 +12,7 @@ import { useStoreShallow } from '../../../utils/use-store-shallow'; import { Button } from '@penumbra-zone/ui/components/ui/button'; const priceHistorySelector = (state: AllSlices) => ({ - startMetadata: getMetadataFromBalancesResponse.optional(state.swap.assetIn), + startMetadata: getMetadataFromBalancesResponseOptional(state.swap.assetIn), endMetadata: state.swap.assetOut, historyLimit: state.swap.priceHistory.historyLimit, historyStart: state.swap.priceHistory.historyStart, diff --git a/apps/minifront/src/components/swap/swap-form/simulate-swap-result/index.tsx b/apps/minifront/src/components/swap/swap-form/simulate-swap-result/index.tsx index b019e4636..dae0e997f 100644 --- a/apps/minifront/src/components/swap/swap-form/simulate-swap-result/index.tsx +++ b/apps/minifront/src/components/swap/swap-form/simulate-swap-result/index.tsx @@ -25,9 +25,9 @@ const simulateSwapResultSelector = (state: AllSlices) => ({ value: { amount: toBaseUnit( new BigNumber(state.swap.amount || 0), - getDisplayDenomExponentFromValueView.optional(state.swap.assetIn?.balanceView), + getDisplayDenomExponentFromValueView.optional()(state.swap.assetIn?.balanceView), ), - metadata: getMetadata.optional(state.swap.assetIn?.balanceView), + metadata: getMetadata.optional()(state.swap.assetIn?.balanceView), }, }, }), diff --git a/apps/minifront/src/components/swap/swap-form/simulate-swap-result/traces/trace/price.tsx b/apps/minifront/src/components/swap/swap-form/simulate-swap-result/traces/trace/price.tsx index 74dd6bc4e..e9c2689e7 100644 --- a/apps/minifront/src/components/swap/swap-form/simulate-swap-result/traces/trace/price.tsx +++ b/apps/minifront/src/components/swap/swap-form/simulate-swap-result/traces/trace/price.tsx @@ -21,8 +21,8 @@ export const Price = ({ const lastValueMetadata = metadataByAssetId[bech32mAssetId(outputValue.assetId)]; if (firstValueMetadata?.symbol && lastValueMetadata?.symbol) { - const inputDisplayDenomExponent = getDisplayDenomExponent.optional(firstValueMetadata) ?? 0; - const outputDisplayDenomExponent = getDisplayDenomExponent.optional(lastValueMetadata) ?? 0; + const inputDisplayDenomExponent = getDisplayDenomExponent.optional()(firstValueMetadata) ?? 0; + const outputDisplayDenomExponent = getDisplayDenomExponent.optional()(lastValueMetadata) ?? 0; const formattedInputAmount = formatAmount({ amount: inputValue.amount, exponent: inputDisplayDenomExponent, diff --git a/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx b/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx index 8ad808d21..6fe017615 100644 --- a/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx +++ b/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx @@ -6,7 +6,7 @@ import { joinLoHiAmount } from '@penumbra-zone/types/amount'; import { getAmount, getBalanceView, - getMetadataFromBalancesResponse, + getMetadataFromBalancesResponseOptional, } from '@penumbra-zone/getters/balances-response'; import { ArrowRight } from 'lucide-react'; import { useMemo } from 'react'; @@ -43,7 +43,7 @@ const getAssetOutBalance = ( getAddressIndex(assetIn.accountAddress), assetOut, ); - const matchedBalance = getBalanceView.optional(match); + const matchedBalance = getBalanceView.optional()(match); return matchedBalance ?? zeroValueView(assetOut); }; @@ -70,10 +70,10 @@ export const TokenSwapInput = () => { useStoreShallow(tokenSwapInputSelector); const assetOutBalance = getAssetOutBalance(balancesResponses?.data, assetIn, assetOut); const assetInExponent = useMemo(() => { - return getDisplayDenomExponent.optional(getMetadataFromBalancesResponse.optional(assetIn)); + return getDisplayDenomExponent.optional()(getMetadataFromBalancesResponseOptional(assetIn)); }, [assetIn]); - const maxAmount = getAmount.optional(assetIn); + const maxAmount = getAmount.optional()(assetIn); const maxAmountAsString = maxAmount ? joinLoHiAmount(maxAmount).toString() : undefined; const setInputToBalanceMax = () => { diff --git a/apps/minifront/src/components/v2/dashboard-layout/assets-page/equivalent-values.tsx b/apps/minifront/src/components/v2/dashboard-layout/assets-page/equivalent-values.tsx index db172933e..8ab394fe8 100644 --- a/apps/minifront/src/components/v2/dashboard-layout/assets-page/equivalent-values.tsx +++ b/apps/minifront/src/components/v2/dashboard-layout/assets-page/equivalent-values.tsx @@ -4,7 +4,7 @@ import { getDisplayDenomFromView, getEquivalentValues } from '@penumbra-zone/get import { ValueViewComponent } from '@penumbra-zone/ui/ValueViewComponent'; export const EquivalentValues = ({ valueView }: { valueView?: ValueView }) => { - const equivalentValuesAsValueViews = (getEquivalentValues.optional(valueView) ?? []).map( + const equivalentValuesAsValueViews = (getEquivalentValues.optional()(valueView) ?? []).map( asValueView, ); diff --git a/apps/minifront/src/components/v2/dashboard-layout/assets-page/index.tsx b/apps/minifront/src/components/v2/dashboard-layout/assets-page/index.tsx index 8ad96547c..e5e2cd849 100644 --- a/apps/minifront/src/components/v2/dashboard-layout/assets-page/index.tsx +++ b/apps/minifront/src/components/v2/dashboard-layout/assets-page/index.tsx @@ -4,7 +4,7 @@ import { BalancesByAccount, groupByAccount, useBalancesResponses } from '../../. import { shouldDisplay } from '../../../../fetchers/balances/should-display'; import { sortByPriorityScore } from '../../../../fetchers/balances/by-priority-score'; import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; -import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; +import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response'; import { PagePath } from '../../../metadata/paths'; import { getAddressIndex } from '@penumbra-zone/getters/address-view'; import { AbridgedZQueryState } from '@penumbra-zone/zquery/src/types'; @@ -19,7 +19,7 @@ import { ConditionalWrap } from '@penumbra-zone/ui/ConditionalWrap'; import { LayoutGroup } from 'framer-motion'; const getTradeLink = (balance: BalancesResponse): string => { - const metadata = getMetadataFromBalancesResponse.optional(balance); + const metadata = getMetadataFromBalancesResponseOptional(balance); const accountIndex = getAddressIndex(balance.accountAddress).account; const accountQuery = accountIndex ? `&account=${accountIndex}` : ''; return metadata ? `${PagePath.SWAP}?from=${metadata.symbol}${accountQuery}` : PagePath.SWAP; diff --git a/apps/minifront/src/fetchers/auction-infos.ts b/apps/minifront/src/fetchers/auction-infos.ts index b3f491488..b83d32ec5 100644 --- a/apps/minifront/src/fetchers/auction-infos.ts +++ b/apps/minifront/src/fetchers/auction-infos.ts @@ -31,8 +31,8 @@ export const getAuctionInfos = async function* ({ const auction = DutchAuction.fromBinary(response.auction.value); - const inputAssetId = getInputAssetId.optional(auction); - const outputAssetId = getOutputAssetId.optional(auction); + const inputAssetId = getInputAssetId.optional()(auction); + const outputAssetId = getOutputAssetId.optional()(auction); const inputMetadataPromise = inputAssetId ? penumbra.service(ViewService).assetMetadataById({ assetId: inputAssetId }) diff --git a/apps/minifront/src/fetchers/balances/by-priority-score.ts b/apps/minifront/src/fetchers/balances/by-priority-score.ts index 664dc3cc6..9cf98616b 100644 --- a/apps/minifront/src/fetchers/balances/by-priority-score.ts +++ b/apps/minifront/src/fetchers/balances/by-priority-score.ts @@ -1,17 +1,17 @@ import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; import { - getMetadataFromBalancesResponse, + getMetadataFromBalancesResponseOptional, getAmount, getAddressIndex, } from '@penumbra-zone/getters/balances-response'; import { multiplyAmountByNumber, joinLoHiAmount } from '@penumbra-zone/types/amount'; export const sortByPriorityScore = (a: BalancesResponse, b: BalancesResponse) => { - const aScore = getMetadataFromBalancesResponse.optional(a)?.priorityScore ?? 1n; - const bScore = getMetadataFromBalancesResponse.optional(b)?.priorityScore ?? 1n; + const aScore = getMetadataFromBalancesResponseOptional(a)?.priorityScore ?? 1n; + const bScore = getMetadataFromBalancesResponseOptional(b)?.priorityScore ?? 1n; - const aAmount = getAmount.optional(a); - const bAmount = getAmount.optional(b); + const aAmount = getAmount.optional()(a); + const bAmount = getAmount.optional()(b); const aPriority = aAmount ? joinLoHiAmount(multiplyAmountByNumber(aAmount, Number(aScore))) diff --git a/apps/minifront/src/state/helpers.ts b/apps/minifront/src/state/helpers.ts index 016ef4e6c..a0918f5b0 100644 --- a/apps/minifront/src/state/helpers.ts +++ b/apps/minifront/src/state/helpers.ts @@ -22,7 +22,7 @@ import { uint8ArrayToHex } from '@penumbra-zone/types/hex'; import { fromValueView } from '@penumbra-zone/types/amount'; import { BigNumber } from 'bignumber.js'; import { - getMetadataFromBalancesResponse, + getMetadataFromBalancesResponseOptional, getValueViewCaseFromBalancesResponse, } from '@penumbra-zone/getters/balances-response'; import { getDisplayDenomExponent } from '@penumbra-zone/getters/metadata'; @@ -201,8 +201,8 @@ export const isIncorrectDecimal = ( throw new Error('Missing balanceView'); } - const exponent = getDisplayDenomExponent.optional( - getMetadataFromBalancesResponse.optional(asset), + const exponent = getDisplayDenomExponent.optional()( + getMetadataFromBalancesResponseOptional(asset), ); const fraction = amountInDisplayDenom.split('.')[1]?.length; return typeof exponent !== 'undefined' && typeof fraction !== 'undefined' && fraction > exponent; @@ -214,4 +214,4 @@ export const isValidAmount = (amount: string, assetIn?: BalancesResponse) => (!assetIn || !isIncorrectDecimal(assetIn, amount)); export const isKnown = (balancesResponse: BalancesResponse) => - getValueViewCaseFromBalancesResponse.optional(balancesResponse) === 'knownAssetId'; + getValueViewCaseFromBalancesResponse.optional()(balancesResponse) === 'knownAssetId'; diff --git a/apps/minifront/src/state/ibc-out.ts b/apps/minifront/src/state/ibc-out.ts index b53a3bde6..608731551 100644 --- a/apps/minifront/src/state/ibc-out.ts +++ b/apps/minifront/src/state/ibc-out.ts @@ -282,7 +282,7 @@ export const filterBalancesPerChain = ( registryAssets: Metadata[], stakingTokenMetadata?: Metadata, ): BalancesResponse[] => { - const penumbraAssetId = getAssetId.optional(stakingTokenMetadata); + const penumbraAssetId = getAssetId.optional()(stakingTokenMetadata); const assetsWithMatchingChannel = registryAssets .filter(a => { const match = assetPatterns.ibc.capture(a.base); diff --git a/apps/minifront/src/state/send/index.ts b/apps/minifront/src/state/send/index.ts index 58782df2c..1f7235cb2 100644 --- a/apps/minifront/src/state/send/index.ts +++ b/apps/minifront/src/state/send/index.ts @@ -133,7 +133,7 @@ const assembleRequest = ({ amount, feeTier, recipient, selection, memo }: SendSl value: { amount: toBaseUnit( BigNumber(amount), - getDisplayDenomExponentFromValueView.optional(selection?.balanceView), + getDisplayDenomExponentFromValueView.optional()(selection?.balanceView), ), assetId: getAssetIdFromValueView(selection?.balanceView), }, diff --git a/apps/minifront/src/state/swap/dutch-auction/assemble-schedule-request.ts b/apps/minifront/src/state/swap/dutch-auction/assemble-schedule-request.ts index 8cc72d151..dd8eb3b68 100644 --- a/apps/minifront/src/state/swap/dutch-auction/assemble-schedule-request.ts +++ b/apps/minifront/src/state/swap/dutch-auction/assemble-schedule-request.ts @@ -13,7 +13,7 @@ export const assembleScheduleRequest = async ({ duration, }: Pick & Pick): Promise => { - const source = getAddressIndex.optional(assetIn); + const source = getAddressIndex.optional()(assetIn); return new TransactionPlannerRequest({ dutchAuctionScheduleActions: await getSubAuctions({ diff --git a/apps/minifront/src/state/swap/dutch-auction/index.ts b/apps/minifront/src/state/swap/dutch-auction/index.ts index b3bdff1e2..b7cc2bcad 100644 --- a/apps/minifront/src/state/swap/dutch-auction/index.ts +++ b/apps/minifront/src/state/swap/dutch-auction/index.ts @@ -122,7 +122,7 @@ export const createDutchAuctionSlice = (): SliceCreator => (s swap.dutchAuction.minOutput = ''; } else { const minMinOutput = getSmallestPossibleAmountAboveZero(get().swap.assetOut); - const exponent = getDisplayDenomExponent.optional(get().swap.assetOut) ?? 0; + const exponent = getDisplayDenomExponent.optional()(get().swap.assetOut) ?? 0; const minOutputAsBaseUnit = Number(minOutput) * 10 ** exponent; const outputLimitAsDisplayUnit = (OUTPUT_LIMIT / 10 ** exponent).toString(); @@ -144,7 +144,7 @@ export const createDutchAuctionSlice = (): SliceCreator => (s swap.dutchAuction.maxOutput = ''; } else { const minMaxOutput = getSmallestPossibleAmountAboveZero(get().swap.assetOut); - const exponent = getDisplayDenomExponent.optional(get().swap.assetOut) ?? 0; + const exponent = getDisplayDenomExponent.optional()(get().swap.assetOut) ?? 0; const maxOutputAsBaseUnit = Number(maxOutput) * 10 ** exponent; const outputLimitAsDisplayUnit = (OUTPUT_LIMIT / 10 ** exponent).toString(); diff --git a/apps/minifront/src/state/swap/getters.ts b/apps/minifront/src/state/swap/getters.ts index e3a33b7f1..28c6cfa23 100644 --- a/apps/minifront/src/state/swap/getters.ts +++ b/apps/minifront/src/state/swap/getters.ts @@ -1,14 +1,14 @@ import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; import { AddressIndex } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; -import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; +import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response'; import { getAddressIndex } from '@penumbra-zone/getters/address-view'; import { getMetadata } from '@penumbra-zone/getters/value-view'; export const balancesResponseAndMetadataAreSameAsset = ( balancesResponse?: BalancesResponse, metadata?: Metadata, -) => getMetadata.optional(balancesResponse?.balanceView)?.equals(metadata); +) => getMetadata.optional()(balancesResponse?.balanceView)?.equals(metadata); export const getFirstBalancesResponseNotMatchingMetadata = ( balancesResponses: BalancesResponse[], @@ -38,7 +38,7 @@ export const getBalanceByMatchingMetadataAndAddressIndex = ( metadata: Metadata, ) => { return balances.find(balance => { - const balanceViewMetadata = getMetadataFromBalancesResponse.optional(balance); + const balanceViewMetadata = getMetadataFromBalancesResponseOptional(balance); return ( getAddressIndex(balance.accountAddress).account === addressIndex.account && diff --git a/apps/minifront/src/state/swap/query-params.ts b/apps/minifront/src/state/swap/query-params.ts index ce6663026..222666ecd 100644 --- a/apps/minifront/src/state/swap/query-params.ts +++ b/apps/minifront/src/state/swap/query-params.ts @@ -1,7 +1,7 @@ import type { AllSlices } from '..'; import { getAddressIndex, - getMetadataFromBalancesResponse, + getMetadataFromBalancesResponseOptional, } from '@penumbra-zone/getters/balances-response'; interface SwapQueryParams { @@ -31,9 +31,9 @@ export const getSwapQueryParams = (): SwapQueryParams => { * Sets the swap query parameters in the URL hash based on the store state */ export const setSwapQueryParams = (state: AllSlices): void => { - const fromSymbol = getMetadataFromBalancesResponse.optional(state.swap.assetIn)?.symbol; + const fromSymbol = getMetadataFromBalancesResponseOptional(state.swap.assetIn)?.symbol; const toSymbol = state.swap.assetOut?.symbol; - const accountIndex = getAddressIndex.optional(state.swap.assetIn)?.account; + const accountIndex = getAddressIndex.optional()(state.swap.assetIn)?.account; const searchParams = new URLSearchParams(); if (fromSymbol) { diff --git a/apps/minifront/src/state/swap/swap-balances-middleware.ts b/apps/minifront/src/state/swap/swap-balances-middleware.ts index d13e38bd5..cf8a571d6 100644 --- a/apps/minifront/src/state/swap/swap-balances-middleware.ts +++ b/apps/minifront/src/state/swap/swap-balances-middleware.ts @@ -32,7 +32,7 @@ const getAssetIn = (state: AllSlices, from?: string, account?: number) => { if (from) { const matchingBalancesResponse = filteredSwappableBalancesResponses.find( - balance => getMetadataFromBalancesResponse.optional(balance)?.symbol === from, + balance => getMetadataFromBalancesResponse.optional()(balance)?.symbol === from, ); if (matchingBalancesResponse) { return matchingBalancesResponse; @@ -73,7 +73,7 @@ const getAssetOut = (state: AllSlices, to?: string, assetIn?: BalancesResponse) return state.swap.assetOut; } - if (getMetadataFromBalancesResponse.optional(assetIn)?.equals(swappableAssets[0])) { + if (getMetadataFromBalancesResponse.optional()(assetIn)?.equals(swappableAssets[0])) { return swappableAssets[1]; } diff --git a/packages/getters/README.md b/packages/getters/README.md index dcaf93ba0..0b5c7cba1 100644 --- a/packages/getters/README.md +++ b/packages/getters/README.md @@ -83,7 +83,7 @@ At this point, it's worth mentioning that getters are _required by default_. If What if the value you're getting _is_ optional, though? What if you don't want your getter to throw if either the value it's passed, or the value it returns, is `undefined`? That's what the `.optional()` property on the getter is for: ```tsx -const addressView = getAddressView.optional(memoView) +const addressView = getAddressView.optional()(memoView)
{addressView && } diff --git a/packages/getters/src/balances-response.ts b/packages/getters/src/balances-response.ts index bc3aeab8e..5eca26893 100644 --- a/packages/getters/src/balances-response.ts +++ b/packages/getters/src/balances-response.ts @@ -11,7 +11,11 @@ export const getAssetIdFromBalancesResponse = getBalanceView.pipe(getMetadata).p export const getMetadataFromBalancesResponse = getBalanceView.pipe(getMetadata); -export const getDisplayFromBalancesResponse = getMetadataFromBalancesResponse.pipe(getDisplay); +export const getDisplayFromBalancesResponse = getMetadataFromBalancesResponse + .optional() + .pipe(getDisplay); + +export const getMetadataFromBalancesResponseOptional = getBalanceView.optional().pipe(getMetadata); export const getAddressIndex = createGetter((balancesResponse?: BalancesResponse) => balancesResponse?.accountAddress?.addressView.case === 'decoded' diff --git a/packages/getters/src/swap-claim-view.ts b/packages/getters/src/swap-claim-view.ts index 45f02ca3d..9bb0821d0 100644 --- a/packages/getters/src/swap-claim-view.ts +++ b/packages/getters/src/swap-claim-view.ts @@ -14,8 +14,8 @@ export const getOutput2 = createGetter((swapClaimView?: SwapClaimView) => : undefined, ); -export const getOutput1Value = getOutput1.pipe(getValue); -export const getOutput2Value = getOutput2.pipe(getValue); +export const getOutput1ValueOptional = getOutput1.optional().pipe(getValue); +export const getOutput2ValueOptional = getOutput2.optional().pipe(getValue); export const getSwapClaimFee = createGetter((swapClaimView?: SwapClaimView) => swapClaimView?.swapClaimView.case === 'visible' || swapClaimView?.swapClaimView.case === 'opaque' diff --git a/packages/getters/src/utils/create-getter.test.ts b/packages/getters/src/utils/create-getter.test.ts index e52559b6d..f60bb5c3d 100644 --- a/packages/getters/src/utils/create-getter.test.ts +++ b/packages/getters/src/utils/create-getter.test.ts @@ -1,215 +1,110 @@ -/* eslint-disable no-restricted-syntax */ - -import { describe, expect, expectTypeOf, it, test } from 'vitest'; +import { assertType, describe, expect, it } from 'vitest'; import { createGetter } from './create-getter.js'; -import { Getter } from './getter.js'; - -type City = 'Seattle' | 'San Francisco' | 'New York City'; interface Address { - city: City; - state?: string; + city: string; + state: string; + country?: string; } -interface Person { +interface Employee { firstName: string; lastName?: string; address?: Address; } -// complete data -const alice: Person = { +const employee: Employee = { firstName: 'Alice', - lastName: 'Liddell', address: { city: 'San Francisco', state: 'California', }, }; -const bob: Person = { - firstName: 'Bob', - // missing lastName - address: { - city: 'Seattle', - // missing state - }, -}; - -const charlie: Person = { - firstName: 'Charlie', - lastName: '', // falsy lastName - // missing address -}; +const getFirstName = createGetter((employee?: Employee) => employee?.firstName); +const getLastName = createGetter((employee?: Employee) => employee?.lastName); +const getAddress = createGetter((employee?: Employee) => employee?.address); +const getCity = createGetter((address?: Address) => address?.city); +const getCountry = createGetter((address?: Address) => address?.country); +const getFirstLetter = createGetter((value?: string) => value?.[0]); describe('createGetter()', () => { - const selectFirstName = (p?: Person) => p?.firstName; - const selectStringIndexOne = (a?: string) => a?.[1]; - - it('creates a getter', () => { - let getFirstNameFromPerson; - expectTypeOf((getFirstNameFromPerson = createGetter(selectFirstName))).toEqualTypeOf< - Getter - >(); - expect(getFirstNameFromPerson).toBeInstanceOf(Function); - expect(getFirstNameFromPerson).toHaveProperty(['optional', 'pipe']); - }); - - it('creates a getter with optional', () => { - let getFirstNameFromPerson_optional; - expectTypeOf( - (getFirstNameFromPerson_optional = createGetter(selectFirstName).optional), - ).toEqualTypeOf>(); - - expect(getFirstNameFromPerson_optional).toBeInstanceOf(Function); - expect(getFirstNameFromPerson_optional).toHaveProperty('optional'); - expect(getFirstNameFromPerson_optional).toHaveProperty('pipe'); - - expect(getFirstNameFromPerson_optional.optional).toBe(getFirstNameFromPerson_optional); - }); - - it('creates a getter pipe', () => { - const getFirstName = createGetter(selectFirstName); - const getSecondLetter = createGetter(selectStringIndexOne); - - let getSecondLetterOfFirstNameFromPerson; - expectTypeOf( - (getSecondLetterOfFirstNameFromPerson = getFirstName.pipe(getSecondLetter)), - ).toEqualTypeOf>(); - - expect(getSecondLetterOfFirstNameFromPerson).toBeInstanceOf(Function); - expect(getSecondLetterOfFirstNameFromPerson).toHaveProperty('optional'); - expect(getSecondLetterOfFirstNameFromPerson).toHaveProperty('pipe'); - }); -}); - -describe('getting values and optional', () => { - const getFirstName = createGetter((p?: Person) => p?.firstName); - const getLastName = createGetter((p?: Person) => p?.lastName); - - it('gets the expected value', () => { - expect(getFirstName(alice)).toBe('Alice'); - expect(getFirstName.optional(alice)).toBe('Alice'); - - expect(getLastName(alice)).toBe('Liddell'); - expect(getLastName.optional(alice)).toBe('Liddell'); - }); + describe('getter()', () => { + it('gets the value via the function passed into `createGetter()`', () => { + expect(getFirstName(employee)).toBe('Alice'); + }); - describe('undefined in the getter', () => { - it('handles undefined input', () => { + it('throws when the whole value is undefined', () => { expect(() => getFirstName(undefined)).toThrow(); - expect(getFirstName.optional(undefined)).toBeUndefined(); }); - it('handles undefined property', () => { - expect(() => getLastName(bob)).toThrow(); - expect(getLastName.optional(bob)).toBeUndefined(); + it('throws for an undefined property', () => { + expect(() => getLastName(employee)).toThrow(); }); - }); - - test('successfully returns a falsy value', () => { - expect(() => getLastName(charlie)).not.toThrow(); - }); -}); - -describe('getter pipes', () => { - const selectAddress = (p?: Person) => p?.address; - const getAddressFromPerson = createGetter(selectAddress); - const selectCity = (a?: Address) => a?.city; - const getCityFromAddress = createGetter(selectCity); - const getStateFromAddress = createGetter((a?: Address) => a?.state); - it('pipes the getters together and returns the final result', () => { - let getCityFromPerson; - - expectTypeOf((getCityFromPerson = getAddressFromPerson.pipe(getCityFromAddress))).toEqualTypeOf< - Getter - >(); - - expect(getCityFromPerson(alice)).toBe('San Francisco'); - expect(getCityFromPerson(bob)).toBe('Seattle'); + it('does not throw if a value is falsey but not undefined', () => { + const employee: Employee = { firstName: 'Alice', lastName: '' }; + expect(() => getLastName(employee)).not.toThrow(); + }); }); - describe('undefined in the pipe', () => { - let getStateFromPerson; - - expectTypeOf( - (getStateFromPerson = getAddressFromPerson.pipe(getStateFromAddress)), - ).toEqualTypeOf>(); - - it('throws on undefined', () => { - expect(() => getStateFromPerson(undefined)).toThrow(); - expect(getStateFromPerson(alice)).toBe('California'); - expect(() => getStateFromPerson(bob)).toThrow(); + describe('getter.optional()', () => { + it('returns `undefined` when the whole value is undefined', () => { + expect(getLastName.optional()(undefined)).toBeUndefined(); }); - it("doesn't throw on undefined when optional", () => { - expect(getStateFromPerson.optional(undefined)).toBeUndefined(); - expect(getStateFromPerson.optional(alice)).toBe('California'); - expect(getStateFromPerson.optional(bob)).toBeUndefined(); + it('returns `undefined` for an undefined property', () => { + expect(getLastName.optional()(employee)).toBeUndefined(); }); }); - describe('longer chains', () => { - const getSecondLetter = createGetter((s?: string) => s?.[1]); - it('applies optional to the chain', () => { - const notOptionalStateLetter = getAddressFromPerson - .pipe(getStateFromAddress) - .pipe(getSecondLetter); - expect(notOptionalStateLetter(alice)).toBe('a'); - expect(() => notOptionalStateLetter(bob)).toThrow(); - expect(() => notOptionalStateLetter(charlie)).toThrow(); - - const notOptionalCityLetter = getAddressFromPerson - .pipe(getCityFromAddress) - .pipe(getSecondLetter); - expect(notOptionalCityLetter(alice)).toBe('a'); - expect(notOptionalCityLetter(bob)).toBe('e'); - expect(() => notOptionalCityLetter(charlie)).toThrow(); - - const midOptionalStateLetter = getAddressFromPerson // address is required - .pipe(getStateFromAddress) // state is optional, turning the whole chain optional - .optional.pipe(getSecondLetter); // letter is required - expect(midOptionalStateLetter(alice)).toBe('a'); - expect(midOptionalStateLetter(bob)).toBeUndefined(); - expect(midOptionalStateLetter(charlie)).toBeUndefined(); - - const midOptionalCityLetter = getAddressFromPerson // address is required - .pipe(getCityFromAddress) // city is optional, turning the whole chain optional - .optional.pipe(getSecondLetter); // letter is required - expect(midOptionalCityLetter(alice)).toBe('a'); - expect(midOptionalCityLetter(bob)).toBe('e'); - expect(midOptionalCityLetter(charlie)).toBeUndefined(); - - const firstOptionalStateLetter = getAddressFromPerson.optional // address is optional, turning the whole chain optional - .pipe(getStateFromAddress) // state is required - .pipe(getSecondLetter); // letter is required - expect(firstOptionalStateLetter(alice)).toBe('a'); - expect(firstOptionalStateLetter(bob)).toBeUndefined(); - expect(firstOptionalStateLetter(charlie)).toBeUndefined(); - - const firstOptionalCityLetter = getAddressFromPerson.optional // address is optional, turning the whole chain optional - .pipe(getCityFromAddress) // city is required - .pipe(getSecondLetter); // letter is required - expect(firstOptionalCityLetter(alice)).toBe('a'); - expect(firstOptionalCityLetter(bob)).toBe('e'); - expect(firstOptionalCityLetter(charlie)).toBeUndefined(); + describe('getter.pipe()', () => { + it('pipes the getters together and returns the final result', () => { + expect(getAddress.pipe(getCity)(employee)).toBe('San Francisco'); + }); + + it('throws when any value in the property chain is undefined', () => { + expect(() => getAddress.pipe(getCity)(undefined)).toThrow(); + expect(() => getAddress.pipe(getCity)({ firstName: 'Alice' })).toThrow(); + expect(() => getAddress.pipe(getCountry)(employee)).toThrow(); }); - it('applies required to the chain', () => { - const allOptionalStateLetter = getAddressFromPerson.optional - .pipe(getStateFromAddress) - .optional.pipe(getSecondLetter).optional; - expect(allOptionalStateLetter.required(alice)).toBe('a'); - expect(() => allOptionalStateLetter.required(bob)).toThrow(); - expect(() => allOptionalStateLetter.required(charlie)).toThrow(); - - const allOptionalCityLetter = getAddressFromPerson.optional - .pipe(getCityFromAddress) - .optional.pipe(getSecondLetter).optional; - expect(allOptionalCityLetter.required(alice)).toBe('a'); - expect(allOptionalCityLetter.required(bob)).toBe('e'); - expect(() => allOptionalCityLetter.required(charlie)).toThrow(); + describe('getter.pipe() with .optional())', () => { + const employee: Employee = { + firstName: 'Alice', + address: { + city: '', // `getFirstLetter` will return undefined + state: 'California', + }, + }; + + it('does not throw when the first getter is used with `.optional()` and some value in the chain is undefined', () => { + expect(() => + getAddress.optional().pipe(getCity).pipe(getFirstLetter)(employee), + ).not.toThrow(); + }); + + it('does not throw when a later getter is used with `.optional()` and some value in the chain is undefined', () => { + const baseGetter = getAddress.pipe(getCity).pipe(getFirstLetter); + + // Before testing that it _doesn't_ throw with `.optional()`, first make + // sure that it _does_ throw without it, to ensure that this test is + // valid. + expect(() => baseGetter(employee)).toThrow(); + expect(() => baseGetter.optional()(employee)).not.toThrow(); + }); + + it('does throw when used without `.optional()` and some value in the chain is undefined', () => { + expect(() => getAddress.pipe(getCity).pipe(getFirstLetter)(employee)).toThrow(); + }); }); }); + + // Type assertions - these will be run at build time, rather than at test + // time. + assertType(getAddress.pipe(getCity)(employee)); + // @ts-expect-error - Assert that `string` on its own is incorrect for an + // optional getter -- it should be `string | undefined`. + assertType(getAddress.pipe(getCity).optional()(employee)); + assertType(getAddress.pipe(getCity).optional()(employee)); }); diff --git a/packages/getters/src/utils/create-getter.ts b/packages/getters/src/utils/create-getter.ts index a2d8cad6f..b376262dc 100644 --- a/packages/getters/src/utils/create-getter.ts +++ b/packages/getters/src/utils/create-getter.ts @@ -1,101 +1,47 @@ import { Getter } from './getter.js'; import { GetterMissingValueError } from './getter-missing-value-error.js'; -const createPiper = - (firstSelector: (s?: PipeSourceType) => IntermediateType) => - ( - secondSelector: (i?: IntermediateType) => PipeTargetType, - ): Getter => { - const pipedFn = (source?: PipeSourceType) => { - const intermediate: IntermediateType = firstSelector(source); - const target: PipeTargetType = secondSelector(intermediate); - return target; - }; - - const pipedGetter = Object.defineProperties(pipedFn, { - pipe: { - enumerable: true, - get: () => createPiper(pipedFn), - }, - optional: { - enumerable: true, - get: () => createOptional(pipedFn), - }, - required: { - enumerable: true, - get: () => createRequired(pipedFn), - }, - }) as Getter; - - return pipedGetter; - }; - -const createOptional = ( - selector: (v?: SourceType) => TargetType | undefined, -): Getter => { - const optionalFn = (source?: SourceType) => { - try { - return selector(source); - } catch (e) { - if (e instanceof GetterMissingValueError) { - return undefined; - } - throw e; +export const createGetter = ( + getterFunction: (value: SourceType | undefined) => TargetType | undefined, + optional?: Optional, +): Getter => { + const getter: Getter = value => { + const result = getterFunction(value); + if (result === undefined && !optional) { + const errorMessage = `Failed to extract from ${JSON.stringify(value)}`; + throw new GetterMissingValueError(errorMessage); } + return result as Optional extends true ? TargetType | undefined : TargetType; }; - const optionalGetter = Object.defineProperties(optionalFn, { - pipe: { - enumerable: true, - value: (nextSelector: (i?: TargetType) => NextTargetType) => { - return createPiper(optionalFn)(nextSelector).optional; - }, - }, - required: { - enumerable: true, - get: () => createRequired(selector), - }, - }) as Getter; - - Object.defineProperty(optionalGetter, 'optional', { - enumerable: true, - get: () => optionalGetter, - }); - - return optionalGetter; -}; - -const createRequired = ( - selector: (v?: SourceType) => TargetType | undefined, -): Getter> => { - const requiredFn = (source?: SourceType) => { - const required = selector(source); - if (required == null) { - throw new GetterMissingValueError( - `Failed to select value from "${String(source)}" with "${selector.name}"`, - { cause: { source, selector } } satisfies ErrorOptions, - ); - } - return required; + getter.optional = () => + createGetter(value => { + try { + return getterFunction(value); + } catch (e) { + if (e instanceof GetterMissingValueError) { + return undefined; + } else { + throw e; + } + } + }, true); + + getter.pipe = ( + next: Getter, + ) => { + return createGetter(value => { + try { + return next(getterFunction(value)); + } catch (e) { + if (!optional || !(e instanceof GetterMissingValueError)) { + throw e; + } else { + return undefined; + } + } + }, optional); }; - const requiredGetter = Object.defineProperties(requiredFn, { - pipe: { - enumerable: true, - get: () => createPiper(requiredFn), - }, - optional: { - enumerable: true, - get: () => createOptional(selector), - }, - }) as Getter>; - - Object.defineProperty(requiredGetter, 'required', { - enumerable: true, - get: () => requiredGetter, - }); - - return requiredGetter; + return getter; }; - -export { createRequired as createGetter }; diff --git a/packages/getters/src/utils/getter-missing-value-error.ts b/packages/getters/src/utils/getter-missing-value-error.ts index 77a94480a..755f855d4 100644 --- a/packages/getters/src/utils/getter-missing-value-error.ts +++ b/packages/getters/src/utils/getter-missing-value-error.ts @@ -1,11 +1,9 @@ /** - * This error will be thrown when a getter that isn't called `optional` returns - * `undefined`. You can import this error class in your code to differentiate - * between this specific type of error and others. - * - * If you want to catch this error just to suppress it, it's easier to just call - * the getter as `optional` instead. - * - * `getAddressIndex.optional(addressView)`.) + * This error will be thrown when a getter that hasn't been marked `.optional()` + * returns `undefined`. You can import this error class in your code to + * differentiate between this specific type of error and others. (If you want to + * catch this error just to make a getter optional, though, it's easier to just + * call `.optional()` on the getter first: + * `getAddressIndex.optional()(addressView)`.) */ export class GetterMissingValueError extends Error {} diff --git a/packages/getters/src/utils/getter.ts b/packages/getters/src/utils/getter.ts index d07096c9e..bc49b4ad6 100644 --- a/packages/getters/src/utils/getter.ts +++ b/packages/getters/src/utils/getter.ts @@ -1,46 +1,36 @@ -export interface Getter { - /** - * Given an input value of `SourceType`, asserts successful retrieval of a - * value of `TargetType`, by the naive retrieval function passed to - * `createGetter`. - * - * If undefined access occurs while retrieving `TargetType`, a - * `GetterMissingValueError` is thrown. - */ - (value?: SourceType): TargetType; +/** + * Given a value of type `SourceType`, returns a (possibly nested) property of + * that value, of type `TargetType`. If `Optional` is `true`, returns + * undefined if the property or an ancestor is undefined; if `false`, throws + * when the property or an ancestor is undefined. + */ +type GetterFunction = ( + value: SourceType | undefined, +) => Optional extends true ? TargetType | undefined : TargetType; +interface GetterMethods { /** - * Every getter contains an `optional` getter, which adds `undefined` to the - * target type, and which will not throw `GetterMissingValueError`. + * Returns a getter that, when given a value of type `SourceType`, returns a + * (possibly nested) property of that value, of type `TargetType`. If the + * property or any of its ancestors are undefined, returned undefined. * * @example * ```ts - * const getMetadataFromValueView = createGetter(valueView => + * const getMetadata = createGetter(valueView => * valueView?.valueView.case === 'knownAssetId' ? valueView.valueView.value.metadata : undefined, * ); * - * // Note that this `emptyValueView` has no metadata, nor even a `case`. - * const emptyValueView = new ValueView(); + * // Note that `valueView` has no metadata, nor even a `case`. + * const valueView = new ValueView(); * * // Doesn't throw, even though the metadata is missing. - * const noMetadata: Metadata | undefined = getMetadataFromValueView.optional(emptyValueView); + * const metadata = getMetadata.optional()(valueView); * ``` */ - readonly optional: Getter; + optional: () => Getter; /** - * Every getter contains a `required` getter, which wraps `NonNullable` around - * the target type, and which will throw `GetterMissingValueError` if the - * accessed result is `undefined`. - */ - readonly required: Getter>; - - /** - * Call `pipe` to create a getter for the return type of another getter or - * selector function provided as a parameter. Your parameter function must - * accept the output of this getter as its 0th input parameter. If the return - * type of your pipe parameter includes `undefined`, the return type of the - * created getter will also include undefined (it will be an optional getter). + * Pipes the output of this getter to another getter or getter function. * * @example * ```ts @@ -48,16 +38,19 @@ export interface Getter { * // if any step in the pipe is undefined. * const assetId1 = getMetadata.pipe(getAssetId).pipe(getInner)(valueView); * // Gets the deeply nested `inner` property in a metadata object, or returns - * // `undefined`. - * const assetId2 = getMetadata.optional.pipe(getAssetId).pipe(getInner)(valueView); + * // undefined if any step in the pipe is undefined. Note that `.optional()` + * // must be called at the _beginning_ of the chain. + * const assetId2 = getMetadata.optional().pipe(getAssetId).pipe(getInner)(valueView); * ``` */ - readonly pipe: ( - pipeSelector: - | Getter - | ((value?: TargetType | NonNullable | undefined) => PipeTargetType), - ) => Getter< - SourceType, - TargetType extends undefined ? PipeTargetType | undefined : PipeTargetType - >; + pipe: ( + pipedGetter: Getter, + ) => Getter; } + +export type Getter< + SourceType = unknown, + TargetType = unknown, + Optional extends boolean = false, +> = GetterFunction & + GetterMethods; diff --git a/packages/getters/src/validator-info.ts b/packages/getters/src/validator-info.ts index 2744f36e7..512b7b381 100644 --- a/packages/getters/src/validator-info.ts +++ b/packages/getters/src/validator-info.ts @@ -26,7 +26,10 @@ export const getBondingStateEnumFromValidatorInfo = getStatus .pipe(getBondingState) .pipe(getBondingStateEnum); -export const getValidatorRewardRateFromValidatorInfo = getRateData.pipe(getValidatorRewardRate); +export const getValidatorRewardRateFromValidatorInfoOptional = getStatus + .optional() + .pipe(getRateData) + .pipe(getValidatorRewardRate); export const getFundingStreamsFromValidatorInfo = getValidator.pipe(getFundingStreams); diff --git a/packages/getters/src/value-view.ts b/packages/getters/src/value-view.ts index affd3c764..574c41d63 100644 --- a/packages/getters/src/value-view.ts +++ b/packages/getters/src/value-view.ts @@ -22,19 +22,15 @@ export const getEquivalentValues = createGetter((valueView?: ValueView) => : undefined, ); +const getValidatorInfo = createGetter((any?: Any) => + any ? ValidatorInfo.fromBinary(any.value) : undefined, +); + /** * Only to be used on `ValueView`s that contain delegation tokens -- and thus, * validator infos. */ -export const getValidatorInfoFromValueView = getExtendedMetadata.pipe( - createGetter((a?: Any) => { - const validatorInfo = new ValidatorInfo(); - if (a?.unpackTo(validatorInfo)) { - return validatorInfo; - } - return undefined; - }), -); +export const getValidatorInfoFromValueView = getExtendedMetadata.pipe(getValidatorInfo); /** * Only to be used on `ValueView`s that contain delegation tokens -- and thus, @@ -46,7 +42,7 @@ export const getValidatorIdentityKeyFromValueView = getValidatorInfoFromValueVie export const getDisplayDenomExponentFromValueView = createGetter((valueView?: ValueView) => valueView?.valueView.case === 'knownAssetId' - ? getDisplayDenomExponent(valueView.valueView.value.metadata) + ? getDisplayDenomExponent.optional()(valueView.valueView.value.metadata) : undefined, ); @@ -65,7 +61,10 @@ export const getAmount = createGetter( (valueView?: ValueView) => valueView?.valueView.value?.amount, ); -export const getSymbolFromValueView = getMetadata.pipe(getSymbol); +export const getSymbolFromValueView = createGetter((valueView?: ValueView) => { + const metadata = getMetadata.optional()(valueView); + return getSymbol.optional()(metadata); +}); export const getDisplayDenomFromView = createGetter((view?: ValueView) => { if (view?.valueView.case === 'unknownAssetId') { diff --git a/packages/perspective/src/plan/view-action-plan.ts b/packages/perspective/src/plan/view-action-plan.ts index e0906f7d4..be136dad3 100644 --- a/packages/perspective/src/plan/view-action-plan.ts +++ b/packages/perspective/src/plan/view-action-plan.ts @@ -324,8 +324,8 @@ export const viewActionPlan = }); case 'actionDutchAuctionSchedule': { - const inputAssetId = getInputAssetId.optional(actionPlan.action.value.description); - const outputAssetId = getOutputAssetId.optional(actionPlan.action.value.description); + const inputAssetId = getInputAssetId.optional()(actionPlan.action.value.description); + const outputAssetId = getOutputAssetId.optional()(actionPlan.action.value.description); const [inputMetadata, outputMetadata] = await Promise.all([ inputAssetId ? await denomMetadataByAssetId(inputAssetId) : undefined, outputAssetId ? await denomMetadataByAssetId(outputAssetId) : undefined, diff --git a/packages/services/src/view-service/balances.ts b/packages/services/src/view-service/balances.ts index 0878c4752..3e8942a69 100644 --- a/packages/services/src/view-service/balances.ts +++ b/packages/services/src/view-service/balances.ts @@ -157,7 +157,7 @@ class BalancesAggregator { * of the equivalent value and the `amount` of the `ValueView`. */ private async aggregateEquivalentValues(valueView: ValueView, toAdd: SpendableNoteRecord) { - const assetId = getAssetIdFromRecord.optional(toAdd); + const assetId = getAssetIdFromRecord.optional()(toAdd); if (!assetId?.inner) { return; } diff --git a/packages/services/src/view-service/unbonding-tokens-by-address-index/helpers.ts b/packages/services/src/view-service/unbonding-tokens-by-address-index/helpers.ts index 0e1bb2f12..a0f28e7b8 100644 --- a/packages/services/src/view-service/unbonding-tokens-by-address-index/helpers.ts +++ b/packages/services/src/view-service/unbonding-tokens-by-address-index/helpers.ts @@ -11,7 +11,7 @@ import { status } from '../status.js'; import { appParameters } from '../app-parameters.js'; export const isUnbondingTokenBalance = (balancesResponse: PartialMessage) => { - const display = getDisplayFromBalancesResponse.optional(new BalancesResponse(balancesResponse)); + const display = getDisplayFromBalancesResponse(new BalancesResponse(balancesResponse)); return display ? assetPatterns.unbondingToken.matches(display) : false; }; @@ -40,7 +40,7 @@ export const getIsClaimable = async ( return false; } - const display = getDisplayFromBalancesResponse.optional(new BalancesResponse(balancesResponse)); + const display = getDisplayFromBalancesResponse(new BalancesResponse(balancesResponse)); if (!display) { return false; } diff --git a/packages/services/src/view-service/unbonding-tokens-by-address-index/index.ts b/packages/services/src/view-service/unbonding-tokens-by-address-index/index.ts index 6da3b4032..af61c9c6b 100644 --- a/packages/services/src/view-service/unbonding-tokens-by-address-index/index.ts +++ b/packages/services/src/view-service/unbonding-tokens-by-address-index/index.ts @@ -44,7 +44,7 @@ export const unbondingTokensByAddressIndex: Impl['unbondingTokensByAddressIndex' } const regexResult = assetPatterns.unbondingToken.capture( - getDisplayFromBalancesResponse.optional(new BalancesResponse(balancesResponse)) ?? '', + getDisplayFromBalancesResponse(new BalancesResponse(balancesResponse)) ?? '', ); if (!regexResult) { throw new Error('expected delegation token identity key not present'); diff --git a/packages/types/src/swap.ts b/packages/types/src/swap.ts index a39765e7b..1be3bd260 100644 --- a/packages/types/src/swap.ts +++ b/packages/types/src/swap.ts @@ -35,8 +35,8 @@ const getUnfilledAmount = (swapView: SwapView): ValueView | undefined => { const delta1I = getDelta1IFromSwapView(swapView); const delta2I = getDelta2IFromSwapView(swapView); - const output1Value = getOutput1Value.optional(swapView); - const output2Value = getOutput2Value.optional(swapView); + const output1Value = getOutput1Value.optional()(swapView); + const output2Value = getOutput2Value.optional()(swapView); const is1To2Swap = isZero(delta2I); const is2To1Swap = isZero(delta1I); @@ -69,8 +69,8 @@ export const getOneWaySwapValues = ( ); } - const output1 = getOutput1Value.optional(swapView); - const output2 = getOutput2Value.optional(swapView); + const output1 = getOutput1Value.optional()(swapView); + const output2 = getOutput2Value.optional()(swapView); const delta1I = getDelta1IFromSwapView(swapView); const delta2I = getDelta2IFromSwapView(swapView); diff --git a/packages/types/src/value-view.ts b/packages/types/src/value-view.ts index 28e257eb7..1e3162589 100644 --- a/packages/types/src/value-view.ts +++ b/packages/types/src/value-view.ts @@ -13,7 +13,7 @@ export const getFormattedAmtFromValueView = (v: ValueView, commas = false): stri if (v.valueView.case === 'knownAssetId' && v.valueView.value.metadata) { const { amount = new Amount(), metadata } = v.valueView.value; - const exponent = getDisplayDenomExponent.optional(metadata); + const exponent = getDisplayDenomExponent.optional()(metadata); return formatAmount({ amount, exponent, commas }); } else { const { amount = new Amount() } = v.valueView.value; diff --git a/packages/ui/components/ui/asset-icon/index.tsx b/packages/ui/components/ui/asset-icon/index.tsx index a6b10e763..ad35fbb2d 100644 --- a/packages/ui/components/ui/asset-icon/index.tsx +++ b/packages/ui/components/ui/asset-icon/index.tsx @@ -21,7 +21,7 @@ export const AssetIcon = ({ size === 'sm' && 'size-6', size === 'lg' && 'size-12', ); - const display = getDisplay.optional(metadata); + const display = getDisplay.optional()(metadata); const isDelegationToken = display ? assetPatterns.delegationToken.matches(display) : false; const isUnbondingToken = display ? assetPatterns.unbondingToken.matches(display) : false; diff --git a/packages/ui/components/ui/balance-value-view/index.tsx b/packages/ui/components/ui/balance-value-view/index.tsx index 69e6092a4..53cb0157b 100644 --- a/packages/ui/components/ui/balance-value-view/index.tsx +++ b/packages/ui/components/ui/balance-value-view/index.tsx @@ -18,8 +18,8 @@ export const BalanceValueView = ({ error?: boolean; onClick?: (valueView: ValueView) => void; }) => { - const exponent = getDisplayDenomExponentFromValueView.optional(valueView); - const amount = getAmount.optional(valueView) ?? new Amount({ hi: 0n, lo: 0n }); + const exponent = getDisplayDenomExponentFromValueView.optional()(valueView); + const amount = getAmount.optional()(valueView) ?? new Amount({ hi: 0n, lo: 0n }); const formattedAmount = formatAmount({ amount, exponent, commas: true }); return ( diff --git a/packages/ui/components/ui/dutch-auction-component/expanded-details/get-price.ts b/packages/ui/components/ui/dutch-auction-component/expanded-details/get-price.ts index 7405a591d..de8cbd821 100644 --- a/packages/ui/components/ui/dutch-auction-component/expanded-details/get-price.ts +++ b/packages/ui/components/ui/dutch-auction-component/expanded-details/get-price.ts @@ -46,7 +46,7 @@ export const getPrice = ( // The input, scaled up by `step_count` to match. const inputScaled = (stepCount - 1n) * input; - const inputDisplayDenomExponent = getDisplayDenomExponent.optional(inputMetadata); + const inputDisplayDenomExponent = getDisplayDenomExponent.optional()(inputMetadata); const multiplier = 10 ** (inputDisplayDenomExponent ?? 0); const price = Math.round((Number(targetOutputScaled) / Number(inputScaled)) * multiplier); diff --git a/packages/ui/components/ui/dutch-auction-component/expanded-details/index.tsx b/packages/ui/components/ui/dutch-auction-component/expanded-details/index.tsx index 5921a7b53..caac99a5b 100644 --- a/packages/ui/components/ui/dutch-auction-component/expanded-details/index.tsx +++ b/packages/ui/components/ui/dutch-auction-component/expanded-details/index.tsx @@ -45,8 +45,8 @@ export const ExpandedDetails = ({ fullSyncHeight >= description.startHeight && fullSyncHeight <= description.endHeight; - const inputExponent = getDisplayDenomExponent.optional(inputMetadata); - const outputExponent = getDisplayDenomExponent.optional(outputMetadata); + const inputExponent = getDisplayDenomExponent.optional()(inputMetadata); + const outputExponent = getDisplayDenomExponent.optional()(outputMetadata); return (
diff --git a/packages/ui/components/ui/tx/actions-views/delegator-vote.tsx b/packages/ui/components/ui/tx/actions-views/delegator-vote.tsx index de2b6ade3..c0912ab6c 100644 --- a/packages/ui/components/ui/tx/actions-views/delegator-vote.tsx +++ b/packages/ui/components/ui/tx/actions-views/delegator-vote.tsx @@ -54,11 +54,11 @@ const umValueView = (amount?: Amount) => { }; export const DelegatorVoteComponent = ({ value }: { value: DelegatorVoteView }) => { - const body = getDelegatorVoteBody.optional(value); + const body = getDelegatorVoteBody.optional()(value); if (value.delegatorVote.case === 'visible') { const note = value.delegatorVote.value.note; - const address = getAddress.optional(note); + const address = getAddress.optional()(note); return ( { if (value.swapView.case === 'visible') { - const claimTx = getClaimTx.optional(value); - const addressView = getAddressView.optional(value); + const claimTx = getClaimTx.optional()(value); + const addressView = getAddressView.optional()(value); const oneWaySwap = isOneWaySwap(value) ? getOneWaySwapValues(value) : undefined; // The 'Fee' protobuf definition does not include assetMetadata. diff --git a/packages/ui/components/ui/tx/actions-views/swap/one-way-swap.tsx b/packages/ui/components/ui/tx/actions-views/swap/one-way-swap.tsx index 6fa0c7f7d..c7aaf3b1a 100644 --- a/packages/ui/components/ui/tx/actions-views/swap/one-way-swap.tsx +++ b/packages/ui/components/ui/tx/actions-views/swap/one-way-swap.tsx @@ -10,7 +10,7 @@ import { getAmount } from '@penumbra-zone/getters/value-view'; * 1.23INPUT -> 4.56OUTPUT */ export const OneWaySwap = ({ input, output }: { input: ValueView; output: ValueView }) => { - const outputAmount = getAmount.optional(output); + const outputAmount = getAmount.optional()(output); return (
diff --git a/packages/ui/components/ui/tx/actions-views/swap/swap-claim.tsx b/packages/ui/components/ui/tx/actions-views/swap/swap-claim.tsx index 749d109ca..c36d3a58e 100644 --- a/packages/ui/components/ui/tx/actions-views/swap/swap-claim.tsx +++ b/packages/ui/components/ui/tx/actions-views/swap/swap-claim.tsx @@ -3,8 +3,8 @@ import { SwapClaimView } from '@penumbra-zone/protobuf/penumbra/core/component/d import { TransactionIdComponent } from './transaction-id'; import { ActionDetails } from '../action-details'; import { - getOutput1Value, - getOutput2Value, + getOutput1ValueOptional, + getOutput2ValueOptional, getSwapClaimFee, } from '@penumbra-zone/getters/swap-claim-view'; import { getAmount } from '@penumbra-zone/getters/value-view'; @@ -30,10 +30,10 @@ const getClaimLabel = ( export const SwapClaimViewComponent = ({ value }: { value: SwapClaimView }) => { if (value.swapClaimView.case === 'visible') { const swapTxId = value.swapClaimView.value.swapTx; - const output1Value = getOutput1Value.optional(value); - const output2Value = getOutput2Value.optional(value); - const output1Amount = getAmount.optional(output1Value); - const output2Amount = getAmount.optional(output2Value); + const output1Value = getOutput1ValueOptional(value); + const output2Value = getOutput2ValueOptional(value); + const output1Amount = getAmount.optional()(output1Value); + const output2Amount = getAmount.optional()(output2Value); const claimLabel = getClaimLabel(output1Amount, output2Amount); return ( diff --git a/packages/ui/src/AddressViewComponent/index.tsx b/packages/ui/src/AddressViewComponent/index.tsx index bd3e8679f..967cde6b2 100644 --- a/packages/ui/src/AddressViewComponent/index.tsx +++ b/packages/ui/src/AddressViewComponent/index.tsx @@ -25,7 +25,7 @@ export const AddressViewComponent = ({ addressView, copyable = true }: AddressVi return null; } - const addressIndex = getAddressIndex.optional(addressView); + const addressIndex = getAddressIndex.optional()(addressView); // a randomized index has nonzero randomizer bytes const isRandomized = addressIndex?.randomizer.some(v => v); diff --git a/packages/ui/src/AssetIcon/index.tsx b/packages/ui/src/AssetIcon/index.tsx index 515593a94..d4f87c27e 100644 --- a/packages/ui/src/AssetIcon/index.tsx +++ b/packages/ui/src/AssetIcon/index.tsx @@ -39,7 +39,7 @@ export interface AssetIconProps { export const AssetIcon = ({ metadata, size = 'md' }: AssetIconProps) => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- possibly empty string const icon = metadata?.images[0]?.png || metadata?.images[0]?.svg; - const display = getDisplay.optional(metadata); + const display = getDisplay.optional()(metadata); const isDelegationToken = display ? assetPatterns.delegationToken.matches(display) : false; const isUnbondingToken = display ? assetPatterns.unbondingToken.matches(display) : false; diff --git a/packages/ui/src/AssetSelector/AssetSelectorDialogContent/MetadataOrBalancesResponse/Balance.tsx b/packages/ui/src/AssetSelector/AssetSelectorDialogContent/MetadataOrBalancesResponse/Balance.tsx index 55129b7a9..1309f7543 100644 --- a/packages/ui/src/AssetSelector/AssetSelectorDialogContent/MetadataOrBalancesResponse/Balance.tsx +++ b/packages/ui/src/AssetSelector/AssetSelectorDialogContent/MetadataOrBalancesResponse/Balance.tsx @@ -15,8 +15,8 @@ export interface BalanceProps { } export const Balance = ({ balancesResponse }: BalanceProps) => { - const addressIndexAccount = getAddressIndex.optional(balancesResponse)?.account; - const valueView = getBalanceView.optional(balancesResponse); + const addressIndexAccount = getAddressIndex.optional()(balancesResponse)?.account; + const valueView = getBalanceView.optional()(balancesResponse); return ( {valueView && {getFormattedAmtFromValueView(valueView, true)}} diff --git a/packages/ui/src/AssetSelector/AssetSelectorDialogContent/MetadataOrBalancesResponse/index.tsx b/packages/ui/src/AssetSelector/AssetSelectorDialogContent/MetadataOrBalancesResponse/index.tsx index 007c647fc..38d18a043 100644 --- a/packages/ui/src/AssetSelector/AssetSelectorDialogContent/MetadataOrBalancesResponse/index.tsx +++ b/packages/ui/src/AssetSelector/AssetSelectorDialogContent/MetadataOrBalancesResponse/index.tsx @@ -44,7 +44,7 @@ export const MetadataOrBalancesResponse = ({ isSelected, onSelect, }: MetadataOrBalancesResponseProps) => { - const metadata = isMetadata(value) ? value : getMetadataFromBalancesResponse.optional(value); + const metadata = isMetadata(value) ? value : getMetadataFromBalancesResponse.optional()(value); const isParentAnimating = useIsAnimating(); const [scope, animate] = useAnimate(); const animationControls = useRef(); diff --git a/packages/ui/src/AssetSelector/index.tsx b/packages/ui/src/AssetSelector/index.tsx index 76e7efc65..e783f76fe 100644 --- a/packages/ui/src/AssetSelector/index.tsx +++ b/packages/ui/src/AssetSelector/index.tsx @@ -60,7 +60,7 @@ export const AssetSelector = ) => { const layoutId = useId(); const density = useDensity(); - const metadata = isMetadata(value) ? value : getMetadataFromBalancesResponse.optional(value); + const metadata = isMetadata(value) ? value : getMetadataFromBalancesResponse.optional()(value); const [isOpen, setIsOpen] = useState(false); diff --git a/packages/ui/src/ValueViewComponent/index.tsx b/packages/ui/src/ValueViewComponent/index.tsx index 2d1a2f549..1292f580c 100644 --- a/packages/ui/src/ValueViewComponent/index.tsx +++ b/packages/ui/src/ValueViewComponent/index.tsx @@ -99,7 +99,7 @@ export const ValueViewComponent = ( } const formattedAmount = getFormattedAmtFromValueView(valueView, true); - const metadata = getMetadata.optional(valueView); + const metadata = getMetadata.optional()(valueView); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- possibly empty string const symbol = metadata?.symbol || 'Unknown'; diff --git a/packages/ui/src/WalletBalance/index.tsx b/packages/ui/src/WalletBalance/index.tsx index 58ea2564e..bb79916f5 100644 --- a/packages/ui/src/WalletBalance/index.tsx +++ b/packages/ui/src/WalletBalance/index.tsx @@ -99,9 +99,9 @@ export const WalletBalance = ({ disabled, onClick, }: WalletBalanceProps) => { - const account = getAddressIndex.optional(balance); - const valueView = getBalanceView.optional(balance); - const metadata = getMetadataFromBalancesResponse.optional(balance); + const account = getAddressIndex.optional()(balance); + const valueView = getBalanceView.optional()(balance); + const metadata = getMetadataFromBalancesResponse.optional()(balance); if (!valueView || !account || !metadata) { return null; diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 1fb370fe7..44fb6188d 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -1,10 +1,19 @@ { "compilerOptions": { "target": "ESNext", - "lib": ["ESNext", "DOM", "DOM.Iterable"], + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", "exactOptionalPropertyTypes": false, + "allowImportingTsExtensions": true, + "isolatedModules": true, "noEmit": true, - "composite": true + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "declaration": true }, "extends": ["@tsconfig/strictest/tsconfig.json", "@tsconfig/vite-react/tsconfig.json"], "include": ["components", "lib", "src", "tests-setup.ts"]