コンテンツにスキップ
BeeuniBeeuni
日本語
Esc
↑↓移動↵開く⌘Jプレビュー
このページの内容

アプリ

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を開いた人はビューアーを持ちます。password Appにはビューアーがありません。パスワードは共有シークレットを証明するものであり、個人を証明するものではないためです。public Appがビューアーを持つのは、アクセスリンク経由で訪れたユーザーに対してのみです。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で応答します。

このページは役に立ちましたか?