Skip to content

Commit

Permalink
Merge pull request Expensify#507 from blazejkustra/ts/onyx-js-prepara…
Browse files Browse the repository at this point in the history
…tion

[TS migration] Prepare remaining files for Onyx.js migration
  • Loading branch information
MariaHCD authored Mar 15, 2024
2 parents b0766c2 + 8379d9a commit 4f9cebf
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 64 deletions.
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

0 comments on commit 4f9cebf

Please sign in to comment.