プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

IndexedDB覚書とお試しメモ群

概要

JavaScript の IndexedDB を触ってみる。
DevTools の Console から遊べる✨

Database と ObjectStore の作成

  • 下記スクリプトの流れ ( 実質、一括セット )
    1. MyDatabaseName Hoge という名前の DB を作成
    2. 成功したら、オブジェクトストア MyObjStr を作成
      ( オブジェクトストア = いわゆるテーブル )
    3. 準備完了したら、オブジェクトを1つ突っ込む
      ( オブジェクト = いわゆるレコード )
{
    // DBを開く(または初回作成)
    const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);

    // 作成時に呼ばれる。ちな、実質最初の一回だけみたいなモンで、
    // 二回目からは「すでに作ってある」ので、基本的に呼ばれない
    openRequest.onupgradeneeded=(e)=>{
        const _db = e.target.result;
        _db.onerror = (e)=>{
            console.warn("DB読み込み失敗" , e);
        }
        // MyObjStr という名前のオブジェクトストア(テーブルみたいなモン?)を作成。
        // このとき "id" という名前を持つキーをメインに据えた
        /**
         * @type IDBObjectStore
         */
        const objectStore = _db.createObjectStore("MyObjStr" , { keyPath : "id"});
        console.log(objectStore);
        objectStore.transaction.oncomplete = function (_e) {
            // 実質初回のみ
            console.log("ObjectStore 作成完了");
        }
        // 【※】↓ 後で使います
        //objectStore.createIndex("type" , "type" , { unique: true});
    }

    openRequest.onerror = (e)=>{
        console._err(e);
    }

    // 準備完了時、ここが呼ばれる という認識で良い(ザル)
    openRequest.onsuccess = (_e1)=>{
        /**
        * @type IDBDatabase
        */
        const _iDB = _e1.target.result;
        addSomeRecord(_iDB);
    }

    /** @type IDBDatabase */
    function addSomeRecord(_iDb){
        // 書き込み可の状態で開く
        const _objStr = _iDb.transaction(["MyObjStr"], "readwrite").objectStore("MyObjStr");
        const _addObj = { id : "hogehoge" ,  type : 0 ,memo : "メモ" };
        // 上記オブジェクト(所謂レコード?)をデータベースへ追加
        const _dbReq = _objStr.add( _addObj );
        _dbReq.onsuccess = (_e)=>{
            console.log(_e);
            console.log(`新規登録に成功やで` , _addObj);
        };
        _dbReq.onerror = (_e)=>{
            console.log("保存失敗 or 保存済");
        };
    }
}

上記スクリプトの諸々は 最初の一回のみ順当に処理され、2回目からは基本的に何も起こらない。

  • 理由
    • 1度目に呼んだ時の影響で、既に MyDatabaseName Hoge データベースは作成されており
    • その中に MyObjStr オブジェクトストアも作成されており
    • キー (id) が hogehoge のオブジェクト(所謂レコード)も登録されているから。
      2回目からは .onerrorで「保存失敗」(既に登録済だから)のログが出てくるはず

DevTools の Application から、 [ DBごと / ObjectStore の中身ごと or オブジェクト単位 ] で削除できるので、
手動でDBを消しながら、都度試す のが良いと思います。

余談:Database バージョン番号について

const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);

第2引数部分は、そのDBのバージョン番号で、今後構成をアップグレードしたいときに増やす使い道があるのだとか。
( 自分は詳しく掘り下げてないのでノータッチです )

Index を増やす ( 更に unique で重複を防止する )

( ※ 作成済みのデータベースを、DevTools側で事前に削除しておいてください )
冒頭サンプルのコメントアウト部分を弄り、以下の処理を有効にしつつ、実行。

openRequest.onupgradeneeded=(e)=>{
    // ...略...
    // 【※】↓ 後で使います
+   objectStore.createIndex("type" , "type" , { unique: true});
    // ...略...
};

MDN - IDBObjectStore: createIndex()

上記 .createIndex() が有効になったスクリプトで作成されたオブジェクトストアは、
{ unique: true} の影響を受け、同じ type 値を持つレコードが登録できなくなります。

  • 以下3パターンによる違い ( 画像付き )
    1. type の Index をそもそも作成しない → 重複 OK
    2. type の Index を unique で作成した → 重複登録できない
    3. type の Index を unique 未指定で作成した → 重複 OK

もし今後 「type が 1 のオブジェクトだけ○○したい」 のような意図があるなら、
unique のオプションはさておくとしても .createIndex() を使った type 用の Index をこさえておくと良いかなと思います。

オブジェクトストア内アイテムを全回収(確認)

