Skip to content

Commit 42f44fb

Browse files
committed
Merge branch 'main' into VickyStash/bugfix/update-eviction-rules
# Conflicts: # lib/OnyxUtils.ts
2 parents a8fbd85 + 6a24680 commit 42f44fb

File tree

12 files changed

+420
-126
lines changed

12 files changed

+420
-126
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,24 @@ To use the extension, simply install it from your favorite web browser store:
441441
- [Microsoft Edge](https://microsoftedge.microsoft.com/addons/detail/redux-devtools/nnkgneoiohoecpdiaponcejilbhhikei)
442442
- [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/)
443443

444-
After installing the extension, Onyx will automatically connect to it and start logging any updates made to the local storage.
444+
### Enabling or Disabling Redux DevTools
445+
446+
You can control whether the Redux DevTools integration is enabled by setting the `enableDevTools` option in the `Onyx.init()` configuration.
447+
448+
- To **enable** Redux DevTools and start logging updates to local storage, set `enableDevTools: true`.
449+
- To **disable** Redux DevTools and prevent any logging to the extension, set `enableDevTools: false`.
450+
451+
This option defaults to `true` (enabled) on Web, so you only need to set it to `false` if you want to disable the integration.
452+
453+
```javascript
454+
import Onyx from 'react-native-onyx';
455+
import Config from './config';
456+
457+
Onyx.init({
458+
keys: ONYXKEYS,
459+
enableDevTools: Config.ENABLE_ONYX_DEVTOOLS,
460+
});
461+
```
445462

446463
### Usage
447464

lib/DevTools.ts

Lines changed: 37 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,42 @@
1-
type DevtoolsOptions = {
2-
maxAge?: number;
3-
name?: string;
4-
postTimelineUpdate?: () => void;
5-
preAction?: () => void;
6-
logTrace?: boolean;
7-
remote?: boolean;
8-
};
9-
10-
type DevtoolsSubscriber = (message: {type: string; payload: unknown; state: string}) => void;
11-
12-
type DevtoolsConnection = {
13-
send(data: Record<string, unknown>, state: Record<string, unknown>): void;
14-
init(state: Record<string, unknown>): void;
15-
unsubscribe(): void;
16-
subscribe(cb: DevtoolsSubscriber): () => void;
17-
};
18-
19-
const ERROR_LABEL = 'Onyx DevTools - Error: ';
20-
21-
type ReduxDevtools = {
22-
connect(options?: DevtoolsOptions): DevtoolsConnection;
23-
};
24-
25-
class DevTools {
26-
private remoteDev?: DevtoolsConnection;
27-
28-
private state: Record<string, unknown>;
29-
30-
private defaultState: Record<string, unknown>;
31-
32-
constructor() {
33-
this.remoteDev = this.connectViaExtension();
34-
this.state = {};
35-
this.defaultState = {};
36-
}
37-
38-
connectViaExtension(options?: DevtoolsOptions): DevtoolsConnection | undefined {
39-
try {
40-
// We don't want to augment the window type in a library code, so we use type assertion instead
41-
// eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-explicit-any
42-
const reduxDevtools: ReduxDevtools = typeof window === 'undefined' ? undefined : (window as any).__REDUX_DEVTOOLS_EXTENSION__;
43-
44-
if (options?.remote || !reduxDevtools) {
45-
return;
46-
}
1+
import type {IDevTools, DevtoolsConnection} from './DevTools/types';
2+
import RealDevTools from './DevTools/RealDevTools';
3+
import NoOpDevTools from './DevTools/NoOpDevTools';
4+
5+
// Start with a no-op instance
6+
let devToolsInstance: IDevTools = new NoOpDevTools();
7+
8+
/**
9+
* Initializes DevTools with the given enabled flag
10+
*/
11+
function initDevTools(enabled: boolean): void {
12+
devToolsInstance = enabled ? new RealDevTools() : new NoOpDevTools();
13+
}
4714

48-
return reduxDevtools.connect(options);
49-
} catch (e) {
50-
console.error(ERROR_LABEL, e);
51-
}
52-
}
15+
/**
16+
* Gets the current DevTools instance (for testing purposes only)
17+
* @private
18+
*/
19+
function getDevToolsInstance(): IDevTools {
20+
return devToolsInstance;
21+
}
5322

54-
/**
55-
* Registers an action that updated the current state of the storage
56-
*
57-
* @param type - name of the action
58-
* @param payload - data written to the storage
59-
* @param stateChanges - partial state that got updated after the changes
60-
*/
23+
/**
24+
* Export a default object that delegates to the current devToolsInstance
25+
* This allows the instance to be swapped out while keeping the same import signature
26+
*/
27+
const DevTools: IDevTools = {
6128
registerAction(type: string, payload: unknown, stateChanges: Record<string, unknown> | null = {}) {
62-
try {
63-
if (!this.remoteDev) {
64-
return;
65-
}
66-
const newState = {
67-
...this.state,
68-
...stateChanges,
69-
};
70-
this.remoteDev.send({type, payload}, newState);
71-
this.state = newState;
72-
} catch (e) {
73-
console.error(ERROR_LABEL, e);
74-
}
75-
}
76-
29+
devToolsInstance.registerAction(type, payload, stateChanges);
30+
},
7731
initState(initialState: Record<string, unknown> = {}) {
78-
try {
79-
if (!this.remoteDev) {
80-
return;
81-
}
82-
this.remoteDev.init(initialState);
83-
this.state = initialState;
84-
this.defaultState = initialState;
85-
} catch (e) {
86-
console.error(ERROR_LABEL, e);
87-
}
88-
}
89-
90-
/**
91-
* This clears the internal state of the DevTools, preserving the keys included in `keysToPreserve`
92-
*/
93-
clearState(keysToPreserve: string[] = []): void {
94-
const newState = Object.entries(this.state).reduce((obj: Record<string, unknown>, [key, value]) => {
95-
// eslint-disable-next-line no-param-reassign
96-
obj[key] = keysToPreserve.includes(key) ? value : this.defaultState[key];
97-
return obj;
98-
}, {});
99-
100-
this.registerAction('CLEAR', undefined, newState);
101-
}
102-
}
32+
devToolsInstance.initState(initialState);
33+
},
34+
clearState(keysToPreserve: string[] = []) {
35+
devToolsInstance.clearState(keysToPreserve);
36+
},
37+
};
10338

