【SvelteKit】load関数のsatisfiesってなに？

# はじめに
SvelteKitでページのレンダリング前にDBなどからデータを取得する際には、
ページと同じ階層に ```+page(.server).ts``` ファイルを作成します。
このファイルは、データ取得処理を行う ```load``` 関数をモジュールとして export するのですが、TypeScript を使用する場合、この関数の戻り値の型を定義するために ```satisfies PageLoad(PageServerLoad)``` を使用します。
この ```satisfies``` ってなに？どういう動きをするのか？という点を解説したいと思います。

# 公式ドキュメントのSampleの確認
公式ドキュメントによるデータ取得時のSampleを見てみましょう。
[公式ドキュメント](https://kit.svelte.jp/docs/load#page-data)には次のように記載があります。
```load``` 関数は、DBからデータを取得し、{ post: data }というオブジェクトを返す動きになっています。


```ts:+page.server.ts
import * as db from '$lib/server/database';
import type { PageServerLoad } from './$types';
 
export const load = (async ({ params }) => {
  return {
    post: await db.getPost(params.slug)
  };
}) satisfies PageServerLoad;
```

画面側では、```load```関数で取得した結果を元にデータを取得しています。
このコードから、dbから「title」と「content」の二つの項目を取得する想定であることがわかりますね。

```html:+page.svelte
<script lang="ts">
  import type { PageData } from './$types';

  export let data: PageData;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
```

# satisfies とは
```satisfies``` は [TypeScript 4.9](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html) にて新しく追加された演算子です。
次のような複数のkeyがあり、それぞれのvalueの型が異なるオブジェクトに指定した型にマッチするかどうかをチェック(型推論)することができます。
これによって、MyBook.title が string として推論されるため、後続の処理において文字列型として扱うことができます。

```typescript
type Book = {
  title: string;
  page: number;
};

const MyBook = {
  title: 'book',
  page: 100,
} satisfies Book;
```

詳細は[公式ドキュメント](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html)を確認するか、  
次の記事が理解しやすかったためおすすめです。

https://zenn.dev/moneyforward/articles/typescript-as-const-satisfies

# SvelteKitにおけるsatisfies

```satisfies``` の動きを理解したところで、SvelteKitでは ```satisfies``` をどう利用しているのかについて見ていきたいと思います。
データ取得処理を行う ```load``` 関数の戻り値の型は ```PageLoad(PageServerLoad)``` と定義されています。
この ```PageLoad(PageServerLoad)``` は、自分で定義するのではなくビルドを行うことでSvelteKitが自動的に型定義を作成してくれます。
（※手元で.svelte-kitディレクトリを削除した後、ビルドを行うとフォルダが再度作成されることを確認できます）
コード上では、```import type { PageServerLoad } from './$types';``` とインポートを行いますが、実際に型を定義している箇所は ```.svelte-kit/types/src/routes/$types.d.ts```になります。
（※$types.d.tsファイルはsrc/routes配下に各アプリの階層に合わせたページごとに作成されます）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/558e6268-af4e-bc3e-7942-8f6c6116ae2b.png)

どのように型定義しているかは、少し読みにくいのですが、load 関数の返り値の型や他の情報を元に型を定義しているように読み取れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/de8a5764-317d-2f20-e30c-760bc3d535b7.png)

```typescript:.svelte-kit/types/src/routes/$types.d.ts
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.AwaitedProperties<Awaited<ReturnType<typeof import('../../../../src/routes/+page.server.js').load>>>>>>;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/0c6aec99-5a83-74e2-e67a-9d68170378a0.png)


また、画面側では先ほど定義したload関数の返り値（サーバから取得した値）は data 変数に格納します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/2ca8d961-4923-95e7-a150-11f2edaff9cd.png)


この data 変数の型は PageData 型になるのですが、これも先ほどの ```.svelte-kit/types/src/routes/$types.d.ts``` ファイルにて定義されています。
どのように定義されているかを見ると、先ほど定義されていたPageServerData型を元に定義されているみたいですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/5fa772d1-6a68-2008-6a9b-d7c00640e748.png)

これによって、ユーザはビルドを行うことによってサーバから取得した値の型をあまり気にせずに、画面の実装時に型推論の恩恵を受けれるということですね！
(↓data.post.titleの値がstringであるという型推論の恩恵を受けている図）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/6d180205-e1a1-3517-6137-b7fb4b326234.png)

# まとめ

最初は satisfies ってなんだという動機から調べ始めましたが、最終的に SvelteKit の型定義の仕方ってどうやっているのという話に着地してしまいました。
しかし、なんとなく受けていた恩恵がどういう風な仕組みで型推論されているかということを知れて一歩理解が深まった気がして良かったです。
ユーザは return の型をどう返すかしっかり書いてビルドすれば、自動的に画面側で型推論できるなんて、SvelteKit便利！

# 余談：今回の調査で躓いた点

### モジュール '$lib/server/database' またはそれに対応する型宣言が見つかりません。

Svelte の CLI を使用してプロジェクトを作成し、+page.server.ts ファイルを作成した際に上記のエラーメッセージが表示されました。
SvelteKit は src/lib フォルダ配下にあるモジュールを $lib エイリアスを使うことで呼び出し側の import 文においてパスを「../../../」のように書かなくて済みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/9a51276f-370c-18e6-778a-0e12a1ad8112.png)

これが発生した原因は、CLI でプロジェクトを作成した際に Skelton project temlate を使って作成したためでした。
Skelton projectで作成した場合、```.svelte-kit/tsconfig.json``` ファイルのエイリアスの設定が不足しているためこのエラーメッセージが表示されていました。
これを修正するにはプロジェクトのフォルダ構成に変更を加えて再度ビルド（```npm run build```）する必要があります。

具体的には ```src/lib``` フォルダを追加しビルドをすることで ```.svelte-kit/tsconfig.json``` ファイルに $lib エイリアスが追加され、コード上でエラーが表示されなくなります。
.svelte-kit/tsconfig.json のデフォルト設定については[公式ドキュメント](https://kit.svelte.jp/docs/types#generated-types-default-tsconfig-json)を参照してください。

```javascript:.svelte-kit/tsconfig.json
{
	"compilerOptions": {
        // before(init) -------------------------
    	"paths": {},

        // after(add lib folder) ----------------
        "paths": {
			"$lib": [
				"../src/lib"
			],
			"$lib/*": [
				"../src/lib/*"
			]
		},
		//...以下省略
}
```