Saltar al contenido
BeeuniBeeuni
Español
Esc
↑↓navegar↵abrir⌘Jvista previa
En esta página

Aplicaciones

Crea, despliega y controla Beeuni Apps desde el SDK y desde React.

Las Apps son despliegues de ámbito de proyecto, cada uno con una URL estable, versiones inmutables, máquinas por grupo de procesos y volúmenes persistentes. Bifurca según app.capabilities. Esta página cubre la interfaz del SDK: la fachada apps en un identificador de proyecto, las llamadas de artefactos, versiones, máquinas y volúmenes, los tokens de inicio de sesión, los tipos exportados y los hooks de React.

Para conocer el modelo, la configuración de versiones, los tipos de origen, la CLI, la URL estable y el comportamiento de activación en frío, consulta Apps.

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

La fachada apps

Método Encapsula Qué hace
apps.list() GET /projects/:pid/apps Enumera las Apps que el solicitante puede abrir (un administrador del proyecto ve todas)
apps.create(input) POST …/apps Crea una App y le asigna su URL estable
apps.get(appId) GET …/apps/:id Lee una App
apps.update(appId, input) PATCH …/apps/:id La renombra o cambia la máquina, el tiempo de inactividad, el modo de ejecución (always_on), uses o labels. El modo de ejecución se aplica en un plazo de 5 minutos; un cambio de máquina se aplica a la siguiente versión
apps.remove(appId) DELETE …/apps/:id Elimina la App, sus máquinas y todas las imágenes de versiones; devuelve images: {released, pending}. Sus volúmenes conservan una instantánea final durante 7 días
apps.start(appId) POST …/apps/:id/start Establece desired_state en running y prepara el entorno de ejecución. Una App estática responde 409 static_app_no_runtime
apps.stop(appId) POST …/apps/:id/stop Suspende el cómputo de inmediato; la siguiente solicitud lo reanuda. Una App estática responde 409 static_app_no_runtime: no tiene entorno de ejecución y sirve contenido mientras tenga un despliegue activo
apps.rollback(appId, deploymentId) POST …/apps/:id/rollback Crea una versión NUEVA a partir del artefacto y la configuración de una versión lista y la publica como un despliegue (202). Los volúmenes conservan sus datos

Los artefactos son la entrada inmutable de un despliegue:

Método Encapsula Qué hace
apps.artifacts.register(input) POST …/apps/artifacts Registra un archive o un oci_image; devuelve la URL de carga para un archivo
apps.artifacts.uploadArchive(bytes, options?) — Registra, carga, calcula el hash y finaliza un .tar.gz en una sola llamada
apps.artifacts.finalize(artifactId, input) POST …/apps/artifacts/:id/finalize Confirma sha256 y size_bytes para una carga manual

Las versiones (despliegues) son inmutables y están numeradas:

Método Encapsula Qué hace
apps.deployments.create(appId, input) POST …/apps/:id/deployments Inicia una versión a partir de un artefacto, un origen y un config de versión opcional
apps.deployments.list(appId) GET …/apps/:id/deployments Enumera el historial de despliegues
apps.deployments.get(appId, deploymentId) GET …/deployments/:did Lee un despliegue junto con sus eventos
apps.deployments.logs(appId, deploymentId, options?) GET …/deployments/:did/logs Lee los registros de ejecución con un cursor
apps.deployments.remove(appId, deploymentId) DELETE …/deployments/:did Elimina un despliegue y su imagen; 409 para el despliegue activo o una compilación en curso. Devuelve image: 'released' | 'pending' | 'none'

Máquinas, escalado y exec (una App de servidor; una App sin máquinas responde 409 app_no_machines):

Método Encapsula Qué hace
apps.machines.list(appId) GET …/apps/:id/machines Todas las máquinas: machine_id, process_group, state, release, vm, volume
apps.machines.start/stop/restart(appId, machineId) POST …/machines/:mid/{start,stop,restart} Actúa sobre una máquina; se resuelve con la lista de máquinas
apps.machines.destroy(appId, machineId) DELETE …/machines/:mid Destruye una máquina. Su volumen se conserva; su grupo ejecuta una máquina menos
apps.scale(appId, count) PUT …/apps/:id/scale Máquinas por grupo de procesos ({ worker: 3 }, 0–20), aplicado en segundo plano (202). Se resuelve con scale y machines
apps.exec(appId, { command, machine_id?, timeout_s? }) POST …/apps/:id/exec Ejecuta un comando en una máquina en ejecución (project.app.admin; auditado). timeout_s 1–300, valor predeterminado 60. Se resuelve con exit_code, stdout, stderr (1 MiB cada uno), truncated

