Saltar para o conteúdo
BeeuniBeeuni
Português
Esc
↑↓navegar↵abrir⌘Jpré-visualizar
Nesta página

Apps

Crie, implante e controle Beeuni Apps pelo SDK e pelo React.

Apps são implantações no escopo do projeto, cada uma com uma URL estável, releases imutáveis, máquinas por grupo de processos e volumes persistentes. Use app.capabilities para definir o fluxo. Esta página aborda a interface do SDK: a fachada apps em um identificador de projeto, as chamadas de artefatos, releases, máquinas e volumes, tokens de login, os tipos exportados e os hooks do React.

Para saber sobre o modelo, a configuração de release, os tipos de origem, a CLI, a URL estável e o comportamento de ativação a frio, leia Apps.

const apps = beeuni.project(projectId).apps;

A fachada apps

Método Encapsula O que faz
apps.list() GET /projects/:pid/apps Lista os Apps que o chamador pode abrir (um gerente de projeto vê todos)
apps.create(input) POST …/apps Cria um App e atribui sua URL estável
apps.get(appId) GET …/apps/:id Lê um App
apps.update(appId, input) PATCH …/apps/:id Renomeia ou altera a máquina, o tempo limite de inatividade, o modo de execução (always_on), uses ou labels. O modo de execução é aplicado em até 5 minutos; uma alteração de máquina é aplicada ao próximo release
apps.remove(appId) DELETE …/apps/:id Exclui o App, suas máquinas e todas as imagens de release; retorna images: {released, pending}. Seus volumes mantêm um snapshot final por 7 dias
apps.start(appId) POST …/apps/:id/start Define desired_state como running e aquece o ambiente de execução. Um App estático responde 409 static_app_no_runtime
apps.stop(appId) POST …/apps/:id/stop Suspende o processamento imediatamente; a próxima requisição o retoma. Um App estático responde 409 static_app_no_runtime: ele não tem ambiente de execução e atende enquanto houver uma implantação ativa
apps.rollback(appId, deploymentId) POST …/apps/:id/rollback Cria um NOVO release a partir do artefato e da configuração de um release pronto e o disponibiliza como uma implantação (202). Os volumes mantêm seus dados

Artefatos são a entrada imutável de uma implantação:

Método Encapsula O que faz
apps.artifacts.register(input) POST …/apps/artifacts Registra um archive ou um oci_image; retorna a URL de upload para um arquivo compactado
apps.artifacts.uploadArchive(bytes, options?) — Registra, envia, calcula o hash e finaliza um .tar.gz em uma única chamada
apps.artifacts.finalize(artifactId, input) POST …/apps/artifacts/:id/finalize Confirma sha256 e size_bytes para um upload manual

Releases (implantações) são imutáveis e numerados:

Método Encapsula O que faz
apps.deployments.create(appId, input) POST …/apps/:id/deployments Inicia um release a partir de um artefato, uma origem e um config opcional do release
apps.deployments.list(appId) GET …/apps/:id/deployments Lista o histórico de implantações
apps.deployments.get(appId, deploymentId) GET …/deployments/:did Lê uma implantação e seus eventos
apps.deployments.logs(appId, deploymentId, options?) GET …/deployments/:did/logs Lê logs de execução com um cursor
apps.deployments.remove(appId, deploymentId) DELETE …/deployments/:did Exclui uma implantação e sua imagem; 409 para a implantação em produção ou uma compilação em andamento. Retorna image: 'released' | 'pending' | 'none'

Máquinas, escala e exec (um App servidor; um App sem máquinas responde 409 app_no_machines):

Método Encapsula O que faz
apps.machines.list(appId) GET …/apps/:id/machines Todas as máquinas: machine_id, process_group, state, release, vm, volume
apps.machines.start/stop/restart(appId, machineId) POST …/machines/:mid/{start,stop,restart} Atua em uma máquina; resolve com a lista de máquinas
apps.machines.destroy(appId, machineId) DELETE …/machines/:mid Destrói uma máquina. Seu volume permanece; seu grupo executa uma máquina a menos
apps.scale(appId, count) PUT …/apps/:id/scale Máquinas por grupo de processos ({ worker: 3 }, 0–20), aplicado em segundo plano (202). Resolve com scale e machines
apps.exec(appId, { command, machine_id?, timeout_s? }) POST …/apps/:id/exec Executa um comando em uma máquina em execução (project.app.admin; auditado). timeout_s 1–300, padrão 60. Resolve com exit_code, stdout, stderr (1 MiB cada), truncated

