【2023環境デッキ？】Bun + Hono + SvelteKit + Cloudflare D1 + Drizzle ORM

# WEB開発環境Tier1を組もうぜ？
大学からの友人に「俺は起業をするぞ！多少なら出すからWEBサイト作れん？」と言われたので **あらゆる自由** を条件に（ほぼ）無償で引き受けた案件という名の趣味制作で「ぼくのかんがえた最強のWEBサイト」を目指してみました。~~保守運用の汎用性は犠牲となったのだ~~
今年もアドベントカレンダーに投稿する機会を得たので、環境構築過程とAPI接続ができるまでをご紹介します。（他の方々の記事もぜひ読んでください）

作業メモも兼ねているため中々な長さかつ読みづらいかと思いますが、必要な部分を掻い摘んで読んでいただければ幸いです。

## 自己紹介
- '97年製
- 実務歴: Angular 1年、React 4年

見ての通りフロントエンド単色染め使いなので今回人生で初めてSQLだのDBだの真面目に勉強しました。ORM使ってるけどSQLライクな記法なので勉強したと言わせてください、元々SELECTとWHEREしか知らなかったんだ...。
これから書くものは全て動作確認はしてるのですが、知見不足ゆえ説明等に誤りがあったら申し訳ないです。

## 作るもの
ざっくり言うと「家事代行サービス」のWEBサイトで業務ロジックは「基本情報の表示」「広告」「予約」が出来れば良いというシンプルなもの、オーバースペック気味な環境ではあるものの無償の範囲なら **あらゆる自由が認可されている** ので練習も兼ねてこれを開発します。
本記事では流石に全工程を書ききれないので環境作ってAPI接続するところまでを記載します。

## 目指すもの
- SSR系のフロントエンドフレームワークやってみたい！
- JSなら多少分かるからJS系のバックエンドフレームワークでAPI製造初挑戦したい！
- 可能な限り常に型を保全したい！
- CloudflareのD1が凄いって小耳に挟んだし使ってみたい！
- Bunとかいうのが出たらしいし触ってみたい！
- 新しいツールどしどし触りたい！

