TypesaurusでFirestoreを型安全に使う

## はじめに
Firestoreを使ってアプリケーションを開発する際、データの型安全性を保つことが難しいことがあります。FirestoreはNoSQLデータベースなので、柔軟性が高い反面、型の保証がなく、データの整合性を保つために多くの手動チェックが必要です。そこで満を持して登場したのが[Typesaurus](https://typesaurus.com/)です。Typesaurusは、Firestoreを型安全に使用できるようにするライブラリです。

## Typesaurusとは
Typesaurusは、Firestoreの型安全な操作をサポートするTypeScriptライブラリです。このライブラリを使うことで、Firestoreに保存されるデータの型を定義し、その型に基づいてデータを操作することができます。これにより、データの整合性を保ちやすくなり、バグの発生を減らすことができます。

https://typesaurus.com/

## Typesaurusの特徴
- 型安全性: TypeScriptの強力な型システムを活用し、Firestoreデータベースの操作を型安全に行うことができます。
- 簡単な導入: Typesaurusの導入は簡単で、既存のプロジェクトに迅速に統合できます。
- 使いやすいAPI: 直感的で使いやすいAPIを提供しており、開発者がすぐに使い始めることができます。

## 導入方法
Typesaurusをプロジェクトに導入するには、まずnpmを使用してパッケージをインストールします。

```bash
npm install typesaurus
```
次に、Firestoreプロジェクトの設定を行い、Typesaurusを使用する準備を整えます。

```ts
import { collection, add, get } from 'typesaurus'
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'

// Firebaseプロジェクトの初期化
const firebaseApp = initializeApp({
  apiKey: 'your-api-key',
  authDomain: 'your-auth-domain',
  projectId: 'your-project-id'
})

// Firestoreのインスタンスを取得
const firestore = getFirestore(firebaseApp)
```

## 使い方の基本
Typesaurusを使って基本的なCRUD操作を行う方法を紹介します。

### スキーマの作成

```ts
import { schema, Typesaurus } from "typesaurus";

// データベース定義
export const db = schema(($) => ({
  users: $.collection<User>().sub({
    notes: $.collection<Note>(),
  }),
  orders: $.collection<Order>(),
  books: $.collection<Book>(),
}));

export type Schema = Typesaurus.Schema<typeof db>;

interface User {
  name: string;
}

interface Note {
  text: string;
}

interface Order {
  userId: Schema["users"]["Id"];
  bookId: Schema["books"]["Id"];
}

interface Book {
  title: string;
}
```

### データの取得
データ取得も非常にシンプルです。クエリによるフィールド名も型チェックが働きます。  
コレクションの階層で取得する場合も補完によって階層をたどることができるので、実装効率が格段に上がります。
```ts
// スキーマのimport
import { db } from "./db";

// IDもコレクション毎に型が定められています
const userId = db.users.id("XXXXXXXXXXXXXXX");

// ドキュメントIDを指定して取得
const user = await db.users.get(userId);

// usersドキュメント配下のnotesコレクションをID指定で取得
await db.users(userId).notes.get(noteId);

// usersコレクション内のドキュメントをすべて取得
await db.users.all();

// usersコレクション内のドキュメントを検索（ソート）して取得
await db.users.query(($) => [
    $.field("name").eq("Sasha"),
    $.field("name").order("asc")
]);
```

#### リアルタイムSubscriptionによる更新イベント
クライアント側でも同じスキーマによってSubscriptionによるSnapshotの取得ができます。
```ts
db.users
  .query(($) => $.field("name").eq("Sasha"))
  .on((users) => {
    users;
    //=> [{ data: { name: "Sasha" }, ... }, { data: { name: "Sasha" }, ... }]
  })
  .catch((error) => {
    error;
    //=> Ruleなどのエラー
  });
```

### データの追加・更新・削除
データの追加・更新でも各フィールドの型安全が保証されます。
```ts
import { db } from "./db";

// ドキュメントを追加してrefを取得
// 追加後のID取得も ref.id で可能
const ref = await db.users.add({ name: "Alexander" });

// ドキュメントの更新
await db.users.set(userId, { name: "Sasha" });

// refからも更新できます
await ref.set({ name: "Sasha" });

// usersドキュメント配下のnotesコレクションにドキュメント追加
await db.users(ref.id).notes.add({ text: "Hello" });

// ドキュメントの更新
await ref.update({ name: "Sasha" });

// Upsetも可能です
await db.users.upset(userId, { name: "Sasha" });

// ドキュメントの削除
await ref.remove();
```

## その他いろいろ
前述の基本機能の他にもFirestoreの機能は一通りAPIとして提供されています。

### トランザクション
```ts
import { transaction } from "typesaurus";

transaction(db)
  .read(($) => $.db.users.get(userId))
  .write(($) => {
    const user = $.result;
    if (!user) return;
    user.update({ rating: user.data.rating + 1 });
  });
```

### コレクショングループ
```ts
const goodPosts = await groups(db).posts.query(($) => $.field("rating").gt(3));
```

## まとめ
Typesaurusを使うことで、Firestoreを型安全に操作することができ、データの整合性を保ちつつ開発を進めることができます。型安全な環境は、コードの品質を高め、バグを減らすことに貢献します。Typesaurusを導入して、より安全で効率的なFirestoreの利用を始めましょう。

## 参考
https://typesaurus.com/

https://firebase.google.com/docs/firestore?hl=ja