@statedelta/actions-manifest
v0.1.0
Published
Gera o manifest funcional de um app Dashbox a partir das actions (IR estático, SEM runtime): infere `auth.strategy` (login/token/none), o catálogo `rbac.grants` e os defaults de UX. O não-inferível (roles, tenancy) entra via `config` (configuração nativa
Readme
@statedelta/actions-manifest
Gera o manifest funcional de um app Dashbox a partir das actions
(ActionIR[] do @statedelta/actions-core) — IR estático, sem runtime.
É o irmão do @statedelta/actions-openapi: mesmo input, output diferente.
Enquanto o actions-openapi projeta o contrato de dados (o spec), este package
projeta a config no-code do app (o manifest). Os dois formam o par de arquivos
que descreve um backend pra um cliente: /openapi.json + /manifest.json.
A régua: nunca finge
Mesma régua do actions-openapi. O gerador só emite o que dá pra inferir
honestamente das actions; o resto entra por config (config nativa do backend)
ou fica de fora.
| Zona | Origem |
| --------------- | --------------------------------------------------- |
| auth.strategy | inferido do IR (login / token / none) |
| rbac.grants | inferido — catálogo grants ∪ requiredGrants |
| ux | inferido (defaults; override por config) |
| rbac.roles | config — papel→grants não sai de controller |
| tenancy | config — não é inferível do contrato de dados |
| app, integration | config — identidade + onde está o spec |
Inferência de auth.strategy
- Há rota de login (action
entrypública,POST/PUT, path/id "login-ish") →"login". - Senão, alguma action exige grant (precisa de credencial externa) →
"token". - Senão →
"none".
Catálogo rbac.grants
É fato do IR: a união ordenada de grants (declarados pelo autor) ∪
requiredGrants (derivados pelo checker) de todas as actions. É o que o backend
realmente conhece — e contra o que as roles se validam.
Validação de RBAC (build-time)
Como o gerador tem os dois lados — o catálogo real (do IR) e as roles
declaradas (config) — ele pega RBAC quebrado antes de rodar
(simétrico ao validateRoutes do actions-openapi):
RBAC_UNKNOWN_GRANT— role concede um grant que nenhuma action declara.RBAC_EMPTY_ROLE— role que não concede nada.
"*" (wildcard = todos) é válido por convenção.
Uso
import { buildManifest, validateManifest, type ManifestConfig } from "@statedelta/actions-manifest";
const config: ManifestConfig = {
app: { name: "Gym", theme: "dark" },
integration: { specUrl: "http://localhost:3000/openapi.json", baseUrl: "http://localhost:3000" },
rbac: { roles: { admin: ["*"], member: ["authenticated"] } },
tenancy: { mode: "single" },
};
// integridade referencial das roles vs o catálogo real
const diagnostics = validateManifest(actions, config);
for (const d of diagnostics) console.warn(`[manifest] ${d.message}`);
const manifest = buildManifest(actions, config); // PURO, determinísticoNo demo-api, ambos saem do mesmo IR que serviu as rotas e o OpenAPI —
ver apps/demo-api/src/infra/manifest.ts (servido em /manifest.json).
API
buildManifest(actions, config) → Manifest— pura, determinística. Funde o inferido (auth/grants/ux) com o declarado (roles/tenancy/app/integration).validateManifest(actions, config) → ManifestDiagnostic[]— integridade referencial do RBAC.grantCatalog(actions)/hasAnyGrant(actions)— o catálogo de grants.inferStrategy(actions, routeKind)/detectLogin(actions, routeKind).routeOf(action, routeKind)/DEFAULT_ROUTE_KIND("route") — leitura mínima do metadado@route(só o suficiente pra detectar login).- Tipos:
ManifestConfig,ManifestDiagnostic,ManifestDiagnosticCode. O contratoManifest/Rbac/Tenancy/AuthConfig/Uxé re-exportado do@statedelta/dashbox(o dono do contrato).
Dependências
@statedelta/actions-core (input: ActionIR) · @statedelta/dashbox (contrato
Manifest). Sem runtime do actions.
Scripts
pnpm test # vitest (15 testes)
pnpm typecheck
pnpm build # tsup