Volúmenes (discos persistentes):

Método Encapsula Qué hace
apps.volumes.list(appId) GET …/apps/:id/volumes Todos los volúmenes: 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 Crea un volumen de antemano; una máquina posterior de su grupo con un montaje de ese nombre lo toma
apps.volumes.update(appId, volumeId, { size_gb?, snapshot_retention_days? }) PATCH …/volumes/:vid Amplía un volumen (nunca lo reduce; máximo 100 GB) o cambia su retención (1–60 días)
apps.volumes.destroy(appId, volumeId, confirmName) DELETE …/volumes/:vid?confirm=<name> project.app.admin. Se rechaza con 409 volume_attached mientras una máquina lo tenga montado. Conserva una instantánea final durante 7 días
apps.volumes.snapshots(appId, volumeId) GET …/volumes/:vid/snapshots Instantáneas daily, manual y final
apps.volumes.snapshot(appId, volumeId) POST …/volumes/:vid/snapshots Una instantánea manual ahora
apps.volumes.restore(appId, volumeId, snapshotId) POST …/volumes/:vid/restore project.app.admin. La máquina se detiene, el volumen vuelve a la instantánea y la máquina arranca con él (202)
apps.volumes.fork(appId, volumeId, { name, snapshot_id? }) POST …/volumes/:vid/fork Un volumen nuevo, sin conectar, en la misma App, a partir de una instantánea o del contenido actual

Tokens de inicio de sesión (capacidad member_tokens; 409 app_capability_unsupported cuando la App carece de ella):

Método Encapsula Qué hace
apps.token(appId) POST …/apps/:id/token Un token de inicio de sesión de 15 minutos para la App, que identifica al solicitante

El acceso es la política de autorización propia de la App:

Método Encapsula Qué hace
apps.access.get(appId) GET …/apps/:id/access Lee la política. Requiere project.app.read
apps.access.update(appId, input) PATCH …/apps/:id/access Sustituye la política e incrementa su revisión
apps.access.session(appId) POST …/apps/:id/access-session Genera una URL de cinco minutos que se canjea por una cookie exclusiva del host

Desplegar un sitio estático

uploadArchive realiza todo el protocolo del artefacto: registra el artefacto, lo verifica frente a max_bytes, envía los bytes mediante PUT, calcula el SHA-256 y 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 también acepta los campos de máquina: cpu, memory_gb, disk_gb, idle_timeout_seconds y always_on. Omítelos para usar los valores predeterminados. Las Apps no tienen presupuesto mensual: los créditos de la cuenta pagan el cómputo. Una App de servidor siempre activa se factura las 24 horas del día; una bajo demanda, solo mientras está activa. always_on: true ejecuta una App de servidor las 24 horas del día (websockets, bucles en segundo plano); false la detiene después de idle_timeout_seconds y la activa con la siguiente solicitud. Una App estática no tiene entorno de ejecución: always_on es false.

La API ignora monthly_budget_usd si un cliente antiguo lo envía (200, sin error). Ninguna respuesta de App lo incluye.

uses enumera, por slug, las Apps para las que esta App puede obtener tokens de inicio de sesión y a las que puede acceder mediante su montaje de enlaces; el valor predeterminado es ninguna, y update(appId, { uses }) sustituye la lista (consulta Apps que usa una App).

create y update devuelven warnings, que siempre es [].

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)

Espera al despliegue consultando periódicamente su estado:

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

Los valores de estado son queued, validating, building, provisioning, checking, ready, failed y cancelled.

Desplegar una imagen OCI

Registra la referencia inmutable de la imagen y luego declara el comando del proceso y el puerto de destino público:

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 devuelve upload: null para un oci_image. Solo un archive obtiene una URL de carga.

CreateAppDeploymentInput también acepta environment (valores de ejecución no secretos), secrets (clave de ejecución a nombre de secreto del proyecto) y config (AppReleaseConfigInput, la configuración de la versión). Un config no válido responde 400 app_config_invalid con issues: [{ path, message }]; un montaje en un entorno de ejecución sin volúmenes 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' },
  },
});

