From 70f769df4ad8d5527f7032ec2fa84719da0ec796 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 30 Oct 2025 10:38:19 +0100 Subject: [PATCH 01/11] Refactor OnyxUpdate type to bring back type safety --- lib/types.ts | 71 ++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/lib/types.ts b/lib/types.ts index f037ef7c3..685887a21 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -326,44 +326,49 @@ type OnyxMergeCollectionInput = Collection< type OnyxMethodMap = typeof OnyxUtils.METHOD; -// Maps onyx methods to their corresponding value types -type OnyxMethodValueMap = { - [OnyxUtils.METHOD.SET]: { - key: OnyxKey; - value: OnyxSetInput; - }; - [OnyxUtils.METHOD.MULTI_SET]: { - key: OnyxKey; - value: OnyxMultiSetInput; - }; - [OnyxUtils.METHOD.MERGE]: { - key: OnyxKey; - value: OnyxMergeInput; - }; - [OnyxUtils.METHOD.CLEAR]: { - key: OnyxKey; - value?: undefined; - }; - [OnyxUtils.METHOD.MERGE_COLLECTION]: { - key: CollectionKeyBase; - value: OnyxMergeCollectionInput; - }; - [OnyxUtils.METHOD.SET_COLLECTION]: { - key: CollectionKeyBase; - value: OnyxMergeCollectionInput; - }; -}; - /** * OnyxUpdate type includes all onyx methods used in OnyxMethodValueMap. * If a new method is added to OnyxUtils.METHOD constant, it must be added to OnyxMethodValueMap type. * Otherwise it will show static type errors. */ -type OnyxUpdate = { - [Method in OnyxMethod]: { - onyxMethod: Method; - } & OnyxMethodValueMap[Method]; -}[OnyxMethod]; +type OnyxUpdate = + // ⚠️ DO NOT CHANGE THIS TYPE, UNLESS YOU KNOW WHAT YOU ARE DOING. ⚠️ + | { + [TKey in OnyxKey]: + | { + onyxMethod: typeof OnyxUtils.METHOD.SET; + key: TKey; + value: OnyxSetInput; + } + | { + onyxMethod: typeof OnyxUtils.METHOD.MULTI_SET; + key: TKey; + value: OnyxMultiSetInput; + } + | { + onyxMethod: typeof OnyxUtils.METHOD.MERGE; + key: TKey; + value: OnyxMergeInput; + } + | { + onyxMethod: typeof OnyxUtils.METHOD.CLEAR; + key: TKey; + value?: undefined; + }; + }[OnyxKey] + | { + [TKey in CollectionKeyBase]: + | { + onyxMethod: typeof OnyxUtils.METHOD.MERGE_COLLECTION; + key: TKey; + value: OnyxMergeCollectionInput; + } + | { + onyxMethod: typeof OnyxUtils.METHOD.SET_COLLECTION; + key: TKey; + value: OnyxMergeCollectionInput; + }; + }[CollectionKeyBase]; /** * Represents the options used in `Onyx.set()` method. From 00d9891c3fa05f6856d9379bdff921c7982f0bd7 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Oct 2025 13:27:41 +0100 Subject: [PATCH 02/11] add type tests to onyx! --- .eslintignore | 3 ++- .github/workflows/test.yml | 2 ++ package.json | 1 + tests/types/test.ts | 52 ++++++++++++++++++++++++++++++++++++++ tsconfig.json | 2 +- tsconfig.test.json | 6 +++++ 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/types/test.ts create mode 100644 tsconfig.test.json diff --git a/.eslintignore b/.eslintignore index d1b71675e..96d1873e5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ *.d.ts dist node_modules -*.config.js \ No newline at end of file +*.config.js +tests/types/**/*.ts \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa13e65f1..de38cf64a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,3 +30,5 @@ jobs: - run: npm run test env: CI: true + + - run: npm run test:types \ No newline at end of file diff --git a/package.json b/package.json index f30679638..27482a2bf 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "lint": "eslint .", "typecheck": "tsc --noEmit", "test": "jest", + "test:types": "npm run build && tsc --noEmit --project tsconfig.test.json", "perf-test": "npx reassure", "build": "tsc -p tsconfig.build.json", "build:watch": "nodemon --watch lib --ext js,json,ts,tsx --exec \"npm run build && npm pack\"", diff --git a/tests/types/test.ts b/tests/types/test.ts new file mode 100644 index 000000000..2522bc6b8 --- /dev/null +++ b/tests/types/test.ts @@ -0,0 +1,52 @@ +import type {OnyxUpdate} from '../../dist/types'; + +const ONYX_KEYS = { + TEST_KEY: 'test', + COLLECTION: { + TEST_KEY: 'test_', + }, +}; + +type OnyxValues = { + [ONYX_KEYS.TEST_KEY]: string; +}; + +type OnyxCollectionValues = { + [ONYX_KEYS.COLLECTION.TEST_KEY]: {str: string}; +}; + +declare module '../../dist/types' { + interface CustomTypeOptions { + keys: keyof OnyxValues; + collectionKeys: keyof OnyxCollectionValues; + values: OnyxValues; + } +} + +const onyxUpdate: OnyxUpdate = { + onyxMethod: 'set', + key: ONYX_KEYS.TEST_KEY, + value: 'string', +}; + +const onyxUpdateError: OnyxUpdate = { + onyxMethod: 'set', + key: 'string', + // @ts-expect-error TEST_KEY is a string, not a number + value: 2, +}; + +const onyxUpdateCollection: OnyxUpdate = { + onyxMethod: 'mergecollection', + key: ONYX_KEYS.COLLECTION.TEST_KEY, + value: { + [ONYX_KEYS.COLLECTION.TEST_KEY]: {str: 'test'}, + }, +}; + +const onyxUpdateCollectionError: OnyxUpdate = { + onyxMethod: 'mergecollection', + key: ONYX_KEYS.COLLECTION.TEST_KEY, + // @ts-expect-error TEST_KEY is an object, not a number + value: 2, +}; diff --git a/tsconfig.json b/tsconfig.json index f8b58a305..d920b8700 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,5 +17,5 @@ "declaration": true, "outDir": "./dist" }, - "exclude": ["**/node_modules/**/*", "**/dist/**/*"] + "exclude": ["**/node_modules/**/*", "**/dist/**/*", "tests/types/**/*.ts"] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 000000000..f62691ceb --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "include": ["tests/types/**/*.ts"], + "exclude": ["**/node_modules/**/*", "lib/**/*"] + } \ No newline at end of file From ffb0db8e5a4a12abc7265356c7b03104ea60a8f9 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Oct 2025 13:33:53 +0100 Subject: [PATCH 03/11] Fix GH workflows --- jest.config.js | 2 +- tsconfig.test.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jest.config.js b/jest.config.js index d904a33c4..947c4caba 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,7 +4,7 @@ module.exports = { transform: { '\\.[jt]sx?$': 'babel-jest', }, - testPathIgnorePatterns: ['/node_modules/', '/tests/unit/mocks/', '/tests/e2e/'], + testPathIgnorePatterns: ['/node_modules/', '/tests/unit/mocks/', '/tests/e2e/', '/tests/types/'], testMatch: ['**/tests/unit/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], globals: { __DEV__: true, diff --git a/tsconfig.test.json b/tsconfig.test.json index f62691ceb..6c4f1b954 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -3,4 +3,4 @@ "extends": "./tsconfig.json", "include": ["tests/types/**/*.ts"], "exclude": ["**/node_modules/**/*", "lib/**/*"] - } \ No newline at end of file +} From 00c9e40272faca984eba4be5c1e052539ce2564e Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Oct 2025 17:25:29 +0100 Subject: [PATCH 04/11] Fix type safety, second try --- lib/Onyx.ts | 12 +++---- lib/OnyxUtils.ts | 8 ++--- lib/types.ts | 15 ++++----- tests/types/test.ts | 56 ++++++++++++++++++++++++++++---- tests/utils/GenericCollection.ts | 2 +- 5 files changed, 66 insertions(+), 27 deletions(-) diff --git a/lib/Onyx.ts b/lib/Onyx.ts index 6752e82e4..c363cdda4 100644 --- a/lib/Onyx.ts +++ b/lib/Onyx.ts @@ -374,7 +374,7 @@ function merge(key: TKey, changes: OnyxMergeInput): * @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT` * @param collection Object collection keyed by individual collection member keys and values */ -function mergeCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { +function mergeCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { return OnyxUtils.mergeCollectionWithPatches(collectionKey, collection, undefined, true); } @@ -545,7 +545,7 @@ function update(data: OnyxUpdate[]): Promise { [OnyxUtils.METHOD.SET]: enqueueSetOperation, [OnyxUtils.METHOD.MERGE]: enqueueMergeOperation, [OnyxUtils.METHOD.MERGE_COLLECTION]: () => { - const collection = value as Collection; + const collection = value as Collection; if (!OnyxUtils.isValidNonEmptyCollectionForMerge(collection)) { Logger.logInfo('mergeCollection enqueued within update() with invalid or empty value. Skipping this operation.'); return; @@ -558,7 +558,7 @@ function update(data: OnyxUpdate[]): Promise { collectionKeys.forEach((collectionKey) => enqueueMergeOperation(collectionKey, mergedCollection[collectionKey])); } }, - [OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as Collection)), + [OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as Collection)), [OnyxUtils.METHOD.MULTI_SET]: (k, v) => Object.entries(v as Partial).forEach(([entryKey, entryValue]) => enqueueSetOperation(entryKey, entryValue)), [OnyxUtils.METHOD.CLEAR]: () => { clearPromise = clear(); @@ -611,14 +611,14 @@ function update(data: OnyxUpdate[]): Promise { promises.push(() => OnyxUtils.mergeCollectionWithPatches( collectionKey, - batchedCollectionUpdates.merge as Collection, + batchedCollectionUpdates.merge as Collection, batchedCollectionUpdates.mergeReplaceNullPatches, true, ), ); } if (!utils.isEmptyObject(batchedCollectionUpdates.set)) { - promises.push(() => OnyxUtils.partialSetCollection(collectionKey, batchedCollectionUpdates.set as Collection)); + promises.push(() => OnyxUtils.partialSetCollection(collectionKey, batchedCollectionUpdates.set as Collection)); } }); @@ -655,7 +655,7 @@ function update(data: OnyxUpdate[]): Promise { * @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT` * @param collection Object collection keyed by individual collection member keys and values */ -function setCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { +function setCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { let resultCollection: OnyxInputKeyValueMapping = collection; let resultCollectionKeys = Object.keys(resultCollection); diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 88d3b1c3a..d0a3a0756 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -1035,7 +1035,7 @@ function initializeWithDefaultKeyStates(): Promise { /** * Validate the collection is not empty and has a correct type before applying mergeCollection() */ -function isValidNonEmptyCollectionForMerge(collection: OnyxMergeCollectionInput): boolean { +function isValidNonEmptyCollectionForMerge(collection: OnyxMergeCollectionInput): boolean { return typeof collection === 'object' && !Array.isArray(collection) && !utils.isEmptyObject(collection); } @@ -1241,9 +1241,9 @@ function updateSnapshots(data: OnyxUpdate[], mergeFn: typeof Onyx.merge): Array< * @param mergeReplaceNullPatches Record where the key is a collection member key and the value is a list of * tuples that we'll use to replace the nested objects of that collection member record with something else. */ -function mergeCollectionWithPatches( +function mergeCollectionWithPatches( collectionKey: TKey, - collection: OnyxMergeCollectionInput, + collection: OnyxMergeCollectionInput, mergeReplaceNullPatches?: MultiMergeReplaceNullPatches, isProcessingCollectionUpdate = false, ): Promise { @@ -1366,7 +1366,7 @@ function mergeCollectionWithPatches( * @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT` * @param collection Object collection keyed by individual collection member keys and values */ -function partialSetCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { +function partialSetCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { let resultCollection: OnyxInputKeyValueMapping = collection; let resultCollectionKeys = Object.keys(resultCollection); diff --git a/lib/types.ts b/lib/types.ts index 685887a21..0f8e696d1 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,5 +1,4 @@ import type {Merge} from 'type-fest'; -import type {BuiltIns} from 'type-fest/source/internal'; import type OnyxUtils from './OnyxUtils'; import type {OnyxMethod} from './OnyxUtils'; import type {FastMergeReplaceNullPatch} from './utils'; @@ -157,6 +156,10 @@ type OnyxValue = string extends TKey ? unknown : TKey exte /** Utility type to extract `TOnyxValue` from `OnyxCollection` */ type ExtractOnyxCollectionValue = TOnyxCollection extends NonNullable> ? U : never; +type Primitive = null | undefined | string | number | boolean | symbol | bigint; + +type BuiltIns = Primitive | void | Date | RegExp; + type NonTransformableTypes = | BuiltIns // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -205,13 +208,7 @@ type NullishObjectDeep = { * Also, the `TMap` type is inferred automatically in `mergeCollection()` method and represents * the object of collection keys/values specified in the second parameter of the method. */ -type Collection = { - [MapK in keyof TMap]: MapK extends `${TKey}${string}` - ? MapK extends `${TKey}` - ? never // forbids empty id - : TValue - : never; -}; +type Collection = Record<`${TKey}${string}`, TValue> & {[P in TKey]?: never}; /** Represents the base options used in `Onyx.connect()` method. */ // NOTE: Any changes to this type like adding or removing options must be accounted in OnyxConnectionManager's `generateConnectionID()` method! @@ -322,7 +319,7 @@ type OnyxMergeInput = OnyxInput; /** * This represents the value that can be passed to `Onyx.merge` and to `Onyx.update` with the method "MERGE" */ -type OnyxMergeCollectionInput = Collection>, TMap>; +type OnyxMergeCollectionInput = Collection>>; type OnyxMethodMap = typeof OnyxUtils.METHOD; diff --git a/tests/types/test.ts b/tests/types/test.ts index 2522bc6b8..d46498c49 100644 --- a/tests/types/test.ts +++ b/tests/types/test.ts @@ -1,11 +1,12 @@ -import type {OnyxUpdate} from '../../dist/types'; +import Onyx from '../../dist/Onyx'; +import type {Collection, KeyValueMapping, NullishDeep, OnyxInput, OnyxUpdate} from '../../dist/types'; const ONYX_KEYS = { TEST_KEY: 'test', COLLECTION: { TEST_KEY: 'test_', }, -}; +} as const; type OnyxValues = { [ONYX_KEYS.TEST_KEY]: string; @@ -19,7 +20,7 @@ declare module '../../dist/types' { interface CustomTypeOptions { keys: keyof OnyxValues; collectionKeys: keyof OnyxCollectionValues; - values: OnyxValues; + values: OnyxValues & OnyxCollectionValues; } } @@ -31,7 +32,7 @@ const onyxUpdate: OnyxUpdate = { const onyxUpdateError: OnyxUpdate = { onyxMethod: 'set', - key: 'string', + key: ONYX_KEYS.TEST_KEY, // @ts-expect-error TEST_KEY is a string, not a number value: 2, }; @@ -40,13 +41,54 @@ const onyxUpdateCollection: OnyxUpdate = { onyxMethod: 'mergecollection', key: ONYX_KEYS.COLLECTION.TEST_KEY, value: { - [ONYX_KEYS.COLLECTION.TEST_KEY]: {str: 'test'}, + [`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: { + str: 'test', + }, + [`${ONYX_KEYS.COLLECTION.TEST_KEY}2`]: { + str: 'test2', + }, }, }; +// @ts-expect-error COLLECTION.TEST_KEY is an object, not a number const onyxUpdateCollectionError: OnyxUpdate = { onyxMethod: 'mergecollection', key: ONYX_KEYS.COLLECTION.TEST_KEY, - // @ts-expect-error TEST_KEY is an object, not a number - value: 2, + value: { + [`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: 2, + }, +}; + +const onyxUpdateCollectionError2: OnyxUpdate = { + onyxMethod: 'mergecollection', + key: ONYX_KEYS.COLLECTION.TEST_KEY, + value: { + [`${ONYX_KEYS.COLLECTION.TEST_KEY}2`]: { + // @ts-expect-error nonExistingKey is not a valid key + nonExistingKey: 'test2', + }, + }, +}; + +// @ts-expect-error COLLECTION.TEST_KEY is invalid key, it is missing the suffix +const onyxUpdateCollectionError3: OnyxUpdate = { + onyxMethod: 'mergecollection', + key: ONYX_KEYS.COLLECTION.TEST_KEY, + value: { + [ONYX_KEYS.COLLECTION.TEST_KEY]: { + str: 'test2', + }, + }, }; + +Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, { + // @ts-expect-error COLLECTION.TEST_KEY is invalid key, it is missing the suffix + test_: { + str: 'test3', + }, + test_2: { + str: 'test4', + }, + // @ts-expect-error COLLECTION.TEST_KEY is object, not a number + test_3: 2, +}); diff --git a/tests/utils/GenericCollection.ts b/tests/utils/GenericCollection.ts index b00fce6dc..4c355433b 100644 --- a/tests/utils/GenericCollection.ts +++ b/tests/utils/GenericCollection.ts @@ -1,5 +1,5 @@ import type {Collection} from '../../lib/types'; -type GenericCollection = Collection; +type GenericCollection = Collection; export default GenericCollection; From 2e98c2c0b7f7f2140720f1a48d0f51d37acdcaf5 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Oct 2025 17:27:45 +0100 Subject: [PATCH 05/11] Split tests --- tests/types/{test.ts => OnyxUpdate.ts} | 39 ++------------------------ tests/types/augmentation.ts | 26 +++++++++++++++++ tests/types/mergeCollection.ts | 26 +++++++++++++++++ 3 files changed, 54 insertions(+), 37 deletions(-) rename tests/types/{test.ts => OnyxUpdate.ts} (61%) create mode 100644 tests/types/augmentation.ts create mode 100644 tests/types/mergeCollection.ts diff --git a/tests/types/test.ts b/tests/types/OnyxUpdate.ts similarity index 61% rename from tests/types/test.ts rename to tests/types/OnyxUpdate.ts index d46498c49..cf6b5d0d4 100644 --- a/tests/types/test.ts +++ b/tests/types/OnyxUpdate.ts @@ -1,28 +1,5 @@ -import Onyx from '../../dist/Onyx'; -import type {Collection, KeyValueMapping, NullishDeep, OnyxInput, OnyxUpdate} from '../../dist/types'; - -const ONYX_KEYS = { - TEST_KEY: 'test', - COLLECTION: { - TEST_KEY: 'test_', - }, -} as const; - -type OnyxValues = { - [ONYX_KEYS.TEST_KEY]: string; -}; - -type OnyxCollectionValues = { - [ONYX_KEYS.COLLECTION.TEST_KEY]: {str: string}; -}; - -declare module '../../dist/types' { - interface CustomTypeOptions { - keys: keyof OnyxValues; - collectionKeys: keyof OnyxCollectionValues; - values: OnyxValues & OnyxCollectionValues; - } -} +import type {OnyxUpdate} from '../../dist/types'; +import ONYX_KEYS from './augmentation'; const onyxUpdate: OnyxUpdate = { onyxMethod: 'set', @@ -80,15 +57,3 @@ const onyxUpdateCollectionError3: OnyxUpdate = { }, }, }; - -Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, { - // @ts-expect-error COLLECTION.TEST_KEY is invalid key, it is missing the suffix - test_: { - str: 'test3', - }, - test_2: { - str: 'test4', - }, - // @ts-expect-error COLLECTION.TEST_KEY is object, not a number - test_3: 2, -}); diff --git a/tests/types/augmentation.ts b/tests/types/augmentation.ts new file mode 100644 index 000000000..df3bcb9b3 --- /dev/null +++ b/tests/types/augmentation.ts @@ -0,0 +1,26 @@ +import '../../dist/types'; + +const ONYX_KEYS = { + TEST_KEY: 'test', + COLLECTION: { + TEST_KEY: 'test_', + }, +} as const; + +type OnyxValues = { + [ONYX_KEYS.TEST_KEY]: string; +}; + +type OnyxCollectionValues = { + [ONYX_KEYS.COLLECTION.TEST_KEY]: {str: string}; +}; + +declare module '../../dist/types' { + interface CustomTypeOptions { + keys: keyof OnyxValues; + collectionKeys: keyof OnyxCollectionValues; + values: OnyxValues & OnyxCollectionValues; + } +} + +export default ONYX_KEYS; diff --git a/tests/types/mergeCollection.ts b/tests/types/mergeCollection.ts new file mode 100644 index 000000000..d2af85c59 --- /dev/null +++ b/tests/types/mergeCollection.ts @@ -0,0 +1,26 @@ +import Onyx from '../../dist/Onyx'; +import ONYX_KEYS from './augmentation'; + +Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, { + test_1: { + str: 'test3', + }, + test_2: { + str: 'test4', + }, + test_3: { + str: 'test5', + }, +}); + +Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, { + // @ts-expect-error COLLECTION.TEST_KEY is invalid key, it is missing the suffix + test_: { + str: 'test3', + }, + test_2: { + str: 'test4', + }, + // @ts-expect-error COLLECTION.TEST_KEY is object, not a number + test_3: 2, +}); From cb08498509d76874b7036aad472909e43b310e44 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Oct 2025 17:33:09 +0100 Subject: [PATCH 06/11] Fix type errors while building onyx --- lib/Onyx.ts | 6 +++--- lib/OnyxUtils.ts | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Onyx.ts b/lib/Onyx.ts index c363cdda4..6a4e26839 100644 --- a/lib/Onyx.ts +++ b/lib/Onyx.ts @@ -558,7 +558,7 @@ function update(data: OnyxUpdate[]): Promise { collectionKeys.forEach((collectionKey) => enqueueMergeOperation(collectionKey, mergedCollection[collectionKey])); } }, - [OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as Collection)), + [OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as OnyxMergeCollectionInput)), [OnyxUtils.METHOD.MULTI_SET]: (k, v) => Object.entries(v as Partial).forEach(([entryKey, entryValue]) => enqueueSetOperation(entryKey, entryValue)), [OnyxUtils.METHOD.CLEAR]: () => { clearPromise = clear(); @@ -611,14 +611,14 @@ function update(data: OnyxUpdate[]): Promise { promises.push(() => OnyxUtils.mergeCollectionWithPatches( collectionKey, - batchedCollectionUpdates.merge as Collection, + batchedCollectionUpdates.merge as OnyxMergeCollectionInput, batchedCollectionUpdates.mergeReplaceNullPatches, true, ), ); } if (!utils.isEmptyObject(batchedCollectionUpdates.set)) { - promises.push(() => OnyxUtils.partialSetCollection(collectionKey, batchedCollectionUpdates.set as Collection)); + promises.push(() => OnyxUtils.partialSetCollection(collectionKey, batchedCollectionUpdates.set as OnyxMergeCollectionInput)); } }); diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index d0a3a0756..d52739df5 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -29,6 +29,7 @@ import type { OnyxUpdate, OnyxValue, Selector, + Collection, } from './types'; import type {FastMergeOptions, FastMergeResult} from './utils'; import utils from './utils'; @@ -1035,7 +1036,7 @@ function initializeWithDefaultKeyStates(): Promise { /** * Validate the collection is not empty and has a correct type before applying mergeCollection() */ -function isValidNonEmptyCollectionForMerge(collection: OnyxMergeCollectionInput): boolean { +function isValidNonEmptyCollectionForMerge(collection: Collection): boolean { return typeof collection === 'object' && !Array.isArray(collection) && !utils.isEmptyObject(collection); } @@ -1350,7 +1351,7 @@ function mergeCollectionWithPatches( }); return Promise.all(promises) - .catch((error) => evictStorageAndRetry(error, mergeCollectionWithPatches, collectionKey, resultCollection)) + .catch((error) => evictStorageAndRetry(error, mergeCollectionWithPatches, collectionKey, resultCollection as OnyxMergeCollectionInput)) .then(() => { sendActionToDevTools(METHOD.MERGE_COLLECTION, undefined, resultCollection); return promiseUpdate; From 2979eceba86d3efe046e025b0165b2bc03c7142f Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Oct 2025 17:42:47 +0100 Subject: [PATCH 07/11] Refactor GenericCollection type to OnyxMergeCollectionInput --- tests/unit/DevToolsTest.ts | 4 +-- tests/unit/OnyxConnectionManagerTest.ts | 4 +-- tests/unit/onyxMultiMergeWebStorageTest.ts | 12 +++---- tests/unit/onyxTest.ts | 37 +++++++++++----------- tests/unit/onyxUtilsTest.ts | 3 +- tests/utils/GenericCollection.ts | 4 +-- 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/tests/unit/DevToolsTest.ts b/tests/unit/DevToolsTest.ts index 425d14a0e..46b86adc3 100644 --- a/tests/unit/DevToolsTest.ts +++ b/tests/unit/DevToolsTest.ts @@ -25,10 +25,10 @@ const initialKeyStates = { [ONYX_KEYS.OBJECT_KEY]: {id: 42}, }; -const exampleCollection: GenericCollection = { +const exampleCollection = { [`${ONYX_KEYS.COLLECTION.NUM_KEY}1`]: 1, [`${ONYX_KEYS.COLLECTION.NUM_KEY}2`]: 2, -}; +} as GenericCollection; const exampleObject = {name: 'Pedro'}; diff --git a/tests/unit/OnyxConnectionManagerTest.ts b/tests/unit/OnyxConnectionManagerTest.ts index 1014eef8f..e0968fade 100644 --- a/tests/unit/OnyxConnectionManagerTest.ts +++ b/tests/unit/OnyxConnectionManagerTest.ts @@ -117,10 +117,10 @@ describe('OnyxConnectionManager', () => { it('should connect two times to the same key but with different options, and fire the callbacks differently', async () => { const obj1 = {id: 'entry1_id', name: 'entry1_name'}; const obj2 = {id: 'entry2_id', name: 'entry2_name'}; - const collection: GenericCollection = { + const collection = { [`${ONYXKEYS.COLLECTION.TEST_KEY}entry1`]: obj1, [`${ONYXKEYS.COLLECTION.TEST_KEY}entry2`]: obj2, - }; + } as GenericCollection; await StorageMock.multiSet([ [`${ONYXKEYS.COLLECTION.TEST_KEY}entry1`, obj1], [`${ONYXKEYS.COLLECTION.TEST_KEY}entry2`, obj2], diff --git a/tests/unit/onyxMultiMergeWebStorageTest.ts b/tests/unit/onyxMultiMergeWebStorageTest.ts index c3f939c71..9375b9f40 100644 --- a/tests/unit/onyxMultiMergeWebStorageTest.ts +++ b/tests/unit/onyxMultiMergeWebStorageTest.ts @@ -3,7 +3,7 @@ import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import Storage from '../../lib/storage'; import type MockedStorage from '../../lib/storage/__mocks__'; import type OnyxInstance from '../../lib/Onyx'; -import type GenericCollection from '../utils/GenericCollection'; +import type {OnyxKey, OnyxMergeCollectionInput} from '../../lib'; const StorageMock = Storage as unknown as typeof MockedStorage; @@ -55,7 +55,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { test_1: additionalDataOne, test_2: additionalDataOne, test_3: additionalDataOne, - } as GenericCollection); + } as unknown as OnyxMergeCollectionInput); // And call again consecutively with different data const additionalDataTwo = {d: 'd', e: [2]}; @@ -63,7 +63,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { test_1: additionalDataTwo, test_2: additionalDataTwo, test_3: additionalDataTwo, - } as GenericCollection); + } as unknown as OnyxMergeCollectionInput); return waitForPromisesToResolve().then(() => { const finalObject = { @@ -103,7 +103,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { test_1: data, test_2: data, test_3: data, - } as GenericCollection); + } as unknown as OnyxMergeCollectionInput); return waitForPromisesToResolve() .then(() => { @@ -125,7 +125,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { test_1: additionalData, test_2: additionalData, test_3: additionalData, - } as GenericCollection); + } as unknown as OnyxMergeCollectionInput); return waitForPromisesToResolve(); }) @@ -164,7 +164,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { // 2nd call Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, { test_1: {d: 'd', e: 'e'}, - } as GenericCollection); + } as unknown as OnyxMergeCollectionInput); // Last call Onyx.merge('test_1', {f: 'f'}); diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 04fdd0ac8..472d6069b 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -5,7 +5,7 @@ import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import OnyxUtils from '../../lib/OnyxUtils'; import type OnyxCache from '../../lib/OnyxCache'; import StorageMock from '../../lib/storage'; -import type {OnyxCollection, OnyxUpdate} from '../../lib/types'; +import type {OnyxCollection, OnyxKey, OnyxMergeCollectionInput, OnyxUpdate} from '../../lib/types'; import type {GenericDeepRecord} from '../types'; import type GenericCollection from '../utils/GenericCollection'; import type {Connection} from '../../lib/OnyxConnectionManager'; @@ -624,7 +624,7 @@ describe('Onyx', () => { ID: 345, value: 'three', }, - } as GenericCollection) + } as unknown as OnyxMergeCollectionInput) .then(() => // 2 key values to update and 2 new keys to add. // MergeCollection will perform a mix of multiSet and multiMerge @@ -646,7 +646,7 @@ describe('Onyx', () => { ID: 567, value: 'one', }, - } as GenericCollection), + } as unknown as OnyxMergeCollectionInput), ) .then(() => { // 3 items on the first mergeCollection + 4 items the next mergeCollection @@ -674,7 +674,7 @@ describe('Onyx', () => { callback: (data, key) => (valuesReceived[key] = data), }); - return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {test_1: {ID: 123}, notMyTest: {beep: 'boop'}} as GenericCollection).then(() => { + return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {test_1: {ID: 123}, notMyTest: {beep: 'boop'}} as unknown as OnyxMergeCollectionInput).then(() => { expect(valuesReceived).toEqual({}); }); }); @@ -705,7 +705,7 @@ describe('Onyx', () => { ID: 234, value: 'two', }, - } as GenericCollection), + } as unknown as OnyxMergeCollectionInput), ) .then(() => { expect(valuesReceived).toEqual({ @@ -899,7 +899,7 @@ describe('Onyx', () => { ID: 345, value: 'three', }, - }, + } as unknown as OnyxMergeCollectionInput, }, ]); }) @@ -1058,7 +1058,7 @@ describe('Onyx', () => { }, }; - return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_CONNECT_COLLECTION, initialCollectionData as GenericCollection) + return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_CONNECT_COLLECTION, initialCollectionData as unknown as OnyxMergeCollectionInput) .then(() => { // When we connect to that collection with waitForCollectionCallback = true connection = Onyx.connect({ @@ -1091,7 +1091,7 @@ describe('Onyx', () => { return ( waitForPromisesToResolve() // When mergeCollection is called with an updated collection - .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate as GenericCollection)) + .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate as unknown as OnyxMergeCollectionInput)) .then(() => { // Then we expect the callback to have called twice, once for the initial connect call + once for the collection update expect(mockCallback).toHaveBeenCalledTimes(2); @@ -1120,7 +1120,7 @@ describe('Onyx', () => { return ( waitForPromisesToResolve() // When mergeCollection is called with an updated collection - .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate as GenericCollection)) + .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate as unknown as OnyxMergeCollectionInput)) .then(() => { // Then we expect the callback to have called twice, once for the initial connect call + once for the collection update expect(mockCallback).toHaveBeenCalledTimes(2); @@ -1268,7 +1268,7 @@ describe('Onyx', () => { Onyx.update([ {onyxMethod: Onyx.METHOD.SET, key: ONYX_KEYS.TEST_KEY, value: 'taco'}, {onyxMethod: Onyx.METHOD.MERGE, key: ONYX_KEYS.OTHER_TEST, value: 'pizza'}, - {onyxMethod: Onyx.METHOD.MERGE_COLLECTION, key: ONYX_KEYS.COLLECTION.TEST_UPDATE, value: {[itemKey]: {a: 'a'}}}, + {onyxMethod: Onyx.METHOD.MERGE_COLLECTION, key: ONYX_KEYS.COLLECTION.TEST_UPDATE, value: {[itemKey]: {a: 'a'}} as OnyxMergeCollectionInput}, ]).then(() => { expect(collectionCallback).toHaveBeenCalledTimes(2); expect(collectionCallback).toHaveBeenNthCalledWith(1, undefined, undefined, undefined); @@ -1525,10 +1525,10 @@ describe('Onyx', () => { const initialValue = {name: 'Fluffy'}; - const collectionDiff: GenericCollection = { + const collectionDiff = { [cat]: initialValue, [dog]: {name: 'Rex'}, - }; + } as OnyxMergeCollectionInput; return Onyx.set(cat, initialValue) .then(() => { @@ -1630,7 +1630,7 @@ describe('Onyx', () => { 0: 'Bed', }, }, - }, + } as OnyxMergeCollectionInput, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -1729,7 +1729,7 @@ describe('Onyx', () => { value: { [cat]: {age: 5, size: 'S'}, [dog]: {size: 'M'}, - }, + } as OnyxMergeCollectionInput, }, {onyxMethod: Onyx.METHOD.SET, key: cat, value: {age: 3}}, {onyxMethod: Onyx.METHOD.MERGE, key: cat, value: {sound: 'meow'}}, @@ -2214,7 +2214,7 @@ describe('Onyx', () => { value: { [routeA]: {name: 'New Route A'}, [routeB]: {name: 'New Route B'}, - }, + } as OnyxMergeCollectionInput, }, ]); }) @@ -2265,7 +2265,7 @@ describe('Onyx', () => { key: ONYX_KEYS.COLLECTION.ROUTES, value: { [routeA]: {name: 'Final Route A'}, - }, + } as OnyxMergeCollectionInput, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -2319,7 +2319,7 @@ describe('Onyx', () => { value: { [key1]: {id: '1', name: 'Updated Item 1'}, [key2]: {id: '2', name: 'Updated Item 2'}, - }, + } as OnyxMergeCollectionInput, }, ]); @@ -2663,8 +2663,9 @@ describe('Onyx', () => { } as GenericCollection); await Onyx.setCollection(ONYX_KEYS.COLLECTION.ROUTES, { + // @ts-expect-error invalidRoute is not a valid key [invalidRoute]: {name: 'Invalid Route'}, - } as GenericCollection); + }); expect(result).toEqual({ [routeA]: {name: 'Route A'}, diff --git a/tests/unit/onyxUtilsTest.ts b/tests/unit/onyxUtilsTest.ts index 9b120a580..25f6d6d38 100644 --- a/tests/unit/onyxUtilsTest.ts +++ b/tests/unit/onyxUtilsTest.ts @@ -210,8 +210,9 @@ describe('OnyxUtils', () => { } as GenericCollection); await OnyxUtils.partialSetCollection(ONYXKEYS.COLLECTION.ROUTES, { + // @ts-expect-error invalidRoute is not a valid key [invalidRoute]: {name: 'Invalid Route'}, - } as GenericCollection); + }); expect(result).toEqual({ [routeA]: {name: 'Route A'}, diff --git a/tests/utils/GenericCollection.ts b/tests/utils/GenericCollection.ts index 4c355433b..69b44cfc6 100644 --- a/tests/utils/GenericCollection.ts +++ b/tests/utils/GenericCollection.ts @@ -1,5 +1,5 @@ -import type {Collection} from '../../lib/types'; +import type {OnyxKey, OnyxMergeCollectionInput} from '../../lib/types'; -type GenericCollection = Collection; +type GenericCollection = OnyxMergeCollectionInput; export default GenericCollection; From 541d23e5ba5966fe5f08b006c9e4136726f30570 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 3 Nov 2025 10:29:52 +0100 Subject: [PATCH 08/11] Add OnyxSetCollectionInput type and update related functions --- lib/Onyx.ts | 7 ++--- lib/OnyxUtils.ts | 6 ++-- lib/index.ts | 2 ++ lib/types.ts | 8 ++++- tests/unit/onyxMultiMergeWebStorageTest.ts | 12 ++++---- tests/unit/onyxTest.ts | 34 +++++++++++----------- tests/utils/GenericCollection.ts | 5 ++-- 7 files changed, 40 insertions(+), 34 deletions(-) diff --git a/lib/Onyx.ts b/lib/Onyx.ts index 6a4e26839..f647bbe9c 100644 --- a/lib/Onyx.ts +++ b/lib/Onyx.ts @@ -4,8 +4,6 @@ import Storage from './storage'; import utils from './utils'; import DevTools, {initDevTools} from './DevTools'; import type { - Collection, - CollectionKey, CollectionKeyBase, ConnectOptions, InitOptions, @@ -15,6 +13,7 @@ import type { MixedOperationsQueue, OnyxKey, OnyxMergeCollectionInput, + OnyxSetCollectionInput, OnyxMergeInput, OnyxMultiSetInput, OnyxSetInput, @@ -545,7 +544,7 @@ function update(data: OnyxUpdate[]): Promise { [OnyxUtils.METHOD.SET]: enqueueSetOperation, [OnyxUtils.METHOD.MERGE]: enqueueMergeOperation, [OnyxUtils.METHOD.MERGE_COLLECTION]: () => { - const collection = value as Collection; + const collection = value as OnyxMergeCollectionInput; if (!OnyxUtils.isValidNonEmptyCollectionForMerge(collection)) { Logger.logInfo('mergeCollection enqueued within update() with invalid or empty value. Skipping this operation.'); return; @@ -655,7 +654,7 @@ function update(data: OnyxUpdate[]): Promise { * @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT` * @param collection Object collection keyed by individual collection member keys and values */ -function setCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { +function setCollection(collectionKey: TKey, collection: OnyxSetCollectionInput): Promise { let resultCollection: OnyxInputKeyValueMapping = collection; let resultCollectionKeys = Object.keys(resultCollection); diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index d52739df5..e2e711778 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -29,7 +29,7 @@ import type { OnyxUpdate, OnyxValue, Selector, - Collection, + OnyxSetCollectionInput, } from './types'; import type {FastMergeOptions, FastMergeResult} from './utils'; import utils from './utils'; @@ -1036,7 +1036,7 @@ function initializeWithDefaultKeyStates(): Promise { /** * Validate the collection is not empty and has a correct type before applying mergeCollection() */ -function isValidNonEmptyCollectionForMerge(collection: Collection): boolean { +function isValidNonEmptyCollectionForMerge(collection: OnyxMergeCollectionInput): boolean { return typeof collection === 'object' && !Array.isArray(collection) && !utils.isEmptyObject(collection); } @@ -1367,7 +1367,7 @@ function mergeCollectionWithPatches( * @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT` * @param collection Object collection keyed by individual collection member keys and values */ -function partialSetCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { +function partialSetCollection(collectionKey: TKey, collection: OnyxSetCollectionInput): Promise { let resultCollection: OnyxInputKeyValueMapping = collection; let resultCollectionKeys = Object.keys(resultCollection); diff --git a/lib/index.ts b/lib/index.ts index ee6cb3cb7..bb6df0e0c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -16,6 +16,7 @@ import type { OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, + OnyxSetCollectionInput, } from './types'; import type {FetchStatus, ResultMetadata, UseOnyxResult, UseOnyxOptions} from './useOnyx'; import type {Connection} from './OnyxConnectionManager'; @@ -40,6 +41,7 @@ export type { OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, + OnyxSetCollectionInput, OnyxUpdate, OnyxValue, ResultMetadata, diff --git a/lib/types.ts b/lib/types.ts index 0f8e696d1..7b9ddb2aa 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -321,6 +321,11 @@ type OnyxMergeInput = OnyxInput; */ type OnyxMergeCollectionInput = Collection>>; +/** + * This represents the value that can be passed to `Onyx.setCollection` and to `Onyx.update` with the method "SET_COLLECTION" + */ +type OnyxSetCollectionInput = Collection>; + type OnyxMethodMap = typeof OnyxUtils.METHOD; /** @@ -363,7 +368,7 @@ type OnyxUpdate = | { onyxMethod: typeof OnyxUtils.METHOD.SET_COLLECTION; key: TKey; - value: OnyxMergeCollectionInput; + value: OnyxSetCollectionInput; }; }[CollectionKeyBase]; @@ -476,6 +481,7 @@ export type { OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, + OnyxSetCollectionInput, OnyxMethod, OnyxMethodMap, OnyxUpdate, diff --git a/tests/unit/onyxMultiMergeWebStorageTest.ts b/tests/unit/onyxMultiMergeWebStorageTest.ts index 9375b9f40..c3f939c71 100644 --- a/tests/unit/onyxMultiMergeWebStorageTest.ts +++ b/tests/unit/onyxMultiMergeWebStorageTest.ts @@ -3,7 +3,7 @@ import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import Storage from '../../lib/storage'; import type MockedStorage from '../../lib/storage/__mocks__'; import type OnyxInstance from '../../lib/Onyx'; -import type {OnyxKey, OnyxMergeCollectionInput} from '../../lib'; +import type GenericCollection from '../utils/GenericCollection'; const StorageMock = Storage as unknown as typeof MockedStorage; @@ -55,7 +55,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { test_1: additionalDataOne, test_2: additionalDataOne, test_3: additionalDataOne, - } as unknown as OnyxMergeCollectionInput); + } as GenericCollection); // And call again consecutively with different data const additionalDataTwo = {d: 'd', e: [2]}; @@ -63,7 +63,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { test_1: additionalDataTwo, test_2: additionalDataTwo, test_3: additionalDataTwo, - } as unknown as OnyxMergeCollectionInput); + } as GenericCollection); return waitForPromisesToResolve().then(() => { const finalObject = { @@ -103,7 +103,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { test_1: data, test_2: data, test_3: data, - } as unknown as OnyxMergeCollectionInput); + } as GenericCollection); return waitForPromisesToResolve() .then(() => { @@ -125,7 +125,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { test_1: additionalData, test_2: additionalData, test_3: additionalData, - } as unknown as OnyxMergeCollectionInput); + } as GenericCollection); return waitForPromisesToResolve(); }) @@ -164,7 +164,7 @@ describe('Onyx.mergeCollection() and WebStorage', () => { // 2nd call Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, { test_1: {d: 'd', e: 'e'}, - } as unknown as OnyxMergeCollectionInput); + } as GenericCollection); // Last call Onyx.merge('test_1', {f: 'f'}); diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 472d6069b..066f4513f 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -5,7 +5,7 @@ import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import OnyxUtils from '../../lib/OnyxUtils'; import type OnyxCache from '../../lib/OnyxCache'; import StorageMock from '../../lib/storage'; -import type {OnyxCollection, OnyxKey, OnyxMergeCollectionInput, OnyxUpdate} from '../../lib/types'; +import type {OnyxCollection, OnyxUpdate} from '../../lib/types'; import type {GenericDeepRecord} from '../types'; import type GenericCollection from '../utils/GenericCollection'; import type {Connection} from '../../lib/OnyxConnectionManager'; @@ -624,7 +624,7 @@ describe('Onyx', () => { ID: 345, value: 'three', }, - } as unknown as OnyxMergeCollectionInput) + } as GenericCollection) .then(() => // 2 key values to update and 2 new keys to add. // MergeCollection will perform a mix of multiSet and multiMerge @@ -646,7 +646,7 @@ describe('Onyx', () => { ID: 567, value: 'one', }, - } as unknown as OnyxMergeCollectionInput), + } as GenericCollection), ) .then(() => { // 3 items on the first mergeCollection + 4 items the next mergeCollection @@ -674,7 +674,7 @@ describe('Onyx', () => { callback: (data, key) => (valuesReceived[key] = data), }); - return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {test_1: {ID: 123}, notMyTest: {beep: 'boop'}} as unknown as OnyxMergeCollectionInput).then(() => { + return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {test_1: {ID: 123}, notMyTest: {beep: 'boop'}} as GenericCollection).then(() => { expect(valuesReceived).toEqual({}); }); }); @@ -705,7 +705,7 @@ describe('Onyx', () => { ID: 234, value: 'two', }, - } as unknown as OnyxMergeCollectionInput), + } as GenericCollection), ) .then(() => { expect(valuesReceived).toEqual({ @@ -899,7 +899,7 @@ describe('Onyx', () => { ID: 345, value: 'three', }, - } as unknown as OnyxMergeCollectionInput, + } as GenericCollection, }, ]); }) @@ -1058,7 +1058,7 @@ describe('Onyx', () => { }, }; - return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_CONNECT_COLLECTION, initialCollectionData as unknown as OnyxMergeCollectionInput) + return Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_CONNECT_COLLECTION, initialCollectionData as GenericCollection) .then(() => { // When we connect to that collection with waitForCollectionCallback = true connection = Onyx.connect({ @@ -1091,7 +1091,7 @@ describe('Onyx', () => { return ( waitForPromisesToResolve() // When mergeCollection is called with an updated collection - .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate as unknown as OnyxMergeCollectionInput)) + .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate as GenericCollection)) .then(() => { // Then we expect the callback to have called twice, once for the initial connect call + once for the collection update expect(mockCallback).toHaveBeenCalledTimes(2); @@ -1120,7 +1120,7 @@ describe('Onyx', () => { return ( waitForPromisesToResolve() // When mergeCollection is called with an updated collection - .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate as unknown as OnyxMergeCollectionInput)) + .then(() => Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate as GenericCollection)) .then(() => { // Then we expect the callback to have called twice, once for the initial connect call + once for the collection update expect(mockCallback).toHaveBeenCalledTimes(2); @@ -1268,7 +1268,7 @@ describe('Onyx', () => { Onyx.update([ {onyxMethod: Onyx.METHOD.SET, key: ONYX_KEYS.TEST_KEY, value: 'taco'}, {onyxMethod: Onyx.METHOD.MERGE, key: ONYX_KEYS.OTHER_TEST, value: 'pizza'}, - {onyxMethod: Onyx.METHOD.MERGE_COLLECTION, key: ONYX_KEYS.COLLECTION.TEST_UPDATE, value: {[itemKey]: {a: 'a'}} as OnyxMergeCollectionInput}, + {onyxMethod: Onyx.METHOD.MERGE_COLLECTION, key: ONYX_KEYS.COLLECTION.TEST_UPDATE, value: {[itemKey]: {a: 'a'}} as GenericCollection}, ]).then(() => { expect(collectionCallback).toHaveBeenCalledTimes(2); expect(collectionCallback).toHaveBeenNthCalledWith(1, undefined, undefined, undefined); @@ -1525,10 +1525,10 @@ describe('Onyx', () => { const initialValue = {name: 'Fluffy'}; - const collectionDiff = { + const collectionDiff: GenericCollection = { [cat]: initialValue, [dog]: {name: 'Rex'}, - } as OnyxMergeCollectionInput; + }; return Onyx.set(cat, initialValue) .then(() => { @@ -1630,7 +1630,7 @@ describe('Onyx', () => { 0: 'Bed', }, }, - } as OnyxMergeCollectionInput, + } as GenericCollection, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -1729,7 +1729,7 @@ describe('Onyx', () => { value: { [cat]: {age: 5, size: 'S'}, [dog]: {size: 'M'}, - } as OnyxMergeCollectionInput, + } as GenericCollection, }, {onyxMethod: Onyx.METHOD.SET, key: cat, value: {age: 3}}, {onyxMethod: Onyx.METHOD.MERGE, key: cat, value: {sound: 'meow'}}, @@ -2214,7 +2214,7 @@ describe('Onyx', () => { value: { [routeA]: {name: 'New Route A'}, [routeB]: {name: 'New Route B'}, - } as OnyxMergeCollectionInput, + } as GenericCollection, }, ]); }) @@ -2265,7 +2265,7 @@ describe('Onyx', () => { key: ONYX_KEYS.COLLECTION.ROUTES, value: { [routeA]: {name: 'Final Route A'}, - } as OnyxMergeCollectionInput, + } as GenericCollection, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -2319,7 +2319,7 @@ describe('Onyx', () => { value: { [key1]: {id: '1', name: 'Updated Item 1'}, [key2]: {id: '2', name: 'Updated Item 2'}, - } as OnyxMergeCollectionInput, + } as GenericCollection, }, ]); diff --git a/tests/utils/GenericCollection.ts b/tests/utils/GenericCollection.ts index 69b44cfc6..a0a613a1d 100644 --- a/tests/utils/GenericCollection.ts +++ b/tests/utils/GenericCollection.ts @@ -1,5 +1,4 @@ -import type {OnyxKey, OnyxMergeCollectionInput} from '../../lib/types'; - -type GenericCollection = OnyxMergeCollectionInput; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type GenericCollection = Record; export default GenericCollection; From 0ff807554c9b52b62fddbdc7b08da086ee645486 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 3 Nov 2025 10:43:41 +0100 Subject: [PATCH 09/11] Update Onyx functions to use OnyxSetCollectionInput type for set operations --- lib/Onyx.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Onyx.ts b/lib/Onyx.ts index f647bbe9c..a6ece914e 100644 --- a/lib/Onyx.ts +++ b/lib/Onyx.ts @@ -557,7 +557,7 @@ function update(data: OnyxUpdate[]): Promise { collectionKeys.forEach((collectionKey) => enqueueMergeOperation(collectionKey, mergedCollection[collectionKey])); } }, - [OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as OnyxMergeCollectionInput)), + [OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as OnyxSetCollectionInput)), [OnyxUtils.METHOD.MULTI_SET]: (k, v) => Object.entries(v as Partial).forEach(([entryKey, entryValue]) => enqueueSetOperation(entryKey, entryValue)), [OnyxUtils.METHOD.CLEAR]: () => { clearPromise = clear(); @@ -617,7 +617,7 @@ function update(data: OnyxUpdate[]): Promise { ); } if (!utils.isEmptyObject(batchedCollectionUpdates.set)) { - promises.push(() => OnyxUtils.partialSetCollection(collectionKey, batchedCollectionUpdates.set as OnyxMergeCollectionInput)); + promises.push(() => OnyxUtils.partialSetCollection(collectionKey, batchedCollectionUpdates.set as OnyxSetCollectionInput)); } }); From 0f77bec640af6a77fb11b20d11791b38baffbc74 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 4 Nov 2025 19:51:18 +0100 Subject: [PATCH 10/11] Ignore tests/types directory in ESLint configuration to prevent type checking issues --- .eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintignore b/.eslintignore index 96d1873e5..eadb211a1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,5 @@ dist node_modules *.config.js +# tests/types catalog is not type checked with the rest of the project, so we need to ignore it in eslint tests/types/**/*.ts \ No newline at end of file From 0443e3bfeac3f814774e2ee6c7455e3d52458cd4 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 4 Nov 2025 19:51:49 +0100 Subject: [PATCH 11/11] Rename to setup.ts --- tests/types/OnyxUpdate.ts | 2 +- tests/types/mergeCollection.ts | 2 +- tests/types/{augmentation.ts => setup.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/types/{augmentation.ts => setup.ts} (100%) diff --git a/tests/types/OnyxUpdate.ts b/tests/types/OnyxUpdate.ts index cf6b5d0d4..e1b8567ce 100644 --- a/tests/types/OnyxUpdate.ts +++ b/tests/types/OnyxUpdate.ts @@ -1,5 +1,5 @@ import type {OnyxUpdate} from '../../dist/types'; -import ONYX_KEYS from './augmentation'; +import ONYX_KEYS from './setup'; const onyxUpdate: OnyxUpdate = { onyxMethod: 'set', diff --git a/tests/types/mergeCollection.ts b/tests/types/mergeCollection.ts index d2af85c59..5d07fd217 100644 --- a/tests/types/mergeCollection.ts +++ b/tests/types/mergeCollection.ts @@ -1,5 +1,5 @@ import Onyx from '../../dist/Onyx'; -import ONYX_KEYS from './augmentation'; +import ONYX_KEYS from './setup'; Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, { test_1: { diff --git a/tests/types/augmentation.ts b/tests/types/setup.ts similarity index 100% rename from tests/types/augmentation.ts rename to tests/types/setup.ts