Edição de schema ao vivo
O editor de schema no painel de administração reescreve o código-fonte da sua coleção. Isso funciona na sua máquina e em nenhum outro lugar: os arquivos de um servidor implantado são reconstruídos a partir do seu repositório a cada deploy, portanto uma edição feita lá seria descartada no próximo.
A edição de schema ao vivo é a resposta para isso. Ela comita a alteração no seu repositório e depois aplica o DDL — assim, a edição sobrevive ao próximo deploy, porque o deploy é construído a partir dela.
GET /api/admin/schema/status whether this backend can do it, and whether you mayPOST /api/admin/schema/plan what would happen, without doing itPOST /api/admin/schema/apply commit, then applyStatus e plan requerem o escopo schema:read, e apply requer schema:write, como
qualquer outra superfície de /api/admin que nomeia um escopo. Um administrador tem os dois.
A aplicação também exige algo além do escopo — consulte Quem pode aplicar.
Planeje antes de aplicar
Seção intitulada “Planeje antes de aplicar”O /plan não tem efeitos colaterais. Envie a alteração, e ele informa o que ela
significa. Uma alteração em uma coleção existente é um patch — o que mudou, como
operações sobre caminhos de chaves — e uma nova coleção é a collection inteira:
{ "collectionId": "posts", "patch": [ { "op": "set", "path": ["properties", "subtitle"], "value": { "name": "Subtitle", "type": "string" } }, { "op": "remove", "path": ["admin", "group"] }] }Apenas as chaves que um patch nomeia são escritas no arquivo da coleção. Todo o
resto permanece como está — imports, comentários, formatação, o onClick de
uma ação de entidade, uma propriedade compartilhada de outro módulo, um enum
importado de outro lugar. Um patch que alcança dentro de algo definido em
código (status: statusProperty, enum: LOCALE_ENUM, um ...spread) é
recusado com a expressão em que bateu, de modo que a alteração seja feita onde
esse código vive. Uma collection inteira enviada para uma coleção que já
existe é transformada no patch do que difere dela, e uma chave cujo valor é
código nunca é removida dessa forma. O painel admin envia patches. Na 0.23 o
/plan e o /apply recebem apenas a collection inteira como ela deve ficar.
$ADMIN_TOKEN é um token de acesso — o accessToken que um login retorna — de uma
conta que tem schema:read: um administrador, ou uma role que o declara. Nada na
máquina o define para você.
curl -X POST https://your-app/api/admin/schema/plan \ -H "authorization: Bearer $ADMIN_TOKEN" \ -H "content-type: application/json" \ -d '{"collectionId":"posts","collection":{}}'{ "applicable": true, "verdict": "safe", "changes": [ { "kind": "add-property", "verdict": "safe", "collection": "posts", "property": "subtitle", "detail": "New optional property subtitle …" } ], "statements": ["ALTER TABLE \"public\".\"posts\" ADD COLUMN IF NOT EXISTS \"subtitle\" TEXT;"], "files": ["config/collections/posts.ts", "backend/src/schema.generated.ts"]}files é cada arquivo que o apply inclui no commit: o código-fonte da coleção, quando o backend pode gravá-lo, e depois o schema gerado.
Isso não é apenas uma conveniência. Dois dos três vereditos são recusas, e um deles é uma recusa que você só descobriria ao apertar o botão em um banco de dados ativo.
Os três vereditos
Seção intitulada “Os três vereditos”| Veredito | Significado |
|---|---|
safe |
O caminho ensure no momento do boot o expressa e o resultado corresponde à sua configuração. Aplicado. |
diverges |
Ele é aplicado em parte, deixando um banco de dados que não corresponde à sua configuração — e nada reporta isso. Recusado. |
needs-migration |
O caminho ensure não consegue expressá-lo de forma alguma. Recusado. |
diverges é o que vale a pena entender, porque essas alterações parecem ter
funcionado:
- Uma propriedade obrigatória adicionada a uma tabela que já contém linhas
chega como anulável (nullable). O
NOT NULLé verificado em cada linha já existente, e as linhas gravadas antes de a propriedade existir não possuem valor para ela. Em uma tabela vazia, não há nada a verificar, portanto a restrição é aplicada e isso ésafe. - Tornar obrigatória uma propriedade existente segue a mesma lógica:
SET NOT NULLvarre a tabela, portanto ésafeem uma tabela vazia edivergesem uma preenchida até que você faça o backfill.
Duas alterações que costumavam ser diverges agora são safe, porque o caminho
ensure as executa:
- Um valor adicionado a um enum existente é aplicado via
ALTER TYPE … ADD VALUE IF NOT EXISTS. Antes, ele era ignorado junto com todo o tipo, e a primeira linha usando o novo valor era rejeitada por um tipo que nunca tinha ouvido falar dele. - Tornar uma propriedade obrigatória opcional remove o
NOT NULL. Antes, ele era mantido, de modo que gravações omitindo a propriedade continuavam falhando.
Restrições solicitadas e não aplicadas
Seção intitulada “Restrições solicitadas e não aplicadas”Uma alteração pode ser aplicável e ainda deixar algo solicitado pela sua
configuração sem aplicação — uma propriedade obrigatória sobre uma tabela
populada é o caso. Isso não é uma recusa, portanto não aparece em changes;
aparece em withheldConstraints, com o obstáculo e o que o resolveria:
{ "withheldConstraints": [ { "target": "public.posts.author", "kind": "not-null", "reason": "\"author\" is required, but \"public.posts\" already holds rows …", "remedy": "Backfill the column, then apply this again." } ]}O caminho ensure de boot relata a mesma coisa como um aviso. Até que isso existisse, uma restrição retida ficava retida em silêncio.
needs-migration cobre tudo o que o caminho ensure não pode fazer: remover uma
coleção ou uma propriedade, alterar o tipo de uma coluna (alternar um inteiro,
uma string se tornando um enum, o tipo de elemento de um array, a largura de um
varchar), renomear uma coluna, alterar uma chave primária, remover um valor de
enum, tornar única uma coluna já existente, e alterar uma relação — seu tipo
(kind), seu destino, seu localKey, seu onDelete. Um hasMany ou hasOne
cuja coluna de vínculo nada cria também é recusado. Cada recusa nomeia a
alteração e o que fazer em vez disso.
O veredito é lido a partir do esquema que cada lado produz — o mesmo plano a
partir do qual o schema.generated.ts e o db push são renderizados — então
uma edição que altera o banco de dados não pode ser relatada como nenhuma
alteração. Duas edições que parecem alterações e não são recusadas (; a 0.23 relata ambas como exigindo uma migração):
- Renomear a chave de uma propriedade mantendo sua coluna (
columnNamedefinido como a coluna antiga) não move nenhum dado. Ésafe; os clientes da API leem o nome novo. - Definir, alterar ou remover um default vincula apenas gravações
futuras. É
safe, e aplicado comALTER COLUMN … SET DEFAULT/DROP DEFAULT.
Editar apenas a origem
Seção intitulada “Editar apenas a origem”Uma alteração recusada ainda pode ser escrita na origem da sua coleção e
commitada, deixando o banco de dados como está — remover uma propriedade que
você não serve mais é o caso comum. Envie /apply com "sourceOnly": true. Nada é
executado; a mensagem do commit nomeia o que o banco de dados mantém, por
exemplo chore(schema): remove sku from products (source only — column products.sku kept),
e cada alteração no plano carrega uma frase sourceOnly dizendo o que ela
deixa para trás — incluindo quando uma coluna deixada para trás é NOT NULL
sem default, o que faz toda inserção futura falhar até que ela seja removida ou
se torne anulável (nullable). Uma alteração sem uma dessas (mover uma chave
primária, uma relação cuja coluna de vínculo nada cria) não pode ser escrita
apenas na origem.
Excluir uma coleção pelo painel admin segue o mesmo caminho: /apply com
"remove": true e "sourceOnly": true exclui o arquivo da coleção e sua
entrada em index.ts e commita os dois; a tabela e suas linhas permanecem.
É recusado enquanto outra coleção importa o arquivo (uma relação a ele),
nomeando o importador — excluí-lo impediria que todas as coleções carregassem.
Na 0.23 o /apply não recebe nem sourceOnly nem remove, e o “Editar apenas
a origem” do painel escreve o arquivo sem um commit.
O que é comitado
Seção intitulada “O que é comitado”Não apenas o arquivo da coleção. O schema Drizzle é gerado a partir dele, e um desatualizado quebra o próximo deploy, então os dois vão no mesmo commit:
config/collections/<name>.ts— a própria coleçãobackend/src/schema.generated.ts— o schema Drizzle
Esses caminhos são relativos ao seu projeto, não ao seu repositório. Quando
os dois são a mesma coisa — um projeto rebase init, que é o caso comum —, não
há nada com o que se preocupar. Quando o seu projeto fica em um subdiretório de
um repositório maior, os caminhos recebem o prefixo dele, localizado ao subir a
partir do diretório de coleções até o rebase.json mais próximo. Um projeto sem
rebase.json mantém os caminhos simples.
Nenhum SQL entra no commit. rebase db push e rebase db generate gravam o próprio
SQL a partir das coleções a cada execução, em .rebase/sql/, que fica no gitignore.
Na 0.23 o commit também carrega drizzle/schema.sql, drizzle/policies.sql e
drizzle/search.sql, escritos na raiz do projeto.
A mensagem de commit descreve a alteração em vez de simplesmente anunciar uma, e
é atribuída à pessoa que a realizou, pelo nome de exibição e pelo e-mail da conta
(um usuário sem nenhum dos dois é registrado como <uid>@users.noreply.rebase.pro).
Desde 0.25 Na 0.24, os commits
da autenticação integrada levam o uid como nome.
Uma alteração de schema com autor e diff no histórico do seu projeto é algo que nem o Firebase nem o Supabase oferecem — as edições de tabela deles são invisíveis para o seu repositório.
Quem pode aplicar
Seção intitulada “Quem pode aplicar”Ter schema:read é suficiente para planejar (plan). O planejamento
não tem efeitos colaterais, e uma tarefa de CI verificando se uma alteração de
coleção proposta é aplicável é um bom uso para ele.
Aplicar é um segundo privilégio, porque aplicar grava um commit e um commit traz um autor:
| Chamador | Plan | Apply |
|---|---|---|
Uma pessoa autenticada que tem schema:write |
sim | sim |
Uma chave de API que tem schema:read / schema:write |
sim | não |
| A chave de serviço do servidor | sim | não |
Uma credencial não é um autor. api-key:7c3f… no seu ambiente de CI não é
alguém, e permitir que ela escreva no seu repositório produz exatamente o
histórico sem autoria que este recurso existe para substituir.
Na 0.23 a linha é a role admin: um admin planeja e aplica, e qualquer chave
de API pode planejar.
Se você deseja uma alteração automatizada de schema — um pipeline de migração, por exemplo —, ative isso deliberadamente:
initializeRebaseBackend({ // …the rest of your config liveSchema: { allowMachineApply: true }})ou REBASE_LIVE_SCHEMA_ALLOW_MACHINE_APPLY=true. O commit é então atribuído à
credencial pelo nome — Rebase API key (7c3f) — para que ler o git log um mês
depois ainda mostre quais alterações foram feitas por uma pessoa.
GET /api/admin/schema/status informa o que você pode fazer, não apenas o que
o servidor suporta, para que um painel possa desativar o controle e dizer o
motivo em vez de recusar depois que você já tiver decidido:
{ "enabled": true, "canPlan": true, "canApply": false, "applyRefusedCode": "SCHEMA_EDIT_REQUIRES_A_PERSON", "applyRefusedBecause": "This request is authenticated with an API key …"}Se o seu projeto mantém migrações versionadas
Seção intitulada “Se o seu projeto mantém migrações versionadas”Aplicar aqui não cria uma migração, e nem poderia: uma migração usa o formato do Atlas com um arquivo de integridade, gerado por um binário externo contra um banco de dados temporário, e um servidor em execução não possui nenhum dos dois.
O que ele comita é a coleção, e é a partir dela que o rebase db generate
grava a migração. Portanto, a migração está a apenas um comando de distância:
rebase db generateO plano e o resultado avisam sobre isso quando o seu projeto possui migrações, porque, caso contrário, a falha seria silenciosa: seu banco de dados teria a alteração e seu repositório a descreveria, mas o próximo ambiente construído reproduzindo as migrações não a teria, sem que nada avisasse.
Um projeto provisionado pelo boot-ensure — o runtime gerenciado e qualquer
self-host deixando REBASE_MIGRATE_ON_BOOT no padrão — não precisa de migração
alguma. Suas coleções são o próprio schema, e a próxima inicialização faz a
reconciliação.
Comitar primeiro, depois aplicar
Seção intitulada “Comitar primeiro, depois aplicar”A ordem importa e não é arbitrária.
Se o DDL rodasse primeiro e o commit falhasse, seu banco de dados teria uma coluna que o seu repositório não descreve. O caminho ensure nunca remove nada, portanto o próximo deploy não a removeria nem a mencionaria — uma coluna invisível, ausente das suas coleções, até que alguém fosse procurá-la.
Comitar primeiro falha no sentido oposto: o repositório descreve algo que o banco de dados ainda não tem. Esse é o estado normal de qualquer projeto entre uma edição e um deploy, e o boot o reconcilia na inicialização seguinte.
Portanto, uma aplicação que falha não é um erro. A resposta diz isso:
{ "applied": false, "applyError": "connection refused", "committed": { "sha": "1a2b3c4de", "branch": "main" }, "summary": "Committed 1a2b3c4de on main, but the database was not changed. The change will be applied on the next boot."}Onde isso funciona
Seção intitulada “Onde isso funciona”A edição ao vivo do schema reescreve o código-fonte das suas coleções com o mesmo editor
que os salvamentos somente de código-fonte do painel usam (ele precisa do ts-morph
instalado). De onde vem esse código-fonte decide onde ela roda:
- Em disco (
collectionsDir, o padrão): roda onde o editor somente de código-fonte roda. Esse editor fica desligado sobNODE_ENV=production, a menos que o servidor definaschemaEditor: true. - Em um repositório que você nomeia (
liveSchema.repository, ouREBASE_LIVE_SCHEMA_REPOSITORYpara um bundle): o código-fonte é lido do GitHub e a alteração é comitada de volta lá, então roda em qualquer lugar, produção inclusive. Esta é a adesão para um bundle compilado.
MongoDB
Seção intitulada “MongoDB”Tudo acima descreve o Postgres, onde uma alteração de schema significa DDL. No MongoDB não há tabela para alterar: adicionar uma propriedade não adiciona nada, remover uma não remove nada, e um documento gravado ontem ainda é válido amanhã.
Portanto, toda alteração é aplicável, nada é recusado e o plano não tem instruções (statements) — o commit é a alteração. O painel diz “Commit” em vez de “Commit and apply”, e não afirma que algo foi executado contra o banco de dados.
O único ponto que vale a pena ler com atenção é uma remoção. No Postgres, remover uma propriedade é recusado porque isso excluiria uma coluna. No MongoDB, o campo permanece em cada documento que o possui; sua API simplesmente para de servi-lo. A alteração expressa isso em vez de deixar você assumir a resposta relacional.
| Implantação | Funciona |
|---|---|
rebase dev na sua máquina |
sim |
| Self-host com o projeto e sua árvore de trabalho do git montados | sim, quando NODE_ENV não é production, ou com schemaEditor: true |
| Um bundle compilado: um self-host servindo uma build, ou a imagem publicada | somente quando adere com REBASE_LIVE_SCHEMA_REPOSITORY — veja A partir de um bundle compilado |
Qualquer servidor com liveSchema.repository configurado |
sim, NODE_ENV=production inclusive |
Um bundle é uma saída compilada, portanto não há código-fonte de coleções nele, e o
runtime que o serve desliga o editor somente de código-fonte. A menos que nomeie um
repositório, /api/admin/schema/status responde enabled: false com o código
SCHEMA_EDITOR_DISABLED: edite as coleções em desenvolvimento, faça o commit e o deploy.
A partir de um bundle compilado
Seção intitulada “A partir de um bundle compilado”Um bundle não tem código próprio para configurar, então adere por meio de variáveis de ambiente. Nada muda para um bundle que não as define.
REBASE_LIVE_SCHEMA_REPOSITORY=acme/storefront # turns it onREBASE_LIVE_SCHEMA_GITHUB_TOKEN=github_pat_… # or the three _GITHUB_APP_* variables# REBASE_LIVE_SCHEMA_BRANCH=main # default# REBASE_LIVE_SCHEMA_COLLECTIONS_PATH=config/collections # defaultA lista completa está em Configuração.
Um repositório nomeado sem uma credencial utilizável (nenhuma, os dois tipos, metade de um GitHub
App), ou um valor que não seja owner/name, se recusa a iniciar, em vez de servir
um editor que falha quando alguém pressiona Apply.
O que permanece igual quando está ligado:
- É uma superfície de
/api/admin: nenhuma resposta semschema:read, e/applyexigeschema:writee uma pessoa (veja Quem pode aplicar). - O alvo da escrita é apenas o repositório que você nomeou. O editor somente de código-fonte, que grava arquivos ao lado do processo, permanece desligado.
- Cada alteração é planejada, mostrada, comitada e então aplicada, exatamente como na sua máquina.
Uma coisa é diferente. A implantação em execução continua servindo as coleções com as quais foi construída até que o seu pipeline construa e faça o deploy do commit. O banco de dados recebe a alteração de imediato; o painel de administração e a API veem a nova definição da coleção depois desse deploy.
Por isso uma implantação faz uma alteração por deploy. Depois que um commit entra, o próximo
apply é recusado com SCHEMA_CHANGE_FAILED, citando o commit, até que um processo
executando esse commit substitua este. Uma segunda alteração planejada contra as
coleções que esta implantação ainda executa comitaria arquivos de schema gerados
que deixam a primeira alteração de fora. O planejamento continua disponível nesse meio tempo.
Fazendo commit em um repositório remoto
Seção intitulada “Fazendo commit em um repositório remoto”liveSchema.repository faz o servidor comitar cada alteração em um repositório do
GitHub através da Git Data API, em vez de executar git commit em uma árvore de
trabalho. Ele não depende de o editor somente de código-fonte estar ligado: com
um repositório configurado, a edição ao vivo funciona sob NODE_ENV=production e com
schemaEditor: false, porque nada nesta máquina é gravado. Um bundle
o define pelas variáveis acima.
initializeRebaseBackend({ // …the rest of your config liveSchema: { repository: { kind: "github", owner: "acme", repo: "storefront", branch: "main", // Where the collection source lives in that repository. // Defaults to "config/collections". collectionsPath: "config/collections", auth: { kind: "token", token: process.env.GITHUB_TOKEN! } } }})O diretório de coleções é então lido do repositório, reescrito com o
mesmo editor executado localmente, e o que mudou é comitado de volta através da
Git Data API: um blob por arquivo, uma tree, um commit e uma atualização de ref. Nada é
clonado e nada é deixado em disco. O diretório inteiro é lido, não apenas o
arquivo editado, então uma relação pode apontar para outra coleção (o arquivo dela é
importado) e excluir uma coleção funciona como localmente: recusado enquanto
outra coleção a importa, e removida de index.ts no mesmo commit.
auth aceita um token ou uma instalação de GitHub App:
auth: { kind: "app", appId: "123456", privateKey: process.env.GITHUB_APP_PRIVATE_KEY!, installationId: "987654"}Use o token para um projeto único comitando em um repositório que você já possui — configurar um App para que seu próprio servidor possa comitar nele é muita burocracia para uma credencial de uma linha. Use o App para um plano de controle mantendo uma única chave para vários projetos, que é o que o Rebase Cloud faz: um App, uma instalação por projeto e nenhum segredo por cliente para rotacionar.
O token precisa da permissão contents: read and write nesse repositório, e
nada mais.
Sem liveSchema.repository, o commit é um simples git commit na árvore de
trabalho que contém collectionsDir: nada para autenticar, sem token, sem
rede.
Duas coisas que tornam seguro executá-lo contra um repositório em que outra pessoa está trabalhando:
- Ele adiciona à stage apenas os arquivos que gerou. Um commit de schema que incluísse trabalho pela metade seria um commit que ninguém conseguiria revisar, e ele se recusa expressamente se a árvore já tiver um de seus próprios arquivos modificado.
- O caminho remoto nunca atualiza uma ref com force (force-update). Se algo entrou no repositório enquanto o commit estava sendo construído, a atualização é rejeitada — perder o commit de alguém silenciosamente é pior do que falhar.
Limitações
Seção intitulada “Limitações”- Apenas alterações aditivas. Todo o resto é recusado com um motivo, porque o caminho ensure é a única coisa que altera um schema e ele só pode adicionar.
- Nenhum arquivo de migração é gerado. Um projeto provisionado pelo boot-ensure
não precisa de nenhum; um projeto provisionado por migrações deve executar
rebase db generate, que cria um através do Atlas com o hash de integridade que o Atlas exige. - Apenas Postgres. A funcionalidade é detectada no driver, e outros mecanismos
respondem
SCHEMA_EDITING_UNSUPPORTED.
Relacionado
Seção intitulada “Relacionado”- Geração de Schema — as mesmas edições a partir da linha de comando
- Definindo Coleções — o que o editor está reescrevendo
- Studio — o painel por trás do qual essas rotas estão