104-
export default new DevTools();
105-
export type {DevtoolsConnection};
39+
export default DevTools;
40+
export {initDevTools, getDevToolsInstance};
41+
export type {DevtoolsConnection, IDevTools};
42+
export type {default as RealDevTools} from './DevTools/RealDevTools';

lib/DevTools/NoOpDevTools.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type {IDevTools} from './types';
2+
3+
/**
4+
* No-op implementation of DevTools that does nothing
5+
* Used when DevTools is disabled
6+
*/
7+
class NoOpDevTools implements IDevTools {
8+
registerAction(): void {
9+
// do nothing
10+
}
11+
12+
initState(): void {
13+
// do nothing
14+
}
15+
16+
clearState(): void {
17+
// do nothing
18+
}
19+
}
20+
21+
export default NoOpDevTools;

lib/DevTools/RealDevTools.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type {IDevTools, DevtoolsOptions, DevtoolsConnection, ReduxDevtools} from './types';
2+
3+
const ERROR_LABEL = 'Onyx DevTools - Error: ';
4+
5+
/**
6+
* Real implementation of DevTools that connects to Redux DevTools Extension
7+
*/
8+
class RealDevTools implements IDevTools {
9+
private remoteDev?: DevtoolsConnection;
10+
11+
private state: Record<string, unknown>;
12+
13+
private defaultState: Record<string, unknown>;
14+
15+
constructor() {
16+
this.remoteDev = this.connectViaExtension();
17+
this.state = {};
18+
this.defaultState = {};
19+
}
20+
21+
connectViaExtension(options?: DevtoolsOptions): DevtoolsConnection | undefined {
22+
try {
23+
// We don't want to augment the window type in a library code, so we use type assertion instead
24+
// eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-explicit-any
25+
const reduxDevtools: ReduxDevtools = typeof window === 'undefined' ? undefined : (window as any).__REDUX_DEVTOOLS_EXTENSION__;
26+
27+
if (options?.remote || !reduxDevtools) {
28+
return;
29+
}
30+
31+
return reduxDevtools.connect(options);
32+
} catch (e) {
33+
console.error(ERROR_LABEL, e);
34+
}
35+
}
36+
37+
/**
38+
* Registers an action that updated the current state of the storage
39+
*
40+
* @param type - name of the action
41+
* @param payload - data written to the storage
42+
* @param stateChanges - partial state that got updated after the changes
43+
*/
44+
registerAction(type: string, payload: unknown, stateChanges: Record<string, unknown> | null = {}) {
45+
try {
46+
if (!this.remoteDev) {
47+
return;
48+
}
49+
const newState = {
50+
...this.state,
51+
...stateChanges,
52+
};
53+
this.remoteDev.send({type, payload}, newState);
54+
this.state = newState;
55+
} catch (e) {
56+
console.error(ERROR_LABEL, e);
57+
}
58+
}
59+
60+
initState(initialState: Record<string, unknown> = {}) {
61+
try {
62+
if (!this.remoteDev) {
63+
return;
64+
}
65+
this.remoteDev.init(initialState);
66+
this.state = initialState;
67+
this.defaultState = initialState;
68+
} catch (e) {
69+
console.error(ERROR_LABEL, e);
70+
}
71+
}
72+
73+
/**
74+
* This clears the internal state of the DevTools, preserving the keys included in `keysToPreserve`
75+
*/
76+
clearState(keysToPreserve: string[] = []): void {
77+
const newState = Object.entries(this.state).reduce((obj: Record<string, unknown>, [key, value]) => {
78+
// eslint-disable-next-line no-param-reassign
79+
obj[key] = keysToPreserve.includes(key) ? value : this.defaultState[key];
80+
return obj;
81+
}, {});
82+
83+
this.registerAction('CLEAR', undefined, newState);
84+
}
85+
}
86+
87+
export default RealDevTools;

