@yarnaimo です ✋
最近 RSS と Twitter から情報を収集するツールを作っているのですが、その副産物として TypeScript 向け Firestore ライブラリ BlueSpark ができたので紹介します。
11/13 追記: v2.1.0 更新に伴い API がわかりやすくなりました。
概要
TypeScript の Firestore ライブラリはすでにいくつか存在していますが、web と admin のどちらか片方にしか対応していなかったり、Timestamp と Date など読み取りと書き込みの際で型が異なるフィールドの扱いが難しいといった課題がありました。
BlueSpark はモデル定義とコレクション定義を分けたことでこれらの課題をクリアしつつ覚えやすい設計になっています。
yarnaimo/bluespark
https://github.com/yarnaimo/bluespark
TypeScript向けFirestoreライブラリ BlueSparkを作りました!!🎉
— やまいも (@yarnaimo) October 20, 2019
✅ web / admin どちらにも対応
✅ シンプルなモデル定義
✅ 読み取り/書き込みの型を分けられる (読み取りはTimestamp / 書き込みはDate | FieldValue など)
yarn add bluespark でインストールできます!https://t.co/gAiyR1bcIn pic.twitter.com/QV1RhDBlpY
1. インストール
yarn add bluespark
# or
npm i -S bluespark
2. 準備
i. 初期化
まず initializeApp() で初期化します。(admin の場合は必要に応じて読み替えてください)
import firebase, { firestore } from 'firebase/app'
import 'firebase/firestore'
import { Blue, Spark } from 'bluespark'
const app = firebase.initializeApp({
apiKey: '### FIREBASE API KEY ###',
authDomain: '### FIREBASE AUTH DOMAIN ###',
projectId: '### CLOUD FIRESTORE PROJECT ID ###',
})
const dbInstance = app.firestore()
ii. モデル定義
Blue.Interface で型を定義しモデルを作成します。
読み取りと書き込みで型が異なる場合は Blue.IO を使います。例えば date フィールドで読み取りが Timestamp、書き込みが Date | FieldValue となる場合以下のようにします。
Blue.Timestamp / Blue.FieldValue などは web と admin の union type です。
ここではルートに users コレクションがあり、各 user が posts サブコレクションを持っている構造とします。
type IUser = Blue.Interface<{ name: string }>
type IPost = Blue.Interface<{
number: number
date: Blue.IO<Blue.Timestamp, Date | Blue.FieldValue>
text: string
tags: string[]
}>
// users
const User = Spark<IUser>()({
root: true,
collection: db => db.collection('users'),
})
// users/{user}/posts
const Post = Spark<IPost>()({
root: false,
collection: user => user.collection('posts'),
})
iii. コレクション定義
次に ii で作ったモデルを使ってコレクションを定義します。
const createCollections = <F extends Blue.Firestore>(db: F) => {
return {
users: User(db),
_postsIn: <D extends Blue.DocRef>(userRef: D) => Post(userRef),
}
}
const db = createCollections(dbInstance)
const dbAdmin = createCollections(dbInstanceAdmin) // admin の場合
3. 使い方 (基本 API)
定義したコレクションとモデルを使ってデータの読み取り/書き込みをしていきます。
i. ドキュメント/クエリを取得する
// `users/userId` を取得
const user = await db.users.getDoc({ doc: 'userId' })
// `users/userId/posts` の中から `number` が 3 を超えるものを取得
const _posts = await db._postsIn(user._ref).getQuery({
q: q => q.where('number', '>', 3),
})
// クエリの取得結果は array と map で取得できます
const firstPost = _posts.array[0]
const postA = _posts.map.get('postId')!
この場合、firstPost と postA の型は以下のようになります。
type _ = {
_createdAt: Blue.Timestamp
_updatedAt: Blue.Timestamp
_id: string
_ref: Blue.DocRef
number: number
date: Blue.Timestamp
text: string
tags: string[]
}
_id にはドキュメントの id、_ref にはドキュメントのリファレンスが入ります。
_createdAt と _updatedAt については後ほど説明します。
decoder を渡すと自動的にデータを変換することができます。
const _posts = await db._postsIn(user._ref).getQuery({
decoder: (post: IPost['_D']) => ({
...post,
number: String(post.number),
}),
})
この場合、各 post の number フィールドは string 型になります。
ii. ドキュメントを作成する
ドキュメントの作成には .create() を使います。
_createdAt と _updatedAt フィールドに ServerTimestamp が自動的にセットされます。
await db._postsIn(user._ref).create('postId', {
number: 17,
date: firestore.FieldValue.serverTimestamp(), // または Date
text: 'text',
tags: ['a', 'b'],
})
iii. ドキュメントを更新する
ドキュメントの更新には .update() を使います。
実行すると _updatedAt フィールドが自動的に更新されます。
await db._postsIn(user._ref).update('postId', {
text: 'new-text',
})
4. 使い方 (React Hooks)
BlueSpark は React Hooks でも簡単に使うことができます。この機能には react-firebase-hooks を使用しています。
const { data: user, loading, error } = useSDoc({
model: db.users,
doc: 'userId',
})
const _posts = await useSCollection({
model: db._postsIn(user._ref),
q: q => q.where('number', '>', 3),
})
const { array, map, query, loading, error } = _posts
読み込み中の場合 loading に true が、エラーの場合 error に Error が入ります。
まとめ
BlueSpark を使うと Firestore がより使いやすくなります。ぜひ試してみてください!!🙌