## 環境
- [SvelteKit](https://kit.svelte.jp/) + [Vite](https://ja.vitejs.dev/) + [Svelte Material UI](https://sveltematerialui.com/)（フロント）
- [Hono](https://hono.dev/) + [Drizzle ORM](https://orm.drizzle.team/)（バック）
- [Cloudflare D1](https://developers.cloudflare.com/d1/)（DB）
- [Cloudflare Workers](https://developers.cloudflare.com/workers/) + [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/)（インフラ）
- [Bun](https://bun.sh/)（JSランタイム）
- [Zod](https://zod.dev/)（バリデータ）
- [Biome](https://biomejs.dev/ja/)（コード整形、リンター）
- [Lucia](https://lucia-auth.com/)（認証）

やりたい事触ってみたいもの全部載せたらえらいことになってしまいました...
小中学生の頃、ヘブンズゲートとバイオレンスサンダーとアポロヌスドラゲリオンとゲキメツ入れたデッキや、サイバーエンドとレッドアイズアンデッドとアームドドラゴンのデッキを作っていたのを思い出しました。

ちなみに、規模も小さい個人プロジェクトなので`Hono`か`SvelteKit`どちらか+αだけで十分に開発可能です。
ただあえて「バックエンド」というものを切り分けて触ってみたかったので分業っぽい構成にしました。

**なんとHono v3.11、そしてSvelteKit v2.0です**（今回の内容的にあまり変わらないけど）



## 環境構築
まず、今回は`client`と`server`それぞれディレクトリを作成し、バックのコードをgit submoduleなどでフロントに共有していることを想定して作成します。
```
│
├── client/
│   └── Sveltekit
├── server/
│   └── Hono
```

### 設定
#### Bunをインストール
```zsh
curl -fsSL https://bun.sh/install | bash
```
成功すると`PATH`を通すコマンドを教えてくれるので、自身の環境に合わせてCLIのPATHを通します。

ちなみにBunには`outdated`や`audit`がまだないので`npm`も最低限使えるようにしておくとライブラリのバージョンや脆弱性チェックが楽になります。
[Issue](https://github.com/oven-sh/bun/issues/5359)はあるのでいつか対応するかもしれませんね。

#### Biomeをインストール
（フロントとバックそれぞれ構築するので、それぞれでやります）
```zsh
bun add --dev --exact @biomejs/biome
bunx @biomejs/biome init
```
VSCodeユーザーの方はついでに[Biome用の設定](https://biomejs.dev/ja/reference/vscode)もしてしまいましょう。もう公式日本語ドキュメントがあるなんて素晴らしいですね。
Rust製の速さはimport解析くらいでしか体感できませんでしたが、設定一本化のうえ依存ライブラリが激減するので満足しています。
たまにVSCodeがエラーを検知し続けるバグが起きるので気をつけたほうが良いかもしれません。
```settings.json
{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.codeActionsOnSave": {
    "quickfix.biome": true,
    "source.organizeImports.biome": true
  }
}
```
ちなみに、興味があって入れたは良いもののBiomeはまだ[Svelte未対応](https://biomejs.dev/ja/internals/language-support/)なのでお気をつけください。
ReactのHooksとかJSXとかは対応しているのでぜひ利用してみてください。

### フロントエンド
#### SvelteKitでプロジェクトの生成
```zsh
bun create svelte@latest client
```
色々質問されるのでTypeScript使うよーとか適当に答えましょう。
Demo Appが不要ならスケルトンにしておけば開発を邪魔なものなく始められます。
```zsh
cd {{Project Name}}
bun i
bun --bun run dev
```
`--bun`オプションをつけるとCLIの実行を設定に依らず[Bun利用にオーバーライドできる](https://bun.sh/docs/cli/run#bun)そうです。

SveltKitのcreateコマンドで入るライブラリは必ずしも最新ではないです。Stableよりも最新体験を優先するのであれば更新しておきましょう。
```zsh
bun update
```
updateコマンドはメジャーアップデート等の大きな更新は自動でしないので、必要ならlatest版を再addなどしてください。

今回はCloudflareWorkersを利用してホスティングをする事が決定しているのでSveltのアダプタを変えておきましょう。
```zsh
bun remove @sveltejs/adapter-auto
bun add @sveltejs/adapter-cloudflare-workers
```
これは[デプロイ先の環境に合わせて切り替える](https://kit.svelte.jp/docs/adapters)ものですが、一応autoのままでも大丈夫です（多分）。
@sveltejs/adapter-cloudflare推奨というような事が[公式サイトに記載ありますが](https://kit.svelte.jp/docs/adapter-cloudflare-workers)、WorkersにこのアダプタでWrangler経由のデプロイをうまくやる方法がいまいち掴めませんでした。

#### Cloudflare用にアダプタ設定
```svelte.config.js
import adapterCloudflare from '@sveltejs/adapter-cloudflare';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	preprocess: [vitePreprocess()],

	kit: {
		adapter: adapterCloudflare({}),
		alias: {
			$features: 'src/features',
			'$features/*': 'src/features',
			$lib: 'src/lib',
			'$lib/*': 'src/lib/*',
            $server: '../server',
            '$server/*': '../server/*',
		}
	}
};

export default config;
```
ついでにエイリアスの設定を記述して`$lib/~~`などでimportが出来るようにしています。

### tsconfigの設定
名前空間設定のためにライブラリを入れます。
```zsh
bun add bun-types @cloudflare/workers-types
```
```tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "$server": ["../server"],
      "$server/*": ["../server/*"]
    },
    "allowJs": true,
    "checkJs": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "moduleResolution": "bundler",
    "noEmit": true,
    "allowImportingTsExtensions": true,
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "types": ["@cloudflare/workers-types", "bun-types"]
  },
  "extends": "./.svelte-kit/tsconfig.json"
}
```
SvelteKitは`ts-config`に別の独自設定をオーバーライドしているので要チェックです。このオーバーライド用のコンフィグファイルはビルドごとにsvelteコンフィグなどから再生成されます。
そのためextendsのファイルがない場合、一度ビルドすると生成されます。そんなファイルないよとエラーが出る場合お試しください。
```zsh
bun run build
```

### インフラ
#### Cloudflareの設定
[Cloudflare](https://www.cloudflare.com/ja-jp/)にアカウントを作りダッシュボードへ行きます。

`Workers & Pages`のタブを開き、アプリケーションの作成からワーカーを作ります。
今回はフロントとバックにそれぞれ一つずつ作成します。

D1のタブを開いてデータベースを作成します。

#### Wranglerのインストール
WranglerというCloudflare Workersの開発ツールを入れ、Cloudflareと連携させます。
```zsh
bun add wrangler @cloudflare/wrangler @cloudflare/workers-types
bunx wrangler login
```
ちなみに今回は使いませんが`bunx wrangler init -y`でプロジェクトを簡単に構築してくれます。

#### プロジェクトの設定
アカウントIDなどの情報をコピーし、プロジェクトのルートディレクトリに`wrangler.toml`を作成して記述していきます。細かい内容は公式資料などを参考にしてください。

```wrangler.toml
name = {{フロント用Workers名}}
account_id = {{Workers&Pagesで見れるアカウントID}}
compatibility_flags = []
workers_dev = true
compatibility_date = "2023-12-07"
main = "./.cloudflare/worker.js"
site.bucket = "./.cloudflare/public"

services = [
  { binding = {{バック用Workers名}}, service = {{バック用Workers名}}}
]

build.command = "bun run build"
```

同じドメイン間・サブドメイン間のWorkers同士はfetch経由などで通信できないため、`service bindings`という[機能](https://developers.cloudflare.com/workers/configuration/bindings/about-service-bindings/)を利用して通信を行います。
直接Workersが閉じたネットワークで通信するため、速度の向上も見込めます。

再度ビルドをするとデプロイ用の`.cloudflare`ディレクトリが生成されます。
```zsh
bun run build
```

ローカル環境でD1などのテストをする場合は`miniflare`を入れておきましょう。
今回は作業時間の都合、検証環境をCloudflare上に作って触っていたため割愛しました。
```zsh
bun add miniflare
```

### バックエンド
#### Honoのインストール
```zsh
cd ../
bunx create-hono
```

プロジェクト名を`server`、Templateは`cloudflare-workers`にします。
ついでにzodと[Zod用のミドルウェア](https://hono.dev/guides/validation#zod-validator-middleware)も入れておきます。
```zsh
cd server
bun add hono @hono/zod-validator zod
```

#### wranglerの設定
バックエンド側も設定を入れていきます。
DB設定がこちらでは入ります。
```zsh
bunx wrangler login
```
```wrangler.toml
name = {{バック用Workers名}}
account_id = {{Workers&Pagesで見れるアカウントID}}
compatibility_flags = []
workers_dev = true
compatibility_date = {{互換性を持たせる基準日}}

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = {{データベース名}}
database_id = {{データベースID}}
```

#### Drizzle ORMのインストール
Drizzle ORMとそのCLI、DB操作を簡単にしてさらに型安全にする。
```zsh
bun add drizzle-orm drizzle-kit
```

## 接続準備
#### Hono側
型定義を作成します。
ついでにDrizzleの設定とAPIルーティング設定も用意します。
`APIContext`が各APIに渡る情報の型で、`D1Databse`がDB側の型設定になります。
また、drizzleのDB連携も共通化しておきます。
```server/src/index.ts
import { Context, Hono } from "hono";
import { drizzle } from "drizzle-orm/d1";

type Bindings = {
  DB: D1Database;
};

export type APIContext = Context<
  {
    Bindings: Bindings;
  },
  "/",
  {}
>;

export const dbClient = (db: D1Database | undefined) => {
  if (!db) {
    throw new Error("Database not found");
  }
  console.log("dbClient");
  return drizzle(db);
};

const app = new Hono<{ Bindings: Bindings }>();

app
  .onError((err, ctx) => {
    console.error(err);
    return ctx.json({ error: "Internal Server Error" }, 500);
  });

  .get("/", (c) => c.text("Hello Hono!"));

export default app;
```
honoのコードをCloudflareにデプロイします。
```zsh
bun run deploy
```

#### Sveltekit側
[Cloudflareの提供する環境変数](https://kit.svelte.jp/docs/adapter-cloudflare-workers#environment-variables)などにアクセスできるように型を渡す。
ここにどこまで追加が必要なのかは環境によるのでご自身の環境に合わせて設定してください。
`Service Bindings`経由で通信をするため、その設定をenvに入れています。
```client/src/app.d.ts
declare global {
	namespace App {
		// interface Error {}
		// interface PageData {}
		interface Platform {
			context?: {
				waitUntil(promise: Promise<unknown>): void;
				passThroughOnException: () => void;
			};
			env: {
				// YOUR_KV_NAMESPACE: KVNamespace;
				// YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace;
				DB: D1Database;
                {{バック用Workers名}}: Fetcher;
			};
			caches?: CacheStorage & { default: Cache };
		}
	}
}

export {};
```

SveltekitのコードをCloudflareにデプロイします。
多分デフォルトでスクリプトないので作成しましょう。
```package.json
"deploy": "wrangler deploy"
```
```zsh
bun run deploy
```

## DBのテーブル生成
Drizzleでテーブル生成と型定義を兼ねたスキーマを実装します。
Cloudflare D1の中身はsqliteなので[それに沿って](https://orm.drizzle.team/docs/quick-sqlite/d1)書きましょう。
```server/src/DB/schema.ts
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const test = sqliteTable("Test", {
  id: integer("id").primaryKey({ autoIncrement: true }).notNull(),
  name: text("name").notNull(),
});
```

書けたら実際にテーブル生成を行います。
```server/package.json
 "migrate": "drizzle-kit generate:sqlite --out migrations --schema src/DB/schema.ts",
 "migrate:apply": "wrangler d1 migrations apply {{D1のデータベース名}}"
```

まずは`migrate`を行います。そうすると`.wrangler`ディレクトリが作成されます。
そのあと`apply`を行うことで、実際にD1上にテーブルが作成されます。

GETのテスト用にカラムを追加しておきましょう。

## API接続
### バックエンド
APIを実装します。
Drzzleを利用してSQLライクなメソッドを繋いでいきます。
envから`wrangler.toml`で指定したDB情報を取得できます。
Schemaの宣言をそのまま利用することで、カラムの型を受け取る事が可能です。

```server/src/ORM/test.ts
import { eq } from "drizzle-orm";
import { APIContext, dbClient } from "..";
import { test } from "../DB/schema";

export const getAllTest = async (c: APIContext) => {
  const result = await dbClient(c.env.DB).select().from(test).all();
  return c.json(result);
}

export const createTest = async (c: APIContext) => {
  const formData = await c.req.formData();
  const request: typeof test.$inferInsert = {
    name: formData.get("name")?.toString() ?? "",
  };
  
  // Validation
  
  await dbClient(c.env.DB).insert(test).values(request);
  return c.json(result);
};
```

次に作ったAPIをルーティングします。

```server/sec/index.ts
import { Context, Hono } from "hono";
import { drizzle } from "drizzle-orm/d1";
+ import { getAllTest, createTest } from "./ORM/test";

type Bindings = {
  DB: D1Database;
};

export type APIContext = Context<
  {
    Bindings: Bindings;
  },
  "/",
  {}
>;

export const dbClient = (db: D1Database | undefined) => {
  if (!db) {
    throw new Error("Database not found");
  }
  console.log("dbClient");
  return drizzle(db);
};

const app = new Hono<{ Bindings: Bindings }>();

app
  .onError((err, ctx) => {
    console.error(err);
    return ctx.json({ error: "Internal Server Error" }, 500);
  });

- .get("/", (c) => c.text("Hello Hono!"));
+ .get("/", getAllTest)
+ .post("/", createTest)

export default app;
```

ここまでできたら再デプロイをします。
```zsh
bun run deploy
```

### フロントエンド
フロントからAPIを呼び出す部分を作ります。
`+page.server.ts`はサーバー上で処理されるロジックを入れるファイルで、`PageServerLoad`はページの読み込み開始時に動かすコードを記載できます。実動作はともかく感覚はReactの`Suspence`のように事前にやりたい処理を入れる感じです。今回はAPIからデータをFetchさせます。
`PageServerLoad`は`$types`からimportしていますが、これはコードをビルドすると自動で生成されます。そのため一通りコードを書いたら`bun run build`で型生成をしましょう。
また、スキーマからの型定義だけ`server`から引っ張っています。コンパイル前のTypeScript上でのみの利用なのでビルドに入ってなくても動いてます。（今のところ）
```client/src/routes/+page.server.ts
import type { test } from "$server/src/DB/schema";
import type { PageServerLoad } from "./$types";

export const load = (async ({ request, platform }) => {
  const res = await platform?.env.{{バック用Workers名}}.fetch(request);
  const result = await res?.json<(typeof test.$inferSelect)[]>();

  return result;
}) satisfies PageServerLoad;
```

GETしてきた値を利用します。
ここで使う`PageServerData`も上記と同じく自動生成なのでエラーになる場合はビルドをしましょう。
重要なのは`let data`をexportする事です。こうしないとserver側からのパラメータを取得できません。
svlete独特？の[リアクティブ宣言](https://svelte.dev/repl/c5beda0fc3f24044bba525042b386174?version=3.47.0)がかっこいいですね。
```client/src/routes/+page.svelte
<script lang="ts">
  import type { PageServerData } from "./$types";

  export let data: PageServerData;

  $: console.log(data[0]?.id);
</script>

<h1>Welcome to SvelteKit</h1>
<p>
  {data[0]?.id ? data[0]?.id + data[0]?.name : ""}
</p>
```

POSTのためにフォームを追加します。
まずはSMUIから必要なパーツを選びaddします。
面倒ですが1パーツずつ入れていくらしいです。
```zsh
bun add @smui/button @smui/textfield
```

フォームを画面に追加します
```client/src/routes/+page.svelte
<script lang="ts">
  import type { PageServerData } from "./$types";
// ここから追加部分
  import { enhance } from "$app/forms";
  import Button, { Label } from "@smui/button";
  import TextField from "@smui/textfield";
// ここまで追加部分

  export let data: PageServerData;

// ここから追加部分
  let question: string | null = null;
  let answer: string | null = null;
  let email: string | null = null;
  let name: string | null = null;
// ここまで追加部分

  $: console.log(data[0]?.id);
</script>

<h1>Welcome to SvelteKit</h1>
<p>
  {data[0]?.id ? data[0]?.id + data[0]?.name : ""}
</p>

<!-- ここから追加部分 -->
<form
  method="post"
  use:enhance={() => {
    return async () => {};
  }}
>
  <TextField
    type="text"
    style="width: 100%;"
    required
    bind:value={question}
    input$name="question"
  />
  <TextField
    type="text"
    style="width: 100%;"
    required
    bind:value={answer}
    input$name="answer"
  />
  <TextField
    type="text"
    style="width: 100%;"
    required
    bind:value={email}
    input$name="email"
  />
  <TextField
    type="text"
    style="width: 100%;"
    required
    bind:value={name}
    input$name="name"
  />
  <Button style="width: 200px;" variant="raised">
    <Label>送信</Label>
  </Button>
</form>
<!-- ここまで追加部分 -->
```

`form`タグに`method`を追加して`button`タグを`submit`タイプにすると自動的に送信ができます。
JavaScriptが有効な環境で実行する場合はsubmitにより不要な画面遷移が発生しますが、`use:enhance`を利用することで[その処理をカットして別の処理に置き換えることができます](https://kit.svelte.jp/docs/form-actions#progressive-enhancement)。`event.preventDefault()`しなくて良くなるので綺麗で良いですね。
今回は他に処理を用意していないので空白にしています。

続いて通信処理を作成します。
`Form Actions`というSveltKitの機能を利用することで、[formのsbmitと同時にサーバー側で処理を走らせることが可能です](https://kit.svelte.jp/docs/form-actions)。

```client/src/routes/+page.server.ts
import type { test } from "$server/src/DB/schema";
import type { PageServerLoad } from "./$types";

export const load = (async ({ request, platform }) => {
  const res = await platform?.env.{{バック用Workers名}}.fetch(request);
  const result = await res?.json<(typeof test.$inferSelect)[]>();

  return result;
}) satisfies PageServerLoad;

+++
export const actions = {
  default: async ({ platform, request }) => {
    // Validation

    await platform?.env.api.fetch(request.clone());

    return { success: true };
  },
} satisfies Actions;
+++
```

ここまでできたら再デプロイをします。
```zsh
bun run deploy
```

## 画面確認
これでようやく、可能な限り型安全な状態で値を画面に表示できます。
Cloudflare Workersのフロント側サービスからサイトにアクセスしましょう。

![スクリーンショット 2023-12-22 0.43.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2273193/174736c6-325a-1a1f-64fa-418c77ae2710.png)

問題なく実装できていればPOSTすることでD1のテーブルにカラムが追加されるはずです。
サーバーサイド側でのログ出力はWorkersのLogsタブからリアルタイムに確認が可能になっています。

## 終わりに
*疲れた*
なんとなく流行りものを触ろうと思い立ち、どうせならフロントとバック別作業可能な構造にしようと思ったのが苦難の始まりでした...慣れないのに挑戦しすぎたけど、まぁなかなか楽しい執筆期間でした。
これが何かの役に立つと幸いです。

書いている途中にBunもHonoも凄い速度でアプデ入るし、[Sveltekitは2.0になってしまいました](https://kit.svelte.dev/docs/migrating-to-sveltekit-2)。
一応12/17現在最新版を入れて動いているのでご安心ください（？）
活発に開発されている最新環境に食らいつく経験は意外と楽しく、パッケージの中身を気合いで読んだり、分離されたフロント・バック間での型情報の取り回しに悩んだり、そもそも初めてバックエンドに触ったので単純に良い経験を得たなと思います。
出来上がった暁には`vitest`も入れてみたいですね、1.0出たので。

友人のサイトについては、DB設計やワイヤーフレームは出来上がっているので年明けに色々ロジック組んだりCSS入れたりして作成する予定です。
そもそも開業前なので宣伝も何もないのですが、来年あたり「おうちの執事」という名前の家事代行業が浅草あたりで始まっていたら`あぁ、去年のTier1か`とでも思っていただければ幸いです。ご利用も是非。
かっけぇロゴだけ上がっているので掲載しておきます。
![logo1 .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2273193/33ee90e6-8efa-d425-2342-e3033bb6ac75.png)


## 参考サイト
- 各公式サイト
- 各OSSのGitHub
- https://dev.classmethod.jp/articles/getting-started-cloudflare-workers-hono-cloudflare-d1
- https://zenn.dev/ryoppippi/articles/9f975b207b7f64
- https://zenn.dev/kosei28/articles/f4bac1ed2b64a7
- https://zenn.dev/mizchi/articles/d1-drizzle-orm
- https://zenn.dev/babel/articles/eslint-flat-config-for-babel
- https://reffect.co.jp/svelte/sveltekit/
- https://zenn.dev/yusukebe/articles/efa173ab4b9360
- https://zenn.dev/melodyclue/articles/70d84feb3234b8
- https://zenn.dev/moutend/articles/f9409d43724da5
- https://qiita.com/Nogi-SY/items/d423b316093ffd0eab93
- https://qiita.com/TellMin/items/25150684160759c91270
- https://qiita.com/kmkkiii/items/2b22fa53a90bf98158c0
- https://github.com/qwacko/sveltekit-lucia-starter/blob/master/src/lib/server/createUserHandler.ts
- https://github.com/gustavocadev/sveltekit-drizzle-orm-planetscale-lucia/blob/main/src/routes/login/%2Bpage.server.ts
- https://zenn.dev/gawarago/articles/f75f5113a3803d
- https://zenn.dev/ryoppippi/articles/aea8dcbc21c39e
- https://ashutosh.dev/how-to-handle-in-forms-in-svelte/
- https://zenn.dev/k_sato/books/b868c1705b8337

## おまけ
Luciaの認証を時間の都合で最後まで出来ませんでした...
調べた限りのことを載せておきます。

### Luciaのインストール
メールや名前、OAuthなどが利用できる認証ライブラリだそうです。
詳しい事はよくわかりませんが、DBに用意した各テーブル`user_key`がメアドやOAuthとの連携を担い、ユニークな`user_id`とそれに紐づくパスワードを、`user_session`に現在の状態管理を担っているっぽいです。

```zsh
bun add lucia @lucia-auth/adapter-sqlite
```

### 設定ファイルの作成
今回はEMailでログインをする想定で設定をします。
```server/src/auth/lucia.ts
import { d1 } from "@lucia-auth/adapter-sqlite";
import { lucia } from "lucia";
import { hono } from "lucia/middleware";

export const initializeLucia = (db: D1Database) => {
  const auth = lucia({
    adapter: d1(db, {
      user: "user",
      key: "user_key",
      session: "user_session",
    }),
    env: "PROD",
    middleware: hono(),
    getUserAttributes: (data) => {
      return {
        id: data.id,
        email: data.email,
      };
    },
  });
  return auth;
};

export type Auth = ReturnType<typeof initializeLucia>;
```

### テーブルの作成
```server/src/DB/schema.ts
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const test = sqliteTable("Test", {
  id: integer("id").primaryKey({ autoIncrement: true }).notNull(),
  name: text("name").notNull(),
});

+++
export const user = sqliteTable("user", {
  id: text("id").notNull(),
});

export const user_key = sqliteTable("user_key", {
  id: text("id").notNull(),
  hashed_password: text("id").notNull(),
  user_id: text("user_id")
    .references(() => user.id)
    .notNull(),
});

export const user_session = sqliteTable("user_session", {
  id: text("id").notNull(),
  user_id: text("user_id")
    .references(() => user.id)
    .notNull(),
  active_expires: integer("active_expires").notNull(),
  idle_expires: integer("idle_expires").notNull(),
});
+++
```

### 型情報の追加
```client/src/app.d.ts
declare global {
	namespace App {
		// interface Error {}
		// interface PageData {}
		interface Platform {
			context?: {
				waitUntil(promise: Promise<unknown>): void;
				passThroughOnException: () => void;
			};
			env: {
				// YOUR_KV_NAMESPACE: KVNamespace;
				// YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace;
				DB: D1Database;
                {{バック用Workers名}}: Fetcher;
			};
			caches?: CacheStorage & { default: Cache };
+			locals?: {
+    			auth: import("lucia").AuthRequest;
+    			session: import("lucia").Session | null;
+    		};
		}
	}
}

+++
declare global {
	namespace Lucia {
		type Auth = import("$server/src/auth/lucia").Auth;
		type DatabaseUserAttributes = { email: string };
		type DatabaseSessionAttributes = object;
	}
}
+++

export {};
```

### APIの作成
```server/ORM/auth.ts
import { APIContext } from "..";
import { initializeLucia } from "../auth/lucia";

export const login = async (c: APIContext) => {
  try {
    const auth = initializeLucia(c.env.DB);

    const formData = await c.req.formData();
    const email = formData.get("email")?.toString() ?? "";
    const password = formData.get("password")?.toString() ?? "";

    // Validation

    const key = await auth.useKey("email", email, password);

    const session = await auth.createSession({
      userId: key.userId,
      attributes: { createdAt: new Date() },
    });

    const authRequest = auth.handleRequest(c);
    authRequest.setSession(session);

    return c.json(session);
    }
    console.error("err");
  } catch (e) {
    console.error(e);
  }
};

export const logout = async (c: APIContext) => {
  try {
    const auth = initializeLucia(c.env.DB);

    const authRequest = auth.handleRequest(c);
    const session = await authRequest.validate();
    if (!session) {
      return c.text("Unauthorized", 401);
    }
    await auth.invalidateSession(session.sessionId);

    authRequest.setSession(null);
  } catch (e) {
    console.error(e);
    throw e;
  }
};

export const register = async (c: APIContext) => {
  try {
    const auth = initializeLucia(c.env.DB);

    const formData = await c.req.formData();
    const email = formData.get("email")?.toString() ?? "";
    const password = formData.get("password")?.toString() ?? "";

    // Validation

    const res = await auth.createUser({
      key: {
        providerId: "email",
        providerUserId: email,
        password: password,
      },
      attributes: {
        email: email,
      },
    });

    return res;
  } catch (e) {
    console.error(e);
  }
};
```

### 認証チェック&API接続
一応こんな感じで認証を監視できるらしいですが、`initializeLucia`をこんな簡単に持ってこれない（サーバー側のコードだからSvelteのビルドに含まれない）のでfetchなど何か別の方法で通信を管理する必要があります。
ここでタイムアップしました。

```client/src/hooks.server.ts
import { initializeLucia } from "$server/src/auth/lucia";
import type { Handle } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";

const authHandler: Handle = async ({ event, resolve }) => {
	if (event.url.pathname.startsWith("/api/auth")) {
		console.log("auth-hook");
		// biome-ignore lint/style/noNonNullAssertion: <explanation>
		event.locals.auth = initializeLucia(event.platform?.env.DB!).handleRequest(
			event,
		);
		event.locals.session = await event.locals.auth.validate();

		const user = await event.locals.auth.validate();

		if (event.route.id?.startsWith("/(back)") && !user) {
			return Response.redirect(`${event.url.origin}/login`, 302);
		}

		if (event.route.id?.startsWith("/(back)") && user) {
			return Response.redirect(`${event.url.origin}/user`, 302);
		}
		return await resolve(event);
	}

	return await resolve(event);
};

export const handle = sequence(authHandler);

```

終わり次第書き足しておきます。(12/21)

--- 
自分の中で遊戯王はアンデッドワールドくらいで止まっているし、デュエマは5神の無限リンクとかゲート呪文？か何かで出てくる両面カードで止まっているんですが、なぜか無性にまたやりたくなって来ました。
M・HEROとかクロスギアとかすげぇカッコよかったけどまだあるのかなぁ。