Volumes (discos persistentes):

Método Encapsula O que faz
apps.volumes.list(appId) GET …/apps/:id/volumes Todos os 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 Cria um volume antecipadamente; uma máquina posterior do seu grupo com uma montagem desse nome o utiliza
apps.volumes.update(appId, volumeId, { size_gb?, snapshot_retention_days? }) PATCH …/volumes/:vid Expande um volume (nunca reduz; no máximo 100 GB) ou altera sua retenção (1–60 dias)
apps.volumes.destroy(appId, volumeId, confirmName) DELETE …/volumes/:vid?confirm=<name> project.app.admin. Recusado com 409 volume_attached enquanto uma máquina o mantém montado. Mantém um snapshot final por 7 dias
apps.volumes.snapshots(appId, volumeId) GET …/volumes/:vid/snapshots Snapshots daily, manual e final
apps.volumes.snapshot(appId, volumeId) POST …/volumes/:vid/snapshots Um snapshot manual agora
apps.volumes.restore(appId, volumeId, snapshotId) POST …/volumes/:vid/restore project.app.admin. A máquina para, o volume retorna ao snapshot e a máquina inicia nele (202)
apps.volumes.fork(appId, volumeId, { name, snapshot_id? }) POST …/volumes/:vid/fork Um novo volume não anexado no mesmo App, a partir de um snapshot ou do conteúdo atual

Tokens de login (capacidade member_tokens; 409 app_capability_unsupported quando o App não a tem):

Método Encapsula O que faz
apps.token(appId) POST …/apps/:id/token Um token de login de 15 minutos para o App, identificando o chamador

O acesso é a política de autorização do próprio App:

Método Encapsula O que faz
apps.access.get(appId) GET …/apps/:id/access Lê a política. Exige project.app.read
apps.access.update(appId, input) PATCH …/apps/:id/access Substitui a política e incrementa sua revisão
apps.access.session(appId) POST …/apps/:id/access-session Gera uma URL de cinco minutos que é trocada por um cookie restrito ao host

Implante um site estático

uploadArchive realiza todo o processo de negociação do artefato: registra o artefato, verifica-o em relação a max_bytes, envia os bytes via PUT, calcula o SHA-256 e finaliza.

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 também aceita os campos de máquina: cpu, memory_gb, disk_gb, idle_timeout_seconds e always_on. Omita-os para usar os valores padrão. Apps não têm orçamento mensal: os créditos da conta pagam pelo processamento. Um App servidor sempre ativo é cobrado 24 horas por dia, 7 dias por semana; um sob demanda, apenas enquanto estiver ativo. always_on: true executa um App servidor 24 horas por dia, 7 dias por semana (websockets, loops em segundo plano); false o interrompe após idle_timeout_seconds e o ativa na próxima requisição. Um App estático não tem ambiente de execução: always_on é false.

A API ignora monthly_budget_usd se um cliente antigo o enviar (200, sem erro). Nenhuma resposta de App o inclui.

uses lista os Apps, por slug, para os quais este App pode obter tokens de login e acessar por meio da montagem de bindings; o padrão é nenhum, e update(appId, { uses }) substitui a lista (consulte Apps que um App usa).

create e update retornam warnings, que é sempre [].

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)

Aguarde a implantação consultando seu status periodicamente:

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));
  }
}

Os valores de status são queued, validating, building, provisioning, checking, ready, failed e cancelled.

Implante uma imagem OCI

Registre a referência imutável da imagem e declare o comando do processo e a porta de destino pública:

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 retorna upload: null para um oci_image. Apenas um archive recebe uma URL de upload.

CreateAppDeploymentInput também aceita environment (valores de execução não secretos), secrets (chave de execução para nome de segredo do projeto) e config (AppReleaseConfigInput, a configuração do release). Um config inválido responde 400 app_config_invalid com issues: [{ path, message }]; uma montagem em um ambiente de execução sem volumes responde 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' },
  },
});

