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

[TS migration] Prepare remaining files for Onyx.js migration #507

Merged
merged 18 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions lib/DevTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class DevTools {
if ((options && options.remote) || typeof window === 'undefined' || !reduxDevtools) {
return;
}
// eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-explicit-any

return reduxDevtools.connect(options);
} catch (e) {
console.error(ERROR_LABEL, e);
Expand Down Expand Up @@ -90,7 +90,7 @@ class DevTools {
/**
* This clears the internal state of the DevTools, preserving the keys included in `keysToPreserve`
*/
public clearState(keysToPreserve: string[] = []): void {
clearState(keysToPreserve: string[] = []): void {
const newState = Object.entries(this.state).reduce((obj: Record<string, unknown>, [key, value]) => {
// eslint-disable-next-line no-param-reassign
obj[key] = keysToPreserve.includes(key) ? value : this.defaultState[key];
Expand Down
1 change: 1 addition & 0 deletions lib/Onyx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type BaseConnectOptions = {
statePropertyName?: string;
withOnyxInstance?: Component;
initWithStoredValues?: boolean;
displayName?: string;
};

type TryGetCachedValueMapping<TKey extends OnyxKey> = {
Expand Down
36 changes: 17 additions & 19 deletions lib/OnyxCache.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
import {deepEqual} from 'fast-equals';
import bindAll from 'lodash/bindAll';
import type {Key, Value} from './storage/providers/types';
import utils from './utils';

type StorageMap = Record<Key, Value>;
import type {OnyxKey, OnyxValue} from './types';

/**
* In memory cache providing data by reference
* Encapsulates Onyx cache related functionality
*/
class OnyxCache {
/** Cache of all the storage keys available in persistent storage */
private storageKeys: Set<Key>;
storageKeys: Set<OnyxKey>;

/** Unique list of keys maintained in access order (most recent at the end) */
private recentKeys: Set<Key>;
private recentKeys: Set<OnyxKey>;

/** A map of cached values */
private storageMap: StorageMap;
private storageMap: Record<OnyxKey, OnyxValue<OnyxKey>>;

/**
* Captured pending tasks for already running storage methods
* Using a map yields better performance on operations such a delete
*/
private pendingPromises: Map<string, Promise<unknown>>;
private pendingPromises: Map<string, Promise<OnyxValue<OnyxKey> | OnyxKey[]>>;

/** Maximum size of the keys store din cache */
private maxRecentKeysSize = 0;
Expand Down Expand Up @@ -54,38 +52,38 @@ class OnyxCache {
}

/** Get all the storage keys */
getAllKeys(): Set<Key> {
getAllKeys(): Set<OnyxKey> {
return this.storageKeys;
}

/**
* Get a cached value from storage
* @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
*/
getValue(key: Key, shouldReindexCache = true): Value {
getValue(key: OnyxKey, shouldReindexCache = true): OnyxValue<OnyxKey> {
if (shouldReindexCache) {
this.addToAccessedKeys(key);
}
return this.storageMap[key];
}

/** Check whether cache has data for the given key */
hasCacheForKey(key: Key): boolean {
hasCacheForKey(key: OnyxKey): boolean {
return this.storageMap[key] !== undefined;
}

/** Saves a key in the storage keys list
* Serves to keep the result of `getAllKeys` up to date
*/
addKey(key: Key): void {
addKey(key: OnyxKey): void {
this.storageKeys.add(key);
}

/**
* Set's a key value in cache
* Adds the key to the storage keys list as well
*/
set(key: Key, value: Value): Value {
set(key: OnyxKey, value: OnyxValue<OnyxKey>): OnyxValue<OnyxKey> {
this.addKey(key);
this.addToAccessedKeys(key);
this.storageMap[key] = value;
Expand All @@ -94,7 +92,7 @@ class OnyxCache {
}

/** Forget the cached value for the given key */
drop(key: Key): void {
drop(key: OnyxKey): void {
delete this.storageMap[key];
this.storageKeys.delete(key);
this.recentKeys.delete(key);
Expand All @@ -104,7 +102,7 @@ class OnyxCache {
* Deep merge data to cache, any non existing keys will be created
* @param data - a map of (cache) key - values
*/
merge(data: StorageMap): void {
merge(data: Record<OnyxKey, OnyxValue<OnyxKey>>): void {
if (typeof data !== 'object' || Array.isArray(data)) {
throw new Error('data passed to cache.merge() must be an Object of onyx key/value pairs');
}
Expand All @@ -128,7 +126,7 @@ class OnyxCache {
*
* @param keys - an array of keys
*/
setAllKeys(keys: Key[]) {
setAllKeys(keys: OnyxKey[]) {
this.storageKeys = new Set(keys);
}

Expand All @@ -146,7 +144,7 @@ class OnyxCache {
* provided from this function
* @param taskName - unique name given for the task
*/
getTaskPromise(taskName: string): Promise<unknown> | undefined {
getTaskPromise(taskName: string): Promise<OnyxValue<OnyxKey> | OnyxKey[]> | undefined {
return this.pendingPromises.get(taskName);
}

Expand All @@ -155,7 +153,7 @@ class OnyxCache {
* hook up to the promise if it's still pending
* @param taskName - unique name for the task
*/
captureTask(taskName: string, promise: Promise<unknown>): Promise<unknown> {
captureTask(taskName: string, promise: Promise<OnyxValue<OnyxKey>>): Promise<OnyxValue<OnyxKey>> {
const returnPromise = promise.finally(() => {
this.pendingPromises.delete(taskName);
});
Expand All @@ -166,7 +164,7 @@ class OnyxCache {
}

/** Adds a key to the top of the recently accessed keys */
private addToAccessedKeys(key: Key): void {
addToAccessedKeys(key: OnyxKey): void {
this.recentKeys.delete(key);
this.recentKeys.add(key);
}
Expand Down Expand Up @@ -198,7 +196,7 @@ class OnyxCache {
}

/** Check if the value has changed */
hasValueChanged(key: Key, value: Value): boolean {
hasValueChanged(key: OnyxKey, value: OnyxValue<OnyxKey>): boolean {
return !deepEqual(this.storageMap[key], value);
}
}
Expand Down
9 changes: 3 additions & 6 deletions lib/PerformanceUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import lodashTransform from 'lodash/transform';
import {deepEqual} from 'fast-equals';
import type {OnyxKey} from './types';
import type {ConnectOptions} from './Onyx';

type UnknownObject = Record<string, unknown>;

Expand All @@ -10,11 +12,6 @@ type LogParams = {
newValue?: unknown;
};

type Mapping = Record<string, unknown> & {
key: string;
displayName: string;
};

let debugSetState = false;

function setShouldDebugSetState(debug: boolean) {
Expand Down Expand Up @@ -44,7 +41,7 @@ function diffObject<TObject extends UnknownObject, TBase extends UnknownObject>(
/**
* Provide insights into why a setState() call occurred by diffing the before and after values.
*/
function logSetStateCall(mapping: Mapping, previousValue: unknown, newValue: unknown, caller: string, keyThatChanged: string) {
function logSetStateCall<TKey extends OnyxKey>(mapping: ConnectOptions<TKey>, previousValue: unknown, newValue: unknown, caller: string, keyThatChanged?: string) {
if (!debugSetState) {
return;
}
Expand Down
19 changes: 17 additions & 2 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import Onyx from './Onyx';
import type {OnyxUpdate, ConnectOptions} from './Onyx';
import type {CustomTypeOptions, OnyxCollection, OnyxEntry, NullishDeep, KeyValueMapping, OnyxKey, Selector, WithOnyxInstanceState} from './types';
import type {CustomTypeOptions, OnyxCollection, OnyxEntry, NullishDeep, KeyValueMapping, OnyxKey, Selector, WithOnyxInstanceState, OnyxValue} from './types';
import type {UseOnyxResult, FetchStatus} from './useOnyx';
import useOnyx from './useOnyx';
import withOnyx from './withOnyx';

export default Onyx;
export {withOnyx, useOnyx};
export type {CustomTypeOptions, OnyxCollection, OnyxEntry, OnyxUpdate, ConnectOptions, NullishDeep, KeyValueMapping, OnyxKey, Selector, WithOnyxInstanceState};
export type {
CustomTypeOptions,
OnyxCollection,
OnyxEntry,
OnyxUpdate,
ConnectOptions,
NullishDeep,
KeyValueMapping,
OnyxKey,
Selector,
WithOnyxInstanceState,
UseOnyxResult,
OnyxValue,
FetchStatus,
};
5 changes: 3 additions & 2 deletions lib/storage/WebStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
* data changes and then stay up-to-date with everything happening in Onyx.
*/
import type {OnyxKey} from '../types';
import Storage from './providers/IDBKeyVal';
import type {KeyList, Key} from './providers/types';
import type {KeyList} from './providers/types';
import type StorageProvider from './providers/types';

const SYNC_ONYX = 'SYNC_ONYX';

/**
* Raise an event thorough `localStorage` to let other tabs know a value changed
*/
function raiseStorageSyncEvent(onyxKey: Key) {
function raiseStorageSyncEvent(onyxKey: OnyxKey) {
global.localStorage.setItem(SYNC_ONYX, onyxKey);
global.localStorage.removeItem(SYNC_ONYX);
}
Expand Down
12 changes: 6 additions & 6 deletions lib/storage/__mocks__/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type {OnyxKey, OnyxValue} from '../../types';
import utils from '../../utils';
import type {Key, KeyValuePairList, Value} from '../providers/types';
import type {KeyValuePairList} from '../providers/types';
import type StorageProvider from '../providers/types';

let storageMapInternal: Record<Key, Value> = {};
let storageMapInternal: Record<OnyxKey, OnyxValue<OnyxKey>> = {};

const set = jest.fn((key, value) => {
storageMapInternal[key] = value;
Expand All @@ -19,8 +20,8 @@ const idbKeyvalMock: StorageProvider = {
Promise.all(setPromises).then(() => resolve(storageMapInternal));
});
},
getItem(key) {
return Promise.resolve(storageMapInternal[key]);
getItem<TKey extends OnyxKey>(key: TKey) {
return Promise.resolve(storageMapInternal[key] as OnyxValue<TKey>);
},
multiGet(keys) {
const getPromises = keys.map(
Expand All @@ -34,8 +35,7 @@ const idbKeyvalMock: StorageProvider = {
multiMerge(pairs) {
pairs.forEach(([key, value]) => {
const existingValue = storageMapInternal[key];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newValue = utils.fastMerge(existingValue as any, value);
const newValue = utils.fastMerge(existingValue as Record<string, unknown>, value as Record<string, unknown>);

set(key, newValue);
});
Expand Down
7 changes: 3 additions & 4 deletions lib/storage/providers/IDBKeyVal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {UseStore} from 'idb-keyval';
import {set, keys, getMany, setMany, get, clear, del, delMany, createStore, promisifyRequest} from 'idb-keyval';
import utils from '../../utils';
import type StorageProvider from './types';
import type {Value} from './types';
import type {OnyxKey, OnyxValue} from '../../types';

// We don't want to initialize the store while the JS bundle loads as idb-keyval will try to use global.indexedDB
// which might not be available in certain environments that load the bundle (e.g. electron main process).
Expand All @@ -21,13 +21,12 @@ const provider: StorageProvider = {
getCustomStore()('readwrite', (store) => {
// Note: we are using the manual store transaction here, to fit the read and update
// of the items in one transaction to achieve best performance.
const getValues = Promise.all(pairs.map(([key]) => promisifyRequest<Value>(store.get(key))));
const getValues = Promise.all(pairs.map(([key]) => promisifyRequest<OnyxValue<OnyxKey>>(store.get(key))));

return getValues.then((values) => {
const upsertMany = pairs.map(([key, value], index) => {
const prev = values[index];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newValue = utils.fastMerge(prev as any, value);
const newValue = utils.fastMerge(prev as Record<string, unknown>, value as Record<string, unknown>);
return promisifyRequest(store.put(newValue, key));
});
return Promise.all(upsertMany);
Expand Down
19 changes: 9 additions & 10 deletions lib/storage/providers/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import type {BatchQueryResult, QueryResult} from 'react-native-quick-sqlite';
import type {OnyxKey, OnyxValue} from '../../types';

type Key = string;
type Value = IDBValidKey;
type KeyValuePair = [Key, Value];
type KeyList = Key[];
type KeyValuePair = [OnyxKey, OnyxValue<OnyxKey>];
type KeyList = OnyxKey[];
type KeyValuePairList = KeyValuePair[];

type OnStorageKeyChanged = (key: Key, value: Value | null) => void;
type OnStorageKeyChanged = <TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey> | null) => void;

type StorageProvider = {
/**
* Gets the value of a given key or return `null` if it's not available in storage
*/
getItem: (key: Key) => Promise<Value | null>;
getItem: <TKey extends OnyxKey>(key: TKey) => Promise<OnyxValue<TKey> | null>;

/**
* Get multiple key-value pairs for the given array of keys in a batch
Expand All @@ -22,7 +21,7 @@ type StorageProvider = {
/**
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
*/
setItem: (key: Key, value: Value) => Promise<QueryResult | void>;
setItem: <TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>) => Promise<QueryResult | void>;

/**
* Stores multiple key-value pairs in a batch
Expand All @@ -39,7 +38,7 @@ type StorageProvider = {
* @param changes - the delta for a specific key
* @param modifiedData - the pre-merged data from `Onyx.applyMerge`
*/
mergeItem: (key: Key, changes: Value, modifiedData: Value) => Promise<BatchQueryResult | void>;
mergeItem: <TKey extends OnyxKey>(key: TKey, changes: OnyxValue<TKey>, modifiedData: OnyxValue<TKey>) => Promise<BatchQueryResult | void>;

/**
* Returns all keys available in storage
Expand All @@ -49,7 +48,7 @@ type StorageProvider = {
/**
* Removes given key and its value from storage
*/
removeItem: (key: Key) => Promise<QueryResult | void>;
removeItem: (key: OnyxKey) => Promise<QueryResult | void>;

/**
* Removes given keys and their values from storage
Expand All @@ -73,4 +72,4 @@ type StorageProvider = {
};

export default StorageProvider;
export type {Value, Key, KeyList, KeyValuePairList};
export type {KeyList, KeyValuePair, KeyValuePairList};
Loading
Loading