From 8bba87c22bb3c06975a7a70762771b4507397a0c Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Sat, 9 Nov 2019 18:44:18 -0500 Subject: [PATCH 1/9] (feat): simple version of transforms - object with `toStorage` and `fromStorage` functions - `toStorage` is called after removing whitelist and blacklists and before serializing to JSON - `fromStorage` is called after deserializing from JSON --- src/index.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index e932123..b3973c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,12 +9,21 @@ export interface IOptions { storage?: any, jsonify?: boolean, readonly whitelist?: Array, - readonly blacklist?: Array + readonly blacklist?: Array, + readonly transforms?: Array } type StrToAnyMap = {[key: string]: any} +export interface ITransform { + readonly toStorage?: ITransformArgs, + readonly fromStorage?: ITransformArgs +} +export interface ITransformArgs { + (snapshot: StrToAnyMap): StrToAnyMap +} + export const persist: IArgs = (name, store, options = {}) => { - let {storage, jsonify = true, whitelist, blacklist} = options + let {storage, jsonify = true, whitelist, blacklist, transforms = []} = options // use AsyncLocalStorage by default (or if localStorage was passed in) if ( @@ -45,6 +54,10 @@ export const persist: IArgs = (name, store, options = {}) => { } }) + transforms.forEach((transform) => { + if (transform.toStorage) { transform.toStorage(snapshot) } + }) + const data = !jsonify ? snapshot : JSON.stringify(snapshot) storage.setItem(name, data) }) @@ -54,6 +67,12 @@ export const persist: IArgs = (name, store, options = {}) => { const snapshot = !isString(data) ? data : JSON.parse(data) // don't apply falsey (which will error), leave store in initial state if (!snapshot) { return } + + // in reverse order, like a stack, so that last transform is first + transforms.slice().reverse().forEach((transform) => { + if (transform.fromStorage) { transform.fromStorage(snapshot) } + }) + applySnapshot(store, snapshot) }) } From a4d8f16a48a3d0bc1167eb48a936034ea87ef085 Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Sat, 9 Nov 2019 19:15:35 -0500 Subject: [PATCH 2/9] (refactor): whitelist and blacklist as transforms - so that these can work as examples and so that their order is very explicit as they're treated like any other transform internally - create new transforms/ directory that will hold all built-in transforms moving forward - and export them in transforms/index.ts --- src/index.ts | 38 +++++++++---------------------------- src/transforms/blacklist.ts | 14 ++++++++++++++ src/transforms/index.ts | 4 ++++ src/transforms/utils.ts | 19 +++++++++++++++++++ src/transforms/whitelist.ts | 14 ++++++++++++++ src/utils.ts | 1 + 6 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 src/transforms/blacklist.ts create mode 100644 src/transforms/index.ts create mode 100644 src/transforms/utils.ts create mode 100644 src/transforms/whitelist.ts create mode 100644 src/utils.ts diff --git a/src/index.ts b/src/index.ts index b3973c9..c8a2381 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ import { onSnapshot, applySnapshot, IStateTreeNode } from 'mobx-state-tree' import AsyncLocalStorage from './asyncLocalStorage' +import { ITransform, whitelistKeys, blacklistKeys } from './transforms/index' +import { StrToAnyMap } from './utils' export interface IArgs { (name: string, store: IStateTreeNode, options?: IOptions): Promise @@ -12,15 +14,7 @@ export interface IOptions { readonly blacklist?: Array, readonly transforms?: Array } -type StrToAnyMap = {[key: string]: any} - -export interface ITransform { - readonly toStorage?: ITransformArgs, - readonly fromStorage?: ITransformArgs -} -export interface ITransformArgs { - (snapshot: StrToAnyMap): StrToAnyMap -} +export { ITransform, ITransformArgs } from './transforms/index' export const persist: IArgs = (name, store, options = {}) => { let {storage, jsonify = true, whitelist, blacklist, transforms = []} = options @@ -39,20 +33,16 @@ export const persist: IArgs = (name, store, options = {}) => { 'engine via the `storage:` option.') } - const whitelistDict = arrToDict(whitelist) - const blacklistDict = arrToDict(blacklist) + // whitelist, blacklist, then any custom transforms + transforms = [ + ...(whitelist ? [whitelistKeys(whitelist)] : []), + ...(blacklist ? [blacklistKeys(blacklist)] : []), + ...transforms + ] onSnapshot(store, (_snapshot: StrToAnyMap) => { // need to shallow clone as otherwise properties are non-configurable (https://github.com/agilgur5/mst-persist/pull/21#discussion_r348105595) const snapshot = { ..._snapshot } - Object.keys(snapshot).forEach((key) => { - if (whitelist && !whitelistDict[key]) { - delete snapshot[key] - } - if (blacklist && blacklistDict[key]) { - delete snapshot[key] - } - }) transforms.forEach((transform) => { if (transform.toStorage) { transform.toStorage(snapshot) } @@ -77,16 +67,6 @@ export const persist: IArgs = (name, store, options = {}) => { }) } -type StrToBoolMap = {[key: string]: boolean} - -function arrToDict (arr?: Array): StrToBoolMap { - if (!arr) { return {} } - return arr.reduce((dict: StrToBoolMap, elem) => { - dict[elem] = true - return dict - }, {}) -} - function isString (value: any): value is string { return typeof value === 'string' } diff --git a/src/transforms/blacklist.ts b/src/transforms/blacklist.ts new file mode 100644 index 0000000..3e7e35e --- /dev/null +++ b/src/transforms/blacklist.ts @@ -0,0 +1,14 @@ +import { ITransform, arrToDict } from './utils' + +export function blacklistKeys (blacklist?: Array): ITransform { + const blacklistDict = arrToDict(blacklist) + + return {toStorage: function blacklistTransform (snapshot) { + Object.keys(snapshot).forEach((key) => { + if (blacklist && blacklistDict[key]) { + delete snapshot[key] + } + }) + return snapshot + }} +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts new file mode 100644 index 0000000..a30e68e --- /dev/null +++ b/src/transforms/index.ts @@ -0,0 +1,4 @@ +export { ITransform, ITransformArgs } from './utils' + +export { whitelistKeys } from './whitelist' +export { blacklistKeys } from './blacklist' diff --git a/src/transforms/utils.ts b/src/transforms/utils.ts new file mode 100644 index 0000000..a28608f --- /dev/null +++ b/src/transforms/utils.ts @@ -0,0 +1,19 @@ +import { StrToAnyMap } from '../utils' + +export interface ITransform { + readonly toStorage?: ITransformArgs, + readonly fromStorage?: ITransformArgs +} +export interface ITransformArgs { + (snapshot: StrToAnyMap): StrToAnyMap +} + +type StrToBoolMap = {[key: string]: boolean} + +export function arrToDict (arr?: Array): StrToBoolMap { + if (!arr) { return {} } + return arr.reduce((dict: StrToBoolMap, elem) => { + dict[elem] = true + return dict + }, {}) +} diff --git a/src/transforms/whitelist.ts b/src/transforms/whitelist.ts new file mode 100644 index 0000000..fbc2364 --- /dev/null +++ b/src/transforms/whitelist.ts @@ -0,0 +1,14 @@ +import { ITransform, arrToDict } from './utils' + +export function whitelistKeys (whitelist?: Array): ITransform { + const whitelistDict = arrToDict(whitelist) + + return {toStorage: function whitelistTransform (snapshot) { + Object.keys(snapshot).forEach((key) => { + if (whitelist && !whitelistDict[key]) { + delete snapshot[key] + } + }) + return snapshot + }} +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..f505a07 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1 @@ +export type StrToAnyMap = {[key: string]: any} From c12fd906f95be75b3bd9a7f1f57fcbd0bb945c2d Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Sat, 9 Nov 2019 19:28:36 -0500 Subject: [PATCH 3/9] (optim): return early if white|blacklist doesn't exist - more importantly, makes the logic a little simpler / easier to read --- src/transforms/blacklist.ts | 6 +++--- src/transforms/whitelist.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/transforms/blacklist.ts b/src/transforms/blacklist.ts index 3e7e35e..77d78bd 100644 --- a/src/transforms/blacklist.ts +++ b/src/transforms/blacklist.ts @@ -4,10 +4,10 @@ export function blacklistKeys (blacklist?: Array): ITransform { const blacklistDict = arrToDict(blacklist) return {toStorage: function blacklistTransform (snapshot) { + if (!blacklist) { return snapshot } + Object.keys(snapshot).forEach((key) => { - if (blacklist && blacklistDict[key]) { - delete snapshot[key] - } + if (blacklistDict[key]) { delete snapshot[key] } }) return snapshot }} diff --git a/src/transforms/whitelist.ts b/src/transforms/whitelist.ts index fbc2364..cd04d7b 100644 --- a/src/transforms/whitelist.ts +++ b/src/transforms/whitelist.ts @@ -4,10 +4,10 @@ export function whitelistKeys (whitelist?: Array): ITransform { const whitelistDict = arrToDict(whitelist) return {toStorage: function whitelistTransform (snapshot) { + if (!whitelist) { return snapshot } + Object.keys(snapshot).forEach((key) => { - if (whitelist && !whitelistDict[key]) { - delete snapshot[key] - } + if (!whitelistDict[key]) { delete snapshot[key] } }) return snapshot }} From 86099a10842f16bb88202725412953c7b8adda7f Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Sun, 10 Nov 2019 14:14:49 -0500 Subject: [PATCH 4/9] (docs): transforms usage & example, ordering & visual - describe usage, link to MST snapshots, show typings, and link to internal examples of whitelist and blacklist transforms - use commit permalink for internal transforms so it won't 404 or change over time (though ofc will need to be updated if the transforms API changes) - describe ordering and show a small diagram of it end-to-end --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index 58972d9..22c06d6 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,46 @@ persist('some', someStore, { - **jsonify** *bool* Enables serialization as JSON (default: `true`). - **whitelist** *Array\* Only these keys will be persisted (defaults to all keys). - **blacklist** *Array\* These keys will not be persisted (defaults to all keys). + - **transforms** *Array\<[Transform](#transforms)\>* [Transforms](#transforms) to apply to snapshots on the way to and from storage. - returns a void Promise +### Transforms + +Transforms allow you to customize the [snapshot](https://github.com/mobxjs/mobx-state-tree#snapshots) that is persisted and used to hydrate your store. + +Transforms are `object`s with `toStorage` and `fromStorage` functions that are called with a `snapshot`-like argument and expected to return a `snapshot`-like object: + +```typescript +interface ITransform { + readonly toStorage?: ITransformArgs, + readonly fromStorage?: ITransformArgs +} +interface ITransformArgs { + (snapshot: StrToAnyMap): StrToAnyMap +} +type StrToAnyMap = {[key: string]: any} +``` + +As an example, one may see how [whitelists](https://github.com/agilgur5/mst-persist/blob/229fd2b1b472ea6a7912a5a06fa079a65e3ba6fa/src/whitelistTransform.ts#L7-L14) and [blacklists](https://github.com/agilgur5/mst-persist/blob/229fd2b1b472ea6a7912a5a06fa079a65e3ba6fa/src/blacklistTransform.ts#L7-L14) are implemented internally as transforms. + +#### Transform Ordering + +`toStorage` functions are called serially in the order specified in the `transforms` configuration array. +`fromStorage` functions are called in the reverse order, such that the last transform is first. + +Before any `toStorage` functions are run, the snapshot will first be stripped of any keys as specified by the `whitelist` and `blacklist` configuration. +Then, once the `toStorage` functions are all run, the object will be serialized to JSON, if that configuration is enabled. + +Before any `fromStorage` functions are run, the JSON will be deserialized into an object, if that configuration is enabled. + +To put this visually with some pseudo-code: + +```text +onSnapshot -> whitelist -> blacklist -> transforms toStorage -> JSON.stringify -> Storage.setItem +Storage.getItem -> JSON.parse -> transforms.reverse() fromStorage -> applySnapshot +``` + ### Node and Server-Side Rendering (SSR) Usage Node environments are supported so long as you configure a Storage Engine that supports Node, such as [`redux-persist-node-storage`](https://github.com/pellejacobs/redux-persist-node-storage), [`redux-persist-cookie-storage`](https://github.com/abersager/redux-persist-cookie-storage), etc. From ac1bbf342bb2ad59d5e04e867884d08217dfc0ba Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Sun, 10 Nov 2019 14:19:37 -0500 Subject: [PATCH 5/9] (docs): README is not longer than the source anymore :'( - more because we added whitespace and split up functionality, not because transforms are in any way a big code addition (~15 LoC) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22c06d6..389b550 100644 --- a/README.md +++ b/README.md @@ -125,8 +125,8 @@ Can view the commit that implements it [here](https://github.com/agilgur5/react- ## How it works -Basically just a small wrapper around MST's [`onSnapshot` and `applySnapshot`](https://github.com/mobxjs/mobx-state-tree#snapshots). -The source code is currently shorter than this README, so [take a look under the hood](https://github.com/agilgur5/mst-persist/tree/master/src)! :) +Basically a small wrapper around MST's [`onSnapshot` and `applySnapshot`](https://github.com/mobxjs/mobx-state-tree#snapshots). +The source code is not much longer than this README, so [take a look under the hood](https://github.com/agilgur5/mst-persist/tree/master/src)! :) ## Credits From 6b51a29031af23615dfe657f830396971d1d4f92 Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Tue, 19 Nov 2019 16:55:16 -0500 Subject: [PATCH 6/9] (refactor): w/blist transform args shouldn't be optional - the array passed into the transforms should be required - same for arrToDict function - they're now called only when array is defined, and I think that behavior is more intuitive -- it should error if the array isn't defined - (test): in internal usage, they're never undefined, so this was reducing coverage because it was dead code - if the transforms were to eventually be externally visible / import-able, they should definitely throw an error in that case too -- if external users pass undefined to it - and the typings change means it'll happen at design-time too - definitely think it's more intuitive for external users this way too - (docs): use this commit for w/blist transform examples - or a variant of this commit -- just one where arr is required --- README.md | 2 +- src/transforms/blacklist.ts | 4 +--- src/transforms/utils.ts | 3 +-- src/transforms/whitelist.ts | 4 +--- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 389b550..486a571 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ interface ITransformArgs { type StrToAnyMap = {[key: string]: any} ``` -As an example, one may see how [whitelists](https://github.com/agilgur5/mst-persist/blob/229fd2b1b472ea6a7912a5a06fa079a65e3ba6fa/src/whitelistTransform.ts#L7-L14) and [blacklists](https://github.com/agilgur5/mst-persist/blob/229fd2b1b472ea6a7912a5a06fa079a65e3ba6fa/src/blacklistTransform.ts#L7-L14) are implemented internally as transforms. +As an example, one may see how [whitelists](https://github.com/agilgur5/mst-persist/blob/9ba76aaf455f42e249dc855d66349351148a17da/src/whitelistTransform.ts#L7-L12) and [blacklists](https://github.com/agilgur5/mst-persist/blob/9ba76aaf455f42e249dc855d66349351148a17da/src/blacklistTransform.ts#L7-L12) are implemented internally as transforms. #### Transform Ordering diff --git a/src/transforms/blacklist.ts b/src/transforms/blacklist.ts index 77d78bd..6d985a9 100644 --- a/src/transforms/blacklist.ts +++ b/src/transforms/blacklist.ts @@ -1,11 +1,9 @@ import { ITransform, arrToDict } from './utils' -export function blacklistKeys (blacklist?: Array): ITransform { +export function blacklistKeys (blacklist: Array): ITransform { const blacklistDict = arrToDict(blacklist) return {toStorage: function blacklistTransform (snapshot) { - if (!blacklist) { return snapshot } - Object.keys(snapshot).forEach((key) => { if (blacklistDict[key]) { delete snapshot[key] } }) diff --git a/src/transforms/utils.ts b/src/transforms/utils.ts index a28608f..7943e9b 100644 --- a/src/transforms/utils.ts +++ b/src/transforms/utils.ts @@ -10,8 +10,7 @@ export interface ITransformArgs { type StrToBoolMap = {[key: string]: boolean} -export function arrToDict (arr?: Array): StrToBoolMap { - if (!arr) { return {} } +export function arrToDict (arr: Array): StrToBoolMap { return arr.reduce((dict: StrToBoolMap, elem) => { dict[elem] = true return dict diff --git a/src/transforms/whitelist.ts b/src/transforms/whitelist.ts index cd04d7b..3df87df 100644 --- a/src/transforms/whitelist.ts +++ b/src/transforms/whitelist.ts @@ -1,11 +1,9 @@ import { ITransform, arrToDict } from './utils' -export function whitelistKeys (whitelist?: Array): ITransform { +export function whitelistKeys (whitelist: Array): ITransform { const whitelistDict = arrToDict(whitelist) return {toStorage: function whitelistTransform (snapshot) { - if (!whitelist) { return snapshot } - Object.keys(snapshot).forEach((key) => { if (!whitelistDict[key]) { delete snapshot[key] } }) From 8417f22c1ce5710de36f664a5de33b5949d54e6b Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Tue, 19 Nov 2019 18:02:42 -0500 Subject: [PATCH 7/9] (test): ensure transforms apply and do so in correct order - yay back to 100% code coverage now! - there's a transform branch missing bc there's no test case where there's something in storage and at least one toStorage-only transform - but covering this else branch doesn't really matter - create some transform fixtures - abstract out a setItem function - should consider splitting the tests and fixtures into separate files soon, esp now that we have a few different test suites here --- test/fixtures.ts | 18 ++++++++++++++++++ test/index.spec.ts | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/test/fixtures.ts b/test/fixtures.ts index 1b110a1..f447883 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -1,5 +1,7 @@ import { types } from 'mobx-state-tree' +import { ITransform, ITransformArgs } from '../src/index' + export const UserStoreF = types.model('UserStore', { name: 'John Doe', age: 32 @@ -13,3 +15,19 @@ export const persistedDataF = { name: 'Persisted Name', age: 35 } + +function changeName (name: string) { + const changeNameTransform: ITransformArgs = function (snapshot) { + snapshot.name = name + return snapshot + } + return changeNameTransform +} + +export function storeNameAsF (name: string): ITransform { + return {toStorage: changeName(name)} +} + +export function retrieveNameAsF (name: string): ITransform { + return {fromStorage: changeName(name)} +} diff --git a/test/index.spec.ts b/test/index.spec.ts index ac0561f..fb926be 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -2,13 +2,17 @@ import { getSnapshot } from 'mobx-state-tree' import { persist } from '../src/index' -import { UserStoreF, persistedDataF } from './fixtures' +import { UserStoreF, persistedDataF, storeNameAsF, retrieveNameAsF } from './fixtures' function getItem(key: string) { const item = window.localStorage.getItem(key) return item ? JSON.parse(item) : null // can only parse strings } +function setItem(key: string, value: object) { + return window.localStorage.setItem(key, JSON.stringify(value)) +} + describe('basic persist functionality', () => { beforeEach(() => window.localStorage.clear()) @@ -28,15 +32,16 @@ describe('basic persist functionality', () => { }) it('should load persisted data', async () => { - window.localStorage.setItem('user', JSON.stringify(persistedDataF)) + setItem('user', persistedDataF) const user = UserStoreF.create() await persist('user', user) + expect(getSnapshot(user)).toStrictEqual(persistedDataF) }) }) -describe('persist options', () => { +describe('basic persist options', () => { beforeEach(() => window.localStorage.clear()) it('shouldn\'t jsonify', async () => { @@ -74,3 +79,30 @@ describe('persist options', () => { expect(getItem('user')).toStrictEqual(snapshot) }) }) + +describe('transforms', () => { + beforeEach(() => window.localStorage.clear()) + + it('should apply toStorage transforms in order', async () => { + const user = UserStoreF.create() + await persist('user', user, { + transforms: [storeNameAsF('Jack'), storeNameAsF('Joe')] + }) + + user.changeName('Not Joe') // fire action to trigger onSnapshot + expect(getItem('user').name).toBe('Joe') + }) + + it('should apply fromStorage transforms in reverse order', async () => { + const persistedData = {...persistedDataF} + persistedData.name = 'Not Joe' + setItem('user', persistedData) + + const user = UserStoreF.create() + await persist('user', user, { + transforms: [retrieveNameAsF('Joe'), retrieveNameAsF('Jack')] + }) + + expect(getSnapshot(user).name).toBe('Joe') + }) +}) From 7998213f4b6e50094e207d06dbadae66cfb065b1 Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Tue, 19 Nov 2019 18:30:16 -0500 Subject: [PATCH 8/9] (docs): add link to fixtures as another transform example - not necessarily the most useful transforms there, but they are examples nonetheless - and they show both to and from Storage usage --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 486a571..a8bdf6b 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ type StrToAnyMap = {[key: string]: any} ``` As an example, one may see how [whitelists](https://github.com/agilgur5/mst-persist/blob/9ba76aaf455f42e249dc855d66349351148a17da/src/whitelistTransform.ts#L7-L12) and [blacklists](https://github.com/agilgur5/mst-persist/blob/9ba76aaf455f42e249dc855d66349351148a17da/src/blacklistTransform.ts#L7-L12) are implemented internally as transforms. +Another example would be how the [transform test fixtures](https://github.com/agilgur5/mst-persist/blob/d3aa4476f92a087c882dccf8530a37096d8c64ed/test/fixtures.ts#L19-L34) are implemented internally. #### Transform Ordering From a117d91cb6e280d06bb99017cf0eb5a997bde8ac Mon Sep 17 00:00:00 2001 From: Anton Gilgur Date: Tue, 17 Dec 2019 18:17:01 -0500 Subject: [PATCH 9/9] (docs): add an example of building a custom transform - now docs are roughly same size as source - and README is getting a bit unwieldy, thinking about splitting the Transform docs into their own file --- README.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a8bdf6b..422261b 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,35 @@ interface ITransformArgs { type StrToAnyMap = {[key: string]: any} ``` -As an example, one may see how [whitelists](https://github.com/agilgur5/mst-persist/blob/9ba76aaf455f42e249dc855d66349351148a17da/src/whitelistTransform.ts#L7-L12) and [blacklists](https://github.com/agilgur5/mst-persist/blob/9ba76aaf455f42e249dc855d66349351148a17da/src/blacklistTransform.ts#L7-L12) are implemented internally as transforms. -Another example would be how the [transform test fixtures](https://github.com/agilgur5/mst-persist/blob/d3aa4476f92a087c882dccf8530a37096d8c64ed/test/fixtures.ts#L19-L34) are implemented internally. +You can create your own transforms to serve a variety of needs. +For example, if you wanted to only store the most recent posts: + +```typescript +import { persist, ITransform } from 'mst-persist' + +import { FeedStore } from '../stores' + +const feedStore = FeedStore.create() + +const twoDaysAgo = new Date() +twoDaysAgo.setDate(twoDaysAgo.getDate() - 2) + +const onlyRecentPosts: ITransform = { + toStorage: (snapshot) => { + snapshot.posts = snapshot.posts.filter( + // note that a snapshotted Date is a string + post => new Date(post.date) > twoDaysAgo + ) + return snapshot + } +} + +persist('feed', feedStore, { + transforms: [onlyRecentPosts] +}) +``` + +For some other examples, one may see how [whitelists](https://github.com/agilgur5/mst-persist/blob/9ba76aaf455f42e249dc855d66349351148a17da/src/whitelistTransform.ts#L7-L12) and [blacklists](https://github.com/agilgur5/mst-persist/blob/9ba76aaf455f42e249dc855d66349351148a17da/src/blacklistTransform.ts#L7-L12) are implemented internally as transforms, as well as how the [transform test fixtures](https://github.com/agilgur5/mst-persist/blob/d3aa4476f92a087c882dccf8530a37096d8c64ed/test/fixtures.ts#L19-L34) are implemented internally. #### Transform Ordering @@ -127,7 +154,7 @@ Can view the commit that implements it [here](https://github.com/agilgur5/react- ## How it works Basically a small wrapper around MST's [`onSnapshot` and `applySnapshot`](https://github.com/mobxjs/mobx-state-tree#snapshots). -The source code is not much longer than this README, so [take a look under the hood](https://github.com/agilgur5/mst-persist/tree/master/src)! :) +The source code is roughly the size of this README, so [take a look under the hood](https://github.com/agilgur5/mst-persist/tree/master/src)! :) ## Credits