lib/DevTools/types.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
type DevtoolsOptions = {
2+
maxAge?: number;
3+
name?: string;
4+
postTimelineUpdate?: () => void;
5+
preAction?: () => void;
6+
logTrace?: boolean;
7+
remote?: boolean;
8+
};
9+
10+
type DevtoolsSubscriber = (message: {type: string; payload: unknown; state: string}) => void;
11+
12+
type DevtoolsConnection = {
13+
send(data: Record<string, unknown>, state: Record<string, unknown>): void;
14+
init(state: Record<string, unknown>): void;
15+
unsubscribe(): void;
16+
subscribe(cb: DevtoolsSubscriber): () => void;
17+
};
18+
19+
type ReduxDevtools = {
20+
connect(options?: DevtoolsOptions): DevtoolsConnection;
21+
};
22+
23+
/**
24+
* Type definition for DevTools instance
25+
*/
26+
type IDevTools = {
27+
registerAction(type: string, payload: unknown, stateChanges?: Record<string, unknown> | null): void;
28+
initState(initialState?: Record<string, unknown>): void;
29+
clearState(keysToPreserve?: string[]): void;
30+
};
31+
32+
export type {DevtoolsOptions, DevtoolsSubscriber, DevtoolsConnection, ReduxDevtools, IDevTools};

lib/Onyx.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as Logger from './Logger';
22
import cache, {TASK} from './OnyxCache';
33
import Storage from './storage';
44
import utils from './utils';
5-
import DevTools from './DevTools';
5+
import DevTools, {initDevTools} from './DevTools';
66
import type {
77
Collection,
88
CollectionKey,
@@ -40,13 +40,16 @@ function init({
4040
maxCachedKeysCount = 1000,
4141
shouldSyncMultipleInstances = !!global.localStorage,
4242
enablePerformanceMetrics = false,
43+
enableDevTools = true,
4344
skippableCollectionMemberIDs = [],
4445
}: InitOptions): void {
4546
if (enablePerformanceMetrics) {
4647
GlobalSettings.setPerformanceMetricsEnabled(true);
4748
applyDecorators();
4849
}
4950

51+
initDevTools(enableDevTools);
52+
5053
Storage.init();
5154

5255
OnyxUtils.setSkippableCollectionMemberIDs(new Set(skippableCollectionMemberIDs));
@@ -374,7 +377,7 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>):
374377
* @param collection Object collection keyed by individual collection member keys and values
375378
*/
376379
function mergeCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TKey, TMap>): Promise<void> {
377-
return OnyxUtils.mergeCollectionWithPatches(collectionKey, collection);
380+
return OnyxUtils.mergeCollectionWithPatches(collectionKey, collection, undefined, true);
378381
}
379382

380383
/**
@@ -612,6 +615,7 @@ function update(data: OnyxUpdate[]): Promise<void> {
612615
collectionKey,
613616
batchedCollectionUpdates.merge as Collection<CollectionKey, unknown, unknown>,
614617
batchedCollectionUpdates.mergeReplaceNullPatches,
618+
true,
615619
),
616620
);
617621
}
@@ -697,7 +701,7 @@ function setCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey
697701
mutableCollection[key] = null;
698702
});
699703

700-
const keyValuePairs = OnyxUtils.prepareKeyValuePairsForStorage(mutableCollection, true);
704+
const keyValuePairs = OnyxUtils.prepareKeyValuePairsForStorage(mutableCollection, true, undefined, true);
701705
const previousCollection = OnyxUtils.getCachedCollection(collectionKey);
702706

703707
keyValuePairs.forEach(([key, value]) => cache.set(key, value));

0 commit comments

Comments
 (0)