アプリ
SDKおよびReactからBeeuniアプリを作成、デプロイ、制御します。
Apps はプロジェクト単位のデプロイで、それぞれに固定 URL が1つあります。
イミュータブルなリリース、プロセスグループごとのマシン、永続ボリュームを備えています。app.capabilities に応じて分岐してください。このページでは SDK の機能として、
プロジェクトハンドル上の apps ファサード、アーティファクト、リリース、マシン、ボリュームの
呼び出し、サインイントークン、エクスポートされる型、React フックを説明します。
モデル、リリース設定、ソースの種類、CLI、固定 URL、 コールド状態からの起動動作については、Apps を参照してください。
const apps = beeuni.project(projectId).apps;
apps ファサード
| メソッド | ラップする API | 動作 |
|---|---|---|
apps.list() |
GET /projects/:pid/apps |
呼び出し元が開ける Apps を一覧表示します(プロジェクト管理者にはすべて表示されます) |
apps.create(input) |
POST …/apps |
App を作成し、固定 URL を割り当てます |
apps.get(appId) |
GET …/apps/:id |
1つの App を読み取ります |
apps.update(appId, input) |
PATCH …/apps/:id |
名前を変更するか、マシン、アイドルタイムアウト、実行モード(always_on)、uses、labels を変更します。実行モードは5分以内に適用され、マシンの変更は次のリリースに適用されます |
apps.remove(appId) |
DELETE …/apps/:id |
App、そのマシン、すべてのリリースイメージを削除し、images: {released, pending} を返します。ボリュームには final スナップショットが7日間保持されます |
apps.start(appId) |
POST …/apps/:id/start |
desired_state を running に設定し、ランタイムをウォームアップします。静的 App は 409 static_app_no_runtime を返します |
apps.stop(appId) |
POST …/apps/:id/stop |
コンピュートを即座に停止し、次のリクエストで再開します。静的 App は 409 static_app_no_runtime を返します。ランタイムを持たず、アクティブなデプロイがある間は配信を続けます |
apps.rollback(appId, deploymentId) |
POST …/apps/:id/rollback |
準備完了のリリースのアーティファクトと設定から新しいリリースを作成し、デプロイと同様にロールアウトします(202)。ボリュームのデータは保持されます |
アーティファクトは、デプロイへのイミュータブルな入力です。
| メソッド | ラップする API | 動作 |
|---|---|---|
apps.artifacts.register(input) |
POST …/apps/artifacts |
archive または oci_image を登録します。アーカイブの場合はアップロード URL を返します |
apps.artifacts.uploadArchive(bytes, options?) |
— | 1回の呼び出しで、1つの .tar.gz の登録、アップロード、ハッシュ計算、確定を行います |
apps.artifacts.finalize(artifactId, input) |
POST …/apps/artifacts/:id/finalize |
手動アップロードの sha256 と size_bytes を確認します |
リリース(デプロイ)はイミュータブルで、番号が付けられます。
| メソッド | ラップする API | 動作 |
|---|---|---|
apps.deployments.create(appId, input) |
POST …/apps/:id/deployments |
アーティファクト、ソース、任意のリリース config からリリースを開始します |
apps.deployments.list(appId) |
GET …/apps/:id/deployments |
デプロイ履歴を一覧表示します |
apps.deployments.get(appId, deploymentId) |
GET …/deployments/:did |
1つのデプロイとそのイベントを読み取ります |
apps.deployments.logs(appId, deploymentId, options?) |
GET …/deployments/:did/logs |
カーソルを使ってランタイムログを読み取ります |
apps.deployments.remove(appId, deploymentId) |
DELETE …/deployments/:did |
1つのデプロイとそのイメージを削除します。稼働中のデプロイまたは進行中のビルドの場合は 409。image: 'released' | 'pending' | 'none' を返します |
マシン、スケール、exec(サーバー App 用。マシンのない App は
409 app_no_machines を返します):
| メソッド | ラップする API | 動作 |
|---|---|---|
apps.machines.list(appId) |
GET …/apps/:id/machines |
すべてのマシン:machine_id、process_group、state、release、vm、volume |
apps.machines.start/stop/restart(appId, machineId) |
POST …/machines/:mid/{start,stop,restart} |
1つのマシンを操作します。マシン一覧を返して完了します |
apps.machines.destroy(appId, machineId) |
DELETE …/machines/:mid |
1つのマシンを破棄します。ボリュームは残り、そのグループの稼働マシン数が1つ減ります |
apps.scale(appId, count) |
PUT …/apps/:id/scale |
プロセスグループごとのマシン数({ worker: 3 }、0~20)をバックグラウンドで適用します(202)。scale と machines を返して完了します |
apps.exec(appId, { command, machine_id?, timeout_s? }) |
POST …/apps/:id/exec |
稼働中のマシンで1つのコマンドを実行します(project.app.admin、監査対象)。timeout_s は1~300、デフォルトは60。exit_code、stdout、stderr(それぞれ1 MiB)、truncated を返して完了します |
ボリューム(永続ディスク):
| メソッド | ラップする API | 動作 |
|---|---|---|
apps.volumes.list(appId) |
GET …/apps/:id/volumes |
すべてのボリューム:name、process_group、machine_id、size_gb、status、attached、snapshot_retention_days |
apps.volumes.create(appId, { name, size_gb, process_group?, snapshot_retention_days? }) |
POST …/volumes |
ボリュームを事前作成します。後で、そのグループ内で同名のマウントを持つマシンが使用します |
apps.volumes.update(appId, volumeId, { size_gb?, snapshot_retention_days? }) |
PATCH …/volumes/:vid |
ボリュームを拡張します(縮小不可、最大100 GB)。または保持期間(1~60日)を変更します |
apps.volumes.destroy(appId, volumeId, confirmName) |
DELETE …/volumes/:vid?confirm=<name> |
project.app.admin。マシンがマウントしている間は 409 volume_attached で拒否されます。final スナップショットを7日間保持します |
apps.volumes.snapshots(appId, volumeId) |
GET …/volumes/:vid/snapshots |
daily、manual、final スナップショット |
apps.volumes.snapshot(appId, volumeId) |
POST …/volumes/:vid/snapshots |
manual スナップショットを即座に作成します |
apps.volumes.restore(appId, volumeId, snapshotId) |
POST …/volumes/:vid/restore |
project.app.admin。マシンを停止し、ボリュームをスナップショットの状態に戻し、そのボリュームでマシンを起動します(202) |
apps.volumes.fork(appId, volumeId, { name, snapshot_id? }) |
POST …/volumes/:vid/fork |
スナップショットまたは現在の内容から、同じ App 内に新しい未接続のボリュームを作成します |
サインイントークン(ケイパビリティ member_tokens。App がこれを持たない場合は
409 app_capability_unsupported):
| メソッド | ラップする API | 動作 |
|---|---|---|
apps.token(appId) |
POST …/apps/:id/token |
呼び出し元を識別する、App 用の有効期間15分のサインイントークン |
アクセスは App 独自の認可ポリシーです。
| メソッド | ラップする API | 動作 |
|---|---|---|
apps.access.get(appId) |
GET …/apps/:id/access |
ポリシーを読み取ります。project.app.read が必要です |
apps.access.update(appId, input) |
PATCH …/apps/:id/access |
ポリシーを置き換え、リビジョンを進めます |
apps.access.session(appId) |
POST …/apps/:id/access-session |
ホスト限定 Cookie と交換できる、有効期間5分の URL を発行します |
静的サイトをデプロイする
uploadArchive はアーティファクトのハンドシェイク全体を実行します。アーティファクトを登録し、
max_bytes に照らして検証し、バイト列を PUT し、SHA-256 を計算して、
確定します。
const apps = beeuni.project(projectId).apps;
const app = await apps.create({ slug: 'docs', name: 'Docs' });
const artifact = await apps.artifacts.uploadArchive(tarGzBytes, {
onProgress: (uploaded, total) => console.log(`${uploaded}/${total}`),
});
const deployment = await apps.deployments.create(app.app_id, {
artifact_id: artifact.artifact_id,
source: { kind: 'static', spa: true },
});
console.log(app.url, deployment.status); // https://…apps.beeuni.ai queued
create はマシンのフィールドも受け付けます:cpu、memory_gb、
disk_gb、idle_timeout_seconds、always_on。デフォルトを使う場合は省略してください。
Apps に月次予算はありません。コンピュートの料金はアカウントのクレジットで支払われます。常時稼働の
サーバー App は24時間365日課金され、オンデマンドの App は起動中のみ課金されます。
always_on: true はサーバー App を24時間365日稼働させます
(WebSocket、バックグラウンドループ)。false は idle_timeout_seconds の後に停止し、
次のリクエストで起動します。静的 App にはランタイムがなく、always_on は
false です。
古いクライアントが monthly_budget_usd を送信しても、API は無視します(200、
エラーなし)。App のレスポンスには含まれません。
uses は、この App がサインイントークンを取得でき、
バインディングのマウント経由でアクセスできる Apps をスラッグで一覧表示します。
デフォルトは空で、update(appId, { uses }) は一覧を置き換えます(
App が使用する Apps を参照)。
create と update は warnings を返し、その値は常に [] です。
const app = await beeuni.project(projectId).apps.create({ slug: 'worker', name: 'Worker', always_on: true });
console.log(app.always_on, app.hosting_type); // true null (until the first deploy)
ステータスをポーリングして、デプロイを待ちます。
async function waitForReady(appId: string, deploymentId: string) {
for (;;) {
const { deployment } = await apps.deployments.get(appId, deploymentId);
if (deployment.status === 'ready') return deployment;
if (deployment.status === 'failed' || deployment.status === 'cancelled') {
throw new Error(deployment.error ?? deployment.error_code ?? deployment.status);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
ステータスの値は queued、validating、building、provisioning、
checking、ready、failed、cancelled です。
OCI イメージをデプロイする
イミュータブルなイメージ参照を登録し、プロセスのコマンドと 公開ターゲットポートを宣言します。
const registered = await apps.artifacts.register({
kind: 'oci_image',
image: 'ghcr.io/hekmat/service:2026-08-07',
});
await apps.deployments.create(app.app_id, {
artifact_id: registered.artifact.artifact_id,
source: {
kind: 'oci_image',
image: 'ghcr.io/hekmat/service:2026-08-07',
command: ['node', 'server.js'],
port: 3000,
readiness_path: '/health',
},
});
register は oci_image に対して upload: null を返します。アップロード URL を取得できるのは
archive のみです。
CreateAppDeploymentInput は environment(機密ではないランタイムの
値)、secrets(ランタイムのキーからプロジェクトのシークレット名への対応)、config(AppReleaseConfigInput、
リリース設定)も受け付けます。無効な config
は issues: [{ path, message }] を伴う 400 app_config_invalid を返します。ボリュームをサポートしないランタイムでの
マウントは 400 app_volumes_unsupported を返します。
await apps.deployments.create(app.app_id, {
artifact_id: registered.artifact.artifact_id,
source: { kind: 'oci_image', image, command: ['node', 'server.js'] },
config: {
processes: { app: '', worker: 'node worker.js' },
http_service: { internal_port: 3000, access: 'app', processes: ['app'] },
mounts: [{ source: 'data', destination: '/data', initial_size_gb: 10, processes: ['app'] }],
deploy: { release_command: 'node migrate.js' },
},
});
ランタイムログを読み取る
let cursor = 0;
for (;;) {
const page = await apps.deployments.logs(app.app_id, deployment.deployment_id, {
after: cursor,
limit: 200,
});
for (const entry of page.entries) console.log(entry.source, entry.line);
cursor = page.next_cursor;
if (page.entries.length === 0) break;
}
各エントリには cursor、time、source(app、appd、caddy)、および
lineが含まれます。caddy行はアクセスログで、メソッド、URI、ステータス、所要時間、
レスポンスヘッダーを記録します。リクエストヘッダーはゲートの認証情報(ビューアートークンとID、プロバイダーのイングレストークン)を含むため、
決して記録されません。
マシン、スケール、ボリューム
const apps = beeuni.project(projectId).apps;
const machines = await apps.machines.list(app.app_id);
await apps.scale(app.app_id, { worker: 3 }); // 202: applied in the background
const { exit_code, stdout } = await apps.exec(app.app_id, { command: ['ls', '/data'] });
const [data] = await apps.volumes.list(app.app_id);
const snapshot = await apps.volumes.snapshot(app.app_id, data.volume_id);
await apps.volumes.restore(app.app_id, data.volume_id, snapshot.snapshot_id); // project.app.admin
const copy = await apps.volumes.fork(app.app_id, data.volume_id, { name: 'data_copy' });
マシンはリリース間でmachine_idを保持します。各リリースではその
ボックス(instance_id)が置き換えられます。1台のマシンが一度にマウントできるボリュームは1つです。リストアは
ボリュームの新しいバージョンです。置き換えられた内容は履歴に残ります。マウントされたボリュームの
スナップショットには、直近約1秒分の書き込みが含まれない場合があります。
Convexバックエンド
Convexバックエンドは、convexテンプレート(beeuni apps launch --template convex --name db。詳しくは
Template convex)から作成した、ボリューム付きの通常のサーバーAppです。
SDKにConvex専用の呼び出しはありません。execでマシンから管理キーを読み取り、
usesでUI Appからバックエンドにアクセスできるようにします。
const db = (await apps.list()).find((a) => a.slug === 'db')!;
const { stdout } = await apps.exec(db.app_id, { command: ['sh', '-c', 'cd /convex && ./generate_admin_key.sh'] });
// The admin key controls all code and data: never log it or ship it to a browser.
await apps.update(site.app_id, { uses: ['db'] }); // the site may mint db tokens and bind to it
db.servicesにはバックエンドのURL、つまりhttp(ConvexクライアントURL)と
site(HTTPアクション)が格納されます。
Appは誰が見ているかをすでに把握しています
BeeuniがホストするAppは、Beeuniにすでにサインインしている人によって開かれます。Appsゲートは最初のバイトが配信される前にユーザーを認証するため、App独自のログインは不要です。2つ目のパスワードも、同意画面も、リダイレクトも必要ありません。
ブラウザーでは次のようになります。
import { createBeeuni, beeuniAppViewerToken } from '@beeuni/sdk';
import { useBeeuniAppViewer } from '@beeuni/sdk/react';
const beeuni = createBeeuni({
backendUrl: '/_beeuni/api/v1', // the gate, on your App's own origin
getToken: beeuniAppViewerToken(), // the viewer's own App-scoped token
});
function Header() {
const { status, viewer } = useBeeuniAppViewer();
return <span>{status === 'viewer' ? viewer.email : 'Signed out'}</span>;
}
ブラウザーはApp自身のオリジン上にある/_beeuni/api/v1/*を呼び出し、https://api.beeuni.ai/v1を直接呼び出すことはありません。APIはAppオリジンからの
CORSプリフライトに応答しないため、ブラウザーからの直接呼び出しは失敗します。ゲートは各呼び出しを、サインイン済みのビューアーとして、ビューアーのAppスコープトークンとともにBeeuni APIへ転送します。ルールは次のとおりです。
viewer_token_scope: 'api'が必要です。そうでない場合は403 viewer_api_disabledを返します。- ゲートがサインインさせたビューアー(Beeuniログインまたはアクセスリンク)に対してのみ動作します。ビューアーがいない場合は
401 no_viewer_identityを返します。 - App自身のオリジンからのリクエストにのみ応答します。クロスサイトリクエストには
403 cross_site_requestを返します。 /v1/oauth/*とMCPエンドポイントを除く/v1/*を転送します。これらには404を返します。AppのCookieと独自のAuthorizationヘッダーがAPIに到達することはなく、APIがAppにCookieを設定することもできません。
Appのサーバーでは、ゲートがすべてのリクエストにIDを署名します。
import { readAppViewer, createAppViewerBeeuni } from '@beeuni/sdk/server';
const viewer = await readAppViewer(request);
// { userId, email, name, picture, groupIds, groups, role, projectId, accountId, appId, accessMode, token }
if (!viewer) return new Response('Not found', { status: 404 });
// and, for an `api`-scoped App, act as them:
const beeuni = await createAppViewerBeeuni(request, { backendUrl });
await beeuni.projects.list(); // their projects, their role
readAppViewerは、Beeuniがデプロイ時にAppへ注入するBEEUNI_APP_VIEWER_SECRETを使って、ヘッダーのHMACを検証します。偽造されたヘッダーが通ることはありません。ゲートはクライアントが送信したコピーを転送前に削除し、署名にはAppごとに導出されたシークレットを使用します。
Appに通知される情報の範囲
Appのアクセスポリシーにある1つの設定、BeeuniのSettings → Access、beeuni apps access <app> --viewer off|identity|api、またはPATCH /projects/:id/apps/:appId/access上のviewer_token_scopeで設定します。
| スコープ | Appが受け取る情報 |
|---|---|
identity(デフォルト) |
ビューアーのID、メールアドレス、名前、画像、アカウントロール、Beeuniグループ(このAppのアカウント内におけるIDと名前)、およびprofile emailトークン。各ユーザーに自身のデータを表示し、グループでアクセスを制限するのに十分です。 |
api |
上記に加え、Beeuni API上でそのユーザーとして動作するトークン(ユーザー自身のロールによる制限付き)。 |
off |
何も受け取りません。 |
このトークンはユーザーのBeeuniセッションではありません。有効期間は1時間で、これらのスコープのみを持ちます。また、Appが発行したすべてのトークンは、Appが削除されるかアクセスポリシーが変更されると無効になります。
ビューアートークンの使用
- サーバーではリクエストごとに読み取ります。 ゲートはプロキシされた各リクエストの
x-beeuni-app-viewer-tokenに有効なトークンを配置します。これをAuthorization: BearerとしてBeeuni APIに送信します。createAppViewerBeeuni(request)がまさにそれを行います。リクエストごとにクライアントを1つ作成し、トークンを保存しないでください。 - アクセスポリシーの変更後は、古いトークンは機能しません。 次にゲートを通るリクエストには新しいトークンが含まれます。ブラウザーでは、
beeuniAppViewerToken()が自動的に復旧します。APIがトークンを拒否すると、SDKは/_beeuni/viewerを再度読み取り、呼び出しを1回だけ再実行します。 - ビューアーのロールが上限です。 トークンで実行できるのはBeeuni上でビューアーが実行できることだけで、それを超えることはありません。エージェント権限を持つプロジェクトでは、
memberに、Appが起動するエージェントへの許可が必要です。許可がない場合、POST /projects/:id/sessionsは403 no_agent_accessを返します。ビューアーには、より広いロールではなくエージェントを付与してください。 - ビューアーを持つユーザー。 Beeuniログイン、またはそのユーザーとしてサインインさせるアクセスリンクでAppを開いた人はビューアーを持ちます。
passwordAppにはビューアーがありません。パスワードは共有シークレットを証明するものであり、個人を証明するものではないためです。publicAppがビューアーを持つのは、アクセスリンク経由で訪れたユーザーに対してのみです。URLを直接開いた場合は匿名のままです。 - これは認証情報です。 BeeuniはAppのアクセスログからリクエストヘッダーを除外します(
beeuni apps logs)。独自コードでこのヘッダーをログに記録しないでください。
独自ドメイン(*.apps.beeuni.aiではない)で提供されるAppの前にはゲートがありません。そこで代わりにSign in with Beeuniを使用してください。
すべてのランタイムで同じメンバー
Beeuniは同じユーザーを3か所で保証します。ブラウザーはゲートに問い合わせ(/_beeuni/viewer)、Appのサーバーは署名済みヘッダーを読み取り、サインイントークンを検証するApp(Convexバックエンドまたは独自サーバー)はBeeuni署名済みトークンを受け取ります。readBeeuniMemberはこれらの形式を1つのBeeuniMemberに変換し、requireBeeuniMemberが続行できるユーザーを決定します。フレームワークも依存関係も必要ありません。同じコードがブラウザー、Node、Bun、Worker、Convex関数で動作します。
import {
readBeeuniMember, requireBeeuniMember, verifyBeeuniToken,
beeuniToken, beeuniBinding, fetchBeeuniAppViewer, BeeuniMemberError,
} from '@beeuni/sdk';
// Browser: who is looking (null when nobody).
const me = readBeeuniMember(await fetchBeeuniAppViewer());
// Browser → an App this App uses, on the same origin: its URL and a token fetcher.
const db = beeuniBinding('db');
const convex = new ConvexReactClient(db.url);
convex.setAuth(db.token);
// React: read the member with useQuery(api.members.me), never useConvexAuth() (see below).
// Browser: a token for this App itself, or for an App it uses.
const token = await beeuniToken()();
const dbToken = beeuniToken({ audience: 'db' });
// A Convex function: the runtime verified the token; enforce the rules.
const member = requireBeeuniMember(await ctx.auth.getUserIdentity(), { groups: ['Finance'] });
// A server App: verify the bearer. No options = the BEEUNI_AUTH_* env Beeuni injects.
// Any other server: pass the App's `auth` values.
const caller = await verifyBeeuniToken(bearer, { issuer: app.auth.issuer, audience: app.auth.audience, jwks: app.auth.jwks_uri });
ReactではuseConvexAuth()を呼び出さないでください。通常のConvexProviderでは例外が発生します(ConvexProviderWithAuthが必要であり、beeuniBindingはそのプロバイダーで検証されません)。クエリでサインイン済みのメンバーを読み取ります。
// convex/members.ts
export const me = query({ args: {}, handler: async (ctx) => readBeeuniMember(await ctx.auth.getUserIdentity()) });
const me = useQuery(api.members.me); // undefined: loading. null: signed out. Otherwise the member.
readBeeuniMemberは匿名の呼び出し元に対してnullを返します。関数で例外を発生させる必要がある場合にのみrequireBeeuniMemberを使用してください。
| フィールド | 値 |
|---|---|
userId |
BeeuniユーザーID。安定しているため、これをキーに行を作成します。 |
email、name、picture |
メンバーのプロフィールから取得します。未知の場合はnullです。 |
groups、groupIds |
このアカウントにおけるメンバーのBeeuniグループ(名前とID)。 |
role |
アカウントロール:owner、admin、またはmember。 |
accountId、projectId |
Appの配置場所。 |
requireBeeuniMember(claims, { groups, roles })はメンバーを返すか、code unauthenticated(ユーザーがいない、またはチェックに失敗したトークン)の場合はBeeuniMemberErrorを、forbidden(グループまたはロールの範囲外)の場合は例外を発生させます。各リストは「いずれか1つ」を意味します。グループは名前またはIDで一致し、空のリストでは誰も許可されません。
サインイントークン
プロジェクトごとに1つの発行者がすべてのAppのトークンに署名します。iss =
<API origin>/v1/projects/<projectId>、aud = トークンの対象となるAppのIDで、有効期間は15分です。キーセットは<issuer>/jwks.jsonで公開され、<issuer>/.well-known/openid-configurationによって名前が付けられます。すべてのAppには、トークンの検証に必要なapp.auth.issuer、app.auth.audience、app.auth.jwks_uriが含まれます。トークンにはsub、email、name、picture、groups、group_ids、role、account_id、project_idが含まれます。エージェントのトークンにはkind: 'agent'が含まれ、グループやロールは含まれません。
| 関数 | 機能 |
|---|---|
beeuniToken({ audience? }) |
App 自体(デフォルト)またはその App が使用する App のトークン取得関数(リアルタイムクライアントが認証コールバックとして受け取る形式)。App 自身のオリジンで /_beeuni/token を読み取ります。有効期限の少し前までキャッシュされ、{ forceRefreshToken: true } で再取得します。サインイン済みの閲覧者がいない場合は null を返し、使用していない App の場合は null を返してコンソール警告を1回出力します(403 app_not_linked)。例外はスローしません。 |
beeuniBinding(slug, { origin? }) |
この App が使用する App の { url, token }:url = <origin>/_beeuni/apps/<slug>(HTTP と WebSocket、同一オリジン、CORS なし)、token = beeuniToken({ audience: slug })。 |
verifyBeeuniToken(bearer, { issuer?, audience?, jwks? }) |
ES256 署名、有効期限(60秒の時刻ずれを許容)、発行者、対象を検証します。デフォルト:BEEUNI_AUTH_ISSUER、BEEUNI_AUTH_AUDIENCE、BEEUNI_AUTH_JWKS。Beeuni はすべてのサーバー App のランタイムにこの3つを注入します(発行者:プロジェクト、対象:App 自身の ID、鍵セット:data: URI)。そのため、そこでは verifyBeeuniToken(bearer) がオプションなしで動作します。サーバー App の env に BEEUNI_AUTH_* を設定しないでください。BEEUNI_ プレフィックスは予約済みで、beeuni validate が拒否します。鍵セットはオブジェクト、JSON テキスト、data: URI、または https URL(auth.jwks_uri、プロセスごとに1回取得)です。鍵セットや対象がない場合を含め、すべての失敗で BeeuniMemberError をスローします。1つのプロジェクト鍵がすべての App のトークンに署名するため、別の App のトークンを排除するのは aud です。audience: false はプロジェクト内のどの App のトークンでも受け入れます。 |
apps.token(appId) |
API から取得する、呼び出し元を示すトークン(POST …/apps/:id/token)。エージェントセッションには、そのエージェントを示すトークンが渡されます。 |
プロジェクトまたはグループから削除したメンバーは、トークンの有効期間内にアクセスを失います。App のアクセスゲートは、次のページ読み込み時にそのメンバーを拒否します。Beeuni のグループには Enterprise プランが必要です。ロールはすべてのプランで利用できます。
アクセスの管理
await apps.access.update(app.app_id, {
mode: 'restricted',
member_ids: [memberId],
group_ids: [groupId],
});
const preview = await apps.access.session(app.app_id);
window.open(preview.url); // valid for five minutes
AppAccessConfig は password_configured を返し、パスワードやそのハッシュは返しません。{ mode: 'password', password } でパスワードを設定します。更新のたびに revision が増加し、既存の App Cookie が無効になります。
型
以下のすべての型は @beeuni/sdk からエクスポートされます。
| 型 | 保持する内容 |
|---|---|
App |
ID 情報、capabilities、labels、scale、services(AppServiceSummary[])、machines(total、running、stopped)、volumes(count、size_gb)、config(稼働中のリリースの AppReleaseConfig、または null)、uses、used_by、auth、url、access_mode、access_revision、desired_state、active_deployment_id、hosting_type(アクティブなリリースの値:static、sandbox、または初回デプロイ前は null)、machine(稼働中のリリースの配信グループのサイズ)、idle_timeout_seconds、always_on、retained_deployments、warnings(作成と更新のみ。常に [])、last_request_at、viewer_can_access |
AppCapability |
'deployments' | 'rollback' | 'preview' | 'sleep' | 'static' | 'member_tokens' | 'machines' | 'scale' | 'exec' | 'volumes' | 'snapshots' |
AppReleaseConfig · AppReleaseConfigInput |
解決済みのリリース設定(processes、services、mounts、vm、deploy) · 記述時の形式(processes、http_service、services、mounts、vm、deploy、および省略形の port、command、readiness_path、always_on、resources) |
AppServiceConfig · AppServiceAccess · AppAutoStop · AppCheck |
name、internal_port、protocol、access、auto_stop_machines、auto_start_machines、min_machines_running、processes、checks · 'public' | 'app' | 'admins' · 'off' | 'stop' | 'suspend' · type、port、path、interval_s、timeout_s、grace_period_s |
AppMountConfig · AppVmConfig · AppDeployStrategy |
source、destination、initial_size_gb、snapshot_retention_days、processes · cpu、memory_gb、disk_gb、processes · 'rolling' | 'immediate' | 'bluegreen' | 'canary' |
AppServiceSummary |
name、protocol、access、internal_port、processes、url(ホストラベルが長すぎる場合は null) |
AppMachine · AppMachineState |
machine_id、instance_id、process_group、state、release(deployment_id、version)、vm、volume、created_at、started_at、stopped_at · 'started' | 'starting' | 'stopped' | 'stopping' | 'provisioning' |
AppVolume · AppVolumeSnapshot · CreateAppVolumeInput |
volume_id、name、process_group、machine_id、size_gb、status、attached、snapshot_retention_days、created_at · snapshot_id、kind(daily、manual、final)、commit_id、created_at · name、size_gb、process_group、snapshot_retention_days |
AppExecInput · AppExecResult |
command、machine_id、timeout_s · machine_id、exit_code、stdout、stderr、truncated |
AppAuth |
issuer、audience(App の ID)、jwks_uri:App のサインイントークンを検証する公開値 |
AppToken |
token、expires_at |
BeeuniTokenOptions · BeeuniTokenFetcher · BeeuniBinding · VerifyBeeuniTokenOptions |
上記のサインイントークンのヘルパー |
AppDeployment |
version、status、source_kind、hosting_type(static:ストレージから配信、ランタイムなし。sandbox:マシン)、hosting_provider、runtime_spec、build_spec、error_code、attempt_count、created_by、actor_type、source_session_id、config(リリースの解決済み設定)、strategy、release_command_status(running、succeeded、failed または null) |
AppRollback |
apps.rollback が解決時に返す値:App と、deployment 内のキューに入ったリリース |
AppDeploymentDetail |
1つの deployment とその events:ライフサイクルイベントとビルドログ(build_log 行、およびビルドの出力が5,000行を超えた場合に1つの log_truncated イベント) |
AppAccessConfig |
mode、revision、member_ids、group_ids、password_configured |
AppAccessMode |
'private' | 'project' | 'restricted' | 'public' | 'password' |
AppSource |
StaticAppSource | BundleAppSource | DockerfileAppSource | OciImageAppSource |
AppArtifact |
kind、status、sha256、size_bytes、image_reference |
AppLogEntry · AppLogsResponse |
ログ1行、および1ページと next_cursor |
apps.list() と useProjectApps は、各 App のアクセスポリシーに従って呼び出し元が開ける App のみを返します(private:作成者、restricted:指定されたメンバーとグループ、project、password、public:プロジェクトチーム)。プロジェクトマネージャーにはすべての App が表示されるため、作成者が離脱しても private の App を管理できます。したがって、一覧にあるすべての App で viewer_can_access は true です。呼び出し元が開けない App の get は、403 ではなく 404 を返します。undefined は拒否ではなく不明として扱ってください。
AppAccessMode はロールモデルに追加されるリソースごとの可視性設定であり、ロールではありません。restricted はユーザーとグループを指定します。これらはロールモデルで使用するものと同じプリンシパルの種類です。
アカウントとアクセス を参照してください。
DockerfileAppSource と OciImageAppSource には command が必要です。port はプライマリサービスのポートです。省略できるのはワーカーのみのリリース(processes があり、サービスのない config)の場合だけです。StaticAppSource と BundleAppSource はどちらも受け取りません。
React フック
@beeuni/sdk/react は App 用の7つのフックをエクスポートします。
useProjectApps(projectId)
呼び出し元が開ける App と、そのライフサイクルのミューテーション。すべてのミューテーションは、成功時にインベントリを無効化します。
import { useProjectApps } from '@beeuni/sdk/react';
function AppList({ projectId }: { projectId: string }) {
const apps = useProjectApps(projectId);
if (!apps.data) return null;
return (
<ul>
{apps.data.map((app) => (
<li key={app.app_id}>
<a href={app.url}>{app.slug}</a>
<button onClick={() => apps.stop.mutate(app.app_id)}>Stop</button>
</li>
))}
</ul>
);
}
クエリフィールドに加えて、create、update、start、stop、remove を返します。
useAppDeployments(projectId, appId)
不変のデプロイ履歴。実行中のビルドの進行が自動で反映されるよう、5秒ごとに再取得します。
const deployments = useAppDeployments(projectId, appId);
await deployments.deploy.mutateAsync({
artifact_id: artifact.artifact_id,
source: { kind: 'static', spa: true },
});
await deployments.rollback.mutateAsync(previousDeploymentId);
両方のミューテーションが、デプロイ一覧と App インベントリを無効化します。
useAppDeployment(projectId, appId, deploymentId)
1つのデプロイとそのイベント。ビルドログも含みます(AppDeploymentDetail)。デプロイが ready、failed、または cancelled になるまで、2秒ごとにポーリングします。待機状態を維持するには、deploymentId として null を渡します。
const detail = useAppDeployment(projectId, appId, deploymentId);
const log = (detail.data?.events ?? [])
.map((event) => (event.type === 'build_log' ? event.message : `[${event.type}] ${event.message}`))
.join('\n');
ビルドでは、最初の2,500行と最後の2,500行が保持されます。行はバッチで書き込まれ、 出力されてから最大1秒以内に書き込まれます。失敗またはキャンセルされたデプロイの ビルドログは14日間保持されます。
useAppAccess(projectId, appId, options?)
アクセスポリシーと短期間有効なアクセスセッション。それぞれ独立した クエリで、どちらも省略可能です。
const access = useAppAccess(projectId, appId, {
policy: canEditAccess,
session: app.viewer_can_access,
});
access.policy.data; // AppAccessConfig
access.session.data; // { url, expires_at }
await access.update.mutateAsync({ mode: 'project' });
| オプション | デフォルト | falseを使う場合 |
|---|---|---|
policy |
true |
画面でAppのプレビューのみを表示する場合。GET …/accessは管理用の読み取りであり、プロジェクト管理者権限のない呼び出し元には403を返します。 |
session |
true |
呼び出し元がAppを開けない可能性がある場合。app.viewer_can_accessを渡します(一覧に表示されるAppでは常にtrue)。 |
Appのグリッドで両方のオプションをtrueのままにすると、Appごとにポリシーの読み取りと
セッションの発行が1回ずつ実行され、そのAppを開けないメンバーに対しては、
それぞれ403になります。
useAppMachines(projectId, appId)
5秒ごとにポーリングされるAppのマシンに加え、start、stop、restart、
destroy(それぞれmachineIdを受け取る)、scale(
{ [group]: count }を受け取る)を提供します。各ミューテーションはマシンとAppの
インベントリを更新します。appMachinesKey(projectId, appId)がクエリキーです。
const machines = useAppMachines(projectId, app.app_id);
await machines.scale.mutateAsync({ worker: 2 });
await machines.restart.mutateAsync(machineId);
useAppVolumes(projectId, appId)
Appのボリュームに加え、snapshot(volumeId)、restore
({ volumeId, snapshotId })、fork({ volumeId, name, snapshotId? })、
extend({ volumeId, sizeGb })、destroy({ volumeId, name })を提供します。各
ミューテーションはボリュームを更新します。appVolumesKey(projectId, appId)がクエリ
キーです。
const volumes = useAppVolumes(projectId, app.app_id);
await volumes.snapshot.mutateAsync(volumeId);
await volumes.restore.mutateAsync({ volumeId, snapshotId });
useAppVolumeSnapshots(projectId, appId, volumeId)
1つのボリュームのスナップショット。実行しない状態に保つには、volumeIdとしてnullを渡します。
エラー
import { featureDisabledKey, isFeatureDisabledError } from '@beeuni/sdk';
try {
await beeuni.project(projectId).apps.list();
} catch (error) {
if (isFeatureDisabledError(error)) {
console.log(`${featureDisabledKey(error)} is off for this project`);
}
}
処理すべきその他の応答:
- slugの重複には
409、アカウントがApp数の上限に達している場合はapp_quota_exceededを伴う402、スペックが範囲外の場合はapp_machine_out_of_rangeを伴う400。 uses内の不明なslugには400 app_not_found(slugs)。- Appが提供していない呼び出しには
409 app_capability_unsupported(capability)。 - デプロイ:
400 app_config_invalid(issues)、400 app_volumes_unsupported。 ロールバック:409 release_not_ready。 - マシンとexec:
409 app_no_machines、404 machine_not_found、409 machine_not_running、400 process_group_unknown、400 scale_out_of_range、400 command_required。 - ボリューム:
400 volume_name_invalid、volume_size_invalid、volume_shrink_refused、volume_retention_invalid、snapshot_invalid、confirmation_required;404 volume_not_found;409 volume_attached。
token()はCache-Control: no-storeで応答します。