5
\$\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\$
0

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.