import { CRUDEngine , error as StoreEngineError } from '@wireapp/store-engine' ; export class WebStorageEngine implements CRUDEngine { private autoIncrementedPrimaryKey : number = 1 ; private readonly webStorage : Storage ; public storeName = '' ; constructor ( useSessionStorage = false ) { this . webStorage = useSessionStorage ? window . sessionStorage : window . localStorage ; } public async create < EntityType , PrimaryKey = string > ( tableName , primaryKey , entity ) { if ( ! entity ) { const message = `Record "${ primaryKey }" cannot be saved in "${ tableName }" because it's "undefined" or "null".` ; throw new StoreEngineError . RecordTypeError ( message ) ; } const internalPrimaryKey = this . createKey < PrimaryKey > ( tableName , primaryKey ) ; try { await this . read ( tableName , primaryKey ) ; const message = `Record "${ primaryKey }" already exists in "${ tableName }". You need to delete the record first if you want to overwrite it.` ; throw new StoreEngineError . RecordAlreadyExistsError ( message ) ; } catch ( error ) { if ( ! ( error instanceof StoreEngineError . RecordNotFoundError || ( error as Error ) . constructor . name === StoreEngineError . RecordNotFoundError . name ) ) { throw error ; } } if ( typeof entity === 'string' ) { this . webStorage . setItem ( `${ internalPrimaryKey }` , entity ) ; } else { this . webStorage . setItem ( `${ internalPrimaryKey }` , JSON . stringify ( entity ) ) ; } const keyWithoutPrefix = internalPrimaryKey . replace ( this . createPrefix ( tableName ) , '' ) ; const numericKey = parseInt ( keyWithoutPrefix , 10 ) ; const returnKey = isNaN ( numericKey ) ? keyWithoutPrefix : numericKey ; return returnKey as PrimaryKey ; } public async clearTables ( ) { this . webStorage . clear ( ) ; } public async delete < PrimaryKey = string > ( tableName , primaryKey ) { const key = this . createKey < PrimaryKey > ( tableName , primaryKey ) ; this . webStorage . removeItem ( `${ key }` ) ; return primaryKey ; } public async deleteAll ( tableName ) { Object . keys ( localStorage ) . forEach ( ( key ) => { const prefix = this . createPrefix ( tableName ) ; if ( key . startsWith ( prefix ) ) { localStorage . removeItem ( key ) ; } } ) ; return true ; } public async init ( storeName ) { await this . isSupported ( ) ; this . storeName = storeName ; return window . localStorage ; } public async isSupported ( ) { if ( typeof window === 'undefined' || ! window . localStorage ) { const message = `LocalStorage is not available on your platform.` ; throw new StoreEngineError . UnsupportedError ( message ) ; } } public async purge ( ) { this . webStorage . clear ( ) ; } public async read < EntityType = Object , PrimaryKey = string > ( tableName , primaryKey ) { const key = `${ this . storeName }@${ tableName }@${ primaryKey }` ; const record = this . webStorage . getItem ( key ) ; if ( record ) { try { const parsed = JSON . parse ( record ) ; return parsed ; } catch ( error ) { return record as any ; } } const message = `Record "${ primaryKey }" in "${ tableName }" could not be found.` ; throw new StoreEngineError . RecordNotFoundError ( message ) ; } public readAll < T > ( tableName ) { const promises = [ ] ; Object . keys ( localStorage ) . forEach ( ( key ) => { const prefix = this . createPrefix ( tableName ) ; if ( key . startsWith ( prefix ) ) { const primaryKey = key . replace ( prefix , '' ) ; promises . push ( this . read ( tableName , primaryKey ) ) ; } } ) ; return Promise . all ( promises ) ; } public async readAllPrimaryKeys ( tableName ) { const primaryKeys = [ ] ; Object . keys ( localStorage ) . forEach ( ( primaryKey ) => { const prefix = this . createPrefix ( tableName ) ; if ( primaryKey . startsWith ( prefix ) ) { primaryKeys . push ( primaryKey . replace ( prefix , '' ) ) ; } } ) ; return primaryKeys ; } public async update < PrimaryKey = string , ChangesType = Object > ( tableName , primaryKey , changes ) { const entity = await this . read ( tableName , primaryKey ) ; const updatedEntity = { ... entity , ... changes } ; let internalPrimaryKey ; try { internalPrimaryKey = await this . create ( tableName , primaryKey , updatedEntity ) ; } catch ( error ) { if ( error instanceof StoreEngineError . RecordAlreadyExistsError ) { await this . delete ( tableName , primaryKey ) ; internalPrimaryKey = await this . create ( tableName , primaryKey , updatedEntity ) ; } else { throw error ; } } return internalPrimaryKey ; } public async updateOrCreate < PrimaryKey = string , ChangesType = Object > ( tableName , primaryKey , changes ) { let internalPrimaryKey ; try { internalPrimaryKey = await this . update ( tableName , primaryKey , changes ) ; } catch ( error ) { if ( error instanceof StoreEngineError . RecordNotFoundError || ( error as Error ) . constructor . name === StoreEngineError . RecordNotFoundError . name ) { internalPrimaryKey = await this . create ( tableName , primaryKey , changes ) ; } else { throw error ; } } return internalPrimaryKey ; } private createKey < PrimaryKey = string > ( tableName , primaryKey ) { if ( primaryKey === undefined ) { primaryKey = this . autoIncrementedPrimaryKey as unknown as PrimaryKey ; this . autoIncrementedPrimaryKey += 1 ; } return `${ this . createPrefix ( tableName ) }${ primaryKey }` as unknown as PrimaryKey ; } private createPrefix ( tableName ) { return `${ this . storeName }@${ tableName }@` ; } }