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] Migrate Onyx public methods to TS #512

Merged
merged 23 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ed93e94
Remove memory only keys logic
blazejkustra Mar 11, 2024
396e926
2.0.22
OSBotify Mar 12, 2024
db5c103
Split Onyx.js into two files
blazejkustra Mar 12, 2024
62476eb
Merge branch 'ts/onyx-js-preparation' into ts/Onyx-public-methods
blazejkustra Mar 13, 2024
c924a56
Import from OnyxUtils
blazejkustra Mar 13, 2024
7a65a45
Move types to types.ts, reorder functions
blazejkustra Mar 13, 2024
430825e
Change logSetStateCall argument type
blazejkustra Mar 13, 2024
31d0a78
Export OnyxUtils as default export
blazejkustra Mar 13, 2024
4437e63
Move part of Onyx.init code to OnyxUtils
blazejkustra Mar 13, 2024
f2cb2ee
Merge branch 'ts/onyx-js-preparation' into ts/Onyx-public-methods
blazejkustra Mar 14, 2024
b747b18
Fix public methods declarations
blazejkustra Mar 14, 2024
261021d
Merge branch 'main' into ts/Onyx-public-methods
blazejkustra Mar 15, 2024
566baa4
Fix type errors
blazejkustra Mar 15, 2024
d4a2003
Adjust return types
blazejkustra Mar 15, 2024
b4a6af7
Small fixes after initial review
blazejkustra Mar 19, 2024
bb10ff9
Fix imports in withOnyx
blazejkustra Mar 19, 2024
83b9194
Fix tests, convert Set to array
blazejkustra Mar 19, 2024
d479c06
Fix issues with getAllKeys
blazejkustra Mar 19, 2024
8c9b867
Fix comment, from OnyxUtils to Omnyx
blazejkustra Mar 19, 2024
c6c7a84
Fix a bug that default values weren't set properly
blazejkustra Mar 19, 2024
0e9acd3
Bring back JSDoc for conect()
blazejkustra Mar 20, 2024
9dc8ed2
Add getters to OnyxUtils
blazejkustra Mar 20, 2024
78b00ca
Address minor comments
blazejkustra Mar 20, 2024
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
56 changes: 35 additions & 21 deletions lib/Onyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,20 @@ function init({
* @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
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
* @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.
* 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).
* @param [mapping.initialValue] THIS PARAM IS ONLY USED WITH withOnyx().
* If included, this will be passed to the component so that something can be rendered while data is being fetched from the DB.
* Note that it will not cause the component to have the loading prop set to true. |
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
* @returns an ID to use when calling disconnect
*/
function connect<TKey extends OnyxKey>(mapping: ConnectOptions<TKey>): number {
const connectionID = lastConnectionID++;
OnyxUtils.callbackToStateMapping[connectionID] = mapping;
OnyxUtils.callbackToStateMapping[connectionID].connectionID = connectionID;
const callbackToStateMapping = OnyxUtils.getCallbackToStateMapping();
callbackToStateMapping[connectionID] = mapping;
callbackToStateMapping[connectionID].connectionID = connectionID;

if (mapping.initWithStoredValues === false) {
return connectionID;
Expand Down Expand Up @@ -175,7 +183,8 @@ function connect<TKey extends OnyxKey>(mapping: ConnectOptions<TKey>): number {
* @param connectionID unique id returned by call to Onyx.connect()
*/
function disconnect(connectionID: number, keyToRemoveFromEvictionBlocklist?: OnyxKey): void {
if (!OnyxUtils.callbackToStateMapping[connectionID]) {
const callbackToStateMapping = OnyxUtils.getCallbackToStateMapping();
if (!callbackToStateMapping[connectionID]) {
return;
}

Expand All @@ -185,7 +194,7 @@ function disconnect(connectionID: number, keyToRemoveFromEvictionBlocklist?: Ony
OnyxUtils.removeFromEvictionBlockList(keyToRemoveFromEvictionBlocklist, connectionID);
}

delete OnyxUtils.callbackToStateMapping[connectionID];
delete callbackToStateMapping[connectionID];
}

/**
Expand All @@ -199,7 +208,7 @@ function set<TKey extends OnyxKey>(key: TKey, value: OnyxEntry<KeyValueMapping[T
const {value: valueAfterRemoving, wasRemoved} = OnyxUtils.removeNullValues(key, value);

if (OnyxUtils.hasPendingMergeForKey(key)) {
delete OnyxUtils.mergeQueue[key];
delete OnyxUtils.getMergeQueue()[key];
}

const hasChanged = cache.hasValueChanged(key, valueAfterRemoving);
Expand Down Expand Up @@ -263,38 +272,41 @@ function multiSet(data: Partial<NullableKeyValueMapping>): Promise<void[]> {
* Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
*/
function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxEntry<NullishDeep<KeyValueMapping[TKey]>>): Promise<void | void[]> {
const mergeQueue = OnyxUtils.getMergeQueue();
const mergeQueuePromise = OnyxUtils.getMergeQueuePromise();

// Top-level undefined values are ignored
// Therefore we need to prevent adding them to the merge queue
if (changes === undefined) {
return OnyxUtils.mergeQueue[key] ? OnyxUtils.mergeQueuePromise[key] : Promise.resolve();
return mergeQueue[key] ? mergeQueuePromise[key] : Promise.resolve();
}

// Merge attempts are batched together. The delta should be applied after a single call to get() to prevent a race condition.
// Using the initial value from storage in subsequent merge attempts will lead to an incorrect final merged value.
if (OnyxUtils.mergeQueue[key]) {
OnyxUtils.mergeQueue[key].push(changes);
return OnyxUtils.mergeQueuePromise[key];
if (mergeQueue[key]) {
mergeQueue[key].push(changes);
return mergeQueuePromise[key];
}
OnyxUtils.mergeQueue[key] = [changes];
mergeQueue[key] = [changes];

OnyxUtils.mergeQueuePromise[key] = OnyxUtils.get(key).then((existingValue) => {
mergeQueuePromise[key] = OnyxUtils.get(key).then((existingValue) => {
// Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
if (OnyxUtils.mergeQueue[key] == null) {
if (mergeQueue[key] == null) {
return undefined;
}

try {
// We first only merge the changes, so we can provide these to the native implementation (SQLite uses only delta changes in "JSON_PATCH" to merge)
// We don't want to remove null values from the "batchedChanges", because SQLite uses them to remove keys from storage natively.
let batchedChanges = OnyxUtils.applyMerge(undefined, OnyxUtils.mergeQueue[key], false);
let batchedChanges = OnyxUtils.applyMerge(undefined, mergeQueue[key], false);

// The presence of a `null` in the merge queue instructs us to drop the existing value.
// In this case, we can't simply merge the batched changes with the existing value, because then the null in the merge queue would have no effect
const shouldOverwriteExistingValue = OnyxUtils.mergeQueue[key].includes(null);
const shouldOverwriteExistingValue = mergeQueue[key].includes(null);

// Clean up the write queue, so we don't apply these changes again
delete OnyxUtils.mergeQueue[key];
delete OnyxUtils.mergeQueuePromise[key];
delete mergeQueue[key];
delete mergeQueuePromise[key];

// If the batched changes equal null, we want to remove the key from storage, to reduce storage size
const {wasRemoved} = OnyxUtils.removeNullValues(key, batchedChanges);
Expand Down Expand Up @@ -332,7 +344,7 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxEntry<NullishDeep<K
}
});

return OnyxUtils.mergeQueuePromise[key];
return mergeQueuePromise[key];
}

/**
Expand Down Expand Up @@ -464,15 +476,16 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void[]> {
// to null would cause unknown behavior)
keys.forEach((key) => {
const isKeyToPreserve = keysToPreserve.includes(key);
const isDefaultKey = key in OnyxUtils.defaultKeyStates;
const defaultKeyStates = OnyxUtils.getDefaultKeyStates();
const isDefaultKey = key in defaultKeyStates;

// If the key is being removed or reset to default:
// 1. Update it in the cache
// 2. Figure out whether it is a collection key or not,
// since collection key subscribers need to be updated differently
if (!isKeyToPreserve) {
const oldValue = cache.getValue(key);
const newValue = OnyxUtils.defaultKeyStates[key] ?? null;
const newValue = defaultKeyStates[key] ?? null;
if (newValue !== oldValue) {
cache.set(key, newValue);
const collectionKey = key.substring(0, key.indexOf('_') + 1);
Expand Down Expand Up @@ -505,12 +518,13 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void[]> {
updatePromises.push(OnyxUtils.scheduleNotifyCollectionSubscribers(key, value));
});

const defaultKeyStates = OnyxUtils.getDefaultKeyStates();
const defaultKeyValuePairs = Object.entries(
Object.keys(OnyxUtils.defaultKeyStates)
Object.keys(defaultKeyStates)
.filter((key) => !keysToPreserve.includes(key))
.reduce((obj: NullableKeyValueMapping, key) => {
// eslint-disable-next-line no-param-reassign
obj[key] = OnyxUtils.defaultKeyStates[key];
obj[key] = defaultKeyStates[key];
return obj;
}, {}),
);
Expand Down
29 changes: 25 additions & 4 deletions lib/OnyxUtils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ declare let defaultKeyStates: Record<OnyxKey, OnyxValue<OnyxKey>>;
declare let batchUpdatesPromise: Promise<void> | null;
declare let batchUpdatesQueue: Array<() => void>;

/** Getter - returns the merge queue. */
declare function getMergeQueue(): Record<string, OnyxValue<string>[]>;

/** Getter - returns the merge queue promise. */
declare function getMergeQueuePromise(): Record<string, Promise<void | void[]>>;

/** Getter - returns the callback to state mapping. */
declare function getCallbackToStateMapping(): Record<string, Mapping<string>>;

/** Getter - returns the default key states. */
declare function getDefaultKeyStates(): Record<string, OnyxValue<string>>;

/**
* Sets the initial values for the Onyx store
*
* @param keys - `ONYXKEYS` constants object from Onyx.init()
* @param initialKeyStates - initial data to set when `init()` and `clear()` are called
* @param safeEvictionKeys - This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal.
*/
declare function initStoreValues(keys: DeepRecord<string, OnyxKey>, initialKeyStates: Partial<NullableKeyValueMapping>, safeEvictionKeys: OnyxKey[]): Record<string, OnyxValue<string>>;
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved

/**
* Sends an action to DevTools extension
*
Expand Down Expand Up @@ -256,11 +277,11 @@ declare function initializeWithDefaultKeyStates(): Promise<void>;

const OnyxUtils = {
METHOD,
mergeQueue,
mergeQueuePromise,
callbackToStateMapping,
getMergeQueue,
getMergeQueuePromise,
getCallbackToStateMapping,
getDefaultKeyStates,
initStoreValues,
defaultKeyStates,
sendActionToDevTools,
maybeFlushBatchUpdates,
batchUpdates,
Expand Down
44 changes: 40 additions & 4 deletions lib/OnyxUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,42 @@ let defaultKeyStates = {};
let batchUpdatesPromise = null;
let batchUpdatesQueue = [];

/**
* Getter - returns the merge queue.
*
* @returns {Object} The callback to state mapping.
*/
function getMergeQueue() {
return mergeQueue;
}

/**
* Getter - returns the merge queue promise.
*
* @returns {Object} The callback to state mapping.
*/
function getMergeQueuePromise() {
return mergeQueuePromise;
}

/**
* Getter - returns the callback to state mapping.
*
* @returns {Object} The callback to state mapping.
*/
function getCallbackToStateMapping() {
return callbackToStateMapping;
}

/**
* Getter - returns the default key states.
*
* @returns {Object} The callback to state mapping.
*/
function getDefaultKeyStates() {
return defaultKeyStates;
}

/**
* Sets the initial values for the Onyx store
*
Expand Down Expand Up @@ -1113,10 +1149,10 @@ function initializeWithDefaultKeyStates() {

const OnyxUtils = {
METHOD,
mergeQueue,
mergeQueuePromise,
callbackToStateMapping,
defaultKeyStates,
getMergeQueue,
getMergeQueuePromise,
getCallbackToStateMapping,
getDefaultKeyStates,
initStoreValues,
sendActionToDevTools,
maybeFlushBatchUpdates,
Expand Down
2 changes: 1 addition & 1 deletion lib/useOnyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(key: TKey
}

if (!OnyxUtils.isSafeEvictionKey(key)) {
throw new Error(`canEvict can't be used on key '${key}'. This key must explicitly be flagged as safe for removal by adding it to OnyxUtils.init({safeEvictionKeys: []}).`);
throw new Error(`canEvict can't be used on key '${key}'. This key must explicitly be flagged as safe for removal by adding it to Onyx.init({safeEvictionKeys: []}).`);
}

if (options.canEvict) {
Expand Down
Loading