Allow onExport handler to return nothing

Summary:
In many cases, the onExport handler doesn't try to customise the format, but merely fetch some additional information before creating an export.

By allowing the `onExport` handler to also return nothing, and instead merely update existing state, this case will be easier to express now;

Reviewed By: nikoant

Differential Revision: D28026558

fbshipit-source-id: 2b90b3e1ced6a6a5b42938b6f6b74b0eb9ceafc0
This commit is contained in:
Michel Weststrate
2021-04-27 14:52:34 -07:00
committed by Facebook GitHub Bot
parent c2a07e7638
commit d26ea5fa46
3 changed files with 42 additions and 11 deletions

View File

@@ -357,8 +357,7 @@ test('plugins can handle import errors', async () => {
});
test('plugins can have custom export handler', async () => {
const {exportStateAsync} = TestUtils.startPlugin(
{
const {exportStateAsync} = TestUtils.startPlugin({
plugin(client: PluginClient) {
const field1 = createState(0, {persist: 'field1'});
@@ -374,15 +373,34 @@ test('plugins can have custom export handler', async () => {
Component() {
return null;
},
});
expect(await exportStateAsync()).toEqual({b: 3});
});
test('plugins can have custom export handler that doesnt return', async () => {
const {exportStateAsync} = TestUtils.startPlugin(
{
plugin(client: PluginClient) {
const field1 = createState(0, {persist: 'field1'});
client.onExport(async () => {
await sleep(10);
field1.set(field1.get() + 1);
});
return {field1};
},
Component() {
return null;
},
},
{
initialState: {
a: 1,
b: 2,
field1: 1,
},
},
);
expect(await exportStateAsync()).toEqual({b: 3});
expect(await exportStateAsync()).toEqual({field1: 2});
});
test('plugins can receive deeplinks', async () => {

View File

@@ -21,7 +21,7 @@ import {Logger} from '../utils/Logger';
type StateExportHandler<T = any> = (
idler: Idler,
onStatusMessage: (msg: string) => void,
) => Promise<T>;
) => Promise<T | undefined | void>;
type StateImportHandler<T = any> = (data: T) => void;
export interface BasePluginClient {
@@ -54,8 +54,11 @@ export interface BasePluginClient {
/**
* Triggered when the current plugin is being exported and should create a snapshot of the state exported.
* Overrides the default export behavior and ignores any 'persist' flags of state.
*
* If an object is returned from the handler, that will be taken as export.
* Otherwise, if nothing is returned, the handler will be run, and after the handler has finished the `persist` keys of the different states will be used as export basis.
*/
onExport<T = any>(exporter: StateExportHandler<T>): void;
onExport<T extends object>(exporter: StateExportHandler<T>): void;
/**
* Triggered directly after the plugin instance was created, if the plugin is being restored from a snapshot.
@@ -350,6 +353,10 @@ export abstract class BasePluginInstance {
'Cannot export sync a plugin that does have an export handler',
);
}
return this.serializeRootStates();
}
private serializeRootStates() {
return Object.fromEntries(
Object.entries(this.rootStates).map(([key, atom]) => [
key,
@@ -363,9 +370,13 @@ export abstract class BasePluginInstance {
onStatusMessage: (msg: string) => void,
): Promise<Record<string, any>> {
if (this.exportHandler) {
return await this.exportHandler(idler, onStatusMessage);
const result = await this.exportHandler(idler, onStatusMessage);
if (result !== undefined) {
return result;
}
return this.exportStateSync();
// intentional fall-through, the export handler merely updated the state, but prefers the default export format
}
return this.serializeRootStates();
}
isPersistable(): boolean {

View File

@@ -149,11 +149,13 @@ Trigger when the users navigates to this plugin using a deeplink, either from an
Usage: `client.onExport(callback: (idler, onStatusMessage) => Promise<state>)`
Overrides the default serialization behavior of this plugin. Should return a promise with persistable state that is to be stored.
Overrides the default serialization behavior of this plugin. Should return a promise with persistable state that is to be stored, or nothing at all.
This process is async, so it is possible to first fetch some additional state from the device.
Serializable is defined as: non-cyclic data, consisting purely of primitive values, plain objects, arrays or Date, Set or Map objects.
If nothing is returned, the handler will be run, and after the handler has finished the `persist` keys of the different states will be used as export basis.
#### `onImport`
Usage: `client.onImport(callback: (snapshot) => void)`