Skip to content

Commit

Permalink
feat: exclude props from export
Browse files Browse the repository at this point in the history
- add storage serialize behaviour
  • Loading branch information
MatthewPattell committed May 29, 2024
1 parent d67fd8d commit e04a980
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 36 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"homepage": "https://github.com/Lomray-Software/react-mobx-manager",
"scripts": {
"build": "rollup -c",
"build:watch": "rollup -c -w --environment BUILD:development",
"build:watch": "rollup -c -w",
"lint:check": "eslint \"src/**/*.{ts,tsx,*.ts,*tsx}\"",
"lint:format": "eslint --fix \"src/**/*.{ts,tsx,*.ts,*tsx}\"",
"ts:check": "tsc --project ./tsconfig.checks.json --skipLibCheck --noemit",
Expand Down
3 changes: 1 addition & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { folderInput } from 'rollup-plugin-folder-input';
import copy from 'rollup-plugin-copy';
import terser from '@rollup/plugin-terser';

const IS_DEVELOP_BUILD = process.env.BUILD === 'development'
const dest = IS_DEVELOP_BUILD ? 'example/node_modules/@lomray/react-mobx-manager' : 'lib';
const dest = 'lib';

export default {
input: [
Expand Down
29 changes: 29 additions & 0 deletions src/deep-compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Deep compare two objects
*/
const deepCompare = (obj1: unknown, obj2: unknown): boolean => {
if (obj1 === obj2) {
return true;
}

if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false;
}

const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);

if (keys1.length !== keys2.length) {
return false;
}

for (const key of keys1) {
if (!keys2.includes(key) || !deepCompare(obj1[key], obj2[key])) {
return false;
}
}

return true;
};

export default deepCompare;
10 changes: 8 additions & 2 deletions src/make-exported.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const exportedPropName = 'libExported';
const makeExported = <T extends object>(
store: T,
props: {
[P in Exclude<keyof T, 'toString'>]?: 'observable' | 'simple';
[P in Exclude<keyof T, 'toString'>]?: 'observable' | 'simple' | 'excluded';
},
): void => {
store[exportedPropName] = props;
Expand All @@ -27,4 +27,10 @@ const isPropObservableExported = (store: TAnyStore, prop: string): boolean =>
const isPropSimpleExported = (store: TAnyStore, prop: string): boolean =>
store?.[exportedPropName]?.[prop] === 'simple';

export { makeExported, isPropObservableExported, isPropSimpleExported };
/**
* Check if store prop is excluded from export
*/
const isPropExcludedFromExport = (store: TAnyStore, prop: string): boolean =>
store?.[exportedPropName]?.[prop] === 'excluded';

export { makeExported, isPropObservableExported, isPropSimpleExported, isPropExcludedFromExport };
21 changes: 12 additions & 9 deletions src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { isObservableProp, toJS } from 'mobx';
import { ROOT_CONTEXT_ID } from './constants';
import deepMerge from './deep-merge';
import Events from './events';
import { isPropObservableExported, isPropSimpleExported } from './make-exported';
import {
isPropExcludedFromExport,
isPropObservableExported,
isPropSimpleExported,
} from './make-exported';
import onChangeListener from './on-change-listener';
import CombinedStorage from './storages/combined-storage';
import StoreStatus from './store-status';
Expand Down Expand Up @@ -621,7 +625,8 @@ class Manager {
return Object.entries(props).reduce(
(res, [prop, value]) => ({
...res,
...(isObservableProp(store, prop) || isPropSimpleExported(store, prop)
...((isObservableProp(store, prop) && !isPropExcludedFromExport(store, prop)) ||
isPropSimpleExported(store, prop)
? { [prop]: value }
: {}),
...(isPropObservableExported(store, prop)
Expand All @@ -640,16 +645,14 @@ class Manager {
id: string,
options: IPersistOptions = {},
): IConstructableStore<TSt> {
if (Manager.persistedStores.has(id)) {
console.warn(`Duplicate serializable store key: ${id}`);

return store;
}

Manager.persistedStores.add(id);

store.libStoreId = id;
store.libStorageOptions = options;

// add storage options
if (!('libStorageOptions' in store.prototype)) {
store.prototype.libStorageOptions = options;
}

// add default wakeup handler
if (!('wakeup' in store.prototype)) {
Expand Down
50 changes: 30 additions & 20 deletions src/storages/combined-storage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import deepCompare from '../deep-compare';
import type { IPersistOptions, IStorage, IStorePersisted } from '../types';

interface ICombinedStorage {
Expand Down Expand Up @@ -89,6 +90,7 @@ class CombinedStorage implements IStorage {
attributes: {
[this.defaultId]: ['*'],
},
behaviour: 'exclude',
...(store.libStorageOptions ?? {}),
};
}
Expand Down Expand Up @@ -130,28 +132,36 @@ class CombinedStorage implements IStorage {
data: Record<string, any> | undefined,
): Promise<void> {
const storeId = store.libStoreId!;
const { attributes } = this.getStoreOptions(store);
const dataKeys = Object.keys(data ?? {});
const { attributes, behaviour } = this.getStoreOptions(store);
const dataKeys = new Set(Object.keys(data ?? {}));

const dataByStorages = Object.entries(attributes!).map(([storageId, attr]) => {
const storeData =
attr[0] === '*'
? data
: attr.reduce(
(r, attrName) => ({
...r,
...(dataKeys.includes(attrName) ? { [attrName]: data?.[attrName] } : {}),
}),
{},
);

return this.set(
{
...(this.persistData?.[storageId] ?? {}),
[storeId]: storeData,
} as Record<string, any>,
storageId,
);
const storeData = (attr[0] === '*' ? [...dataKeys] : attr).reduce((r, attrName) => {
if (!dataKeys.has(attrName)) {
return r;
}

if (behaviour === 'exclude') {
dataKeys.delete(attrName);
}

return {
...r,
[attrName]: data?.[attrName],
};
}, {});

const newData = {
...(this.persistData?.[storageId] ?? {}),
[storeId]: storeData,
} as Record<string, any>;

// skip updating if nothing changed
if (deepCompare(this.persistData?.[storageId]?.[storeId] ?? {}, storeData)) {
return null;
}

return this.set(newData, storageId);
});

await Promise.all(dataByStorages);
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ export interface IGroupedStores {
}

export interface IPersistOptions {
// default: exclude. Exclude - except attributes from other storages
behaviour?: 'exclude' | 'include';
attributes?: {
// storageId => attributes, * - all attributes
// first storage => *, by default
Expand Down
4 changes: 2 additions & 2 deletions src/wakeup.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import deepMerge from './deep-merge';
import type { TStores, TWakeup } from './types';
import type { IStorePersisted, TWakeup } from './types';

/**
* Restore persisted store state
*/
function wakeup(
this: TStores[string],
this: IStorePersisted,
{ initState, persistedState, manager }: Parameters<TWakeup>[0],
) {
const resState = {};
Expand Down

0 comments on commit e04a980

Please sign in to comment.