IndexedDBをSQLで操作可能なSqlWebというライブラリを使ってみた

# 概要
IndexedDBをSQLで操作してみたいと思って探してみたところ良さそうなライブラリがあったのでメモ

## 環境
macOS 10.13.6
npm 6.9.0

# インストール
公式のGitHubではReact、TypeScript、Webpack、CDNの４つのサンプルを置いてくれているので、今回はWebpackでテスト。npmは導入されている状態です。

## Webpackのインストール
適当なディレクトリを作成してWebpackをインストールします。

```
$ mkdir test_sqlweb
$ cd test_sqlweb
$ npm init -y
$ npm i webpack webpack-cli --save-dev
```

インストールが完了したらwebpack.config.jsを作成しておきます。
src/sqlweb.jsにコードを書き、バンドルされたファイルはdist/bundle.jsに出力させる予定なので下記のように記載します。

```javascript:webpack.config.js
const path = require('path');

module.exports = {
    mode: "development",
    entry: path.resolve(__dirname, "src/sqlweb.js"),
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "sqlweb.bundle.js"
    }
};
```

## SqlWebのインストール
SqlWebはIndexedDBをSQL「風に」扱うJsStoreというライブラリの拡張ですので、JsStoreとコード中に用いるfile-loaderも同時にインストールします。
~~※この記事を書いている時にJsStoreのアップデートがあったみたいで、最新の3.0.0だと用意したプログラムが動作しなかったので、以前のバージョンをインストールします。~~
※2019/07/14追記 最新版でも動作を確認できましたので、最新バージョンで問題なさそうです。

```
$ npm i jsstore sqlweb file-loader --save-dev
```

# 動作確認
## コード作成
前述の通りsrc/sqlweb.jsにコードを書いていきます。

```javascript:sqlweb.js
import * as JsStore from "jsstore";
import * as SqlWeb from 'sqlweb';

// バックグラウンドで動かすためのWeb workerの設定
const workerPath = require("file-loader?name=scripts/jsstore.worker.js!../node_modules/jsstore/dist/jsstore.worker");
const con = new JsStore.Instance(new Worker('dist/'+workerPath));

// SQL Webを使用する宣言
JsStore.useSqlWeb(SqlWeb);

// indexedDBの設定
const dbName = "sample_db";
const tbName = "sample_tb";

// indexedDBの初期化
initJsStore();

function initJsStore(){
    con.runSql(`ISDBEXIST ${dbName}`).then((isExist) => {
        if (isExist) {
            const qry = 'OPENDB ' + dbName;
            con.runSql(qry);
        } else {
            const qry = getDbQuery();
            con.runSql(qry);
            // 初期データの追加
            addData();
        }
    }).catch(err => {
        console.error(err);
    })
}

// 初期データの追加
function addData(){
    const data = [
        {name:'TARO', club:'baseball'},
        {name:'JIRO', club:'soccer'},
        {name:'SABU', club:'tennis'},
    ]
    data.forEach(val =>{
        const qry = `insert into ${tbName} values ({name: '${val.name}', club: '${val.club}'})`;
        con.runSql(qry);
    })
}

// DBとテーブルの初期化用SQL作成
function getDbQuery() {
    const db = `DEFINE DB ${dbName};`;
    const tblSampleQry = `
DEFINE TABLE ${tbName}(
id PRIMARYKEY AUTOINCREMENT,
name STRING NOTNULL ,
club STRING NOTNULL
);`;
    const dbCreatequery = db + tblSampleQry;
    return dbCreatequery;
}

```

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>SqlWeb Test Page</title>
</head>
<body>
    <h1>Hello SqlWeb!</h1>
    <script src="dist/bundle.js"></script>
</body>
</html>
```

現状のディレクトリ構成は下記のようになっています。

```
test_sqlweb
├── dist
│   ├── scripts
│   │   └── jsstore.worker.js
│   └── sqlweb.bundle.js
├── index.html
├── node_modules
|   └── ...
├── package-lock.json
├── package.json
├── src
│   └── sqlweb.js
└── webpack.config.js
```


ここまで書いたらpackage.jsonを以下のように修正して、コマンドを実行します。

```json:package.json
{
  "name": "test_sqlweb",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "build": "webpack",
    "start": "webpack --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "file-loader": "^3.0.1",
    "jsstore": "^2.12.0",
    "sqlweb": "^1.2.1",
    "webpack": "^4.32.2",
    "webpack-cli": "^3.3.2"
  },
  "dependencies": {}
}
```

```
$ npm start
```

## ブラウザでの確認
ブラウザでindex.htmlを開き、デベロッパーツールで確認するとIndexedDBにデータが書き込まれているのが確認できます。

### Chrome
<img width="872" alt="スクリーンショット 2019-06-02 11.20.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183878/2d40fa76-6ac5-69ce-b275-f216a7a434e7.png">

### Firefox
<img width="863" alt="スクリーンショット 2019-06-02 11.20.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183878/19df900a-e9c4-1f73-ef0c-b8f97daabe2c.png">

# はまったところ
* NetworkError: Failed to load worker script
Chromeだとエラー文すら出力されなくてFirefoxだとこのエラーになる。workerファイルのパスがずれていたのでそれを修正して解決。

* Uncaught (in promise) DOMException: Failed to execute 'open' on 'IDBFactory': access to the Indexed Database API is denied in this context.
Chromeでindex.htmlを開いた場合に出たエラー。Firefoxではエラーにならない。Chromeに起動オプションを付けて起動することで解決。
[Access to the Indexed Database API is denied in this context](https://stackoverflow.com/questions/42805775/access-to-the-indexed-database-api-is-denied-in-this-context)
[MacでGoogleChromeで起動オプションを設定する方法](https://xenontenter.com/srware-iron-flash/)

* JsStoreのバージョンが上がり、以前のプログラムが動作しなくなった。(2019/06/02時点)
恐らくこれもWebworkerのパスの指定方法が問題じゃないかとあたりを付けているが、現状は調べきれなかった。JsStoreのバージョンを下げることで解決。

# 感想
SQLでローカルに保存できるデータを扱いたいなあと思って探していたらSqlwebに行き着きました。NoSQLであるIndexedDBをSQLで操作できるという点は非常に良いです。ただし、SQL文に若干癖があり(CREATE DBがDEFINE DBだったり、insertの際の値の指定法等)、うまく利用するには工夫が必要といった印象です。

また、Sqlwebの動作を確認するために、フォームに打ったSQLをそのまま実行するページを作りましたので貼っておきます。
https://github.com/ryu022304/sqlweb-test

# 参考サイト
* [JsStore](http://jsstore.net/)
* [Sqlweb](https://github.com/ujjwalguptaofficial/sqlweb)
* [JsStore - IndexedDBを使いやすくするO/Rマッパー風ライブラリ](https://www.moongift.jp/2018/09/jsstore-indexeddb%E3%82%92%E4%BD%BF%E3%81%84%E3%82%84%E3%81%99%E3%81%8F%E3%81%99%E3%82%8Bor%E3%83%9E%E3%83%83%E3%83%91%E3%83%BC%E9%A2%A8%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA/)
* [lovefield で IndexedDB を SQL風に操作する方法](https://webbibouroku.com/Blog/Article/lovefield-indexeddb)