Pular para o conteúdo

Arquitetura

O slash-kit tem três partes de código que você pode ler e estender: o painel (panel/), a interface que todo projeto copia (ui/) e os módulos (modules/). O CLI (cli/) cria os projetos a partir dos templates (templates/). Esta página descreve o painel, que é onde os módulos entram. A linha entre as pastas está em Fronteiras e projeto gerado.

Só o template blog copia o painel e os módulos para src/. Os outros templates copiam ui/ e mais nada do painel (cli/commands/create.ts:240-245).

PastaO que temOnde vai no projeto
ui/Componentes, temas, seções e o documento do sitesrc/ui/, em todo projeto, sem testes nem galerias
panel/Núcleo, portas, adaptadores, host, contrato, coleções, telas e servidorsrc/panel/, só no blog
modules/O catálogo e os módulos oficiais, um por pastasrc/modules/<id>/, só os instalados
templates/Camadas de cada template: files/ e manifest.tsNão é copiada; as camadas são aplicadas por cima da base
cli/Os comandos do slashNão vai para o projeto
Camadas do painel: a raiz de produção chama o createPanel, que monta o servidor, as telas, as coleções e o host. Telas, coleções e host usam o núcleo, que fala com as portas. Os adaptadores implementam as portas. A raiz de produção liga o servidor e os adaptadores. Os módulos entram pelo contrato, que o host lê.

As setas vão de quem usa para quem é usado. O núcleo usa as portas; cada adaptador implementa uma ou mais portas; os módulos entram pelo contrato, que o host lê. Toque na imagem para abrir o SVG em tamanho cheio.

O desenho mostra as dependências principais, não uma pilha estrita. O núcleo também usa o host, as coleções, o servidor e a sessão (panel/core/records.ts:1-4, panel/core/services.ts:2-6, panel/core/auth.ts:2), e o host usa o núcleo (panel/host/services.ts:2). A única regra de importação garantida por teste aqui é panel/ não importar modules/ (architecture.test.ts:159-166).

O núcleo (panel/core/) tem as regras do painel: sessão e usuários, configurações, backups, publicação e mídia. Ele não importa adaptador nenhum. Para banco, arquivos, e-mail e build, ele usa as portas, que são as interfaces de panel/ports/. A interface Ports junta as que o núcleo usa: store, storeBackup, sessions, passwords, audit, media, mailer, clock, scheduler, publisher e contentSource (panel/ports/index.ts:51).

Quem monta as portas é a raiz de produção. A função launch (panel/production/root.ts:110) abre o banco pela DATABASE_URL, prepara as tabelas e liga as portas SQL (sqlPorts, root.ts:129). A mesma raiz importa os adaptadores (root.ts:3-11) e o servidor (root.ts:20).

AdaptadorPastaPapel
Memóriapanel/adapters/memory/Testes. Na produção, a auditoria do arranque (root.ts:125) e o relógio do sistema (root.ts:5, :119).
SQLpanel/adapters/sql/Banco de produção. A URL escolhe o driver pelo esquema; hoje só sqlite (panel/adapters/sql/open.ts:7-9). O padrão de openDatabase() é sqlite://storage/db.sqlite (open.ts:5); na produção, o caminho sai da pasta do projeto (panel/production/env.ts:94).
Publicaçãopanel/adapters/publisher-ssg.tsRoda o build do slash-ssg (ver Publicação).
Mídiapanel/adapters/media-disk.tsArquivos em disco.
E-mailpanel/adapters/mailer-*.tsResend, com RESEND_API_KEY e MAIL_FROM. Na produção as duas são obrigatórias: sem elas o painel não sobe (panel/production/env.ts:82; panel/adapters/mailer-env.ts:22-27). O console vale só no desenvolvimento (mailer-env.ts:11).
Senhaspanel/adapters/passwords-bun.tsHash argon2id pelo Bun.password (passwords-bun.ts:3).
Agendadorpanel/adapters/scheduler-interval.tsTarefas em intervalo, com setInterval (scheduler-interval.ts:36).