冒頭スクリプトにて、DBが一通り構成されている前提の話です。
DB読み出し成功時、 IDBObjectStore.getAll() で中身を全部取得・確認します。
.getAll() が成立すると、.onsuccess() が呼ばれるので、そこで受け取る感じ。
ちなみに 回収した型はArray との事。

{
    const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
    openRequest.onsuccess = (_e1)=>{
        /**
        * @type IDBDatabase
        */
        const _iDB = _e1.target.result;
        // ※ readonly で開いた
        const _objStore = _iDB.transaction(["MyObjStr"]).objectStore("MyObjStr");
        const _reqs = _objStore.getAll();
        // 成功時
        _reqs.onsuccess = (_e)=>{
            if(_e.target.result !== undefined){
                const _resArr = _e.target.result;
                if(Array.isArray(_resArr)){
                    console.log("リスト取得成功");
                    console.log(_resArr);
                    return;
                }
            }
            console.warn("取得失敗");
        }
        _reqs.onerror = (_err)=>{
            console._err(_err);
        }
    }
}

.getAll() の他に、 .get() .getAllKeys() .getKey() 等がある模様

任意キーを持つレコードを 1つ削除

IDBObjectStore.delete() を使い、DBから 指定キー hogehoge を削除します

{
    const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
    openRequest.onsuccess = (_e1)=>{
        /**
        * @type IDBDatabase
        */
        const _iDB = _e1.target.result;
        // ※ "readwrite" を指定する事
        const _objStore = _iDB.transaction(["MyObjStr"] , "readwrite" ).objectStore("MyObjStr");
        // 削除して💕
        const _dbReq = _muteObjStr.delete( "hogehoge" );
        _dbReq.onsuccess = (_e)=>{
            console.log(_e);
            console.warn("削除成功");
        };
        _dbReq.onerror = (_e)=>{
            console.log(_e);
            console.log("削除失敗 or 削除済");
        };
    }
}

範囲指定 IDBKeyRange を使う

デモにあたり、冒頭スクリプトのエラー・コメント表記等を削った 圧縮版 を用意しました
( ※ 事前にDBを削除してから実行してください )

DB構築~オブジェクト追加用スクリプト 【圧縮版】

{
    const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
    openRequest.onupgradeneeded=(e)=>{
        const _db = e.target.result;
        const objectStore = _db.createObjectStore("MyObjStr" , { keyPath : "id"});
        // ※ デモのため、本例では type に index を付けて構築
        objectStore.createIndex("type" , "type" );
    }
    openRequest.onsuccess = (_e1)=>{
        const _iDB = _e1.target.result;
        addSomeRecords(_iDB);
    }
    function addSomeRecords(_iDb){
        const _objStr = _iDb.transaction(["MyObjStr"], "readwrite").objectStore("MyObjStr");
        _objStr.add( { id : "hogehoge" ,  type : 0 ,memo : "メモ" } );
        _objStr.add( { id : "fugafuga" ,  type : 10 ,memo : "メモ" } );
        _objStr.add( { id : "piyopiyo" ,  type : 100 ,memo : "メモ" } );
        _objStr.add( { id : "mogemoge" ,  type : 10 ,memo : "メモ" } );
        _objStr.add( { id : "poyopoyo" ,  type : 10 ,memo : "メモ" } );
    }
}

上記を実行しなおすと、以下のようにずらっとレコードがセットされている筈。
( 更新しないと表示反映されない場合があるかも )

この状態から、更に以下のスクリプトで type == 10 にマッチするものをリストアップしてみます

{
    const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
    openRequest.onsuccess = (_e1)=>{
        const _iDB = _e1.target.result;
        const _objStore = _iDB.transaction(["MyObjStr"] , "readwrite" ).objectStore("MyObjStr");
        /** @type IDBIndex */
        const _typeIndex = _objStore.index("type"); // type 用の Index を取り出す
        // 条件 : 値 = 10 である
        const _range = IDBKeyRange.only(10);
        // その index 内で、条件に合う物を全て取得
        const _query = _typeIndex.getAll( _range );
        _query.onsuccess = (_e)=>{
            const _result = _e.target.result;
            console.log(_result);
        }
    }
}

条件を type >= 10 にしたいなら、下記の変更を加えてください。

- const _range = IDBKeyRange.only(10);
+ const _range = IDBKeyRange.lowerBound(10);

この様に IDBKeyRange を使った、範囲条件付けメソッドが複数用意されています。

任意 Index が指定値以上のものをすべて削除

先程の DB & レコード構成 を対象として、
今度は type >= 10 に該当するオブジェクト(レコード)を削除してみます。

