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 7 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
16 changes: 8 additions & 8 deletions lib/OnyxCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ class OnyxCache {
private recentKeys: Set<OnyxKey>;

/** A map of cached values */
private storageMap: Record<OnyxKey, OnyxValue>;
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<OnyxValue | OnyxKey[]>>;
private pendingPromises: Map<string, Promise<OnyxValue<OnyxKey> | OnyxKey[]>>;

/** Maximum size of the keys store din cache */
private maxRecentKeysSize = 0;
Expand Down Expand Up @@ -60,7 +60,7 @@ class OnyxCache {
* 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: OnyxKey, shouldReindexCache = true): OnyxValue {
getValue(key: OnyxKey, shouldReindexCache = true): OnyxValue<OnyxKey> {
if (shouldReindexCache) {
this.addToAccessedKeys(key);
}
Expand All @@ -83,7 +83,7 @@ class OnyxCache {
* Set's a key value in cache
* Adds the key to the storage keys list as well
*/
set(key: OnyxKey, value: OnyxValue): OnyxValue {
set(key: OnyxKey, value: OnyxValue<OnyxKey>): OnyxValue<OnyxKey> {
this.addKey(key);
this.addToAccessedKeys(key);
this.storageMap[key] = value;
Expand All @@ -102,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: Record<OnyxKey, OnyxValue>): 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 Down Expand Up @@ -144,7 +144,7 @@ class OnyxCache {
* provided from this function
* @param taskName - unique name given for the task
*/
getTaskPromise(taskName: string): Promise<OnyxValue | OnyxKey[]> | undefined {
getTaskPromise(taskName: string): Promise<OnyxValue<OnyxKey> | OnyxKey[]> | undefined {
return this.pendingPromises.get(taskName);
}

Expand All @@ -153,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<OnyxValue>): Promise<OnyxValue> {
captureTask(taskName: string, promise: Promise<OnyxValue<OnyxKey>>): Promise<OnyxValue<OnyxKey>> {
const returnPromise = promise.finally(() => {
this.pendingPromises.delete(taskName);
});
Expand Down Expand Up @@ -196,7 +196,7 @@ class OnyxCache {
}

/** Check if the value has changed */
hasValueChanged(key: OnyxKey, value: OnyxValue): boolean {
hasValueChanged(key: OnyxKey, value: OnyxValue<OnyxKey>): boolean {
return !deepEqual(this.storageMap[key], value);
}
}
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,
};
8 changes: 4 additions & 4 deletions lib/storage/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import utils from '../../utils';
import type {KeyValuePairList} from '../providers/types';
import type StorageProvider from '../providers/types';

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

const set = jest.fn((key, value) => {
storageMapInternal[key] = value;
Expand All @@ -20,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 @@ -35,7 +35,7 @@ const idbKeyvalMock: StorageProvider = {
multiMerge(pairs) {
pairs.forEach(([key, value]) => {
const existingValue = storageMapInternal[key];
const newValue = utils.fastMerge(existingValue!, value!);
const newValue = utils.fastMerge(existingValue, value);

set(key, newValue);
});
Expand Down
6 changes: 3 additions & 3 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 {OnyxValue} 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,12 +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<OnyxValue>(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];
const newValue = utils.fastMerge(prev!, value!);
const newValue = utils.fastMerge(prev, value);
return promisifyRequest(store.put(newValue, key));
});
return Promise.all(upsertMany);
Expand Down
10 changes: 5 additions & 5 deletions lib/storage/providers/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import type {BatchQueryResult, QueryResult} from 'react-native-quick-sqlite';
import type {OnyxKey, OnyxValue} from '../../types';

type KeyValuePair = [OnyxKey, OnyxValue];
type KeyValuePair = [OnyxKey, OnyxValue<OnyxKey>];
type KeyList = OnyxKey[];
type KeyValuePairList = KeyValuePair[];

type OnStorageKeyChanged = (key: OnyxKey, value: OnyxValue | 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: OnyxKey) => Promise<OnyxValue | 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 @@ -21,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: OnyxKey, value: OnyxValue) => 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 @@ -38,7 +38,7 @@ type StorageProvider = {
* @param changes - the delta for a specific key
* @param modifiedData - the pre-merged data from `Onyx.applyMerge`
*/
mergeItem: (key: OnyxKey, changes: OnyxValue, modifiedData: OnyxValue) => 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 Down
2 changes: 1 addition & 1 deletion lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ type OnyxKey = Key | CollectionKey;
/**
* Represents a Onyx value that can be either a single entry or a collection of entries, depending on the `TKey` provided.
*/
type OnyxValue<TKey extends OnyxKey = OnyxKey> = TKey extends CollectionKeyBase ? OnyxCollection<KeyValueMapping[TKey]> : OnyxEntry<KeyValueMapping[TKey]>;
type OnyxValue<TKey extends OnyxKey> = TKey extends CollectionKeyBase ? OnyxCollection<KeyValueMapping[TKey]> : OnyxEntry<KeyValueMapping[TKey]>;

/**
* Represents a mapping of Onyx keys to values, where keys are either normal or collection Onyx keys
Expand Down
22 changes: 11 additions & 11 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/prefer-for-of */
import type {OnyxKey} from './types';
import type {OnyxEntry, OnyxKey, OnyxValue} from './types';

type EmptyObject = Record<string, never>;
type EmptyValue = EmptyObject | null | undefined;
Expand All @@ -14,7 +14,7 @@ function isEmptyObject<T>(obj: T | EmptyValue): obj is EmptyValue {
/**
* Checks whether the given value can be merged. It has to be an object, but not an array, RegExp or Date.
*/
function isMergeableObject(value: unknown): boolean {
function isMergeableObject(value: unknown): value is Record<string, unknown> {
const nonNullObject = value != null ? typeof value === 'object' : false;
return nonNullObject && Object.prototype.toString.call(value) !== '[object RegExp]' && Object.prototype.toString.call(value) !== '[object Date]' && !Array.isArray(value);
}
Expand All @@ -26,15 +26,15 @@ function isMergeableObject(value: unknown): boolean {
* @param shouldRemoveNullObjectValues - If true, null object values will be removed.
* @returns - The merged object.
*/
function mergeObject<TTarget extends unknown[] | Record<string, unknown>>(target: TTarget, source: TTarget, shouldRemoveNullObjectValues = true): TTarget {
function mergeObject<TValue extends OnyxValue<OnyxKey>>(target: OnyxEntry<TValue>, source: TValue, shouldRemoveNullObjectValues = true): TValue {
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
const destination: Record<string, unknown> = {};
if (isMergeableObject(target)) {
// lodash adds a small overhead so we don't use it here
const targetKeys = Object.keys(target);
for (let i = 0; i < targetKeys.length; ++i) {
const key = targetKeys[i];
const sourceValue = source?.[key as keyof TTarget];
const targetValue = target?.[key as keyof TTarget];
const sourceValue = source?.[key];
const targetValue = target?.[key];

// If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
const isSourceOrTargetNull = targetValue === null || sourceValue === null;
Expand All @@ -46,11 +46,11 @@ function mergeObject<TTarget extends unknown[] | Record<string, unknown>>(target
}
}

const sourceKeys = Object.keys(source);
const sourceKeys = Object.keys(source!);
for (let i = 0; i < sourceKeys.length; ++i) {
const key = sourceKeys[i];
const sourceValue = source?.[key as keyof TTarget];
const targetValue = target?.[key as keyof TTarget];
const sourceValue = source?.[key];
const targetValue = target?.[key];

// If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
const shouldOmitSourceKey = shouldRemoveNullObjectValues && sourceValue === null;
Expand All @@ -63,14 +63,14 @@ function mergeObject<TTarget extends unknown[] | Record<string, unknown>>(target

if (isSourceKeyMergable && targetValue) {
// eslint-disable-next-line no-use-before-define
destination[key] = fastMerge(targetValue, sourceValue as typeof targetValue, shouldRemoveNullObjectValues);
destination[key] = fastMerge(targetValue as OnyxValue<OnyxKey>, sourceValue, shouldRemoveNullObjectValues);
} else if (!shouldRemoveNullObjectValues || sourceValue !== null) {
destination[key] = sourceValue;
}
}
}

return destination as TTarget;
return destination as TValue;
}

/**
Expand All @@ -80,7 +80,7 @@ function mergeObject<TTarget extends unknown[] | Record<string, unknown>>(target
* On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
* To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
*/
function fastMerge<TTarget extends unknown[] | Record<string, unknown>>(target: TTarget, source: TTarget, shouldRemoveNullObjectValues = true): TTarget {
function fastMerge<TValue extends OnyxValue<OnyxKey>>(target: TValue, source: TValue, shouldRemoveNullObjectValues = true): TValue {
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
// We have to ignore arrays and nullish values here,
// otherwise "mergeObject" will throw an error,
// because it expects an object as "source"
Expand Down
Loading