Em panel/adapters/sql/, o código é escrito uma vez sobre o SQL do Bun (sqlite.ts:4). O que muda entre bancos fica na interface Dialect (panel/adapters/sql/dialect.ts:15-21).

Um módulo é um objeto ModuleDef criado por defineModule (panel/contract/module.ts:322). Os campos do ModuleDef (panel/contract/module.ts:216-247) são id, version, core, title, description, requires, permissions, permissionLabels, activityLabels, menuGroups, menu, collections, screens, api, jobs, settings, extend, hooks, site e migrations.

requires ordena os módulos: cada um entra depois das dependências (panel/host/order.ts:9). Os campos de rótulo dão o texto que o painel mostra: title e permissionLabels vão para a matriz de permissões (panel/host/host.ts:43-46; permissionLabels em module.ts:228-229); activityLabels vai para a atividade da Visão geral (module.ts:230-234); menuGroups acrescenta grupos ao menu, além dos do núcleo (module.ts:236).

createHost (panel/host/host.ts:84) junta os módulos: ordena pelas dependências (panel/host/order.ts:9), monta o menu e as permissões e recusa colisões de coleção, rota, endereço do menu e grupo entre módulos e com o núcleo (panel/host/registry.ts:76).

Um gancho reage a uma ação de escrita. O nome segue o padrão <algo>.before<Algo> ou <algo>.after<Algo> (panel/contract/module.ts:106).

  • Um gancho before pode recusar a ação com uma mensagem. A primeira recusa para a cadeia e a mensagem vai ao usuário. Se o gancho falha, ou a mensagem vem vazia, a ação para com HOOK_FAILED (panel/host/hooks.ts:5, 14-16).
  • Um gancho after roda sempre. Um erro nele não para os outros (panel/host/hooks.ts:14-16).
  • O erro vai para a auditoria como hook.error, com o alvo <modulo>:<gancho> (panel/host/hooks.ts:41-43).

extend.fields acrescenta campos à coleção de outro módulo. O formulário mostra os campos da coleção, depois os de extend.fields e por último os dos painéis laterais (panel/host/extensions.ts:26).

collections declara uma coleção: lista, formulário e ações. O motor de coleções (createMountCollections, panel/collections/routes.ts:125) gera as rotas. A lista fica em /painel/c/<chave>. A edição fica em /painel/c/<chave>/<id>, a menos que o manifesto defina editHref (panel/host/registry.ts:30-35; panel/host/extensions.ts:33).

  • Telas do núcleo estão na lista CORE_SCREENS (panel/screens/registry.ts:23).
  • Telas de módulo ficam em /painel/<modulo>/<tela> (panel/host/registry.ts:24-27) e são montadas por mountScreens (panel/app/modules.ts:59, chamada em panel/app.ts:267).
  • API de módulo fica em /api/<modulo>/… quando é pública e em /painel/api/<modulo>/… quando exige login (panel/app/modules.ts:89-91).
  • Roteador. Uma rota sem correspondência responde 404 (panel/server/router.ts:4-6). Um método que não existe nessa rota responde 405 com os métodos aceitos (panel/server/router.ts:102).

Quando um conteúdo muda, publishOnWrite pede uma publicação (panel/core/publish-on-write.ts:68). O pedido vira um job na fila, guardada no banco (PUBLISH_JOBS, panel/ports/publisher.ts:33). O ssgPublisher (panel/adapters/publisher-ssg.ts:126) faz quatro passos, registrados no log do job:

  1. Copia o projeto para storage/build (publisher-ssg.ts:213).
  2. Grava o snapshot do conteúdo (publisher-ssg.ts:216).
  3. Roda bun run build nessa cópia (publisher-ssg.ts:221).
  4. Troca dist/ pelo resultado do build (publisher-ssg.ts:232).

O build recebe só as variáveis da lista BUILD_ENV_ALLOWED (publisher-ssg.ts:31). Segredos do painel, como DATABASE_URL, não chegam a ele.