Ir para o conteúdo principal

Plugin Database

Plugin Database

Plugin em beta

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.

Acesso compartilhado à aplicação

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étodoCaminhoOperação
GET/api/database/notesListar linhas
GET/api/database/notes/:idBuscar uma linha
POST/api/database/notesCriar uma linha
PATCH/api/database/notes/:idAtualizar uma linha
DELETE/api/database/notes/:idExcluir 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çãoPadrãoEfeito
schemaExport nomeado em config/database/schema.tsSubstitui o carregamento automático do schema
apitruefalse desativa todas as rotas geradas
api.tablesTodas as tabelas declaradasLimita quais tabelas têm rotas
api.writestruefalse mantém apenas rotas de leitura
api.writes.tablesTodas as tabelas expostasLimita quais tabelas expostas aceitam gravações
api.writes.operationscreate, update, deleteLimita 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.

Referência da API

Databricks Developer Hub

Pronto para lançar seu próximo aplicativo baseado em agentes em minutos?

Ler a documentação