関数、定期ジョブ、データベース Webhook
関数のコードの形、できることとできないこと(外部リクエストは有効にすれば可能)、呼び出し方、cron のスケジュール(UTC)、テーブルの変更を外部に知らせる Webhook。
関数
サーバーなしでリクエストを1つ受け取って処理するコードです。決済サービスの Webhook の受信、シークレットキーが必要な計算、定期ジョブの本体に使います。
list_functions デプロイした関数と呼び出しアドレス
get_function 現在のコード (変更する前にまず読んでください)
deploy_function デプロイ (同じ名前ならまるごと置き換え、次の呼び出しから適用)
set_function_public 公開かどうかだけを変更
set_function_limits 時間の制限を変更(呼び出し 1 回、SQL 1 文)
remove_function 削除
list_function_secrets 関数シークレットの名前(値は返しません)
set_function_secrets 関数シークレットを保存(同じ名前は上書き)
copy_schema_from_project 別のプロジェクトのテーブルと構造(関数・ビュー・ポリシー含む)をここに作る
copy_data_from_project 別のプロジェクトのテーブルの行をここへコピー(無い行のみ、または変更分も)
copy_users_from_project 別のプロジェクトのログインユーザーをコピー(ハッシュごと、サーバー内で)
compare_projects_data 二つのプロジェクトのテーブルを行数とハッシュで比較(コピーが完全か)
get_project_forward 別のプロジェクトがこのプロジェクトの公開関数の呼び出しを代わりに受けているか確認
set_project_forward 公開関数の呼び出しを別のプロジェクトに代わりに受けさせる(分割後も Webhook が途切れない)
set_project_write_lock プロジェクトを一時的に読み取り専用にする(分割の最後のコピー前。自動で解除されます)
get_project_write_lock 書き込みロックを見る
copy_functions_from_project 別のプロジェクトの関数をコードごとコピー(非公開で)
copy_function_secrets 別のプロジェクトの関数シークレットを名前でコピー
delete_function_secret 関数シークレットを1つ削除コードはファイル1つです:
export default async (request, { sql }) => {
const body = request.json() as { id?: string } | null
const rows = await sql('select * from orders where id = $1', [body?.id])
return { order: rows[0] ?? null }
}request: method、headers、claims(呼び出したキーのクレーム。roleはanon・authenticated・service_role。キーなしで呼んだ公開関数では null)、text()、json()sql(text, values): このプロジェクトの service_role 権限で1文を実行。RLS を通らないため、claims で自分で確認してください。- レスポンス: オブジェクト(JSON)、文字列、または
{ status, headers, body } - ネットワークは既定で無効です。
set_function_egress(またはコンソールの関数画面の外部リクエスト)で有効にすると、fetch()で公開 HTTPS アドレスを呼び出せます(サーバーが代わりに送信し、内部アドレスはブロック、プランの1分あたりの上限まで(Free 300・Pro 3,000・Enterprise 10,000回、list_functionsで確認できます)、すべて関数のログに残ります)。 - 接続(ソケット):
Deno.connect、Deno.connectTls、Deno.startTlsも使えるので、メールライブラリ(denomailer)やデータベースドライバがそのまま動きます。サーバーが代わりに開き、外部リクエストと同じルールに従います。ネットワークを有効にする必要があり、接続1回は1分あたりの上限に1回として数え、公開アドレスのみ許可されます。ポートは1024以上と、Web(80・443)およびメール(110・143・465・587・993・995)のみ開いており、25番などそれ以外の低いポートは遮断します。TLS証明書はサーバーが名前で検証します。1回の呼び出しで同時に4つまで、60秒使われない接続は閉じ、呼び出しが終わると一緒に閉じます。 - このプロジェクトのAPI(
https://<ref>.<zone>)はネットワークを有効にしなくても呼び出せ、自動で入る変数(SUPABASE_URL、SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEY、blackwall://env)が入っているため、Supabaseの関数のcreateClient(Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!)がそのまま動きます。この呼び出しは1分あたりの上限に数えず、ログには残ります。Supabaseの新しい名前SUPABASE_SECRET_KEYS・SUPABASE_PUBLISHABLE_KEYS({"default": "<キー>"}の形のJSON)も入ります。SUPABASE_DB_URLはありません。関数はネットワークのない場所で動きデータベースに直接接続できないため、createClientか2番目の引数のsqlを使ってください。 - npm パッケージ:
deploy_functionにdependencies({"zod": "^3.23.8"})またはpackage_json(+package_lock)を一緒に送ると、デプロイ時にインストールし、コードと一緒に1つの ESM ファイルにまとめます(最大5MB)。インストールスクリプトは実行せず、ネイティブモジュール(.node)は使えません。複数のファイルはfiles({"lib/util.ts": "..."})で送り、相対パスで import します。バンドルされた関数を変更するときは、get_functionが返したpackage_jsonとfilesも一緒に送り直してください。 - インストール時間: 一度インストールしたパッケージの組み合わせ(package.json とロックファイル)は再利用するので、2回目からのデプロイは数秒で終わります。MCP の
deploy_functionは40秒以内に終わらないと、すぐにstatus: "building"で応答し、サーバーでデプロイを続けます。get_functionのbuild.statusがdeployedになれば新しいバージョンが呼び出しを受け、failedならbuild.errorに理由があります。 - フォルダーごとのデプロイ:
index.tsとほかのモジュール(任意でdeno.json)が入ったフォルダーをoneend functions deploy <名前> <フォルダー>で、またはdeploy_functionのfilesにindex.tsを含むすべてを入れて送ります。Supabase と同じようにdeno.jsonのimportsとnpm:zod@3のような import を使え、デプロイ時に npm パッケージに変換してインストールします。 - jsr: と URL の import:
jsr:@supabase/supabase-js@2は npm の同じパッケージとして、ほかのjsr:パッケージは JSR の npm 互換レジストリからインストールします。https://deno.land/[email protected]/http/server.tsやhttps://esm.sh/...のような URL の import はデプロイ時に一度だけ取得して一緒にまとめます(取得したアドレス、最終的なアドレス、SHA-256 がget_functionのremote_importsに残り、deno.lockがあればハッシュが一致しないとデプロイできません)。関数が実行中に取得することはありません。アドレスに届かなければ、そのアドレスと理由を示してデプロイが失敗します。 - _shared フォルダー: Supabase と同じように
supabase/functions/<名前>/index.tsが../_shared/cors.tsを import していれば、oneend functions deploy <名前>がそのフォルダーも一緒に送ります。MCP ではfilesに../_shared/cors.tsのパスのまま入れます。 - 時間の制限: 呼び出し 1 回は既定で 150 秒(Supabase のエッジ関数と同じ)、関数の中の SQL 1 文は既定で 8 秒です。
set_function_limits(またはoneend functions limits、コンソールの関数画面)で関数ごとに変えられ、最大はget_functionのtimeout_cap_secondsです。超えると 504 で打ち切られます。 - 本番の環境変数(blackwall://env)が
process.envで渡されます。値を変更すると、次の呼び出しから新しい値になります。 - 関数シークレット: Webhook の署名キーやメールサービスの API キーのように関数だけが使う値は
set_function_secrets { ref, secrets: { "RESEND_API_KEY": "re_…" } }で保存します(コンソールの関数シークレット、oneend secrets set、RESTPOST /v1/projects/<ref>/function-secrets)。サイトのビルドには入らず、同じ名前の環境変数より優先してprocess.envに入り、次の呼び出しから使われます。値は保存できますが、どのツールや API でも読み返せません。list_function_secretsは名前と変更日時だけを返し、削除はdelete_function_secret(DELETE /v1/projects/<ref>/function-secrets/<name>)です。別のプロジェクトにすでに保存したキーは貼り直さず、copy_function_secrets { ref, from: "<別のプロジェクトの ref>", names: ["ANTHROPIC_API_KEY"] }でコピーします(oneend secrets copy --from <ref> 名前...、RESTPOST /v1/projects/<ref>/function-secrets/copy)。値はサーバーの中だけで移り、すでにある名前はoverwrite: trueのときだけ置き換え、応答はcopied・skipped・missingの名前だけです。両方のプロジェクトでオーナーか管理者である必要があります。表示・変更できるのはオーナーと管理者のみです。 - 関数は本番のシークレットを読めるため、オーナーと管理者だけがデプロイできます。
呼び出し:
POST https://<ref>.<zone>/functions/v1/<name>
Authorization: Bearer <公開キー、ユーザートークンまたはシークレットキー>呼び出せる人:
| 関数 | キーなし | 公開キー | ユーザートークン・シークレットキー |
|---|---|---|---|
| 非公開(既定) | 401 | 可(claims.role = anon) | 可(authenticated・service_role) |
公開(public: true) | 可(claims = null) | 可 | 可 |
非公開は Supabase の verify_jwt と同じなので、公開キーを付けた fetch と supabase.functions.invoke はそのまま動きます。
公開キーはアプリに入っていて誰でも見られるため、ログインしたユーザーだけを受け付けるには関数の中で request.claims.role === 'authenticated' を確認してください。
呼び出し元の Authorization と apikey ヘッダーはそのまま関数に届きます(Supabase と同じ)。そのため関数の中で createClient(url, anonKey, { global: { headers: { Authorization: req.headers.get('Authorization') } } }) とすれば、そのユーザーの権限で問い合わせられます。このプロジェクトのキーとトークンで確認できた値だけが届き、確認できない apikey、Cookie、プラットフォームの認証情報は届きません。
キーなしで届く決済サービスの Webhook だけを public: true にし、その中で署名を自分で検証してください。出力(console.log)は get_logs の source "functions" で確認します。
定期ジョブ
save_job 作成・変更 (同じ名前なら置き換え)
list_jobs 一覧、次回の実行、最後の結果
run_job 今すぐ1回実行 (1分以内)
list_job_runs 実行履歴とエラー
set_job_enabled 無効化・有効化
remove_job 削除
set_job_retention 実行履歴を残す日数(1 | 3 | 7 | 14 | 30 | 90、null は既定値)- スケジュールは5つの欄の cron で、UTC です。韓国時間は UTC + 9時間(日本時間も同じです): 韓国の午前0時は
0 15 * * *、韓国の午前9時は0 0 * * *。 - ジョブが行うのは次のどちらかです:
kind: "function"(デプロイした関数を本文 {} の POST で呼び出す)またはkind: "sql"(サービスロールで1文)。 save_jobでは外部の URL は呼び出せません(SQL からはnet.http_post)。同じ時刻の実行は、サーバーが複数台あっても1回だけです。SQL ジョブはtimeout_seconds(既定120秒)以内に終わる必要があります。ジョブの SQL がset statement_timeout to '15min'のように自分で延ばすとその分待ち、どちらでもプランの最大(Free 300秒、Pro・Enterprise 3600秒)を超えると止まります。- プロジェクトあたりのジョブ数はプランごとに異なります(Free 50、Pro 200、Enterprise 500、SQL で作ったジョブを含む)。
list_jobsのlimit・usedで確認します。あるプロジェクトのジョブが集中しても、ほかのプロジェクトのジョブは遅れません(プロジェクトごとに同時に最大4つ)。
Supabase の SQL をそのまま: スキーマ、pg_cron、pg_net、Vault
Supabase で使っていた SQL を変えずに実行します。SQL エディター、run_sql、apply_migrations、oneend db push、SQL 予約ジョブはすべて同じ名前変換を通ります。
create schema if not exists blackquant; -- app_<ref>_blackquant として作られる
create table blackquant.prices (...); -- SQL では書いた名前のまま
select cron.schedule('collect', '* * * * *', $$ select net.http_post(url := 'https://<ref>.<zone>/functions/v1/collect') $$);
select decrypted_secret from vault.decrypted_secrets where name = 'service_role_key';- スキーマ:
create schema blackquantはこのプロジェクトだけのapp_<ref>_blackquantになります。新しいテーブルは public と同じくアクセスルール(RLS)が有効の状態で作られ、anon・authenticated・service_role の権限も public と同じです。ほかのプロジェクトのスキーマは名前も作れず、読むこともできません。 - 公開スキーマ: データ API は public だけに応答します。
set_exposed_schemas(コンソールのデータベース → スキーマ、oneend db schemas expose)で公開すると、Accept-Profile・Content-Profileヘッダー、supabase-js のdb: { schema: 'blackquant' }で使えます。一覧はlist_schemas。公開していないスキーマは PGRST106(406)です。 - 検索パスと最大行数:
get_data_api_settingsで見て、set_data_api_settingsで変えます(コンソールの設定 → データ API)。search_pathに入れたスキーマ(create schemaで作ったもの、extensions)は要求したスキーマの後ろに付き、関数とビューがスキーマ名なしでその中を見つけます。 - 接続数の上限:
get_database_settingsで見て、set_database_settingsで変えます(コンソールの設定 → データベース)。定期ジョブや関数がtoo many connections for roleで失敗するときは、API ログイン(<ref>_authenticator)が上限に止まっています。enforced.api_connectionsがlimits.api_connectionsより小さければ、api_connectionsをその値まで上げます。すぐに反映され、開いている接続は切りません。サーバーを独占するプロジェクトは、サーバーの最大接続数から直接ログイン(既定30)と予約10を引いた数まで上げられ、他のプロジェクトと共有するサーバーではサーバーの最大接続数の10分の1(20〜80)です。定期ジョブは上限に当たると最大1分待ってから接続し直します。 - pg_cron:
cron.schedule(name, schedule, command)、cron.unschedule、cron.alter_job、cron.job、cron.job_run_details(7日保存)を使います。One Server のスケジューラーがサービスロールで実行し、list_jobsにsource: "sql"で表示されます。最短1分間隔(30 secondsのような秒単位は拒否)、@hourly・@dailyなどの略記は使えます。create extension pg_cronはエラーなく通ります。 - pg_net:
net.http_post(url, body, params, headers, timeout_milliseconds)、net.http_get、net.http_deleteはリクエストをキューに入れて id を返します。トランザクションが終わってから数秒以内に送信し、応答はnet._http_responseに6時間残ります。https のみ、内部アドレスは拒否、タイムアウトは指定したtimeout_milliseconds(既定5秒)を関数の時間上限の最大(既定150秒、運営者が900秒まで)まで守り、応答を待つのをやめても呼び出された関数は最後まで動きます。関数の外部リクエストと同じ1分あたりの上限を共有します。自分のプロジェクトのアドレス(https://<ref>.<zone>)は関数の外部リクエストが無効でも使えます。ほかのアドレスはset_function_egressで有効にする必要があります。 - http 拡張(同期の
http_get・http_post)はありません。 同じトランザクションで応答を待つ呼び出しは、net.http_getで送って後からnet._http_responseで読むか、関数のfetch()に置き換えてください。応答を使わない呼び出しはnet.http_postに置き換えればそのまま動きます。データベースサーバーが直接外部に送ると内部アドレスの遮断と1分あたりの上限を通らないため、入れていません。移行ツールがこの拡張を呼ぶ関数と予約ジョブを名前で示します。 - Vault:
vault.create_secret、vault.decrypted_secretsはサービスロール(SQL エディター、run_sql、シークレットキー、SQL 予約ジョブ)でだけ復号されます。専用データベースのプロジェクトで初めて使うときに有効になります。
データベース Webhook
テーブルの行が変わると通知します。関数の外部リクエストを有効にしていないなら、外部サービス(Slack、メール送信 API など)への通知にはこれを使います。
create_db_webhook 作成: テーブル、イベント(insert/update/delete)、送信先(url または function)
list_db_webhooks 一覧と最近の結果(署名キーは末尾4文字だけ)
list_db_webhook_deliveries 配信履歴と失敗の理由
set_db_webhook_enabled 無効化・有効化
remove_db_webhook 削除- 本文:
{ type: "INSERT"|"UPDATE"|"DELETE", table, schema: "public", record, old_record, hook, delivery, occurred_at } - コミットされた変更だけを送ります。サーバーが再起動中だった場合も、後で送ります。順序は保証しないため、
deliveryで重複を取り除いてください。 - 2xx 以外、または10秒以内に応答がなければ失敗です。10秒後、1分後、5分後、30分後に再送し、5回目で破棄します。
- url は公開された https アドレスだけが使えます。プライベートや内部のアドレス、リダイレクトは拒否します。
- 受信側での検証:
x-blackwall-signature: t=<秒>,v1=<署名>。署名は HMAC-SHA256(signing_secret, "<t>.<本文>") の16進数です。 - signing_secret は Webhook の作成時に一度だけ表示します。一覧(
list_db_webhooks)にはsigning_secret_setと末尾4文字(signing_secret_last4)だけが出ます。なくした場合は Webhook を削除して作り直すと新しいキーが発行されます。
Web 標準の Response、Deno.serve
Deno.serve(async (req) => {
const { name } = await req.json()
return new Response(JSON.stringify({ hello: name }), { headers: { "content-type": "application/json" } })
})export default {
async fetch(req, ctx) {
const hits = await ctx.kv.incr("hits")
const on = await ctx.flags.isEnabled("new-checkout", req.claims?.sub ?? null)
return Response.json({ hits, on })
},
}Supabase 向けに書いた関数もそのまま動きます。new Response(...) (ストリームを含む)、Deno.serve(handler)、export default { fetch }、Deno.env.get() が使えます。関数名のあとのパス (/functions/v1/<name>/a/b) は req.url で受け取ります。EdgeRuntime.waitUntil(promise) に渡した処理は、応答を先に返したあとも関数の時間上限の中で最後まで動き、その間もクエリと外部へのリクエストができます。
ストリームから作った Response は書いたとおりに送ります (サーバー送信イベント、モデルの回答)。ctx.kv、ctx.config、ctx.flags で KV とエッジ設定を使います (blackwall://edge)。