Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
ebfad39
Add tests for replacing old value after top-level null merges
fabioh8010 Mar 3, 2025
cf70ee0
Add tests for replacing old value after nested property null merges
fabioh8010 Mar 3, 2025
2eca1c6
Fix tests
fabioh8010 Mar 6, 2025
4e61d18
Use a temporary marker during merging to replace old values when appl…
fabioh8010 Mar 10, 2025
404af80
Change mergeItem() to not use delta changes anymore
fabioh8010 Mar 24, 2025
2d1ceb1
Change multiMerge() to not use JSON_PATCH anymore
fabioh8010 Mar 24, 2025
34bb448
Merge branch 'main' into bugfix/615-solution2
fabioh8010 Mar 28, 2025
a619757
Add comments
fabioh8010 Mar 29, 2025
aa28a4d
Add tests to applyMerge()
fabioh8010 Mar 30, 2025
934e768
Improve and fix native multiMerge logic
fabioh8010 Mar 30, 2025
1b8c804
Address small review comments
fabioh8010 Apr 6, 2025
19e2d20
Add tests for fastMerge
fabioh8010 Apr 7, 2025
befaaeb
Improve tests
fabioh8010 Apr 13, 2025
df65c33
Simplify and extract applyMerge() batching merges logic to a separate…
fabioh8010 Apr 13, 2025
6301dfd
Improve comments in Onyx.merge()
fabioh8010 Apr 13, 2025
5b676b4
Merge remote-tracking branch 'origin/main' into bugfix/615-solution2
fabioh8010 Apr 22, 2025
bd928cd
First experiments with JSON_PATCH + JSON_REPLACE solution
fabioh8010 May 12, 2025
d88c9dd
Refactor and move getReplacePatches() logic to fastMerge() / mergeObj…
fabioh8010 May 15, 2025
01e9ff4
fix: improve fastMerge code
chrispader May 15, 2025
a08b5e4
adapt fastMerge usages
chrispader May 15, 2025
d89bdd0
combine back batchMergeChanges and applyMerge
chrispader May 15, 2025
43e6665
fix: simplify Onyx.merge
chrispader May 15, 2025
f190fbd
fix: improve Onyx.update
chrispader May 15, 2025
2701874
fix: change signature of storage.mergeItem
chrispader May 15, 2025
defed16
refactor: remove return values from storage providers
chrispader May 20, 2025
a6312b1
fix: invalid fastMerge option
chrispader May 20, 2025
5f69d7a
fix: TS error
chrispader May 20, 2025
173802a
add empty lines
chrispader May 20, 2025
351cc72
add missing continue
chrispader May 20, 2025
b7c84fb
fix: simplify fastMerge
chrispader May 20, 2025
ee5d461
fix: further improve fastMerge code
chrispader May 21, 2025
6d6fbb4
remove old comment
chrispader May 21, 2025
24b2269
fix: missing key in path
chrispader May 21, 2025
4c23dba
fix: onyxUtilsTest
chrispader May 21, 2025
0744724
fix: tests
chrispader May 21, 2025
ccedbd8
further simplify code
chrispader May 21, 2025
8391cc7
keep performance merge logic on native
chrispader May 21, 2025
ed66dad
fix: invalid param for `sendActionToDevTools`
chrispader May 21, 2025
cf15718
Merge branch 'main' into bugfix/615-solution2
fabioh8010 Jun 4, 2025
46e412c
Merge pull request #3 from margelo/@chrispader/fast-merge-modififer-r…
fabioh8010 Jun 4, 2025
bdccd90
Merge branch 'main' into bugfix/615-solution2-jsonPatchReplace
fabioh8010 Jun 4, 2025
7d60502
Fix TS and tests
fabioh8010 Jun 5, 2025
d145430
More cleanup and fixes
fabioh8010 Jun 5, 2025
7b8801f
More fixes
fabioh8010 Jun 5, 2025
06304e7
Merge branch 'bugfix/615-solution2-jsonPatchReplace' into bugfix/615-…
fabioh8010 Jun 5, 2025
e577505
Remove wrong assignment
fabioh8010 Jun 5, 2025
6d7b634
Increase Jest timeout for Reassure
fabioh8010 Jun 5, 2025
e466103
Merge branch 'main' into bugfix/615-solution2
fabioh8010 Jun 9, 2025
1abaaa5
Tests with new removeNestedNullValues implementation
fabioh8010 Jun 10, 2025
46d1230
Re-run Reassure
fabioh8010 Jun 10, 2025
7fca303
Re-run Reassure
fabioh8010 Jun 10, 2025
ab9be04
Minor fix to mergeObject and add test for mergeCollection
fabioh8010 Jun 18, 2025
d731a72
Add StorageMock checks for each test and make Jest always use OnyxMer…
fabioh8010 Jun 18, 2025
7f3c4d9
Fix Onyx.update() to use mergeChanges instead of marking
fabioh8010 Jun 23, 2025
8af4d44
Add more comments
fabioh8010 Jun 23, 2025
b14f7eb
Improve types
fabioh8010 Jun 23, 2025
bd8d67e
Minor fixes
fabioh8010 Jun 23, 2025
38a5f13
Remove useless test
fabioh8010 Jun 23, 2025
d18d042
Merge branch 'main' into bugfix/615-solution2
fabioh8010 Jun 27, 2025
73626dd
Merge branch 'main' into bugfix/615-solution2
fabioh8010 Jun 30, 2025
a27d3c8
Apply feedback
fabioh8010 Jun 30, 2025
ed207df
Merge branch 'main' into bugfix/615-solution2
fabioh8010 Jul 2, 2025
32c725d
Address comments
fabioh8010 Jul 2, 2025
90d9000
Build docs
fabioh8010 Jul 2, 2025
79b018d
Move GenericDeepRecord to tests directory
fabioh8010 Jul 2, 2025
c671974
Rollback removeNestedNullValues refactor
fabioh8010 Jul 2, 2025
dc0aa25
Merge branch 'main' into bugfix/615-solution2
fabioh8010 Jul 3, 2025
466feb1
Address comments
fabioh8010 Jul 3, 2025
405f255
Implement internal mergeCollectionWithPatches function
fabioh8010 Jul 3, 2025
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
150 changes: 72 additions & 78 deletions API-INTERNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@
<dt><a href="#getDeferredInitTask">getDeferredInitTask()</a></dt>
<dd><p>Getter - returns the deffered init task.</p>
</dd>
<dt><a href="#getEvictionBlocklist">getEvictionBlocklist()</a></dt>
<dd><p>Getter - returns the eviction block list.</p>
</dd>
<dt><a href="#getSkippableCollectionMemberIDs">getSkippableCollectionMemberIDs()</a></dt>
<dd><p>Getter - returns the skippable collection member IDs.</p>
</dd>
<dt><a href="#setSkippableCollectionMemberIDs">setSkippableCollectionMemberIDs()</a></dt>
<dd><p>Setter - sets the skippable collection member IDs.</p>
</dd>
<dt><a href="#initStoreValues">initStoreValues(keys, initialKeyStates, evictableKeys)</a></dt>
<dt><a href="#initStoreValues">initStoreValues(keys, initialKeyStates, evictableKeys, fullyMergedSnapshotKeys)</a></dt>
<dd><p>Sets the initial values for the Onyx store</p>
</dd>
<dt><a href="#maybeFlushBatchUpdates">maybeFlushBatchUpdates()</a></dt>
Expand Down Expand Up @@ -71,9 +68,6 @@ is associated with a collection of keys.</p>
<dd><p>Checks to see if a provided key is the exact configured key of our connected subscriber
or if the provided key is a collection member key (in case our configured key is a &quot;collection key&quot;)</p>
</dd>
<dt><a href="#isEvictableKey">isEvictableKey()</a></dt>
<dd><p>Checks to see if this key has been flagged as safe for removal.</p>
</dd>
<dt><a href="#getCollectionKey">getCollectionKey(key)</a> ⇒</dt>
<dd><p>Extracts the collection identifier of a given collection member key.</p>
<p>For example:</p>
Expand All @@ -88,20 +82,6 @@ or if the provided key is a collection member key (in case our configured key is
<dd><p>Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
If the requested key is a collection, it will return an object with all the collection members.</p>
</dd>
<dt><a href="#removeLastAccessedKey">removeLastAccessedKey()</a></dt>
<dd><p>Remove a key from the recently accessed key list.</p>
</dd>
<dt><a href="#addLastAccessedKey">addLastAccessedKey()</a></dt>
<dd><p>Add a key to the list of recently accessed keys. The least
recently accessed key should be at the head and the most
recently accessed key at the tail.</p>
</dd>
<dt><a href="#addEvictableKeysToRecentlyAccessedList">addEvictableKeysToRecentlyAccessedList()</a></dt>
<dd><p>Take all the keys that are safe to evict and add them to
the recently accessed list when initializing the app. This
enables keys that have not recently been accessed to be
removed.</p>
</dd>
<dt><a href="#keysChanged">keysChanged()</a></dt>
<dd><p>When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks</p>
</dd>
Expand Down Expand Up @@ -139,18 +119,20 @@ whatever it is we attempted to do.</p>
<dt><a href="#broadcastUpdate">broadcastUpdate()</a></dt>
<dd><p>Notifies subscribers and writes current value to cache</p>
</dd>
<dt><a href="#removeNullValues">removeNullValues()</a> ⇒</dt>
<dd><p>Removes a key from storage if the value is null.
Otherwise removes all nested null values in objects,
if shouldRemoveNestedNulls is true and returns the object.</p>
</dd>
<dt><a href="#prepareKeyValuePairsForStorage">prepareKeyValuePairsForStorage()</a> ⇒</dt>
<dd><p>Storage expects array like: [[&quot;@MyApp_user&quot;, value_1], [&quot;@MyApp_key&quot;, value_2]]
This method transforms an object like {&#39;@MyApp_user&#39;: myUserValue, &#39;@MyApp_key&#39;: myKeyValue}
to an array of key-value pairs in the above format and removes key-value pairs that are being set to null</p>
</dd>
<dt><a href="#applyMerge">applyMerge(changes)</a></dt>
<dd><p>Merges an array of changes with an existing value</p>
<dt><a href="#mergeChanges">mergeChanges(changes, existingValue)</a></dt>
<dd><p>Merges an array of changes with an existing value or creates a single change.</p>
</dd>
<dt><a href="#mergeAndMarkChanges">mergeAndMarkChanges(changes, existingValue)</a></dt>
<dd><p>Merges an array of changes with an existing value or creates a single change.
It will also mark deep nested objects that need to be entirely replaced during the merge.</p>
</dd>
<dt><a href="#mergeInternal">mergeInternal(changes, existingValue)</a></dt>
<dd><p>Merges an array of changes with an existing value or creates a single change.</p>
</dd>
<dt><a href="#initializeWithDefaultKeyStates">initializeWithDefaultKeyStates()</a></dt>
<dd><p>Merge user provided default key value pairs.</p>
Expand All @@ -167,6 +149,14 @@ to an array of key-value pairs in the above format and removes key-value pairs t
<dt><a href="#unsubscribeFromKey">unsubscribeFromKey(subscriptionID)</a></dt>
<dd><p>Disconnects and removes the listener from the Onyx key.</p>
</dd>
<dt><a href="#mergeCollectionWithPatches">mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullPatches)</a></dt>
<dd><p>Merges a collection based on their keys.
Serves as core implementation for <code>Onyx.mergeCollection()</code> public function, the difference being
that this internal function allows passing an additional <code>mergeReplaceNullPatches</code> parameter.</p>
</dd>
<dt><a href="#clearOnyxUtilsInternals">clearOnyxUtilsInternals()</a></dt>
<dd><p>Clear internal variables used in this file, useful in test environments.</p>
</dd>
</dl>

<a name="getMergeQueue"></a>
Expand All @@ -192,12 +182,6 @@ Getter - returns the default key states.
## getDeferredInitTask()
Getter - returns the deffered init task.

**Kind**: global function
<a name="getEvictionBlocklist"></a>

## getEvictionBlocklist()
Getter - returns the eviction block list.

**Kind**: global function
<a name="getSkippableCollectionMemberIDs"></a>

Expand All @@ -213,7 +197,7 @@ Setter - sets the skippable collection member IDs.
**Kind**: global function
<a name="initStoreValues"></a>

## initStoreValues(keys, initialKeyStates, evictableKeys)
## initStoreValues(keys, initialKeyStates, evictableKeys, fullyMergedSnapshotKeys)
Sets the initial values for the Onyx store

**Kind**: global function
Expand All @@ -222,7 +206,8 @@ Sets the initial values for the Onyx store
| --- | --- |
| keys | `ONYXKEYS` constants object from Onyx.init() |
| initialKeyStates | initial data to set when `init()` and `clear()` are called |
| evictableKeys | This is an array of keys (individual or collection patterns) that are eligible for automatic removal when storage limits are reached. |
| evictableKeys | This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal. |
| fullyMergedSnapshotKeys | Array of snapshot collection keys where full merge is supported and data structure can be changed after merge. |

<a name="maybeFlushBatchUpdates"></a>

Expand Down Expand Up @@ -318,12 +303,6 @@ or throws an Error if the key is not a collection one.
Checks to see if a provided key is the exact configured key of our connected subscriber
or if the provided key is a collection member key (in case our configured key is a "collection key")

**Kind**: global function
<a name="isEvictableKey"></a>

## isEvictableKey()
Checks to see if this key has been flagged as safe for removal.

**Kind**: global function
<a name="getCollectionKey"></a>

Expand All @@ -349,29 +328,6 @@ For example:
Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
If the requested key is a collection, it will return an object with all the collection members.

**Kind**: global function
<a name="removeLastAccessedKey"></a>

## removeLastAccessedKey()
Remove a key from the recently accessed key list.

**Kind**: global function
<a name="addLastAccessedKey"></a>

## addLastAccessedKey()
Add a key to the list of recently accessed keys. The least
recently accessed key should be at the head and the most
recently accessed key at the tail.

**Kind**: global function
<a name="addEvictableKeysToRecentlyAccessedList"></a>

## addEvictableKeysToRecentlyAccessedList()
Take all the keys that are safe to evict and add them to
the recently accessed list when initializing the app. This
enables keys that have not recently been accessed to be
removed.

**Kind**: global function
<a name="keysChanged"></a>

Expand Down Expand Up @@ -465,15 +421,6 @@ whatever it is we attempted to do.
Notifies subscribers and writes current value to cache

**Kind**: global function
<a name="removeNullValues"></a>

## removeNullValues() ⇒
Removes a key from storage if the value is null.
Otherwise removes all nested null values in objects,
if shouldRemoveNestedNulls is true and returns the object.

**Kind**: global function
**Returns**: The value without null values and a boolean "wasRemoved", which indicates if the key got removed completely
<a name="prepareKeyValuePairsForStorage"></a>

## prepareKeyValuePairsForStorage() ⇒
Expand All @@ -483,16 +430,42 @@ to an array of key-value pairs in the above format and removes key-value pairs t

**Kind**: global function
**Returns**: an array of key - value pairs <[key, value]>
<a name="applyMerge"></a>
<a name="mergeChanges"></a>

## mergeChanges(changes, existingValue)
Merges an array of changes with an existing value or creates a single change.

**Kind**: global function

| Param | Description |
| --- | --- |
| changes | Array of changes that should be merged |
| existingValue | The existing value that should be merged with the changes |

<a name="mergeAndMarkChanges"></a>

## mergeAndMarkChanges(changes, existingValue)
Merges an array of changes with an existing value or creates a single change.
It will also mark deep nested objects that need to be entirely replaced during the merge.

**Kind**: global function

| Param | Description |
| --- | --- |
| changes | Array of changes that should be merged |
| existingValue | The existing value that should be merged with the changes |

## applyMerge(changes)
Merges an array of changes with an existing value
<a name="mergeInternal"></a>

## mergeInternal(changes, existingValue)
Merges an array of changes with an existing value or creates a single change.

**Kind**: global function

| Param | Description |
| --- | --- |
| changes | Array of changes that should be applied to the existing value |
| changes | Array of changes that should be merged |
| existingValue | The existing value that should be merged with the changes |

<a name="initializeWithDefaultKeyStates"></a>

Expand Down Expand Up @@ -535,3 +508,24 @@ Disconnects and removes the listener from the Onyx key.
| --- | --- |
| subscriptionID | Subscription ID returned by calling `OnyxUtils.subscribeToKey()`. |

<a name="mergeCollectionWithPatches"></a>

## mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullPatches)
Merges a collection based on their keys.
Serves as core implementation for `Onyx.mergeCollection()` public function, the difference being
that this internal function allows passing an additional `mergeReplaceNullPatches` parameter.

**Kind**: global function

| Param | Description |
| --- | --- |
| collectionKey | e.g. `ONYXKEYS.COLLECTION.REPORT` |
| collection | Object collection keyed by individual collection member keys and values |
| mergeReplaceNullPatches | Record where the key is a collection member key and the value is a list of tuples that we'll use to replace the nested objects of that collection member record with something else. |

<a name="clearOnyxUtilsInternals"></a>

## clearOnyxUtilsInternals()
Clear internal variables used in this file, useful in test environments.

**Kind**: global function
4 changes: 2 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ applied in the order they were called. Note: <code>Onyx.set()</code> calls do no
<code>Onyx.merge()</code> and <code>Onyx.set()</code>.</p>
</dd>
<dt><a href="#mergeCollection">mergeCollection(collectionKey, collection)</a></dt>
<dd><p>Merges a collection based on their keys</p>
<dd><p>Merges a collection based on their keys.</p>
</dd>
<dt><a href="#clear">clear(keysToPreserve)</a></dt>
<dd><p>Clear out all the data in the store</p>
Expand Down Expand Up @@ -158,7 +158,7 @@ Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Wor
<a name="mergeCollection"></a>

## mergeCollection(collectionKey, collection)
Merges a collection based on their keys
Merges a collection based on their keys.

**Kind**: global function

Expand Down
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ module.exports = {
testTimeout: 60000,
transformIgnorePatterns: ['node_modules/(?!((@)?react-native|@ngneat/falso|uuid)/)'],
testSequencer: './jest-sequencer.js',
moduleNameMapper: {
// Redirect all imports of OnyxMerge to its web version during unit tests.
'^(.*)/OnyxMerge$': '<rootDir>/lib/OnyxMerge/index.ts',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we somehow generalize this so this applies to all files with .ts or .web.ts file ending, so we don't have to update this in the future when we add more platform-specific files?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried some approaches but I wasn't able yet to come up with something generic enough to cover all possible cases (also tried a custom resolver). I think we can explore this in a follow-up if you don't mind.

},
};
Loading
Loading