import { Observable , Scheduler } from 'rx' ; /**
 * A Factory type to produce Idb compatible objects. Requires a database name for the Idb
 * implementation to use and a list of stores that the Idb implementation may access.
 */ export type IdbFactory = ( database : string , stores : string [ ] ) => Idb ; /**
 * A Factory to produce an InMemoryIdb implementation of Idb. Requires a database name for
 * InMemoryIdb to use and a list of stores that InMemoryIdb may access.
 */ export var InMemoryIdbFactory = ( database , stores ) : InMemoryIdb => { var db = { } ; stores . forEach ( ( store ) => { db [ store ] = { } ; } ) ; return new InMemoryIdb ( db ) ; } ; /**
 * A Factory type to produce an IndexedDB implementation of Idb. Requires a database name for
 * IndexedDB to use and a list of stores that IndexedDB may access.
 */ export var IndexedDBFactory = ( database , stores ) : IndexedDB => { return new IndexedDB ( Observable . create < IDBDatabase > ( ( observer ) => { if ( indexedDB ) { var DBOpenRequest = indexedDB . open ( database ) ; DBOpenRequest . onupgradeneeded = ( upgrade ) => { stores . forEach ( ( store ) => { DBOpenRequest . result . createObjectStore ( store ) ; } ) ; } ; DBOpenRequest . onsuccess = ( success ) => { observer . onNext ( DBOpenRequest . result ) ; } ; DBOpenRequest . onerror = ( error ) => { observer . onError ( error . message ) ; } ; } else { observer . onError ( 'indexedDB is undefined' ) ; } } ) ) ; } ; /**
 * A very minimal interface for a key-value store, designed as an abstraction
 * layer over IndexedDB.
 */ export interface Idb { keys ( store ) : Observable < string > ; get ( store , key ) : Observable < Object > ; put ( store , key , value ) : Observable < boolean > ; remove ( store , key ) : Observable < boolean > ; transaction ( stores ) : Observable < IdbTransaction > ; } /**
 * An interface for a single Idb transaction that provides a lock on all scoped
 * stores.
 */ export interface IdbTransaction { get ( store , key ) : Observable < Object > ; put ( store , key , value ) : Observable < boolean > ; remove ( store , key ) : Observable < boolean > ; } /**
 * An in-memory implementation of the Idb interface, mostly useful for testing.
 */ export class InMemoryIdb implements Idb { constructor ( public db ) { } /**
     * Emits all the keys present in the given store.
     */ keys ( store ) { if ( ! this . db . hasOwnProperty ( store ) ) { return Observable . empty < string > ( ) ; } return Observable . from < string > ( Object . keys ( this . db [ store ] ) ) ; } /**
     * Retrieve the object associated with a key from the given store, or return
     * null if the key doesn't exist in that store.
     */ get ( store , key ) { if ( ! this . db . hasOwnProperty ( store ) ) { return Observable . just < Object > ( null , Scheduler . currentThread ) ; } return Observable . just < Object > ( this . _clone ( this . db [ store ] [ key ] ) , Scheduler . currentThread ) ; } /**
     * Store an object by key in the given store.
     */ put ( store , key , value ) { if ( ! this . db . hasOwnProperty ( store ) ) { return Observable . just < boolean > ( false , Scheduler . currentThread ) ; } this . db [ store ] [ key ] = this . _clone ( value ) ; return Observable . just < boolean > ( true , Scheduler . currentThread ) ; } /**
     * Remove the given key from the given store.
     */ remove ( store , key ) { if ( ! this . db . hasOwnProperty ( store ) || ! this . db [ store ] . hasOwnProperty ( key ) ) { return Observable . just < boolean > ( false , Scheduler . currentThread ) ; } delete this . db [ store ] [ key ] ; return Observable . just < boolean > ( true , Scheduler . currentThread ) ; } /**
     * Returns a single IdbTransaction scoped over the given stores.
     */ transaction ( stores ) { return Observable . just < IdbTransaction > ( new InMemoryTransaction ( this . db ) , Scheduler . currentThread ) ; } _clone ( value ) { if ( value === null || value === undefined ) { return value ; } var data = JSON . stringify ( value ) ; return JSON . parse ( data ) ; } } /**
 * An implementation of the IdbTransaction interface for testing.
 */ export class InMemoryTransaction implements IdbTransaction { constructor ( public db ) { } /**
     * Retrieve the object associated with a key from the given store, or return
     * null if the key doesn't exist in that store.
     */ get ( store , key ) { if ( ! this . db . hasOwnProperty ( store ) ) { return Observable . just < Object > ( null , Scheduler . immediate ) ; } return Observable . just < Object > ( this . db [ store ] [ key ] , Scheduler . immediate ) ; } /**
     * Store an object by key in the given store.
     */ put ( store , key , value ) { if ( ! this . db . hasOwnProperty ( store ) ) { return Observable . just < boolean > ( false , Scheduler . immediate ) ; } this . db [ store ] [ key ] = value ; return Observable . just < boolean > ( true , Scheduler . immediate ) ; } /**
     * Remove the given key from the given store.
     */ remove ( store , key ) { if ( ! this . db . hasOwnProperty ( store ) || ! this . db [ store ] . hasOwnProperty ( key ) ) { return Observable . just < boolean > ( false , Scheduler . immediate ) ; } delete this . db [ store ] [ key ] ; return Observable . just < boolean > ( true , Scheduler . immediate ) ; } } /**
 * An implementation of the Idb interface over IndexedDB. Requires an open connection to indexeddb.
 *
 * TODO(ttowncompiled): Reopen the connection if it closes unexpectedly.
 */ export class IndexedDB implements Idb { static READ_WRITE : string = 'readwrite' ; constructor ( private _dbConnection ) { } /**
     * Emits all the keys present in the given store.
     */ keys ( store ) { return this . _dbConnection . flatMap ( ( idbDatabase ) => { return Observable . create < string > ( ( observer ) => { var transaction = idbDatabase . transaction ( [ store ] ) ; transaction . onerror = ( error ) => { observer . onNext ( null ) ; } ; var objectStore = transaction . objectStore ( store ) ; objectStore . openCursor ( ) . onsuccess = ( success ) => { var cursor = ( < any > event . target ) . result ; if ( cursor ) { observer . onNext ( cursor . key ) ; cursor . continue ( ) ; } } ; } ) ; } ) ; } /**
     * Retrieve the object associated with a key from the given store, or return
     * null if the key doesn't exist in that store.
     */ get ( store , key ) { return this . _dbConnection . flatMap ( ( idbDatabase ) => { return Observable . create < Object > ( ( observer ) => { var transaction = idbDatabase . transaction ( [ store ] ) ; transaction . onerror = ( error ) => { observer . onNext ( null ) ; } ; var objectStore = transaction . objectStore ( store ) ; var getRequest = objectStore . get ( key ) ; getRequest . onsuccess = ( success ) => { observer . onNext ( getRequest . result ) ; } ; getRequest . onerror = ( error ) => { observer . onNext ( null ) ; } ; } ) ; } ) ; } /**
     * Store an object by key in the given store.
     */ put ( store , key , value ) { return this . _dbConnection . flatMap ( ( idbDatabase ) => { return Observable . create < boolean > ( ( observer ) => { var transaction = idbDatabase . transaction ( [ store ] , IndexedDB . READ_WRITE ) ; transaction . onerror = ( error ) => { observer . onNext ( false ) ; } ; var objectStore = transaction . objectStore ( store ) ; var putRequest = objectStore . put ( value , key ) ; putRequest . onsuccess = ( success ) => { observer . onNext ( true ) ; } ; putRequest . onerror = ( error ) => { observer . onNext ( false ) ; } ; } ) ; } ) ; } /**
     * Remove the given key from the given store.
     */ remove ( store , key ) { return this . _dbConnection . flatMap ( ( idbDatabase ) => { return Observable . create < boolean > ( ( observer ) => { var transaction = idbDatabase . transaction ( [ store ] , IndexedDB . READ_WRITE ) ; transaction . onerror = ( error ) => { observer . onNext ( false ) ; } ; var objectStore = transaction . objectStore ( store ) ; var removeRequest = objectStore . delete ( key ) ; removeRequest . onsuccess = ( success ) => { observer . onNext ( true ) ; } ; removeRequest . onerror = ( error ) => { observer . onNext ( false ) ; } ; } ) ; } ) ; } /**
     * Returns a single IdbTransaction scoped over the given stores.
     */ transaction ( stores ) { return this . _dbConnection . flatMap ( ( idbDatabase ) => { return Observable . create < IdbTransaction > ( ( observer ) => { var transaction = idbDatabase . transaction ( stores , IndexedDB . READ_WRITE ) ; transaction . onerror = ( error ) => { observer . onError ( error ) ; } ; observer . onNext ( new IndexedDBTransaction ( transaction ) ) ; } ) ; } ) ; } } /**
 * An implementation of the IdbTransaction interface for IndexedDB.
 */ export class IndexedDBTransaction implements IdbTransaction { constructor ( private _transaction ) { } /**
     * Retrieve the object associated with a key from the given store, or return
     * null if the key doesn't exist in that store.
     */ get ( store , key ) { return Observable . create < Object > ( ( observer ) => { var getRequest = this . _transaction . objectStore ( store ) . get ( key ) ; getRequest . onsuccess = ( success ) => { observer . onNext ( getRequest . result ) ; } ; getRequest . onerror = ( error ) => { observer . onError ( error ) ; } ; } ) ; } /**
     * Store an object by key in the given store.
     */ put ( store , key , value ) { return Observable . create < boolean > ( ( observer ) => { var putRequest = this . _transaction . objectStore ( store ) . put ( value , key ) ; putRequest . onsuccess = ( success ) => { observer . onNext ( true ) ; } ; putRequest . onerror = ( error ) => { observer . onError ( error ) ; } ; } ) ; } /**
     * Remove the given key from the given store.
     */ remove ( store , key ) { return Observable . create < boolean > ( ( observer ) => { var removeRequest = this . _transaction . objectStore ( store ) . delete ( key ) ; removeRequest . onsuccess = ( success ) => { observer . onNext ( true ) ; } ; removeRequest . onerror = ( error ) => { observer . onError ( error ) ; } ; } ) ; } }