Skip to content

Commit 5b3fcd1

Browse files
committed
Merge branch 'main' into VickyStash/bugfix/update-eviction-rules
# Conflicts: # lib/Onyx.ts # lib/OnyxUtils.ts # tests/unit/onyxUtilsTest.ts
2 parents fddfe2d + cc0f68e commit 5b3fcd1

18 files changed

+206
-74
lines changed

.eslintignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
*.d.ts
22
dist
33
node_modules
4-
*.config.js
4+
*.config.js
5+
# tests/types catalog is not type checked with the rest of the project, so we need to ignore it in eslint
6+
tests/types/**/*.ts

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ jobs:
3030
- run: npm run test
3131
env:
3232
CI: true
33+
34+
- run: npm run test:types

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module.exports = {
44
transform: {
55
'\\.[jt]sx?$': 'babel-jest',
66
},
7-
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/tests/unit/mocks/', '<rootDir>/tests/e2e/'],
7+
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/tests/unit/mocks/', '<rootDir>/tests/e2e/', '<rootDir>/tests/types/'],
88
testMatch: ['**/tests/unit/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
99
globals: {
1010
__DEV__: true,

lib/Onyx.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import Storage from './storage';
44
import utils from './utils';
55
import DevTools, {initDevTools} from './DevTools';
66
import type {
7-
Collection,
8-
CollectionKey,
97
CollectionKeyBase,
108
ConnectOptions,
119
InitOptions,
@@ -15,6 +13,7 @@ import type {
1513
MixedOperationsQueue,
1614
OnyxKey,
1715
OnyxMergeCollectionInput,
16+
OnyxSetCollectionInput,
1817
OnyxMergeInput,
1918
OnyxMultiSetInput,
2019
OnyxSetInput,
@@ -270,7 +269,7 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>):
270269
* @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
271270
* @param collection Object collection keyed by individual collection member keys and values
272271
*/
273-
function mergeCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TKey, TMap>): Promise<void> {
272+
function mergeCollection<TKey extends CollectionKeyBase>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TKey>): Promise<void> {
274273
return OnyxUtils.mergeCollectionWithPatches({collectionKey, collection, isProcessingCollectionUpdate: true});
275274
}
276275

@@ -441,7 +440,7 @@ function update(data: OnyxUpdate[]): Promise<void> {
441440
[OnyxUtils.METHOD.SET]: enqueueSetOperation,
442441
[OnyxUtils.METHOD.MERGE]: enqueueMergeOperation,
443442
[OnyxUtils.METHOD.MERGE_COLLECTION]: () => {
444-
const collection = value as Collection<CollectionKey, unknown, unknown>;
443+
const collection = value as OnyxMergeCollectionInput<OnyxKey>;
445444
if (!OnyxUtils.isValidNonEmptyCollectionForMerge(collection)) {
446445
Logger.logInfo('mergeCollection enqueued within update() with invalid or empty value. Skipping this operation.');
447446
return;
@@ -454,7 +453,7 @@ function update(data: OnyxUpdate[]): Promise<void> {
454453
collectionKeys.forEach((collectionKey) => enqueueMergeOperation(collectionKey, mergedCollection[collectionKey]));
455454
}
456455
},
457-
[OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as Collection<CollectionKey, unknown, unknown>)),
456+
[OnyxUtils.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v as OnyxSetCollectionInput<OnyxKey>)),
458457
[OnyxUtils.METHOD.MULTI_SET]: (k, v) => Object.entries(v as Partial<OnyxInputKeyValueMapping>).forEach(([entryKey, entryValue]) => enqueueSetOperation(entryKey, entryValue)),
459458
[OnyxUtils.METHOD.CLEAR]: () => {
460459
clearPromise = clear();
@@ -507,14 +506,14 @@ function update(data: OnyxUpdate[]): Promise<void> {
507506
promises.push(() =>
508507
OnyxUtils.mergeCollectionWithPatches({
509508
collectionKey,
510-
collection: batchedCollectionUpdates.merge as Collection<CollectionKey, unknown, unknown>,
509+
collection: batchedCollectionUpdates.merge as OnyxMergeCollectionInput<OnyxKey>,
511510
mergeReplaceNullPatches: batchedCollectionUpdates.mergeReplaceNullPatches,
512511
isProcessingCollectionUpdate: true,
513512
}),
514513
);
515514
}
516515
if (!utils.isEmptyObject(batchedCollectionUpdates.set)) {
517-
promises.push(() => OnyxUtils.partialSetCollection({collectionKey, collection: batchedCollectionUpdates.set as Collection<CollectionKey, unknown, unknown>}));
516+
promises.push(() => OnyxUtils.partialSetCollection({collectionKey, collection: batchedCollectionUpdates.set as OnyxSetCollectionInput<OnyxKey>}));
518517
}
519518
});
520519

@@ -551,7 +550,7 @@ function update(data: OnyxUpdate[]): Promise<void> {
551550
* @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
552551
* @param collection Object collection keyed by individual collection member keys and values
553552
*/
554-
function setCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TKey, TMap>): Promise<void> {
553+
function setCollection<TKey extends CollectionKeyBase>(collectionKey: TKey, collection: OnyxSetCollectionInput<TKey>): Promise<void> {
555554
return OnyxUtils.setCollectionWithRetry({collectionKey, collection});
556555
}
557556

lib/OnyxUtils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
OnyxUpdate,
3030
OnyxValue,
3131
Selector,
32+
OnyxSetCollectionInput,
3233
MergeCollectionWithPatchesParams,
3334
SetCollectionParams,
3435
SetParams,
@@ -1125,7 +1126,7 @@ function initializeWithDefaultKeyStates(): Promise<void> {
11251126
/**
11261127
* Validate the collection is not empty and has a correct type before applying mergeCollection()
11271128
*/
1128-
function isValidNonEmptyCollectionForMerge<TKey extends CollectionKeyBase, TMap>(collection: OnyxMergeCollectionInput<TKey, TMap>): boolean {
1129+
function isValidNonEmptyCollectionForMerge<TKey extends CollectionKeyBase>(collection: OnyxMergeCollectionInput<TKey>): boolean {
11291130
return typeof collection === 'object' && !Array.isArray(collection) && !utils.isEmptyObject(collection);
11301131
}
11311132

@@ -1533,8 +1534,8 @@ function setCollectionWithRetry<TKey extends CollectionKeyBase, TMap>({collectio
15331534
* @param params.isProcessingCollectionUpdate whether this is part of a collection update operation.
15341535
* @param retryAttempt retry attempt
15351536
*/
1536-
function mergeCollectionWithPatches<TKey extends CollectionKeyBase, TMap>(
1537-
{collectionKey, collection, mergeReplaceNullPatches, isProcessingCollectionUpdate = false}: MergeCollectionWithPatchesParams<TKey, TMap>,
1537+
function mergeCollectionWithPatches<TKey extends CollectionKeyBase>(
1538+
{collectionKey, collection, mergeReplaceNullPatches, isProcessingCollectionUpdate = false}: MergeCollectionWithPatchesParams<TKey>,
15381539
retryAttempt?: number,
15391540
): Promise<void> {
15401541
if (!isValidNonEmptyCollectionForMerge(collection)) {

lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
OnyxMultiSetInput,
1717
OnyxMergeInput,
1818
OnyxMergeCollectionInput,
19+
OnyxSetCollectionInput,
1920
} from './types';
2021
import type {FetchStatus, ResultMetadata, UseOnyxResult, UseOnyxOptions} from './useOnyx';
2122
import type {Connection} from './OnyxConnectionManager';
@@ -40,6 +41,7 @@ export type {
4041
OnyxMultiSetInput,
4142
OnyxMergeInput,
4243
OnyxMergeCollectionInput,
44+
OnyxSetCollectionInput,
4345
OnyxUpdate,
4446
OnyxValue,
4547
ResultMetadata,

lib/types.ts

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type {Merge} from 'type-fest';
2-
import type {BuiltIns} from 'type-fest/source/internal';
32
import type OnyxUtils from './OnyxUtils';
43
import type {OnyxMethod} from './OnyxUtils';
54
import type {FastMergeReplaceNullPatch} from './utils';
@@ -157,6 +156,10 @@ type OnyxValue<TKey extends OnyxKey> = string extends TKey ? unknown : TKey exte
157156
/** Utility type to extract `TOnyxValue` from `OnyxCollection<TOnyxValue>` */
158157
type ExtractOnyxCollectionValue<TOnyxCollection> = TOnyxCollection extends NonNullable<OnyxCollection<infer U>> ? U : never;
159158

159+
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
160+
161+
type BuiltIns = Primitive | void | Date | RegExp;
162+
160163
type NonTransformableTypes =
161164
| BuiltIns
162165
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -205,13 +208,7 @@ type NullishObjectDeep<ObjectType extends object> = {
205208
* Also, the `TMap` type is inferred automatically in `mergeCollection()` method and represents
206209
* the object of collection keys/values specified in the second parameter of the method.
207210
*/
208-
type Collection<TKey extends CollectionKeyBase, TValue, TMap = never> = {
209-
[MapK in keyof TMap]: MapK extends `${TKey}${string}`
210-
? MapK extends `${TKey}`
211-
? never // forbids empty id
212-
: TValue
213-
: never;
214-
};
211+
type Collection<TKey extends CollectionKeyBase, TValue> = Record<`${TKey}${string}`, TValue> & {[P in TKey]?: never};
215212

216213
/** Represents the base options used in `Onyx.connect()` method. */
217214
// NOTE: Any changes to this type like adding or removing options must be accounted in OnyxConnectionManager's `generateConnectionID()` method!
@@ -322,48 +319,58 @@ type OnyxMergeInput<TKey extends OnyxKey> = OnyxInput<TKey>;
322319
/**
323320
* This represents the value that can be passed to `Onyx.merge` and to `Onyx.update` with the method "MERGE"
324321
*/
325-
type OnyxMergeCollectionInput<TKey extends OnyxKey, TMap = object> = Collection<TKey, NonNullable<OnyxInput<TKey>>, TMap>;
322+
type OnyxMergeCollectionInput<TKey extends OnyxKey> = Collection<TKey, NonNullable<OnyxInput<TKey>>>;
326323

327-
type OnyxMethodMap = typeof OnyxUtils.METHOD;
324+
/**
325+
* This represents the value that can be passed to `Onyx.setCollection` and to `Onyx.update` with the method "SET_COLLECTION"
326+
*/
327+
type OnyxSetCollectionInput<TKey extends OnyxKey> = Collection<TKey, OnyxInput<TKey>>;
328328

329-
// Maps onyx methods to their corresponding value types
330-
type OnyxMethodValueMap = {
331-
[OnyxUtils.METHOD.SET]: {
332-
key: OnyxKey;
333-
value: OnyxSetInput<OnyxKey>;
334-
};
335-
[OnyxUtils.METHOD.MULTI_SET]: {
336-
key: OnyxKey;
337-
value: OnyxMultiSetInput;
338-
};
339-
[OnyxUtils.METHOD.MERGE]: {
340-
key: OnyxKey;
341-
value: OnyxMergeInput<OnyxKey>;
342-
};
343-
[OnyxUtils.METHOD.CLEAR]: {
344-
key: OnyxKey;
345-
value?: undefined;
346-
};
347-
[OnyxUtils.METHOD.MERGE_COLLECTION]: {
348-
key: CollectionKeyBase;
349-
value: OnyxMergeCollectionInput<CollectionKeyBase>;
350-
};
351-
[OnyxUtils.METHOD.SET_COLLECTION]: {
352-
key: CollectionKeyBase;
353-
value: OnyxMergeCollectionInput<CollectionKeyBase>;
354-
};
355-
};
329+
type OnyxMethodMap = typeof OnyxUtils.METHOD;
356330

357331
/**
358332
* OnyxUpdate type includes all onyx methods used in OnyxMethodValueMap.
359333
* If a new method is added to OnyxUtils.METHOD constant, it must be added to OnyxMethodValueMap type.
360334
* Otherwise it will show static type errors.
361335
*/
362-
type OnyxUpdate = {
363-
[Method in OnyxMethod]: {
364-
onyxMethod: Method;
365-
} & OnyxMethodValueMap[Method];
366-
}[OnyxMethod];
336+
type OnyxUpdate =
337+
// ⚠️ DO NOT CHANGE THIS TYPE, UNLESS YOU KNOW WHAT YOU ARE DOING. ⚠️
338+
| {
339+
[TKey in OnyxKey]:
340+
| {
341+
onyxMethod: typeof OnyxUtils.METHOD.SET;
342+
key: TKey;
343+
value: OnyxSetInput<TKey>;
344+
}
345+
| {
346+
onyxMethod: typeof OnyxUtils.METHOD.MULTI_SET;
347+
key: TKey;
348+
value: OnyxMultiSetInput;
349+
}
350+
| {
351+
onyxMethod: typeof OnyxUtils.METHOD.MERGE;
352+
key: TKey;
353+
value: OnyxMergeInput<TKey>;
354+
}
355+
| {
356+
onyxMethod: typeof OnyxUtils.METHOD.CLEAR;
357+
key: TKey;
358+
value?: undefined;
359+
};
360+
}[OnyxKey]
361+
| {
362+
[TKey in CollectionKeyBase]:
363+
| {
364+
onyxMethod: typeof OnyxUtils.METHOD.MERGE_COLLECTION;
365+
key: TKey;
366+
value: OnyxMergeCollectionInput<TKey>;
367+
}
368+
| {
369+
onyxMethod: typeof OnyxUtils.METHOD.SET_COLLECTION;
370+
key: TKey;
371+
value: OnyxSetCollectionInput<TKey>;
372+
};
373+
}[CollectionKeyBase];
367374

368375
/**
369376
* Represents the options used in `Onyx.set()` method.
@@ -499,6 +506,7 @@ export type {
499506
OnyxMultiSetInput,
500507
OnyxMergeInput,
501508
OnyxMergeCollectionInput,
509+
OnyxSetCollectionInput,
502510
OnyxMethod,
503511
OnyxMethodMap,
504512
OnyxUpdate,

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-onyx",
3-
"version": "3.0.10",
3+
"version": "3.0.11",
44
"author": "Expensify, Inc.",
55
"homepage": "https://expensify.com",
66
"description": "State management for React Native",
@@ -32,6 +32,7 @@
3232
"lint": "eslint .",
3333
"typecheck": "tsc --noEmit",
3434
"test": "jest",
35+
"test:types": "npm run build && tsc --noEmit --project tsconfig.test.json",
3536
"perf-test": "npx reassure",
3637
"build": "tsc -p tsconfig.build.json",
3738
"build:watch": "nodemon --watch lib --ext js,json,ts,tsx --exec \"npm run build && npm pack\"",

tests/types/OnyxUpdate.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type {OnyxUpdate} from '../../dist/types';
2+
import ONYX_KEYS from './setup';
3+
4+
const onyxUpdate: OnyxUpdate = {
5+
onyxMethod: 'set',
6+
key: ONYX_KEYS.TEST_KEY,
7+
value: 'string',
8+
};
9+
10+
const onyxUpdateError: OnyxUpdate = {
11+
onyxMethod: 'set',
12+
key: ONYX_KEYS.TEST_KEY,
13+
// @ts-expect-error TEST_KEY is a string, not a number
14+
value: 2,
15+
};
16+
17+
const onyxUpdateCollection: OnyxUpdate = {
18+
onyxMethod: 'mergecollection',
19+
key: ONYX_KEYS.COLLECTION.TEST_KEY,
20+
value: {
21+
[`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: {
22+
str: 'test',
23+
},
24+
[`${ONYX_KEYS.COLLECTION.TEST_KEY}2`]: {
25+
str: 'test2',
26+
},
27+
},
28+
};
29+
30+
// @ts-expect-error COLLECTION.TEST_KEY is an object, not a number
31+
const onyxUpdateCollectionError: OnyxUpdate = {
32+
onyxMethod: 'mergecollection',
33+
key: ONYX_KEYS.COLLECTION.TEST_KEY,
34+
value: {
35+
[`${ONYX_KEYS.COLLECTION.TEST_KEY}1`]: 2,
36+
},
37+
};
38+
39+
const onyxUpdateCollectionError2: OnyxUpdate = {
40+
onyxMethod: 'mergecollection',
41+
key: ONYX_KEYS.COLLECTION.TEST_KEY,
42+
value: {
43+
[`${ONYX_KEYS.COLLECTION.TEST_KEY}2`]: {
44+
// @ts-expect-error nonExistingKey is not a valid key
45+
nonExistingKey: 'test2',
46+
},
47+
},
48+
};
49+
50+
// @ts-expect-error COLLECTION.TEST_KEY is invalid key, it is missing the suffix
51+
const onyxUpdateCollectionError3: OnyxUpdate = {
52+
onyxMethod: 'mergecollection',
53+
key: ONYX_KEYS.COLLECTION.TEST_KEY,
54+
value: {
55+
[ONYX_KEYS.COLLECTION.TEST_KEY]: {
56+
str: 'test2',
57+
},
58+
},
59+
};

0 commit comments

Comments
 (0)