A API context.data
Cada callback recebe um objeto context que inclui context.data — uma camada unificada de acesso a dados para realizar operações entre coleções a partir de hooks de ciclo de vida.
Acessando Coleções
Seção intitulada “Acessando Coleções”O context.data usa um Proxy JavaScript, portanto você pode acessar qualquer coleção pelo seu slug como uma propriedade:
afterSave: async ({ values, id, context }) => { // Dynamic property access — works for any collection slug const jobs = context.data.jobs; const users = context.data.users;
// Alternatively, use the .collection() method for dynamic slugs const collectionName = "jobs"; const accessor = context.data.collection(collectionName);}Uma propriedade é o acessador da coleção, o nome que o SDK gerado lhe dá:
context.data.myNotes é a coleção my-notes, seja qual for a tabela em que
ela está armazenada. Um nome que não é o acessador de nenhuma coleção é lido
como o seu snake_case, então context.data.auditLog é audit_log.
As linhas não têm tipos (Record<string, unknown>) até o projeto registrar o seu
Database gerado; então cada acessador recebe tipos a partir dele. Veja
Dados tipados no servidor.
Métodos Disponíveis
Seção intitulada “Métodos Disponíveis”Cada acessor de coleção (context.data.<slug>) fornece estes métodos:
| Método | Assinatura | Descrição |
|---|---|---|
.find() |
find(params?: FindParams) → FindResponse |
Consultar entidades com filtros, ordenação e paginação |
.findById() |
findById(id: string | number) → Entity | undefined |
Buscar uma única entidade por ID |
.create() |
create(data: Partial<Values>, id?: string) → Entity |
Criar uma nova entidade |
.update() |
update(id: string | number, data: Partial<Values>) → Entity |
Atualizar uma entidade existente |
.delete() |
delete(id: string | number) → void |
Excluir um registro |
.count() |
count(params?: FindParams) → number |
Contar entidades correspondentes |
.listen() |
listen(params, onUpdate, onError?) → unsubscribe |
Inscrição em tempo real (onde suportado) |
.listenById() |
listenById(id, onUpdate, onError?) → unsubscribe |
Escutar uma única entidade |
.trash |
trash.find(), .count(), .restore(id), .restoreMany(ids), .purge(id), .purgeMany(ids) |
As linhas marcadas por uma exclusão lógica |
Consultando com .find()
Seção intitulada “Consultando com .find()”O método find() filtra com tuplas [operator, value] — a forma tipada da
query string ?status=eq.published que a API REST lê:
afterSave: async ({ values, context }) => { // Equality const { data: activeJobs } = await context.data.jobs.find({ where: { status: ["==", "published"] }, limit: 10, orderBy: ["createdAt", "desc"] });
// Several conditions, AND-ed const { data: expensiveJobs } = await context.data.jobs.find({ where: { salary: [">=", 100000], role: ["in", ["admin", "manager"]] } });}Criando Entidades
Seção intitulada “Criando Entidades”.create() e .update() recebem os valores a gravar, com as assinaturas
acima. Sincronizando Dados Entre Coleções
usa ambos: uma submissão aprovada cria um job publicado e é vinculada de volta
a ele.
Segurança: com quais privilégios o context.data é executado
Seção intitulada “Segurança: com quais privilégios o context.data é executado”context.data herda os privilégios do que quer que tenha disparado o callback. Não se trata de um nível de confiança fixo.
- Disparado por uma requisição do usuário (REST, realtime, uma edição no painel admin) → escopo de usuário. O callback é executado dentro da transação vinculada a RLS aberta para essa requisição, portanto, as políticas se aplicam a leituras e gravações. Um callback não pode ver uma linha que seu chamador não pôde.
- Disparado por
rebase.dataAsAdminou um cron job (o mesmo singleton) → escopo de admin, não sem escopo. Esse driver é delimitado como{ uid: "service", roles: ["admin"] }, de modo que o callback ainda roda em uma transação vinculada a RLS — suas políticas são avaliadas contra essa identidade. - Disparado pelo driver base (fluxos internos de autenticação, migrações) → sem escopo. Ele é executado na conexão do proprietário e ignora o RLS.
Isso é especialmente relevante na direção que falha silenciosamente. O RLS filtra, ele não lança erros — portanto, um callback que lê uma linha irmã a encontrará quando uma tarefa de admin salvar, e poderá não encontrar nada quando um usuário final salvar, sem erros em nenhum dos casos. Escreva callbacks que tolerem um resultado vazio, ou acesse o plano de admin deliberadamente:
afterSave: async ({ context }) => { // User-scoped when a user triggered this save: RLS applies. await context.data.audit_logs.create({ action: "approved" });
// Deliberately admin-scoped — for work the caller genuinely may not see, // such as an audit trail they must not be able to read or edit. Note this // is an admin's reach, not a bypass: a collection whose only rule is // `policy.serverContext()` stays closed to it, since that compiles to // `rebase.uid() IS NULL` and this accessor's uid is `service`. // `dataAsAdmin` is always there server-side; its type allows for the // browser SDK, which has none — hence the `!`. await context.client.dataAsAdmin!.audit_logs.create({ action: "approved" });}Semântica de Transações
Seção intitulada “Semântica de Transações”As gravações de context.data de um callback fazem parte da gravação que o disparou. No Postgres, beforeSave, o salvamento e afterSave — ou beforeDelete, a exclusão e afterDelete — são executados dentro de uma única transação, cada callback aguardado antes do commit, e o context.data grava por meio dessa mesma transação.
Portanto, a gravação que disparou o evento e tudo o que seus callbacks gravaram realizam commit juntos ou não realizam de forma alguma:
- Um erro lançado em
afterSaveouafterDeletereverte (rollback) a gravação acionadora, junto com todas as gravações decontext.datafeitas pelos callbacks. O chamador recebe a resposta 400CALLBACK_REJECTEDcomdetails.stageindicando o hook — ou com o próprio status do erro quando este o contiver: umRebaseApiErrorque você lançou, o 409 de uma violação de unicidade. - Assinantes em tempo real são notificados sobre a linha apenas após o commit, portanto, uma gravação revertida nunca é anunciada.
- Um callback mantém a transação aberta enquanto executa; portanto, um callback lento significa um lock retido e uma conexão do pool ocupada —
beforeSaveebeforeDeletetanto quanto os hooksafter. Veja Um callback segura uma conexão. - Uma gravação de
context.datatambém executa os callbacks da coleção de destino, então umafterSaveque atualiza sua própria linha executa a si mesmo novamente. Gravações aninhadas em mais de 16 níveis são recusadas com 500CALLBACK_RECURSION, nomeando o hook e a coleção, e toda a gravação sofre rollback. Torne essa gravação condicional, como faz o exemplo abaixo.
Permita que uma falha lance um erro quando a gravação de origem não deva sobreviver a ela. Trate-a com catch quando ela dever, mas apenas em torno de uma gravação de context.data: um create, update ou delete que o banco de dados recusa (uma violação de chave única ou estrangeira, um trigger) é desfeito isoladamente, e o restante realiza o commit.
Qualquer outra instrução que falhe na transação da gravação — uma consulta, a leitura que um update ou delete faz para encontrar sua linha (um id que a coluna de chave não consegue conter), o enfileiramento de um job que o banco de dados recusou — aborta essa transação no Postgres, e capturar o erro em JavaScript não desfaz isso. A gravação é recusada com 500 TRANSACTION_ABORTED e nada é armazenado, em vez de responder sucesso para uma gravação que sofreu rollback. Deixe uma falha desse tipo ser lançada, ou verifique a condição antes de executar a instrução.
afterSave: async ({ values, id, status, context }) => { // The update below saves this collection again, which runs this callback // again: act on creates only, or it never stops. if (status !== "new") return; try { await context.data.jobs.create({ title: values.title, status: "published" }); } catch (error) { // Only the failed create is undone. The submission and this marker commit. await context.data.job_submissions.update(id, { promotion_status: "failed", promotion_error: String(error) }); }}Tarefas que precisam sair do banco de dados — um e-mail, um webhook, uma chamada a uma API de terceiros — não pertencem ao corpo do callback. Elas manteriam a transação aberta durante uma viagem de ida e volta de rede (network round trip), e nada poderá desfazê-las caso a gravação sofra rollback. Enfileire um job para isso, ou execute após o retorno da gravação: publique em um canal de tempo real, ou use waitUntil em uma função customizada. A documentação de Hooks explica qual abordagem se adequa melhor.
No MongoDB, nada disso se aplica. Esse driver executa os mesmos callbacks sem uma transação, de modo que a gravação já está armazenada quando afterSave é executado, e um throw ali apenas reporta a falha sem desfazê-la.
Um callback segura uma conexão
Seção intitulada “Um callback segura uma conexão”No Postgres, uma gravação roda em uma conexão retirada do pool do banco de dados,
e todo callback roda nela: beforeSave e beforeDelete depois que a
transação começou, afterSave e afterDelete antes de ela ser confirmada. Pelo
tempo em que um callback executa — aguardando uma consulta, um cálculo, qualquer coisa — essa
conexão não atende mais nada, e os locks que a gravação tomou continuam tomados:
no afterSave, a linha recém-gravada; em uma gravação em lote ou _batch,
toda linha que a transação gravou antes dele.
O custo é a duração do próprio callback, e as gravações concorrentes o somam:
- O pool tem 20 conexões por padrão (
DB_POOL_MAX). 25 salvamentos simultâneos cujobeforeSaveaguardava 300 ms fizeram uma leitura de uma linha sem relação levar 290 ms, quase tudo esperando por uma conexão. - Uma criação em lote de 100 linhas cujo
beforeSaveaguarda 50 ms é uma transação aberta por 5,3 s, segurando sua conexão e os locks de toda linha gravada até então durante todo esse tempo.
Portanto:
- Mantenha qualquer coisa lenta fora dos callbacks. Uma chamada a outro serviço pertence a um job, ou ao momento depois que a gravação retorna.
- Busque o que um hook precisa antes da gravação, quando puder — no cliente, ou em uma função customizada que então grava.
- Envie linhas cujos hooks são lentos em lotes menores.
- Se muitos hooks lentos rodam ao mesmo tempo por projeto, aumente
DB_POOL_MAX, dentro do que omax_connectionsdo banco de dados permite para todas as instâncias. - Desde 0.25 Com
REBASE_METRICS=true,/metricsinformarebase_db_pool_wait_ms: quanto tempo cada consulta esperou por uma conexão. Um pool saudável o mantém perto de zero; uma cauda que cresce com o tráfego são requisições na fila por conexões que os callbacks seguram. Veja Métricas.