Plugin Database
Plugin Database
Este plugin está atualmente em beta. As APIs podem mudar entre versões menores. Importe de @databricks/appkit/beta. Consulte Níveis de estabilidade de plugins.
Declare suas tabelas em config/database/schema.ts e registre database() para
obter um CRUD HTTP gerado e um cliente de banco de dados no servidor. Por padrão, o CRUD é
habilitado para todas as tabelas declaradas. Use api para restringir as rotas geradas
sem desabilitar o acesso no servidor.
Este plugin usa o service principal do app em Databricks Apps implantados. Ele não oferece suporte a OBO e não aplica autorização por usuário ou por linha. Qualquer chamador que consiga alcançar a API gerada pode executar todas as operações habilitadas em todas as linhas expostas, inclusive a exclusão.
Restrinja o acesso ao app e conceda ao seu service principal apenas as permissions de banco de dados necessárias. Se os usuários precisarem de permissions diferentes ou de verificações de propriedade de linha, desabilite as rotas geradas correspondentes e implemente a autorização em rotas de servidor personalizadas. A admissão ao app, por si só, não garante isolamento em nível de linha.
Uso básico
Configure um recurso postgres do Lakebase e suas variáveis de ambiente de conexão
conforme descrito em Configuração do Lakebase.
As tabelas do banco de dados já devem existir e corresponder ao esquema declarado. Este plugin
verifica a conectividade durante o setup; ele não cria nem migra tabelas.
Aplicativos gerados com o plugin Database selecionado incluem um
config/database/schema.ts vazio, para que database() possa iniciar sem exigir tabelas
de exemplo. Substitua a declaração vazia pelos seus modelos quando as respectivas tabelas
do PostgreSQL estiverem prontas.
No desenvolvimento local, o nome de usuário do PostgreSQL é obtido a partir das suas
credenciais do Databricks quando PGUSER e DATABRICKS_CLIENT_ID não estão definidos. Um nome de usuário
configurado explicitamente tem precedência.
// config/database/schema.ts
import { defineSchema, id, text } from "@databricks/appkit/beta";
export const schema = defineSchema((builder) => ({
notes: builder.table("notes", {
id: id(),
body: text().notNull(),
}),
}));// server/index.ts
import { createApp, server } from "@databricks/appkit";
import { database } from "@databricks/appkit/beta";
const AppKit = await createApp({
plugins: [server(), database()],
});Com o plugin de servidor ativado, isso registra:
| Método | Caminho | Operação |
|---|---|---|
| GET | /api/database/notes | Listar linhas |
| GET | /api/database/notes/:id | Buscar uma linha |
| POST | /api/database/notes | Criar uma linha |
| PATCH | /api/database/notes/:id | Atualizar uma linha |
| DELETE | /api/database/notes/:id | Excluir uma linha |
Uma tabela sem chave primária pública oferece suporte apenas a listagem e criação. O upsert está
disponível para o código do servidor, mas não gera uma rota HTTP.
Descoberta e sobrescrita de schema
database() e database({}) usam os mesmos padrões. Durante o setup, o plugin
carrega a exportação nomeada schema de config/database/schema.ts, relativa ao
diretório de trabalho da aplicação. O arquivo deve exportar um resultado finalizado de
defineSchema(). Arquivos ausentes, falhas de importação e exportações inválidas fazem o setup falhar antes que
o plugin crie um pool de conexões; nesses casos, nenhum schema vazio é criado silenciosamente.
Mantenha config/database/schema.ts e suas importações locais no seu deployment. O
plugin carrega TypeScript por meio do Jiti, portanto um processo Node de produção comum não
precisa de um carregador TypeScript separado. O módulo de schema deve apenas declarar tabelas,
sem se conectar ao banco de dados nem iniciar a aplicação.
Para um layout diferente ou um deployment que contenha apenas um bundle de servidor, importe o schema explicitamente e passe-o ao plugin:
import { schema } from "../config/database/schema";
database({ schema });Um schema explícito sempre tem precedência e ignora a descoberta de arquivos. Um schema explícito inválido faz o setup falhar, em vez de recorrer a outro arquivo.
Execute appkit generate-types para gerar o registro do banco de dados. Com esse registro,
uma configuração sem schema explícito ainda consegue inferir nomes de tabelas e payloads de hooks.
Um schema fornecido explicitamente também valida as chaves de configuração contra seus próprios
nomes de tabelas.
Restringir a API gerada
Omitir api, ou defini-lo como true ou {}, habilita o CRUD completo. As restrições
são opcionais. Não existe uma opção separada para habilitar a gravação.
// Nenhuma rota HTTP gerada. O cliente do lado do servidor continua funcionando.
database({ api: false });
// Rotas somente leitura para todas as tabelas.
database({ api: { writes: false } });
// CRUD completo apenas para as tabelas selecionadas.
database({ api: { tables: ["notes"] } });
// Permite leitura, criação e atualização, mas não exclusão.
database({
api: { writes: { operations: ["create", "update"] } },
});
// Lê todas as tabelas, mas permite gravações apenas em notes.
database({
api: { writes: { tables: ["notes"] } },
});| Opção | Padrão | Efeito |
|---|---|---|
schema | Export nomeado em config/database/schema.ts | Substitui o carregamento automático do schema |
api | true | false desativa todas as rotas geradas |
api.tables | Todas as tabelas declaradas | Limita quais tabelas têm rotas |
api.writes | true | false mantém apenas rotas de leitura |
api.writes.tables | Todas as tabelas expostas | Limita quais tabelas expostas aceitam gravações |
api.writes.operations | create, update, delete | Limita quais gravações ficam ativadas |
api.tables: [] desativa todas as rotas geradas. Uma lista vazia de tabelas de
grav ação ou de operações de gravação mantém as leituras e desativa as gravações.
Tabelas omitidas de api.tables também não podem ser incluídas por meio de
relações em tabelas expostas. Essas restrições valem apenas para HTTP, e não
para o cliente do lado do servidor nem para os hooks.
Uma restrição omitida usa o seu padrão. Uma restrição malformada faz o setup
falhar. Por exemplo, { api: { write: false } } é um erro, e não uma permissão
para gerar todas as gravações. Tabelas desconhecidas, nomes duplicados e operações
não suportadas também fazem o setup falhar antes de o plugin criar um pool de conexões.
Substitua a antiga opção crudRoutes por api. O nome antigo é rejeitado em
runtime, de modo que uma desativação antiga não pode ativar a API silenciosamente.
Para preservar o comportamento somente de leitura, especifique api: { writes: false }.
Nomes de tabelas e erros de setup
Os nomes de rotas gerados devem:
- Começar com uma letra ASCII.
- Conter apenas letras ASCII, dígitos, sublinhados ou hifens.
- Ter no máximo 64 caracteres.
- Ser únicos independentemente de maiúsculas e minúsculas, pois as rotas do Express não diferenciam maiúsculas de minúsculas.
A API padrão valida todas as tabelas declaradas. Uma tabela como _events faz o
setup falhar em vez de ser omitida silenciosamente. O erro indica o nome da tabela e sugere
renomeá-la, excluí-la com api.tables ou desativar as rotas com api: false.
Uma tabela excluída continua disponível para o código do servidor.
Os erros de configuration incluem detalhes acionáveis na mensagem de erro do lado do servidor. As mensagens exibidas ao cliente não expõem esses detalhes.
Validação e colunas privadas
A API gerada valida os corpos das requisições e rejeita campos desconhecidos ou
somente leitura. Colunas marcadas com .private() não ficam disponíveis nas rotas geradas.
Chaves primárias geradas pelo banco de dados, incluindo uuid().primaryKey().defaultRandom(),
não podem ser informadas por chamadores HTTP. Uma chave primária natural pode ser informada na
criação, mas chaves primárias não podem ser atualizadas via HTTP.
Validação não é autorização. Uma requisição válida ainda pode ler ou alterar qualquer linha exposta. Use rotas personalizadas com autorização quando esse não for o modelo de acesso desejado.
Hooks de mutação
Uma tabela pode declarar beforeCreate, afterCreate, beforeUpdate, afterUpdate,
beforeUpsert, afterUpsert, beforeDelete e afterDelete. Os hooks e a
mutação são executados em uma única transação de banco de dados. As gravações relacionadas feitas por meio do
ctx.app.database participam dessa mesma transação. Isso não torna transacionais as gravações feitas por
outros plugins ou services externos.
import { DatabaseValidationError } from "@databricks/appkit";
database({
hooks: {
notes: {
beforeCreate(values) {
if (typeof values.body === "string" && values.body.length > 5_000) {
throw new DatabaseValidationError("Note too long", [
{ path: ["body"], message: "Must be at most 5000 characters" },
]);
}
},
},
},
});Um hook before* pode retornar valores de substituição, que são validados novamente antes
da persistência. DatabaseValidationError gera um HTTP 422 com problemas limitados às
colunas públicas. As demais falhas de hook retornam um erro de servidor opaco.
Mantenha os hooks curtos e aguarde todas as operações no banco de dados. Uma transação tem um prazo
de 30 segundos para o callback, um limite compartilhado de 100 operações de banco de dados e uma profundidade
máxima de aninhamento de mutações igual a 8. Repetir a mesma entidade e operação de mutação em
uma cadeia de hooks aninhada é rejeitado. O PostgreSQL também impõe um statement_timeout de 30 segundos
e um idle_in_transaction_session_timeout de 30 segundos.
O prazo do callback não cancela JavaScript arbitrário, requisições HTTP nem outros efeitos colaterais externos. Evite colocar efeitos colaterais externos em hooks que dependam da semântica de rollback do banco de dados.