import { action , observable } from "mobx" ; export interface IStringifier < T > { serialize : ( value : T ) => string ; deserialize : ( value : string ) => T ; } export class JSONStringifier < T > implements IStringifier < T > { serialize ( value ) { return JSON . stringify ( value ) ; } deserialize ( value ) { return JSON . parse ( value ) as T ; } } export class ArrayStringifier < T > implements IStringifier < T [ ] > { constructor ( private stringifier ) { } serialize ( value ) { return JSON . stringify ( value . map ( ( v ) => this . stringifier . serialize ( v ) ) ) ; } deserialize ( value ) { return ( JSON . parse ( value ) as string [ ] ) . map ( ( v ) => this . stringifier . deserialize ( v ) ) ; } } export class Persistent < T > { public static json < T > ( key , defaultValue ) { return new Persistent < T > ( new JSONStringifier ( ) , key , defaultValue ) ; } public static key ( ... parts ) { return parts . map ( ( part ) => encodeURIComponent ( part ) ) . join ( "/" ) ; } public static custom < T > ( key , defaultValue , stringifier ) { return new Persistent < T > ( stringifier , key , defaultValue ) ; } @ observable private value : T = null ; private constructor ( private stringifier , public readonly key , defaultValue ) { const localStorageValue = localStorage . getItem ( key ) ; if ( typeof localStorageValue === "undefined" || localStorageValue === null ) { this . value = defaultValue ; } else { try { this . value = this . stringifier . deserialize ( localStorageValue ) ; } catch ( e ) { this . value = defaultValue ; } } } get ( ) { return this . value ; } set ( value ) { this . value = value ; localStorage . setItem ( this . key , this . stringifier . serialize ( value ) ) ; } /**
     * Call after mutations to the object itself to synchronize value to storage.
     */ sync ( ) { this . set ( this . get ( ) ) ; } }