Dexie.jsを触ってみた。

こんにちは!k-junです。 入社してからは初のエントリーとなります。皆さんいかがお過ごしでしょうか。コロナの影響で研修が全てリモートに移行してしまい かなりの精神的ダメージを負っております。辛いです。対面のコミュニケーションが好きです。

今回はindexedDBを扱うjsのwrapperであるDexie.jsを触っていこうと思います。

基本的な概念 - Web API | MDN

LocalStorageと役割が似ていますが、以下のような点でLocalStorageと比べて優れています。

  • データのやりとりが高速
  • トランザクションを発行できる
  • 保存形式の自由度が高い
  • 保存容量が多い
  • WebWorkerからアクセス可能

セキュリティに関しても上記のリンクで以下のように述べられています。

IndexedDB に与えられたセキュリティ境界は、アプリケーションが別の生成元のデータにアクセスできないようにします。例えば http://www.example.com/app/ のアプリやページは、同一生成元である http://www.example.com/dir/ からデータを取り出すことができます。しかし、生成元が異なる http://www.example.com:8080/dir/ (ポートが異なる) や https://www.example.com/dir/ (プロトコルが異なる) からデータを取り出すことはできません。

Client側でデータの保存場所としてかなり強力に動作するDBみたいですね。SQLのようなものこそ存在しないものの、Dexie.js側に多くの便利なmethodが用意されておりサーバー側で使用するDBのような感覚で利用することができます。

まずは保存場所となるDBを作ります。 todosのvalueとして定義されている'++id,&name,*subTasks'がschemaとなります。 保存形式はJsonとなるため、ここで定義されているのはindexを付与するcolumnのみになります。 各記号と持つ意味は以下のようになります。

mark effect
++ Auto-incremented primary key
& Unique
* Multi-entry index
[A+B] Compound index
const db = new Dexie('todo_database');
db.version(1).stores({
     todos: '++id,&name,*subTasks'
});

作成時に指定しているversionはそのままversionの更新度として用いられるようです。更新時には数字を増やし、schemaを再度定義する感じですね。 どうでもいいんですけれども、どのサンプルをみても数字が1から始まるのはなんでなんでしょうか??

db.version(2).stores({
  todos: '++id,&name,*subTasks,author'
}).upgrade(tx => {
  return tx.table('todos').modify(todo => {
    todo.author = 'created by Anonymous'
  })
})

migrationの際には以下のように既存のデータを更新していきます。dexie.jsのversionによっては以前のversionを残して置く必要があるようです。 ここら辺のversion管理どうやってるんですかね?service-workerとかもそうですけれども、ブラウザでできることが調べれば調べるほど出てくるんですよねぇ...

ここまで完成したらindexedDBが使い放題なので、各種CRUDや検索などを行っていけます。基本的なCRUDはもちろんのことですが、 検索に関しても正規表現の他に各種便利なmethodが生えていたりといい感じです。

// create
const id = await db.todos.add({ name: 'unique name', subTasks: ['sub1', 'sub2', 'sub3'], author: 'kesjun' })
// read
db.todos.get(id, (todo) => console.log(todo))
// update
db.todos.update(id, { name: 'updated name', subTasks: ['sub4', 'sub5'], author: 'keijun2' })
// search
db.todos.where('author').equalsIgnoreCase('keijun').each(todo => console.log(todo))
// delete
db.todos.delete(id)

migration周りは多少手間ですが、簡単に強力なデータの保存場所となりうるので使える状況に遭遇したらどんどん使っていければなあと思います。 それでは!