VuexとFirestoreをいい感じにつなぐnpmパッケージを作ってみた

# 経緯
Nuxt.jsでvuexfire使おうとしたときにTwitterとかQiitaとかGihubとか色々検索してたら、Nuxtと相性がよろしくなさそうなことがわかったため、Nuxtjsで動くようなパッケージ作ろうと思い、作りました！:grinning:
ちなみにnpmは初公開で、お作法があまりわかってません。多目に見てください。:bow_tone4:

- https://github.com/vuejs/vuefire/issues/203

# 作ったパッケージとサンプル
## firex-store
- https://github.com/nor-ko-hi-jp/firex-store

## firex-store使ってるサンプルプログラム
- https://github.com/nor-ko-hi-jp/firex-store-sample

## 機能
機能については大きく分けて3つあります！
1. firestoreからのデータのサブスクライブ・アンサブスクライブ解除機能
2. データ取得機能
3. サブスクライブ・サブスクライブ解除のAction生成機能

使い方については以下になります！
https://github.com/nor-ko-hi-jp/firex-store/blob/master/docs/v1/v1-usage.md



## オプションについて
上記機能についてですが、
FirestoreSubscriber/FirestoreFinder/firestoreSubscribeAction
にはoptionsという引数が存在します。
その機能についてです。
説明を先にして、例は最後に載せます！

## 1. mapper
配列やrxjsを使っている人ならよくご存知の機能です。
この場合、firestoreより取得・サブスクライブしてくる値を変換することができます。
ただ、注意していただきたいのは、
firestoreのdocumentIdは`docId`という値でmap機能を通しても入ってきます。
何も定義しない場合は、何も変換処理が走らず、firestoreに登録されているデータがそのまま入ってきます。


## 2. errorHandler
そのままです。エラーが発生した場合の処理を設定できます。
デフォルトだと`console.error(error)`が呼び出されます。

## 3. completionHandler
そのままです。処理が終わった場合に呼ばれます。

## 4. afterMutationCalled
**※ データ取得処理（FirestoreFinder）では呼ばれません。**
サブスクライブ機能の中でmutationが終わった後に呼び出されます。
呼び出される際の引数としては以下のような構成になっています。

```Typescript
interface Payload {
  data: { docId: string, [key: string]: any }
  statePropName: string
  isLast?: boolean
}
```
- dataはfirestoreからデータを取得→Mapperで変換した後の値が入ってます。
- statePropNameはバインドするプロパティ名が入ってます。
- isLastはコレクションをサブスクライブした際にのみtrue/falseが入ります。文字通り、collectionの最後のみtrueになります。
これを用いることで、progress circularのようなloading描画の終了のタイミングを設定することができるようになります。

## 5. notFoundHandler
**※ データ取得処理（FirestoreFinder）では呼ばれません。**
データ存在しなかった際に呼びされます。
呼び出される際の引数としては以下の2つの引数を持ちます。


```Javascript
type: string,
isAll: boolean
```
- typeはサブスクライブしているデータが'document'か'collection'かが入っています。
- isAllは'collection'の場合、全データがなかったのか、1ドキュメントがなかったのかという判定で使用することができるフラグになっています。


上記のサンプルから一部抜粋です。オプション項目はこのように使います。

```Javascript:store/comment.js

import {
  firestoreMutations,
  firestoreUnsubscribeAction,
  on,
  from,
  actionTypes
} from 'firex-store'
import { firestore } from '../plugins/firebase'

export const state = () => ({
  comments: [],
  isLoaded: false
})

export const getters = {
  comments: (state) => state.comments,
  isLoaded: (state) => state.isLoaded
}

export const mutations = {
  ...firestoreMutations('collection'),
  INITIALIZED: (state) => {
    state.comments = []
  },
  SET_LOAD_STATE: (state, isLoaded) => {
    state.isLoaded = isLoaded
  }
}

export const actions = {
  [actionTypes.collection.SUBSCRIBE]: ({ state, commit }) => {
    from(firestore.collection('/comments').orderBy('date', 'asc'))
      .bindTo('comments')
      .subscribe(state, commit, {
        mapper: (data) => ({
          message: data.message,
          date: data.date,
          user: {
            docId: data.user.docId,
            displayName: data.user.display_name
          }
        }),
        afterMutationCalled: (payload) => {
          if (typeof payload.isLast === 'undefined') {
            return
          }
          commit('SET_LOAD_STATE', payload.isLast)
        }
      })
  },
  ...firestoreUnsubscribeAction(on('comments'), { type: 'collection' }),
  CREATE: (_, { comment }) => {
    firestore.collection('/comments').add(comment)
  }
}
```


```Javascript:pages/comments.vue
...
 async asyncData({ store }) {
    const user = await from(
      firestore.collection('/users').doc(store.getters['auth/uid'])
    )
      .once()
      .find({ completionHandler: () => console.log('can fetch') })
    return { user }
  },
  async fetch({ store }) {
    // store.commit('comment/INITIALIZED') //  if you'd like to unsubscribe, comment out, please
    await store.dispatch(`comment/${actionTypes.collection.SUBSCRIBE}`)
  },
...
```

# 今後の展開
データの書き込み処理（transactionやincrementも含む）も追加していく予定です！
気に入れば是非使ってやってください！:bow_tone4: