Plugin Database
Plugin Database
Ce plugin est actuellement en bêta. Les API sont susceptibles de changer d'une version mineure à l'autre. Importez-le depuis @databricks/appkit/beta. Consultez Niveaux de stabilité des plugins.
Déclarez vos tables dans config/database/schema.ts et enregistrez database()
pour obtenir un CRUD HTTP généré ainsi qu'un client de base de données côté serveur.
Le CRUD est activé par défaut pour chaque table déclarée. Utilisez api pour restreindre
les routes générées sans désactiver l'accès côté serveur.
Ce plugin utilise le service principal de l'application dans les Databricks Apps déployées. Il ne prend pas en charge l'OBO et n'applique aucune autorisation par utilisateur ni par ligne. Tout appelant capable d'atteindre l'API générée peut effectuer l'ensemble des opérations activées sur chaque ligne exposée, y compris la suppression.
Restreignez l'accès à l'application et n'accordez à son service principal que les permissions de base de données strictement nécessaires. Si les utilisateurs ont besoin de permissions distinctes ou de vérifications de propriété des lignes, désactivez les routes générées concernées et implémentez l'autorisation dans des routes serveur personnalisées. L'accès à l'application ne suffit pas à garantir une isolation au niveau des lignes.
Utilisation de base
Configurez une ressource Lakebase postgres et ses variables d'environnement de connexion
comme décrit dans Configuration de Lakebase.
Les tables de la base de données doivent déjà exister et correspondre au schéma déclaré. Ce plugin
vérifie la connectivité lors de la configuration ; il ne crée ni ne migre les tables.
Les applications dont l'ossature a été générée avec le plugin Database sélectionné contiennent un fichier
config/database/schema.ts vide, ce qui permet à database() de démarrer sans nécessiter de tables
d'exemple. Remplacez cette déclaration vide par vos modèles dès que leurs tables PostgreSQL
sont prêtes.
En développement local, le nom d'utilisateur PostgreSQL est déduit de vos identifiants
Databricks lorsque PGUSER et DATABRICKS_CLIENT_ID sont absents. Un nom d'utilisateur
explicitement configuré est prioritaire.
// 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()],
});Lorsque le plugin serveur est activé, cela enregistre :
| Méthode | Chemin | Opération |
|---|---|---|
| GET | /api/database/notes | Lister les lignes |
| GET | /api/database/notes/:id | Récupérer une ligne |
| POST | /api/database/notes | Créer une ligne |
| PATCH | /api/database/notes/:id | Mettre à jour une ligne |
| DELETE | /api/database/notes/:id | Supprimer une ligne |
Une table sans clé primaire publique ne prend en charge que la liste et la création. upsert est
disponible dans le code serveur, mais aucune route HTTP n'est générée pour cette opération.
Découverte du schéma et surcharges
database() et database({}) utilisent les mêmes valeurs par défaut. Lors de la configuration, le plugin
charge l'export nommé schema depuis config/database/schema.ts, relativement au répertoire
de travail de l'application. Le fichier doit exporter un résultat defineSchema()
finalisé. Un fichier manquant, un échec d'import ou un export invalide font échouer la configuration avant que le
plugin ne crée un pool de connexions ; aucun schéma vide n'est créé silencieusement.
Conservez config/database/schema.ts et ses imports locaux dans votre déploiement. Le
plugin charge le TypeScript via Jiti : un simple processus Node de production n'a donc pas
besoin d'un chargeur TypeScript distinct. Le module de schéma doit se limiter à déclarer des tables,
sans se connecter à la base de données ni démarrer l'application.
Pour une organisation différente ou un déploiement qui ne contient qu'un bundle serveur, importez explicitement le schéma et transmettez-le au plugin :
import { schema } from "../config/database/schema";
database({ schema });Un schéma explicite est toujours prioritaire et contourne la découverte de fichiers. Un schéma explicite non valide fait échouer la configuration au lieu de se rabattre sur un autre fichier.
Exécutez appkit generate-types pour générer le registre de base de données. Grâce à ce registre,
une configuration sans schéma explicite déduit malgré tout les noms de tables et les charges utiles des hooks.
Un schéma fourni explicitement vérifie en outre les clés de configuration par rapport à ses propres noms
de tables.
Restreindre l'API générée
Omettre api, ou lui donner la valeur true ou {}, active l'ensemble des opérations CRUD. Les restrictions
sont facultatives. Il n'existe pas d'option distincte pour activer l'écriture.
// Aucune route HTTP générée. Le client côté serveur reste fonctionnel.
database({ api: false });
// Routes en lecture seule pour toutes les tables.
database({ api: { writes: false } });
// CRUD complet uniquement pour les tables sélectionnées.
database({ api: { tables: ["notes"] } });
// Autorise la lecture, la création et la mise à jour, mais pas la suppression.
database({
api: { writes: { operations: ["create", "update"] } },
});
// Lecture de toutes les tables, mais écritures autorisées uniquement sur notes.
database({
api: { writes: { tables: ["notes"] } },
});| Option | Valeur par défaut | Effet |
|---|---|---|
schema | Export nommé dans config/database/schema.ts | Remplace le chargement automatique du schéma |
api | true | false désactive toutes les routes générées |
api.tables | Toutes les tables déclarées | Limite les tables disposant de routes |
api.writes | true | false ne conserve que les routes de lecture |
api.writes.tables | Toutes les tables exposées | Limite les tables exposées acceptant des écritures |
api.writes.operations | create, update, delete | Limite les écritures activées |
api.tables: [] désactive toutes les routes générées. Une liste vide de tables
ou d'opérations d'écriture conserve les lectures et désactive les écritures. Les
tables absentes de api.tables ne peuvent pas non plus être incluses via des
relations sur des tables exposées. Ces restrictions s'appliquent uniquement au
HTTP, pas au client côté serveur ni aux hooks.
Une restriction omise prend sa valeur par défaut. Une restriction mal formée fait
échouer la configuration. Par exemple, { api: { write: false } } est une erreur,
et non l'autorisation de générer toutes les écritures. Les tables inconnues, les
noms en double et les opérations non prises en charge font également échouer la
configuration avant que le plugin ne crée un pool de connexions.
Remplacez l'ancienne option crudRoutes par api. L'ancien nom est rejeté au
runtime afin qu'une ancienne désactivation ne puisse pas activer l'API de manière
silencieuse. Pour conserver un comportement en lecture seule, spécifiez
api: { writes: false }.
Noms de tables et erreurs de configuration
Les noms de routes générés doivent :
- Commencer par une lettre ASCII.
- Ne contenir que des lettres ASCII, des chiffres, des tirets bas ou des traits d'union.
- Comporter au maximum 64 caractères.
- Être uniques sans distinction de casse, car les routes Express sont insensibles à la casse.
L'API par défaut valide chaque table déclarée. Une table comme _events fait
échouer la configuration au lieu d'être ignorée silencieusement. L'erreur indique le nom de la table et propose
de la renommer, de l'exclure avec api.tables ou de désactiver les routes avec api: false.
Une table exclue reste accessible au code serveur.
Les erreurs de configuration incluent des détails exploitables dans le message d'erreur côté serveur. Les messages destinés au client n'exposent pas ces détails.
Validation et colonnes privées
L'API générée valide les corps de requête et rejette les champs inconnus ou en
lecture seule. Les colonnes marquées .private() ne sont pas exposées par les routes générées.
Les clés primaires générées par la base de données, y compris uuid().primaryKey().defaultRandom(),
ne peuvent pas être fournies par les appelants HTTP. Une clé primaire naturelle peut être fournie à
la création, mais les clés primaires ne peuvent pas être modifiées via HTTP.
La validation ne vaut pas autorisation. Une requête valide peut tout de même lire ou modifier n'importe quelle ligne exposée. Utilisez des routes personnalisées avec autorisation si ce modèle d'accès ne correspond pas à vos besoins.
Hooks de mutation
Une table peut déclarer beforeCreate, afterCreate, beforeUpdate, afterUpdate,
beforeUpsert, afterUpsert, beforeDelete et afterDelete. Les hooks et la
mutation s'exécutent au sein d'une même transaction de base de données. Les écritures
associées effectuées via ctx.app.database rejoignent cette même transaction. En
revanche, cela ne rend pas transactionnelles les écritures passant par d'autres
plugins ou services externes.
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" },
]);
}
},
},
},
});Un hook before* peut renvoyer des valeurs de remplacement, qui sont validées à nouveau avant
d'être persistées. DatabaseValidationError produit un code HTTP 422 dont les problèmes signalés se limitent
aux colonnes publiques. Les autres échecs de hooks renvoient une erreur serveur opaque.
Gardez les hooks courts et attendez la fin de toutes les opérations de base de données. Une transaction dispose d'un délai
de rappel de 30 secondes, d'un budget partagé de 100 opérations de base de données et d'une profondeur
d'imbrication maximale de 8 mutations. La répétition d'une même entité et d'une même opération de mutation
dans une chaîne de hooks imbriqués est rejetée. PostgreSQL applique également un
statement_timeout de 30 secondes et un idle_in_transaction_session_timeout de 30 secondes.
Le délai de rappel n'interrompt ni le code JavaScript arbitraire, ni les requêtes HTTP, ni les autres effets de bord externes. Évitez de placer des effets de bord externes dans des hooks qui nécessitent une sémantique de rollback en base de données.