Leer los registros de ejecución

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 contiene cursor, time, source (app, appd, caddy) y line. Una línea caddy es el registro de acceso: método, URI, estado, duración y cabeceras de respuesta. Nunca contiene cabeceras de solicitud, porque estas incluyen las credenciales de la puerta de acceso (el token y la identidad del visitante, el token de entrada del proveedor).

Máquinas, escalado y volúmenes

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

Una máquina conserva su machine_id entre versiones; cada versión sustituye su contenedor (instance_id). Una máquina monta un volumen a la vez. Una restauración es una nueva versión del volumen: el contenido sustituido permanece en su historial. Una instantánea de un volumen montado puede omitir el último ~1 s de escrituras.

Un backend de Convex

Un backend de Convex es una App de servidor normal con un volumen, creada a partir de la plantilla convex (beeuni apps launch --template convex --name db; consulta Plantilla convex). El SDK no tiene ninguna llamada específica de Convex. Lee la clave de administrador de la máquina con exec y da acceso al backend a una App de interfaz con 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 contiene las URL del backend: http (la URL del cliente de Convex) y site (acciones HTTP).

Tu App ya sabe quién la está viendo

Quien abre una App alojada en Beeuni es alguien que ya ha iniciado sesión en Beeuni. La puerta de acceso de Apps autentica a esa persona antes de que se sirva el primer byte, por lo que tu App no necesita un inicio de sesión propio: ni una segunda contraseña, ni una pantalla de consentimiento, ni una redirección.

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

