動機
zodでFirestoreに格納するオブジェクトのスキーマを作るとき、Timestamp型のプロパティに指定するためのスキーマ定義が欲しい。
import { z } from "zod";
/** ユーザー情報スキーマ */
const userSchema = z.object({
/** ユーザー名 */
name: z.string().max(100).default(""),
/** 最終ログイン日時 */
lastLoginedAt: // 【ここに指定するスキーマは?】
});
スキーマ定義
import { z } from "zod";
import { FieldValue, Timestamp } from "firebase/firestore";
/** firestoreのFieldValueオブジェクトを受け付けるスキーマ */
export const firestoreFieldValueSchema = z.custom<FieldValue>(
(data) => data instanceof FieldValue
);
/** firestoreのTimestampオブジェクトを受け付けるスキーマ(FieldValueは受け付けない) */
export const firestoreTimestampSchema = z.instanceof(Timestamp);
/** firestoreのTimestampオブジェクトまたはFieldValueオブジェクトを受け付けるスキーマ */
export const firestoreTimestampLooseSchema = z.union([
firestoreFieldValueSchema,
firestoreTimestampSchema,
]);
※Firebase v9以降を前提としています(v8以前はインターフェースが大きく異なるため、上記のソースはそのまま使用できないと思います)
使い方
厳密さを求めない場合は、下記のようにfirestoreTimestampLooseSchemaのみを使用すればOKです。
firestoreTimestampLooseSchemaは、Firestoreへデータを格納する時に設定する値(serverTimestamp() で生成した値)と、Firestoreからデータを取得したときに入っている値(Timestamp)の両方を受け付けてくれます。
import { z } from "zod";
import { firestoreTimestampLooseSchema } from "firebaseSchema";
/** ユーザー情報スキーマ */
const userSchema = z.object({
/** ユーザー名 */
name: z.string().max(100).default(""),
/** 最終ログイン日時 */
lastLoginedAt: firestoreTimestampLooseSchema,
});
/** ユーザー情報の型 */
export type User = z.infer<typeof userSchema>;
// => {
// name: string;
// lastLoginedAt: FieldValue | Timestamp;
// }
ただし上記のような定義だと、Firestoreから取得したデータの場合にも、lastLoginedAtフィールドの型がFieldValue | Timestamp
となります。(この場合、FieldValueが入っている可能性はないはずです)
より厳密さを求めるなら、下記のように「未保存のデータ」と「保存済のデータ(Firestoreから取得したデータ)」を別々のスキーマとして定義してください。
import { z } from "zod";
import { firestoreTimestampLooseSchema, firestoreTimestampSchema } from "firebaseSchema";
/** ユーザー情報スキーマの共通プロパティ */
const baseSchema = z.object({
/** ユーザー名 */
name: z.string().max(100).default(""),
});
/** ユーザー情報スキーマ(保存済) Firestoreから取得したデータをパースする場合はこちらを使う */
export const storedUserSchema = baseSchema.extend({
/** 最終ログイン日時 */
lastLoginedAt: firestoreTimestampSchema,
});
/** ユーザー情報スキーマ(未保存) Firestoreへ格納するためのオブジェクトを生成する場合はこちらを使う */
export const unstoredUserSchema = baseSchema.extend({
/** 最終ログイン日時 */
lastLoginedAt: firestoreTimestampLooseSchema,
});
/** ユーザー情報の型 */
export type StoredUser = z.infer<typeof storedUserSchema>;
// => {
// name: string;
// lastLoginedAt: Timestamp;
// }
export type UnstoredUser = z.infer<typeof unstoredUserSchema>;
// => {
// name: string;
// lastLoginedAt: FieldValue | Timestamp;
// }
おまけ:Firebase v8以前向けのスキーマ定義(未検証)
注:ビルドエラーが出ないことは確認しましたが、動作は検証していないため、動くかどうかは分かりません。
import firebase from "firebase";
import { z } from "zod";
/** firestoreのFieldValueオブジェクトを受け付けるスキーマ */
export const firestoreFieldValueSchema =
z.custom<firebase.firestore.FieldValue>(
(data) => data instanceof firebase.firestore.FieldValue
);
/** firestoreのTimestampオブジェクトを受け付けるスキーマ(FieldValueは受け付けない) */
export const firestoreTimestampSchema = z.instanceof(
firebase.firestore.Timestamp
);
/** firestoreのTimestampオブジェクトまたはFieldValueオブジェクトを受け付けるスキーマ */
export const firestoreTimestampLooseSchema = z.union([
firestoreFieldValueSchema,
firestoreTimestampSchema,
]);
参考資料
FieldValueを受け付けるスキーマの定義については、GitHub内の下記Issueを参考にしています。