From efdac06140bc7a9c314be9c4460675b8feb0d674 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Thu, 15 Jan 2026 15:34:55 +0100 Subject: [PATCH 01/34] Make UpdateMoneyRequestData type generic and replace default OnyxUpdate union type --- src/libs/actions/IOU/index.ts | 52 +++++++++++++++++++++------- src/libs/actions/MergeTransaction.ts | 4 +-- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index a318c227d7ba..447bccee9bc9 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -420,9 +420,9 @@ type SplitsAndOnyxData = { onyxData: OnyxData; }; -type UpdateMoneyRequestData = { +type UpdateMoneyRequestData = { params: UpdateMoneyRequestParams; - onyxData: OnyxData; + onyxData: OnyxData; }; type PayMoneyRequestData = { @@ -4079,7 +4079,20 @@ type GetUpdateMoneyRequestParamsType = { policyRecentlyUsedCurrencies?: string[]; }; -function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): UpdateMoneyRequestData { +type UpdateMoneyRequestDataKeys = + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES + | typeof ONYXKEYS.RECENTLY_USED_CURRENCIES + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.NVP_RECENT_ATTENDEES + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + | typeof ONYXKEYS.COLLECTION.NEXT_STEP + | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT; + +function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): UpdateMoneyRequestData { const { transactionID, transactionThreadReport, @@ -4100,7 +4113,20 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U isASAPSubmitBetaEnabled, policyRecentlyUsedCurrencies, } = params; - const optimisticData: OnyxUpdate[] = []; + const optimisticData: Array< + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES + | typeof ONYXKEYS.RECENTLY_USED_CURRENCIES + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.NVP_RECENT_ATTENDEES + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + | typeof ONYXKEYS.COLLECTION.NEXT_STEP + > + > = []; const successData: Array< OnyxUpdate > = []; @@ -4409,7 +4435,6 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 value: violations?.filter((violation) => violation.name !== 'overLimit') ?? [], }); } @@ -4643,7 +4668,9 @@ function getUpdateTrackExpenseParams( transactionChanges: TransactionChanges, policy: OnyxEntry, shouldBuildOptimisticModifiedExpenseReportAction = true, -): UpdateMoneyRequestData { +): UpdateMoneyRequestData< + typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.TRANSACTION | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT +> { const optimisticData: Array> = []; const successData: Array> = []; const failureData: Array> = []; @@ -4843,7 +4870,7 @@ function updateMoneyRequestDate({ const transactionChanges: TransactionChanges = { created: value, }; - let data: UpdateMoneyRequestData; + let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); @@ -4949,7 +4976,7 @@ function updateMoneyRequestMerchant( const transactionChanges: TransactionChanges = { merchant: value, }; - let data: UpdateMoneyRequestData; + let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); @@ -5189,7 +5216,7 @@ function updateMoneyRequestDistance({ ...(odometerStart !== undefined && {odometerStart}), ...(odometerEnd !== undefined && {odometerEnd}), }; - let data: UpdateMoneyRequestData; + let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); @@ -5327,7 +5354,7 @@ function updateMoneyRequestDescription( const transactionChanges: TransactionChanges = { comment: parsedComment, }; - let data: UpdateMoneyRequestData; + let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); @@ -5395,7 +5422,7 @@ function updateMoneyRequestDistanceRate({ } } - let data: UpdateMoneyRequestData; + let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); @@ -8450,7 +8477,7 @@ function updateMoneyRequestAmountAndCurrency({ taxAmount, }; - let data: UpdateMoneyRequestData; + let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); @@ -14347,6 +14374,7 @@ export type { RequestMoneyParticipantParams, PerDiemExpenseTransactionParams, UpdateMoneyRequestData, + UpdateMoneyRequestDataKeys, BasePolicyParams, RejectMoneyRequestData, }; diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index 7941e91ef389..9258c4567fa6 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -24,7 +24,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {CardList, MergeTransaction, Policy, PolicyCategories, PolicyTagLists, Report, Transaction, TransactionViolations} from '@src/types/onyx'; import {getUpdateMoneyRequestParams, getUpdateTrackExpenseParams} from './IOU'; -import type {UpdateMoneyRequestData} from './IOU'; +import type {UpdateMoneyRequestData, UpdateMoneyRequestDataKeys} from './IOU'; /** * Setup merge transaction data for merging flow @@ -205,7 +205,7 @@ function getOnyxTargetTransactionData({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; }) { - let data: UpdateMoneyRequestData; + let data: UpdateMoneyRequestData; const isUnreportedExpense = !mergeTransaction.reportID || mergeTransaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID; // Compare mergeTransaction with targetTransaction and remove fields with same values From 3f0abfb16012e1d09cf334f9e933775d1df16afa Mon Sep 17 00:00:00 2001 From: Olgierd Date: Thu, 15 Jan 2026 16:54:50 +0100 Subject: [PATCH 02/34] Replace default union OnyxUpdate type in buildOnyxDataForMoneyRequest function --- src/libs/actions/IOU/index.ts | 92 +++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 14 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 447bccee9bc9..c32d4d4bc975 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1675,7 +1675,25 @@ function buildOnyxDataForTestDriveIOU( } /** Builds the Onyx data for an expense */ -function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyRequestParams): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { +function buildOnyxDataForMoneyRequest( + moneyRequestParams: BuildOnyxDataForMoneyRequestParams, +): OnyxData< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES + | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.RECENTLY_USED_CURRENCIES + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST + | typeof ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING + | typeof ONYXKEYS.COLLECTION.NEXT_STEP + | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.SNAPSHOT +> { const { isNewChatReport, shouldCreateNewMoneyRequestReport, @@ -1710,9 +1728,47 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); const isMoneyRequestToManagerMcTest = isTestTransactionReport(iou.report); - const optimisticData: OnyxUpdate[] = []; - const successData: OnyxUpdate[] = []; - const failureData: OnyxUpdate[] = []; + const optimisticData: Array< + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES + | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.RECENTLY_USED_CURRENCIES + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS + | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST + | typeof ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING + | typeof ONYXKEYS.COLLECTION.NEXT_STEP + | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + > + > = []; + const successData: Array< + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT + > + > = []; + const failureData: Array< + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT + > + > = []; + let newQuickAction: ValueOf; if (isScanRequest) { newQuickAction = CONST.QUICK_ACTIONS.REQUEST_SCAN; @@ -1913,10 +1969,10 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR iouAction = optimisticIOUReportAction; optimisticData.push( - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING}`, + // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 value: {[CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP]: DateUtils.getDBTime(date.valueOf())}, }, { @@ -2265,7 +2321,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR // We don't need to compute violations unless we're on a paid policy if (!policy || !isPaidGroupPolicy(policy) || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID) { - return [optimisticData, successData, failureData]; + return {optimisticData, successData, failureData}; } const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( transaction, @@ -2341,7 +2397,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR }); } - return [optimisticData, successData, failureData]; + return {optimisticData, successData, failureData}; } type BuildOnyxDataForTrackExpenseParams = { @@ -3310,7 +3366,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma }); // STEP 5: Build Onyx Data - const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest({ + const {optimisticData, successData, failureData} = buildOnyxDataForMoneyRequest({ participant, isNewChatReport, shouldCreateNewMoneyRequestReport, @@ -3632,7 +3688,7 @@ function getPerDiemExpenseInformation(perDiemExpenseInformation: PerDiemExpenseI }); // STEP 5: Build Onyx Data - const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest({ + const {optimisticData, successData, failureData} = buildOnyxDataForMoneyRequest({ isNewChatReport, shouldCreateNewMoneyRequestReport, policyParams: { @@ -7213,7 +7269,11 @@ function createSplitsAndOnyxData({ const hasViolations = hasViolationsReportUtils(oneOnOneIOUReport.reportID, transactionViolations, currentUserAccountID, currentUserEmailForIOUSplit); // STEP 5: Build Onyx Data - const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest({ + const { + optimisticData: oneOnOneOptimisticData, + successData: oneOnOneSuccessData, + failureData: oneOnOneFailureData, + } = buildOnyxDataForMoneyRequest({ isNewChatReport: isNewOneOnOneChatReport, shouldCreateNewMoneyRequestReport: shouldCreateNewOneOnOneIOUReport, isOneOnOneSplit: true, @@ -7265,9 +7325,9 @@ function createSplitsAndOnyxData({ }; splits.push(individualSplit); - optimisticData.push(...oneOnOneOptimisticData); - successData.push(...oneOnOneSuccessData); - failureData.push(...oneOnOneFailureData); + optimisticData.push(...(oneOnOneOptimisticData ?? [])); + successData.push(...(oneOnOneSuccessData ?? [])); + failureData.push(...(oneOnOneFailureData ?? [])); } optimisticData.push({ @@ -8083,7 +8143,11 @@ function completeSplitBill( } const hasViolations = hasViolationsReportUtils(oneOnOneIOUReport.reportID, transactionViolations, sessionAccountID, sessionEmail ?? ''); - const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest({ + const { + optimisticData: oneOnOneOptimisticData, + successData: oneOnOneSuccessData, + failureData: oneOnOneFailureData, + } = buildOnyxDataForMoneyRequest({ isNewChatReport: isNewOneOnOneChatReport, isOneOnOneSplit: true, shouldCreateNewMoneyRequestReport: shouldCreateNewOneOnOneIOUReport, From 6f4a23ea679167b6cc1363e1f4595b5f9b4f1811 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Thu, 15 Jan 2026 17:31:18 +0100 Subject: [PATCH 03/34] Prevent undefined in data arrays --- src/libs/actions/IOU/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index c32d4d4bc975..c7fdbe0d316c 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -8191,9 +8191,9 @@ function completeSplitBill( createdReportActionIDForThread: optimisticCreatedActionForTransactionThread?.reportActionID, }); - optimisticData.push(...oneOnOneOptimisticData); - successData.push(...oneOnOneSuccessData); - failureData.push(...oneOnOneFailureData); + optimisticData.push(...(oneOnOneOptimisticData ?? [])); + successData.push(...(oneOnOneSuccessData ?? [])); + failureData.push(...(oneOnOneFailureData ?? [])); } const { From 0bb6503babc1ed6ea4f783bf73a558616dbf1534 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Thu, 15 Jan 2026 19:21:49 +0100 Subject: [PATCH 04/34] Create BuildOnyxDataForMoneyRequestKeys type to replace default union OnyxUpdate type --- src/libs/actions/IOU/index.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index c7fdbe0d316c..ad67639272de 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -304,7 +304,7 @@ type MoneyRequestInformation = { reportPreviewAction: OnyxTypes.ReportAction; transactionThreadReportID?: string; createdReportActionIDForThread: string | undefined; - onyxData: OnyxData; + onyxData: OnyxData; billable?: boolean; reimbursable?: boolean; }; @@ -417,7 +417,7 @@ type SplitData = { type SplitsAndOnyxData = { splitData: SplitData; splits: Split[]; - onyxData: OnyxData; + onyxData: OnyxData; }; type UpdateMoneyRequestData = { @@ -1674,10 +1674,7 @@ function buildOnyxDataForTestDriveIOU( }; } -/** Builds the Onyx data for an expense */ -function buildOnyxDataForMoneyRequest( - moneyRequestParams: BuildOnyxDataForMoneyRequestParams, -): OnyxData< +type BuildOnyxDataForMoneyRequestKeys = | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.TRANSACTION | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS @@ -1692,8 +1689,10 @@ function buildOnyxDataForMoneyRequest( | typeof ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING | typeof ONYXKEYS.COLLECTION.NEXT_STEP | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE - | typeof ONYXKEYS.COLLECTION.SNAPSHOT -> { + | typeof ONYXKEYS.COLLECTION.SNAPSHOT; + +/** Builds the Onyx data for an expense */ +function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyRequestParams): OnyxData { const { isNewChatReport, shouldCreateNewMoneyRequestReport, @@ -6937,7 +6936,7 @@ function createSplitsAndOnyxData({ }; } - const optimisticData: OnyxUpdate[] = [ + const optimisticData: Array> = [ { // Use set for new reports because it doesn't exist yet, is faster, // and we need the data to be available when we navigate to the chat page @@ -6987,7 +6986,7 @@ function createSplitsAndOnyxData({ }); } - const successData: OnyxUpdate[] = [ + const successData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${splitChatReport.reportID}`, @@ -7022,7 +7021,7 @@ function createSplitsAndOnyxData({ }); } - const failureData: OnyxUpdate[] = [ + const failureData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransaction.transactionID}`, @@ -7958,7 +7957,7 @@ function completeSplitBill( const unmodifiedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; // Save optimistic updated transaction and action - const optimisticData: OnyxUpdate[] = [ + const optimisticData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, @@ -7983,7 +7982,7 @@ function completeSplitBill( }, ]; - const successData: OnyxUpdate[] = [ + const successData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, @@ -7996,7 +7995,7 @@ function completeSplitBill( }, ]; - const failureData: OnyxUpdate[] = [ + const failureData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, From d93b36f3983dbb7782798104449f603faf87695f Mon Sep 17 00:00:00 2001 From: Olgierd Date: Fri, 16 Jan 2026 13:21:35 +0100 Subject: [PATCH 05/34] Replace OnyxData generic argument OnyxKey with union of specific keys --- src/libs/actions/IOU/index.ts | 183 ++++++++++++++++++------------ src/libs/actions/Policy/Policy.ts | 20 +++- 2 files changed, 131 insertions(+), 72 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index ad67639272de..b91a9747a8c8 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -7,7 +7,7 @@ import lodashUnionBy from 'lodash/unionBy'; import {InteractionManager} from 'react-native'; import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxInputValue, OnyxKey, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import type {SetRequired, ValueOf} from 'type-fest'; +import type {ValueOf} from 'type-fest'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; import type {PaymentMethod} from '@components/KYCWall/types'; import type {SearchContextProps, SearchQueryJSON} from '@components/Search/types'; @@ -229,6 +229,7 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import {clearByKey as clearPdfByOnyxKey} from '@userActions/CachedPDFPaths'; import {buildAddMembersToWorkspaceOnyxData, buildUpdateWorkspaceMembersRoleOnyxData} from '@userActions/Policy/Member'; import {buildPolicyData, generatePolicyID} from '@userActions/Policy/Policy'; +import type {BuildPolicyDataKeys} from '@userActions/Policy/Policy'; import {buildOptimisticPolicyRecentlyUsedTags, getPolicyTagsData} from '@userActions/Policy/Tag'; import type {GuidedSetupData} from '@userActions/Report'; import {buildInviteToRoomOnyxData, completeOnboarding, getCurrentUserAccountID, notifyNewAction, optimisticReportLastData} from '@userActions/Report'; @@ -366,7 +367,7 @@ type TrackExpenseInformation = { actionableWhisperReportActionIDParam?: string; optimisticReportID: string | undefined; optimisticReportActionID: string | undefined; - onyxData: OnyxData; + onyxData: OnyxData; }; type TrackedExpenseTransactionParams = Omit & { @@ -1579,7 +1580,7 @@ function getReceiptError( } /** Helper function to get optimistic fields violations onyx data */ -function getFieldViolationsOnyxData(iouReport: OnyxTypes.Report): SetRequired, 'optimisticData' | 'failureData'> { +function getFieldViolationsOnyxData(iouReport: OnyxTypes.Report): OnyxData { const missingFields: OnyxTypes.ReportFieldsViolations = {}; const excludedFields = Object.values(CONST.REPORT_VIOLATIONS_EXCLUDED_FIELDS) as string[]; @@ -2413,6 +2414,16 @@ type BuildOnyxDataForTrackExpenseParams = { quickAction: OnyxEntry; }; +type BuildOnyxDataForTrackExpenseKeys = + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.COLLECTION.REPORT_VIOLATIONS; + /** Builds the Onyx data for track expense */ function buildOnyxDataForTrackExpense({ chat, @@ -2426,7 +2437,7 @@ function buildOnyxDataForTrackExpense({ participant, isASAPSubmitBetaEnabled, quickAction, -}: BuildOnyxDataForTrackExpenseParams): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { +}: BuildOnyxDataForTrackExpenseParams): OnyxData { const {report: chatReport, previewAction: reportPreviewAction} = chat; const {report: iouReport, createdAction: iouCreatedAction, action: iouAction} = iou; const {transaction, threadReport: transactionThreadReport, threadCreatedReportAction: transactionThreadCreatedReportAction} = transactionParams; @@ -2436,9 +2447,37 @@ function buildOnyxDataForTrackExpense({ const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); - const optimisticData: OnyxUpdate[] = []; - const successData: OnyxUpdate[] = []; - const failureData: OnyxUpdate[] = []; + const optimisticData: Array< + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.COLLECTION.REPORT_VIOLATIONS + > + > = []; + const successData: Array< + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + > + > = []; + const failureData: Array< + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE + | typeof ONYXKEYS.COLLECTION.REPORT_VIOLATIONS + > + > = []; const isSelfDMReport = isSelfDM(chatReport); let newQuickAction: QuickActionName = isSelfDMReport ? CONST.QUICK_ACTIONS.TRACK_MANUAL : CONST.QUICK_ACTIONS.REQUEST_MANUAL; @@ -2808,7 +2847,7 @@ function buildOnyxDataForTrackExpense({ // We don't need to compute violations unless we're on a paid policy if (!policy || !isPaidGroupPolicy(policy) || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID) { - return [optimisticData, successData, failureData]; + return {optimisticData, successData, failureData}; } const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( @@ -2833,11 +2872,11 @@ function buildOnyxDataForTrackExpense({ // Show field violations only for control policies if (isControlPolicy(policy) && iouReport) { const {optimisticData: fieldViolationsOptimisticData, failureData: fieldViolationsFailureData} = getFieldViolationsOnyxData(iouReport); - optimisticData.push(...fieldViolationsOptimisticData); - failureData.push(...fieldViolationsFailureData); + optimisticData.push(...(fieldViolationsOptimisticData ?? [])); + failureData.push(...(fieldViolationsFailureData ?? [])); } - return [optimisticData, successData, failureData]; + return {optimisticData, successData, failureData}; } function getDeleteTrackExpenseInformation( @@ -3792,9 +3831,9 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T odometerEnd, } = transactionParams; - const optimisticData: OnyxUpdate[] = []; - const successData: OnyxUpdate[] = []; - const failureData: OnyxUpdate[] = []; + const optimisticData: Array> = []; + const successData: Array> = []; + const failureData: Array> = []; const isPolicyExpenseChat = participant.isPolicyExpenseChat; @@ -3904,9 +3943,9 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T activePolicyID, }); createdWorkspaceParams = workspaceData.params; - optimisticData.push(...workspaceData.optimisticData); - successData.push(...workspaceData.successData); - failureData.push(...workspaceData.failureData); + optimisticData.push(...(workspaceData.optimisticData ?? [])); + successData.push(...(workspaceData.successData ?? [])); + failureData.push(...(workspaceData.failureData ?? [])); } // STEP 2: If not in the self-DM flow, we need to use the expense report. @@ -4062,6 +4101,10 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T quickAction, }); + optimisticData.push(...(trackExpenseOnyxData.optimisticData ?? [])); + successData.push(...(trackExpenseOnyxData.successData ?? [])); + failureData.push(...(trackExpenseOnyxData.failureData ?? [])); + return { createdWorkspaceParams, chatReport, @@ -4076,9 +4119,9 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T optimisticReportID, optimisticReportActionID, onyxData: { - optimisticData: optimisticData.concat(trackExpenseOnyxData[0]), - successData: successData.concat(trackExpenseOnyxData[1]), - failureData: failureData.concat(trackExpenseOnyxData[2]), + optimisticData, + successData, + failureData, }, }; } @@ -6563,53 +6606,53 @@ function trackExpense(params: CreateTrackExpenseParams) { actionableWhisperReportActionIDParam, optimisticReportID, optimisticReportActionID, - onyxData, - } = - getTrackExpenseInformation({ - parentChatReport: currentChatReport, - moneyRequestReportID, - existingTransactionID: - isMovingTransactionFromTrackExpense && linkedTrackedExpenseReportAction && isMoneyRequestAction(linkedTrackedExpenseReportAction) - ? getOriginalMessage(linkedTrackedExpenseReportAction)?.IOUTransactionID - : undefined, - participantParams: { - participant, - payeeAccountID, - payeeEmail, - }, - transactionParams: { - comment, - amount, - distance, - currency, - created, - merchant, - receipt: trackedReceipt, - category, - tag, - taxCode, - taxAmount, - billable, - reimbursable, - linkedTrackedExpenseReportAction, - attendees, - odometerStart, - odometerEnd, - }, - policyParams: { - policy, - policyCategories, - policyTagList, - }, - retryParams, - isASAPSubmitBetaEnabled, - currentUserAccountIDParam, - currentUserEmailParam, - introSelected, - activePolicyID, - quickAction, - }) ?? {}; + onyxData: trackExpenseInformationOnyxData, + } = getTrackExpenseInformation({ + parentChatReport: currentChatReport, + moneyRequestReportID, + existingTransactionID: + isMovingTransactionFromTrackExpense && linkedTrackedExpenseReportAction && isMoneyRequestAction(linkedTrackedExpenseReportAction) + ? getOriginalMessage(linkedTrackedExpenseReportAction)?.IOUTransactionID + : undefined, + participantParams: { + participant, + payeeAccountID, + payeeEmail, + }, + transactionParams: { + comment, + amount, + distance, + currency, + created, + merchant, + receipt: trackedReceipt, + category, + tag, + taxCode, + taxAmount, + billable, + reimbursable, + linkedTrackedExpenseReportAction, + attendees, + odometerStart, + odometerEnd, + }, + policyParams: { + policy, + policyCategories, + policyTagList, + }, + retryParams, + isASAPSubmitBetaEnabled, + currentUserAccountIDParam, + currentUserEmailParam, + introSelected, + activePolicyID, + quickAction, + }) ?? {}; const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport?.reportID; + const onyxData = trackExpenseInformationOnyxData as OnyxData; const recentServerValidatedWaypoints = recentWaypoints.filter((item) => !item.pendingAction); onyxData?.failureData?.push({ @@ -6619,10 +6662,10 @@ function trackExpense(params: CreateTrackExpenseParams) { }); if (isMapDistanceRequest(transaction) || isManualDistanceRequestTransactionUtils(transaction) || isOdometerDistanceRequestTransactionUtils(transaction)) { - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 onyxData?.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE, + // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 value: transaction?.iouRequestType, }); } @@ -9821,9 +9864,9 @@ function getPayMoneyRequestParams({ policyName, }; - optimisticData.push(...policyOptimisticData, {onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, value: payerPolicyID}); - successData.push(...policySuccessData); - failureData.push(...policyFailureData, {onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, value: activePolicy?.id ?? null}); + optimisticData.push(...(policyOptimisticData ?? []), {onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, value: payerPolicyID}); + successData.push(...(policySuccessData ?? [])); + failureData.push(...(policyFailureData ?? []), {onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, value: activePolicy?.id ?? null}); } if (isIndividualInvoiceRoom(chatReport) && payAsBusiness && existingB2BInvoiceReport) { diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 0525b2e9f5e1..94b809ae2e7f 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2079,6 +2079,22 @@ function createDraftInitialWorkspace( Onyx.update(optimisticData); } +type BuildPolicyDataKeys = + | typeof ONYXKEYS.COLLECTION.POLICY + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.POLICY_DRAFTS + | typeof ONYXKEYS.NVP_ACTIVE_POLICY_ID + | typeof ONYXKEYS.NVP_INTRO_SELECTED + | typeof ONYXKEYS.NVP_ONBOARDING + | typeof ONYXKEYS.COLLECTION.REPORT_DRAFT + | typeof ONYXKEYS.COLLECTION.POLICY_CATEGORIES + | typeof ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT + | typeof ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS + | typeof ONYXKEYS.NVP_LAST_PAYMENT_METHOD + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST; + /** * Generates onyx data for creating a new workspace * @@ -2092,7 +2108,7 @@ function createDraftInitialWorkspace( * @param [file] Optional, avatar file for workspace * @param [shouldAddOnboardingTasks] whether to add onboarding tasks to the workspace */ -function buildPolicyData(options: BuildPolicyDataOptions) { +function buildPolicyData(options: BuildPolicyDataOptions): OnyxData & {params: CreateWorkspaceParams} { const { policyOwnerEmail = '', makeMeAdmin = false, @@ -2325,7 +2341,6 @@ function buildPolicyData(options: BuildPolicyDataOptions) { | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.REPORT_METADATA | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.NVP_LAST_PAYMENT_METHOD | typeof ONYXKEYS.NVP_ONBOARDING | typeof ONYXKEYS.COLLECTION.POLICY_CATEGORIES @@ -6670,3 +6685,4 @@ export { setWorkspaceConfirmationCurrency, setPolicyRequireCompanyCardsEnabled, }; +export type {BuildPolicyDataKeys}; From 688db53c7cfc0daecc73d2477d43c3c6526c9818 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 19 Jan 2026 13:22:24 +0100 Subject: [PATCH 06/34] Replace OnyxData generic argument OnyxKey with union of specific keys - TrackedExpenseParams type --- src/libs/actions/IOU/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 13a963d5b2f2..3bfa7b2d54f0 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -398,7 +398,7 @@ type TrackedExpenseReportInformation = { isLinkedTrackedExpenseReportArchived: boolean | undefined; }; type TrackedExpenseParams = { - onyxData?: OnyxData; + onyxData?: OnyxData; reportInformation: TrackedExpenseReportInformation; transactionParams: TrackedExpenseTransactionParams; policyParams: TrackedExpensePolicyParams; @@ -3785,7 +3785,7 @@ function getPerDiemExpenseInformation(perDiemExpenseInformation: PerDiemExpenseI * Gathers all the data needed to make an expense. It attempts to find existing reports, iouReports, and receipts. If it doesn't find them, then * it creates optimistic versions of them and uses those instead */ -function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): TrackExpenseInformation | null { +function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): TrackExpenseInformation { const { parentChatReport, moneyRequestReportID = '', @@ -6737,7 +6737,8 @@ function trackExpense(params: CreateTrackExpenseParams) { quickAction, }) ?? {}; const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport?.reportID; - const onyxData = trackExpenseInformationOnyxData as OnyxData; + const onyxData: OnyxData = + trackExpenseInformationOnyxData; const recentServerValidatedWaypoints = recentWaypoints.filter((item) => !item.pendingAction); onyxData?.failureData?.push({ @@ -6747,10 +6748,10 @@ function trackExpense(params: CreateTrackExpenseParams) { }); if (isMapDistanceRequest(transaction) || isManualDistanceRequestTransactionUtils(transaction) || isOdometerDistanceRequestTransactionUtils(transaction)) { + // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 onyxData?.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE, - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 value: transaction?.iouRequestType, }); } From 4f663885db28646c403fe4367ea9cb4d3a87c95f Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 19 Jan 2026 13:30:56 +0100 Subject: [PATCH 07/34] Replace OnyxData generic argument OnyxKey with union of specific keys - ConvertTrackedExpenseToRequestParams type --- src/libs/actions/IOU/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 3bfa7b2d54f0..23f7288de525 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -5761,7 +5761,7 @@ type ConvertTrackedExpenseToRequestParams = { createdReportActionID: string | undefined; reportActionID: string; }; - onyxData: OnyxData; + onyxData: OnyxData; workspaceParams?: ConvertTrackedWorkspaceParams; }; From c53f68421331b12cf91a89a94c1a70da6f79440e Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 19 Jan 2026 13:42:10 +0100 Subject: [PATCH 08/34] Replace OnyxData generic argument OnyxKey with union of specific keys - addTrackedExpenseToPolicy argument type --- src/libs/actions/IOU/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 23f7288de525..2ebb4d611a87 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -5765,7 +5765,7 @@ type ConvertTrackedExpenseToRequestParams = { workspaceParams?: ConvertTrackedWorkspaceParams; }; -function addTrackedExpenseToPolicy(parameters: AddTrackedExpenseToPolicyParam, onyxData: OnyxData) { +function addTrackedExpenseToPolicy(parameters: AddTrackedExpenseToPolicyParam, onyxData: OnyxData) { API.write(WRITE_COMMANDS.ADD_TRACKED_EXPENSE_TO_POLICY, parameters, onyxData); } @@ -5787,9 +5787,9 @@ function convertTrackedExpenseToRequest(convertTrackedExpenseParams: ConvertTrac transactionThreadReportID, isLinkedTrackedExpenseReportArchived, } = transactionParams; - const optimisticData: Array> = []; - const successData: Array> = []; - const failureData: Array> = []; + const optimisticData: Array> = []; + const successData: Array> = []; + const failureData: Array> = []; optimisticData?.push(...(onyxData.optimisticData ?? [])); successData?.push(...(onyxData.successData ?? [])); From 132082d27c0195d1ebef6784d32c62bda4b9dcb0 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 19 Jan 2026 13:52:14 +0100 Subject: [PATCH 09/34] Replace OnyxData generic argument OnyxKey with union of specific keys - onyxData variable --- src/libs/actions/IOU/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 2ebb4d611a87..826505882d39 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -8447,7 +8447,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest : receipt; let parameters: CreateDistanceRequestParams; - let onyxData: OnyxData; + let onyxData: OnyxData; const sanitizedWaypoints = !isManualDistanceRequest ? sanitizeRecentWaypoints(validWaypoints) : null; if (iouType === CONST.IOU.TYPE.SPLIT) { const { @@ -8564,10 +8564,10 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest onyxData = moneyRequestOnyxData; if (transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MAP || isManualDistanceRequest || transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER) { - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 onyxData?.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE, + // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 value: transaction.iouRequestType, }); } From 580fd46d06201685de2e14a7acb249a6f2511ddd Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 19 Jan 2026 15:01:01 +0100 Subject: [PATCH 10/34] Remove redundant OnyxData type variable --- src/libs/actions/IOU/index.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 826505882d39..e18b9dd871ee 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -13821,7 +13821,6 @@ function updateSplitTransactions({ policyRecentlyUsedCurrencies, }); - let updateMoneyRequestParamsOnyxData: OnyxData = {}; const currentSplit = splits.at(index); // For existing split transactions, update the field change messages @@ -13874,7 +13873,9 @@ function updateSplitTransactions({ if (currentSplit) { currentSplit.modifiedExpenseReportActionID = params.reportActionID; } - updateMoneyRequestParamsOnyxData = moneyRequestParamsOnyxData; + optimisticData.push(...(moneyRequestParamsOnyxData.optimisticData ?? [])); + successData.push(...(moneyRequestParamsOnyxData.successData ?? [])); + failureData.push(...(moneyRequestParamsOnyxData.failureData ?? [])); } // For new split transactions, set the reportID once the transaction and associated report are created } else if (currentSplit) { @@ -13887,9 +13888,9 @@ function updateSplitTransactions({ currentSplit.splitReportActionID = iouAction.reportActionID; } - optimisticData.push(...(onyxData.optimisticData ?? []), ...(updateMoneyRequestParamsOnyxData.optimisticData ?? [])); - successData.push(...(onyxData.successData ?? []), ...(updateMoneyRequestParamsOnyxData.successData ?? [])); - failureData.push(...(onyxData.failureData ?? []), ...(updateMoneyRequestParamsOnyxData.failureData ?? [])); + optimisticData.push(...(onyxData.optimisticData ?? [])); + successData.push(...(onyxData.successData ?? [])); + failureData.push(...(onyxData.failureData ?? [])); } // All transactions that were deleted in the split list will be marked as deleted in onyx From c28689f25d597b4159814878afe9252b2c25c853 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 19 Jan 2026 18:05:26 +0100 Subject: [PATCH 11/34] Replace OnyxData generic argument OnyxKey with union of specific keys - removeTransactionFromDuplicateTransactionViolation argument --- src/libs/TransactionUtils/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 5d1858e27346..fcf3d28b5f9d 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -3,7 +3,7 @@ import {deepEqual} from 'fast-equals'; import lodashDeepClone from 'lodash/cloneDeep'; import lodashHas from 'lodash/has'; import lodashSet from 'lodash/set'; -import type {OnyxCollection, OnyxEntry, OnyxKey} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {Coordinate} from '@components/MapView/MapViewTypes'; @@ -48,7 +48,7 @@ import { isSettled, isThread, } from '@libs/ReportUtils'; -import type {IOURequestType} from '@userActions/IOU'; +import type {IOURequestType, UpdateMoneyRequestDataKeys} from '@userActions/IOU'; import CONST from '@src/CONST'; import type {IOUType} from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; @@ -2135,7 +2135,7 @@ function getValidDuplicateTransactionIDs(transactionID: string, transactionColle * */ function removeTransactionFromDuplicateTransactionViolation( - onyxData: OnyxData, + onyxData: OnyxData, transactionID: string, transactions: OnyxCollection, transactionViolations: OnyxCollection, From 51f5180746e9353becd3687ce0fc60588cea8a4c Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 19 Jan 2026 19:03:25 +0100 Subject: [PATCH 12/34] Replace OnyxData generic argument OnyxKey with union of specific keys - paginate --- src/libs/API/index.ts | 21 +++++++++++++++++---- src/libs/actions/Report.ts | 23 +++++++++++------------ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index 049e01a58196..63725be61dd2 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -12,6 +12,7 @@ import Pusher from '@libs/Pusher'; import {addMiddleware, processWithMiddleware} from '@libs/Request'; import {getAll, getLength as getPersistedRequestsLength} from '@userActions/PersistedRequests'; import CONST from '@src/CONST'; +import type ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; import type {OnyxData, PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; @@ -237,25 +238,37 @@ function read(command: TCommand, apiCommandParamet }); } +type PaginateOnyxKeys = + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.NVP_INTRO_SELECTED + | typeof ONYXKEYS.COLLECTION.POLICY + | typeof ONYXKEYS.NVP_ONBOARDING + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST + | typeof ONYXKEYS.IS_CHECKING_PUBLIC_ROOM; + function paginate>( type: TRequestType, command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData, + onyxData: OnyxData, config: PaginationConfig, ): Promise; function paginate>( type: TRequestType, command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData, + onyxData: OnyxData, config: PaginationConfig, ): void; function paginate>( type: TRequestType, command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData, + onyxData: OnyxData, config: PaginationConfig, conflictResolver?: RequestConflictResolver, ): void; @@ -263,7 +276,7 @@ function paginate, + onyxData: OnyxData, config: PaginationConfig, conflictResolver: RequestConflictResolver = {}, ): Promise | void { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index b8cfdb7019ca..1d09a367ff9a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -995,18 +995,17 @@ function openReport( }; const optimisticData: Array< - | OnyxUpdate< - | typeof ONYXKEYS.COLLECTION.REPORT_METADATA - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.TRANSACTION - | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.NVP_INTRO_SELECTED - | typeof ONYXKEYS.COLLECTION.POLICY - | typeof ONYXKEYS.NVP_ONBOARDING - | typeof ONYXKEYS.PERSONAL_DETAILS_LIST - > - | OnyxUpdate + OnyxUpdate< + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.TRANSACTION + | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.NVP_INTRO_SELECTED + | typeof ONYXKEYS.COLLECTION.POLICY + | typeof ONYXKEYS.NVP_ONBOARDING + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST + > > = [ { onyxMethod: Onyx.METHOD.MERGE, From 6a6ef8ad3e59280b55ccc6b64eaedcc149be8557 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Tue, 20 Jan 2026 12:07:43 +0100 Subject: [PATCH 13/34] Replace OnyxData generic argument OnyxKey with union of specific keys - API/index.ts functions --- src/libs/API/index.ts | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index 63725be61dd2..f77333b3a62c 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -163,7 +163,13 @@ function write( function writeWithNoDuplicatesConflictAction( command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData = {}, + onyxData: OnyxData< + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.IS_LOADING_REPORT_DATA + | typeof ONYXKEYS.HAS_LOADED_APP + | typeof ONYXKEYS.IS_LOADING_APP + | typeof ONYXKEYS.LAST_FULL_RECONNECT_TIME + > = {}, requestMatcher: RequestMatcher = (request) => request.command === command, ): Promise { const conflictResolver = { @@ -180,7 +186,7 @@ function writeWithNoDuplicatesConflictAction( function writeWithNoDuplicatesEnableFeatureConflicts( command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData = {}, + onyxData: OnyxData = {}, ): Promise { const conflictResolver = { checkAndFixConflictingRequest: (persistedRequests: OnyxRequest[]) => resolveEnableFeatureConflicts(command, persistedRequests, apiCommandParameters), @@ -200,7 +206,29 @@ function writeWithNoDuplicatesEnableFeatureConflicts( command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData = {}, + onyxData: OnyxData< + | typeof ONYXKEYS.COLLECTION.POLICY + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.SNAPSHOT + | typeof ONYXKEYS.IS_LOADING_REPORT_DATA + | typeof ONYXKEYS.HAS_LOADED_APP + | typeof ONYXKEYS.IS_LOADING_APP + | typeof ONYXKEYS.LAST_FULL_RECONNECT_TIME + | typeof ONYXKEYS.CARD_LIST + | typeof ONYXKEYS.ACCOUNT + | typeof ONYXKEYS.PRIVATE_PERSONAL_DETAILS + | typeof ONYXKEYS.IS_LOADING_BILL_WHEN_DOWNGRADE + | typeof ONYXKEYS.NVP_INTRO_SELECTED + | typeof ONYXKEYS.NVP_ONBOARDING + | typeof ONYXKEYS.ONBOARDING_ERROR_MESSAGE_TRANSLATION_KEY + | typeof ONYXKEYS.NVP_TRAVEL_SETTINGS + | typeof ONYXKEYS.TRAVEL_PROVISIONING + | typeof ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE + | typeof ONYXKEYS.NVP_TRY_NEW_DOT + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST + > = {}, ): Promise { Log.info('[API] Called API makeRequestWithSideEffects', false, {command, ...apiCommandParameters}); const request = prepareRequest(command, CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS, apiCommandParameters, onyxData); From fdf7a05bdb80974dc0cbe4b5f9747429272d57c7 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 20 Jan 2026 15:16:23 +0100 Subject: [PATCH 14/34] Remove all OnyxKey default arguments --- src/libs/API/index.ts | 99 ++++++++---------------- src/libs/Middleware/types.ts | 3 +- src/libs/Network/SequentialQueue.ts | 4 +- src/libs/Request.ts | 5 +- src/libs/actions/PersistedRequests.ts | 21 ++--- src/libs/actions/RequestConflictUtils.ts | 4 +- src/types/onyx/Request.ts | 16 ++-- 7 files changed, 59 insertions(+), 93 deletions(-) diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index f77333b3a62c..ad2452870180 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -12,7 +12,6 @@ import Pusher from '@libs/Pusher'; import {addMiddleware, processWithMiddleware} from '@libs/Request'; import {getAll, getLength as getPersistedRequestsLength} from '@userActions/PersistedRequests'; import CONST from '@src/CONST'; -import type ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; import type {OnyxData, PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; @@ -55,13 +54,13 @@ let requestIndex = 0; /** * Prepare the request to be sent. Bind data together with request metadata and apply optimistic Onyx data. */ -function prepareRequest( +function prepareRequest( command: TCommand, type: ApiRequestType, params: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData = {}, - conflictResolver: RequestConflictResolver = {}, -): OnyxRequest { + onyxData: OnyxData = {}, + conflictResolver: RequestConflictResolver = {}, +): OnyxRequest { Log.info('[API] Preparing request', false, {command, type}); let shouldApplyOptimisticData = true; @@ -96,7 +95,7 @@ function prepareRequest( }; // Assemble all request metadata (used by middlewares, and for persisted requests stored in Onyx) - const request: SetRequired = { + const request: SetRequired, 'data'> = { command, data, initiatedOffline: isOffline(), @@ -119,7 +118,7 @@ function prepareRequest( /** * Process a prepared request according to its type. */ -function processRequest(request: OnyxRequest, type: ApiRequestType): Promise { +function processRequest(request: OnyxRequest, type: ApiRequestType): Promise { Log.info('[API] Processing request', false, {command: request.command, type}); // Write commands can be saved and retried, so push it to the SequentialQueue if (type === CONST.API_REQUEST_TYPE.WRITE) { @@ -145,11 +144,11 @@ function processRequest(request: OnyxRequest, type: ApiRequestType): Promise( +function write( command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData = {}, - conflictResolver: RequestConflictResolver = {}, + onyxData: OnyxData = {}, + conflictResolver: RequestConflictResolver = {}, ): Promise { Log.info('[API] Called API write', false, {command, ...apiCommandParameters}); const request = prepareRequest(command, CONST.API_REQUEST_TYPE.WRITE, apiCommandParameters, onyxData, conflictResolver); @@ -160,17 +159,11 @@ function write( * This function is used to write data to the API while ensuring that there are no duplicate requests in the queue. * If a duplicate request is found, it resolves the conflict by replacing the duplicated request with the new one. */ -function writeWithNoDuplicatesConflictAction( +function writeWithNoDuplicatesConflictAction( command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData< - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.IS_LOADING_REPORT_DATA - | typeof ONYXKEYS.HAS_LOADED_APP - | typeof ONYXKEYS.IS_LOADING_APP - | typeof ONYXKEYS.LAST_FULL_RECONNECT_TIME - > = {}, - requestMatcher: RequestMatcher = (request) => request.command === command, + onyxData: OnyxData = {}, + requestMatcher: RequestMatcher = (request) => request.command === command, ): Promise { const conflictResolver = { checkAndFixConflictingRequest: (persistedRequests: OnyxRequest[]) => resolveDuplicationConflictAction(persistedRequests, requestMatcher), @@ -183,10 +176,10 @@ function writeWithNoDuplicatesConflictAction( * This function is used to write data to the API while ensuring that there are no conflicts with enabling policy features. * If a conflict is found, it resolves the conflict by deleting the duplicated request. */ -function writeWithNoDuplicatesEnableFeatureConflicts( +function writeWithNoDuplicatesEnableFeatureConflicts( command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData = {}, + onyxData: OnyxData = {}, ): Promise { const conflictResolver = { checkAndFixConflictingRequest: (persistedRequests: OnyxRequest[]) => resolveEnableFeatureConflicts(command, persistedRequests, apiCommandParameters), @@ -203,32 +196,10 @@ function writeWithNoDuplicatesEnableFeatureConflicts( +function makeRequestWithSideEffects( command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData< - | typeof ONYXKEYS.COLLECTION.POLICY - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.REPORT_METADATA - | typeof ONYXKEYS.COLLECTION.SNAPSHOT - | typeof ONYXKEYS.IS_LOADING_REPORT_DATA - | typeof ONYXKEYS.HAS_LOADED_APP - | typeof ONYXKEYS.IS_LOADING_APP - | typeof ONYXKEYS.LAST_FULL_RECONNECT_TIME - | typeof ONYXKEYS.CARD_LIST - | typeof ONYXKEYS.ACCOUNT - | typeof ONYXKEYS.PRIVATE_PERSONAL_DETAILS - | typeof ONYXKEYS.IS_LOADING_BILL_WHEN_DOWNGRADE - | typeof ONYXKEYS.NVP_INTRO_SELECTED - | typeof ONYXKEYS.NVP_ONBOARDING - | typeof ONYXKEYS.ONBOARDING_ERROR_MESSAGE_TRANSLATION_KEY - | typeof ONYXKEYS.NVP_TRAVEL_SETTINGS - | typeof ONYXKEYS.TRAVEL_PROVISIONING - | typeof ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE - | typeof ONYXKEYS.NVP_TRY_NEW_DOT - | typeof ONYXKEYS.PERSONAL_DETAILS_LIST - > = {}, + onyxData: OnyxData = {}, ): Promise { Log.info('[API] Called API makeRequestWithSideEffects', false, {command, ...apiCommandParameters}); const request = prepareRequest(command, CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS, apiCommandParameters, onyxData); @@ -251,7 +222,11 @@ function waitForWrites(command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], onyxData: OnyxData = {}): void { +function read( + command: TCommand, + apiCommandParameters: ApiRequestCommandParameters[TCommand], + onyxData: OnyxData = {} as OnyxData, +): void { Log.info('[API] Called API.read', false, {command, ...apiCommandParameters}); // Apply optimistic updates of read requests immediately @@ -266,50 +241,38 @@ function read(command: TCommand, apiCommandParamet }); } -type PaginateOnyxKeys = - | typeof ONYXKEYS.COLLECTION.REPORT_METADATA - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.TRANSACTION - | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.NVP_INTRO_SELECTED - | typeof ONYXKEYS.COLLECTION.POLICY - | typeof ONYXKEYS.NVP_ONBOARDING - | typeof ONYXKEYS.PERSONAL_DETAILS_LIST - | typeof ONYXKEYS.IS_CHECKING_PUBLIC_ROOM; - -function paginate>( +function paginate, TKey extends OnyxKey>( type: TRequestType, command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData, + onyxData: OnyxData, config: PaginationConfig, ): Promise; -function paginate>( +function paginate, TKey extends OnyxKey>( type: TRequestType, command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData, + onyxData: OnyxData, config: PaginationConfig, ): void; -function paginate>( +function paginate, TKey extends OnyxKey>( type: TRequestType, command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData, + onyxData: OnyxData, config: PaginationConfig, conflictResolver?: RequestConflictResolver, ): void; -function paginate>( +function paginate, TKey extends OnyxKey>( type: TRequestType, command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData, + onyxData: OnyxData, config: PaginationConfig, - conflictResolver: RequestConflictResolver = {}, + conflictResolver: RequestConflictResolver = {}, ): Promise | void { Log.info('[API] Called API.paginate', false, {command, ...apiCommandParameters}); - const request: PaginatedRequest = { + const request: PaginatedRequest = { ...prepareRequest(command, type, apiCommandParameters, onyxData, conflictResolver), ...config, ...{ diff --git a/src/libs/Middleware/types.ts b/src/libs/Middleware/types.ts index 794143123768..fc969d712747 100644 --- a/src/libs/Middleware/types.ts +++ b/src/libs/Middleware/types.ts @@ -1,7 +1,8 @@ +import type {OnyxKey} from 'react-native-onyx'; import type Request from '@src/types/onyx/Request'; import type {PaginatedRequest} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; -type Middleware = (response: Promise, request: Request | PaginatedRequest, isFromSequentialQueue: boolean) => Promise; +type Middleware = (response: Promise, request: Request | PaginatedRequest, isFromSequentialQueue: boolean) => Promise; export default Middleware; diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index e78c84458d00..1570b74f2356 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -322,7 +322,7 @@ onReconnection(flush); // Flush the queue when the persisted requests are initialized onPersistedRequestsInitialization(flush); -function handleConflictActions(conflictAction: ConflictData, newRequest: OnyxRequest) { +function handleConflictActions(conflictAction: ConflictData, newRequest: OnyxRequest) { if (conflictAction.type === 'push') { savePersistedRequest(newRequest); } else if (conflictAction.type === 'replace') { @@ -340,7 +340,7 @@ function handleConflictActions(conflictAction: ConflictData, newRequest: OnyxReq } } -function push(newRequest: OnyxRequest) { +function push(newRequest: OnyxRequest) { const {checkAndFixConflictingRequest} = newRequest; if (checkAndFixConflictingRequest) { diff --git a/src/libs/Request.ts b/src/libs/Request.ts index 3d084af40e3e..0028431cfe38 100644 --- a/src/libs/Request.ts +++ b/src/libs/Request.ts @@ -1,3 +1,4 @@ +import type {OnyxKey} from 'react-native-onyx'; import type Request from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import HttpUtils from './HttpUtils'; @@ -7,14 +8,14 @@ import {hasReadRequiredDataFromStorage} from './Network/NetworkStore'; let middlewares: Middleware[] = []; -function makeXHR(request: Request): Promise { +function makeXHR(request: Request): Promise { const finalParameters = enhanceParameters(request.command, request?.data ?? {}); return hasReadRequiredDataFromStorage().then((): Promise => { return HttpUtils.xhr(request.command, finalParameters, request.type, request.shouldUseSecure, request.initiatedOffline); }); } -function processWithMiddleware(request: Request, isFromSequentialQueue = false): Promise { +function processWithMiddleware(request: Request, isFromSequentialQueue = false): Promise { return middlewares.reduce((last, middleware) => middleware(last, request, isFromSequentialQueue), makeXHR(request)); } diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index e537fedde7a2..e0bdbb5a9d96 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -1,4 +1,5 @@ import {deepEqual} from 'fast-equals'; +import type {OnyxKey} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -75,17 +76,17 @@ function getLength(): number { return persistedRequests.length + (ongoingRequest ? 1 : 0); } -function save(requestToPersist: Request) { +function save(requestToPersist: Request) { Log.info('[PersistedRequests] Saving request to queue started', false, {command: requestToPersist.command}); // If not initialized yet, queue the request for later processing if (!isInitialized) { Log.info('[PersistedRequests] Queueing request until initialization completes', false); - pendingSaveOperations.push(requestToPersist); + pendingSaveOperations.push(requestToPersist as Request); return; } // If the command is not in the keepLastInstance array, add the new request as usual - const requests = [...persistedRequests, requestToPersist]; + const requests: Request[] = [...persistedRequests, requestToPersist as Request]; persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests) .then(() => { @@ -96,7 +97,7 @@ function save(requestToPersist: Request) { }); } -function endRequestAndRemoveFromQueue(requestToRemove: Request) { +function endRequestAndRemoveFromQueue(requestToRemove: Request) { ongoingRequest = null; /** * We only remove the first matching request because the order of requests matters. @@ -132,21 +133,21 @@ function deleteRequestsByIndices(indices: number[]) { }); } -function update(oldRequestIndex: number, newRequest: Request) { - const requests = [...persistedRequests]; +function update(oldRequestIndex: number, newRequest: Request) { + const requests: Request[] = [...persistedRequests]; const oldRequest = requests.at(oldRequestIndex); Log.info('[PersistedRequests] Updating a request', false, {oldRequest, newRequest, oldRequestIndex}); - requests.splice(oldRequestIndex, 1, newRequest); + requests.splice(oldRequestIndex, 1, newRequest as Request); persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); } -function updateOngoingRequest(newRequest: Request) { +function updateOngoingRequest(newRequest: Request) { Log.info('[PersistedRequests] Updating the ongoing request', false, {ongoingRequest, newRequest}); - ongoingRequest = newRequest; + ongoingRequest = newRequest as Request; if (newRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest as Request); } } diff --git a/src/libs/actions/RequestConflictUtils.ts b/src/libs/actions/RequestConflictUtils.ts index b1b239df562b..5fad7ecdada7 100644 --- a/src/libs/actions/RequestConflictUtils.ts +++ b/src/libs/actions/RequestConflictUtils.ts @@ -1,4 +1,4 @@ -import type {OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {TupleToUnion} from 'type-fest'; import type {OpenReportParams, UpdateCommentParams} from '@libs/API/parameters'; @@ -8,7 +8,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; import type {ConflictActionData} from '@src/types/onyx/Request'; -type RequestMatcher = (request: OnyxRequest) => boolean; +type RequestMatcher = (request: OnyxRequest) => boolean; const addNewMessage = new Set([WRITE_COMMANDS.ADD_COMMENT, WRITE_COMMANDS.ADD_ATTACHMENT, WRITE_COMMANDS.ADD_TEXT_AND_ATTACHMENT]); diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index ea361d97cfd4..058930c5f9e6 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -23,7 +23,7 @@ type OnyxData = { type RequestType = 'get' | 'post'; /** Model of overall requests sent to the API */ -type RequestData = { +type RequestData = { /** Name of the API command */ command: string; @@ -74,12 +74,12 @@ type RequestData = { /** * Represents the possible actions to take in case of a conflict in the request queue. */ -type ConflictData = ConflictRequestReplace | ConflictRequestDelete | ConflictRequestPush | ConflictRequestNoAction; +type ConflictData = ConflictRequestReplace | ConflictRequestDelete | ConflictRequestPush | ConflictRequestNoAction; /** * Model of a conflict request that has to be replaced in the request queue. */ -type ConflictRequestReplace = { +type ConflictRequestReplace = { /** * The action to take in case of a conflict. */ @@ -93,7 +93,7 @@ type ConflictRequestReplace = { /** * The new request to replace the existing request in the queue. */ - request?: Request; + request?: Request; }; /** @@ -155,11 +155,11 @@ type ConflictActionData = { * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ -type RequestConflictResolver = { +type RequestConflictResolver = { /** * A function that checks if a new request conflicts with any existing requests in the queue. */ - checkAndFixConflictingRequest?: (persistedRequest: Array>) => ConflictActionData; + checkAndFixConflictingRequest?: (persistedRequest: Request[]) => ConflictActionData; /** * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads @@ -174,7 +174,7 @@ type RequestConflictResolver = { }; /** Model of requests sent to the API */ -type Request = RequestData & OnyxData & RequestConflictResolver; +type Request = RequestData & OnyxData & RequestConflictResolver; /** * An object used to describe how a request can be paginated. @@ -194,7 +194,7 @@ type PaginationConfig = { /** * A paginated request object. */ -type PaginatedRequest = Request & +type PaginatedRequest = Request & PaginationConfig & { /** * A boolean flag to mark a request as Paginated. From b3b582a8e4c784768fccf79c2b26c7896fd08868 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Thu, 22 Jan 2026 10:44:08 +0100 Subject: [PATCH 15/34] Make types and functions generic to reduce the size of used OnyxUpdate type unions --- src/ONYXKEYS.ts | 4 +- src/hooks/useLoadingBarVisibility.ts | 2 +- src/libs/API/index.ts | 24 +++++++---- src/libs/ApiUtils.ts | 6 +-- .../AppState/RequestsQueuesState/types.ts | 2 +- src/libs/Middleware/Logging.ts | 3 +- src/libs/Middleware/Pagination.ts | 4 +- src/libs/Middleware/SaveResponseInOnyx.ts | 4 +- src/libs/Middleware/SupportalPermission.ts | 3 +- src/libs/Network/MainQueue.ts | 13 +++--- src/libs/Network/SequentialQueue.ts | 2 +- src/libs/Notification/triggerNotifications.ts | 4 +- src/libs/PusherUtils.ts | 13 +++--- src/libs/actions/IOU/Duplicate.ts | 14 +++---- src/libs/actions/MergeTransaction.ts | 41 ++++++++++--------- src/libs/actions/OnyxUpdates.ts | 32 ++++++++++----- src/libs/actions/PersistedRequests.ts | 24 +++++------ src/libs/actions/Policy/Policy.ts | 2 +- src/libs/actions/QueuedOnyxUpdates.ts | 4 +- src/libs/actions/RequestConflictUtils.ts | 25 ++++++----- src/libs/tryResolveUrlFromApiRoot.ts | 2 +- src/types/onyx/OnyxUpdatesFromServer.ts | 16 ++++---- src/types/onyx/Request.ts | 16 ++++---- src/types/onyx/Response.ts | 4 +- tests/actions/ReportTest.ts | 6 +-- tests/unit/AvatarUtilsTest.ts | 2 +- tests/unit/RequestTest.ts | 2 +- tests/unit/SequentialQueueTest.ts | 20 ++++----- 28 files changed, 162 insertions(+), 132 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 37cf5fb7cac2..570e8b7f2462 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1183,8 +1183,8 @@ type OnyxValuesMapping = { [ONYXKEYS.DEVICE_ID]: string; [ONYXKEYS.ACTIVATED_CARD_PIN]: string | undefined; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; - [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.Request[]; - [ONYXKEYS.PERSISTED_ONGOING_REQUESTS]: OnyxTypes.Request; + [ONYXKEYS.PERSISTED_REQUESTS]: Array>; + [ONYXKEYS.PERSISTED_ONGOING_REQUESTS]: Array>; [ONYXKEYS.CURRENT_DATE]: string; [ONYXKEYS.CREDENTIALS]: OnyxTypes.Credentials; [ONYXKEYS.STASHED_CREDENTIALS]: OnyxTypes.Credentials; diff --git a/src/hooks/useLoadingBarVisibility.ts b/src/hooks/useLoadingBarVisibility.ts index acf2692c9819..b9a32aa0442b 100644 --- a/src/hooks/useLoadingBarVisibility.ts +++ b/src/hooks/useLoadingBarVisibility.ts @@ -21,7 +21,7 @@ export default function useLoadingBarVisibility(): boolean { } const hasPersistedRequests = !!persistedRequests?.some((request) => RELEVANT_COMMANDS.has(request.command) && !request.initiatedOffline); - const hasOngoingRequests = !!ongoingRequests && RELEVANT_COMMANDS.has(ongoingRequests?.command); + const hasOngoingRequests = !!ongoingRequests?.some((request) => RELEVANT_COMMANDS.has(request.command)); return hasPersistedRequests || hasOngoingRequests; } diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index ad2452870180..811de6f70d43 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -143,6 +143,14 @@ function processRequest(request: OnyxRequest, type: * All calls to API.write() will be persisted to disk as JSON with the params, successData, and failureData (or finallyData, if included in place of the former two values). * This is so that if the network is unavailable or the app is closed, we can send the WRITE request later. */ +function write(command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand]): Promise; + +function write( + command: TCommand, + apiCommandParameters: ApiRequestCommandParameters[TCommand], + onyxData: OnyxData, + conflictResolver?: RequestConflictResolver, +): Promise; function write( command: TCommand, @@ -166,7 +174,7 @@ function writeWithNoDuplicatesConflictAction = (request) => request.command === command, ): Promise { const conflictResolver = { - checkAndFixConflictingRequest: (persistedRequests: OnyxRequest[]) => resolveDuplicationConflictAction(persistedRequests, requestMatcher), + checkAndFixConflictingRequest: (persistedRequests: Array>) => resolveDuplicationConflictAction(persistedRequests, requestMatcher), }; return write(command, apiCommandParameters, onyxData, conflictResolver); @@ -182,7 +190,7 @@ function writeWithNoDuplicatesEnableFeatureConflicts = {}, ): Promise { const conflictResolver = { - checkAndFixConflictingRequest: (persistedRequests: OnyxRequest[]) => resolveEnableFeatureConflicts(command, persistedRequests, apiCommandParameters), + checkAndFixConflictingRequest: (persistedRequests: Array>) => resolveEnableFeatureConflicts(command, persistedRequests, apiCommandParameters), }; return write(command, apiCommandParameters, onyxData, conflictResolver); @@ -222,11 +230,11 @@ function waitForWrites( - command: TCommand, - apiCommandParameters: ApiRequestCommandParameters[TCommand], - onyxData: OnyxData = {} as OnyxData, -): void { +function read(command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand]): void; + +function read(command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], onyxData: OnyxData): void; + +function read(command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], onyxData: OnyxData = {}): void { Log.info('[API] Called API.read', false, {command, ...apiCommandParameters}); // Apply optimistic updates of read requests immediately @@ -261,7 +269,7 @@ function paginate, config: PaginationConfig, - conflictResolver?: RequestConflictResolver, + conflictResolver?: RequestConflictResolver, ): void; function paginate, TKey extends OnyxKey>( type: TRequestType, diff --git a/src/libs/ApiUtils.ts b/src/libs/ApiUtils.ts index a52c7b891e09..d0d917ffbf3c 100644 --- a/src/libs/ApiUtils.ts +++ b/src/libs/ApiUtils.ts @@ -1,4 +1,4 @@ -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxKey} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; @@ -36,7 +36,7 @@ getEnvironment().then((envName) => { * Get the currently used API endpoint, unless forceProduction is set to true * (Non-production environments allow for dynamically switching the API) */ -function getApiRoot(request?: Request, forceProduction = false): string { +function getApiRoot(request?: Request, forceProduction = false): string { const shouldUseSecure = request?.shouldUseSecure ?? false; if (shouldUseStagingServer && forceProduction !== true) { @@ -55,7 +55,7 @@ function getApiRoot(request?: Request, forceProduction = false): string { * Get the command url for the given request * @param - the name of the API command */ -function getCommandURL(request: Request): string { +function getCommandURL(request: Request): string { // If request.command already contains ? then we don't need to append it return `${getApiRoot(request)}api/${request.command}${request.command.includes('?') ? '' : '?'}`; } diff --git a/src/libs/AppState/RequestsQueuesState/types.ts b/src/libs/AppState/RequestsQueuesState/types.ts index aecfb914a415..5324a77646c0 100644 --- a/src/libs/AppState/RequestsQueuesState/types.ts +++ b/src/libs/AppState/RequestsQueuesState/types.ts @@ -11,7 +11,7 @@ type MainQueueInfo = { queuedCommands?: string[]; }; -type OngoingRequestInfo = Pick; +type OngoingRequestInfo = Pick, 'command' | 'persistWhenOngoing' | 'isRollback'>; /** * Persisted requests state diff --git a/src/libs/Middleware/Logging.ts b/src/libs/Middleware/Logging.ts index aec84cf28517..3ffe7725a45e 100644 --- a/src/libs/Middleware/Logging.ts +++ b/src/libs/Middleware/Logging.ts @@ -1,3 +1,4 @@ +import {OnyxKey} from 'react-native-onyx'; import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; import type HttpsError from '@libs/Errors/HttpsError'; import Log from '@libs/Log'; @@ -34,7 +35,7 @@ function serializeLoggingData | undefined>(log } } -function logRequestDetails(message: string, request: Request, response?: Response | void) { +function logRequestDetails(message: string, request: Request, response?: Response | void) { // Don't log about log or else we'd cause an infinite loop if (request.command === 'Log') { return; diff --git a/src/libs/Middleware/Pagination.ts b/src/libs/Middleware/Pagination.ts index acb4850a0022..e15a528e3213 100644 --- a/src/libs/Middleware/Pagination.ts +++ b/src/libs/Middleware/Pagination.ts @@ -1,5 +1,5 @@ import fastMerge from 'expensify-common/dist/fastMerge'; -import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxKey} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ApiCommand} from '@libs/API/types'; import Log from '@libs/Log'; @@ -66,7 +66,7 @@ function registerPaginationConfig(request: Request | PaginatedRequest): request is PaginatedRequest { return 'isPaginated' in request && request.isPaginated; } diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index 3d6c575fb97c..cafa1e061c07 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -32,11 +32,11 @@ const SaveResponseInOnyx: Middleware = (requestResponse, request) => }; if (requestsToIgnoreLastUpdateID.has(request.command) || !OnyxUpdates.doesClientNeedToBeUpdated({previousUpdateID: Number(response?.previousUpdateID ?? CONST.DEFAULT_NUMBER_ID)})) { - return OnyxUpdates.apply(responseToApply); + return OnyxUpdates.apply(responseToApply as any); } // Save the update IDs to Onyx so they can be used to fetch incremental updates if the client gets out of sync from the server - OnyxUpdates.saveUpdateInformation(responseToApply); + OnyxUpdates.saveUpdateInformation(responseToApply as any); // Ensure the queue is paused while the client resolves the gap in onyx updates so that updates are guaranteed to happen in a specific order. return Promise.resolve({ diff --git a/src/libs/Middleware/SupportalPermission.ts b/src/libs/Middleware/SupportalPermission.ts index 47e99c24ded7..c70a76a2bac1 100644 --- a/src/libs/Middleware/SupportalPermission.ts +++ b/src/libs/Middleware/SupportalPermission.ts @@ -1,3 +1,4 @@ +import {OnyxKey} from 'react-native-onyx'; import Log from '@libs/Log'; import {isSupportAuthToken} from '@libs/Network/NetworkStore'; import {showSupportalPermissionDenied} from '@userActions/App'; @@ -9,7 +10,7 @@ import type Middleware from './types'; * Middleware that detects when a support token attempts an unauthorized command * and triggers a global modal while preventing retries for that request. */ -const SupportalPermission: Middleware = (responsePromise: Promise, request: Request) => +const SupportalPermission: Middleware = (responsePromise: Promise, request: Request) => responsePromise.then((response) => { const message = response?.message; const isUnauthorizedSupportalAction = diff --git a/src/libs/Network/MainQueue.ts b/src/libs/Network/MainQueue.ts index 9047b6a3d339..69163ef6a5c4 100644 --- a/src/libs/Network/MainQueue.ts +++ b/src/libs/Network/MainQueue.ts @@ -1,25 +1,26 @@ import {processWithMiddleware} from '@libs/Request'; import type OnyxRequest from '@src/types/onyx/Request'; +import type { OnyxKey } from 'react-native-onyx'; import {isAuthenticating, isOffline} from './NetworkStore'; import {isRunning as sequentialQueueIsRunning} from './SequentialQueue'; // Queue for network requests so we don't lose actions done by the user while offline -let networkRequestQueue: OnyxRequest[] = []; +let networkRequestQueue: Array> = []; /** * Checks to see if a request can be made. */ -function canMakeRequest(request: OnyxRequest): boolean { +function canMakeRequest(request: OnyxRequest): boolean { // Some requests are always made even when we are in the process of authenticating (typically because they require no authToken e.g. Log, BeginSignIn) // However, if we are in the process of authenticating we always want to queue requests until we are no longer authenticating. return request.data?.forceNetworkRequest === true || (!isAuthenticating() && !sequentialQueueIsRunning()); } -function push(request: OnyxRequest) { +function push(request: OnyxRequest) { networkRequestQueue.push(request); } -function replay(request: OnyxRequest) { +function replay(request: OnyxRequest) { push(request); // eslint-disable-next-line @typescript-eslint/no-use-before-define @@ -43,7 +44,7 @@ function process() { // - we are in the process of authenticating and the request is retryable (most are) // - the request does not have forceNetworkRequest === true (this will trigger it to process immediately) // - the request does not have shouldRetry === false (specified when we do not want to retry, defaults to true) - const requestsToProcessOnNextRun: OnyxRequest[] = []; + const requestsToProcessOnNextRun: Array> = []; for (const queuedRequest of networkRequestQueue) { // Check if we can make this request at all and if we can't see if we should save it for the next run or chuck it into the ether @@ -73,7 +74,7 @@ function clear() { networkRequestQueue = networkRequestQueue.filter((request) => !request.data?.canCancel); } -function getAll(): OnyxRequest[] { +function getAll(): Array> { return networkRequestQueue; } diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index 1570b74f2356..f22b222174b9 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -322,7 +322,7 @@ onReconnection(flush); // Flush the queue when the persisted requests are initialized onPersistedRequestsInitialization(flush); -function handleConflictActions(conflictAction: ConflictData, newRequest: OnyxRequest) { +function handleConflictActions(conflictAction: ConflictData, newRequest: OnyxRequest) { if (conflictAction.type === 'push') { savePersistedRequest(newRequest); } else if (conflictAction.type === 'replace') { diff --git a/src/libs/Notification/triggerNotifications.ts b/src/libs/Notification/triggerNotifications.ts index 30b6f50637bf..4c364c3fae50 100644 --- a/src/libs/Notification/triggerNotifications.ts +++ b/src/libs/Notification/triggerNotifications.ts @@ -1,10 +1,10 @@ -import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxKey} from 'react-native-onyx'; import {showReportActionNotification} from '@libs/actions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportAction} from '@src/types/onyx'; import type {OnyxServerUpdate} from '@src/types/onyx/OnyxUpdatesFromServer'; -export default function triggerNotifications(onyxUpdates: OnyxServerUpdate[]): void { +export default function triggerNotifications(onyxUpdates: Array>): void { for (const update of onyxUpdates) { if (!update.shouldNotify && !update.shouldShowPushNotification) { continue; diff --git a/src/libs/PusherUtils.ts b/src/libs/PusherUtils.ts index 703b5bc4a44c..14293b7b3b30 100644 --- a/src/libs/PusherUtils.ts +++ b/src/libs/PusherUtils.ts @@ -1,31 +1,32 @@ -import type {OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import type {OnyxUpdatesFromServer} from '@src/types/onyx'; +import type {OnyxServerUpdate} from '@src/types/onyx/OnyxUpdatesFromServer'; import Log from './Log'; import NetworkConnection from './NetworkConnection'; import Pusher from './Pusher'; import type {PingPongEvent} from './Pusher/types'; -type Callback = (data: OnyxUpdate[]) => Promise; +type Callback = (data: Array>) => Promise; // Keeps track of all the callbacks that need triggered for each event type -const multiEventCallbackMapping: Record = {}; +const multiEventCallbackMapping: Record> = {}; function getUserChannelName(accountID: string) { return `${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${accountID}${CONFIG.PUSHER.SUFFIX}` as const; } -function subscribeToMultiEvent(eventType: string, callback: Callback) { +function subscribeToMultiEvent(eventType: string, callback: Callback) { multiEventCallbackMapping[eventType] = callback; } -function triggerMultiEventHandler(eventType: string, data: OnyxUpdate[]): Promise { +function triggerMultiEventHandler(eventType: string, data: Array>): Promise { if (!multiEventCallbackMapping[eventType]) { Log.warn('[PusherUtils] Received unexpected multi-event', {eventType}); return Promise.resolve(); } - return multiEventCallbackMapping[eventType](data); + return (multiEventCallbackMapping[eventType] as Callback)(data); } /** diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index da8822844c31..080e29a0f6a8 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -88,30 +88,29 @@ function mergeDuplicates({transactionThreadReportID: optimisticTransactionThread value: originalSelectedTransaction as OnyxTypes.Transaction, }; - const optimisticTransactionDuplicatesData: OnyxUpdate[] = params.transactionIDList.map((id) => ({ + const optimisticTransactionDuplicatesData: Array> = params.transactionIDList.map((id) => ({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${id}`, value: null, })); - const failureTransactionDuplicatesData: OnyxUpdate[] = params.transactionIDList.map((id) => ({ + const failureTransactionDuplicatesData: Array> = params.transactionIDList.map((id) => ({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${id}`, // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style value: allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`] as OnyxTypes.Transaction, })); - const optimisticTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => { + const optimisticTransactionViolations: Array> = [...params.transactionIDList, params.transactionID].map((id) => { const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? []; return { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 value: violations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION), }; }); - const failureTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => { + const failureTransactionViolations: Array> = [...params.transactionIDList, params.transactionID].map((id) => { const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? []; return { onyxMethod: Onyx.METHOD.MERGE, @@ -335,19 +334,18 @@ function resolveDuplicates(params: MergeDuplicatesParams) { value: originalSelectedTransaction as OnyxTypes.Transaction, }; - const optimisticTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => { + const optimisticTransactionViolations: Array> = [...params.transactionIDList, params.transactionID].map((id) => { const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? []; const newViolation = {name: CONST.VIOLATIONS.HOLD, type: CONST.VIOLATION_TYPES.VIOLATION}; const updatedViolations = id === params.transactionID ? violations : [...violations, newViolation]; return { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 value: updatedViolations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION), }; }); - const failureTransactionViolations: OnyxUpdate[] = [...params.transactionIDList, params.transactionID].map((id) => { + const failureTransactionViolations: Array> = [...params.transactionIDList, params.transactionID].map((id) => { const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`] ?? []; return { onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index 80209688a876..8dc455688256 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -444,25 +444,28 @@ function mergeTransactionRequest({ }; // Optimistic delete duplicated transaction violations - const optimisticTransactionViolations: OnyxUpdate[] = [targetTransaction.transactionID, sourceTransaction.transactionID].map((id) => { - const violations = allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? []; - - return { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 - value: violations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION), - }; - }); - const failureTransactionViolations: OnyxUpdate[] = [targetTransaction.transactionID, sourceTransaction.transactionID].map((id) => { - const violations = allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? []; - - return { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, - value: violations, - }; - }); + const optimisticTransactionViolations: Array> = [targetTransaction.transactionID, sourceTransaction.transactionID].map( + (id) => { + const violations = allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? []; + + return { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, + value: violations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION), + }; + }, + ); + const failureTransactionViolations: Array> = [targetTransaction.transactionID, sourceTransaction.transactionID].map( + (id) => { + const violations = allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? []; + + return { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`, + value: violations, + }; + }, + ); // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 3feb758a8f07..97fb23c21d09 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -1,5 +1,5 @@ import {Platform} from 'react-native'; -import type {OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {Merge} from 'type-fest'; import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; @@ -31,12 +31,12 @@ let pusherEventsPromise = Promise.resolve(); let airshipEventsPromise = Promise.resolve(); -function applyHTTPSOnyxUpdates(request: Request, response: Response, lastUpdateID: number) { +function applyHTTPSOnyxUpdates(request: Request, response: Response, lastUpdateID: number) { Performance.markStart(CONST.TIMING.APPLY_HTTPS_UPDATES); Log.info('[OnyxUpdateManager] Applying https update', false, {lastUpdateID}); // For most requests we can immediately update Onyx. For write requests we queue the updates and apply them after the sequential queue has flushed to prevent a replay effect in // the UI. See https://github.com/Expensify/App/issues/12775 for more info. - const updateHandler: (updates: OnyxUpdate[]) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxUpdates : Onyx.update; + const updateHandler: (updates: Array>) => Promise = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? queueOnyxUpdates : Onyx.update; // First apply any onyx data updates that are being sent back from the API. We wait for this to complete and then // apply successData or failureData. This ensures that we do not update any pending, loading, or other UI states contained @@ -90,7 +90,7 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response, lastUpdateI }); } -function applyPusherOnyxUpdates(updates: OnyxUpdateEvent[], lastUpdateID: number) { +function applyPusherOnyxUpdates(updates: Array>, lastUpdateID: number) { Performance.markStart(CONST.TIMING.APPLY_PUSHER_UPDATES); pusherEventsPromise = pusherEventsPromise.then(() => { @@ -107,7 +107,7 @@ function applyPusherOnyxUpdates(updates: OnyxUpdateEvent[], lastUpdateID: number return pusherEventsPromise; } -function applyAirshipOnyxUpdates(updates: OnyxUpdateEvent[], lastUpdateID: number) { +function applyAirshipOnyxUpdates(updates: Array>, lastUpdateID: number) { Performance.markStart(CONST.TIMING.APPLY_AIRSHIP_UPDATES); airshipEventsPromise = airshipEventsPromise.then(() => { @@ -115,7 +115,7 @@ function applyAirshipOnyxUpdates(updates: OnyxUpdateEvent[], lastUpdateID: numbe }); airshipEventsPromise = updates - .reduce((promise, update) => promise.then(() => Onyx.update(update.data)), airshipEventsPromise) + .reduce((promise, update) => promise.then(() => Onyx.update(update.data as Array>)), airshipEventsPromise) .then(() => { Performance.markEnd(CONST.TIMING.APPLY_AIRSHIP_UPDATES); Log.info('[OnyxUpdateManager] Done applying Airship updates', false, {lastUpdateID}); @@ -129,10 +129,22 @@ function applyAirshipOnyxUpdates(updates: OnyxUpdateEvent[], lastUpdateID: numbe * @param [updateParams.response] Exists if updateParams.type === 'https' * @param [updateParams.updates] Exists if updateParams.type === 'pusher' */ -function apply({lastUpdateID, type, request, response, updates}: Merge): Promise; -function apply({lastUpdateID, type, request, response, updates}: Merge): Promise; -function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFromServer): Promise; -function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFromServer): Promise | undefined { +function apply({ + lastUpdateID, + type, + request, + response, + updates, +}: Merge, {updates: Array>; type: 'pusher'}>): Promise; +function apply({ + lastUpdateID, + type, + request, + response, + updates, +}: Merge, {request: Request; response: Response; type: 'https'}>): Promise; +function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFromServer): Promise>; +function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFromServer): Promise> | undefined { Log.info(`[OnyxUpdateManager] Applying update type: ${type} with lastUpdateID: ${lastUpdateID}`, false, {command: request?.command}); const isUpdateOld = lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) <= lastUpdateIDAppliedToClient; diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index e0bdbb5a9d96..56b1ca040a54 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -5,9 +5,9 @@ import Log from '@libs/Log'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Request} from '@src/types/onyx'; -let persistedRequests: Request[] = []; -let ongoingRequest: Request | null = null; -let pendingSaveOperations: Request[] = []; +let persistedRequests: Array> = []; +let ongoingRequest: Request | null = null; +let pendingSaveOperations: Array> = []; let isInitialized = false; let initializationCallback: () => void; function triggerInitializationCallback() { @@ -81,12 +81,12 @@ function save(requestToPersist: Request) { // If not initialized yet, queue the request for later processing if (!isInitialized) { Log.info('[PersistedRequests] Queueing request until initialization completes', false); - pendingSaveOperations.push(requestToPersist as Request); + pendingSaveOperations.push(requestToPersist as Request); return; } // If the command is not in the keepLastInstance array, add the new request as usual - const requests: Request[] = [...persistedRequests, requestToPersist as Request]; + const requests: Array> = [...persistedRequests, requestToPersist as Request]; persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests) .then(() => { @@ -134,24 +134,24 @@ function deleteRequestsByIndices(indices: number[]) { } function update(oldRequestIndex: number, newRequest: Request) { - const requests: Request[] = [...persistedRequests]; + const requests: Array> = [...persistedRequests]; const oldRequest = requests.at(oldRequestIndex); Log.info('[PersistedRequests] Updating a request', false, {oldRequest, newRequest, oldRequestIndex}); - requests.splice(oldRequestIndex, 1, newRequest as Request); + requests.splice(oldRequestIndex, 1, newRequest as Request); persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); } function updateOngoingRequest(newRequest: Request) { Log.info('[PersistedRequests] Updating the ongoing request', false, {ongoingRequest, newRequest}); - ongoingRequest = newRequest as Request; + ongoingRequest = newRequest as Request; if (newRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest as Request); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest as Request); } } -function processNextRequest(): Request | null { +function processNextRequest(): Request | null { if (ongoingRequest) { Log.info(`Ongoing Request already set returning same one ${ongoingRequest.commandName}`); return ongoingRequest; @@ -187,11 +187,11 @@ function rollbackOngoingRequest() { ongoingRequest = null; } -function getAll(): Request[] { +function getAll(): Array> { return persistedRequests; } -function getOngoingRequest(): Request | null { +function getOngoingRequest(): Request | null { return ongoingRequest; } diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index ad5eabf066b2..67493b0b7140 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1725,7 +1725,7 @@ function updateGeneralSettings(policyID: string | undefined, name: string, curre const createWorkspaceRequest = persistedRequests.at(createWorkspaceRequestChangedIndex); if (createWorkspaceRequest && createWorkspaceRequestChangedIndex !== -1) { - const workspaceRequest: Request = { + const workspaceRequest: Request = { ...createWorkspaceRequest, data: { ...createWorkspaceRequest.data, diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index 9fb826445f1f..4c0f0ff38202 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -5,7 +5,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; // In this file we manage a queue of Onyx updates while the SequentialQueue is processing. There are functions to get the updates and clear the queue after saving the updates in Onyx. -let queuedOnyxUpdates: OnyxUpdate[] = []; +let queuedOnyxUpdates: Array> = []; let currentAccountID: number | undefined; // We use `connectWithoutView` because it is not connected to any UI component. @@ -19,7 +19,7 @@ Onyx.connectWithoutView({ /** * @param updates Onyx updates to queue for later */ -function queueOnyxUpdates(updates: OnyxUpdate[]): Promise { +function queueOnyxUpdates(updates: Array>): Promise { queuedOnyxUpdates = queuedOnyxUpdates.concat(updates); return Promise.resolve(); diff --git a/src/libs/actions/RequestConflictUtils.ts b/src/libs/actions/RequestConflictUtils.ts index 5fad7ecdada7..a9f16b05aab8 100644 --- a/src/libs/actions/RequestConflictUtils.ts +++ b/src/libs/actions/RequestConflictUtils.ts @@ -40,7 +40,7 @@ const enablePolicyFeatureCommand = [ type EnablePolicyFeatureCommand = TupleToUnion; function createUpdateCommentMatcher(reportActionID: string) { - return function (request: OnyxRequest) { + return function (request: OnyxRequest) { return request.command === WRITE_COMMANDS.UPDATE_COMMENT && request.data?.reportActionID === reportActionID; }; } @@ -52,7 +52,7 @@ function createUpdateCommentMatcher(reportActionID: string) { * - If no match is found, it suggests adding the request to the list, indicating a 'push' action. * - If a match is found, it suggests updating the existing entry, indicating a 'replace' action at the found index. */ -function resolveDuplicationConflictAction(persistedRequests: OnyxRequest[], requestMatcher: RequestMatcher): ConflictActionData { +function resolveDuplicationConflictAction(persistedRequests: Array>, requestMatcher: RequestMatcher): ConflictActionData { const index = persistedRequests.findIndex(requestMatcher); if (index === -1) { return { @@ -70,7 +70,7 @@ function resolveDuplicationConflictAction(persistedRequests: OnyxRequest[], requ }; } -function resolveOpenReportDuplicationConflictAction(persistedRequests: OnyxRequest[], parameters: OpenReportParams): ConflictActionData { +function resolveOpenReportDuplicationConflictAction(persistedRequests: Array>, parameters: OpenReportParams): ConflictActionData { for (let index = 0; index < persistedRequests.length; index++) { const request = persistedRequests.at(index); if (request && request.command === WRITE_COMMANDS.OPEN_REPORT && request.data?.reportID === parameters.reportID && request.data?.emailList === parameters.emailList) { @@ -101,7 +101,7 @@ function resolveOpenReportDuplicationConflictAction(persistedRequests: OnyxReque }; } -function resolveCommentDeletionConflicts(persistedRequests: OnyxRequest[], reportActionID: string, originalReportID: string): ConflictActionData { +function resolveCommentDeletionConflicts(persistedRequests: Array>, reportActionID: string, originalReportID: string): ConflictActionData { const commentIndicesToDelete: number[] = []; const commentCouldBeThread: Record = {}; let addCommentFound = false; @@ -159,7 +159,12 @@ function resolveCommentDeletionConflicts(persistedRequests: OnyxRequest[], repor }; } -function resolveEditCommentWithNewAddCommentRequest(persistedRequests: OnyxRequest[], parameters: UpdateCommentParams, reportActionID: string, addCommentIndex: number): ConflictActionData { +function resolveEditCommentWithNewAddCommentRequest( + persistedRequests: Array>, + parameters: UpdateCommentParams, + reportActionID: string, + addCommentIndex: number, +): ConflictActionData { const indicesToDelete: number[] = []; for (const [index, request] of persistedRequests.entries()) { if (request.command !== WRITE_COMMANDS.UPDATE_COMMENT || request.data?.reportActionID !== reportActionID) { @@ -181,7 +186,7 @@ function resolveEditCommentWithNewAddCommentRequest(persistedRequests: OnyxReque if (indicesToDelete.length === 0) { return { conflictAction: nextAction, - } as ConflictActionData; + } as ConflictActionData; } } @@ -192,14 +197,14 @@ function resolveEditCommentWithNewAddCommentRequest(persistedRequests: OnyxReque pushNewRequest: false, nextAction, }, - } as ConflictActionData; + } as ConflictActionData; } -function resolveEnableFeatureConflicts( +function resolveEnableFeatureConflicts( command: EnablePolicyFeatureCommand, - persistedRequests: OnyxRequest[], + persistedRequests: Array>, parameters: ApiRequestCommandParameters[EnablePolicyFeatureCommand], -): ConflictActionData { +): ConflictActionData { const deleteRequestIndex = persistedRequests.findIndex( (request) => request.command === command && request.data?.policyID === parameters.policyID && request.data?.enabled !== parameters.enabled, ); diff --git a/src/libs/tryResolveUrlFromApiRoot.ts b/src/libs/tryResolveUrlFromApiRoot.ts index 7656677c99f8..367fd7dd5708 100644 --- a/src/libs/tryResolveUrlFromApiRoot.ts +++ b/src/libs/tryResolveUrlFromApiRoot.ts @@ -29,7 +29,7 @@ function tryResolveUrlFromApiRoot(url: string | ImageSourcePropType | ReceiptSou if (typeof url !== 'string') { return url; } - const apiRoot = getApiRoot({shouldUseSecure: false} as Request); + const apiRoot = getApiRoot({shouldUseSecure: false} as Request); return url.replace(ORIGIN_PATTERN, apiRoot); } diff --git a/src/types/onyx/OnyxUpdatesFromServer.ts b/src/types/onyx/OnyxUpdatesFromServer.ts index ef82ab013e39..a0a83b51a26a 100644 --- a/src/types/onyx/OnyxUpdatesFromServer.ts +++ b/src/types/onyx/OnyxUpdatesFromServer.ts @@ -1,10 +1,10 @@ -import type {OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import CONST from '@src/CONST'; import type Request from './Request'; import type Response from './Response'; /** Model of a onyx server update */ -type OnyxServerUpdate = OnyxUpdate & { +type OnyxServerUpdate = OnyxUpdate & { /** Whether the update should notify UI */ shouldNotify?: boolean; @@ -13,16 +13,16 @@ type OnyxServerUpdate = OnyxUpdate & { }; /** Model of a onyx update event */ -type OnyxUpdateEvent = { +type OnyxUpdateEvent = { /** Type of the update event received from the server */ eventType: string; /** Collections of data updates */ - data: OnyxServerUpdate[]; + data: Array>; }; /** Model of onyx server updates */ -type OnyxUpdatesFromServer = { +type OnyxUpdatesFromServer = { /** Delivery method of onyx updates */ type: 'https' | 'pusher' | 'airship'; @@ -36,13 +36,13 @@ type OnyxUpdatesFromServer = { shouldFetchPendingUpdates?: boolean; /** Request data sent to the server */ - request?: Request; + request?: Request; /** Response data from server */ - response?: Response; + response?: Response; /** Collection of onyx updates */ - updates?: OnyxUpdateEvent[]; + updates?: Array>; }; /** diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 058930c5f9e6..ad07c7c1b7d3 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -74,12 +74,12 @@ type RequestData = { /** * Represents the possible actions to take in case of a conflict in the request queue. */ -type ConflictData = ConflictRequestReplace | ConflictRequestDelete | ConflictRequestPush | ConflictRequestNoAction; +type ConflictData = ConflictRequestReplace | ConflictRequestDelete | ConflictRequestPush | ConflictRequestNoAction; /** * Model of a conflict request that has to be replaced in the request queue. */ -type ConflictRequestReplace = { +type ConflictRequestReplace = { /** * The action to take in case of a conflict. */ @@ -93,13 +93,13 @@ type ConflictRequestReplace = { /** * The new request to replace the existing request in the queue. */ - request?: Request; + request?: Request; }; /** * Model of a conflict request that needs to be deleted from the request queue. */ -type ConflictRequestDelete = { +type ConflictRequestDelete = { /** * The action to take in case of a conflict. */ @@ -118,7 +118,7 @@ type ConflictRequestDelete = { /** * The next action to execute after the current conflict is resolved. */ - nextAction?: ConflictData; + nextAction?: ConflictData; }; /** @@ -144,11 +144,11 @@ type ConflictRequestNoAction = { /** * An object that has the request and the action to take in case of a conflict. */ -type ConflictActionData = { +type ConflictActionData = { /** * The action to take in case of a conflict. */ - conflictAction: ConflictData; + conflictAction: ConflictData; }; /** @@ -159,7 +159,7 @@ type RequestConflictResolver = { /** * A function that checks if a new request conflicts with any existing requests in the queue. */ - checkAndFixConflictingRequest?: (persistedRequest: Request[]) => ConflictActionData; + checkAndFixConflictingRequest?: (persistedRequest: Array>) => ConflictActionData; /** * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads diff --git a/src/types/onyx/Response.ts b/src/types/onyx/Response.ts index ab93314762b2..e00e589a03c7 100644 --- a/src/types/onyx/Response.ts +++ b/src/types/onyx/Response.ts @@ -10,7 +10,7 @@ type Data = { }; /** Model of server response */ -type Response = { +type Response = { /** ID of the next update that needs to be fetched from the server */ previousUpdateID?: number | string; @@ -21,7 +21,7 @@ type Response = { jsonCode?: number | string; /** Collection of onyx updates (SET/MERGE/...) */ - onyxData?: Array>; + onyxData?: Array>; /** ID of the request that triggered this response */ requestID?: string; diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 690c2977ec05..de533292a1c1 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -1316,7 +1316,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], [fileA, fileB, fileC], 'Hello world', CONST.DEFAULT_TIME_ZONE, shouldPlaySound); - const relevant = (await relevantPromise) as OnyxTypes.Request[]; + const relevant = (await relevantPromise) as Array>; expect(playSoundMock).toHaveBeenCalledTimes(1); expect(playSoundMock).toHaveBeenCalledWith(SOUNDS.DONE); @@ -1350,7 +1350,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], [fileA, fileB], undefined, CONST.DEFAULT_TIME_ZONE, shouldPlaySound); - const relevant = (await relevantPromise) as OnyxTypes.Request[]; + const relevant = (await relevantPromise) as Array>; expect(playSoundMock).toHaveBeenCalledTimes(1); expect(playSoundMock).toHaveBeenCalledWith(SOUNDS.DONE); @@ -1383,7 +1383,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], file); - const relevant = (await relevantPromise) as OnyxTypes.Request[]; + const relevant = (await relevantPromise) as Array>; expect(playSoundMock).toHaveBeenCalledTimes(0); expect(relevant.at(0)?.command).toBe(WRITE_COMMANDS.ADD_ATTACHMENT); diff --git a/tests/unit/AvatarUtilsTest.ts b/tests/unit/AvatarUtilsTest.ts index 56c209052d66..28fed313bf2e 100644 --- a/tests/unit/AvatarUtilsTest.ts +++ b/tests/unit/AvatarUtilsTest.ts @@ -357,7 +357,7 @@ describe('AvatarUtils', () => { const encodedImageFileName = encodeURIComponent(imageFileName); const absoluteEncodedImageFilePath = `/${encodedImageFileName}`; - const apiRoot = getApiRoot({shouldUseSecure: false} as Request); + const apiRoot = getApiRoot({shouldUseSecure: false} as Request); const prodImageFileUrl = `${apiRoot}${imageFileName}`; const encodedProdImageFileUrl = `${apiRoot}${encodedImageFileName}`; diff --git a/tests/unit/RequestTest.ts b/tests/unit/RequestTest.ts index 632d90a07bb6..4968b0309bb8 100644 --- a/tests/unit/RequestTest.ts +++ b/tests/unit/RequestTest.ts @@ -12,7 +12,7 @@ beforeEach(() => { Request.clearMiddlewares(); }); -const request: OnyxTypes.Request = { +const request: OnyxTypes.Request = { command: 'MockCommand', data: {authToken: 'testToken'}, }; diff --git a/tests/unit/SequentialQueueTest.ts b/tests/unit/SequentialQueueTest.ts index 3d2d259ba8fa..28a1e5ebeeae 100644 --- a/tests/unit/SequentialQueueTest.ts +++ b/tests/unit/SequentialQueueTest.ts @@ -1,5 +1,5 @@ import Onyx from 'react-native-onyx'; -import type {OnyxSetInput, OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey, OnyxSetInput, OnyxUpdate} from 'react-native-onyx'; import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import {getAll, getLength, getOngoingRequest} from '@userActions/PersistedRequests'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -37,7 +37,7 @@ describe('SequentialQueue', () => { it('should push two requests with conflict resolution and replace', () => { SequentialQueue.push(request); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: (persistedRequests) => { @@ -62,7 +62,7 @@ describe('SequentialQueue', () => { it('should push two requests with conflict resolution and push', () => { SequentialQueue.push(request); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: () => { @@ -75,7 +75,7 @@ describe('SequentialQueue', () => { it('should push two requests with conflict resolution and noAction', () => { SequentialQueue.push(request); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: () => { @@ -93,7 +93,7 @@ describe('SequentialQueue', () => { // wait for Onyx.connect execute the callback and start processing the queue await Promise.resolve(); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: (persistedRequests) => { @@ -120,7 +120,7 @@ describe('SequentialQueue', () => { // wait for Onyx.connect execute the callback and start processing the queue await Promise.resolve(); - const conflictResolver = (persistedRequests: Request[]): ConflictActionData => { + const conflictResolver = (persistedRequests: Array>): ConflictActionData => { // should be one instance of ReconnectApp, get the index to replace it later const index = persistedRequests.findIndex((r) => r.command === 'ReconnectApp'); if (index === -1) { @@ -132,13 +132,13 @@ describe('SequentialQueue', () => { }; }; - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: conflictResolver, }; - const requestWithConflictResolution2: Request = { + const requestWithConflictResolution2: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: conflictResolver, @@ -154,7 +154,7 @@ describe('SequentialQueue', () => { SequentialQueue.push({command: 'OpenReport'}); SequentialQueue.push(request); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: (persistedRequests) => { @@ -193,7 +193,7 @@ describe('SequentialQueue', () => { SequentialQueue.push({command: 'OpenReport6'}); // wait for Onyx.connect execute the callback and start processing the queue await Promise.resolve(); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp-replaced', data: {accountID: 56789}, checkAndFixConflictingRequest: (persistedRequests) => { From 3fee43a372b0a25cbe3dc13c5ed1a39f4c22c6d5 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Fri, 23 Jan 2026 11:27:59 +0100 Subject: [PATCH 16/34] Reduce generic type nesting with general type alternatives in Request.ts --- src/libs/ApiUtils.ts | 5 +- src/libs/Network/MainQueue.ts | 7 +- src/libs/Network/SequentialQueue.ts | 2 +- src/libs/Network/index.ts | 2 +- src/libs/actions/PersistedRequests.ts | 23 +-- src/libs/actions/QueuedOnyxUpdates.ts | 2 +- src/libs/actions/RequestConflictUtils.ts | 18 +- .../getCompanyCardBankConnection/index.tsx | 2 - src/libs/tryResolveUrlFromApiRoot.ts | 3 +- src/types/onyx/Request.ts | 171 ++++++++++++++++-- tests/unit/AvatarUtilsTest.ts | 3 +- tests/unit/SequentialQueueTest.ts | 24 +-- 12 files changed, 205 insertions(+), 57 deletions(-) diff --git a/src/libs/ApiUtils.ts b/src/libs/ApiUtils.ts index d0d917ffbf3c..11c9fd5ca936 100644 --- a/src/libs/ApiUtils.ts +++ b/src/libs/ApiUtils.ts @@ -1,4 +1,5 @@ -import Onyx, {OnyxKey} from 'react-native-onyx'; +import type {OnyxKey} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; @@ -36,7 +37,7 @@ getEnvironment().then((envName) => { * Get the currently used API endpoint, unless forceProduction is set to true * (Non-production environments allow for dynamically switching the API) */ -function getApiRoot(request?: Request, forceProduction = false): string { +function getApiRoot(request?: Partial, 'shouldUseSecure' | 'shouldSkipWebProxy' | 'command'>>, forceProduction = false): string { const shouldUseSecure = request?.shouldUseSecure ?? false; if (shouldUseStagingServer && forceProduction !== true) { diff --git a/src/libs/Network/MainQueue.ts b/src/libs/Network/MainQueue.ts index 69163ef6a5c4..d1cf83f5df7e 100644 --- a/src/libs/Network/MainQueue.ts +++ b/src/libs/Network/MainQueue.ts @@ -1,11 +1,12 @@ +import type {OnyxKey} from 'react-native-onyx'; import {processWithMiddleware} from '@libs/Request'; import type OnyxRequest from '@src/types/onyx/Request'; -import type { OnyxKey } from 'react-native-onyx'; +import type {GenericRequest} from '@src/types/onyx/Request'; import {isAuthenticating, isOffline} from './NetworkStore'; import {isRunning as sequentialQueueIsRunning} from './SequentialQueue'; // Queue for network requests so we don't lose actions done by the user while offline -let networkRequestQueue: Array> = []; +let networkRequestQueue: GenericRequest[] = []; /** * Checks to see if a request can be made. @@ -44,7 +45,7 @@ function process() { // - we are in the process of authenticating and the request is retryable (most are) // - the request does not have forceNetworkRequest === true (this will trigger it to process immediately) // - the request does not have shouldRetry === false (specified when we do not want to retry, defaults to true) - const requestsToProcessOnNextRun: Array> = []; + const requestsToProcessOnNextRun: GenericRequest[] = []; for (const queuedRequest of networkRequestQueue) { // Check if we can make this request at all and if we can't see if we should save it for the next run or chuck it into the ether diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index f22b222174b9..1570b74f2356 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -322,7 +322,7 @@ onReconnection(flush); // Flush the queue when the persisted requests are initialized onPersistedRequestsInitialization(flush); -function handleConflictActions(conflictAction: ConflictData, newRequest: OnyxRequest) { +function handleConflictActions(conflictAction: ConflictData, newRequest: OnyxRequest) { if (conflictAction.type === 'push') { savePersistedRequest(newRequest); } else if (conflictAction.type === 'replace') { diff --git a/src/libs/Network/index.ts b/src/libs/Network/index.ts index edca026b645d..e9240e6e4e93 100644 --- a/src/libs/Network/index.ts +++ b/src/libs/Network/index.ts @@ -33,7 +33,7 @@ function clearProcessQueueInterval() { */ function post(command: string, data: Record = {}, type = CONST.NETWORK.METHOD.POST, shouldUseSecure = false): Promise { return new Promise((resolve, reject) => { - const request: Request = { + const request: Request = { command, data, type, diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index 56b1ca040a54..05db97b70d39 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -4,10 +4,11 @@ import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Request} from '@src/types/onyx'; +import type {GenericRequest} from '@src/types/onyx/Request'; -let persistedRequests: Array> = []; -let ongoingRequest: Request | null = null; -let pendingSaveOperations: Array> = []; +let persistedRequests: GenericRequest[] = []; +let ongoingRequest: GenericRequest | null = null; +let pendingSaveOperations: GenericRequest[] = []; let isInitialized = false; let initializationCallback: () => void; function triggerInitializationCallback() { @@ -81,12 +82,12 @@ function save(requestToPersist: Request) { // If not initialized yet, queue the request for later processing if (!isInitialized) { Log.info('[PersistedRequests] Queueing request until initialization completes', false); - pendingSaveOperations.push(requestToPersist as Request); + pendingSaveOperations.push(requestToPersist as GenericRequest); return; } // If the command is not in the keepLastInstance array, add the new request as usual - const requests: Array> = [...persistedRequests, requestToPersist as Request]; + const requests: GenericRequest[] = [...persistedRequests, requestToPersist]; persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests) .then(() => { @@ -134,20 +135,20 @@ function deleteRequestsByIndices(indices: number[]) { } function update(oldRequestIndex: number, newRequest: Request) { - const requests: Array> = [...persistedRequests]; + const requests: GenericRequest[] = [...persistedRequests]; const oldRequest = requests.at(oldRequestIndex); Log.info('[PersistedRequests] Updating a request', false, {oldRequest, newRequest, oldRequestIndex}); - requests.splice(oldRequestIndex, 1, newRequest as Request); + requests.splice(oldRequestIndex, 1, newRequest as GenericRequest); persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); } function updateOngoingRequest(newRequest: Request) { Log.info('[PersistedRequests] Updating the ongoing request', false, {ongoingRequest, newRequest}); - ongoingRequest = newRequest as Request; + ongoingRequest = newRequest as GenericRequest; if (newRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest as Request); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest); } } @@ -187,11 +188,11 @@ function rollbackOngoingRequest() { ongoingRequest = null; } -function getAll(): Array> { +function getAll(): GenericRequest[] { return persistedRequests; } -function getOngoingRequest(): Request | null { +function getOngoingRequest(): GenericRequest | null { return ongoingRequest; } diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index 4c0f0ff38202..c01ad86edcdc 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -20,7 +20,7 @@ Onyx.connectWithoutView({ * @param updates Onyx updates to queue for later */ function queueOnyxUpdates(updates: Array>): Promise { - queuedOnyxUpdates = queuedOnyxUpdates.concat(updates); + queuedOnyxUpdates = queuedOnyxUpdates.concat(updates as Array>); return Promise.resolve(); } diff --git a/src/libs/actions/RequestConflictUtils.ts b/src/libs/actions/RequestConflictUtils.ts index a9f16b05aab8..aef6a34874c5 100644 --- a/src/libs/actions/RequestConflictUtils.ts +++ b/src/libs/actions/RequestConflictUtils.ts @@ -6,7 +6,7 @@ import {WRITE_COMMANDS} from '@libs/API/types'; import type {ApiRequestCommandParameters} from '@libs/API/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; -import type {ConflictActionData} from '@src/types/onyx/Request'; +import type {ConflictActionData, GenericRequest} from '@src/types/onyx/Request'; type RequestMatcher = (request: OnyxRequest) => boolean; @@ -40,7 +40,7 @@ const enablePolicyFeatureCommand = [ type EnablePolicyFeatureCommand = TupleToUnion; function createUpdateCommentMatcher(reportActionID: string) { - return function (request: OnyxRequest) { + return function (request: GenericRequest) { return request.command === WRITE_COMMANDS.UPDATE_COMMENT && request.data?.reportActionID === reportActionID; }; } @@ -52,7 +52,7 @@ function createUpdateCommentMatcher(reportActionID: string) { * - If no match is found, it suggests adding the request to the list, indicating a 'push' action. * - If a match is found, it suggests updating the existing entry, indicating a 'replace' action at the found index. */ -function resolveDuplicationConflictAction(persistedRequests: Array>, requestMatcher: RequestMatcher): ConflictActionData { +function resolveDuplicationConflictAction(persistedRequests: Array>, requestMatcher: RequestMatcher): ConflictActionData { const index = persistedRequests.findIndex(requestMatcher); if (index === -1) { return { @@ -70,7 +70,7 @@ function resolveDuplicationConflictAction(persistedRequest }; } -function resolveOpenReportDuplicationConflictAction(persistedRequests: Array>, parameters: OpenReportParams): ConflictActionData { +function resolveOpenReportDuplicationConflictAction(persistedRequests: Array>, parameters: OpenReportParams): ConflictActionData { for (let index = 0; index < persistedRequests.length; index++) { const request = persistedRequests.at(index); if (request && request.command === WRITE_COMMANDS.OPEN_REPORT && request.data?.reportID === parameters.reportID && request.data?.emailList === parameters.emailList) { @@ -101,7 +101,7 @@ function resolveOpenReportDuplicationConflictAction(persis }; } -function resolveCommentDeletionConflicts(persistedRequests: Array>, reportActionID: string, originalReportID: string): ConflictActionData { +function resolveCommentDeletionConflicts(persistedRequests: Array>, reportActionID: string, originalReportID: string): ConflictActionData { const commentIndicesToDelete: number[] = []; const commentCouldBeThread: Record = {}; let addCommentFound = false; @@ -164,7 +164,7 @@ function resolveEditCommentWithNewAddCommentRequest( parameters: UpdateCommentParams, reportActionID: string, addCommentIndex: number, -): ConflictActionData { +): ConflictActionData { const indicesToDelete: number[] = []; for (const [index, request] of persistedRequests.entries()) { if (request.command !== WRITE_COMMANDS.UPDATE_COMMENT || request.data?.reportActionID !== reportActionID) { @@ -186,7 +186,7 @@ function resolveEditCommentWithNewAddCommentRequest( if (indicesToDelete.length === 0) { return { conflictAction: nextAction, - } as ConflictActionData; + } as ConflictActionData; } } @@ -197,14 +197,14 @@ function resolveEditCommentWithNewAddCommentRequest( pushNewRequest: false, nextAction, }, - } as ConflictActionData; + } as ConflictActionData; } function resolveEnableFeatureConflicts( command: EnablePolicyFeatureCommand, persistedRequests: Array>, parameters: ApiRequestCommandParameters[EnablePolicyFeatureCommand], -): ConflictActionData { +): ConflictActionData { const deleteRequestIndex = persistedRequests.findIndex( (request) => request.command === command && request.data?.policyID === parameters.policyID && request.data?.enabled !== parameters.enabled, ); diff --git a/src/libs/actions/getCompanyCardBankConnection/index.tsx b/src/libs/actions/getCompanyCardBankConnection/index.tsx index b593774f7f0d..2c5c061dc5a9 100644 --- a/src/libs/actions/getCompanyCardBankConnection/index.tsx +++ b/src/libs/actions/getCompanyCardBankConnection/index.tsx @@ -44,7 +44,6 @@ function getCompanyCardBankConnection(policyID?: string, bankName?: string | nul const commandURL = getApiRoot( { shouldSkipWebProxy: true, - command: '', }, forceProductionAPI, ); @@ -68,7 +67,6 @@ function getCompanyCardPlaidConnection(policyID?: string, publicToken?: string, const commandURL = getApiRoot({ shouldSkipWebProxy: true, - command: '', }); return `${commandURL}partners/banks/plaid/oauth_callback.php?${new URLSearchParams(params).toString()}`; } diff --git a/src/libs/tryResolveUrlFromApiRoot.ts b/src/libs/tryResolveUrlFromApiRoot.ts index 367fd7dd5708..69609cc2e933 100644 --- a/src/libs/tryResolveUrlFromApiRoot.ts +++ b/src/libs/tryResolveUrlFromApiRoot.ts @@ -1,6 +1,5 @@ import type {ImageSourcePropType} from 'react-native'; import Config from '@src/CONFIG'; -import type {Request} from '@src/types/onyx'; import type {ReceiptSource} from '@src/types/onyx/Transaction'; import {getApiRoot} from './ApiUtils'; @@ -29,7 +28,7 @@ function tryResolveUrlFromApiRoot(url: string | ImageSourcePropType | ReceiptSou if (typeof url !== 'string') { return url; } - const apiRoot = getApiRoot({shouldUseSecure: false} as Request); + const apiRoot = getApiRoot({shouldUseSecure: false}); return url.replace(ORIGIN_PATTERN, apiRoot); } diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index ad07c7c1b7d3..b932bbd68dfd 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -1,6 +1,81 @@ -import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; +import type {CustomTypeOptions, OnyxKey, OnyxUpdate} from 'react-native-onyx'; +import type OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; +import type {Merge} from 'type-fest'; import type Response from './Response'; +/** + * Represents type options to configure all Onyx methods. + * It's a combination of predefined options with user-provided options (CustomTypeOptions). + * + * The user-defined options (CustomTypeOptions) are merged into these predefined options. + * In case of conflicting properties, the ones from CustomTypeOptions are prioritized. + */ +type TypeOptions = Merge< + { + /** Represents a string union of all Onyx normal keys. */ + keys: string; + /** Represents a string union of all Onyx collection keys. */ + collectionKeys: string; + /** Represents a Record where each key is an Onyx key and each value is its corresponding Onyx value type. */ + values: Record; + }, + CustomTypeOptions +>; + +/** + * + */ +type CollectionKeyBase = TypeOptions['collectionKeys']; + +/** + * + */ +type ExpandOnyxKeys = TKey extends CollectionKeyBase ? NoInfer<`${TKey}${string}`> : TKey; + +/** + * + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type GenericOnyxUpdate = { + /** + * + */ + onyxMethod: + | typeof OnyxUtils.METHOD.SET + | typeof OnyxUtils.METHOD.MULTI_SET + | typeof OnyxUtils.METHOD.MERGE + | typeof OnyxUtils.METHOD.CLEAR + | typeof OnyxUtils.METHOD.MERGE_COLLECTION + | typeof OnyxUtils.METHOD.SET_COLLECTION; + /** + * + */ + key: ExpandOnyxKeys; + /** + * + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value?: any; +}; + +/** Model of onyx requests sent to the API */ +type GenericOnyxData = { + /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ + successData?: GenericOnyxUpdate[]; + + /** Onyx instructions that are executed after getting response from server with jsonCode !== 200 */ + failureData?: GenericOnyxUpdate[]; + + /** Onyx instructions that are executed after getting any response from server */ + finallyData?: GenericOnyxUpdate[]; + + /** Onyx instructions that are executed before request is made to the server */ + optimisticData?: GenericOnyxUpdate[]; + + /** Onyx instructions that are executed when Onyx queue is flushed */ + queueFlushedData?: GenericOnyxUpdate[]; +}; + /** Model of onyx requests sent to the API */ type OnyxData = { /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ @@ -22,6 +97,55 @@ type OnyxData = { /** HTTP request method names */ type RequestType = 'get' | 'post'; +/** Model of overall requests sent to the API */ +type GenericRequestData = { + /** Name of the API command */ + command: string; + + /** Command name for logging purposes */ + commandName?: string; + + /** Additional parameters that can be sent with the request */ + data?: Record; + + /** The HTTP request method name */ + type?: RequestType; + + /** Whether the app should connect to the secure API endpoints */ + shouldUseSecure?: boolean; + + /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ + successData?: GenericOnyxUpdate[]; + + /** Onyx instructions that are executed after getting response from server with jsonCode !== 200 */ + failureData?: GenericOnyxUpdate[]; + + /** Onyx instructions that are executed after getting any response from server */ + finallyData?: GenericOnyxUpdate[]; + + /** Promise resolve handler */ + resolve?: (value: Response) => void; + + /** Promise reject handler */ + reject?: (value?: unknown) => void; + + /** Whether the app should skip the web proxy to connect to API endpoints */ + shouldSkipWebProxy?: boolean; + + /** + * Whether the request is initiated offline. + * + * This field is used to indicate if the app initiates the request while offline. + * It is particularly useful for scenarios such as receipts recreating, where + * the app needs to regenerate a blob once the user gets back online. + * More info https://github.com/Expensify/App/issues/51761 + */ + initiatedOffline?: boolean; + + /** The unique ID of the request */ + requestID?: number; +}; + /** Model of overall requests sent to the API */ type RequestData = { /** Name of the API command */ @@ -74,12 +198,12 @@ type RequestData = { /** * Represents the possible actions to take in case of a conflict in the request queue. */ -type ConflictData = ConflictRequestReplace | ConflictRequestDelete | ConflictRequestPush | ConflictRequestNoAction; +type ConflictData = ConflictRequestReplace | ConflictRequestDelete | ConflictRequestPush | ConflictRequestNoAction; /** * Model of a conflict request that has to be replaced in the request queue. */ -type ConflictRequestReplace = { +type ConflictRequestReplace = { /** * The action to take in case of a conflict. */ @@ -93,13 +217,13 @@ type ConflictRequestReplace = { /** * The new request to replace the existing request in the queue. */ - request?: Request; + request?: GenericRequest; }; /** * Model of a conflict request that needs to be deleted from the request queue. */ -type ConflictRequestDelete = { +type ConflictRequestDelete = { /** * The action to take in case of a conflict. */ @@ -118,7 +242,7 @@ type ConflictRequestDelete = { /** * The next action to execute after the current conflict is resolved. */ - nextAction?: ConflictData; + nextAction?: ConflictData; }; /** @@ -144,11 +268,33 @@ type ConflictRequestNoAction = { /** * An object that has the request and the action to take in case of a conflict. */ -type ConflictActionData = { +type ConflictActionData = { /** * The action to take in case of a conflict. */ - conflictAction: ConflictData; + conflictAction: ConflictData; +}; + +/** + * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, + * and how to resolve those conflicts. + */ +type GenericRequestConflictResolver = { + /** + * A function that checks if a new request conflicts with any existing requests in the queue. + */ + checkAndFixConflictingRequest?: (persistedRequest: GenericRequest[]) => ConflictActionData; + + /** + * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads + * the ongoing request, it will be removed from the persisted request queue. + */ + persistWhenOngoing?: boolean; + + /** + * A boolean flag to mark a request as rollback, if set to true it means the request failed and was added back into the queue. + */ + isRollback?: boolean; }; /** @@ -159,7 +305,7 @@ type RequestConflictResolver = { /** * A function that checks if a new request conflicts with any existing requests in the queue. */ - checkAndFixConflictingRequest?: (persistedRequest: Array>) => ConflictActionData; + checkAndFixConflictingRequest?: (persistedRequest: Array>) => ConflictActionData; /** * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads @@ -174,7 +320,10 @@ type RequestConflictResolver = { }; /** Model of requests sent to the API */ -type Request = RequestData & OnyxData & RequestConflictResolver; +type GenericRequest = GenericRequestData & GenericOnyxData & GenericRequestConflictResolver; + +/** Model of requests sent to the API */ +type Request = RequestData & OnyxData & GenericRequestConflictResolver; /** * An object used to describe how a request can be paginated. @@ -203,4 +352,4 @@ type PaginatedRequest = Request & }; export default Request; -export type {OnyxData, RequestType, PaginationConfig, PaginatedRequest, RequestConflictResolver, ConflictActionData, ConflictData}; +export type {OnyxData, RequestType, PaginationConfig, PaginatedRequest, RequestConflictResolver, ConflictActionData, ConflictData, GenericRequest}; diff --git a/tests/unit/AvatarUtilsTest.ts b/tests/unit/AvatarUtilsTest.ts index 28fed313bf2e..0596fe7c6f41 100644 --- a/tests/unit/AvatarUtilsTest.ts +++ b/tests/unit/AvatarUtilsTest.ts @@ -3,7 +3,6 @@ import CONST from '@src/CONST'; import {getValidatedImageSource, isValidExtension, isValidResolution, isValidSize, validateAvatarImage} from '@src/libs/AvatarUtils'; import * as FileUtils from '@src/libs/fileDownload/FileUtils'; import * as getImageResolution from '@src/libs/fileDownload/getImageResolution'; -import type {Request} from '@src/types/onyx'; import type {FileObject} from '@src/types/utils/Attachment'; jest.mock('@src/libs/fileDownload/FileUtils'); @@ -357,7 +356,7 @@ describe('AvatarUtils', () => { const encodedImageFileName = encodeURIComponent(imageFileName); const absoluteEncodedImageFilePath = `/${encodedImageFileName}`; - const apiRoot = getApiRoot({shouldUseSecure: false} as Request); + const apiRoot = getApiRoot({shouldUseSecure: false}); const prodImageFileUrl = `${apiRoot}${imageFileName}`; const encodedProdImageFileUrl = `${apiRoot}${encodedImageFileName}`; diff --git a/tests/unit/SequentialQueueTest.ts b/tests/unit/SequentialQueueTest.ts index 28a1e5ebeeae..5cc9e59c0d4d 100644 --- a/tests/unit/SequentialQueueTest.ts +++ b/tests/unit/SequentialQueueTest.ts @@ -1,5 +1,5 @@ import Onyx from 'react-native-onyx'; -import type {OnyxKey, OnyxSetInput, OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import {waitForActiveRequestsToBeEmpty} from '@libs/E2E/utils/NetworkInterceptor'; import {getAll, getLength, getOngoingRequest} from '@userActions/PersistedRequests'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -37,7 +37,7 @@ describe('SequentialQueue', () => { it('should push two requests with conflict resolution and replace', () => { SequentialQueue.push(request); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: (persistedRequests) => { @@ -62,7 +62,7 @@ describe('SequentialQueue', () => { it('should push two requests with conflict resolution and push', () => { SequentialQueue.push(request); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: () => { @@ -75,7 +75,7 @@ describe('SequentialQueue', () => { it('should push two requests with conflict resolution and noAction', () => { SequentialQueue.push(request); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: () => { @@ -93,7 +93,7 @@ describe('SequentialQueue', () => { // wait for Onyx.connect execute the callback and start processing the queue await Promise.resolve(); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: (persistedRequests) => { @@ -120,7 +120,7 @@ describe('SequentialQueue', () => { // wait for Onyx.connect execute the callback and start processing the queue await Promise.resolve(); - const conflictResolver = (persistedRequests: Array>): ConflictActionData => { + const conflictResolver = (persistedRequests: Array>): ConflictActionData => { // should be one instance of ReconnectApp, get the index to replace it later const index = persistedRequests.findIndex((r) => r.command === 'ReconnectApp'); if (index === -1) { @@ -132,13 +132,13 @@ describe('SequentialQueue', () => { }; }; - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: conflictResolver, }; - const requestWithConflictResolution2: Request = { + const requestWithConflictResolution2: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: conflictResolver, @@ -154,7 +154,7 @@ describe('SequentialQueue', () => { SequentialQueue.push({command: 'OpenReport'}); SequentialQueue.push(request); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp', data: {accountID: 56789}, checkAndFixConflictingRequest: (persistedRequests) => { @@ -193,7 +193,7 @@ describe('SequentialQueue', () => { SequentialQueue.push({command: 'OpenReport6'}); // wait for Onyx.connect execute the callback and start processing the queue await Promise.resolve(); - const requestWithConflictResolution: Request = { + const requestWithConflictResolution: Request = { command: 'ReconnectApp-replaced', data: {accountID: 56789}, checkAndFixConflictingRequest: (persistedRequests) => { @@ -247,8 +247,8 @@ describe('SequentialQueue', () => { }); it('should get the ongoing request from onyx and start processing it', async () => { - const persistedRequest = {...request, persistWhenOngoing: true, initiatedOffline: false}; - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, persistedRequest as OnyxSetInput); + const persistedRequest = {...request, persistWhenOngoing: true, initiatedOffline: false} as Request<'userMetadata'>; + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, [persistedRequest]); SequentialQueue.push({command: 'OpenReport'}); await Promise.resolve(); From 05b9a6ac5724f366cb3b50d444ddf4830317caa7 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 26 Jan 2026 12:04:42 +0100 Subject: [PATCH 17/34] Improve generic types usage --- src/ONYXKEYS.ts | 4 ++-- src/libs/API/index.ts | 4 ++-- src/libs/Network/SequentialQueue.ts | 6 ++---- src/libs/actions/PersistedRequests.ts | 12 ++++++------ src/types/onyx/Request.ts | 8 ++++---- src/types/onyx/index.ts | 2 ++ tests/actions/ReportTest.ts | 6 +++--- tests/unit/SequentialQueueTest.ts | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 570e8b7f2462..264df12108c2 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1183,8 +1183,8 @@ type OnyxValuesMapping = { [ONYXKEYS.DEVICE_ID]: string; [ONYXKEYS.ACTIVATED_CARD_PIN]: string | undefined; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; - [ONYXKEYS.PERSISTED_REQUESTS]: Array>; - [ONYXKEYS.PERSISTED_ONGOING_REQUESTS]: Array>; + [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.GenericRequest[]; + [ONYXKEYS.PERSISTED_ONGOING_REQUESTS]: OnyxTypes.GenericRequest[]; [ONYXKEYS.CURRENT_DATE]: string; [ONYXKEYS.CREDENTIALS]: OnyxTypes.Credentials; [ONYXKEYS.STASHED_CREDENTIALS]: OnyxTypes.Credentials; diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index 811de6f70d43..77f3bf9adba4 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -13,7 +13,7 @@ import {addMiddleware, processWithMiddleware} from '@libs/Request'; import {getAll, getLength as getPersistedRequestsLength} from '@userActions/PersistedRequests'; import CONST from '@src/CONST'; import type OnyxRequest from '@src/types/onyx/Request'; -import type {OnyxData, PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request'; +import type {GenericRequestConflictResolver, OnyxData, PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import type {ApiCommand, ApiRequestCommandParameters, ApiRequestType, CommandOfType, ReadCommand, SideEffectRequestCommand, WriteCommand} from './types'; import {READ_COMMANDS} from './types'; @@ -59,7 +59,7 @@ function prepareRequest( type: ApiRequestType, params: ApiRequestCommandParameters[TCommand], onyxData: OnyxData = {}, - conflictResolver: RequestConflictResolver = {}, + conflictResolver: GenericRequestConflictResolver = {}, ): OnyxRequest { Log.info('[API] Preparing request', false, {command, type}); diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index 1570b74f2356..3c23d5416036 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -341,11 +341,9 @@ function handleConflictActions(conflictAction: ConflictDat } function push(newRequest: OnyxRequest) { - const {checkAndFixConflictingRequest} = newRequest; - - if (checkAndFixConflictingRequest) { + if (newRequest.checkAndFixConflictingRequest) { const requests = getAllPersistedRequests(); - const {conflictAction} = checkAndFixConflictingRequest(requests); + const {conflictAction} = newRequest.checkAndFixConflictingRequest(requests as Array>); Log.info(`[SequentialQueue] Conflict action for command ${newRequest.command} - ${conflictAction.type}:`); // don't try to serialize a function. diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index 05db97b70d39..e0e7df8de403 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -59,7 +59,7 @@ Onyx.connectWithoutView({ Onyx.connectWithoutView({ key: ONYXKEYS.PERSISTED_ONGOING_REQUESTS, callback: (val) => { - ongoingRequest = val ?? null; + ongoingRequest = val?.at(0) ?? null; }, }); @@ -145,14 +145,14 @@ function update(oldRequestIndex: number, newRequest: Reque function updateOngoingRequest(newRequest: Request) { Log.info('[PersistedRequests] Updating the ongoing request', false, {ongoingRequest, newRequest}); - ongoingRequest = newRequest as GenericRequest; + ongoingRequest = newRequest; if (newRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, [newRequest]); } } -function processNextRequest(): Request | null { +function processNextRequest(): GenericRequest | null { if (ongoingRequest) { Log.info(`Ongoing Request already set returning same one ${ongoingRequest.commandName}`); return ongoingRequest; @@ -163,14 +163,14 @@ function processNextRequest(): Request | null { throw new Error('No requests to process'); } - ongoingRequest = persistedRequests.length > 0 ? (persistedRequests.at(0) ?? null) : null; + ongoingRequest = persistedRequests?.at(0) ?? null; // Create a new array without the first element const newPersistedRequests = persistedRequests.slice(1); persistedRequests = newPersistedRequests; if (ongoingRequest && ongoingRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, ongoingRequest); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, [ongoingRequest]); } return ongoingRequest; diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index b932bbd68dfd..e1fa78a24516 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -283,7 +283,7 @@ type GenericRequestConflictResolver = { /** * A function that checks if a new request conflicts with any existing requests in the queue. */ - checkAndFixConflictingRequest?: (persistedRequest: GenericRequest[]) => ConflictActionData; + checkAndFixConflictingRequest?(persistedRequest: GenericRequest[]): ConflictActionData; /** * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads @@ -305,7 +305,7 @@ type RequestConflictResolver = { /** * A function that checks if a new request conflicts with any existing requests in the queue. */ - checkAndFixConflictingRequest?: (persistedRequest: Array>) => ConflictActionData; + checkAndFixConflictingRequest?(persistedRequest: Array>): ConflictActionData; /** * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads @@ -323,7 +323,7 @@ type RequestConflictResolver = { type GenericRequest = GenericRequestData & GenericOnyxData & GenericRequestConflictResolver; /** Model of requests sent to the API */ -type Request = RequestData & OnyxData & GenericRequestConflictResolver; +type Request = RequestData & OnyxData & RequestConflictResolver; /** * An object used to describe how a request can be paginated. @@ -352,4 +352,4 @@ type PaginatedRequest = Request & }; export default Request; -export type {OnyxData, RequestType, PaginationConfig, PaginatedRequest, RequestConflictResolver, ConflictActionData, ConflictData, GenericRequest}; +export type {OnyxData, RequestType, PaginationConfig, PaginatedRequest, RequestConflictResolver, GenericRequestConflictResolver, ConflictActionData, ConflictData, GenericRequest}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 81e89f0df9e3..8604711ca939 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -107,6 +107,7 @@ import type ReportUserIsTyping from './ReportUserIsTyping'; import type {ReportFieldsViolations, ReportViolationName} from './ReportViolation'; import type ReportViolations from './ReportViolation'; import type Request from './Request'; +import type {GenericRequest} from './Request'; import type Response from './Response'; import type ReviewDuplicates from './ReviewDuplicates'; import type {SaveSearch} from './SaveSearch'; @@ -236,6 +237,7 @@ export type { ReportFieldsViolations, ReportLayoutGroupBy, GroupedTransactions, + GenericRequest, Request, Response, ScreenShareRequest, diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index de533292a1c1..cad8c1cf0771 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -1316,7 +1316,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], [fileA, fileB, fileC], 'Hello world', CONST.DEFAULT_TIME_ZONE, shouldPlaySound); - const relevant = (await relevantPromise) as Array>; + const relevant = (await relevantPromise) as OnyxTypes.GenericRequest[]; expect(playSoundMock).toHaveBeenCalledTimes(1); expect(playSoundMock).toHaveBeenCalledWith(SOUNDS.DONE); @@ -1350,7 +1350,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], [fileA, fileB], undefined, CONST.DEFAULT_TIME_ZONE, shouldPlaySound); - const relevant = (await relevantPromise) as Array>; + const relevant = (await relevantPromise) as OnyxTypes.GenericRequest[]; expect(playSoundMock).toHaveBeenCalledTimes(1); expect(playSoundMock).toHaveBeenCalledWith(SOUNDS.DONE); @@ -1383,7 +1383,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], file); - const relevant = (await relevantPromise) as Array>; + const relevant = (await relevantPromise) as OnyxTypes.GenericRequest[]; expect(playSoundMock).toHaveBeenCalledTimes(0); expect(relevant.at(0)?.command).toBe(WRITE_COMMANDS.ADD_ATTACHMENT); diff --git a/tests/unit/SequentialQueueTest.ts b/tests/unit/SequentialQueueTest.ts index 5cc9e59c0d4d..2992a36b2725 100644 --- a/tests/unit/SequentialQueueTest.ts +++ b/tests/unit/SequentialQueueTest.ts @@ -247,7 +247,7 @@ describe('SequentialQueue', () => { }); it('should get the ongoing request from onyx and start processing it', async () => { - const persistedRequest = {...request, persistWhenOngoing: true, initiatedOffline: false} as Request<'userMetadata'>; + const persistedRequest = {...request, persistWhenOngoing: true, initiatedOffline: false}; Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, [persistedRequest]); SequentialQueue.push({command: 'OpenReport'}); From 31d60beaf4b2351038b29de179e64b996aed8c16 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 26 Jan 2026 12:28:09 +0100 Subject: [PATCH 18/34] Improve generic types usage --- src/ONYXKEYS.ts | 2 +- src/hooks/useLoadingBarVisibility.ts | 2 +- src/libs/actions/PersistedRequests.ts | 12 ++++++------ src/libs/actions/Policy/Policy.ts | 3 +-- src/libs/actions/QueuedOnyxUpdates.ts | 5 +++-- src/types/onyx/Request.ts | 13 ++++++++++++- tests/unit/SequentialQueueTest.ts | 2 +- 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 264df12108c2..ab084dd24b96 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1184,7 +1184,7 @@ type OnyxValuesMapping = { [ONYXKEYS.ACTIVATED_CARD_PIN]: string | undefined; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.GenericRequest[]; - [ONYXKEYS.PERSISTED_ONGOING_REQUESTS]: OnyxTypes.GenericRequest[]; + [ONYXKEYS.PERSISTED_ONGOING_REQUESTS]: OnyxTypes.GenericRequest; [ONYXKEYS.CURRENT_DATE]: string; [ONYXKEYS.CREDENTIALS]: OnyxTypes.Credentials; [ONYXKEYS.STASHED_CREDENTIALS]: OnyxTypes.Credentials; diff --git a/src/hooks/useLoadingBarVisibility.ts b/src/hooks/useLoadingBarVisibility.ts index b9a32aa0442b..acf2692c9819 100644 --- a/src/hooks/useLoadingBarVisibility.ts +++ b/src/hooks/useLoadingBarVisibility.ts @@ -21,7 +21,7 @@ export default function useLoadingBarVisibility(): boolean { } const hasPersistedRequests = !!persistedRequests?.some((request) => RELEVANT_COMMANDS.has(request.command) && !request.initiatedOffline); - const hasOngoingRequests = !!ongoingRequests?.some((request) => RELEVANT_COMMANDS.has(request.command)); + const hasOngoingRequests = !!ongoingRequests && RELEVANT_COMMANDS.has(ongoingRequests?.command); return hasPersistedRequests || hasOngoingRequests; } diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index e0e7df8de403..9ab203c4210b 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -59,7 +59,7 @@ Onyx.connectWithoutView({ Onyx.connectWithoutView({ key: ONYXKEYS.PERSISTED_ONGOING_REQUESTS, callback: (val) => { - ongoingRequest = val?.at(0) ?? null; + ongoingRequest = val ?? null; }, }); @@ -87,7 +87,7 @@ function save(requestToPersist: Request) { } // If the command is not in the keepLastInstance array, add the new request as usual - const requests: GenericRequest[] = [...persistedRequests, requestToPersist]; + const requests = [...persistedRequests, requestToPersist]; persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests) .then(() => { @@ -135,10 +135,10 @@ function deleteRequestsByIndices(indices: number[]) { } function update(oldRequestIndex: number, newRequest: Request) { - const requests: GenericRequest[] = [...persistedRequests]; + const requests = [...persistedRequests]; const oldRequest = requests.at(oldRequestIndex); Log.info('[PersistedRequests] Updating a request', false, {oldRequest, newRequest, oldRequestIndex}); - requests.splice(oldRequestIndex, 1, newRequest as GenericRequest); + requests.splice(oldRequestIndex, 1, newRequest); persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); } @@ -148,7 +148,7 @@ function updateOngoingRequest(newRequest: Request) { ongoingRequest = newRequest; if (newRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, [newRequest]); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest); } } @@ -170,7 +170,7 @@ function processNextRequest(): GenericRequest | null { persistedRequests = newPersistedRequests; if (ongoingRequest && ongoingRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, [ongoingRequest]); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, ongoingRequest); } return ongoingRequest; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 67493b0b7140..91395e532ff7 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -114,7 +114,6 @@ import type { Report, ReportAction, ReportActions, - Request, TaxRatesWithDefault, Transaction, TransactionViolations, @@ -1725,7 +1724,7 @@ function updateGeneralSettings(policyID: string | undefined, name: string, curre const createWorkspaceRequest = persistedRequests.at(createWorkspaceRequestChangedIndex); if (createWorkspaceRequest && createWorkspaceRequestChangedIndex !== -1) { - const workspaceRequest: Request = { + const workspaceRequest = { ...createWorkspaceRequest, data: { ...createWorkspaceRequest.data, diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index c01ad86edcdc..e29965844b57 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -2,10 +2,11 @@ import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {GenericOnyxUpdate} from '@src/types/onyx/Request'; // In this file we manage a queue of Onyx updates while the SequentialQueue is processing. There are functions to get the updates and clear the queue after saving the updates in Onyx. -let queuedOnyxUpdates: Array> = []; +let queuedOnyxUpdates: GenericOnyxUpdate[] = []; let currentAccountID: number | undefined; // We use `connectWithoutView` because it is not connected to any UI component. @@ -20,7 +21,7 @@ Onyx.connectWithoutView({ * @param updates Onyx updates to queue for later */ function queueOnyxUpdates(updates: Array>): Promise { - queuedOnyxUpdates = queuedOnyxUpdates.concat(updates as Array>); + queuedOnyxUpdates = queuedOnyxUpdates.concat(updates); return Promise.resolve(); } diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index e1fa78a24516..2729111fb4c7 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -352,4 +352,15 @@ type PaginatedRequest = Request & }; export default Request; -export type {OnyxData, RequestType, PaginationConfig, PaginatedRequest, RequestConflictResolver, GenericRequestConflictResolver, ConflictActionData, ConflictData, GenericRequest}; +export type { + GenericOnyxUpdate, + OnyxData, + RequestType, + PaginationConfig, + PaginatedRequest, + RequestConflictResolver, + GenericRequestConflictResolver, + ConflictActionData, + ConflictData, + GenericRequest, +}; diff --git a/tests/unit/SequentialQueueTest.ts b/tests/unit/SequentialQueueTest.ts index 2992a36b2725..2b0fbd1a9bae 100644 --- a/tests/unit/SequentialQueueTest.ts +++ b/tests/unit/SequentialQueueTest.ts @@ -248,7 +248,7 @@ describe('SequentialQueue', () => { it('should get the ongoing request from onyx and start processing it', async () => { const persistedRequest = {...request, persistWhenOngoing: true, initiatedOffline: false}; - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, [persistedRequest]); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, persistedRequest); SequentialQueue.push({command: 'OpenReport'}); await Promise.resolve(); From f7bf8cab4c0b125f1ea304c3d59426c7a9d82453 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Mon, 26 Jan 2026 16:44:25 +0100 Subject: [PATCH 19/34] Fix eslint check errors and limit code assertions --- src/libs/AppState/RequestsQueuesState/types.ts | 4 ++-- src/libs/Middleware/SaveResponseInOnyx.ts | 12 ++++++++---- src/libs/Middleware/types.ts | 6 +++++- src/libs/PusherUtils.ts | 3 ++- src/libs/actions/OnyxUpdates.ts | 4 ++-- tests/unit/RequestTest.ts | 2 +- 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/libs/AppState/RequestsQueuesState/types.ts b/src/libs/AppState/RequestsQueuesState/types.ts index 5324a77646c0..b840a7d2a620 100644 --- a/src/libs/AppState/RequestsQueuesState/types.ts +++ b/src/libs/AppState/RequestsQueuesState/types.ts @@ -1,4 +1,4 @@ -import type {Request} from '@src/types/onyx'; +import type {GenericRequest} from '@src/types/onyx'; /** * Main queue state @@ -11,7 +11,7 @@ type MainQueueInfo = { queuedCommands?: string[]; }; -type OngoingRequestInfo = Pick, 'command' | 'persistWhenOngoing' | 'isRollback'>; +type OngoingRequestInfo = Pick; /** * Persisted requests state diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index cafa1e061c07..ab0b3ef2ddbd 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -1,6 +1,10 @@ +import type {OnyxKey} from 'react-native-onyx'; import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as OnyxUpdates from '@userActions/OnyxUpdates'; import CONST from '@src/CONST'; +import type {OnyxUpdatesFromServer} from '@src/types/onyx'; +import type OnyxRequest from '@src/types/onyx/Request'; +import type Response from '@src/types/onyx/Response'; import type Middleware from './types'; // If we're executing any of these requests, we don't need to trigger our OnyxUpdates flow to update the current data even if our current value is out of @@ -13,7 +17,7 @@ const requestsToIgnoreLastUpdateID = new Set([ SIDE_EFFECT_REQUEST_COMMANDS.GET_MISSING_ONYX_MESSAGES, ]); -const SaveResponseInOnyx: Middleware = (requestResponse, request) => +const SaveResponseInOnyx: Middleware = (requestResponse: Promise | void>, request: OnyxRequest) => requestResponse.then((response = {}) => { const onyxUpdates = response?.onyxData ?? []; @@ -23,7 +27,7 @@ const SaveResponseInOnyx: Middleware = (requestResponse, request) => return Promise.resolve(response); } - const responseToApply = { + const responseToApply: OnyxUpdatesFromServer = { type: CONST.ONYX_UPDATE_TYPES.HTTPS, lastUpdateID: Number(response?.lastUpdateID ?? CONST.DEFAULT_NUMBER_ID), previousUpdateID: Number(response?.previousUpdateID ?? CONST.DEFAULT_NUMBER_ID), @@ -32,11 +36,11 @@ const SaveResponseInOnyx: Middleware = (requestResponse, request) => }; if (requestsToIgnoreLastUpdateID.has(request.command) || !OnyxUpdates.doesClientNeedToBeUpdated({previousUpdateID: Number(response?.previousUpdateID ?? CONST.DEFAULT_NUMBER_ID)})) { - return OnyxUpdates.apply(responseToApply as any); + return OnyxUpdates.apply(responseToApply); } // Save the update IDs to Onyx so they can be used to fetch incremental updates if the client gets out of sync from the server - OnyxUpdates.saveUpdateInformation(responseToApply as any); + OnyxUpdates.saveUpdateInformation(responseToApply); // Ensure the queue is paused while the client resolves the gap in onyx updates so that updates are guaranteed to happen in a specific order. return Promise.resolve({ diff --git a/src/libs/Middleware/types.ts b/src/libs/Middleware/types.ts index fc969d712747..1bad7deb6a3d 100644 --- a/src/libs/Middleware/types.ts +++ b/src/libs/Middleware/types.ts @@ -3,6 +3,10 @@ import type Request from '@src/types/onyx/Request'; import type {PaginatedRequest} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; -type Middleware = (response: Promise, request: Request | PaginatedRequest, isFromSequentialQueue: boolean) => Promise; +type Middleware = ( + response: Promise | void>, + request: Request | PaginatedRequest, + isFromSequentialQueue: boolean, +) => Promise | void>; export default Middleware; diff --git a/src/libs/PusherUtils.ts b/src/libs/PusherUtils.ts index 14293b7b3b30..6a1dc9dfe554 100644 --- a/src/libs/PusherUtils.ts +++ b/src/libs/PusherUtils.ts @@ -1,4 +1,4 @@ -import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey} from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import type {OnyxUpdatesFromServer} from '@src/types/onyx'; @@ -11,6 +11,7 @@ import type {PingPongEvent} from './Pusher/types'; type Callback = (data: Array>) => Promise; // Keeps track of all the callbacks that need triggered for each event type +// eslint-disable-next-line @typescript-eslint/no-explicit-any const multiEventCallbackMapping: Record> = {}; function getUserChannelName(accountID: string) { diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 97fb23c21d09..e99ff3947ce0 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -193,7 +193,7 @@ function apply({lastUpdateID, type, request, response, upd * @param [updateParams.response] Exists if updateParams.type === 'https' * @param [updateParams.updates] Exists if updateParams.type === 'pusher' */ -function saveUpdateInformation(updateParams: OnyxUpdatesFromServer) { +function saveUpdateInformation(updateParams: OnyxUpdatesFromServer) { let modifiedUpdateParams = updateParams; // We don't want to store the data in the updateParams if it's a HTTPS update since it is useless anyways // and it causes serialization issues when storing in Onyx @@ -201,7 +201,7 @@ function saveUpdateInformation(updateParams: OnyxUpdatesFromServer) { modifiedUpdateParams = {...modifiedUpdateParams, request: {...updateParams.request, data: {apiRequestType: updateParams.request?.data?.apiRequestType}}}; } // Always use set() here so that the updateParams are never merged and always unique to the request that came in - Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, modifiedUpdateParams); + Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, modifiedUpdateParams as OnyxUpdatesFromServer); } type DoesClientNeedToBeUpdatedParams = { diff --git a/tests/unit/RequestTest.ts b/tests/unit/RequestTest.ts index 4968b0309bb8..6f4ee489fea0 100644 --- a/tests/unit/RequestTest.ts +++ b/tests/unit/RequestTest.ts @@ -12,7 +12,7 @@ beforeEach(() => { Request.clearMiddlewares(); }); -const request: OnyxTypes.Request = { +const request: OnyxTypes.GenericRequest = { command: 'MockCommand', data: {authToken: 'testToken'}, }; From 4797450570486c721287fe1955cb4f72777486c0 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Tue, 27 Jan 2026 18:01:35 +0100 Subject: [PATCH 20/34] Extract base type with shared generic logic --- src/libs/API/index.ts | 20 +-- src/libs/HttpUtils.ts | 20 ++- .../Middleware/HandleUnusedOptimisticID.ts | 3 +- src/libs/Middleware/Logging.ts | 4 +- src/libs/Middleware/Pagination.ts | 4 +- src/libs/Middleware/SupportalPermission.ts | 5 +- src/libs/Network/MainQueue.ts | 4 +- src/libs/Network/SequentialQueue.ts | 4 +- src/libs/Request.ts | 6 +- src/libs/actions/PersistedRequests.ts | 10 +- src/libs/actions/Report.ts | 3 +- src/types/onyx/Request.ts | 121 ++++-------------- tests/actions/SessionTest.ts | 2 +- tests/unit/SequentialQueueTest.ts | 4 +- 14 files changed, 76 insertions(+), 134 deletions(-) diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index 77f3bf9adba4..1669bd6d8d32 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -13,7 +13,7 @@ import {addMiddleware, processWithMiddleware} from '@libs/Request'; import {getAll, getLength as getPersistedRequestsLength} from '@userActions/PersistedRequests'; import CONST from '@src/CONST'; import type OnyxRequest from '@src/types/onyx/Request'; -import type {GenericRequestConflictResolver, OnyxData, PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request'; +import type {OnyxData, PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import type {ApiCommand, ApiRequestCommandParameters, ApiRequestType, CommandOfType, ReadCommand, SideEffectRequestCommand, WriteCommand} from './types'; import {READ_COMMANDS} from './types'; @@ -59,14 +59,14 @@ function prepareRequest( type: ApiRequestType, params: ApiRequestCommandParameters[TCommand], onyxData: OnyxData = {}, - conflictResolver: GenericRequestConflictResolver = {}, + conflictResolver: RequestConflictResolver = {}, ): OnyxRequest { Log.info('[API] Preparing request', false, {command, type}); let shouldApplyOptimisticData = true; if (conflictResolver?.checkAndFixConflictingRequest) { const requests = getAll(); - const {conflictAction} = conflictResolver.checkAndFixConflictingRequest(requests); + const {conflictAction} = conflictResolver.checkAndFixConflictingRequest(requests as Array>); shouldApplyOptimisticData = conflictAction.type !== 'noAction'; } @@ -118,7 +118,7 @@ function prepareRequest( /** * Process a prepared request according to its type. */ -function processRequest(request: OnyxRequest, type: ApiRequestType): Promise { +function processRequest(request: OnyxRequest, type: ApiRequestType): Promise> { Log.info('[API] Processing request', false, {command: request.command, type}); // Write commands can be saved and retried, so push it to the SequentialQueue if (type === CONST.API_REQUEST_TYPE.WRITE) { @@ -150,14 +150,14 @@ function write( apiCommandParameters: ApiRequestCommandParameters[TCommand], onyxData: OnyxData, conflictResolver?: RequestConflictResolver, -): Promise; +): Promise>; function write( command: TCommand, apiCommandParameters: ApiRequestCommandParameters[TCommand], onyxData: OnyxData = {}, conflictResolver: RequestConflictResolver = {}, -): Promise { +): Promise> { Log.info('[API] Called API write', false, {command, ...apiCommandParameters}); const request = prepareRequest(command, CONST.API_REQUEST_TYPE.WRITE, apiCommandParameters, onyxData, conflictResolver); return processRequest(request, CONST.API_REQUEST_TYPE.WRITE); @@ -172,7 +172,7 @@ function writeWithNoDuplicatesConflictAction = {}, requestMatcher: RequestMatcher = (request) => request.command === command, -): Promise { +): Promise> { const conflictResolver = { checkAndFixConflictingRequest: (persistedRequests: Array>) => resolveDuplicationConflictAction(persistedRequests, requestMatcher), }; @@ -188,7 +188,7 @@ function writeWithNoDuplicatesEnableFeatureConflicts = {}, -): Promise { +): Promise> { const conflictResolver = { checkAndFixConflictingRequest: (persistedRequests: Array>) => resolveEnableFeatureConflicts(command, persistedRequests, apiCommandParameters), }; @@ -208,7 +208,7 @@ function makeRequestWithSideEffects = {}, -): Promise { +): Promise> { Log.info('[API] Called API makeRequestWithSideEffects', false, {command, ...apiCommandParameters}); const request = prepareRequest(command, CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS, apiCommandParameters, onyxData); @@ -278,7 +278,7 @@ function paginate, config: PaginationConfig, conflictResolver: RequestConflictResolver = {}, -): Promise | void { +): Promise | void> | void { Log.info('[API] Called API.paginate', false, {command, ...apiCommandParameters}); const request: PaginatedRequest = { ...prepareRequest(command, type, apiCommandParameters, onyxData, conflictResolver), diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index b36d68ddbf12..c3bef2de8377 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -1,3 +1,4 @@ +import type {OnyxKey} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import alert from '@components/Alert'; @@ -53,7 +54,12 @@ const APICommandRegex = /\/api\/([^&?]+)\??.*/; * Send an HTTP request, and attempt to resolve the json response. * If there is a network error, we'll set the application offline. */ -function processHTTPRequest(url: string, method: RequestType = 'get', body: FormData | null = null, abortSignal: AbortSignal | undefined = undefined): Promise { +function processHTTPRequest( + url: string, + method: RequestType = 'get', + body: FormData | null = null, + abortSignal: AbortSignal | undefined = undefined, +): Promise> { const startTime = new Date().valueOf(); return fetch(url, { // We hook requests to the same Controller signal, so we can cancel them all at once @@ -116,7 +122,7 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form }); } - return response.json() as Promise; + return response.json() as Promise>; }) .then((response) => { // Some retried requests will result in a "Unique Constraints Violation" error from the server, which just means the record already exists @@ -148,7 +154,7 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form // Trigger a modal and disable the app as the user needs to upgrade to the latest minimum version to continue alertUser(); } - return response as Promise; + return response; }); } @@ -159,7 +165,13 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form * @param type HTTP request type (get/post) * @param shouldUseSecure should we use the secure server */ -function xhr(command: string, data: Record, type: RequestType = CONST.NETWORK.METHOD.POST, shouldUseSecure = false, initiatedOffline = false): Promise { +function xhr( + command: string, + data: Record, + type: RequestType = CONST.NETWORK.METHOD.POST, + shouldUseSecure = false, + initiatedOffline = false, +): Promise> { return prepareRequestPayload(command, data, initiatedOffline).then((formData) => { const url = getCommandURL({shouldUseSecure, command}); const abortSignalController = data.canCancel ? (abortControllerMap.get(command as AbortCommand) ?? abortControllerMap.get(ABORT_COMMANDS.All)) : undefined; diff --git a/src/libs/Middleware/HandleUnusedOptimisticID.ts b/src/libs/Middleware/HandleUnusedOptimisticID.ts index 0f30c98c8cf4..e8bef9d34282 100644 --- a/src/libs/Middleware/HandleUnusedOptimisticID.ts +++ b/src/libs/Middleware/HandleUnusedOptimisticID.ts @@ -9,6 +9,7 @@ import * as PersistedRequests from '@userActions/PersistedRequests'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList} from '@src/types/onyx'; import type Report from '@src/types/onyx/Report'; +import type {GenericOnyxUpdate} from '@src/types/onyx/Request'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; // Local cache of reportID to optimistic Onyx data @@ -80,7 +81,7 @@ const handleUnusedOptimisticID: Middleware = (requestResponse, request, isFromSe const {settledPersonalDetails, redundantParticipants} = reportOptimisticData.get(currentRequestReportID) ?? {}; reportOptimisticData.delete(currentRequestReportID); if (!isEmptyObject(settledPersonalDetails) && !isEmptyObject(redundantParticipants)) { - response.onyxData.push( + (response.onyxData as GenericOnyxUpdate[]).push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${currentRequestReportID}`, diff --git a/src/libs/Middleware/Logging.ts b/src/libs/Middleware/Logging.ts index 3ffe7725a45e..35d32caa5a4a 100644 --- a/src/libs/Middleware/Logging.ts +++ b/src/libs/Middleware/Logging.ts @@ -1,4 +1,4 @@ -import {OnyxKey} from 'react-native-onyx'; +import type {OnyxKey} from 'react-native-onyx'; import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; import type HttpsError from '@libs/Errors/HttpsError'; import Log from '@libs/Log'; @@ -35,7 +35,7 @@ function serializeLoggingData | undefined>(log } } -function logRequestDetails(message: string, request: Request, response?: Response | void) { +function logRequestDetails(message: string, request: Request, response?: Response | void) { // Don't log about log or else we'd cause an infinite loop if (request.command === 'Log') { return; diff --git a/src/libs/Middleware/Pagination.ts b/src/libs/Middleware/Pagination.ts index e15a528e3213..90395581bdb7 100644 --- a/src/libs/Middleware/Pagination.ts +++ b/src/libs/Middleware/Pagination.ts @@ -7,7 +7,7 @@ import PaginationUtils from '@libs/PaginationUtils'; import CONST from '@src/CONST'; import type {OnyxCollectionKey, OnyxPagesKey, OnyxValues} from '@src/ONYXKEYS'; import type {Request} from '@src/types/onyx'; -import type {PaginatedRequest} from '@src/types/onyx/Request'; +import type {GenericOnyxUpdate, PaginatedRequest} from '@src/types/onyx/Request'; import type Middleware from './types'; type PagedResource = OnyxValues[TResourceKey] extends Record ? TResource : never; @@ -125,7 +125,7 @@ const Pagination: Middleware = (requestResponse, request) => { const existingPages = pagesCollections[pageKey] ?? []; const mergedPages = PaginationUtils.mergeAndSortContinuousPages(sortedAllItems, [...existingPages, newPage], getItemID); - response.onyxData.push({ + (response.onyxData as GenericOnyxUpdate[]).push({ key: pageKey, onyxMethod: Onyx.METHOD.SET, value: mergedPages, diff --git a/src/libs/Middleware/SupportalPermission.ts b/src/libs/Middleware/SupportalPermission.ts index c70a76a2bac1..bb195e48d266 100644 --- a/src/libs/Middleware/SupportalPermission.ts +++ b/src/libs/Middleware/SupportalPermission.ts @@ -1,8 +1,9 @@ -import {OnyxKey} from 'react-native-onyx'; +import type {OnyxKey} from 'react-native-onyx'; import Log from '@libs/Log'; import {isSupportAuthToken} from '@libs/Network/NetworkStore'; import {showSupportalPermissionDenied} from '@userActions/App'; import type Request from '@src/types/onyx/Request'; +import type {PaginatedRequest} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import type Middleware from './types'; @@ -10,7 +11,7 @@ import type Middleware from './types'; * Middleware that detects when a support token attempts an unauthorized command * and triggers a global modal while preventing retries for that request. */ -const SupportalPermission: Middleware = (responsePromise: Promise, request: Request) => +const SupportalPermission: Middleware = (responsePromise: Promise | void>, request: Request | PaginatedRequest) => responsePromise.then((response) => { const message = response?.message; const isUnauthorizedSupportalAction = diff --git a/src/libs/Network/MainQueue.ts b/src/libs/Network/MainQueue.ts index d1cf83f5df7e..c4a13947dc9b 100644 --- a/src/libs/Network/MainQueue.ts +++ b/src/libs/Network/MainQueue.ts @@ -18,7 +18,7 @@ function canMakeRequest(request: OnyxRequest): boole } function push(request: OnyxRequest) { - networkRequestQueue.push(request); + networkRequestQueue.push(request as GenericRequest); } function replay(request: OnyxRequest) { @@ -75,7 +75,7 @@ function clear() { networkRequestQueue = networkRequestQueue.filter((request) => !request.data?.canCancel); } -function getAll(): Array> { +function getAll(): GenericRequest[] { return networkRequestQueue; } diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index 3c23d5416036..a6ea73e5d07c 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -20,7 +20,7 @@ import RequestThrottle from '@libs/RequestThrottle'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; -import type {ConflictData} from '@src/types/onyx/Request'; +import type {ConflictData, GenericRequest} from '@src/types/onyx/Request'; import {isOffline, onReconnection} from './NetworkStore'; let shouldFailAllRequests: boolean; @@ -326,7 +326,7 @@ function handleConflictActions(conflictAction: ConflictDat if (conflictAction.type === 'push') { savePersistedRequest(newRequest); } else if (conflictAction.type === 'replace') { - updatePersistedRequest(conflictAction.index, conflictAction.request ?? newRequest); + updatePersistedRequest(conflictAction.index, conflictAction.request ?? (newRequest as GenericRequest)); } else if (conflictAction.type === 'delete') { deletePersistedRequestsByIndices(conflictAction.indices); if (conflictAction.pushNewRequest) { diff --git a/src/libs/Request.ts b/src/libs/Request.ts index 0028431cfe38..79c3d119fe36 100644 --- a/src/libs/Request.ts +++ b/src/libs/Request.ts @@ -8,14 +8,14 @@ import {hasReadRequiredDataFromStorage} from './Network/NetworkStore'; let middlewares: Middleware[] = []; -function makeXHR(request: Request): Promise { +function makeXHR(request: Request): Promise | void> { const finalParameters = enhanceParameters(request.command, request?.data ?? {}); - return hasReadRequiredDataFromStorage().then((): Promise => { + return hasReadRequiredDataFromStorage().then((): Promise | void> => { return HttpUtils.xhr(request.command, finalParameters, request.type, request.shouldUseSecure, request.initiatedOffline); }); } -function processWithMiddleware(request: Request, isFromSequentialQueue = false): Promise { +function processWithMiddleware(request: Request, isFromSequentialQueue = false): Promise | void> { return middlewares.reduce((last, middleware) => middleware(last, request, isFromSequentialQueue), makeXHR(request)); } diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index 9ab203c4210b..5799e26014fa 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -88,8 +88,8 @@ function save(requestToPersist: Request) { // If the command is not in the keepLastInstance array, add the new request as usual const requests = [...persistedRequests, requestToPersist]; - persistedRequests = requests; - Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests) + persistedRequests = requests as GenericRequest[]; + Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests as GenericRequest[]) .then(() => { Log.info(`[SequentialQueue] '${requestToPersist.command}' command queued. Queue length is ${getLength()}`); }) @@ -138,17 +138,17 @@ function update(oldRequestIndex: number, newRequest: Reque const requests = [...persistedRequests]; const oldRequest = requests.at(oldRequestIndex); Log.info('[PersistedRequests] Updating a request', false, {oldRequest, newRequest, oldRequestIndex}); - requests.splice(oldRequestIndex, 1, newRequest); + requests.splice(oldRequestIndex, 1, newRequest as GenericRequest); persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); } function updateOngoingRequest(newRequest: Request) { Log.info('[PersistedRequests] Updating the ongoing request', false, {ongoingRequest, newRequest}); - ongoingRequest = newRequest; + ongoingRequest = newRequest as GenericRequest; if (newRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest as GenericRequest); } } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 28ffcaad112d..acebbe1201d4 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -187,6 +187,7 @@ import SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/NewRoomForm'; import type { BankAccountList, + GenericRequest, IntroSelected, InvitedEmailsToAccountIDs, NewGroupChatDraft, @@ -2310,7 +2311,7 @@ function editReportComment( if (addCommentIndex > -1) { return resolveEditCommentWithNewAddCommentRequest(persistedRequests, parameters, reportActionID, addCommentIndex); } - return resolveDuplicationConflictAction(persistedRequests, createUpdateCommentMatcher(reportActionID)); + return resolveDuplicationConflictAction(persistedRequests as GenericRequest[], createUpdateCommentMatcher(reportActionID)); }, }, ); diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 2729111fb4c7..4ef148f49ed0 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -59,46 +59,34 @@ type GenericOnyxUpdate = { }; /** Model of onyx requests sent to the API */ -type GenericOnyxData = { +type OnyxDataBase = { /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ - successData?: GenericOnyxUpdate[]; + successData?: TOnyxUpdate[]; /** Onyx instructions that are executed after getting response from server with jsonCode !== 200 */ - failureData?: GenericOnyxUpdate[]; + failureData?: TOnyxUpdate[]; /** Onyx instructions that are executed after getting any response from server */ - finallyData?: GenericOnyxUpdate[]; + finallyData?: TOnyxUpdate[]; /** Onyx instructions that are executed before request is made to the server */ - optimisticData?: GenericOnyxUpdate[]; + optimisticData?: TOnyxUpdate[]; /** Onyx instructions that are executed when Onyx queue is flushed */ - queueFlushedData?: GenericOnyxUpdate[]; + queueFlushedData?: TOnyxUpdate[]; }; /** Model of onyx requests sent to the API */ -type OnyxData = { - /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ - successData?: Array>; - - /** Onyx instructions that are executed after getting response from server with jsonCode !== 200 */ - failureData?: Array>; - - /** Onyx instructions that are executed after getting any response from server */ - finallyData?: Array>; +type OnyxData = OnyxDataBase>; - /** Onyx instructions that are executed before request is made to the server */ - optimisticData?: Array>; - - /** Onyx instructions that are executed when Onyx queue is flushed */ - queueFlushedData?: Array>; -}; +/** Model of onyx requests sent to the API */ +type GenericOnyxData = OnyxDataBase; /** HTTP request method names */ type RequestType = 'get' | 'post'; /** Model of overall requests sent to the API */ -type GenericRequestData = { +type RequestDataBase = { /** Name of the API command */ command: string; @@ -114,17 +102,8 @@ type GenericRequestData = { /** Whether the app should connect to the secure API endpoints */ shouldUseSecure?: boolean; - /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ - successData?: GenericOnyxUpdate[]; - - /** Onyx instructions that are executed after getting response from server with jsonCode !== 200 */ - failureData?: GenericOnyxUpdate[]; - - /** Onyx instructions that are executed after getting any response from server */ - finallyData?: GenericOnyxUpdate[]; - /** Promise resolve handler */ - resolve?: (value: Response) => void; + resolve?: (value: Response) => void; /** Promise reject handler */ reject?: (value?: unknown) => void; @@ -147,53 +126,10 @@ type GenericRequestData = { }; /** Model of overall requests sent to the API */ -type RequestData = { - /** Name of the API command */ - command: string; +type RequestData = RequestDataBase & OnyxData; - /** Command name for logging purposes */ - commandName?: string; - - /** Additional parameters that can be sent with the request */ - data?: Record; - - /** The HTTP request method name */ - type?: RequestType; - - /** Whether the app should connect to the secure API endpoints */ - shouldUseSecure?: boolean; - - /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ - successData?: Array>; - - /** Onyx instructions that are executed after getting response from server with jsonCode !== 200 */ - failureData?: Array>; - - /** Onyx instructions that are executed after getting any response from server */ - finallyData?: Array>; - - /** Promise resolve handler */ - resolve?: (value: Response) => void; - - /** Promise reject handler */ - reject?: (value?: unknown) => void; - - /** Whether the app should skip the web proxy to connect to API endpoints */ - shouldSkipWebProxy?: boolean; - - /** - * Whether the request is initiated offline. - * - * This field is used to indicate if the app initiates the request while offline. - * It is particularly useful for scenarios such as receipts recreating, where - * the app needs to regenerate a blob once the user gets back online. - * More info https://github.com/Expensify/App/issues/51761 - */ - initiatedOffline?: boolean; - - /** The unique ID of the request */ - requestID?: number; -}; +/** Model of overall requests sent to the API */ +type GenericRequestData = RequestDataBase & GenericOnyxData; /** * Represents the possible actions to take in case of a conflict in the request queue. @@ -279,11 +215,12 @@ type ConflictActionData = { * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ -type GenericRequestConflictResolver = { +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +interface RequestConflictResolverBase { /** * A function that checks if a new request conflicts with any existing requests in the queue. */ - checkAndFixConflictingRequest?(persistedRequest: GenericRequest[]): ConflictActionData; + checkAndFixConflictingRequest?(persistedRequest: TRequest[]): ConflictActionData; /** * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads @@ -295,29 +232,19 @@ type GenericRequestConflictResolver = { * A boolean flag to mark a request as rollback, if set to true it means the request failed and was added back into the queue. */ isRollback?: boolean; -}; +} /** * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ -type RequestConflictResolver = { - /** - * A function that checks if a new request conflicts with any existing requests in the queue. - */ - checkAndFixConflictingRequest?(persistedRequest: Array>): ConflictActionData; - - /** - * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads - * the ongoing request, it will be removed from the persisted request queue. - */ - persistWhenOngoing?: boolean; +type RequestConflictResolver = RequestConflictResolverBase>; - /** - * A boolean flag to mark a request as rollback, if set to true it means the request failed and was added back into the queue. - */ - isRollback?: boolean; -}; +/** + * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, + * and how to resolve those conflicts. + */ +type GenericRequestConflictResolver = RequestConflictResolverBase; /** Model of requests sent to the API */ type GenericRequest = GenericRequestData & GenericOnyxData & GenericRequestConflictResolver; diff --git a/tests/actions/SessionTest.ts b/tests/actions/SessionTest.ts index 496e088983df..daadc53ceabc 100644 --- a/tests/actions/SessionTest.ts +++ b/tests/actions/SessionTest.ts @@ -24,7 +24,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; // We are mocking this method so that we can later test to see if it was called and what arguments it was called with. // We test HttpUtils.xhr() since this means that our API command turned into a network request and isn't only queued. -HttpUtils.xhr = jest.fn(); +HttpUtils.xhr = jest.fn() as typeof HttpUtils.xhr; // Mocked to ensure push notifications are subscribed/unsubscribed as the session changes jest.mock('@libs/Notification/PushNotification'); diff --git a/tests/unit/SequentialQueueTest.ts b/tests/unit/SequentialQueueTest.ts index 2b0fbd1a9bae..db22db30d5bb 100644 --- a/tests/unit/SequentialQueueTest.ts +++ b/tests/unit/SequentialQueueTest.ts @@ -5,7 +5,7 @@ import {getAll, getLength, getOngoingRequest} from '@userActions/PersistedReques import ONYXKEYS from '@src/ONYXKEYS'; import * as SequentialQueue from '../../src/libs/Network/SequentialQueue'; import type Request from '../../src/types/onyx/Request'; -import type {ConflictActionData} from '../../src/types/onyx/Request'; +import type {ConflictActionData, GenericRequest} from '../../src/types/onyx/Request'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -248,7 +248,7 @@ describe('SequentialQueue', () => { it('should get the ongoing request from onyx and start processing it', async () => { const persistedRequest = {...request, persistWhenOngoing: true, initiatedOffline: false}; - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, persistedRequest); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, persistedRequest as GenericRequest); SequentialQueue.push({command: 'OpenReport'}); await Promise.resolve(); From d84b09a1fa3424f2369f2c3510088ddf9d1b905b Mon Sep 17 00:00:00 2001 From: Olgierd Date: Tue, 27 Jan 2026 19:23:08 +0100 Subject: [PATCH 21/34] Remove redundant OnyxData from Request types and fix spread syntax related error --- src/libs/actions/IOU/Split.ts | 14 +++++++++----- src/libs/actions/IOU/index.ts | 1 + src/types/onyx/Request.ts | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts index e9711a7b904e..0eb24375cf0e 100644 --- a/src/libs/actions/IOU/Split.ts +++ b/src/libs/actions/IOU/Split.ts @@ -71,7 +71,7 @@ import { mergePolicyRecentlyUsedCategories, mergePolicyRecentlyUsedCurrencies, } from './index'; -import type {MoneyRequestInformationParams, OneOnOneIOUReport, StartSplitBilActionParams} from './index'; +import type {BuildOnyxDataForMoneyRequestKeys, MoneyRequestInformationParams, OneOnOneIOUReport, StartSplitBilActionParams} from './index'; type IOURequestType = ValueOf; @@ -698,7 +698,7 @@ function completeSplitBill( const unmodifiedTransaction = getAllTransactions()[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; // Save optimistic updated transaction and action - const optimisticData: OnyxUpdate[] = [ + const optimisticData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, @@ -723,7 +723,7 @@ function completeSplitBill( }, ]; - const successData: OnyxUpdate[] = [ + const successData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, @@ -736,7 +736,7 @@ function completeSplitBill( }, ]; - const failureData: OnyxUpdate[] = [ + const failureData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, @@ -883,7 +883,11 @@ function completeSplitBill( } const hasViolations = hasViolationsReportUtils(oneOnOneIOUReport.reportID, transactionViolations, sessionAccountID, sessionEmail ?? ''); - const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest({ + const { + optimisticData: oneOnOneOptimisticData = [], + successData: oneOnOneSuccessData = [], + failureData: oneOnOneFailureData = [], + } = buildOnyxDataForMoneyRequest({ isNewChatReport: isNewOneOnOneChatReport, isOneOnOneSplit: true, shouldCreateNewMoneyRequestReport: shouldCreateNewOneOnOneIOUReport, diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 57fa1d46f91c..40459772ced9 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -13297,4 +13297,5 @@ export type { OneOnOneIOUReport, RejectMoneyRequestData, CreateDistanceRequestInformation, + BuildOnyxDataForMoneyRequestKeys, }; diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 4ef148f49ed0..36dee356c356 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -247,10 +247,10 @@ type RequestConflictResolver = RequestConflictResolverBase type GenericRequestConflictResolver = RequestConflictResolverBase; /** Model of requests sent to the API */ -type GenericRequest = GenericRequestData & GenericOnyxData & GenericRequestConflictResolver; +type GenericRequest = GenericRequestData & GenericRequestConflictResolver; /** Model of requests sent to the API */ -type Request = RequestData & OnyxData & RequestConflictResolver; +type Request = RequestData & RequestConflictResolver; /** * An object used to describe how a request can be paginated. From fab82e71c9f13735db23f900a9077d6ae74b8715 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Tue, 27 Jan 2026 19:39:27 +0100 Subject: [PATCH 22/34] Rename types starting with 'Generic' to avoid confusion --- .../Middleware/HandleUnusedOptimisticID.ts | 4 +-- src/libs/Middleware/Pagination.ts | 4 +-- src/libs/Network/MainQueue.ts | 10 +++---- src/libs/Network/SequentialQueue.ts | 4 +-- src/libs/actions/PersistedRequests.ts | 26 +++++++++---------- src/libs/actions/QueuedOnyxUpdates.ts | 4 +-- src/libs/actions/RequestConflictUtils.ts | 4 +-- src/types/onyx/Request.ts | 25 +++++------------- src/types/onyx/index.ts | 4 +-- tests/unit/SequentialQueueTest.ts | 4 +-- 10 files changed, 39 insertions(+), 50 deletions(-) diff --git a/src/libs/Middleware/HandleUnusedOptimisticID.ts b/src/libs/Middleware/HandleUnusedOptimisticID.ts index e8bef9d34282..f1c76e261f5f 100644 --- a/src/libs/Middleware/HandleUnusedOptimisticID.ts +++ b/src/libs/Middleware/HandleUnusedOptimisticID.ts @@ -9,7 +9,7 @@ import * as PersistedRequests from '@userActions/PersistedRequests'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList} from '@src/types/onyx'; import type Report from '@src/types/onyx/Report'; -import type {GenericOnyxUpdate} from '@src/types/onyx/Request'; +import type {AnyOnyxUpdate} from '@src/types/onyx/Request'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; // Local cache of reportID to optimistic Onyx data @@ -81,7 +81,7 @@ const handleUnusedOptimisticID: Middleware = (requestResponse, request, isFromSe const {settledPersonalDetails, redundantParticipants} = reportOptimisticData.get(currentRequestReportID) ?? {}; reportOptimisticData.delete(currentRequestReportID); if (!isEmptyObject(settledPersonalDetails) && !isEmptyObject(redundantParticipants)) { - (response.onyxData as GenericOnyxUpdate[]).push( + (response.onyxData as AnyOnyxUpdate[]).push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${currentRequestReportID}`, diff --git a/src/libs/Middleware/Pagination.ts b/src/libs/Middleware/Pagination.ts index 90395581bdb7..3f5c76474f19 100644 --- a/src/libs/Middleware/Pagination.ts +++ b/src/libs/Middleware/Pagination.ts @@ -7,7 +7,7 @@ import PaginationUtils from '@libs/PaginationUtils'; import CONST from '@src/CONST'; import type {OnyxCollectionKey, OnyxPagesKey, OnyxValues} from '@src/ONYXKEYS'; import type {Request} from '@src/types/onyx'; -import type {GenericOnyxUpdate, PaginatedRequest} from '@src/types/onyx/Request'; +import type {AnyOnyxUpdate, PaginatedRequest} from '@src/types/onyx/Request'; import type Middleware from './types'; type PagedResource = OnyxValues[TResourceKey] extends Record ? TResource : never; @@ -125,7 +125,7 @@ const Pagination: Middleware = (requestResponse, request) => { const existingPages = pagesCollections[pageKey] ?? []; const mergedPages = PaginationUtils.mergeAndSortContinuousPages(sortedAllItems, [...existingPages, newPage], getItemID); - (response.onyxData as GenericOnyxUpdate[]).push({ + (response.onyxData as AnyOnyxUpdate[]).push({ key: pageKey, onyxMethod: Onyx.METHOD.SET, value: mergedPages, diff --git a/src/libs/Network/MainQueue.ts b/src/libs/Network/MainQueue.ts index c4a13947dc9b..2cf72456616b 100644 --- a/src/libs/Network/MainQueue.ts +++ b/src/libs/Network/MainQueue.ts @@ -1,12 +1,12 @@ import type {OnyxKey} from 'react-native-onyx'; import {processWithMiddleware} from '@libs/Request'; import type OnyxRequest from '@src/types/onyx/Request'; -import type {GenericRequest} from '@src/types/onyx/Request'; +import type {AnyRequest} from '@src/types/onyx/Request'; import {isAuthenticating, isOffline} from './NetworkStore'; import {isRunning as sequentialQueueIsRunning} from './SequentialQueue'; // Queue for network requests so we don't lose actions done by the user while offline -let networkRequestQueue: GenericRequest[] = []; +let networkRequestQueue: AnyRequest[] = []; /** * Checks to see if a request can be made. @@ -18,7 +18,7 @@ function canMakeRequest(request: OnyxRequest): boole } function push(request: OnyxRequest) { - networkRequestQueue.push(request as GenericRequest); + networkRequestQueue.push(request as AnyRequest); } function replay(request: OnyxRequest) { @@ -45,7 +45,7 @@ function process() { // - we are in the process of authenticating and the request is retryable (most are) // - the request does not have forceNetworkRequest === true (this will trigger it to process immediately) // - the request does not have shouldRetry === false (specified when we do not want to retry, defaults to true) - const requestsToProcessOnNextRun: GenericRequest[] = []; + const requestsToProcessOnNextRun: AnyRequest[] = []; for (const queuedRequest of networkRequestQueue) { // Check if we can make this request at all and if we can't see if we should save it for the next run or chuck it into the ether @@ -75,7 +75,7 @@ function clear() { networkRequestQueue = networkRequestQueue.filter((request) => !request.data?.canCancel); } -function getAll(): GenericRequest[] { +function getAll(): AnyRequest[] { return networkRequestQueue; } diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index a6ea73e5d07c..53a0657c47b4 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -20,7 +20,7 @@ import RequestThrottle from '@libs/RequestThrottle'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; -import type {ConflictData, GenericRequest} from '@src/types/onyx/Request'; +import type {AnyRequest, ConflictData} from '@src/types/onyx/Request'; import {isOffline, onReconnection} from './NetworkStore'; let shouldFailAllRequests: boolean; @@ -326,7 +326,7 @@ function handleConflictActions(conflictAction: ConflictDat if (conflictAction.type === 'push') { savePersistedRequest(newRequest); } else if (conflictAction.type === 'replace') { - updatePersistedRequest(conflictAction.index, conflictAction.request ?? (newRequest as GenericRequest)); + updatePersistedRequest(conflictAction.index, conflictAction.request ?? (newRequest as AnyRequest)); } else if (conflictAction.type === 'delete') { deletePersistedRequestsByIndices(conflictAction.indices); if (conflictAction.pushNewRequest) { diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index 5799e26014fa..07d2c519c4d8 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -4,11 +4,11 @@ import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Request} from '@src/types/onyx'; -import type {GenericRequest} from '@src/types/onyx/Request'; +import type {AnyRequest} from '@src/types/onyx/Request'; -let persistedRequests: GenericRequest[] = []; -let ongoingRequest: GenericRequest | null = null; -let pendingSaveOperations: GenericRequest[] = []; +let persistedRequests: AnyRequest[] = []; +let ongoingRequest: AnyRequest | null = null; +let pendingSaveOperations: AnyRequest[] = []; let isInitialized = false; let initializationCallback: () => void; function triggerInitializationCallback() { @@ -82,14 +82,14 @@ function save(requestToPersist: Request) { // If not initialized yet, queue the request for later processing if (!isInitialized) { Log.info('[PersistedRequests] Queueing request until initialization completes', false); - pendingSaveOperations.push(requestToPersist as GenericRequest); + pendingSaveOperations.push(requestToPersist as AnyRequest); return; } // If the command is not in the keepLastInstance array, add the new request as usual const requests = [...persistedRequests, requestToPersist]; - persistedRequests = requests as GenericRequest[]; - Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests as GenericRequest[]) + persistedRequests = requests as AnyRequest[]; + Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests as AnyRequest[]) .then(() => { Log.info(`[SequentialQueue] '${requestToPersist.command}' command queued. Queue length is ${getLength()}`); }) @@ -138,21 +138,21 @@ function update(oldRequestIndex: number, newRequest: Reque const requests = [...persistedRequests]; const oldRequest = requests.at(oldRequestIndex); Log.info('[PersistedRequests] Updating a request', false, {oldRequest, newRequest, oldRequestIndex}); - requests.splice(oldRequestIndex, 1, newRequest as GenericRequest); + requests.splice(oldRequestIndex, 1, newRequest as AnyRequest); persistedRequests = requests; Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, requests); } function updateOngoingRequest(newRequest: Request) { Log.info('[PersistedRequests] Updating the ongoing request', false, {ongoingRequest, newRequest}); - ongoingRequest = newRequest as GenericRequest; + ongoingRequest = newRequest as AnyRequest; if (newRequest.persistWhenOngoing) { - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest as GenericRequest); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, newRequest as AnyRequest); } } -function processNextRequest(): GenericRequest | null { +function processNextRequest(): AnyRequest | null { if (ongoingRequest) { Log.info(`Ongoing Request already set returning same one ${ongoingRequest.commandName}`); return ongoingRequest; @@ -188,11 +188,11 @@ function rollbackOngoingRequest() { ongoingRequest = null; } -function getAll(): GenericRequest[] { +function getAll(): AnyRequest[] { return persistedRequests; } -function getOngoingRequest(): GenericRequest | null { +function getOngoingRequest(): AnyRequest | null { return ongoingRequest; } diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index e29965844b57..0837b8d45bb3 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -2,11 +2,11 @@ import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {GenericOnyxUpdate} from '@src/types/onyx/Request'; +import type {AnyOnyxUpdate} from '@src/types/onyx/Request'; // In this file we manage a queue of Onyx updates while the SequentialQueue is processing. There are functions to get the updates and clear the queue after saving the updates in Onyx. -let queuedOnyxUpdates: GenericOnyxUpdate[] = []; +let queuedOnyxUpdates: AnyOnyxUpdate[] = []; let currentAccountID: number | undefined; // We use `connectWithoutView` because it is not connected to any UI component. diff --git a/src/libs/actions/RequestConflictUtils.ts b/src/libs/actions/RequestConflictUtils.ts index aaabfe1f0b0c..92201a6c1196 100644 --- a/src/libs/actions/RequestConflictUtils.ts +++ b/src/libs/actions/RequestConflictUtils.ts @@ -6,7 +6,7 @@ import {WRITE_COMMANDS} from '@libs/API/types'; import type {ApiRequestCommandParameters} from '@libs/API/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; -import type {ConflictActionData, GenericRequest} from '@src/types/onyx/Request'; +import type {AnyRequest, ConflictActionData} from '@src/types/onyx/Request'; type RequestMatcher = (request: OnyxRequest) => boolean; @@ -41,7 +41,7 @@ const enablePolicyFeatureCommand = [ type EnablePolicyFeatureCommand = TupleToUnion; function createUpdateCommentMatcher(reportActionID: string) { - return function (request: GenericRequest) { + return function (request: AnyRequest) { return request.command === WRITE_COMMANDS.UPDATE_COMMENT && request.data?.reportActionID === reportActionID; }; } diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 36dee356c356..ad31df2396f3 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -36,7 +36,7 @@ type ExpandOnyxKeys = TKey extends CollectionKeyBase ? NoI * */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -type GenericOnyxUpdate = { +type AnyOnyxUpdate = { /** * */ @@ -80,7 +80,7 @@ type OnyxDataBase = { type OnyxData = OnyxDataBase>; /** Model of onyx requests sent to the API */ -type GenericOnyxData = OnyxDataBase; +type AnyOnyxData = OnyxDataBase; /** HTTP request method names */ type RequestType = 'get' | 'post'; @@ -129,7 +129,7 @@ type RequestDataBase = { type RequestData = RequestDataBase & OnyxData; /** Model of overall requests sent to the API */ -type GenericRequestData = RequestDataBase & GenericOnyxData; +type AnyRequestData = RequestDataBase & AnyOnyxData; /** * Represents the possible actions to take in case of a conflict in the request queue. @@ -153,7 +153,7 @@ type ConflictRequestReplace = { /** * The new request to replace the existing request in the queue. */ - request?: GenericRequest; + request?: AnyRequest; }; /** @@ -244,10 +244,10 @@ type RequestConflictResolver = RequestConflictResolverBase * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ -type GenericRequestConflictResolver = RequestConflictResolverBase; +type AnyRequestConflictResolver = RequestConflictResolverBase; /** Model of requests sent to the API */ -type GenericRequest = GenericRequestData & GenericRequestConflictResolver; +type AnyRequest = AnyRequestData & AnyRequestConflictResolver; /** Model of requests sent to the API */ type Request = RequestData & RequestConflictResolver; @@ -279,15 +279,4 @@ type PaginatedRequest = Request & }; export default Request; -export type { - GenericOnyxUpdate, - OnyxData, - RequestType, - PaginationConfig, - PaginatedRequest, - RequestConflictResolver, - GenericRequestConflictResolver, - ConflictActionData, - ConflictData, - GenericRequest, -}; +export type {AnyOnyxUpdate, OnyxData, RequestType, PaginationConfig, PaginatedRequest, RequestConflictResolver, AnyRequestConflictResolver, ConflictActionData, ConflictData, AnyRequest}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 24da90179cc7..252ae11cf118 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -118,7 +118,7 @@ import type ReportUserIsTyping from './ReportUserIsTyping'; import type {ReportFieldsViolations, ReportViolationName} from './ReportViolation'; import type ReportViolations from './ReportViolation'; import type Request from './Request'; -import type {GenericRequest} from './Request'; +import type {AnyRequest} from './Request'; import type Response from './Response'; import type ReviewDuplicates from './ReviewDuplicates'; import type {SaveSearch} from './SaveSearch'; @@ -250,7 +250,7 @@ export type { ReportFieldsViolations, ReportLayoutGroupBy, GroupedTransactions, - GenericRequest, + AnyRequest as GenericRequest, Request, Response, ScreenShareRequest, diff --git a/tests/unit/SequentialQueueTest.ts b/tests/unit/SequentialQueueTest.ts index db22db30d5bb..2e91198ef136 100644 --- a/tests/unit/SequentialQueueTest.ts +++ b/tests/unit/SequentialQueueTest.ts @@ -5,7 +5,7 @@ import {getAll, getLength, getOngoingRequest} from '@userActions/PersistedReques import ONYXKEYS from '@src/ONYXKEYS'; import * as SequentialQueue from '../../src/libs/Network/SequentialQueue'; import type Request from '../../src/types/onyx/Request'; -import type {ConflictActionData, GenericRequest} from '../../src/types/onyx/Request'; +import type {AnyRequest, ConflictActionData} from '../../src/types/onyx/Request'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -248,7 +248,7 @@ describe('SequentialQueue', () => { it('should get the ongoing request from onyx and start processing it', async () => { const persistedRequest = {...request, persistWhenOngoing: true, initiatedOffline: false}; - Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, persistedRequest as GenericRequest); + Onyx.set(ONYXKEYS.PERSISTED_ONGOING_REQUESTS, persistedRequest as AnyRequest); SequentialQueue.push({command: 'OpenReport'}); await Promise.resolve(); From f8c5fe8e4a08ccb2b4d66a6b95b2dba1766ad7dc Mon Sep 17 00:00:00 2001 From: Olgierd Date: Tue, 27 Jan 2026 20:08:18 +0100 Subject: [PATCH 23/34] Add elementary jsdoc comments to new types --- src/types/onyx/Request.ts | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index ad31df2396f3..137d1faaf534 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -22,24 +22,19 @@ type TypeOptions = Merge< CustomTypeOptions >; -/** - * - */ +/** Represents a string union of all Onyx collection keys. */ type CollectionKeyBase = TypeOptions['collectionKeys']; -/** - * - */ +/** Expands an Onyx key, allowing template patterns for collections or enforcing literals otherwise. */ type ExpandOnyxKeys = TKey extends CollectionKeyBase ? NoInfer<`${TKey}${string}`> : TKey; /** - * + * Represents an OnyxUpdate type without strict type checks on the value. + * Useful for contexts where the specific Onyx key is not known ahead of time. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyOnyxUpdate = { - /** - * - */ + /** The Onyx method to perform */ onyxMethod: | typeof OnyxUtils.METHOD.SET | typeof OnyxUtils.METHOD.MULTI_SET @@ -47,18 +42,14 @@ type AnyOnyxUpdate = { | typeof OnyxUtils.METHOD.CLEAR | typeof OnyxUtils.METHOD.MERGE_COLLECTION | typeof OnyxUtils.METHOD.SET_COLLECTION; - /** - * - */ + /** The Onyx key to update */ key: ExpandOnyxKeys; - /** - * - */ + /** The data to be written to Onyx. Typed as `any` to allow flexibility */ // eslint-disable-next-line @typescript-eslint/no-explicit-any value?: any; }; -/** Model of onyx requests sent to the API */ +/** Generic base for types of onyx requests model sent to the API */ type OnyxDataBase = { /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ successData?: TOnyxUpdate[]; @@ -79,13 +70,13 @@ type OnyxDataBase = { /** Model of onyx requests sent to the API */ type OnyxData = OnyxDataBase>; -/** Model of onyx requests sent to the API */ +/** Loosely typed model of onyx requests sent to the API */ type AnyOnyxData = OnyxDataBase; /** HTTP request method names */ type RequestType = 'get' | 'post'; -/** Model of overall requests sent to the API */ +/** Base model for API requests containing common metadata and handlers */ type RequestDataBase = { /** Name of the API command */ command: string; @@ -128,7 +119,7 @@ type RequestDataBase = { /** Model of overall requests sent to the API */ type RequestData = RequestDataBase & OnyxData; -/** Model of overall requests sent to the API */ +/** Loosely typed model of overall requests sent to the API */ type AnyRequestData = RequestDataBase & AnyOnyxData; /** From dfb004f595d4e6212633c4256ae73cf6e0dcd070 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Wed, 28 Jan 2026 11:52:11 +0100 Subject: [PATCH 24/34] Rename types starting with 'Generic' to avoid confusion --- src/ONYXKEYS.ts | 4 ++-- src/libs/AppState/RequestsQueuesState/types.ts | 4 ++-- src/libs/actions/Report.ts | 4 ++-- src/types/onyx/index.ts | 2 +- tests/actions/ReportTest.ts | 6 +++--- tests/unit/RequestTest.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index fd8ba7567efa..66e21c055234 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1190,8 +1190,8 @@ type OnyxValuesMapping = { [ONYXKEYS.DEVICE_ID]: string; [ONYXKEYS.ACTIVATED_CARD_PIN]: string | undefined; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; - [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.GenericRequest[]; - [ONYXKEYS.PERSISTED_ONGOING_REQUESTS]: OnyxTypes.GenericRequest; + [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.AnyRequest[]; + [ONYXKEYS.PERSISTED_ONGOING_REQUESTS]: OnyxTypes.AnyRequest; [ONYXKEYS.CURRENT_DATE]: string; [ONYXKEYS.CREDENTIALS]: OnyxTypes.Credentials; [ONYXKEYS.STASHED_CREDENTIALS]: OnyxTypes.Credentials; diff --git a/src/libs/AppState/RequestsQueuesState/types.ts b/src/libs/AppState/RequestsQueuesState/types.ts index b840a7d2a620..b79432d1cc45 100644 --- a/src/libs/AppState/RequestsQueuesState/types.ts +++ b/src/libs/AppState/RequestsQueuesState/types.ts @@ -1,4 +1,4 @@ -import type {GenericRequest} from '@src/types/onyx'; +import type {AnyRequest} from '@src/types/onyx'; /** * Main queue state @@ -11,7 +11,7 @@ type MainQueueInfo = { queuedCommands?: string[]; }; -type OngoingRequestInfo = Pick; +type OngoingRequestInfo = Pick; /** * Persisted requests state diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 1c9f8fc2453a..17ac0ed773af 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -182,8 +182,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NewRoomForm'; import type { + AnyRequest, BankAccountList, - GenericRequest, IntroSelected, InvitedEmailsToAccountIDs, NewGroupChatDraft, @@ -2299,7 +2299,7 @@ function editReportComment( if (addCommentIndex > -1) { return resolveEditCommentWithNewAddCommentRequest(persistedRequests, parameters, reportActionID, addCommentIndex); } - return resolveDuplicationConflictAction(persistedRequests as GenericRequest[], createUpdateCommentMatcher(reportActionID)); + return resolveDuplicationConflictAction(persistedRequests as AnyRequest[], createUpdateCommentMatcher(reportActionID)); }, }, ); diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 252ae11cf118..2bc7d813a4d2 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -250,7 +250,7 @@ export type { ReportFieldsViolations, ReportLayoutGroupBy, GroupedTransactions, - AnyRequest as GenericRequest, + AnyRequest, Request, Response, ScreenShareRequest, diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 5fb93959b77c..dc1af020aced 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -1411,7 +1411,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], [fileA, fileB, fileC], 'Hello world', CONST.DEFAULT_TIME_ZONE, shouldPlaySound); - const relevant = (await relevantPromise) as OnyxTypes.GenericRequest[]; + const relevant = (await relevantPromise) as OnyxTypes.AnyRequest[]; expect(playSoundMock).toHaveBeenCalledTimes(1); expect(playSoundMock).toHaveBeenCalledWith(SOUNDS.DONE); @@ -1445,7 +1445,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], [fileA, fileB], undefined, CONST.DEFAULT_TIME_ZONE, shouldPlaySound); - const relevant = (await relevantPromise) as OnyxTypes.GenericRequest[]; + const relevant = (await relevantPromise) as OnyxTypes.AnyRequest[]; expect(playSoundMock).toHaveBeenCalledTimes(1); expect(playSoundMock).toHaveBeenCalledWith(SOUNDS.DONE); @@ -1478,7 +1478,7 @@ describe('actions/Report', () => { const REPORT: OnyxTypes.Report = createRandomReport(1, undefined); Report.addAttachmentWithComment(REPORT, REPORT_ID, [], file); - const relevant = (await relevantPromise) as OnyxTypes.GenericRequest[]; + const relevant = (await relevantPromise) as OnyxTypes.AnyRequest[]; expect(playSoundMock).toHaveBeenCalledTimes(0); expect(relevant.at(0)?.command).toBe(WRITE_COMMANDS.ADD_ATTACHMENT); diff --git a/tests/unit/RequestTest.ts b/tests/unit/RequestTest.ts index 6f4ee489fea0..0fb5ec250a11 100644 --- a/tests/unit/RequestTest.ts +++ b/tests/unit/RequestTest.ts @@ -12,7 +12,7 @@ beforeEach(() => { Request.clearMiddlewares(); }); -const request: OnyxTypes.GenericRequest = { +const request: OnyxTypes.AnyRequest = { command: 'MockCommand', data: {authToken: 'testToken'}, }; From e9e3e7bc6ffc56734133ad853dca3b3712cd8be7 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Wed, 28 Jan 2026 13:10:46 +0100 Subject: [PATCH 25/34] Improve new Base and Any type jsdoc comments --- src/types/onyx/Request.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 137d1faaf534..14e8bf53c197 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -76,7 +76,7 @@ type AnyOnyxData = OnyxDataBase; /** HTTP request method names */ type RequestType = 'get' | 'post'; -/** Base model for API requests containing common metadata and handlers */ +/** Generic base model for API requests containing common metadata and handlers */ type RequestDataBase = { /** Name of the API command */ command: string; @@ -203,7 +203,7 @@ type ConflictActionData = { }; /** - * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, + * Generic base for objects that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions @@ -232,7 +232,7 @@ interface RequestConflictResolverBase { type RequestConflictResolver = RequestConflictResolverBase>; /** - * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, + * Loosely typed object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ type AnyRequestConflictResolver = RequestConflictResolverBase; From a5e02c445d5d19314dadf7d72888cf02d0210767 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Wed, 28 Jan 2026 15:52:18 +0100 Subject: [PATCH 26/34] Improve generic types usage --- src/libs/API/index.ts | 8 ++--- src/libs/actions/RequestConflictUtils.ts | 6 ++-- src/types/onyx/Request.ts | 40 ++++++------------------ 3 files changed, 16 insertions(+), 38 deletions(-) diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index 1669bd6d8d32..eb31018caaa4 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx'; import type {OnyxKey} from 'react-native-onyx'; import type {SetRequired} from 'type-fest'; import {resolveDuplicationConflictAction, resolveEnableFeatureConflicts} from '@libs/actions/RequestConflictUtils'; -import type {EnablePolicyFeatureCommand, RequestMatcher} from '@libs/actions/RequestConflictUtils'; +import type {AnyRequestMatcher, EnablePolicyFeatureCommand} from '@libs/actions/RequestConflictUtils'; import Log from '@libs/Log'; import {handleDeletedAccount, HandleUnusedOptimisticID, Logging, Pagination, Reauthentication, RecheckConnection, SaveResponseInOnyx, SupportalPermission} from '@libs/Middleware'; import FraudMonitoring from '@libs/Middleware/FraudMonitoring'; @@ -13,7 +13,7 @@ import {addMiddleware, processWithMiddleware} from '@libs/Request'; import {getAll, getLength as getPersistedRequestsLength} from '@userActions/PersistedRequests'; import CONST from '@src/CONST'; import type OnyxRequest from '@src/types/onyx/Request'; -import type {OnyxData, PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request'; +import type {AnyRequest, OnyxData, PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import type {ApiCommand, ApiRequestCommandParameters, ApiRequestType, CommandOfType, ReadCommand, SideEffectRequestCommand, WriteCommand} from './types'; import {READ_COMMANDS} from './types'; @@ -171,10 +171,10 @@ function writeWithNoDuplicatesConflictAction = {}, - requestMatcher: RequestMatcher = (request) => request.command === command, + requestMatcher: AnyRequestMatcher = (request) => request.command === command, ): Promise> { const conflictResolver = { - checkAndFixConflictingRequest: (persistedRequests: Array>) => resolveDuplicationConflictAction(persistedRequests, requestMatcher), + checkAndFixConflictingRequest: (persistedRequests: AnyRequest[]) => resolveDuplicationConflictAction(persistedRequests, requestMatcher), }; return write(command, apiCommandParameters, onyxData, conflictResolver); diff --git a/src/libs/actions/RequestConflictUtils.ts b/src/libs/actions/RequestConflictUtils.ts index 92201a6c1196..fffb9968536f 100644 --- a/src/libs/actions/RequestConflictUtils.ts +++ b/src/libs/actions/RequestConflictUtils.ts @@ -8,7 +8,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; import type {AnyRequest, ConflictActionData} from '@src/types/onyx/Request'; -type RequestMatcher = (request: OnyxRequest) => boolean; +type AnyRequestMatcher = (request: AnyRequest) => boolean; const addNewMessage = new Set([WRITE_COMMANDS.ADD_COMMENT, WRITE_COMMANDS.ADD_ATTACHMENT, WRITE_COMMANDS.ADD_TEXT_AND_ATTACHMENT]); @@ -53,7 +53,7 @@ function createUpdateCommentMatcher(reportActionID: string) { * - If no match is found, it suggests adding the request to the list, indicating a 'push' action. * - If a match is found, it suggests updating the existing entry, indicating a 'replace' action at the found index. */ -function resolveDuplicationConflictAction(persistedRequests: Array>, requestMatcher: RequestMatcher): ConflictActionData { +function resolveDuplicationConflictAction(persistedRequests: AnyRequest[], requestMatcher: AnyRequestMatcher): ConflictActionData { const index = persistedRequests.findIndex(requestMatcher); if (index === -1) { return { @@ -237,4 +237,4 @@ export { enablePolicyFeatureCommand, }; -export type {EnablePolicyFeatureCommand, RequestMatcher}; +export type {EnablePolicyFeatureCommand, AnyRequestMatcher}; diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 14e8bf53c197..5556f9d1f6ab 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -1,32 +1,10 @@ -import type {CustomTypeOptions, OnyxKey, OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import type OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; -import type {Merge} from 'type-fest'; +import type {OnyxCollectionKey} from '@src/ONYXKEYS'; import type Response from './Response'; -/** - * Represents type options to configure all Onyx methods. - * It's a combination of predefined options with user-provided options (CustomTypeOptions). - * - * The user-defined options (CustomTypeOptions) are merged into these predefined options. - * In case of conflicting properties, the ones from CustomTypeOptions are prioritized. - */ -type TypeOptions = Merge< - { - /** Represents a string union of all Onyx normal keys. */ - keys: string; - /** Represents a string union of all Onyx collection keys. */ - collectionKeys: string; - /** Represents a Record where each key is an Onyx key and each value is its corresponding Onyx value type. */ - values: Record; - }, - CustomTypeOptions ->; - -/** Represents a string union of all Onyx collection keys. */ -type CollectionKeyBase = TypeOptions['collectionKeys']; - /** Expands an Onyx key, allowing template patterns for collections or enforcing literals otherwise. */ -type ExpandOnyxKeys = TKey extends CollectionKeyBase ? NoInfer<`${TKey}${string}`> : TKey; +type ExpandOnyxKeys = TKey extends OnyxCollectionKey ? NoInfer<`${TKey}${string}`> : TKey; /** * Represents an OnyxUpdate type without strict type checks on the value. @@ -206,12 +184,11 @@ type ConflictActionData = { * Generic base for objects that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -interface RequestConflictResolverBase { +type RequestConflictResolverBase = { /** * A function that checks if a new request conflicts with any existing requests in the queue. */ - checkAndFixConflictingRequest?(persistedRequest: TRequest[]): ConflictActionData; + checkAndFixConflictingRequest?(persistedRequest: Array>): ConflictActionData; /** * A boolean flag to mark a request as persisting into Onyx, if set to true it means when Onyx loads @@ -223,19 +200,20 @@ interface RequestConflictResolverBase { * A boolean flag to mark a request as rollback, if set to true it means the request failed and was added back into the queue. */ isRollback?: boolean; -} +}; /** * An object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ -type RequestConflictResolver = RequestConflictResolverBase>; +type RequestConflictResolver = RequestConflictResolverBase; /** * Loosely typed object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. */ -type AnyRequestConflictResolver = RequestConflictResolverBase; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyRequestConflictResolver = RequestConflictResolverBase; /** Model of requests sent to the API */ type AnyRequest = AnyRequestData & AnyRequestConflictResolver; From 7770ca297bac05b71bc4db7b1aef93a707a2fc08 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Wed, 28 Jan 2026 16:12:59 +0100 Subject: [PATCH 27/34] Improve jsdoc comments --- src/types/onyx/Request.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 5556f9d1f6ab..f5c297ba8751 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -8,7 +8,7 @@ type ExpandOnyxKeys = TKey extends OnyxCollectionKey ? NoI /** * Represents an OnyxUpdate type without strict type checks on the value. - * Useful for contexts where the specific Onyx key is not known ahead of time. + * Useful for contexts where the specific Onyx keys are not known ahead of time. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyOnyxUpdate = { @@ -215,12 +215,12 @@ type RequestConflictResolver = RequestConflictResolverBase // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyRequestConflictResolver = RequestConflictResolverBase; -/** Model of requests sent to the API */ -type AnyRequest = AnyRequestData & AnyRequestConflictResolver; - /** Model of requests sent to the API */ type Request = RequestData & RequestConflictResolver; +/** Loosely typed model of requests sent to the API */ +type AnyRequest = AnyRequestData & AnyRequestConflictResolver; + /** * An object used to describe how a request can be paginated. */ From 23d5156f9696d42da626a231fe305623da22ccb9 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Wed, 28 Jan 2026 16:37:48 +0100 Subject: [PATCH 28/34] Fix generic type usage in mock --- tests/actions/SessionTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/SessionTest.ts b/tests/actions/SessionTest.ts index daadc53ceabc..cbfed8d162e0 100644 --- a/tests/actions/SessionTest.ts +++ b/tests/actions/SessionTest.ts @@ -24,7 +24,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; // We are mocking this method so that we can later test to see if it was called and what arguments it was called with. // We test HttpUtils.xhr() since this means that our API command turned into a network request and isn't only queued. -HttpUtils.xhr = jest.fn() as typeof HttpUtils.xhr; +HttpUtils.xhr = jest.fn>(); // Mocked to ensure push notifications are subscribed/unsubscribed as the session changes jest.mock('@libs/Notification/PushNotification'); From db3fb3e70981b4ba4e3addd92d85064c19bc5fbc Mon Sep 17 00:00:00 2001 From: Olgierd Date: Wed, 28 Jan 2026 16:48:40 +0100 Subject: [PATCH 29/34] Use OnyxData instead of props separately in buildOnyxDataForMoneyRequest --- src/libs/actions/IOU/index.ts | 121 ++++++++++++---------------------- 1 file changed, 43 insertions(+), 78 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index c3db10a4e045..63cb6004e5d2 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1749,46 +1749,11 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); const isMoneyRequestToManagerMcTest = isTestTransactionReport(iou.report); - const optimisticData: Array< - OnyxUpdate< - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.TRANSACTION - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.REPORT_METADATA - | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES - | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT - | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS - | typeof ONYXKEYS.RECENTLY_USED_CURRENCIES - | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS - | typeof ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS - | typeof ONYXKEYS.PERSONAL_DETAILS_LIST - | typeof ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING - | typeof ONYXKEYS.COLLECTION.NEXT_STEP - | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE - | typeof ONYXKEYS.COLLECTION.SNAPSHOT - > - > = []; - const successData: Array< - OnyxUpdate< - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.TRANSACTION - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.REPORT_METADATA - | typeof ONYXKEYS.PERSONAL_DETAILS_LIST - | typeof ONYXKEYS.COLLECTION.SNAPSHOT - | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT - > - > = []; - const failureData: Array< - OnyxUpdate< - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.TRANSACTION - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS - | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE - | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT - > - > = []; + const onyxData: OnyxData = { + optimisticData: [], + successData: [], + failureData: [], + }; let newQuickAction: ValueOf; if (isScanRequest) { @@ -1805,7 +1770,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR const existingTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${existingTransactionThreadReportID}`] ?? null; if (chat.report) { - optimisticData.push({ + onyxData.optimisticData?.push({ // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chat.report.reportID}`, @@ -1821,7 +1786,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR }); } - optimisticData.push( + onyxData.optimisticData?.push( { onyxMethod: shouldCreateNewMoneyRequestReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iou.report.reportID}`, @@ -1873,7 +1838,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR ); if (shouldGenerateTransactionThreadReport) { - optimisticData.push( + onyxData.optimisticData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport?.reportID}`, @@ -1893,7 +1858,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (isNewChatReport) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${chat.report?.reportID}`, value: { @@ -1903,7 +1868,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (shouldCreateNewMoneyRequestReport) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${iou.report?.reportID}`, value: { @@ -1914,7 +1879,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (shouldGenerateTransactionThreadReport && !isEmptyObject(transactionThreadCreatedReportAction)) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport?.reportID}`, value: { @@ -1924,7 +1889,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (policyRecentlyUsed.categories?.length) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${iou.report.policyID}`, value: policyRecentlyUsed.categories, @@ -1932,7 +1897,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (policyRecentlyUsed.currencies?.length) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.RECENTLY_USED_CURRENCIES, value: policyRecentlyUsed.currencies, @@ -1940,7 +1905,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (!isEmptyObject(policyRecentlyUsed.tags)) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iou.report.policyID}`, value: policyRecentlyUsed.tags, @@ -1948,7 +1913,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (policyRecentlyUsed.destinations?.length) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${iou.report.policyID}`, value: policyRecentlyUsed.destinations, @@ -1966,9 +1931,9 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR chatOptimisticParams: chat, testDriveCommentReportActionID, }); - optimisticData.push(...testDriveOptimisticData); - successData.push(...testDriveSuccessData); - failureData.push(...testDriveFailureData); + onyxData.optimisticData?.push(...testDriveOptimisticData); + onyxData.successData?.push(...testDriveSuccessData); + onyxData.failureData?.push(...testDriveFailureData); } let iouAction = iou.action; @@ -1998,7 +1963,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR lastActorAccountID: deprecatedCurrentUserPersonalDetails?.accountID, }; - optimisticData.push( + onyxData.optimisticData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING}`, @@ -2047,12 +2012,12 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR redundantParticipants[accountID] = null; } - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.PERSONAL_DETAILS_LIST, value: personalDetailListAction, }); - successData.push({ + onyxData.successData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.PERSONAL_DETAILS_LIST, value: successPersonalDetailListAction, @@ -2060,14 +2025,14 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (!isEmptyObject(nextStepDeprecated)) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iou.report.reportID}`, value: nextStepDeprecated, }); } if (!isEmptyObject(nextStep)) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iou.report.reportID}`, value: { @@ -2077,7 +2042,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR }, }, }); - successData.push({ + onyxData.successData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iou.report.reportID}`, value: { @@ -2086,7 +2051,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR }, }, }); - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iou.report.reportID}`, value: { @@ -2099,7 +2064,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (isNewChatReport) { - successData.push( + onyxData.successData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chat.report?.reportID}`, @@ -2119,7 +2084,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR ); } - successData.push( + onyxData.successData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iou.report.reportID}`, @@ -2191,7 +2156,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR ); if (shouldGenerateTransactionThreadReport) { - successData.push( + onyxData.successData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport?.reportID}`, @@ -2212,7 +2177,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (shouldGenerateTransactionThreadReport && !isEmptyObject(transactionThreadCreatedReportAction)) { - successData.push({ + onyxData.successData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport?.reportID}`, value: { @@ -2227,7 +2192,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR const errorKey = DateUtils.getMicroseconds(); - failureData.push( + onyxData.failureData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chat.report?.reportID}`, @@ -2287,7 +2252,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR ); if (shouldGenerateTransactionThreadReport) { - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport?.reportID}`, value: { @@ -2302,7 +2267,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (!isOneOnOneSplit) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, value: { @@ -2311,7 +2276,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR isFirstQuickAction: isEmptyObject(quickAction), }, }); - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, value: quickAction ?? null, @@ -2319,7 +2284,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } if (shouldGenerateTransactionThreadReport && !isEmptyObject(transactionThreadCreatedReportAction)) { - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport?.reportID}`, value: { @@ -2342,16 +2307,16 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR if (searchUpdate) { if (searchUpdate.optimisticData) { - optimisticData.push(...searchUpdate.optimisticData); + onyxData.optimisticData?.push(...searchUpdate.optimisticData); } if (searchUpdate.successData) { - successData.push(...searchUpdate.successData); + onyxData.successData?.push(...searchUpdate.successData); } } // We don't need to compute violations unless we're on a paid policy if (!policy || !isPaidGroupPolicy(policy) || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID) { - return {optimisticData, successData, failureData}; + return onyxData; } const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( transaction, @@ -2375,7 +2340,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR hasViolations, isASAPSubmitBetaEnabled, }); - optimisticData.push(violationsOnyxData, { + onyxData.optimisticData?.push(violationsOnyxData, { key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iou.report.reportID}`, onyxMethod: Onyx.METHOD.SET, // buildOptimisticNextStep is used in parallel @@ -2391,7 +2356,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR isASAPSubmitBetaEnabled, }), }); - optimisticData.push({ + onyxData.optimisticData?.push({ key: `${ONYXKEYS.COLLECTION.REPORT}${iou.report.reportID}`, onyxMethod: Onyx.METHOD.MERGE, value: { @@ -2401,7 +2366,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR }, }, }); - successData.push({ + onyxData.successData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iou.report.reportID}`, value: { @@ -2410,12 +2375,12 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR }, }, }); - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`, value: [], }); - failureData.push({ + onyxData.failureData?.push({ key: `${ONYXKEYS.COLLECTION.REPORT}${iou.report.reportID}`, onyxMethod: Onyx.METHOD.MERGE, value: { @@ -2427,7 +2392,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR }); } - return {optimisticData, successData, failureData}; + return onyxData; } type BuildOnyxDataForTrackExpenseParams = { From ba0f6925d5f7a4ca05144e77a11e9f8ca85bd778 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Wed, 28 Jan 2026 16:56:57 +0100 Subject: [PATCH 30/34] Use OnyxData instead of props separately in buildOnyxDataForTrackExpense --- src/libs/actions/IOU/index.ts | 92 +++++++++++++---------------------- 1 file changed, 33 insertions(+), 59 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 63cb6004e5d2..d66472f56af4 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -2442,37 +2442,11 @@ function buildOnyxDataForTrackExpense({ const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); - const optimisticData: Array< - OnyxUpdate< - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.REPORT_METADATA - | typeof ONYXKEYS.COLLECTION.TRANSACTION - | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE - | typeof ONYXKEYS.COLLECTION.SNAPSHOT - | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS - | typeof ONYXKEYS.COLLECTION.REPORT_VIOLATIONS - > - > = []; - const successData: Array< - OnyxUpdate< - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.REPORT_METADATA - | typeof ONYXKEYS.COLLECTION.TRANSACTION - | typeof ONYXKEYS.COLLECTION.SNAPSHOT - > - > = []; - const failureData: Array< - OnyxUpdate< - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.TRANSACTION - | typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS - | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE - | typeof ONYXKEYS.COLLECTION.REPORT_VIOLATIONS - > - > = []; + const onyxData: OnyxData = { + optimisticData: [], + successData: [], + failureData: [], + }; const isSelfDMReport = isSelfDM(chatReport); let newQuickAction: QuickActionName = isSelfDMReport ? CONST.QUICK_ACTIONS.TRACK_MANUAL : CONST.QUICK_ACTIONS.REQUEST_MANUAL; @@ -2484,7 +2458,7 @@ function buildOnyxDataForTrackExpense({ const existingTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${existingTransactionThreadReportID}`] ?? null; if (chatReport) { - optimisticData.push( + onyxData.optimisticData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, @@ -2510,14 +2484,14 @@ function buildOnyxDataForTrackExpense({ ); if (actionableTrackExpenseWhisper && !iouReport) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, value: { [actionableTrackExpenseWhisper.reportActionID]: actionableTrackExpenseWhisper, }, }); - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, value: { @@ -2526,14 +2500,14 @@ function buildOnyxDataForTrackExpense({ lastMessageText: CONST.ACTIONABLE_TRACK_EXPENSE_WHISPER_MESSAGE, }, }); - successData.push({ + onyxData.successData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, value: { [actionableTrackExpenseWhisper.reportActionID]: {pendingAction: null, errors: null}, }, }); - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, value: {[actionableTrackExpenseWhisper.reportActionID]: null}, @@ -2542,7 +2516,7 @@ function buildOnyxDataForTrackExpense({ } if (iouReport) { - optimisticData.push( + onyxData.optimisticData?.push( { onyxMethod: shouldCreateNewMoneyRequestReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, @@ -2580,7 +2554,7 @@ function buildOnyxDataForTrackExpense({ }, ); if (shouldCreateNewMoneyRequestReport) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${iouReport.reportID}`, value: { @@ -2589,7 +2563,7 @@ function buildOnyxDataForTrackExpense({ }); } } else { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, value: { @@ -2598,7 +2572,7 @@ function buildOnyxDataForTrackExpense({ }); } - optimisticData.push( + onyxData.optimisticData?.push( { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, @@ -2622,7 +2596,7 @@ function buildOnyxDataForTrackExpense({ ); if (!isEmptyObject(transactionThreadCreatedReportAction)) { - optimisticData.push({ + onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport?.reportID}`, value: { @@ -2632,7 +2606,7 @@ function buildOnyxDataForTrackExpense({ } if (iouReport) { - successData.push( + onyxData.successData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`, @@ -2668,7 +2642,7 @@ function buildOnyxDataForTrackExpense({ }, ); if (shouldCreateNewMoneyRequestReport) { - successData.push({ + onyxData.successData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${iouReport.reportID}`, value: { @@ -2677,7 +2651,7 @@ function buildOnyxDataForTrackExpense({ }); } } else { - successData.push({ + onyxData.successData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, value: { @@ -2690,7 +2664,7 @@ function buildOnyxDataForTrackExpense({ }); } - successData.push( + onyxData.successData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport?.reportID}`, @@ -2718,7 +2692,7 @@ function buildOnyxDataForTrackExpense({ ); if (!isEmptyObject(transactionThreadCreatedReportAction)) { - successData.push({ + onyxData.successData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport?.reportID}`, value: { @@ -2730,14 +2704,14 @@ function buildOnyxDataForTrackExpense({ }); } - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, value: quickAction ?? null, }); if (iouReport) { - failureData.push( + onyxData.failureData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, @@ -2770,7 +2744,7 @@ function buildOnyxDataForTrackExpense({ }, ); } else { - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, value: { @@ -2781,7 +2755,7 @@ function buildOnyxDataForTrackExpense({ }); } - failureData.push( + onyxData.failureData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, @@ -2814,7 +2788,7 @@ function buildOnyxDataForTrackExpense({ ); if (transactionThreadCreatedReportAction?.reportActionID) { - failureData.push({ + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport?.reportID}`, value: { @@ -2833,16 +2807,16 @@ function buildOnyxDataForTrackExpense({ if (searchUpdate) { if (searchUpdate.optimisticData) { - optimisticData.push(...searchUpdate.optimisticData); + onyxData.optimisticData?.push(...searchUpdate.optimisticData); } if (searchUpdate.successData) { - successData.push(...searchUpdate.successData); + onyxData.successData?.push(...searchUpdate.successData); } } // We don't need to compute violations unless we're on a paid policy if (!policy || !isPaidGroupPolicy(policy) || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID) { - return {optimisticData, successData, failureData}; + return onyxData; } const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( @@ -2856,8 +2830,8 @@ function buildOnyxDataForTrackExpense({ ); if (violationsOnyxData) { - optimisticData.push(violationsOnyxData); - failureData.push({ + onyxData.optimisticData?.push(violationsOnyxData); + onyxData.failureData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`, value: [], @@ -2867,11 +2841,11 @@ function buildOnyxDataForTrackExpense({ // Show field violations only for control policies if (isControlPolicy(policy) && iouReport) { const {optimisticData: fieldViolationsOptimisticData, failureData: fieldViolationsFailureData} = getFieldViolationsOnyxData(iouReport); - optimisticData.push(...(fieldViolationsOptimisticData ?? [])); - failureData.push(...(fieldViolationsFailureData ?? [])); + onyxData.optimisticData?.push(...(fieldViolationsOptimisticData ?? [])); + onyxData.failureData?.push(...(fieldViolationsFailureData ?? [])); } - return {optimisticData, successData, failureData}; + return onyxData; } function getDeleteTrackExpenseInformation( From ded4815718e5b4033843a10caf95fc6c50b17154 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Thu, 29 Jan 2026 13:43:28 +0100 Subject: [PATCH 31/34] Improve jsdoc comments --- src/types/onyx/Request.ts | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index f5c297ba8751..34c2adb41231 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -8,7 +8,9 @@ type ExpandOnyxKeys = TKey extends OnyxCollectionKey ? NoI /** * Represents an OnyxUpdate type without strict type checks on the value. - * Useful for contexts where the specific Onyx keys are not known ahead of time. + * + * This type was created as a solution during the migration away from the large OnyxKey union and is useful for contexts where the specific Onyx keys are not known ahead of time. + * It should only be used in legacy code where providing exact key types would require major restructuring. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyOnyxUpdate = { @@ -48,7 +50,12 @@ type OnyxDataBase = { /** Model of onyx requests sent to the API */ type OnyxData = OnyxDataBase>; -/** Loosely typed model of onyx requests sent to the API */ +/** + * Loosely typed model of onyx requests sent to the API + * + * This type was created as a solution during the migration away from the large OnyxKey union. + * It should only be used in legacy code where providing exact key types would require major restructuring. + */ type AnyOnyxData = OnyxDataBase; /** HTTP request method names */ @@ -97,7 +104,12 @@ type RequestDataBase = { /** Model of overall requests sent to the API */ type RequestData = RequestDataBase & OnyxData; -/** Loosely typed model of overall requests sent to the API */ +/** + * Loosely typed model of overall requests sent to the API + * + * This type was created as a solution during the migration away from the large OnyxKey union. + * It should only be used in legacy code where providing exact key types would require major restructuring. + */ type AnyRequestData = RequestDataBase & AnyOnyxData; /** @@ -211,6 +223,9 @@ type RequestConflictResolver = RequestConflictResolverBase /** * Loosely typed object that describes how a new write request can identify any queued requests that may conflict with or be undone by the new request, * and how to resolve those conflicts. + * + * This type was created as a solution during the migration away from the large OnyxKey union. + * It should only be used in legacy code where providing exact key types would require major restructuring. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyRequestConflictResolver = RequestConflictResolverBase; @@ -218,7 +233,12 @@ type AnyRequestConflictResolver = RequestConflictResolverBase; /** Model of requests sent to the API */ type Request = RequestData & RequestConflictResolver; -/** Loosely typed model of requests sent to the API */ +/** + * Loosely typed model of requests sent to the API + * + * This type was created as a solution during the migration away from the large OnyxKey union and is useful for contexts where the specific Onyx keys are not known ahead of time. + * It should only be used in legacy code where providing exact key types would require major restructuring. + */ type AnyRequest = AnyRequestData & AnyRequestConflictResolver; /** @@ -248,4 +268,4 @@ type PaginatedRequest = Request & }; export default Request; -export type {AnyOnyxUpdate, OnyxData, RequestType, PaginationConfig, PaginatedRequest, RequestConflictResolver, AnyRequestConflictResolver, ConflictActionData, ConflictData, AnyRequest}; +export type {AnyOnyxUpdate, OnyxData, RequestType, PaginationConfig, PaginatedRequest, RequestConflictResolver, ConflictActionData, ConflictData, AnyRequest}; From ea9ca33bbc546eb2a50db2c1d8344821887a1993 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Thu, 29 Jan 2026 14:06:43 +0100 Subject: [PATCH 32/34] Fix the typechecks after the main merge --- src/libs/actions/IOU/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 7486e8ca1532..57c3e757e529 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -395,7 +395,9 @@ type TrackedExpenseReportInformation = { isLinkedTrackedExpenseReportArchived: boolean | undefined; }; type TrackedExpenseParams = { - onyxData?: OnyxData; + onyxData?: OnyxData< + BuildOnyxDataForTrackExpenseKeys | BuildPolicyDataKeys | typeof ONYXKEYS.NVP_RECENT_WAYPOINTS | typeof ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE | typeof ONYXKEYS.GPS_DRAFT_DETAILS + >; reportInformation: TrackedExpenseReportInformation; transactionParams: TrackedExpenseTransactionParams; policyParams: TrackedExpensePolicyParams; @@ -6769,8 +6771,7 @@ function trackExpense(params: CreateTrackExpenseParams) { quickAction, }) ?? {}; const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport?.reportID; - const onyxData: OnyxData = - trackExpenseInformationOnyxData; + const onyxData: TrackedExpenseParams['onyxData'] = trackExpenseInformationOnyxData; const recentServerValidatedWaypoints = recentWaypoints.filter((item) => !item.pendingAction); onyxData?.failureData?.push({ @@ -7625,7 +7626,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest : receipt; let parameters: CreateDistanceRequestParams; - let onyxData: OnyxData; + let onyxData: OnyxData; const sanitizedWaypoints = !isManualDistanceRequest ? sanitizeRecentWaypoints(validWaypoints) : null; if (iouType === CONST.IOU.TYPE.SPLIT) { const { From 767bf99f3eae8c38f13cb28efcd459bf008982c8 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Tue, 3 Feb 2026 13:35:59 +0100 Subject: [PATCH 33/34] Add comment explaining any usage --- src/libs/PusherUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/PusherUtils.ts b/src/libs/PusherUtils.ts index 6a1dc9dfe554..f6525ce0bd30 100644 --- a/src/libs/PusherUtils.ts +++ b/src/libs/PusherUtils.ts @@ -11,6 +11,8 @@ import type {PingPongEvent} from './Pusher/types'; type Callback = (data: Array>) => Promise; // Keeps track of all the callbacks that need triggered for each event type +// Using `any` because callbacks can be registered with different key types dynamically. +// 'any' was introduced during migration away from OnyxKey union for TypeScript performance improvement // eslint-disable-next-line @typescript-eslint/no-explicit-any const multiEventCallbackMapping: Record> = {}; From 5bc93f3a69b77d9475d005a25dda40f2248708e2 Mon Sep 17 00:00:00 2001 From: Olgierd Date: Tue, 3 Feb 2026 14:24:51 +0100 Subject: [PATCH 34/34] Adapt changes to the current main --- src/libs/actions/IOU/index.ts | 41 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 293137a4b1a8..ff2728984f10 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -370,7 +370,7 @@ type TrackExpenseInformation = { actionableWhisperReportActionIDParam?: string; optimisticReportID: string | undefined; optimisticReportActionID: string | undefined; - onyxData: OnyxData; + onyxData: OnyxData; }; type TrackedExpenseTransactionParams = Omit & { @@ -402,7 +402,12 @@ type TrackedExpenseReportInformation = { }; type TrackedExpenseParams = { onyxData?: OnyxData< - BuildOnyxDataForTrackExpenseKeys | BuildPolicyDataKeys | typeof ONYXKEYS.NVP_RECENT_WAYPOINTS | typeof ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE | typeof ONYXKEYS.GPS_DRAFT_DETAILS + | BuildOnyxDataForTrackExpenseKeys + | BuildPolicyDataKeys + | typeof ONYXKEYS.NVP_RECENT_WAYPOINTS + | typeof ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE + | typeof ONYXKEYS.GPS_DRAFT_DETAILS + | typeof ONYXKEYS.SELF_DM_REPORT_ID >; reportInformation: TrackedExpenseReportInformation; transactionParams: TrackedExpenseTransactionParams; @@ -3927,9 +3932,11 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T gpsCoordinates, } = transactionParams; - const optimisticData: Array> = []; - const successData: Array> = []; - const failureData: Array> = []; + const onyxData: OnyxData = { + optimisticData: [], + successData: [], + failureData: [], + }; const isPolicyExpenseChat = participant.isPolicyExpenseChat; @@ -3952,7 +3959,7 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T optimisticReportActionID = selfDMCreatedReportAction.reportActionID; chatReport = selfDMReport; - optimisticData.push( + onyxData.optimisticData?.push( { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticReportID}`, @@ -3981,7 +3988,7 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T }, }, ); - successData.push( + onyxData.successData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticReportID}`, @@ -4006,7 +4013,7 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T }, }, ); - failureData.push( + onyxData.failureData?.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticReportID}`, @@ -4044,9 +4051,9 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T activePolicyID, }); createdWorkspaceParams = workspaceData.params; - optimisticData.push(...(workspaceData.optimisticData ?? [])); - successData.push(...(workspaceData.successData ?? [])); - failureData.push(...(workspaceData.failureData ?? [])); + onyxData.optimisticData?.push(...(workspaceData.optimisticData ?? [])); + onyxData.successData?.push(...(workspaceData.successData ?? [])); + onyxData.failureData?.push(...(workspaceData.failureData ?? [])); } // STEP 2: If not in the self-DM flow, we need to use the expense report. @@ -4204,9 +4211,9 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T quickAction, }); - optimisticData.push(...(trackExpenseOnyxData.optimisticData ?? [])); - successData.push(...(trackExpenseOnyxData.successData ?? [])); - failureData.push(...(trackExpenseOnyxData.failureData ?? [])); + onyxData.optimisticData?.push(...(trackExpenseOnyxData.optimisticData ?? [])); + onyxData.successData?.push(...(trackExpenseOnyxData.successData ?? [])); + onyxData.failureData?.push(...(trackExpenseOnyxData.failureData ?? [])); return { createdWorkspaceParams, @@ -4221,11 +4228,7 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T actionableWhisperReportActionIDParam: actionableTrackExpenseWhisper?.reportActionID, optimisticReportID, optimisticReportActionID, - onyxData: { - optimisticData, - successData, - failureData, - }, + onyxData, }; }