El navegador llama a /_beeuni/api/v1/* en el origen de tu propia App, nunca a https://api.beeuni.ai/v1 directamente: la API no responde a solicitudes de comprobación previa CORS desde el origen de una App, por lo que una llamada directa falla en el navegador. La puerta de acceso reenvía cada llamada a la API de Beeuni como el visitante que ha iniciado sesión, con su token limitado a la App. Reglas:

  • Necesita viewer_token_scope: 'api'. De lo contrario, responde 403 viewer_api_disabled.
  • Actúa solo para un visitante al que la puerta de acceso haya iniciado sesión (mediante el inicio de sesión de Beeuni o un enlace de acceso). Si no hay ninguno, responde 401 no_viewer_identity.
  • Solo responde a solicitudes del origen de la propia App. Una solicitud desde otro sitio recibe 403 cross_site_request.
  • Reenvía /v1/* excepto /v1/oauth/* y los endpoints de MCP, que responden 404. La cookie de tu App y su propia cabecera Authorization nunca llegan a la API, y la API no puede establecer una cookie en tu App.

En el servidor de tu App, la puerta de acceso incluye la identidad firmada en cada solicitud:

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 un HMAC de la cabecera con BEEUNI_APP_VIEWER_SECRET, que Beeuni inyecta en tu App al desplegarla. Una cabecera falsificada nunca supera la verificación: la puerta de acceso elimina cualquier copia proporcionada por el cliente antes de reenviarla, y la firma se crea con un secreto derivado para cada App.

Cuánta información recibe tu App

Un ajuste de la política de acceso de la App: Settings → Access en Beeuni, beeuni apps access <app> --viewer off|identity|api o viewer_token_scope en PATCH /projects/:id/apps/:appId/access:

Ámbito Tu App recibe
identity (predeterminado) El id, correo electrónico, nombre, imagen, rol de cuenta y grupos de Beeuni del visitante (ids y nombres, solo en la cuenta de esta App), además de un token profile email. Suficiente para mostrar a cada persona sus propios datos y controlar el acceso por grupo.
api Lo anterior y un token que actúa como esa persona en la API de Beeuni, limitado por su propio rol.
off Nada.

El token nunca es la sesión de Beeuni del usuario: dura una hora, incluye solo esos ámbitos y todos los tokens emitidos por una App dejan de funcionar cuando se elimina la App o cambia su política de acceso.

Uso del token del visitante

  • En tu servidor, léelo en cada solicitud. La puerta de acceso coloca un token válido en x-beeuni-app-viewer-token en cada solicitud enviada a través del proxy. Envíalo como Authorization: Bearer a la API de Beeuni: createAppViewerBeeuni(request) hace exactamente eso. Crea un cliente por solicitud; no almacenes el token.
  • Tras un cambio en la política de acceso, los tokens antiguos dejan de funcionar. La siguiente solicitud a través de la puerta de acceso lleva uno nuevo. En el navegador, beeuniAppViewerToken() se recupera por sí solo: cuando la API rechaza su token, el SDK vuelve a leer /_beeuni/viewer y repite la llamada una vez.
  • El rol del visitante es el límite. El token puede hacer exactamente lo que el visitante puede hacer en Beeuni, nunca más. En un proyecto con permisos de agente, un member necesita un permiso sobre el agente que inicia tu App: sin él, POST /projects/:id/sessions responde 403 no_agent_access. Concede a tus visitantes acceso al agente, no un rol más amplio.
  • Quién tiene un visitante identificado. Una persona que abre la App con su inicio de sesión de Beeuni, o con un enlace de acceso que inicia sesión como esa persona, lo tiene. Una App password no lo tiene: la contraseña demuestra un secreto compartido, no una identidad personal. Una App public solo lo tiene para un visitante que haya llegado mediante un enlace de acceso; la URL sin más sigue siendo anónima.
  • Es una credencial. Beeuni excluye las cabeceras de solicitud del registro de acceso de la App (beeuni apps logs). No registres la cabecera en tu propio código.

Una App servida en su propio dominio (no en *.apps.beeuni.ai) no tiene una puerta de acceso por delante: usa Iniciar sesión con Beeuni allí en su lugar.

Un miembro, en todos los entornos de ejecución

Beeuni avala a la misma persona en tres lugares: el navegador consulta a la puerta de acceso (/_beeuni/viewer), el servidor de la App lee la cabecera firmada y una App que verifica tokens de inicio de sesión (un backend de Convex o tu propio servidor) recibe un token firmado por Beeuni. readBeeuniMember convierte cada uno de esos formatos en un BeeuniMember y requireBeeuniMember decide quién puede continuar. Sin framework ni dependencias: el mismo código se ejecuta en un navegador, Node, Bun, un Worker y una función de 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 });

En React, no llames a useConvexAuth(): lanza una excepción con un ConvexProvider simple (necesita ConvexProviderWithAuth y beeuniBinding no se verifica con ese proveedor). Lee el miembro que ha iniciado sesión mediante una 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 devuelve null para una llamada anónima. Usa requireBeeuniMember solo cuando la función deba lanzar una excepción.

Campo Valor
userId Id de usuario de Beeuni. Estable; úsalo como clave de las filas.
email, name, picture Del perfil del miembro. null cuando se desconoce.
groups, groupIds Los grupos de Beeuni del miembro en esta cuenta, por nombre y por id.
role Rol de cuenta: owner, admin o member.
accountId, projectId Dónde reside la App.

requireBeeuniMember(claims, { groups, roles }) devuelve el miembro o lanza BeeuniMemberError con code unauthenticated (nadie, o un token que no superó una comprobación) o forbidden (fuera de los grupos o roles). Cada lista significa “cualquiera de estos”; un grupo coincide por nombre o id; una lista vacía no admite a nadie.

Tokens de inicio de sesión

Un emisor por proyecto firma los tokens de todas las Apps: iss = <API origin>/v1/projects/<projectId>, aud = el id de la App a la que está destinado el token, 15 minutos. Su conjunto de claves es público en <issuer>/jwks.json, identificado por <issuer>/.well-known/openid-configuration. Cada App incluye los valores que verifican sus tokens: app.auth.issuer, app.auth.audience, app.auth.jwks_uri. Un token contiene sub, email, name, picture, groups, group_ids, role, account_id y project_id; el token de un agente contiene kind: 'agent' y ningún grupo ni rol.

Función Qué hace
beeuniToken({ audience? }) Un proveedor de tokens (con la forma que los clientes en tiempo real aceptan como callback de autenticación) para la propia App (por defecto) o una App que utiliza. Lee /_beeuni/token en el origen de la propia App. Se almacena en caché hasta poco antes de caducar; { forceRefreshToken: true } vuelve a solicitarlo. Devuelve null si no hay un usuario con sesión iniciada, y null más una advertencia en la consola para una App que no utiliza (403 app_not_linked). Nunca lanza excepciones.
beeuniBinding(slug, { origin? }) { url, token } para una App que esta App utiliza: url = <origin>/_beeuni/apps/<slug> (HTTP y WebSocket, mismo origen, sin CORS), token = beeuniToken({ audience: slug }).
verifyBeeuniToken(bearer, { issuer?, audience?, jwks? }) Comprueba la firma ES256, la caducidad (60 s de tolerancia), el emisor y la audiencia. Valores predeterminados: BEEUNI_AUTH_ISSUER, BEEUNI_AUTH_AUDIENCE, BEEUNI_AUTH_JWKS. Beeuni inyecta los tres en el entorno de ejecución de cada App de servidor (emisor: el proyecto; audiencia: el id de la propia App; conjunto de claves: un URI data:), por lo que verifyBeeuniToken(bearer) funciona allí sin opciones. Nunca establezcas BEEUNI_AUTH_* en env de una App de servidor: el prefijo BEEUNI_ está reservado y beeuni validate lo rechaza. El conjunto de claves es un objeto, texto JSON, un URI data: o una URL https (auth.jwks_uri, obtenida una vez por proceso). Cada fallo lanza BeeuniMemberError, incluidos la ausencia de un conjunto de claves o de una audiencia: una sola clave del proyecto firma los tokens de todas las Apps, por lo que aud es lo que impide aceptar el token de otra App. audience: false acepta un token para cualquier App del proyecto.
apps.token(appId) Un token que identifica al invocador, procedente de la API (POST …/apps/:id/token). Una sesión de agente recibe uno que identifica a su agente.

Un miembro que elimines del proyecto o de un grupo pierde el acceso en el plazo de una vida útil del token; el control de acceso de la App lo rechaza en la siguiente carga de página. Los grupos de Beeuni requieren el plan Enterprise; los roles funcionan en todos los planes.

Gestionar el acceso

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 de password_configured, nunca de la contraseña ni de su hash. Establece una contraseña con { mode: 'password', password }. Cada actualización incrementa revision, lo que revoca las cookies existentes de la App.

Tipos

Todos los tipos siguientes se exportan desde @beeuni/sdk.

Tipo Qué contiene
App Identidad, capabilities, labels, scale, services (AppServiceSummary[]), machines (total, running, stopped), volumes (count, size_gb), config (AppReleaseConfig de la versión en producción, o null), uses, used_by, auth, url, access_mode, access_revision, desired_state, active_deployment_id, hosting_type (de la versión activa: static, sandbox o null antes del primer despliegue), machine (el tamaño del grupo que atiende las solicitudes en la versión en producción), idle_timeout_seconds, always_on, retained_deployments, warnings (solo creación y actualización; siempre []), last_request_at, viewer_can_access
AppCapability 'deployments' | 'rollback' | 'preview' | 'sleep' | 'static' | 'member_tokens' | 'machines' | 'scale' | 'exec' | 'volumes' | 'snapshots'
AppReleaseConfig · AppReleaseConfigInput La configuración resuelta de la versión (processes, services, mounts, vm, deploy) · la forma redactada (processes, http_service, services, mounts, vm, deploy y las 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 cuando la etiqueta del host sería demasiado larga)
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 (el id de la App), jwks_uri: los valores públicos que verifican los tokens de inicio de sesión de la App
AppToken token, expires_at
BeeuniTokenOptions · BeeuniTokenFetcher · BeeuniBinding · VerifyBeeuniTokenOptions Las funciones auxiliares para tokens de inicio de sesión descritas arriba
AppDeployment version, status, source_kind, hosting_type (static: servido desde el almacenamiento, sin entorno de ejecución; sandbox: máquinas), hosting_provider, runtime_spec, build_spec, error_code, attempt_count, created_by, actor_type, source_session_id, config (la configuración resuelta de la versión), strategy, release_command_status (running, succeeded, failed o null)
AppRollback El valor con el que se resuelve apps.rollback: App más la versión en cola en deployment
AppDeploymentDetail Un deployment más su events: eventos del ciclo de vida y el registro de compilación (líneas build_log y un evento log_truncated cuando la compilación imprimió más de 5.000 líneas)
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 Una línea de registro y una página más next_cursor

apps.list() y useProjectApps devuelven solo las Apps que el invocador puede abrir según la política de acceso de cada App (privada: el creador; restringida: los miembros y grupos indicados; proyecto, contraseña y pública: el equipo del proyecto). Un responsable del proyecto ve todas las Apps, por lo que una privada sigue siendo administrable cuando su creador se marcha. Por tanto, viewer_can_access es true en todas las Apps enumeradas. Un get de una App que el invocador no puede abrir responde con 404, no con 403. Trata undefined como desconocido, no como denegado.

AppAccessMode es un ajuste de visibilidad por recurso que se superpone al modelo de roles, no un rol. restricted identifica usuarios y grupos: los mismos tipos de entidades que usa el modelo de roles. Consulta Cuentas y acceso.

DockerfileAppSource y OciImageAppSource requieren command. port es el puerto del servicio principal: omítelo solo para una versión que tenga únicamente workers (un config con processes y sin servicio). StaticAppSource y BundleAppSource no aceptan ninguno de los dos.

Hooks de React

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

useProjectApps(projectId)

Las Apps que el invocador puede abrir, más sus mutaciones del ciclo de vida. Cada mutación invalida el inventario cuando se completa correctamente.

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

Devuelve los campos de la consulta más create, update, start, stop y remove.

useAppDeployments(projectId, appId)

El historial inmutable de despliegues, que se vuelve a obtener cada 5 s para que una compilación en curso avance por sí sola.

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 mutaciones invalidan la lista de despliegues y el inventario de Apps.

useAppDeployment(projectId, appId, deploymentId)

Un despliegue y sus eventos, incluido el registro de compilación (AppDeploymentDetail). Realiza consultas cada 2 s hasta que el despliegue sea ready, failed o cancelled. Pasa null como deploymentId para mantenerlo inactivo.

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

Una compilación conserva sus primeras 2.500 líneas y sus últimas 2.500 líneas. Las líneas se escriben en lotes, como máximo 1 s después de imprimirse. Un despliegue fallido o cancelado conserva su registro de compilación durante 14 días.

useAppAccess(projectId, appId, options?)

La política de acceso y una sesión de acceso de corta duración. Ambas partes son consultas independientes y cada una es 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' });
Opción Valor predeterminado Usa false cuando
policy true La interfaz solo muestra una vista previa de la App. GET …/access es una lectura administrativa y responde 403 a quien realiza la llamada sin permisos de administrador del proyecto.
session true Quien realiza la llamada no tiene permiso para abrir la App. Pasa app.viewer_can_access (siempre true para una App incluida en la lista).

Una cuadrícula de Apps que deja ambas opciones en true ejecuta una lectura de la política y una creación de sesión por App, y cada una devuelve 403 para un miembro que no tiene permiso para abrir esa App.

useAppMachines(projectId, appId)

Las máquinas de una App, consultadas cada 5 s, junto con start, stop, restart, destroy (cada una recibe un machineId) y scale (recibe { [group]: count }). Cada mutación actualiza las máquinas y el inventario de Apps. appMachinesKey(projectId, appId) es la clave de consulta.

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

useAppVolumes(projectId, appId)

Los volúmenes de una App, junto con snapshot (volumeId), restore ({ volumeId, snapshotId }), fork ({ volumeId, name, snapshotId? }), extend ({ volumeId, sizeGb }) y destroy ({ volumeId, name }). Cada mutación actualiza los volúmenes. appVolumesKey(projectId, appId) es la clave de consulta.

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

useAppVolumeSnapshots(projectId, appId, volumeId)

Las instantáneas de un volumen. Pasa null como volumeId para mantener la consulta inactiva.

Errores

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

Otras respuestas que debes gestionar:

  • 409 para un slug duplicado, 402 con app_quota_exceeded cuando la cuenta alcanza su límite de Apps y 400 con app_machine_out_of_range para una especificación fuera de sus límites.
  • 400 app_not_found (slugs) para un slug desconocido en uses.
  • 409 app_capability_unsupported (capability) para una llamada que la App no ofrece.
  • Un despliegue: 400 app_config_invalid (issues), 400 app_volumes_unsupported. Una reversión: 409 release_not_ready.
  • Máquinas y 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.
  • Volúmenes: 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 con Cache-Control: no-store.

¿Te ha resultado útil esta página?