diff --git "a/docs/code/deploy/Vercel\343\201\247\343\203\207\343\203\227\343\203\255\343\202\244\343\201\231\343\202\213\346\211\213\351\240\206.md" "b/docs/code/deploy/Vercel\343\201\247\343\203\207\343\203\227\343\203\255\343\202\244\343\201\231\343\202\213\346\211\213\351\240\206.md" index e9e105b..61956a0 100644 --- "a/docs/code/deploy/Vercel\343\201\247\343\203\207\343\203\227\343\203\255\343\202\244\343\201\231\343\202\213\346\211\213\351\240\206.md" +++ "b/docs/code/deploy/Vercel\343\201\247\343\203\207\343\203\227\343\203\255\343\202\244\343\201\231\343\202\213\346\211\213\351\240\206.md" @@ -1,16 +1,78 @@ # Deployする手順 -## PartyKit編 +## PartyKit編(Cloudflare Workers) -手順:PartyKitのディレクトリで`npx partykit deploy`を実行するだけ +このプロジェクトでは、Cloudflare版PartyKit(`partyserver`パッケージ)を使用しています。 +Cloudflare Workersへのデプロイには**Wrangler CLI**を使用します。 -初めてデプロイしているのであればGitHubでのログインが求められる。\ -デプロイしたサーバーのURLは以下のフォーマットに従う\ -[プロジェクトの名前].[GitHubのユーザ名].partykit.dev\ - ^ partykit.jsonで設定できる +### 前提条件 -`npx partykit list`でデプロイされているプロジェクトの一覧が確認できる\ -[PartyKit CLIのコマンド一覧](https://docs.partykit.io/reference/partykit-cli/) +- Cloudflareアカウントが作成済みであること +- `npx wrangler login` で認証済みであること + +### 1. 環境変数の設定 + +**方法1: wrangler.toml で設定(推奨)** + +`partykit/wrangler.toml` のコメントアウトを解除: + +```toml +# 環境変数 +[vars] +API_BASE_URL = "https://for-moku.vercel.app" # 本番のVercel URLに変更 +``` + +**方法2: Cloudflare Dashboard で設定** + +1. [Cloudflare Dashboard](https://dash.cloudflare.com/) にログイン +2. Workers & Pages → `for-moku-partykit` を選択 +3. Settings → Variables → Environment Variables +4. **Add variable** をクリック +5. Variable name: `API_BASE_URL` +6. Value: `https://for-moku.vercel.app` +7. **Save** をクリック + +### 2. デプロイ + +PartyKitのディレクトリで以下のコマンドを実行: +```bash +cd partykit +npx wrangler deploy +``` + +初めてデプロイする場合: +1. Cloudflareアカウントでのログインが求められる: `npx wrangler login` +2. ブラウザで認証を完了 +3. 再度 `npx wrangler deploy` を実行 + +### 3. デプロイ先URL + +デプロイしたサーバーのURLは以下のフォーマットに従う: +``` +https://for-moku-partykit..workers.dev +``` + +デプロイ成功時にターミナルに表示されます。 + +### 4. 動作確認 + +デプロイされたURLをテスト: +```bash +curl https://for-moku-partykit..workers.dev/parties/for-moku-server/test +# 期待結果: [] +``` + +### 便利なコマンド + +- **デプロイ済み環境の確認**: `npx wrangler deployments list` +- **ログのリアルタイム確認**: `npx wrangler tail` +- **認証状態の確認**: `npx wrangler whoami` +- **ログアウト**: `npx wrangler logout` + +参考: +- [Wrangler CLI ドキュメント](https://developers.cloudflare.com/workers/wrangler/) +- [Cloudflare Workers 環境変数](https://developers.cloudflare.com/workers/configuration/environment-variables/) +- [移行ガイド](../websocket/cloudflare-partykit-migration.md) ## Google OAuth2編 - [Google Cloud Console](https://console.cloud.google.com/)で新たなプロジェクトを作成 @@ -34,12 +96,24 @@ npx drizzle-kit migrate デプロイするとおそらくeslintがいろんなエラー(主にunused var)を出しますが、\ next.config.tsに`eslint: { ignoreDuringBuilds: true }`を追加することで無視できる -環境変数 -- AUTH_SECRET -> `openssl rand -base64 33`で生成できる -- AUTH_GOOGLE_ID -> Google OAuthのクライアント ID -- AUTH_GOOGLE_SECRET -> Google OAuthのクライントシークレット -- NEXT_PUBLIC_PARTYKIT_HOST -> デプロイしたPartyKitサーバーのURL -- DATABASE_URL -> その名の通り +### 環境変数 + +- **AUTH_SECRET** → `openssl rand -base64 33`で生成できる +- **AUTH_GOOGLE_ID** → Google OAuthのクライアント ID +- **AUTH_GOOGLE_SECRET** → Google OAuthのクライアントシークレット +- **NEXT_PUBLIC_PARTYKIT_HOST** → デプロイしたCloudflare WorkersのURL(例: `for-moku-partykit..workers.dev`) + - ⚠️ **重要**: プロトコル(`https://`)は不要、ホスト名のみ指定 +- **DATABASE_URL** → NeonデータベースのURL + +### NEXT_PUBLIC_PARTYKIT_HOST の設定手順 + +1. PartyKitを `npx wrangler deploy` でデプロイ +2. デプロイ成功時に表示されるURL(`https://for-moku-partykit.XXX.workers.dev`)をコピー +3. Vercel Dashboard → Settings → Environment Variables +4. `NEXT_PUBLIC_PARTYKIT_HOST` に **`for-moku-partykit.XXX.workers.dev`** を設定 + - ⚠️ `https://` を含めない +5. **Production**, **Preview**, **Development** 全ての環境に設定 +6. Redeploy 環境変数は編集しても、再デプロイするまで反映されないので注意! diff --git a/docs/code/websocket/cloudflare-partykit-migration.md b/docs/code/websocket/cloudflare-partykit-migration.md new file mode 100644 index 0000000..8cfdd55 --- /dev/null +++ b/docs/code/websocket/cloudflare-partykit-migration.md @@ -0,0 +1,831 @@ +# Cloudflare版PartyKitへの移行ガイド + +## 背景 + +2025年10月以降、元の `partykit/partykit` リポジトリでデプロイが504エラーで失敗する問題が発生しています。 +PartyKitチームは修正が難しいと表明しており、実質的にメンテナンスが停止している状態です。 + +**参考**: [GitHub Issue #971](https://github.com/partykit/partykit/issues/971) + +Cloudflareが公式にフォークした **`cloudflare/partykit`** が新しく積極的にメンテナンスされているリポジトリとして推奨されています。 + +## 移行の理由 + +1. **元のPartyKitはデプロイ不可**: `api.partykit.io` が解決できず、マネージドプラットフォームへのデプロイが不可能 +2. **積極的なメンテナンス**: Cloudflare版は活発に開発されている +3. **インフラの利点**: Cloudflare Workersの可視性、ログ、パフォーマンス最適化などの恩恵 +4. **将来性**: Cloudflareのエコシステムとの統合 + +## 現在の構成 + +### パッケージバージョン +- **PartyKitサーバー**: `partykit@0.0.115` (partykit/package.json) +- **フロントエンド**: `partysocket@1.1.4` (front/package.json) + +### 使用箇所 +- `partykit/src/server.ts`: WebSocketサーバー実装 +- `front/src/components/templates/room/ActiveEventTemplate.tsx`: `usePartySocket` フック +- `front/src/components/organisms/room/DraggableIcon.tsx`: `PartySocket` インスタンス +- `front/src/components/organisms/room/EditProfileDialog.tsx`: `PartySocket` インスタンス + +### 主な機能 +- イベント参加者のリアルタイム座席管理 +- ユーザーアイコンの位置情報同期 +- イベント終了時の自動クローズ (Alarm API使用) +- ストレージを使った状態永続化 + +## 前提条件 + +### Cloudflareアカウントの準備 + +Cloudflare Workersを使用するため、事前にCloudflareアカウントが必要です。 + +1. **Cloudflareアカウント作成** + - [Cloudflare](https://dash.cloudflare.com/sign-up) にアクセス + - メールアドレスとパスワードで無料アカウントを作成 + - メール認証を完了 + +2. **Workersプランの確認** + - 無料プラン(Free): 1日あたり100,000リクエスト + - 有料プラン(Workers Paid): $5/月〜、追加リクエスト可能 + - [料金プラン詳細](https://developers.cloudflare.com/workers/platform/pricing/) + +3. **API Tokenの準備(任意)** + - ダッシュボード → My Profile → API Tokens + - デプロイ時に `wrangler login` で自動的に認証可能 + - CI/CDで使用する場合はAPI Tokenを発行 + +### 必要なツール + +- **Node.js**: v18以上推奨 +- **npm**: v9以上推奨 +- **Wrangler CLI**: Cloudflare Workers用デプロイツール(自動インストールされる) + +## 移行手順 + +### 1. Cloudflare版PartyKitのインストール + +**重要**: Cloudflare版PartyKitは、元のPartyKitとは**パッケージ名が異なります**。 + +#### サーバー側 (partykit/) +```bash +cd partykit +npm uninstall partykit +npm install partyserver +``` + +#### フロントエンド側 (front/) +```bash +cd front +npm install partysocket # バージョンは互換性があるため変更不要 +``` + +### 2. サーバーコードの修正 + +`partykit/src/server.ts` のインポート文と基底クラスを変更: + +**変更前:** +```typescript +import type * as Party from "partykit/server"; +import Server from "partykit/server"; + +export default class ForMokuServer extends Server { + // ... +} +``` + +**変更後:** +```typescript +import type * as Party from "partyserver"; +import { Server } from "partyserver"; + +export default class ForMokuServer extends Server { + // 型パラメータで環境変数の型を指定 + declare env: Env; // DurableObjectのenv propertyを使用するための宣言 + declare ctx: DurableObjectState; // storageアクセス用 + + // ... +} +``` + +**重要な変更点**: +1. `partyserver`からの`Server`インポートは名前付きエクスポート(`{}`が必要) +2. `Server`で型パラメータを指定(環境変数の型安全性向上) +3. `this.ctx`(DurableObjectState)と`this.env`(環境変数)は`declare`で宣言が必要 + - これらはDurableObjectの親クラスが提供するプロパティだが、TypeScript型定義には含まれていない + +### 3. Entry Point(index.ts)の作成 + +Cloudflare Workers/Wranglerでは、明示的なエントリーポイントファイルが必要です。 + +`partykit/src/index.ts` を新規作成: + +```typescript +import { routePartykitRequest } from "partyserver"; +import ForMokuServer from "./server"; + +// 環境変数とDurable Objectバインディングの型定義 +type Env = { + ForMokuServer: any; // DurableObjectNamespace + API_BASE_URL?: string; +}; + +// Durable Objectクラスをexport(バインディング用) +export { ForMokuServer }; + +// Cloudflare Workers fetch handler +export default { + async fetch(request: Request, env: Env): Promise { + // PartyServerのルーティング処理 + // /parties/:party/:room 形式のURLを処理 + const response = await routePartykitRequest(request, env); + + if (response) { + return response; + } + + // ルーティングにマッチしなかった場合は404 + return new Response("Not Found", { status: 404 }); + }, +}; +``` + +**ポイント**: +- `routePartykitRequest`: PartyServerのURLルーティング処理 +- `/parties/:party/:room` の`:party`はDurable Object binding名(kebab-case変換される) +- `ForMokuServer`バインディング → `/parties/for-moku-server/:room` + +### 4. 設定ファイルの更新 + +### 4. 設定ファイルの更新 + +#### partykit/package.json +```json +{ + "scripts": { + "dev": "wrangler dev src/index.ts --local --ip 0.0.0.0 --port 1999", + "deploy": "wrangler deploy" + }, + "dependencies": { + "partyserver": "^0.0.75", + "date-fns-tz": "^3.2.0" + }, + "devDependencies": { + "wrangler": "^3.94.0" + } +} +``` + +**注意**: +- `partyserver` が最新バージョンです(2025年11月時点で v0.0.75) +- `dev`スクリプトは`wrangler dev`を使用(`partykit dev`ではない) +- エントリーポイントは`src/index.ts` + +#### partykit/wrangler.toml を作成 + +Cloudflare Workers設定ファイル(**新規作成**): + +```toml +name = "for-moku-partykit" +main = "src/index.ts" +compatibility_date = "2025-11-01" +compatibility_flags = [] + +# Durable Objects設定 +[[durable_objects.bindings]] +name = "ForMokuServer" # バインディング名(URL: /parties/for-moku-server/...) +class_name = "ForMokuServer" # src/server.tsでexportしているクラス名 + +# マイグレーション設定(初回デプロイ時) +[[migrations]] +tag = "v1" +new_classes = ["ForMokuServer"] + +# 環境変数(オプション) +[vars] +# API_BASE_URL = "https://for-moku.vercel.app" # 本番用 +``` + +**重要なポイント**: +1. `main = "src/index.ts"` - エントリーポイントを指定 +2. `name` - Durable Objectのバインディング名は**kebab-caseに変換**される + - `ForMokuServer` → URLの`for-moku-server` +3. `class_name` - `src/server.ts`の`export default class ForMokuServer`と一致させる +4. `migrations` - 初回デプロイ時に新しいDurable Objectクラスを登録 + +#### partykit/partykit.json(削除可能) + +**重要**: `wrangler.toml`を使用する場合、`partykit.json`は不要です。 +Cloudflare Workers環境では`wrangler.toml`が優先されます。 + +削除しても問題ありませんが、残しておいても害はありません。 + +### 5. フロントエンドコードの確認・修正 + +フロントエンド側の `partysocket` ライブラリは互換性がありますが、**party名の指定**が必要です。 + +#### 重要: Party名の指定 + +PartyServerのルーティングは `/parties/:party/:room` 形式です。 +`:party`はDurable Object binding名(kebab-case)と一致させる必要があります。 + +**修正が必要なファイル**: + +1. **`front/src/components/templates/room/ActiveEventTemplate.tsx`**: +```typescript +const socket = usePartySocket({ + host: PARTYKIT_HOST, + party: "for-moku-server", // ← 追加: binding名をkebab-caseに変換 + room: roomId, + id: userId, + onMessage(event) { /* ... */ } +}); +``` + +2. **`front/src/app/room/[roomId]/page.tsx`**: +```typescript +// 全てのfetch URLを修正 +const url = `${PARTYKIT_URL}/parties/for-moku-server/${roomId}`; + +// GET: イベント終了後の状態取得 +const req = await fetch(`${PARTYKIT_URL}/parties/for-moku-server/${roomId}`); + +// POST: ユーザー追加 +await fetch(`${PARTYKIT_URL}/parties/for-moku-server/${roomId}`, { + method: "POST", + // ... +}); + +// DELETE: ユーザー削除 +await fetch(`${PARTYKIT_URL}/parties/for-moku-server/${roomId}`, { + method: "DELETE", + // ... +}); +``` + +3. **`front/src/lib/auth/auth.ts`**: +```typescript +// ログアウト時のクリーンアップ +await fetch(`${PARTYKIT_URL}/parties/for-moku-server/${roomId}`, { + method: "DELETE", + // ... +}); +``` + +**接続先URL**: +- 開発: `http://localhost:1999/parties/for-moku-server/:room` +- 本番: `https://your-worker-name.your-subdomain.workers.dev/parties/for-moku-server/:room` + +### 6. 環境変数の更新 + +`.env` または Vercelの環境変数設定で、PartyKitのホストを更新: + +**ローカル開発** (`front/.env` または `.env.local`): +```env +NEXT_PUBLIC_PARTYKIT_HOST=localhost:1999 +``` + +**注意**: +- プロトコル(`http://`)は不要 +- `127.0.0.1`ではなく`localhost`を使用(Next.js SSRのIPv6問題を回避) +- 引用符不要 + +**本番環境** (Vercel): +```env +NEXT_PUBLIC_PARTYKIT_HOST=your-worker-name.your-subdomain.workers.dev +``` + +### 7. Docker環境の設定(このプロジェクトの場合) + +このプロジェクトではDockerを使用してPartyKitを起動します。 + +#### Dockerfile +`partykit/Dockerfile`: +```dockerfile +FROM node:22-bookworm # GLIBC 2.36対応 + +WORKDIR /app +COPY package.json . +RUN npm install +COPY . . + +CMD ["npm", "run", "dev"] +``` + +**重要**: `node:22-bookworm`を使用(Wrangler 3.x系はGLIBC 2.36が必要) + +#### compose.yml +```yaml +services: + partykit: + build: ./partykit + ports: + - "1999:1999" + container_name: partykit-local + # 環境変数はwrangler.tomlで設定 +``` + +#### 起動方法 +```bash +docker compose up -d partykit +``` + +#### ログ確認 +```bash +docker logs -f partykit-local +``` + +### 8. タイムゾーン処理の修正 + +**重要なバグ修正**: イベント終了時刻のアラーム設定で9時間のズレが発生する問題があります。 + +#### 問題のコード +```typescript +// ❌ 間違い: fromZonedTimeを使うと9時間早くアラームが発火 +const endTime = new Date(message.endTime); +const alarm = fromZonedTime(endTime, 'Asia/Tokyo'); +await this.ctx.storage.setAlarm(alarm); +``` + +#### 修正後のコード +`partykit/src/server.ts`: +```typescript +// ✅ 正しい: endTimeは既にUTC時刻なのでそのまま使用 +const endTime = new Date(message.endTime); +await this.ctx.storage.setAlarm(endTime); +``` + +**理由**: +- フロントエンドのAPI (`/api/room/[roomId]`) が返す`endTime`は、`convertUTCToJST()`で変換されたDateオブジェクト +- JSONシリアライズ時に**UTCのISO8601文字列**になる(例: `"2025-11-17T11:00:00.000Z"`) +- `new Date(message.endTime)`で得られるDateオブジェクトは既に正しいUTC時刻 +- `fromZonedTime()`を使うと9時間戻ってしまう + +**import文も修正**: +```typescript +// date-fns-tzのimportは不要 +import type * as Party from "partyserver"; +import { Server } from "partyserver"; +``` + +初回のみ、Cloudflareアカウントとの認証が必要です。 + +```bash +cd partykit +npx wrangler login +``` + +ブラウザが開き、Cloudflareダッシュボードでの認証を求められます。 +認証が完了すると、ローカルに認証情報が保存されます。 + +**確認コマンド**: +```bash +npx wrangler whoami +``` + +### 9. Cloudflareアカウントへのログイン + +初回のみ、Cloudflareアカウントとの認証が必要です。 + +```bash +cd partykit +npx wrangler login +``` + +ブラウザが開き、Cloudflareダッシュボードでの認証を求められます。 +認証が完了すると、ローカルに認証情報が保存されます。 + +**確認コマンド**: +```bash +npx wrangler whoami +``` + +### 10. ローカル開発環境のテスト + +デプロイ前に、ローカルでの動作確認を推奨します。 + +#### Docker環境の場合(推奨): +```bash +docker compose up -d partykit +docker logs -f partykit-local + +# 動作確認 +curl http://localhost:1999/parties/for-moku-server/test-room +# 期待結果: [] (空の配列) +``` + +#### Wrangler直接実行の場合: +```bash +cd partykit +npm run dev +``` + +**フロントエンドの起動**: +```bash +cd front +npm run dev +``` + +**動作確認**: +1. ブラウザで `http://localhost:3000/room/[イベントID]` にアクセス +2. WebSocket接続が成功することを確認 +3. 複数ブラウザでアクセスし、リアルタイム同期を確認 + +### 11. 本番環境へのデプロイ + +#### 初回デプロイ +```bash +cd partykit +npm run deploy +# または +npx wrangler deploy +``` + +成功すると、以下のようなメッセージが表示されます: +``` +Deployed for-moku-partykit + https://for-moku-partykit..workers.dev +``` + +#### デプロイURLの確認 +```bash +npx wrangler deployments list +``` + +### 11. 本番環境へのデプロイ + +#### 初回デプロイ +```bash +cd partykit +npx wrangler deploy +``` + +成功すると、以下のようなメッセージが表示されます: +``` +Deployed for-moku-partykit + https://for-moku-partykit..workers.dev +``` + +#### デプロイURLの確認 +```bash +npx wrangler deployments list +``` + +#### 環境変数の設定(本番) + +**方法1: wrangler.toml に追記** +```toml +[vars] +API_BASE_URL = "https://for-moku.vercel.app" +``` + +再デプロイが必要: +```bash +npx wrangler deploy +``` + +**方法2: Cloudflare Dashboard** +1. Cloudflare Dashboard → Workers & Pages +2. `for-moku-partykit` を選択 +3. Settings → Variables → Environment Variables +4. `API_BASE_URL` = `https://for-moku.vercel.app` を追加 +5. Deploy + +**推奨**: 機密情報以外は`wrangler.toml`で管理、機密情報のみDashboardで設定 + +### 12. フロントエンドの環境変数を更新 + +Vercelの環境変数に以下を設定: + +1. Vercelダッシュボード → プロジェクト → Settings → Environment Variables +2. `NEXT_PUBLIC_PARTYKIT_HOST` を更新: + ``` + for-moku-partykit..workers.dev + ``` +3. **Production**, **Preview**, **Development** 全てに設定 +4. Redeploy + +**本番環境での動作確認**: +```bash +curl https://for-moku-partykit..workers.dev/parties/for-moku-server/test +# 期待結果: [] +``` + +## 主な変更点・注意事項 + +### 互換性 +- **Party.Server インターフェース**: 完全互換 +- **Party.Room API**: 完全互換 +- **Alarm API**: 完全互換 +- **Storage API**: 完全互換 +- **WebSocket API**: 完全互換 + +### デプロイ先の変更 +- **旧**: `*.partykit.dev` (マネージドプラットフォーム) +- **新**: `*.workers.dev` (Cloudflare Workers) またはカスタムドメイン + +### Cloudflare固有の利点 +1. **ダッシュボード**: Cloudflareダッシュボードでログ、メトリクス、トレースを確認可能 +2. **カスタムドメイン**: 簡単にカスタムドメインを設定可能 +3. **グローバルエッジネットワーク**: Cloudflareのグローバルネットワークでレイテンシ削減 +4. **Durable Objects**: CloudflareのDurable Objects技術を直接利用 + +### 既知の制限 +- Cloudflare Workersの制限に準拠: + - CPU時間: 50ms (無料プラン) / 50ms (有料プラン、延長可能) + - メモリ: 128MB + - WebSocket接続数: アカウントプランに依存 + +## トラブルシューティング + +### WebSocket接続エラー + +#### `WebSocket connection to 'ws://localhost:1999/parties/main/...' failed` +**原因**: party名が間違っている。デフォルトの`main`ではなく`for-moku-server`を使用する必要がある。 + +**解決策**: +1. **usePartySocket に party パラメータを追加**: +```typescript +usePartySocket({ + host: PARTYKIT_HOST, + party: "for-moku-server", // ← 追加 + room: roomId, + // ... +}); +``` + +2. **全てのfetch URLを修正**: +```typescript +// ❌ 間違い +`${PARTYKIT_URL}/parties/main/${roomId}` + +// ✅ 正しい +`${PARTYKIT_URL}/parties/for-moku-server/${roomId}` +``` + +#### `TypeError: Cannot read properties of undefined (reading 'idFromName')` +**原因**: 指定されたparty名に対応するDurable Object bindingが存在しない。 + +**解決策**: +1. `wrangler.toml` の binding名を確認: +```toml +[[durable_objects.bindings]] +name = "ForMokuServer" # これがfor-moku-serverに変換される +``` + +2. Docker環境を再起動: +```bash +docker compose restart partykit +``` + +### Docker起動エラー + +#### `Error: Cannot find module 'partyserver'` +**原因**: node_modulesがインストールされていない。 + +**解決策**: +```bash +cd partykit +npm install +docker compose up --build +``` + +#### `Error: The package "wrangler" wasn't found` +**原因**: devDependenciesがインストールされていない。 + +**解決策**: +```bash +cd partykit +npm install --include=dev +``` + +### タイムゾーン・アラーム関連 + +#### イベント終了時刻より9時間早くアラームが発火する +**原因**: `fromZonedTime()`の二重適用。 + +**解決策**: `partykit/src/server.ts` を修正: +```typescript +// ❌ 間違い +const endTime = new Date(message.endTime); +const alarm = fromZonedTime(endTime, 'Asia/Tokyo'); +await this.ctx.storage.setAlarm(alarm); + +// ✅ 正しい +const endTime = new Date(message.endTime); +await this.ctx.storage.setAlarm(endTime); // fromZonedTime不要 +``` + +`date-fns-tz`のimportも削除: +```typescript +// ❌ 削除 +import { fromZonedTime } from 'date-fns-tz'; +``` + +### デプロイエラー + +#### `Error: No account_id found` +**原因**: Cloudflareアカウントにログインしていない + +**解決策**: +```bash +npx wrangler login +``` + +#### `Error: A request to the Cloudflare API failed` +**原因**: +- ネットワーク接続の問題 +- API Tokenの権限不足 +- アカウントの制限 + +**解決策**: +1. インターネット接続を確認 +2. `npx wrangler whoami` で認証状態を確認 +3. 必要に応じて再ログイン: `npx wrangler logout` → `npx wrangler login` + +#### `Error: You must enable Durable Objects on your account` +**原因**: CloudflareアカウントでDurable Objectsが有効化されていない + +**解決策**: +1. Cloudflareダッシュボード → Workers & Pages +2. Durable Objectsタブを開く +3. 有料プラン(Workers Paid)へのアップグレードが必要な場合あり +4. 無料プランでも利用可能だが、制限あり + +### 接続エラー(環境変数) + +#### Next.jsでIPv6エラー: `ECONNREFUSED ::1` +**原因**: `127.0.0.1`をホストに指定すると、Next.js SSRがIPv6 (`::1`) として解釈する。 + +**解決策**: +```env +# ❌ 間違い +NEXT_PUBLIC_PARTYKIT_HOST=127.0.0.1:1999 + +# ✅ 正しい +NEXT_PUBLIC_PARTYKIT_HOST=localhost:1999 +``` + +`front/src/app/env.ts` も修正: +```typescript +export const PROTOCOL = PARTYKIT_HOST.startsWith("127.0.0.1") || PARTYKIT_HOST.startsWith("localhost") + ? "http" + : "https"; +``` + +### データベースクエリエラー + +#### `TypeError: Cannot read properties of undefined (reading 'userIcons')` +**原因**: `finished_event_state`テーブルにレコードが存在しない場合のnullチェック不足。 + +**解決策**: `front/src/lib/db/finished_event_state.ts`: +```typescript +// ❌ 間違い +return res[0].userIcons as UserIcon[] ?? null; + +// ✅ 正しい +return res[0]?.userIcons as UserIcon[] ?? null; +``` + +## 重要な注意点 + +### パッケージ名の違い + +| 元のPartyKit | Cloudflare版 | +|-------------|------------| +| `partykit` (サーバー) | `partyserver` | +| `partysocket` (クライアント) | `partysocket` (同じ) | + +**混同しないように注意**: +- リポジトリ名: `cloudflare/partykit` +- サーバーパッケージ名: `partyserver` (NOT `partykit`) +- クライアントパッケージ名: `partysocket` (変更なし) + +### インポート文の変更まとめ + +```typescript +// 旧: partykit/partykit +import type * as Party from "partykit/server"; + +// 新: cloudflare/partykit +import type * as Party from "partyserver"; +``` + +## 移行チェックリスト + +### 準備 +- [ ] Cloudflareアカウントを作成 +- [ ] Node.js v18以上がインストールされているか確認 +- [ ] Docker環境が起動可能か確認 + +### コード変更 +- [ ] `partykit` パッケージをアンインストール +- [ ] `partyserver@0.0.75` をインストール +- [ ] `server.ts` のインポート文を `partyserver` に変更 +- [ ] `Server` で型パラメータを追加 +- [ ] `declare env: Env` と `declare ctx: DurableObjectState` を追加 +- [ ] `src/index.ts` を新規作成(エントリーポイント) +- [ ] `wrangler.toml` を作成してDurable Objects設定を追加 +- [ ] `package.json` の`dev`/`deploy`スクリプトを更新 +- [ ] タイムゾーン処理を修正(`fromZonedTime()`削除) + +### フロントエンド変更 +- [ ] `usePartySocket` に `party: "for-moku-server"` を追加 +- [ ] 全てのfetch URLを `/parties/for-moku-server/` に変更 +- [ ] `.env` で `NEXT_PUBLIC_PARTYKIT_HOST=localhost:1999` を設定 +- [ ] `env.ts` のPROTOCOL判定に`localhost`チェックを追加 +- [ ] Optional chaining追加(`res[0]?.userIcons`) + +### Docker環境 +- [ ] `Dockerfile` で `node:22-bookworm` を使用 +- [ ] `compose.yml` でポート1999をマッピング +- [ ] Docker環境を再ビルド: `docker compose up --build` +- [ ] ログ確認: `docker logs -f partykit-local` + +### ローカルテスト +- [ ] Docker起動: `docker compose up -d partykit` +- [ ] HTTP接続確認: `curl http://localhost:1999/parties/for-moku-server/test` +- [ ] フロントエンド起動: `cd front && npm run dev` +- [ ] ブラウザで `/room/[eventId]` にアクセス +- [ ] WebSocket接続成功を確認 +- [ ] リアルタイム同期動作確認(座席移動など) + +### デプロイ +- [ ] `npx wrangler login` でCloudflareアカウントにログイン +- [ ] `npx wrangler whoami` で認証確認 +- [ ] 本番環境変数を設定(`wrangler.toml` または Dashboard) +- [ ] 本番環境へデプロイ: `npx wrangler deploy` +- [ ] デプロイされたURLを確認: `npx wrangler deployments list` + +### フロントエンド本番設定 +- [ ] Vercelの環境変数 `NEXT_PUBLIC_PARTYKIT_HOST` を更新 +- [ ] Vercelで再デプロイ +- [ ] 本番環境で動作確認 + +### 最終テスト +- [ ] 本番環境でイベント作成 +- [ ] WebSocket接続確認 +- [ ] 複数ユーザーでリアルタイム同期確認 +- [ ] イベント終了時のAlarm動作確認 +- [ ] Cloudflareダッシュボードでログ・メトリクス確認 + +## よくある質問 (FAQ) + +### Q1: Cloudflareアカウントは有料プランが必要ですか? +A: 無料プランでも利用可能です。ただし、Durable Objectsには以下の制限があります: +- 無料プラン: 1日あたり100,000リクエスト、最大30のDurable Objectsインスタンス +- 詳細: [Cloudflare Workers 料金](https://developers.cloudflare.com/workers/platform/pricing/) + +### Q2: カスタムドメインを使用できますか? +A: はい、可能です。Cloudflareダッシュボードから設定できます。 +1. Workers & Pages → 該当のWorker → Settings → Domains & Routes +2. Custom Domainを追加 + +### Q3: 既存のデータは移行されますか? +A: いいえ、自動的には移行されません。元のPartyKitのストレージデータは別環境です。 +必要に応じて、元のPartyKitからデータをエクスポートし、新しい環境で初期化が必要です。 + +### Q4: 開発環境とステージング環境を分けられますか? +A: はい、`wrangler.toml` で複数環境を設定可能です: +```toml +[env.production] +name = "for-moku-partykit" + +[env.staging] +name = "for-moku-partykit-staging" +``` + +デプロイ時に環境を指定: +```bash +npx wrangler deploy --env staging +``` + +### Q5: ログはどこで確認できますか? +A: Cloudflareダッシュボードで確認できます: +1. Workers & Pages → 該当のWorker → Logs +2. リアルタイムログストリームまたは過去のログを参照可能 + +## 参考リンク + +### 公式ドキュメント +- [Cloudflare PartyKit GitHub](https://github.com/cloudflare/partykit) +- [PartyServer パッケージ README](https://github.com/cloudflare/partykit/tree/main/packages/partyserver) +- [PartySocket パッケージ README](https://github.com/cloudflare/partykit/tree/main/packages/partysocket) +- [Cloudflare Workers ドキュメント](https://developers.cloudflare.com/workers/) +- [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/) +- [Wrangler CLI ドキュメント](https://developers.cloudflare.com/workers/wrangler/) + +### サンプル・チュートリアル +- [PartyServer Fixtures (Examples)](https://github.com/cloudflare/partykit/tree/main/fixtures) +- [Cloudflare Workers Examples](https://developers.cloudflare.com/workers/examples/) + +### 関連情報 +- [元のPartyKitの問題 (Issue #971)](https://github.com/partykit/partykit/issues/971) +- [Cloudflare Workers 料金プラン](https://developers.cloudflare.com/workers/platform/pricing/) + +## まとめ + +Cloudflare版PartyKitへの移行は、コード変更が最小限で済み、インフラの信頼性とパフォーマンスが向上します。 +元のPartyKitがメンテナンス停止状態である以上、早期の移行を強く推奨します。 + +移行作業は基本的にインポート文の変更とデプロイ先の切り替えのみで、既存のコードロジックはほぼそのまま使用できます。 diff --git a/docs/code/websocket/partyKit.md b/docs/code/websocket/partyKit.md index 314530f..b0c343e 100644 --- a/docs/code/websocket/partyKit.md +++ b/docs/code/websocket/partyKit.md @@ -1,5 +1,153 @@ -パーティーキット使います。 +# PartyKit (Cloudflare版) -https://docs.partykit.io/ +WebSocketによるリアルタイム通信に使用しています。 -サンプル:GitHubのどこか \ No newline at end of file +**重要**: このプロジェクトでは、Cloudflareが公式にメンテナンスしている **PartyServer** (`partyserver` パッケージ) を使用しています。元の `partykit/partykit` は2025年10月以降デプロイ不可となっているため、移行済みです。 + +- **公式ドキュメント**: https://docs.partykit.io/ +- **移行ガイド**: [cloudflare-partykit-migration.md](./cloudflare-partykit-migration.md) + +## パッケージ情報 + +- **サーバー**: `partyserver@0.0.75` +- **フロントエンド**: `partysocket@1.1.4` +- **デプロイツール**: Wrangler (Cloudflare Workers CLI) + +## アーキテクチャ + +``` +/parties/:party/:room + ^^^^^^ ^^^^ + | └─ ルームID (イベントID) + └─ Durable Object binding名 (kebab-case変換) + +例: /parties/for-moku-server/26 + → ForMokuServer Durable Objectのルーム26 +``` + +**Durable Object**: Cloudflareのステートフルサーバーレス機能。各ルーム(イベント)ごとに独立したインスタンスが起動します。 + +## ローカル開発 + +### 環境変数 + +ローカル開発時は環境変数の設定は不要です。 + +`API_BASE_URL`が未設定の場合、自動的に`http://host.docker.internal:3000`(Docker Composeから起動した場合にホストマシンのNext.jsにアクセスするURL)を使用します。 + +**設定方法** (`partykit/wrangler.toml`): +```toml +[vars] +API_BASE_URL = "http://host.docker.internal:3000" +``` + +### 起動方法 + +Docker Composeで起動(推奨): +```bash +docker compose up -d partykit +``` + +または、ローカルでWranglerを直接実行: +```bash +cd partykit +npm run dev +``` + +### ログ確認 + +Docker起動の場合: +```bash +docker logs -f partykit-local +``` + +Wrangler直接実行の場合: +```bash +# ログはコンソールに表示されます +``` + +### 動作確認 + +HTTPエンドポイントのテスト: +```bash +curl http://localhost:1999/parties/for-moku-server/test-room +# 期待結果: [] (空の配列) +``` + +WebSocket接続は、フロントエンドから `/room/[roomId]` ページにアクセスして確認してください。 + +## 本番環境 + +### デプロイ + +Cloudflare Workersへのデプロイ(Wrangler使用): +```bash +cd partykit +npx wrangler login # 初回のみ +npx wrangler deploy +``` + +**デプロイ先URL**: `for-moku-partykit.your-subdomain.workers.dev` + +詳細なデプロイ手順については [Vercelでデプロイする手順.md](../deploy/Vercelでデプロイする手順.md) を参照してください。 + +### 環境変数の設定 + +本番環境では、Cloudflare Dashboardまたはwrangler.tomlで環境変数を設定します。 + +**wrangler.toml** (推奨): +```toml +[vars] +API_BASE_URL = "https://for-moku.vercel.app" +``` + +**Cloudflare Dashboard**: +1. Workers & Pages → for-moku-partykit → Settings → Variables +2. `API_BASE_URL` = `https://for-moku.vercel.app` を追加 + +### フロントエンドの環境変数 + +Vercelの環境変数に以下を設定: +```env +NEXT_PUBLIC_PARTYKIT_HOST=for-moku-partykit.your-subdomain.workers.dev +``` + +## トラブルシューティング + +### WebSocket接続エラー + +**症状**: `WebSocket connection to 'ws://localhost:1999/parties/main/...' failed` + +**原因**: party名が間違っている。`for-moku-server`を使用する必要があります。 + +**解決策**: +- フロントエンド: `usePartySocket({ party: "for-moku-server", ... })` +- URL: `/parties/for-moku-server/${roomId}` + +### Docker起動エラー + +**症状**: `Error: Cannot find module 'partyserver'` + +**原因**: node_modulesがインストールされていない。 + +**解決策**: +```bash +cd partykit +npm install +docker compose up --build +``` + +### アラームが早く発火する + +**症状**: イベント終了時刻より9時間早くアラームが発火する。 + +**原因**: タイムゾーン変換の二重適用。 + +**解決策**: `server.ts`で`fromZonedTime()`を削除し、`new Date(message.endTime)`をそのまま使用。 + +## 参考リンク + +- [Cloudflare Workers ドキュメント](https://developers.cloudflare.com/workers/) +- [Durable Objects ガイド](https://developers.cloudflare.com/durable-objects/) +- [Wrangler CLI リファレンス](https://developers.cloudflare.com/workers/wrangler/) +- [PartyKit公式ドキュメント](https://docs.partykit.io/) \ No newline at end of file diff --git a/front/src/app/env.ts b/front/src/app/env.ts index e6d547b..d433746 100644 --- a/front/src/app/env.ts +++ b/front/src/app/env.ts @@ -1,6 +1,6 @@ export const PARTYKIT_HOST = - process.env.NEXT_PUBLIC_PARTYKIT_HOST ?? "127.0.0.1:1999"; -export const PROTOCOL = PARTYKIT_HOST.startsWith("127.0.0.1") + process.env.NEXT_PUBLIC_PARTYKIT_HOST ?? "localhost:1999"; +export const PROTOCOL = PARTYKIT_HOST.startsWith("127.0.0.1") || PARTYKIT_HOST.startsWith("localhost") ? "http" : "https"; export const PARTYKIT_URL = `${PROTOCOL}://${PARTYKIT_HOST}`; diff --git a/front/src/app/room/[roomId]/page.tsx b/front/src/app/room/[roomId]/page.tsx index ca1ecc4..d7e90ed 100644 --- a/front/src/app/room/[roomId]/page.tsx +++ b/front/src/app/room/[roomId]/page.tsx @@ -35,13 +35,14 @@ export default async function RoomPage({ return eventNotStarted(roomId); } if (currDateTime > event.endDateTime) { - let userIcons = await selectFinishedEventState(eventId); + let userIcons = await selectFinishedEventState(eventId) as UserIcon[] | null; if (!userIcons) { console.log("record doesn't exist") - const url = `${PARTYKIT_URL}/parties/main/${roomId}`; + const url = `${PARTYKIT_URL}/parties/for-moku-server/${roomId}`; + console.log("Fetching from:", url); const req = await fetch(url); - userIcons = await req.json(); + userIcons = await req.json() as UserIcon[]; } return ( @@ -139,7 +140,7 @@ async function insertNewUser(email: string) { } async function removeUserFromRoom(userId: string, roomId: string) { - await fetch(`${PARTYKIT_URL}/parties/main/${roomId}`, { + await fetch(`${PARTYKIT_URL}/parties/for-moku-server/${roomId}`, { method: "DELETE", body: JSON.stringify(userId), headers: { @@ -149,7 +150,7 @@ async function removeUserFromRoom(userId: string, roomId: string) { } async function addUserToRoom(user: User, roomId: string) { - await fetch(`${PARTYKIT_URL}/parties/main/${roomId}`, { + await fetch(`${PARTYKIT_URL}/parties/for-moku-server/${roomId}`, { method: "POST", body: JSON.stringify(user), headers: { diff --git a/front/src/components/templates/room/ActiveEventTemplate.tsx b/front/src/components/templates/room/ActiveEventTemplate.tsx index 15f0c77..650cb39 100644 --- a/front/src/components/templates/room/ActiveEventTemplate.tsx +++ b/front/src/components/templates/room/ActiveEventTemplate.tsx @@ -32,6 +32,7 @@ export default function ActiveEventTemplate({ const socket = usePartySocket({ host: PARTYKIT_HOST, + party: "for-moku-server", room: roomId, id: userId, diff --git a/front/src/lib/auth/auth.ts b/front/src/lib/auth/auth.ts index 1b021a5..7e1c3f1 100644 --- a/front/src/lib/auth/auth.ts +++ b/front/src/lib/auth/auth.ts @@ -88,7 +88,7 @@ async function removeUserFromRoom() { if (session?.user && session.roomId) { const { user, roomId } = session; - await fetch(`${PARTYKIT_URL}/parties/main/${roomId}`, { + await fetch(`${PARTYKIT_URL}/parties/for-moku-server/${roomId}`, { method: "DELETE", body: JSON.stringify(user.id), headers: { diff --git a/front/src/lib/db/finished_event_state.ts b/front/src/lib/db/finished_event_state.ts index 41be4c3..da82e6e 100644 --- a/front/src/lib/db/finished_event_state.ts +++ b/front/src/lib/db/finished_event_state.ts @@ -14,7 +14,7 @@ export async function selectFinishedEventState(eventId: number) { .select() .from(finishedEventState) .where(eq(finishedEventState.eventId, eventId)); - return res[0].userIcons as UserIcon[] ?? null; + return res[0]?.userIcons as UserIcon[] ?? null; } /** diff --git a/partykit/.gitignore b/partykit/.gitignore index 0e39bef..44a4e7c 100644 --- a/partykit/.gitignore +++ b/partykit/.gitignore @@ -1,3 +1,11 @@ /node_modules -.partykit \ No newline at end of file +# PartyKit (legacy) +.partykit + +# Wrangler (Cloudflare Workers) +.wrangler/ +.dev.vars +node_modules/.cache/wrangler/ +dist/ +worker-configuration.d.ts \ No newline at end of file diff --git a/partykit/Dockerfile b/partykit/Dockerfile index 93ad920..08567d6 100644 --- a/partykit/Dockerfile +++ b/partykit/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.16.0-bullseye +FROM node:22-bookworm WORKDIR /app @@ -8,4 +8,5 @@ RUN npm install COPY . . -CMD ["npm", "run", "partykit"] \ No newline at end of file +# Wranglerのローカル開発サーバーを起動 +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/partykit/package-lock.json b/partykit/package-lock.json index 8256a5a..258d2ab 100644 --- a/partykit/package-lock.json +++ b/partykit/package-lock.json @@ -6,603 +6,15 @@ "": { "dependencies": { "date-fns-tz": "^3.2.0", - "partykit": "^0.0.115" - } - }, - "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20240718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240718.0.tgz", - "integrity": "sha512-BsPZcSCgoGnufog2GIgdPuiKicYTNyO/Dp++HbpLRH+yQdX3x4aWx83M+a0suTl1xv76dO4g9aw7SIB6OSgIyQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20240718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240718.0.tgz", - "integrity": "sha512-nlr4gaOO5gcJerILJQph3+2rnas/nx/lYsuaot1ntHu4LAPBoQo1q/Pucj2cSIav4UiMzTbDmoDwPlls4Kteog==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20240718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240718.0.tgz", - "integrity": "sha512-LJ/k3y47pBcjax0ee4K+6ZRrSsqWlfU4lbU8Dn6u5tSC9yzwI4YFNXDrKWInB0vd7RT3w4Yqq1S6ZEbfRrqVUg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20240718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240718.0.tgz", - "integrity": "sha512-zBEZvy88EcAMGRGfuVtS00Yl7lJdUM9sH7i651OoL+q0Plv9kphlCC0REQPwzxrEYT1qibSYtWcD9IxQGgx2/g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20240718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240718.0.tgz", - "integrity": "sha512-YpCRvvT47XanFum7C3SedOZKK6BfVhqmwdAAVAQFyc4gsCdegZo0JkUkdloC/jwuWlbCACOG2HTADHOqyeolzQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" + "partyserver": "^0.0.75" } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20240718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240718.0.tgz", - "integrity": "sha512-7RqxXIM9HyhjfZ9ztXjITuc7mL0w4s+zXgypqKmMuvuObC3DgXutJ3bOYbQ+Ss5QbywrzWSNMlmGdL/ldg/yZg==", - "license": "MIT OR Apache-2.0" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "license": "MIT", - "dependencies": { - "printable-characters": "^1.0.42" - } - }, - "node_modules/capnp-ts": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/capnp-ts/-/capnp-ts-0.7.0.tgz", - "integrity": "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.1", - "tslib": "^2.2.0" - } - }, - "node_modules/clipboardy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz", - "integrity": "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==", - "license": "MIT", - "dependencies": { - "execa": "^8.0.1", - "is-wsl": "^3.1.0", - "is64bit": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "license": "MIT" + "version": "4.20251115.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251115.0.tgz", + "integrity": "sha512-aM7jp7IfKhqKvfSaK1IhVTbSzxB6KQ4gX8e/W29tOuZk+YHlYXuRd/bMm4hWkfd7B1HWNWdsx1GTaEUoZIuVsw==", + "license": "MIT OR Apache-2.0", + "peer": true }, "node_modules/date-fns": { "version": "4.1.0", @@ -624,630 +36,34 @@ "date-fns": "^3.0.0 || ^4.0.0" } }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true + "node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" } - } - }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit-hook": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", - "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "license": "Unlicense", - "dependencies": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is64bit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", - "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", - "license": "MIT", - "dependencies": { - "system-architecture": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/miniflare": { - "version": "3.20240718.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240718.0.tgz", - "integrity": "sha512-TKgSeyqPBeT8TBLxbDJOKPWlq/wydoJRHjAyDdgxbw59N6wbP8JucK6AU1vXCfu21eKhrEin77ssXOpbfekzPA==", - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "acorn": "^8.8.0", - "acorn-walk": "^8.2.0", - "capnp-ts": "^0.7.0", - "exit-hook": "^2.2.1", - "glob-to-regexp": "^0.4.1", - "stoppable": "^1.1.0", - "undici": "^5.28.4", - "workerd": "1.20240718.0", - "ws": "^8.17.1", - "youch": "^3.2.2", - "zod": "^3.22.3" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", - "license": "MIT", - "dependencies": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "license": "MIT", - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ohash": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz", - "integrity": "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==", - "license": "MIT" - }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/partykit": { - "version": "0.0.115", - "resolved": "https://registry.npmjs.org/partykit/-/partykit-0.0.115.tgz", - "integrity": "sha512-WHmJIZsAzRWrm1lrtU7wcl0tpD4rg2vgmn4+hXGPjU4mnXgFxyjVq2ZzO547xRXa1IbETOP6J9INl/ergR99bA==", "license": "MIT", - "dependencies": { - "@cloudflare/workers-types": "4.20240718.0", - "clipboardy": "4.0.0", - "esbuild": "0.21.5", - "miniflare": "3.20240718.0", - "ts-dedent": "^2.2.0", - "unenv": "2.0.0-rc.0", - "yoga-wasm-web": "0.3.3" - }, "bin": { - "partykit": "dist/bin.mjs" - }, - "optionalDependencies": { - "fsevents": "2.3.3" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "license": "MIT" - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "license": "Unlicense" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "license": "Unlicense", - "dependencies": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "license": "MIT", - "engines": { - "node": ">=4", - "npm": ">=6" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/system-architecture": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", - "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "license": "MIT", - "engines": { - "node": ">=6.10" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "license": "MIT" - }, - "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", - "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": ">=14.0" - } - }, - "node_modules/unenv": { - "version": "2.0.0-rc.0", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.0.tgz", - "integrity": "sha512-H0kl2w8jFL/FAk0xvjVing4bS3jd//mbg1QChDnn58l9Sc5RtduaKmLAL8n+eBw5jJo8ZjYV7CrEGage5LAOZQ==", - "license": "MIT", - "dependencies": { - "defu": "^6.1.4", - "mlly": "^1.7.4", - "ohash": "^1.1.4", - "pathe": "^1.1.2", - "ufo": "^1.5.4" + "node": "^18 || >=20" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/partyserver": { + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.0.75.tgz", + "integrity": "sha512-i/18vvdxuGjx+rpQ+fDdExlvQoRb7EfTF+6b+kA2ILEpHemtpLWV8NdgDrOPEklRNdCc/4WlzDtYn05d17aZAQ==", "license": "ISC", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerd": { - "version": "1.20240718.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240718.0.tgz", - "integrity": "sha512-w7lOLRy0XecQTg/ujTLWBiJJuoQvzB3CdQ6/8Wgex3QxFhV9Pbnh3UbwIuUfMw3OCCPQc4o7y+1P+mISAgp6yg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20240718.0", - "@cloudflare/workerd-darwin-arm64": "1.20240718.0", - "@cloudflare/workerd-linux-64": "1.20240718.0", - "@cloudflare/workerd-linux-arm64": "1.20240718.0", - "@cloudflare/workerd-windows-64": "1.20240718.0" - } - }, - "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" + "nanoid": "^5.1.6" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yoga-wasm-web": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", - "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", - "license": "MIT" - }, - "node_modules/youch": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", - "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", - "license": "MIT", - "dependencies": { - "cookie": "^0.7.1", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" - } - }, - "node_modules/zod": { - "version": "3.25.49", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.49.tgz", - "integrity": "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" + "@cloudflare/workers-types": "^4.20240729.0" } } } diff --git a/partykit/package.json b/partykit/package.json index 4a416fe..1eae7bf 100644 --- a/partykit/package.json +++ b/partykit/package.json @@ -1,9 +1,13 @@ { "scripts": { - "partykit": "npx partykit dev" + "dev": "wrangler dev src/index.ts --local --ip 0.0.0.0 --port 1999", + "deploy": "wrangler deploy" }, "dependencies": { "date-fns-tz": "^3.2.0", - "partykit": "^0.0.115" + "partyserver": "^0.0.75" + }, + "devDependencies": { + "wrangler": "^3.94.0" } } diff --git a/partykit/src/index.ts b/partykit/src/index.ts new file mode 100644 index 0000000..d5b52f2 --- /dev/null +++ b/partykit/src/index.ts @@ -0,0 +1,29 @@ +import { routePartykitRequest } from "partyserver"; +import ForMokuServer from "./server"; +import type { DurableObjectNamespace } from "@cloudflare/workers-types"; + +// Env型: Durable Objectバインディングと環境変数を定義 +type Env = { + ForMokuServer: DurableObjectNamespace; // DurableObjectNamespace type (対応するURLパス: /parties/for-moku-server/...) + API_BASE_URL?: string; +}; + +// Export the Durable Object class for binding +export { ForMokuServer }; + +// Default export: Cloudflare Workers fetch handler +export default { + async fetch(request: Request, env: Env): Promise { + // Route requests to PartyServer + // routePartykitRequest は /parties/:party/:room 形式のURLを処理 + const response = await routePartykitRequest(request, env); + + // If routePartykitRequest handled it, return the response + if (response) { + return response; + } + + // Otherwise return 404 + return new Response("Not Found", { status: 404 }); + }, +}; diff --git a/partykit/src/server.ts b/partykit/src/server.ts index 7ac7d5b..de70899 100644 --- a/partykit/src/server.ts +++ b/partykit/src/server.ts @@ -1,8 +1,5 @@ -import type * as Party from "partykit/server"; - -import { toZonedTime, fromZonedTime } from 'date-fns-tz'; - -const BASE_URL = "https://for-moku-deploy-test.vercel.app/api/room/" +import type * as Party from "partyserver"; +import { Server } from "partyserver"; type User = { id: string, @@ -18,46 +15,100 @@ type UserIcon = { position: { x: number, y: number }, } -export default class Server implements Party.Server { +type Env = { + API_BASE_URL?: string; +} + +export default class ForMokuServer extends Server { userIcons?: UserIcon[]; - constructor(readonly room: Party.Room) {} + + // DurableObject has ctx and env properties but TypeScript doesn't expose them + // @ts-expect-error - DurableObjectState type not available in partyserver + declare ctx: DurableObjectState; + // Env is passed to constructor and stored by parent DurableObject class + declare env: Env; async ensureLoadUserIcons() { if (!this.userIcons) { - this.userIcons = (await this.room.storage.get("userIcons")) ?? []; + // Server extends DurableObject, so we use this.ctx.storage + this.userIcons = (await this.ctx.storage.get("userIcons")) ?? []; } return this.userIcons; } - async onStart() { - await this.room.storage.put("roomId", this.room.id); + getApiBaseUrl(): string { + // Docker環境では環境変数 API_BASE_URL を設定可能 + // wrangler.toml の [vars] セクションで定義できる + // 例: API_BASE_URL = "https://for-moku-deploy-test.vercel.app" + + // 本番環境(Cloudflare Workers): wrangler.toml の vars または Cloudflare Dashboard で設定 + // DurableObject のコンストラクタで env が渡され、親クラスに保存されている + if (this.env?.API_BASE_URL) { + return this.env.API_BASE_URL; + } + + // ローカル開発(Docker): host.docker.internal でホストマシンの localhost にアクセス + // フロントエンドが localhost:3000 で動作している前提 + return 'http://host.docker.internal:3000'; + } - const response = await fetch(`${BASE_URL}${this.room.id}`); - const { message } = await response.json(); - const endTime = new Date(message.endTime); + async onStart() { + // remember room id + await this.ctx.storage.put("roomId", this.name); + + const baseUrl = this.getApiBaseUrl(); + try { + const response = await fetch(`${baseUrl}/api/room/${this.name}`); + if (!response.ok) { + console.error(`[onStart] API error: ${response.status}`); + return; + } + const { message } = await response.json(); + const endTime = new Date(message.endTime); - if (endTime.getTime() > Date.now()) { - const alarm = fromZonedTime(endTime, 'Asia/Tokyo'); - await this.room.storage.setAlarm(alarm); + if (endTime.getTime() > Date.now()) { + // endTimeは既にUTCのISO8601文字列なので、fromZonedTimeは不要 + await this.ctx.storage.setAlarm(endTime); + console.log(`[onStart] Room ${this.name}: Alarm set for ${endTime.toISOString()}`); + } + } catch (error) { + console.error(`[onStart] Error fetching room metadata:`, error); } } async onAlarm() { - const roomId = await this.room.storage.get("roomId"); + console.log(`[onAlarm] Starting alarm handler at ${new Date().toISOString()}`); + const roomId = await this.ctx.storage.get("roomId"); const userIcons = await this.ensureLoadUserIcons(); + const baseUrl = this.getApiBaseUrl(); + + try { + const response = await fetch(`${baseUrl}/api/room/${roomId}`, { + method: "POST", + body: JSON.stringify(userIcons), + headers: { + "Content-Type": "application/json", + }, + }); - await fetch(`${BASE_URL}${roomId}`, { - method: "POST", - body: JSON.stringify(userIcons), - headers: { - "Content-Type": "application/json", - }, - }); + if (!response.ok) { + console.error(`[onAlarm] Failed to save state: ${response.status}`); + } else { + console.log(`[onAlarm] Room ${roomId}: Event state saved (${userIcons?.length ?? 0} users)`); + } + } catch (error) { + console.error(`[onAlarm] Error saving event state:`, error); + } - this.room.broadcast(JSON.stringify({ type: "close" })) + // broadcast close to connected clients + try { + this.broadcast(JSON.stringify({ type: "close" })); + } catch (e) { + console.error("[onAlarm] Failed to broadcast close:", e); + } } - async onRequest(request: Party.Request) { + async onRequest(request: Request) { const userIcons = await this.ensureLoadUserIcons(); if (request.method === "GET") { @@ -66,14 +117,14 @@ export default class Server implements Party.Server { if (request.method === "POST") { const user = (await request.json()) as User; - const userIcon = userIcons.find((icon) => { + const userIcon = userIcons?.find((icon) => { return icon.user.id === user.id; }) if (!userIcon) { - this.room.broadcast(JSON.stringify({ type: "new", user })); + this.broadcast(JSON.stringify({ type: "new", user })); this.userIcons!.push({ user: user, position: { x: 0, y: 0 }}); - await this.room.storage.put("userIcons", this.userIcons); + await this.ctx.storage.put("userIcons", this.userIcons); } return new Response(JSON.stringify(user.id)); @@ -82,11 +133,11 @@ export default class Server implements Party.Server { if (request.method === "DELETE") { const userId = await request.json(); - this.room.broadcast(JSON.stringify({ type: "delete", userId })) + this.broadcast(JSON.stringify({ type: "delete", userId })) this.userIcons = userIcons!.filter((icon) => { return icon.user.id !== userId; }); - await this.room.storage.put("userIcons", this.userIcons); + await this.ctx.storage.put("userIcons", this.userIcons); return new Response(null, { status: 204 }); } @@ -99,27 +150,30 @@ export default class Server implements Party.Server { conn.send(JSON.stringify({ type: "sync", userIcons })); } - async onMessage(messageString: string, _sender: Party.Connection) { + async onMessage(connection: Party.Connection, messageString: Party.WSMessage) { + if (typeof messageString !== "string") { + console.warn("Received non-string message"); + return; + } + const message = JSON.parse(messageString); if (message.type === "move") { const userIcon = { user: message.user, position: message.position } - this.room.broadcast(JSON.stringify({ type: "move", ...userIcon })); + this.broadcast(JSON.stringify({ type: "move", ...userIcon })); this.userIcons = this.userIcons!.map((icon) => icon.user.id === message.user.id ? userIcon : icon ); } if (message.type === "edit") { - this.room.broadcast(JSON.stringify({ type: "edit", user: message.user })); + this.broadcast(JSON.stringify({ type: "edit", user: message.user })); this.userIcons = this.userIcons!.map((icon) => { const userIcon = { user: message.user, position: icon.position } return icon.user.id === message.user.id ? userIcon : icon }); } - await this.room.storage.put("userIcons", this.userIcons); + await this.ctx.storage.put("userIcons", this.userIcons); } } - -Server satisfies Party.Worker; diff --git a/partykit/wrangler.toml b/partykit/wrangler.toml new file mode 100644 index 0000000..e4909bb --- /dev/null +++ b/partykit/wrangler.toml @@ -0,0 +1,21 @@ +name = "for-moku-partykit" +main = "src/index.ts" +compatibility_date = "2025-11-01" +compatibility_flags = [] + +# Durable Objects設定 +[[durable_objects.bindings]] +name = "ForMokuServer" # バインディング名(URLの/parties/for-moku-server/...に対応) +class_name = "ForMokuServer" # server.tsでexportしているクラス名 + +# マイグレーション設定(初回デプロイ時) +# 無料プラン対応: new_sqlite_classes を使用 +[[migrations]] +tag = "v1" +new_sqlite_classes = ["ForMokuServer"] + +# 環境変数 +# ローカル開発時: 未設定の場合は自動的に http://host.docker.internal:3000 を使用 +# 本番環境: 以下のコメントアウトを解除してVercelのURLを設定 +# [vars] +# API_BASE_URL = "https://for-moku.vercel.app"