A versão 0.25 também altera o funcionamento de algumas chamadas existentes, sem quebrá-las. Verifique
estas opções caso sua aplicação dependa do comportamento antigo:
Os wrappers de campos de plugins agora se compõem. O fieldBuilder.wrap de cada plugin é renderizado,
cada um envolvendo o campo deixado pelos plugins anteriores a ele, em vez de apenas o último.
As sobreposições (overrides) de componentes agora se compõem. A sobreposição de uma coleção com wrap: true recebe
a sobreposição do mesmo componente da aplicação como OriginalComponent, e não o componente nativo embutido.
rebase.auth, rebase.admin, rebase.functions e rebase.cron no servidor alcançam as rotas do backend em processo (in-process),
de forma que os middlewares adicionados à sua própria aplicação Hono não são mais executados neles.
O socket de tempo real responde apenas ao seu próprio caminho (/ para uma API em /api).
Um cliente que conectar a outro caminho no mesmo servidor recebe um 404, ou o seu próprio ouvinte de upgrade.
O socket obtém seu token da mesma forma que o HTTP, logo, client.setAuthTokenGetter()
agora o autentica e, após o logout, ele não recorre mais à opção token.
O diretório de um bucket local nomeado fica ao lado de STORAGE_PATH, como
<STORAGE_PATH>__<key>, e não ao lado do caminho base do próprio runtime. Se você definir
STORAGE_PATH e um bucket local nomeado já contiver arquivos, a inicialização apontará para o
diretório antigo, e STORAGE_PATH__<KEY> continuará a servi-lo. Um arquivo compose gerado por
rebase init ou rebase eject monta seu volume em /uploads. Esse volume
contém apenas o bucket padrão, e o usuário da imagem não pode escrever nele. Monte-o
em /storage com STORAGE_PATH: /storage/uploads, como fazem os novos arquivos compose.
Um serviço que uma requisição chama e que não pode ser alcançado é respondido com
503 SERVICE_UNAVAILABLE, como um bucket S3 fora do ar ou um host que uma função chama
com fetch(). Antes era um 500 cujo código era o erro do socket (ECONNREFUSED), ou
INTERNAL_ERROR.
rebase db push mantém as restrições CHECK e EXCLUDE da tabela de uma coleção, onde o
primeiro push depois de rebase schema introspect as removia. A verificação de destruição de
dados agora também conta a remoção de uma, então rebase db migrate --refuse-destructive
para em uma migração que remove uma. Remova à mão uma restrição que você não quer mais.
Estes nomes ainda funcionam, emitem um aviso uma única vez em tempo de execução e possuem substitutos:
Obsoleto (Deprecated)
Usar
loadFunctionsFromDirectory, loadFunctionsWithDiagnostics
loadFunctions(dir, { onSkip })
loadCronJobsFromDirectory, loadCronJobsWithDiagnostics
loadCrons(dir, { onSkip }), depois declareCrons(crons) onde o par antigo os declarava
client.ws.setAuthTokenGetter()
client.setAuthTokenGetter()
plugin hooks.modifyCollectionAsync (nunca chamado)
hooks.modifyCollection, com os dados obtidos antes da renderização de <Rebase>
plugin lifecycle.onCollectionChange (nunca chamado)
context.data.collection(slug).listen(…) a partir de lifecycle.onMount
DatabaseAdapter.mountRoutes, BackendBootstrapper.mountRoutes (nunca chamados)
uma fábrica de rotas que sua aplicação monta
:::
?count= em uma listagem REST agora é o seletor para a contagem por trás de meta.total:
?count=none a ignora e ?count=exact é o padrão. Qualquer outro valor é
recusado com 400 INVALID_COUNT. Anteriormente, ?count=5 filtrava as linhas cuja
coluna count era igual a 5.
O que mudar: nada no SDK, que já envia filtros em colunas
chamadas count dentro de ?where=. Uma chamada REST manual que filtre por
essa coluna deve mover o filtro para lá:
GET /api/data/products?count=5
GET /api/data/products?where={"count":["==",5]}
Um novo SDK contra um servidor antigo: iterate() e findAll() enviam
?count=none em todas as páginas, assim como find({ count: "none" }). Um servidor
na versão 0.24 interpreta isso como um filtro em uma coluna chamada count. Onde a coleção não
possui tal coluna, ele responde com 400 UNKNOWN_FILTER_FIELD, e o SDK reenvia a leitura
sem o parâmetro. Onde a coleção possui uma coluna de texto chamada count, o
servidor antigo filtra para as linhas cujo count seja 'none', sem emitir erro:
a página retorna incompleta ou vazia e a iteração termina precocemente. Atualize o servidor
antes do SDK quando uma coleção possuir tal coluna.
Consulte Skipping the count .
O list_collections enviava sua listagem duas vezes: formatada no bloco de texto e
novamente como structuredContent. Em um projeto grande, ambos somados ultrapassavam
o contexto do modelo. Agora, ele envia a listagem apenas uma vez, no bloco de texto, como
JSON compacto. As outras ferramentas mantêm seu structuredContent.
O que mudar: nada para assistentes, que leem o texto. Um programa
que lia a listagem a partir de structuredContent deve processar o bloco de texto;
o conteúdo é o mesmo. Consulte The remote endpoint .
O ponto de entrada do pacote reexportava a tabela de tradução para espanhol, es, o que incluía
as strings em espanhol em todos os bundles construídos a partir dele. Traduções além do
inglês agora são buscadas quando o usuário escolhe seu idioma, e es não é mais
exportado. en continua sendo exportado.
O que mudar: nada, a menos que você tenha importado es. Para alterar as strings
do painel administrativo, passe sobreposições para <Rebase translations={…}>; para fixar o idioma,
passe locale.
grep -rnE "import \{[^}]*\bes\b[^}]*\} from \" @rebasepro/app \" " --exclude-dir=node_modules .
O middleware de CORS costumava responder a todas as requisições OPTIONS diretamente, com um
204 vazio. Agora, ele responde apenas a preflights de CORS (um OPTIONS contendo
Access-Control-Request-Method); qualquer outro OPTIONS chega até a rota de destino. Uma rota
sem um handler para OPTIONS responde com 404. OPTIONS /api/storage/tus agora retorna
as capacidades do TUS (Tus-Version, Tus-Extension, Tus-Max-Size), como a
documentação sempre descreveu.
Quem é afetado: clientes, proxies ou verificações de integridade (health checks) que sondavam rotas com
OPTIONS e interpretavam 204 como “a rota existe”. Preflights de navegadores não
são afetados.
O que mudar: realize a sondagem com o método real da rota (HEAD ou GET).
Uma tabela, tabela de junção ou tipo enum cujos nomes coincidissem após a
truncação de 63 bytes do Postgres costumava falhar no db push com “already exists”. Duas
propriedades resolvidas para a mesma coluna (myTitle e my_title), ou um target de
relação definido como string (target: "authors"), falhavam mais tarde ou
de maneira confusa. Agora, cada um desses casos é recusado durante o carregamento da configuração,
e a mensagem informa o que renomear ou ajustar.
Quem é afetado: projetos que declaram pares conflitantes. O comando db push
já não era concluído com sucesso anteriormente, portanto isso apenas antecipa a falha.
O que mudar: renomeie um dos identificadores conflitantes e defina o target da
relação como a coleção, não seu slug:
relation: { kind: "belongsTo", target: "authors" },
relation: { kind: "belongsTo", target: () => authors },
O cliente de cron do SDK usava por padrão /api/cron, o alias obsoleto. Agora,
ele chama /api/admin/cron, onde residem todas as interfaces administrativas.
Quem é afetado: servidores (ou regras de proxy) que servem apenas o caminho antigo.
As chamadas de cron do SDK responderão com 404 contra eles.
O que mudar: atualize o servidor junto com o SDK, ou mantenha o caminho antigo
explicitamente:
const client = createRebaseClient ({ baseUrl, cron: { cronPath: "/api/cron" } });
Ao operar offline, findAll() e iterate() retornavam a parte da coleção
armazenada em cache no dispositivo como se fosse o conjunto completo, e count() contava as
linhas em cache. Uma página nunca buscada pelo dispositivo agora falha com OFFLINE, assim como
um count() sem contagem armazenada em cache a partir do servidor.
Quem é afetado: aplicações que exibem essas leituras em modo offline.
O que mudar: capture o erro nos pontos onde a aplicação renderizava resultados parciais,
e exiba o que houver com um aviso, ou solicite uma página que o dispositivo já possua.
rows = await client.data.posts. findAll ();
if (e instanceof RebaseApiError && e.code === "OFFLINE" ) rows = cached;
ClientStorageSourceRegistry.getOrDefault(key) retornava a fonte padrão para
chaves desconhecidas; com isso, uma propriedade referenciando uma fonte ainda não
carregada pelo painel acabava enviando arquivos para o bucket padrão. Agora, ele lança um erro.
Quem é afetado: códigos que chamam getOrDefault passando chaves não
registradas. O próprio painel de administração não faz mais isso.
O que mudar: registre a fonte antes da chamada ou utilize get(key), que retorna
undefined para chaves desconhecidas nos cenários em que uma fonte ausente for esperada.
TextField declarava maxRows, e MultiSelect declarava onChange, name,
position, endAdornment, multiple e padding. Nenhuma dessas propriedades era utilizada, logo,
passá-las não tinha efeito prático. Elas foram removidas das tipagens, e fornecê-las agora
gera um erro de tipo (type error).
Quem é afetado: código que passava alguma dessas propriedades. Nada muda em tempo de execução.
O que mudar: remova a propriedade. Para alterações em seleções múltiplas, utilize
onValueChange.
A coleção de usuários (aquela com auth habilitado) agora responde com 400
INVALID_CONFLICT_TARGET a operações de upsert em todas as interfaces: ?on_conflict=, /bulk
com upsert: true, operações upsert em lote (_batch), upsert() do SDK e
createMany(…, { upsert: true }), bem como o socket em tempo real. Um upsert só
determina no momento da escrita se está criando ou atualizando; portanto, ele não aplicava
nenhum dos dois conjuntos de regras: não havia hash de senha, normalização de e-mail, execução de
onCreateUser ou envio de convite na criação, nem verificação de último administrador na atualização.
Quem é afetado: códigos que executam upsert em usuários, por exemplo, rotinas de sincronização com
client.data.users.upsert(user, { onConflict: ["email"] }).
O que mudar: busque o usuário primeiro e, em seguida, crie ou atualize.
const user = { email: "ada@example.com" , displayName: "Ada" };
const { data : [ found ] } = await client.data.users. find ({ where: { email: [ "==" , user.email] }, limit: 1 });
if ( ! found) await client.data.users. create (user);
else await client.data.users. update ( String (found.id), user);
Leituras informando tanto page quanto offset agora são recusadas. O servidor responde com
400 PAGE_WITH_OFFSET, e o SDK recusa a combinação antes de disparar qualquer requisição, com um
RebaseClientError trazendo o mesmo código: via HTTP, em processo
(rebase.data) e offline. Anteriormente, page prevalecia e o offset era descartado
sem aviso.
Quem é afetado: código que envia ambos, por exemplo, find({ page, offset })
ou uma requisição REST com ?page=3&offset=40. Essas leituras já consideravam
apenas page.
O que mudar: envie apenas um dos parâmetros. Para manter o resultado retornado anteriormente,
remova offset.
await client.data.products. find ({ page: 3 , offset: 40 , limit: 20 });
await client.data.products. find ({ page: 3 , limit: 20 });
Em tabelas cujo nome possui 42 bytes ou mais, as três políticas padrão de escrita de administrador
(…_default_admin_write_insert, _update e _delete) passam a ser nomeadas como
<head>_<hash8>_<op>, com no máximo 63 bytes. Como o Postgres preserva 63 bytes de um identificador,
os três nomes antigos eram truncados para a mesma string e apenas uma política — a de DELETE —
era criada: administradores não conseguiam inserir ou atualizar registros.
Quem é afetado: projetos com nomes de tabela de 42 bytes ou mais, bem como rotinas
que façam referência a essas políticas pelo nome (como migrações ou scripts
de exclusão). Os nomes antigos nunca existiram no banco de dados.
O que mudar: nenhuma alteração é necessária no banco de dados. A próxima inicialização ou execução de rebase db push
removerá a política truncada e criará uma política por operação. Atualize quaisquer
scripts que referenciem os nomes antigos de políticas.
O tempo de vida padrão do refresh token agora é de 30 dias em todos os pontos de entrada. Um
servidor iniciado com o runtime rebase-server (ou rebase start) utilizava 400
dias sempre que JWT_REFRESH_EXPIRES_IN não estivesse definido; inicializações personalizadas com
initializeRebaseBackend já usavam 30 dias.
O tempo de expiração é deslizante: cada renovação emite um token válido por mais 30
dias, garantindo que um usuário que abra a aplicação pelo menos uma vez por mês continue autenticado.
Usuários inativos por períodos superiores precisarão fazer login novamente. Tokens emitidos antes da
atualização mantêm a validade com a qual foram criados até sua próxima renovação.
Quem é afetado: implantações no runtime que não configuraram
JWT_REFRESH_EXPIRES_IN, cujos usuários retornam com intervalo superior a uma vez por mês.
O que mudar: nada, caso queira adotar o novo padrão. Para preservar o comportamento
anterior, defina a variável explicitamente:
JWT_REFRESH_EXPIRES_IN = 400d
ou auth: { refreshExpiresIn: "400d" } em backends ejetados (ejected). Os navegadores limitam
o cookie de refresh a 400 dias, portanto valores maiores não trazem efeito prático no modo cookie.
POST /api/admin/users e criações na coleção de usuários via API de dados
(POST /api/data/users, /bulk, operações em lote, MCP create_document, gravação via socket)
agora recusam senhas (password) que violem a política com o erro 400 WEAK_PASSWORD,
da mesma forma que cadastros, redefinições e PUT /api/admin/users/:uid já faziam. Nenhuma
escrita é realizada e nenhum hook de criação é disparado. Em operações /bulk, a mensagem começa com
Row <n>: , seguindo o padrão de outros erros de linha do bulk. Criações sem fornecimento de senha
permanecem inalteradas: uma senha é gerada automaticamente ou um convite é disparado.
Quem é afetado: código que cria usuários com senhas originadas externamente,
como rotinas de importação ou sincronizações com outros sistemas.
O que mudar: para importar contas com senhas já definidas externamente, envie
skipPasswordPolicy: true no corpo de cada criação (em cada linha de um /bulk), atuando
como administrador ou utilizando a service key:
await client.admin. createUser ({ email, password: legacyPassword, skipPasswordPolicy: true });
Para chamadas de outros perfis, a flag é ignorada. Ela nunca é persistida e nunca
é enviada para onCreateUser ou onAdminCreateUser. A importação do painel administrativo
envia essa flag automaticamente nas linhas que contêm password. Um AuthAdapter customizado
passa a receber essa decisão como terceiro argumento de prepareUserCreation
({ mayWaivePasswordPolicy }); adaptadores que ignorarem o parâmetro mantêm o comportamento
atual. Para customizar as regras da política de senhas, utilize auth.hooks.validatePasswordStrength.
Escritas que enviem "" para uma propriedade string configurada com validation.required
agora retornam 400 VALIDATION_CONSTRAINT, acompanhado de um item em details.violations com
{ field, code: "required" }: exatamente a mesma resposta emitida para valores ausentes. Isso se aplica
a criações e atualizações em todas as portas de entrada (REST, bulk, batch, upsert, socket em tempo real,
MCP), inclusive quando a propriedade possui um defaultValue, visto que defaults preenchem
chaves ausentes, e não valores vazios. O painel administrativo já enviava null
para campos limpos, não sendo impactado.
Quem é afetado: código que grava "" em campos de string obrigatórios, por
exemplo, formulários ou importações que enviam todas as colunas independentemente de estarem preenchidas.
O que mudar: envie um valor válido, omita a chave na criação quando o defaultValue
dever prevalecer, ou remova required de campos que possam legitimamente ser vazios.
Coleções que declaram beforeSave não sofrem essa validação na borda (pois o hook
pode preencher o valor), assim como antes. Códigos de servidor executados em processo
(rebase.data) não são afetados.
Ambos atuavam como mecanismos internos por trás das chamadas do cliente.
new QueryBuilder(collection).where(…) → client.data.<collection>.where(…);
os utilitários or, and, not e cond continuam sendo exportados.
createBackups(transport) → client.backups (mantendo os mesmos métodos list, download,
downloadUrl e stream). A tipagem CreateBackupsOptions também foi removida;
BackupStream continua sendo exportada.
client.ws permanece inalterado.
const storageSource = useBackendStorageSource ({ apiUrl, getAuthToken });
// after — the client you already pass to the admin
const storageSource = client.storage; // default source
const media = client. createStorageSource ( "media" ); // a named source
A fonte obtida pelo cliente também transmite public e context em uploads (limites
de propriedade validados no servidor) e storageId para fontes nomeadas, atributos que o hook
não repassava.
Remova a chave de storage: { type: "s3" | "gcs", … }; o TypeScript apontará a configuração
como uma propriedade desconhecida. Ela nunca definiu o tempo de validade da URL recebida pelo navegador.
Para definir por quanto tempo a URL de um arquivo privado permanece funcional, configure storageDownloadTokenTtl
(em segundos; STORAGE_DOWNLOAD_TOKEN_TTL em implantações empacotadas via bundle). Códigos de servidor que
chamem controller.getSignedUrl() diretamente obtêm URLs válidas por uma hora.
Um projeto que declarava apenas buckets nomeados em config/resources.ts, como
bucket("media", { engine: "s3" }), tinha o primeiro deles promovido a padrão:
os uploads cuja propriedade não especifica nenhum storageSource iam para ele. O padrão agora
sempre lê a sua própria configuração, seja qual for o que está declarado: STORAGE_TYPE,
STORAGE_PATH (ou ./uploads) e as S3_* / GCS_* sem sufixo.
Se esses uploads devem continuar indo para o bucket nomeado, diga isso:
export const media = bucket ( "media" , { engine: "s3" , default: true });
Sem a marca, em produção esses uploads respondem
501 STORAGE_NOT_CONFIGURED até que o padrão seja vinculado (STORAGE_TYPE=s3 e
S3_BUCKET, ou STORAGE_TYPE=gcs e GCS_BUCKET), e o boot registra um aviso
nomeando as duas soluções. Os arquivos que o padrão promovido gravou estão no bucket nomeado;
com a marca, eles são servidos pelo padrão como antes. Em desenvolvimento, os
arquivos que já estão em ./uploads voltam a ser servidos.
O boot também recusa duas fontes que resolvem para um mesmo diretório (ou um dentro do
outro), ou para um mesmo bucket em um mesmo endpoint. Dê à fonte nomeada uma localização
própria (STORAGE_PATH__MEDIA, S3_BUCKET__MEDIA), ou marque-a como padrão.