Firestore algolia extension (Firestore search with Algolia) の Transform Functions の書き方

## Algolia extension
Firebase の Cloud Firestore と Algolia を連携させる Extension がある。
Firebase 側と Algolia 側で名称が異なるので正解がよくわからないが、ここでは Algolia extension と呼ぶことにする
- [Search Firestore with Algolia](https://extensions.dev/extensions/algolia/firestore-algolia-search) (Firebase)
- [Firestore search with Algolia](https://www.algolia.com/developers/firebase-search-extension/) (Algolia)

Algolia extension を使うと Firestore と Algolia のインデックスが自動的に同期される。
この Extension の使い方は、手順に従って設定するだけであり、いろいろな記事もあるのでここでは触れない。

## Transform Function
Extension には、データを同期させる際、なんらかの加工が必要な場合に備えて、Firebase Function の同期フック[^1]がある。
Extension の設定で、Firebase Function の名前を設定すればその関数が呼び出されるという仕組みである。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635639/357cfd18-ae89-8ec6-2060-97c5b64f3de0.png)
しかしながら、この Firebase Function のサンプルが見つからなかったので、メモとしてこの記事を残しておく。

## Firebase Function (Node.js)
今回は、Node.js で Firebase Function を作成した。
例えば、以下のような仕様の Transfer Function を作るとする。

- draft プロパティがあったら、インデックスから除外
- title と subTitle プロパティを文字列結合して titleとしてインデックスに登録する

ソースは以下のようになる。

```js
const { onCall, HttpsError } = require("firebase-functions/v2/https");

exports.transformForAlgolia = onCall((payload) => {

  const data = payload.data;
  if (data.draft)
    throw new HttpsError("invalid-argument", "draft");

  return {
    title: `${data.title}: ${data.subTitle}`, 
    description: data.description,
    objectID: data.objectID,
  };
});
```

### onCall
Firebase アプリ内から関数を呼び出すときは、 onCall[^2] を利用する。
基本的にはHTTP経由で呼び出す onRequest と変わらないが、引数は一つのみである。
### 引数
引数で引き渡される情報を示すドキュメントは探せなかった。
ソース[^3]を見る限り、 firestore の DocumentSnapshot[^4] が引数となっているようなので、`.data`でdocumentを取得できる。
### index 除外
動的に index を除外する方法だが、色々調べたが正解はわからなかった。（多分、正攻法では実現できない）
今回は、除外したいドキュメントに対しては、`HttpsError`を throw するという方法にした。問題なく動作しているが、難点は、functions 側のログに警告が残ること。この機能に関してはニーズは高いと思うので、今後の機能追加を期待したい。
### 戻り値
戻り値はシンプルにプレーンなJSONで返せばよい。Algoriaのインデックスとして`objectID`だけは必要なので忘れずに追加する必要がある。基本的には引数の段階でfirestore のドキュメントIDが objectID として設定されているので、そのまま戻せばよい。
### リージョンに注意
これだけで基本的には動くはずである。
一つ気を付けたいのが Firestore Functions のリージョンと extension のリージョンが一致していないと、動作しないということである。
Extension のリージョンは Extension の設定の "Cloud Functions location"で指定される。これは最初にExtensionをインストールした時点の設定を変えられない。
このリージョンと Functions のリージョンが一致していないときは、Function をデプロイするときに下記のように指定すればよい。[^5]

```js
exports.transformForAlgolia = onCall(
    { region: 'asia-northeast1'},
    (payload) => { ...}
);
```

[^1]: [Firebase extensions - 拡張機能にユーザーフックを追加する - 同期フック](https://firebase.google.com/docs/extensions/publishers/user-hooks?hl=ja#sync-hooks)
[^2]: [Cloud Functions API reference - onCall](https://firebase.google.com/docs/reference/functions/2nd-gen/node/firebase-functions.https.md?hl=ja#httpsoncall)
[^3]: [GitHub algolia/firestore-algolia-search](https://github.com/algolia/firestore-algolia-search/blob/34592d513eac22691d76917874a6466032976f67/functions/src/transform.ts)
[^4]: [Firebase Node.js API reference -  firebase.firestore.DocumentSnapshot](https://firebase.google.com/docs/reference/node/firebase.firestore.DocumentSnapshot)
[^5]: [Firebase Cloud Functions API reference - https.HttpsOptions.region](https://firebase.google.com/docs/reference/functions/2nd-gen/node/firebase-functions.https.httpsoptions.md?hl=ja#httpshttpsoptionsregion)