Leia os logs de execução

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;
}

Cada entrada contém cursor, time, source (app, appd, caddy) e line. Uma linha caddy é o log de acesso: método, URI, status, duração e cabeçalhos de resposta. Ela nunca contém cabeçalhos de requisição, pois eles contêm as credenciais do gateway (o token e a identidade do visitante, o token de entrada do provedor).

Máquinas, escala e volumes

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' });

Uma máquina mantém seu machine_id entre versões; cada versão substitui sua box (instance_id). Uma máquina monta um volume por vez. Uma restauração é uma nova versão do volume: o conteúdo substituído permanece no histórico dele. Um snapshot de um volume montado pode não incluir o último ~1 s de gravações.

Um backend Convex

Um backend Convex é um App de servidor comum com um volume, criado a partir do modelo convex (beeuni apps launch --template convex --name db; veja Modelo convex). O SDK não tem nenhuma chamada específica para Convex. Leia a chave de administrador da máquina com exec e dê a um App de interface acesso ao backend com uses:

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 contém as URLs do backend: http (a URL do cliente Convex) e site (ações HTTP).

Seu App já sabe quem está acessando

Um App hospedado no Beeuni é aberto por alguém que já fez login no Beeuni. O gateway de Apps autentica essa pessoa antes de servir o primeiro byte, então seu App não precisa de um login próprio — sem segunda senha, sem tela de consentimento, sem redirecionamento.

No navegador:

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>;
}

