4
\$\begingroup\$

Inspired by Wrapping IndexDB with RxJs.

(Edit - Added a Stackblitz example).

My use case is a simple, on-disk temporary cache. No errors are propagated, null values are returned instead. All errors are logged.

import { Injectable } from '@angular/core';
import { Observable, Observer, ReplaySubject, Subject } from 'rxjs';
import { take, filter } from 'rxjs/operators';

const VERSION = 1;
interface Record {
    key: string;
    value: any;
    ttl: number;
    timestamp: number;
}
type RecordInput = Omit<Record, 'timestamp'>;
@Injectable({
    providedIn: 'root',
})
export class IndexedDbService {
    db = new ReplaySubject<IDBDatabase | null>(1);
    $db = this.db.pipe(take(1), filter(db => !!db));

    constructor() {
        const onError = error => {
            console.log(error);
            this.db.complete();
        };
        if (!window.indexedDB) {
            onError('IndexedDB not available');
        } else {
            const openRequest = indexedDB.open('myapp', VERSION);
            openRequest.onerror = () => onError(openRequest.error);
            openRequest.onsuccess = () => this.db.next(openRequest.result);
            openRequest.onupgradeneeded = () => {
                try {
                    const db: IDBDatabase = openRequest.result;
                    const cacheStore = db.createObjectStore('store', { keyPath: 'key' });
                    cacheStore.createIndex('value', 'value');
                    cacheStore.createIndex('timestamp', 'timestamp');
                    cacheStore.createIndex('ttl', 'ttl');
                } catch (error) {
                    onError(error);
                }
            };
        }
    }

    get(storeName: string, key: string): Observable<Record | null> {
        return Observable.create((observer: Observer<Record>) => {
            const onError = error => {
                console.log(error);
                observer.complete();
            };
            this.$db.subscribe(db => {
                try {
                    const txn = db.transaction([storeName], 'readonly');
                    const store = txn.objectStore(storeName);
                    const getRequest: IDBRequest<Record> = store.get(key);
                    getRequest.onerror = () => onError(getRequest.error);
                    getRequest.onsuccess = () => {
                        const record = getRequest.result;
                        if (!record ||
                            new Date(Date.now() - record.timestamp).getSeconds() > record.ttl
                        ) {
                            observer.next(null);
                        } else {
                            observer.next(getRequest.result);
                        }
                        observer.complete();
                    };
                } catch (err) {
                    onError(err);
                }
            });
        });
    }

    put(storeName: string, value: RecordInput): Observable<IDBValidKey | null> {
        return Observable.create((observer: Observer<IDBValidKey>) => {
            const onError = error => {
                console.log(error);
                observer.complete();
            };
            this.$db.subscribe(db => {
                try {
                    const txn = db.transaction([storeName], 'readwrite');
                    const store = txn.objectStore(storeName);
                    const record: Record = {...value, timestamp: Date.now()};
                    const putRequest = store.put(record);
                    putRequest.onerror = () => onError(putRequest.error);
                    putRequest.onsuccess = () => {
                        observer.next(putRequest.result);
                        observer.complete();
                    };
                } catch (err) {
                    onError(err);
                }
            });
        });
    }

    clear(storeName: string) {
        return Observable.create((observer: Observer<void>) => {
            this.$db.subscribe(db => {
                try {
                    db.transaction([storeName], 'readwrite').objectStore(storeName).clear();
                } catch (error) {
                    console.log(error);
                }
                observer.complete();
            });
        });
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.