本文へ移動
runlot

ローカルで動かす

runlot dev は、自分のマシンで runlot のバインディングを繋いだままワーカーを実行します。デプロイせずに変更を確認できます。

runlot dev

runlot dev は runlot deploy がアップロードするのと同じバンドルを作り、workerd の上で実行します。デプロイされたアプリが動くのと同じランタイムです。env.db、env.storage、env.auth、env.email、env.assets はローカルの代役に繋ぎます。起動時に開くアドレスと、どのバインディングが本物でどれが代役かの表を出力し、ファイルを保存するたびにビルドし直して再起動します。

  runlot dev — my-app
  http://127.0.0.1:8787

  env.db                   real        real PostgreSQL (postgres://***@localhost:5432/app)
  env.storage              local       real bytes in .runlot/dev/storage
  env.auth                 fake        one dev user, a stub sign-in page, no providers
  env.email                fake        writes .eml into .runlot/dev/mail, sends nothing
  env.assets / env.ASSETS  real        the bundle's assets directory
  fetch()                  widened     dev also allows private and loopback addresses; production allows public only

状態は .runlot/dev に置きます。.gitignore に .runlot を入れてください。

オプション

runlot dev [dir] [--port 8787] [--cron "*/5 * * * *"]

--port は開くアドレスで、既定は 8787 です。--cron はワーカーの scheduled() ハンドラーをその 5 フィールドの UTC スケジュールで実行します。好きなときに 1 回だけ実行することもできます。

curl localhost:8787/__runlot/cron

ランタイム

runlot dev は Node による模倣ではなく、実際の workerd バイナリ — デプロイ後にワーカーを実行するのと同じランタイム — でワーカーを実行します。これには理由があります。ローカル実行が捕まえるべき不具合 — 自分の compatibilityDate では nodejs_compat がまだ開けていない Node 組み込みモジュール、ランタイムが読み込めないモジュールグラフ、Node のものではない Request — は、Node の上にはそもそも存在しません。Node で緑になっても、デプロイについて何も分かりません。

バイナリは CLI と一緒に入ります。インストール時にオプション依存を飛ばした場合は、プロジェクトに追加してください。

npm i -D workerd

すでに持っているバイナリがあれば RUNLOT_DEV_WORKERD=/path/to/workerd で指定します。

何が本物で、何がそうでないか

env.db

PostgreSQL の URL を渡すと、env.db はそのデータベースそのものになります。文は実際に実行され、セッションはトランザクションを開いたまま保ち、失敗した文は本物の SQLSTATE を error.code に入れて返します。セルは本番と同じ表で解釈するので、int8 はここでも向こうでも文字列です。

export RUNLOT_DEV_DATABASE_URL=postgres://localhost:5432/my_app_dev

1 つのアドレスをチームで共有するなら runlot.json に書いても構いません。環境変数が優先されます。

{ "dev": { "database": "postgres://localhost:5432/my_app_dev" } }

URL がない場合、env.db はすべての文を受け付けて 0 行を返す偽物になり、runlot dev は起動時にそう伝えます。データベースに触れるワーカーが起動するには十分ですが、クエリが正しいかどうかは教えてくれません。

runlot pg migrate はデプロイ済みのプロジェクトに migrations/ を適用します。ローカルのデータベースには psql で適用してください。

env.storage

.runlot/dev/storage に実際のバイトとして保存します。put、get、head、delete、list は本番と同じように振る舞います。カーソルのページングも、存在しないキーの削除が成功であることも同じです。presign は開発サーバーのポートに向けた署名付き URL を返し、署名を実際に検証します。バイト数を束ねた場合はそれも検証するので、長さの違うアップロードはここでも拒否されます。

env.auth

ユーザー 1 人と、/__runlot/auth/sign-in の仮サインインページだけです。どのアドレスでも受け付け、パスワードは確認せず、どのプロバイダーとも通信しません。env.auth.user(request) はクッキーができた後はそのユーザーを、その前は null を返します。コードが実際に頼っている区別はこの 2 つです。ページ自体を飛ばすにはこうします。

export RUNLOT_DEV_USER=[email protected]

env.auth.users の読み取りは動きます。書き込みは再現しません。

env.email

env.email.send は .runlot/dev/mail に .eml ファイルを書き、1 行出力します。何も送信しません。送ったものがどう見えるかは、そのファイルをメールクライアントで開けば分かります。

env.ai

env.ai はログインしていればローカルで動きます。呼び出しはコントロールプレーン経由で、Studio のローカルランタイムが使うのと同じレーンに中継されます — 同じカタログ、同じ価格、すべてのプランに無料プランの上限(出力 4,096 トークン、毎分 30 回、同時 2 件) — そして組織の AI クレジットに lane = dev として課金されるので、ダッシュボードの台帳に開発で使った額が出ます。runlot.json に "org" が必要で(または --org)、プロジェクトの AI が有効である必要があります(無効にしていなければどのプロジェクトも有効です)。ストリーミング(stream: true)は名指しで拒否します。中継は JSON 本文を 1 つ返すため、コードがチャンクを期待する場所に丸ごとの答えが来ると、ローカルでは通ってデプロイすると形が変わるからです。RUNLOT_DEV_AI=off を設定するとその実行は env.ai を拒否します。ログインしていなければ拒否し、バインディング表にその理由を書きます。

env.assets

バンドルのアセットディレクトリをディスクから配信します。"notFound": "spa" を守り、"runWorkerFirst": false は本番とまったく同じようにアセットルーターをワーカーの前に置きます。

Durable Objects

代役ではなく本物です。Durable Objects を実装しているランタイムが workerd なので、runlot dev は durable_objects と migrations のブロックから同じネームスペースを同じランタイムに立て、new_classes / new_sqlite_classes の区別もそのまま守ります。ctx.storage、ctx.storage.sql、transactionSync、alarm、idFromName はランタイム自身のものです。ここで見つけた欠陥は、本番でも見つかったはずの欠陥です。

状態は .runlot/dev/objects に書きます。ビルドのたびに消えるディレクトリの 外側 です。ファイルを保存するたびにカウンターが 0 に戻るオブジェクトは Durable Object ではないからです。ストアはプロジェクトの name を鍵にします。runlot.json でプロジェクト名を変えると、以前の状態は付いてこずにその場に残ります。

dev にないのはオブジェクトを取り巻くプラットフォームです。プロセスが一つなので、配置も、locationHint や jurisdiction も、ノードをまたぐスタブも、park と wake も、バックアップもありません。ローカルの同じ env.db、env.storage、env.auth、env.email、env.ai、アセット、シークレットは、デフォルトエクスポートと各オブジェクトクラスの両方に繋がります。それでもローカルのレイテンシは本番を表しません。本番ではオブジェクトの env.db 呼び出しがプロジェクトのホームまでネットワークを移動します。

  env.ROOMS         real        class Room, state in .runlot/dev/objects
  durable objects   local only  one process, no placement, no park and wake, no backup — env.db from an object is local here and a network call in production

dev が強制するマニフェストの規則はデプロイの規則であり、保証は一方向です。デプロイが受け入れるものをここで拒否することはありません。逆は約束しません。ここを通ったマニフェストがデプロイで拒否されることはあります。

シークレット

名前と値はプロジェクトルートの .env.runlot から読みます。1 行に NAME=value を 1 つ、# はコメントです。このファイルは .gitignore に入れてください。runlot dev は読み込んだ名前だけを出力し、値は決して出力しません。名前はデプロイが強制するのと同じ規則を通るので、ここで通る名前は向こうでも通ります。

値はディスク上のファイルではなくプロセス環境を通ってワーカーに届きます。プラットフォームが使っているのと同じ分け方です。

fetch()

ワーカーからの fetch は、開発ではプライベートアドレスとループバックまで開いています。ローカルのアプリをローカルの API に繋いでみることが、ローカルで動かす普通の理由だからです。デプロイされたワーカーは公開アドレスにしか届きません。コードが http://localhost:… を呼ぶなら、ここでは動いて向こうでは失敗します。

フレームワーク

framework: "next" は変更のたびに OpenNext でビルドし、その成果物を配信します。バインディングとルーティング、そしてデプロイが持つ形を確かめる場所としては正しいのですが、速い UI のループのための場所ではありません。UI は next dev で、実際に出ていくものの確認は runlot dev で行ってください。フレームワーク を参照してください。

CLI を別の API エンドポイントへ向ける

CLI は特に指定しなければ https://dash.runlot.io と通信します。RUNLOT_API_URL または --api フラグで、コマンド 1 つ、あるいはシェルセッション全体を別の宛先へ向けられます。

ログインの資格情報はエンドポイントごとに保存されるため、対象を切り替えても、あるホストのトークンが別のホストへ送られることはありません。

runlot versions --api https://dash.runlot.io

このページの目次