Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type declarations #267

Merged
merged 44 commits into from
Aug 12, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7d8ed5c
Generate `Onyx` and `withOnyx` declaration files
fabioh8010 Jun 27, 2023
d10db07
Create `Logger` declaration file
fabioh8010 Jun 27, 2023
98ac024
Improve basic `Onyx` method declarations
fabioh8010 Jun 27, 2023
e0b50fd
Ignore .d.ts files during linting
fabioh8010 Jun 27, 2023
65f0cf3
Add "types" entry in package.json
fabioh8010 Jun 28, 2023
2eb85e6
Initial implementation of generic keys
fabioh8010 Jun 28, 2023
64a1d1a
Improve typings for Onyx methods
fabioh8010 Jun 29, 2023
dbfbd8c
Improve custom type approach
fabioh8010 Jun 30, 2023
78a766c
Address reviews suggestions and improve several types
fabioh8010 Jun 30, 2023
e389d32
Minor fixes
fabioh8010 Jul 3, 2023
cd1807b
Improve merge() and updated() typings
fabioh8010 Jul 4, 2023
533dfdf
Improves mergeCollection() typings
fabioh8010 Jul 4, 2023
5cce58e
Add type to withOnyxInstance property on connect()
fabioh8010 Jul 4, 2023
d42ea7e
Change callback type based on waitForCollectionCallback on connect()
fabioh8010 Jul 4, 2023
0b487d5
Improve connect() typing
fabioh8010 Jul 4, 2023
b0a0321
First version of withOnyx HOC typing
fabioh8010 Jul 10, 2023
c5b36a8
Fix withOnyx key type and improve all typings
fabioh8010 Jul 12, 2023
af68220
Use a separate type for collection keys
fabioh8010 Jul 13, 2023
7b3492a
Fix update() mergeCollection object
fabioh8010 Jul 13, 2023
7944e13
Improve connect() type for collection keys
fabioh8010 Jul 13, 2023
2592b36
Fix Selector type for unknown values
fabioh8010 Jul 13, 2023
02f0c1e
General improvements on HOC typings
fabioh8010 Jul 17, 2023
a25920a
Minor fix in getAllKeys() type
fabioh8010 Jul 18, 2023
e0ebe55
Address review comments
fabioh8010 Jul 19, 2023
6103f73
Address review comments
fabioh8010 Jul 25, 2023
32abd45
Remove string selectors and fix safeEvictionKeys
fabioh8010 Jul 26, 2023
6ec2d95
First attempt of GetOnyxValue
fabioh8010 Jul 26, 2023
38e8eed
Merge branch 'main' into feature/type-declarations
fabioh8010 Jul 26, 2023
30b2dc4
Add type declarations for new Onyx methods
fabioh8010 Jul 26, 2023
4332d2f
Add comments to some draft types
fabioh8010 Jul 26, 2023
343d7d9
Second attempt to solve collection key issue
fabioh8010 Jul 27, 2023
949b48e
Add JSDoc comments for several types
fabioh8010 Jul 27, 2023
3a101df
Clean up typings and add more JSDoc comments
fabioh8010 Jul 28, 2023
f06ce38
Merge remote-tracking branch 'origin/main' into feature/type-declarat…
fabioh8010 Jul 31, 2023
826ee48
Address review comments
fabioh8010 Jul 31, 2023
834bf1d
Address review comments
fabioh8010 Aug 1, 2023
3eaf539
Replace MergeBy with Merge from type-fest and remove unused exports
fabioh8010 Aug 2, 2023
6215c2f
Add examples for OnyxEntry and OnyxCollectionEntries types
fabioh8010 Aug 2, 2023
c5fc96e
Change KeyValueMapping type to automatically consider collection keys…
fabioh8010 Aug 2, 2023
521426f
Address review comments
fabioh8010 Aug 3, 2023
d632845
First test with new OnyxUpdate approach
fabioh8010 Aug 4, 2023
eae275b
Remove unused code
fabioh8010 Aug 4, 2023
86aa549
Export OnyxUpdate type
fabioh8010 Aug 4, 2023
500d228
Address review comments
fabioh8010 Aug 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 67 additions & 25 deletions lib/Onyx.d.ts
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,64 +1,105 @@
import {Component} from 'react';
import {PartialDeep} from 'type-fest';
import * as Logger from './Logger';
import {CollectionKey, DeepRecord, Key, OnyxKey, KeyValueMapping, Selector} from './types';
import {
CollectionKey,
CollectionKeyBase,
DeepRecord,
Key,
KeyValueMapping,
OnyxCollectionEntries,
OnyxKey,
OnyxEntry,
} from './types';

fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Represents a mapping object where each `OnyxKey` maps to either a value of its corresponding type in `KeyValueMapping` or `null`.
*
* It's very similar to `KeyValueMapping` but this type accepts using `null` as well.
*/
type NullableKeyValueMapping = {
[TKey in OnyxKey]: KeyValueMapping[TKey] | null;
[TKey in OnyxKey]: OnyxEntry<KeyValueMapping[TKey]>;
};

/**
* Represents the base options used in `Onyx.connect()` method.
*/
type BaseConnectOptions<TKey extends OnyxKey> = {
statePropertyName?: string;
withOnyxInstance?: Component;
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
initWithStoredValues?: boolean;
selector?: Selector<TKey, KeyValueMapping[TKey] | null>;
};

/**
* Represents the options used in `Onyx.connect()` method.
* The type is built from `BaseConnectOptions` and extended to handle key/callback related options.
* It includes two different forms, depending on whether we are waiting for a collection callback or not.
*
* If `waitForCollectionCallback` is `true`, it expects `key` to be a Onyx collection key and `callback` will pass `value` as an `OnyxCollectionEntries`.
*
* If `waitForCollectionCallback` is `false` or not specified, the `key` can be any Onyx key and `callback` will pass `value` as an `OnyxEntry`.
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
*/
type ConnectOptions<TKey extends OnyxKey> = BaseConnectOptions<TKey> &
(
| {
key: TKey extends CollectionKey ? TKey : never;
callback?: (value: Record<string, KeyValueMapping[TKey] | null> | null, key?: TKey) => void;
callback?: (value: OnyxCollectionEntries<KeyValueMapping[TKey]>, key?: TKey) => void;
waitForCollectionCallback: true;
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
}
| {
key: TKey;
callback?: (value: KeyValueMapping[TKey] | null, key?: TKey) => void;
callback?: (value: OnyxEntry<KeyValueMapping[TKey]>, key?: TKey) => void;
waitForCollectionCallback?: false;
}
);

