Skip to content
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
13 changes: 13 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ value will be saved to storage after the default value.</p>
<dd><p>Sets a collection by replacing all existing collection members with new values.
Any existing collection members not included in the new data will be removed.</p>
</dd>
<dt><a href="#exportState">exportState()</a></dt>
<dd><p>Returns persisted Onyx key-value pairs as a plain object.
Live RAM-only values and writes that have not reached storage are not included.
Treat the returned object and its nested values as read-only.</p>
</dd>
</dl>

<a name="init"></a>
Expand Down Expand Up @@ -257,3 +262,11 @@ Onyx.setCollection(ONYXKEYS.COLLECTION.REPORT, {
[`${ONYXKEYS.COLLECTION.REPORT}2`]: report2,
});
```
<a name="exportState"></a>

## exportState()
Returns persisted Onyx key-value pairs as a plain object.
Live RAM-only values and writes that have not reached storage are not included.
Treat the returned object and its nested values as read-only.

**Kind**: global function
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ function signOut() {
}
```

Comment thread
chrispader marked this conversation as resolved.
## Export persisted state

`Onyx.exportState()` returns a plain object containing persisted keys and values from the active storage provider. It works on native and web without opening another database connection. The export excludes stale persisted rows for keys that are now RAM-only. Current RAM-only values and writes still in progress are never included. Treat the returned object and its nested values as read-only because some providers return references to their stored values. The result can contain sensitive data; callers should redact it before sharing.

## Storage Providers
`Onyx.get`, `Onyx.set`, and the rest of the API accesses the underlying storage
differently depending on the platform
Expand Down
23 changes: 23 additions & 0 deletions lib/Onyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,28 @@ function setCollection<TKey extends CollectionKeyBase>(collectionKey: TKey, coll
return OnyxUtils.afterInit(() => OnyxUtils.setCollectionWithRetry({collectionKey, collection}));
}

/**
* Returns persisted Onyx key-value pairs as a plain object.
* Live RAM-only values and writes that have not reached storage are not included.
* Treat the returned object and its nested values as read-only.
*/
function exportState(): Promise<Record<OnyxKey, OnyxValue<OnyxKey>>> {
return OnyxUtils.afterInit(() =>
Storage.getAll().then((entries) => {
const state: Record<OnyxKey, OnyxValue<OnyxKey>> = {};

for (const [key, value] of entries) {
if (OnyxKeys.isRamOnlyKey(key)) {
continue;
}
state[key] = value;
}

return state;
}),
);
}

const Onyx = {
METHOD: OnyxUtils.METHOD,
connect,
Expand All @@ -623,6 +645,7 @@ const Onyx = {
setCollection,
update,
clear,
exportState,
init,
registerLogger: Logger.registerLogger,
};
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/onyxTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,45 @@ describe('Onyx', () => {
return Onyx.clear();
});

describe('exportState', () => {
Comment thread
chrispader marked this conversation as resolved.
Comment thread
chrispader marked this conversation as resolved.
it('exports persisted values without current RAM-only values', async () => {
await Onyx.set(ONYX_KEYS.TEST_KEY, {nested: 'value'});
await Onyx.set(ONYX_KEYS.RAM_ONLY_TEST_KEY, 'not persisted');

const state = await Onyx.exportState();

expect(state[ONYX_KEYS.TEST_KEY]).toEqual({nested: 'value'});
expect(state).not.toHaveProperty(ONYX_KEYS.RAM_ONLY_TEST_KEY);
});

it('exports persisted collection members', async () => {
const collectionMemberKey = `${ONYX_KEYS.COLLECTION.TEST_KEY}1`;
await Onyx.set(collectionMemberKey, {nested: 'value'});

const state = await Onyx.exportState();

expect(state[collectionMemberKey]).toEqual({nested: 'value'});
});

it('propagates storage read failures', async () => {
const error = new Error('Storage read failed');
jest.mocked(StorageMock.getAll).mockRejectedValueOnce(error);

await expect(Onyx.exportState()).rejects.toBe(error);
});

it('excludes stale persisted RAM-only values', async () => {
const staleCollectionMember = `${ONYX_KEYS.COLLECTION.RAM_ONLY_COLLECTION}1`;
jest.mocked(StorageMock.getAll).mockResolvedValueOnce([
[ONYX_KEYS.TEST_KEY, 'persisted'],
[ONYX_KEYS.RAM_ONLY_TEST_KEY, 'stale'],
[staleCollectionMember, 'stale collection member'],
]);

await expect(Onyx.exportState()).resolves.toEqual({[ONYX_KEYS.TEST_KEY]: 'persisted'});
});
});

it('should remove key value from OnyxCache/Storage when set is called with null value', () =>
Onyx.set(ONYX_KEYS.OTHER_TEST, 42)
.then(() => OnyxUtils.getAllKeys())
Expand Down Expand Up @@ -3342,6 +3381,20 @@ describe('Onyx.init', () => {

expect(cache.get(`${ONYX_KEYS.COLLECTION.TEST_KEY}entry1`)).toEqual('test_1');
});

it('exportState', async () => {
await StorageMock.setItem(ONYX_KEYS.TEST_KEY, 'persisted');
jest.mocked(StorageMock.getAll).mockClear();

const exportPromise = Onyx.exportState();
await act(async () => waitForPromisesToResolve());

expect(StorageMock.getAll).not.toHaveBeenCalled();

Onyx.init({keys: ONYX_KEYS});

await expect(exportPromise).resolves.toEqual({[ONYX_KEYS.TEST_KEY]: 'persisted'});
});
});
});

Expand Down
Loading