ちなみに cursor なる概念が新しく登場。

{
    const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
    openRequest.onsuccess = (_e1)=>{
        const _iDB = _e1.target.result;
        const _transaction = _iDB.transaction(["MyObjStr"] , "readwrite" );
        _transaction.oncomplete = (_e)=>{
            console.log("処理(削除)完了");
        }
        const _objStore = _transaction.objectStore("MyObjStr");
        /** @type IDBIndex */
        const _typeIndex = _objStore.index("type"); // type 用の Index を取り出す
        // 条件 : 値 >= 10 である
        const _range = IDBKeyRange.lowerBound(10);
        /** @type IDBRequest */
        const _cursor = _typeIndex.openCursor( _range );
        // 条件に合ったものを1つずつ .onsuccess で呼ぶ
        _cursor.onsuccess = (_e)=>{
            const _cursr = _e.target.result;
            console.log(_cursr);
            if(_cursr){
                // 削除する
                _cursr.delete();
                // 次のレコードを扱う .onsuccess を呼ぶ …を繰り返す。
                _cursr.continue();
            }else{
                // 削除できるレコードがなくなったので終わり
                console.log("これ以上削除するものはありません");
            }
        }
    }
}

削除対象がなくなるまでコールバック(.onsuccess())をブンブン回しつづける、ってのがポイントのようです。
個人的に、イテレータ的概念を連想しました。

削除完了のタイミングは _cursr が 偽 になったタイミングより、
トランザクションの .oncomplete() 内部で判断させたほうが行儀良さそう。

日付指定でなんやかんやする

レコードに Date も突っ込めるらしい ので、日付系条件指定のレコード操作も書いてみました

作成される DB 構成も、冒頭スクリプトのものから変更しています

以下は、実行した当日を含み、さらに三日ずつ遡った Date オブジェクトを5つ登録する処理です。

{
    const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
    openRequest.onupgradeneeded=(e)=>{
        const _db = e.target.result;
        const objectStore = _db.createObjectStore("MyObjStr" , { keyPath : "id"});
        objectStore.createIndex("date" , "date" );
    }
    openRequest.onsuccess = (_e1)=>{
        const _iDB = _e1.target.result;
        addSomeRecords(_iDB);
    }
    function addSomeRecords(_iDb){
        const _objStr = _iDb.transaction(["MyObjStr"], "readwrite").objectStore("MyObjStr");
        const _step = 3;
        const _now = new Date();
        // [0,1,2,3,4] の配列を作成し、現在含む、三日ずつ遡った Date を登録
        (Array.from({ length:5 },(_,i)=>i)).forEach((_e,_ind)=>{
            const _date = new Date();
            _date.setDate( _now.getDate() - ( _step *_ind) );
            const _obj = { id : _ind, date : _date }
            console.log( _obj );
            _objStr.add( _obj );
        });
    }
}

このレコード群を対象に、7日以上前のレコードを全て削除する 下記処理をあてがってみます

{
    // 7日前の date を持つレコードを削除
    const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
    openRequest.onsuccess = (_e1)=>{
        const _iDB = _e1.target.result;
        const _transaction = _iDB.transaction(["MyObjStr"] , "readwrite" );
        _transaction.oncomplete = (_e)=>{
            console.log("処理(削除)完了");
        }
        const _objStore = _transaction.objectStore("MyObjStr");
        /** @type IDBIndex */
        const _dateIndex = _objStore.index("date"); // date 用の Index を取り出す
        const _now = new Date();
        const _targetDate = new Date();
        _targetDate.setDate( _now.getDate() - 7 );
        // 条件 : 値 <= (今から七日前の日付) である
        const _range = IDBKeyRange.upperBound( _targetDate );
        /** @type IDBRequest */
        const _cursor = _dateIndex.openCursor( _range );
        // 条件に合ったものを1つずつ .onsuccess で呼ぶ
        _cursor.onsuccess = (_e)=>{
            const _cursr = _e.target.result;
            console.log(_cursr);
            if(_cursr){
                // 削除する
                _cursr.delete();
                // 次のレコードを扱う .onsuccess を呼ぶ …を繰り返す。
                _cursr.continue();
            }else{
                // 削除できるレコードがなくなったので終わり
                console.log("これ以上削除するものはありません");
            }
        }
    }
}

[ 実行日(本日) , 3日前 , 6日前 , 9日前 , 12日前 ] と、5つあったレコードのうち、
[ 9日前 , 12日前 ] が削除されました。

今回、条件付けの範囲指定として IDBKeyRange.upperBound() を用いています。