type Collection<TKey extends Key, TMap, TValue> = {
/**
* Represents a mapping between Onyx collection keys and their respective values.
* It helps to enforce that a Onyx collection key should not be without suffix (e.g. should always be of the form `${TKey}${string}`),
* and to map each Onyx collection key with suffix to a value of type `TValue`.
*/
type Collection<TKey extends CollectionKeyBase, TMap, TValue> = {
[MapK in keyof TMap]: MapK extends `${TKey}${string}`
? MapK extends `${TKey}`
? never // forbids empty id
: TValue
: never;
};

/**
* Represents different kinds of updates that can be passed to `Onyx.update()` method. It is a discriminated union of
* different update methods (`SET`, `MERGE`, `MERGE_COLLECTION`), each with their own key and value structure.
*/
type OnyxUpdate<TKey extends OnyxKey> =
| {
onyxMethod: typeof METHOD.SET;
key: TKey;
value: KeyValueMapping[TKey] | null;
value: OnyxEntry<KeyValueMapping[TKey]>;
}
| {
onyxMethod: typeof METHOD.MERGE;
key: TKey;
value: PartialDeep<KeyValueMapping[TKey]>;
}
| {
[TKey in CollectionKey]: {
[TKey in CollectionKeyBase]: {
onyxMethod: typeof METHOD.MERGE_COLLECTION;
key: TKey;
value: Record<`${TKey}${string}`, PartialDeep<KeyValueMapping[TKey]>>;
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
};
}[CollectionKey];
}[CollectionKeyBase];

/**
* Represents a record of `OnyxUpdate`s to be passed to `Onyx.update()` method, indexed by keys of type `OnyxKey`.
* Each `OnyxUpdate` will be associated with its respective Onyx key, ensuring different type-safety for each object.
*/
type OnyxUpdates<TKeyList extends OnyxKey[]> = {
[TKey in keyof TKeyList]: OnyxUpdate<TKeyList[TKey]>;
};

/**
* Represents the options used in `Onyx.init()` method.
*/
type InitOptions = {
keys?: DeepRecord<string, string>;
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
initialKeyStates?: Partial<NullableKeyValueMapping>;
Expand All @@ -84,25 +125,17 @@ declare function getAllKeys(): Promise<Array<OnyxKey>>;
/**
* Checks to see if this key has been flagged as
* safe for removal.
*
* @param testKey
*/
declare function isSafeEvictionKey(testKey: OnyxKey): boolean;

/**
* Removes a key previously added to this list
* which will enable it to be deleted again.
*
* @param key
* @param connectionID
*/
declare function removeFromEvictionBlockList(key: OnyxKey, connectionID: number): void;

/**
* Keys added to this list can never be deleted.
*
* @param key
* @param connectionID
*/
declare function addToEvictionBlockList(key: OnyxKey, connectionID: number): void;

Expand All @@ -125,11 +158,6 @@ declare function addToEvictionBlockList(key: OnyxKey, connectionID: number): voi
* @param [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
* component
* @param [mapping.waitForCollectionCallback] If set to true, it will return the entire collection to the callback as a single object
* @param [mapping.selector] THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data.
* If the selector is a string, the selector is passed to lodashGet on the sourceData. If the selector is a function, the sourceData and withOnyx state are
* passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive performance benefits because the component
* will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can
* be expensive from a performance standpoint).
* @returns an ID to use when calling disconnect
*/
declare function connect<TKey extends OnyxKey>(mapping: ConnectOptions<TKey>): number;
Expand All @@ -140,7 +168,6 @@ declare function connect<TKey extends OnyxKey>(mapping: ConnectOptions<TKey>): n
* Onyx.disconnect(connectionID);
*
* @param connectionID unique id returned by call to Onyx.connect()
* @param [keyToRemoveFromEvictionBlocklist]
*/
declare function disconnect(connectionID: number, keyToRemoveFromEvictionBlocklist?: OnyxKey): void;

Expand All @@ -150,7 +177,7 @@ declare function disconnect(connectionID: number, keyToRemoveFromEvictionBlockli
* @param key ONYXKEY to set
* @param value value to store
*/
declare function set<TKey extends OnyxKey>(key: TKey, value: KeyValueMapping[TKey] | null): Promise<void>;
declare function set<TKey extends OnyxKey>(key: TKey, value: OnyxEntry<KeyValueMapping[TKey]>): Promise<void>;

/**
* Sets multiple keys and values
Expand Down Expand Up @@ -220,7 +247,10 @@ declare function clear(keysToPreserve?: OnyxKey[]): Promise<void>;
* @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
* @param collection Object collection keyed by individual collection member keys and values
*/
declare function mergeCollection<TKey extends CollectionKey, TMap>(collectionKey: TKey, collection: Collection<TKey, TMap, PartialDeep<KeyValueMapping[TKey]>>): Promise<void>;
declare function mergeCollection<TKey extends CollectionKeyBase, TMap>(
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
collectionKey: TKey,
collection: Collection<TKey, TMap, PartialDeep<KeyValueMapping[TKey]>>,
): Promise<void>;

/**
* Insert API responses and lifecycle data into Onyx
Expand Down Expand Up @@ -257,13 +287,24 @@ declare function update<TKeyList extends OnyxKey[]>(data: OnyxUpdates<[...TKeyLi
*/
declare function init(config?: InitOptions): void;

/**
* @private
*/
declare function hasPendingMergeForKey(key: OnyxKey): boolean;

/**
* When set these keys will not be persisted to storage
*/
declare function setMemoryOnlyKeys(keyList: OnyxKey[]): void;

declare const Onyx: {
connect: typeof connect;
disconnect: typeof disconnect;
set: typeof set;
multiSet: typeof multiSet;
merge: typeof merge;
mergeCollection: typeof mergeCollection;
hasPendingMergeForKey: typeof hasPendingMergeForKey;
update: typeof update;
clear: typeof clear;
getAllKeys: typeof getAllKeys;
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -273,6 +314,7 @@ declare const Onyx: {
removeFromEvictionBlockList: typeof removeFromEvictionBlockList;
isSafeEvictionKey: typeof isSafeEvictionKey;
METHOD: typeof METHOD;
setMemoryOnlyKeys: typeof setMemoryOnlyKeys;
};

export default Onyx;
4 changes: 2 additions & 2 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Onyx from './Onyx';
import {CustomTypeOptions} from './types';
import {CustomTypeOptions, OnyxCollectionEntries, OnyxEntry} from './types';
import withOnyx from './withOnyx';

export default Onyx;
export {CustomTypeOptions, withOnyx};
export {CustomTypeOptions, OnyxCollectionEntries, OnyxEntry, withOnyx};
139 changes: 125 additions & 14 deletions lib/types.d.ts
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import {IsEqual, ValueOf} from 'type-fest';
import {IsEqual} from 'type-fest';

/**
* Represents a merged object between `TObject` and `TOtherObject`.
* If both objects have a property with the same name, the type from `TOtherObject` is used.
*/
type MergeBy<TObject, TOtherObject> = Omit<TObject, keyof TOtherObject> & TOtherObject;
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Represents a deeply nested record. It maps keys to values,
* and those values can either be of type `TValue` or further nested `DeepRecord` instances.
*/
type DeepRecord<TKey extends string | number | symbol, TValue> = {[key: string]: TValue | DeepRecord<TKey, TValue>};

/**
* Represents type options to configure all Onyx methods.
* It's a combination of predefined options with user-provided options (CustomTypeOptions).
*
* The options are:
* - `keys`: Represents a string union of all Onyx normal keys.
* - `collectionKeys`: Represents a string union of all Onyx collection keys.
* - `values`: Represents a Record where each key is an Onyx key and each value is its corresponding Onyx value type.
*
* The user-defined options (CustomTypeOptions) are merged into these predefined options.
* In case of conflicting properties, the ones from CustomTypeOptions are prioritized.
*/
type TypeOptions = MergeBy<
{
keys: string;
Expand All @@ -13,28 +33,119 @@ type TypeOptions = MergeBy<
CustomTypeOptions
>;

/**
* Represents the user-defined options to configure all Onyx methods.
*
* The developer can configure Onyx methods by augmenting this library and overriding CustomTypeOptions.
*
* @example
* ```ts
* // ONYXKEYS.ts
* import {ValueOf} from 'type-fest';
*
* const ONYXKEYS = {
* ACCOUNT: 'account',
* IS_SIDEBAR_LOADED: 'isSidebarLoaded',
*
* // Collection Keys
* COLLECTION: {
* REPORT: 'report_',
* },
* } as const;
*
* type OnyxKeysMap = typeof ONYXKEYS;
* type OnyxCollectionKey = ValueOf<OnyxKeysMap['COLLECTION']>;
* type OnyxKey = DeepValueOf<Omit<OnyxKeysMap, 'COLLECTION'>>;
*
* type OnyxValues = {
* [ONYXKEYS.ACCOUNT]: Account;
* [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean;
* [report: `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`]: Report;
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
* };
*
* export default ONYXKEYS;
* export type {OnyxKey, OnyxCollectionKey, OnyxValues};
*
* // global.d.ts
* import {OnyxKey, OnyxCollectionKey, OnyxValues} from './ONYXKEYS';
*
* declare module 'react-native-onyx' {
* interface CustomTypeOptions {
* keys: OnyxKey;
* collectionKeys: OnyxCollectionKey;
* values: OnyxValues;
* }
* }
* ```
*/
interface CustomTypeOptions {}

/**
* Represents a string union of all Onyx normal keys.
*/
type Key = TypeOptions['keys'];

/**
* Represents a string union of all Onyx collection keys.
*/
type CollectionKeyBase = TypeOptions['collectionKeys'];

/**
* Represents a literal string union of all Onyx collection keys.
* It allows appending a string after each collection key e.g. `report_some-id`.
*/
type CollectionKey = `${CollectionKeyBase}${string}`;

/**
* Represents a string union of all Onyx normal and collection keys.
*/
type OnyxKey = Key | CollectionKey;

/**
* Represents a Record where each key is an Onyx key and each value is its corresponding Onyx value type.
*/
type KeyValueMapping = TypeOptions['values'];

type GetOnyxValue<K extends Key | `${CollectionKeyBase}${string}`> = K extends Key
? KeyValueMapping[K]
: K extends `${CollectionKeyBase}`
? Record<string, KeyValueMapping[K] | null>
: KeyValueMapping[K];
/**
* Represents a selector function type which operates based on the provided `TKey` and `ReturnType`.
*
* A `Selector` is a function that accepts a value and returns a processed value.
* This type accepts two type parameters: `TKey` and `TReturnType`.
*
* The type `TKey` extends `OnyxKey` and it is the key used to access a value in `KeyValueMapping`.
* `TReturnType` is the type of the returned value from the selector function.
*
* If `KeyValueMapping[TKey]` is equal to 'unknown', the `Selector` type represents a function
* that takes any value and returns an `unknown` value.
*
* If `KeyValueMapping[TKey]` is not 'unknown', the `Selector` type represents a function that
* takes either the value of type `OnyxEntry<KeyValueMapping[TKey]>`, and returns a value of `TReturnType`.
*/
type Selector<TKey extends OnyxKey, TReturnType> = IsEqual<KeyValueMapping[TKey], unknown> extends true
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
? (value: unknown) => unknown
: (value: OnyxEntry<KeyValueMapping[TKey]>) => TReturnType;

// type Selector<TKey extends OnyxKey, TReturnType> = KeyValueMapping[TKey] extends object | string | number | boolean
// ? (value: KeyValueMapping[TKey] | null) => TReturnType
// : KeyValueMapping[TKey] extends unknown
// ? (value: unknown) => unknown
// : never;
/**
* Represents a single Onyx entry, that can be either `TOnyxValue` or `null` if it doesn't exist.
*/
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
type OnyxEntry<TOnyxValue> = TOnyxValue | null;

// type Selector<TKey extends OnyxKey, TReturnType> = IsEqual<KeyValueMapping[TKey], unknown> extends true ? (value: unknown) => unknown : (value: KeyValueMapping[TKey] | null) => TReturnType;
type Selector<TKey extends OnyxKey, TReturnType> = IsEqual<GetOnyxValue<TKey>, unknown> extends true ? (value: unknown) => unknown : (value: GetOnyxValue<TKey> | null) => TReturnType;
/**
* Represents an Onyx collection of entries, that can be either a record of `TOnyxValue`s or `null` if it is empty or doesn't exist.
*/
type OnyxCollectionEntries<TOnyxValue> = OnyxEntry<Record<string, TOnyxValue | null>>;
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved

export {CollectionKey, CustomTypeOptions, DeepRecord, Key, MergeBy, OnyxKey, KeyValueMapping, Selector, GetOnyxValue, TypeOptions};
export {
CollectionKey,
CollectionKeyBase,
CustomTypeOptions,
DeepRecord,
Key,
KeyValueMapping,
MergeBy,
OnyxKey,
Selector,
TypeOptions,
OnyxEntry,
OnyxCollectionEntries,
};
Loading