O navegador chama /_beeuni/api/v1/* na própria origem do seu App, nunca https://api.beeuni.ai/v1 diretamente: a API não responde a preflight CORS de uma origem de App, então uma chamada direta falha no navegador. O gateway encaminha cada chamada à API do Beeuni como o visitante autenticado, com o token do visitante restrito ao App. Regras:

  • Ela precisa de viewer_token_scope: 'api'. Caso contrário, responde 403 viewer_api_disabled.
  • Ela atua apenas para um visitante autenticado pelo gateway (login do Beeuni ou um link de acesso). Sem um, responde 401 no_viewer_identity.
  • Ela responde apenas a requisições da própria origem do App. Uma requisição de outro site recebe 403 cross_site_request.
  • Ela encaminha /v1/*, exceto /v1/oauth/* e endpoints MCP, que respondem 404. O cookie do seu App e seu próprio cabeçalho Authorization nunca chegam à API, e a API não pode definir um cookie no seu App.

No servidor do seu App, o gateway inclui a identidade assinada em cada requisição:

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 verifica um HMAC do cabeçalho com BEEUNI_APP_VIEWER_SECRET, que o Beeuni injeta no seu App na implantação. Um cabeçalho falsificado nunca passa: o gateway exclui qualquer cópia enviada pelo cliente antes de encaminhar, e a assinatura é feita com um segredo derivado para cada App.

Quanto seu App recebe de informação

Uma configuração na política de acesso do App — Settings → Access no Beeuni, beeuni apps access <app> --viewer off|identity|api ou viewer_token_scope em PATCH /projects/:id/apps/:appId/access:

Escopo Seu App recebe
identity (padrão) O id, email, nome, foto, função na conta e grupos do Beeuni do visitante (ids e nomes, apenas na conta deste App), além de um token profile email. O suficiente para mostrar a cada pessoa seus próprios dados e controlar o acesso por grupo.
api Tudo acima e um token que atua como essa pessoa na API do Beeuni — limitado pela função dela.
off Nada.

O token nunca é a sessão do usuário no Beeuni: ele dura uma hora, contém apenas esses escopos, e todo token emitido por um App deixa de valer quando o App é excluído ou sua política de acesso muda.

Como usar o token do visitante

  • No seu servidor, leia-o a cada requisição. O gateway coloca um token válido em x-beeuni-app-viewer-token em cada requisição encaminhada pelo proxy. Envie-o como Authorization: Bearer para a API do Beeuni — createAppViewerBeeuni(request) faz exatamente isso. Crie um cliente por requisição; não armazene o token.
  • Após uma mudança na política de acesso, os tokens antigos param de funcionar. A próxima requisição pelo gateway contém um novo token. No navegador, beeuniAppViewerToken() se recupera sozinho: quando a API recusa seu token, o SDK relê /_beeuni/viewer e repete a chamada uma vez.
  • A função do visitante é o limite. O token pode fazer exatamente o que o visitante pode fazer no Beeuni, nunca mais. Em um projeto com permissões de agente, um member precisa de uma concessão de acesso ao agente que seu App inicia: sem ela, POST /projects/:id/sessions responde 403 no_agent_access. Conceda aos seus visitantes acesso ao agente, não uma função mais ampla.
  • Quem tem um visitante. Uma pessoa que abre o App com seu login do Beeuni — ou com um link de acesso, que a autentica como essa pessoa — tem um. Um App password não tem nenhum: a senha comprova um segredo compartilhado, não uma pessoa. Um App public tem um apenas para um visitante que chegou por um link de acesso; a URL direta continua anônima.
  • É uma credencial. O Beeuni omite os cabeçalhos de requisição do log de acesso do App (beeuni apps logs). Não registre o cabeçalho no seu próprio código.

Um App servido em seu próprio domínio (não *.apps.beeuni.ai) não tem um gateway à frente — use Entrar com Beeuni nesse caso.

Um membro, em todos os ambientes de execução

O Beeuni atesta a mesma pessoa em três lugares: o navegador consulta o gateway (/_beeuni/viewer), o servidor do App lê o cabeçalho assinado, e um App que verifica tokens de login (um backend Convex ou seu próprio servidor) recebe um token assinado pelo Beeuni. readBeeuniMember transforma cada um desses formatos em um BeeuniMember, e requireBeeuniMember decide quem pode prosseguir. Sem framework e sem dependência: o mesmo código roda em um navegador, Node, Bun, um Worker e uma função 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 });

No React, não chame useConvexAuth(): ele lança uma exceção sob um ConvexProvider simples (precisa de ConvexProviderWithAuth, e beeuniBinding não é verificado com esse provedor). Leia o membro autenticado com uma consulta:

// 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 retorna null para um chamador anônimo. Use requireBeeuniMember apenas onde a função deve lançar uma exceção.

Campo Valor
userId Id de usuário do Beeuni. Estável; use-o como chave das linhas.
email, name, picture Do perfil do membro. null quando desconhecido.
groups, groupIds Os grupos do Beeuni do membro nesta conta, por nome e por id.
role Função na conta: owner, admin ou member.
accountId, projectId Onde o App reside.

requireBeeuniMember(claims, { groups, roles }) retorna o membro ou lança BeeuniMemberError com code unauthenticated (ninguém, ou um token que falhou em uma verificação) ou forbidden (fora dos grupos ou funções). Cada lista significa “qualquer um destes”; um grupo corresponde por nome ou id; uma lista vazia não admite ninguém.

Tokens de login

Um emissor por projeto assina os tokens de todos os Apps: iss = <API origin>/v1/projects/<projectId>, aud = o id do App ao qual o token se destina, 15 minutos. Seu conjunto de chaves é público em <issuer>/jwks.json, identificado por <issuer>/.well-known/openid-configuration. Cada App contém os valores que verificam seus tokens: app.auth.issuer, app.auth.audience, app.auth.jwks_uri. Um token contém sub, email, name, picture, groups, group_ids, role, account_id e project_id; o token de um agente contém kind: 'agent' e nenhum grupo ou função.

Função O que faz
beeuniToken({ audience? }) Um buscador de tokens (o formato que os clientes em tempo real usam como callback de autenticação) para o próprio App (padrão) ou um App que ele usa. Lê /_beeuni/token na origem do próprio App. Armazenado em cache até pouco antes da expiração; { forceRefreshToken: true } solicita novamente. Retorna null sem um usuário conectado, e null mais um aviso no console para um App que ele não usa (403 app_not_linked). Nunca lança exceções.
beeuniBinding(slug, { origin? }) { url, token } para um App que este App usa: url = <origin>/_beeuni/apps/<slug> (HTTP e WebSocket, mesma origem, sem CORS), token = beeuniToken({ audience: slug }).
verifyBeeuniToken(bearer, { issuer?, audience?, jwks? }) Verifica a assinatura ES256, a expiração (tolerância de 60 s), o emissor e o público-alvo. Padrões: BEEUNI_AUTH_ISSUER, BEEUNI_AUTH_AUDIENCE, BEEUNI_AUTH_JWKS. Beeuni injeta os três no ambiente de execução de cada App de servidor (emissor: o projeto; público-alvo: o id do próprio App; conjunto de chaves: um URI data:), portanto verifyBeeuniToken(bearer) funciona ali sem opções. Nunca defina BEEUNI_AUTH_* no env de um App de servidor: o prefixo BEEUNI_ é reservado e beeuni validate o rejeita. O conjunto de chaves é um objeto, texto JSON, um URI data: ou uma URL https (auth.jwks_uri, buscada uma vez por processo). Toda falha lança BeeuniMemberError, inclusive a ausência de um conjunto de chaves ou de um público-alvo: uma única chave do projeto assina os tokens de todos os Apps, então aud é o que impede a entrada do token de outro App. audience: false aceita um token para qualquer App do projeto.
apps.token(appId) Um token que identifica o chamador, obtido da API (POST …/apps/:id/token). Uma sessão de agente recebe um que identifica seu agente.

Um membro que você remove do projeto ou de um grupo perde o acesso em até um período de validade do token; o controle de acesso do App o rejeita no próximo carregamento de página. Os grupos do Beeuni exigem o plano Enterprise; as funções de acesso funcionam em todos os planos.

Gerenciar acesso

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 informa password_configured, nunca a senha nem seu hash. Defina uma senha com { mode: 'password', password }. Cada atualização incrementa revision, o que revoga os cookies existentes do App.

Tipos

Todos os tipos abaixo são exportados de @beeuni/sdk.

Tipo O que contém
App Identidade, capabilities, labels, scale, services (AppServiceSummary[]), machines (total, running, stopped), volumes (count, size_gb), config (o AppReleaseConfig da versão em produção, ou null), uses, used_by, auth, url, access_mode, access_revision, desired_state, active_deployment_id, hosting_type (da versão ativa: static, sandbox ou null antes da primeira implantação), machine (o tamanho do grupo de serviço na versão em produção), idle_timeout_seconds, always_on, retained_deployments, warnings (somente criação e atualização; sempre []), last_request_at, viewer_can_access
AppCapability 'deployments' | 'rollback' | 'preview' | 'sleep' | 'static' | 'member_tokens' | 'machines' | 'scale' | 'exec' | 'volumes' | 'snapshots'
AppReleaseConfig · AppReleaseConfigInput A configuração resolvida da versão (processes, services, mounts, vm, deploy) · a forma escrita (processes, http_service, services, mounts, vm, deploy e as formas abreviadas 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 quando o rótulo do host seria longo demais)
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 (o id do App), jwks_uri: os valores públicos que verificam os tokens de login do App
AppToken token, expires_at
BeeuniTokenOptions · BeeuniTokenFetcher · BeeuniBinding · VerifyBeeuniTokenOptions Os auxiliares de tokens de login acima
AppDeployment version, status, source_kind, hosting_type (static: servido a partir do armazenamento, sem ambiente de execução; sandbox: máquinas), hosting_provider, runtime_spec, build_spec, error_code, attempt_count, created_by, actor_type, source_session_id, config (a configuração resolvida da versão), strategy, release_command_status (running, succeeded, failed ou null)
AppRollback O valor com que apps.rollback é resolvido: o App mais a versão na fila em deployment
AppDeploymentDetail Um deployment mais seu events: eventos do ciclo de vida e o log de build (linhas build_log e um evento log_truncated quando o build imprimiu mais de 5.000 linhas)
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 Uma linha de log e uma página mais next_cursor

apps.list() e useProjectApps retornam apenas os Apps que o chamador pode abrir conforme a política de acesso de cada App (privado: o criador; restrito: os membros e grupos listados; projeto, senha e público: a equipe do projeto). Um gerente de projeto vê todos os Apps, então um App privado continua gerenciável quando seu criador sai. Portanto, viewer_can_access é true em todos os Apps listados. Um get de um App que o chamador não pode abrir responde 404, não 403. Trate undefined como desconhecido, não como negado.

AppAccessMode é uma configuração de visibilidade por recurso sobre o modelo de funções de acesso, não uma função de acesso. restricted identifica usuários e grupos — os mesmos tipos de entidades que o modelo de funções de acesso usa. Consulte Contas e acesso.

DockerfileAppSource e OciImageAppSource exigem command. port é a porta do serviço principal: omita-a apenas para uma versão somente com workers (um config com processes e sem serviço). StaticAppSource e BundleAppSource não recebem nenhum dos dois.

Hooks do React

@beeuni/sdk/react exporta sete hooks para Apps.

useProjectApps(projectId)

Os Apps que o chamador pode abrir, mais suas mutações de ciclo de vida. Cada mutação invalida o inventário em caso de sucesso.

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>
  );
}

Retorna os campos da consulta mais create, update, start, stop e remove.

useAppDeployments(projectId, appId)

O histórico imutável de implantações, buscado novamente a cada 5 s para que um build em execução avance por conta própria.

const deployments = useAppDeployments(projectId, appId);

await deployments.deploy.mutateAsync({
  artifact_id: artifact.artifact_id,
  source: { kind: 'static', spa: true },
});
await deployments.rollback.mutateAsync(previousDeploymentId);

Ambas as mutações invalidam a lista de implantações e o inventário de Apps.

useAppDeployment(projectId, appId, deploymentId)

Uma implantação e seus eventos, incluindo o log de build (AppDeploymentDetail). Faz consultas a cada 2 s até que a implantação esteja ready, failed ou cancelled. Passe null como deploymentId para mantê-lo inativo.

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');

Um build mantém suas primeiras 2.500 e suas últimas 2.500 linhas. As linhas são gravadas em lotes, no máximo 1 s após serem impressas. Uma implantação com falha ou cancelada mantém seu log de build por 14 dias.

useAppAccess(projectId, appId, options?)

A política de acesso e uma sessão de acesso de curta duração. As duas partes são consultas separadas, e cada uma é opcional.

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' });
Opção Padrão Use false quando
policy true A interface apenas exibe uma prévia do App. GET …/access é uma leitura administrativa e retorna 403 para um chamador sem permissões de gerente de projeto.
session true O chamador não pode abrir o App. Passe app.viewer_can_access (sempre true para um App listado).

Uma grade de Apps que deixa ambas as opções em true dispara uma leitura de política e uma emissão de sessão por App, e cada uma resulta em 403 para um membro que não pode abrir esse App.

useAppMachines(projectId, appId)

As máquinas de um App, consultadas a cada 5 s, mais start, stop, restart, destroy (cada uma recebe um machineId) e scale (recebe { [group]: count }). Cada mutação atualiza as máquinas e o inventário de Apps. appMachinesKey(projectId, appId) é a chave da consulta.

const machines = useAppMachines(projectId, app.app_id);
await machines.scale.mutateAsync({ worker: 2 });
await machines.restart.mutateAsync(machineId);

useAppVolumes(projectId, appId)

Os volumes de um App, mais snapshot (volumeId), restore ({ volumeId, snapshotId }), fork ({ volumeId, name, snapshotId? }), extend ({ volumeId, sizeGb }) e destroy ({ volumeId, name }). Cada mutação atualiza os volumes. appVolumesKey(projectId, appId) é a chave da consulta.

const volumes = useAppVolumes(projectId, app.app_id);
await volumes.snapshot.mutateAsync(volumeId);
await volumes.restore.mutateAsync({ volumeId, snapshotId });

useAppVolumeSnapshots(projectId, appId, volumeId)

Os snapshots de um volume. Passe null como volumeId para manter a consulta inativa.

Erros

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`);
  }
}

Outras respostas que você deve tratar:

  • 409 para um slug duplicado, 402 com app_quota_exceeded quando a conta está no limite de Apps, e 400 com app_machine_out_of_range para uma especificação fora dos limites.
  • 400 app_not_found (slugs) para um slug desconhecido em uses.
  • 409 app_capability_unsupported (capability) para uma chamada que o App não oferece.
  • Uma implantação: 400 app_config_invalid (issues), 400 app_volumes_unsupported. Uma reversão: 409 release_not_ready.
  • Máquinas e 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.
  • Volumes: 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() responde com Cache-Control: no-store.

Esta página foi útil?