Édition de schéma en direct
L’éditeur de schéma dans le panneau d’administration réécrit le code source de vos collections. Cela fonctionne sur votre machine et nulle part ailleurs : les fichiers d’un serveur déployé sont reconstruits à partir de votre dépôt à chaque déploiement, de sorte qu’une modification effectuée à cet endroit serait ignorée au suivant.
L’édition de schéma en direct est la réponse à cela. Elle valide la modification dans votre dépôt, puis applique le DDL — ainsi, la modification survit au déploiement suivant, car celui-ci est construit à partir d’elle.
GET /api/admin/schema/status whether this backend can do it, and whether you mayPOST /api/admin/schema/plan what would happen, without doing itPOST /api/admin/schema/apply commit, then applyLe statut et le plan exigent la portée schema:read, et l’application exige schema:write, comme toutes les autres interfaces /api/admin qui nomment une portée. Un administrateur détient les deux. L’application nécessite aussi une condition de plus que la portée — voir Qui peut appliquer.
Planifier avant d’appliquer
Section intitulée « Planifier avant d’appliquer »/plan n’a pas d’effets secondaires. Envoyez le changement, et il vous indique ce que la modification
implique. Un changement sur une collection existante est un patch — ce qui a
changé, sous forme d’opérations sur des chemins de clés — et une nouvelle collection est la collection
entière :
{ "collectionId": "posts", "patch": [ { "op": "set", "path": ["properties", "subtitle"], "value": { "name": "Subtitle", "type": "string" } }, { "op": "remove", "path": ["admin", "group"] }] }Seules les clés qu’un patch nomme sont écrites dans le fichier de la collection. Tout le reste
demeure tel quel — les imports, les commentaires, le formatage, l’onClick d’une action
d’entité, une propriété partagée depuis un autre module, un enum importé d’ailleurs. Un patch
qui atteint l’intérieur de quelque chose défini en code (status: statusProperty,
enum: LOCALE_ENUM, un ...spread) est refusé avec l’expression qu’il a rencontrée, de sorte
que le changement se fasse là où ce code vit. Une collection entière envoyée pour une
collection qui existe déjà est transformée en le patch de ce qui diffère d’elle, et une clé dont
la valeur est du code n’est jamais supprimée de cette façon. Le panneau d’administration envoie
des patches. Sur la 0.23 /plan et /apply ne prennent que la collection entière telle
qu’elle devrait être au final.
$ADMIN_TOKEN est un jeton d’accès — l’accessToken renvoyé lors de la connexion — pour un compte qui détient schema:read : un administrateur, ou un rôle qui la déclare. Rien sur la machine ne le définit pour vous.
curl -X POST https://your-app/api/admin/schema/plan \ -H "authorization: Bearer $ADMIN_TOKEN" \ -H "content-type: application/json" \ -d '{"collectionId":"posts","collection":{}}'{ "applicable": true, "verdict": "safe", "changes": [ { "kind": "add-property", "verdict": "safe", "collection": "posts", "property": "subtitle", "detail": "New optional property subtitle …" } ], "statements": ["ALTER TABLE \"public\".\"posts\" ADD COLUMN IF NOT EXISTS \"subtitle\" TEXT;"], "files": ["config/collections/posts.ts", "backend/src/schema.generated.ts"]}files est chaque fichier que l’apply commite : la source de la collection, quand le backend peut l’écrire, puis le schéma généré.
Il ne s’agit pas d’un simple confort. Deux des trois verdicts sont des refus, et l’un d’eux est un refus que vous ne découvririez autrement qu’en appuyant sur le bouton sur une base de données en production.
Les trois verdicts
Section intitulée « Les trois verdicts »| Verdict | Signification |
|---|---|
safe |
Le chemin d’assurance (ensure path) au démarrage l’exprime et le résultat correspond à votre configuration. Appliqué. |
diverges |
Il s’applique en partie, laissant une base de données qui ne correspond pas à votre configuration — et rien ne le signale. Refusé. |
needs-migration |
Le chemin d’assurance ne peut pas du tout l’exprimer. Refusé. |
diverges est celui qui mérite d’être compris, car ces modifications semblent avoir fonctionné :
- Une propriété obligatoire ajoutée à une table contenant déjà des lignes arrive sous forme nullable.
NOT NULLest vérifié pour chaque ligne déjà présente, et les lignes écrites avant que la propriété n’existe n’ont aucune valeur pour celle-ci. Sur une table vide, il n’y a rien à vérifier, la contrainte est donc appliquée et cela est considéré commesafe. - Rendre obligatoire une propriété existante présente la même dynamique :
SET NOT NULLanalyse la table, c’est doncsafesur une table vide etdivergessur une table peuplée jusqu’à ce que vous effectuiez un remplissage rétroactif (backfill).
Deux modifications qui étaient autrefois diverges sont maintenant safe, car le chemin d’assurance les prend en charge :
- Une valeur ajoutée à un enum existant s’applique, via
ALTER TYPE … ADD VALUE IF NOT EXISTS. Auparavant, elle était ignorée en même temps que tout le type, et la première ligne utilisant la nouvelle valeur était rejetée par un type qui n’en avait jamais entendu parler. - Assouplir une propriété obligatoire supprime le
NOT NULL. Auparavant, il était laissé en place, de sorte que les écritures omettant la propriété échouaient toujours.
Contraintes demandées mais non appliquées
Section intitulée « Contraintes demandées mais non appliquées »Une modification peut être applicable tout en laissant non appliquée une exigence de votre configuration — une propriété obligatoire sur une table peuplée en est un exemple. Cela ne constitue pas un refus, donc cela n’apparaît pas dans changes ; cela apparaît dans withheldConstraints, avec l’obstacle et ce qui permettrait de le lever :
{ "withheldConstraints": [ { "target": "public.posts.author", "kind": "not-null", "reason": "\"author\" is required, but \"public.posts\" already holds rows …", "remedy": "Backfill the column, then apply this again." } ]}Le chemin d’assurance au démarrage signale la même chose sous forme d’avertissement. Avant que cela n’existe, une contrainte retenue l’était en silence.
needs-migration couvre tout ce que le chemin d’assurance ne peut pas faire : supprimer une collection ou
une propriété, changer le type d’une colonne (une bascule d’entier, une chaîne devenant un enum, le type
d’élément d’un tableau, la largeur d’un varchar), renommer une colonne, modifier une clé primaire, supprimer
une valeur d’enum, rendre unique une colonne existante, et changer une relation — son genre, sa cible, son
localKey, son onDelete. Un hasMany ou un hasOne dont rien ne crée la colonne de lien est refusé aussi.
Chaque refus nomme le changement et indique quoi faire à la place.
Le verdict est lu depuis le schéma que chaque côté produit — le même plan dont sont rendus
schema.generated.ts et db push — si bien qu’une modification qui change la base de données ne peut pas
être signalée comme n’étant pas un changement. Deux modifications qui ressemblent à des changements et ne
sont pas refusées (; la 0.23 signale les deux comme nécessitant une migration) :
- Renommer la clé d’une propriété en conservant sa colonne (
columnNameréglé sur l’ancienne colonne) ne déplace aucune donnée. C’estsafe; les clients API lisent le nouveau nom. - Définir, changer ou supprimer une valeur par défaut ne lie que les futures écritures. C’est
safe, et appliqué avecALTER COLUMN … SET DEFAULT/DROP DEFAULT.
Modifier uniquement la source
Section intitulée « Modifier uniquement la source »Un changement refusé peut tout de même être écrit dans le code source de votre collection et committé, en
laissant la base de données telle qu’elle est — retirer une propriété que vous ne servez plus est le cas
habituel. Envoyez /apply avec "sourceOnly": true. Rien ne s’exécute ; le message
de commit nomme ce que la base de données conserve, par exemple
chore(schema): remove sku from products (source only — column products.sku kept),
et chaque changement du plan porte une phrase sourceOnly indiquant ce qu’il laisse derrière lui —
y compris lorsqu’une colonne laissée derrière est NOT NULL sans valeur par défaut, ce qui fait
échouer toute insertion ultérieure jusqu’à ce qu’elle soit supprimée ou rendue nullable. Un
changement sans cela (déplacer une clé primaire, une relation dont rien ne crée la colonne de
lien) ne peut pas être écrit dans la seule source.
Supprimer une collection depuis le panneau d’administration suit le même chemin : /apply avec
"remove": true et "sourceOnly": true supprime le fichier de la collection et son entrée dans
index.ts, et committe les deux ; la table et ses lignes restent. C’est refusé tant qu’une autre
collection importe le fichier (une relation vers lui), en nommant l’importateur — le supprimer
empêcherait chaque collection de se charger.
Sur la 0.23 /apply ne prend ni sourceOnly ni remove, et le « Modifier uniquement la source »
du panneau écrit le fichier sans commit.
Ce qui est validé (commit)
Section intitulée « Ce qui est validé (commit) »Pas seulement le fichier de collection. Le schéma Drizzle en est généré, et un schéma obsolète casse le prochain déploiement ; les deux vont donc dans le même commit :
config/collections/<name>.ts— la collection elle-mêmebackend/src/schema.generated.ts— le schéma Drizzle
Ces chemins sont relatifs à votre projet, et non à votre dépôt. Lorsque les deux sont identiques — un projet rebase init, ce qui est le cas habituel — il n’y a pas à s’en soucier. Lorsque votre projet se trouve dans un sous-répertoire d’un dépôt plus vaste, les chemins sont préfixés par celui-ci, trouvé en remontant depuis votre répertoire de collections jusqu’au rebase.json le plus proche. Un projet sans rebase.json conserve les chemins simples.
Aucun SQL n’entre dans le commit. rebase db push et rebase db generate écrivent le leur à partir des collections à chaque exécution, dans .rebase/sql/, qui est ignoré par git.
Sur la 0.23 le commit porte aussi drizzle/schema.sql, drizzle/policies.sql et
drizzle/search.sql, écrits à la racine du projet.
Le message de commit décrit la modification plutôt que d’en annoncer une, et est attribué à la personne qui l’a effectuée, par le nom d’affichage et l’e-mail de son compte
(un utilisateur qui n’a ni l’un ni l’autre est commité sous <uid>@users.noreply.rebase.pro).
Depuis 0.25 En 0.24, les commits de l’authentification intégrée portent l’uid comme nom.
Une modification de schéma avec un auteur et un diff dans l’historique de votre projet est une chose que ni Firebase ni Supabase ne vous offrent — leurs modifications de tables sont invisibles pour votre dépôt.
Qui peut appliquer
Section intitulée « Qui peut appliquer »Détenir schema:read est suffisant pour planifier. La planification n’a aucun effet secondaire, et un job CI demandant si un changement de collection proposé est applicable en est une bonne utilisation.
L’application est un second privilège, car appliquer écrit un commit et un commit porte un auteur :
| Appelant | Planifier | Appliquer |
|---|---|---|
Une personne connectée qui détient schema:write |
oui | oui |
Une clé d’API qui détient schema:read / schema:write |
oui | non |
| La clé de service du serveur | oui | non |
Un identifiant n’est pas un auteur. api-key:7c3f… dans votre environnement de CI n’est pas une personne physique, et lui permettre d’écrire dans votre dépôt produit exactement l’historique non attribuable que cette fonctionnalité vise à remplacer.
Sur la 0.23 la limite est le rôle admin : un administrateur planifie et applique, et n’importe
quelle clé API peut planifier.
Si une modification automatisée du schéma est ce que vous souhaitez — un pipeline de migration, par exemple — activez-la délibérément :
initializeRebaseBackend({ // …the rest of your config liveSchema: { allowMachineApply: true }})ou REBASE_LIVE_SCHEMA_ALLOW_MACHINE_APPLY=true. Le commit est ensuite attribué à l’identifiant par son nom — Rebase API key (7c3f) — de sorte que la lecture de git log un mois plus tard vous indique toujours quels changements ont été effectués par une personne.
GET /api/admin/schema/status indique ce que vous pouvez faire, et pas seulement ce que le serveur prend en charge, afin qu’un panneau puisse désactiver la commande et expliquer pourquoi plutôt que de vous refuser l’accès après votre décision :
{ "enabled": true, "canPlan": true, "canApply": false, "applyRefusedCode": "SCHEMA_EDIT_REQUIRES_A_PERSON", "applyRefusedBecause": "This request is authenticated with an API key …"}Si votre projet conserve des migrations versionnées
Section intitulée « Si votre projet conserve des migrations versionnées »Appliquer ici n’écrit pas de migration, et ne le peut pas : une migration utilise le format d’Atlas avec un fichier d’intégrité, généré par un binaire externe sur une base de données temporaire, et un serveur en cours d’exécution ne dispose ni de l’un ni de l’autre.
Ce qu’il valide en revanche, c’est la collection, et c’est à partir d’elle que rebase db generate écrit la migration. La migration n’est donc qu’à une commande :
rebase db generateLe plan et le résultat l’indiquent tous deux lorsque votre projet a des migrations, car sinon l’échec est silencieux : votre base de données contient la modification et votre dépôt la décrit, mais le prochain environnement construit en rejouant les migrations ne l’aura pas, et rien ne l’aura signalé.
Un projet provisionné par boot-ensure — le runtime managé, ainsi que tout auto-hébergement laissant REBASE_MIGRATE_ON_BOOT à sa valeur par défaut — n’a besoin d’aucune migration. Ses collections constituent le schéma, et le prochain démarrage procède à la réconciliation.
Valider d’abord, appliquer ensuite
Section intitulée « Valider d’abord, appliquer ensuite »L’ordre compte et il n’est pas arbitraire.
Si le DDL s’exécutait en premier et que le commit échouait, votre base de données contiendrait une colonne que votre dépôt ne décrit pas. Le chemin d’assurance ne supprimant jamais rien, le prochain déploiement ne la retirerait pas et ne la mentionnerait pas non plus — une colonne invisible, absente de vos collections, jusqu’à ce que quelqu’un la remarque.
Valider d’abord échoue dans l’autre sens : le dépôt décrit quelque chose que la base de données ne possède pas encore. C’est l’état ordinaire de tout projet entre une modification et un déploiement, et le démarrage le réconcilie au prochain lancement.
Un échec de l’application n’est donc pas une erreur. La réponse le précise :
{ "applied": false, "applyError": "connection refused", "committed": { "sha": "1a2b3c4de", "branch": "main" }, "summary": "Committed 1a2b3c4de on main, but the database was not changed. The change will be applied on the next boot."}Où cela fonctionne
Section intitulée « Où cela fonctionne »L’édition de schéma en direct réécrit le code source de vos collections avec le même éditeur que celui des enregistrements du panneau qui ne touchent que la source (il nécessite que ts-morph soit installé). D’où vient ce code source détermine où elle fonctionne :
- Sur le disque (
collectionsDir, le défaut) : elle fonctionne là où fonctionne l’éditeur source seule. Cet éditeur est désactivé avecNODE_ENV=production, sauf si le serveur définitschemaEditor: true. - Dans un dépôt que vous nommez (
liveSchema.repository, ouREBASE_LIVE_SCHEMA_REPOSITORYpour un bundle) : le code source est lu depuis GitHub et la modification y est validée en retour ; elle fonctionne donc partout, production comprise. C’est l’option à activer pour un bundle compilé.
Tout ce qui précède décrit Postgres, où une modification de schéma implique du DDL. Sur MongoDB, il n’y a pas de table à modifier : ajouter une propriété n’ajoute rien, en supprimer une ne supprime rien, et un document écrit hier est toujours valide demain.
Chaque modification est donc applicable, rien n’est jamais refusé, et le plan ne contient aucune instruction — le commit est la modification. Le panneau indique « Valider » plutôt que « Valider et appliquer », et ne prétend pas que quelque chose a été exécuté sur la base de données.
La seule chose qui mérite une lecture attentive est une suppression. Sur Postgres, la suppression d’une propriété est refusée car elle entraînerait la suppression d’une colonne. Sur MongoDB, le champ reste dans chaque document qui le possède ; votre API cesse simplement de le renvoyer. La modification l’indique explicitement plutôt que de vous laisser supposer le comportement relationnel.
| Déploiement | Fonctionne |
|---|---|
rebase dev sur votre machine |
oui |
| Auto-hébergement avec le projet et son arbre de travail git montés | oui, lorsque NODE_ENV n’est pas production, ou avec schemaEditor: true |
| Un bundle compilé : un auto-hébergement servant un build, ou l’image publiée | uniquement s’il y souscrit avec REBASE_LIVE_SCHEMA_REPOSITORY — voir Depuis un bundle compilé |
Tout serveur avec liveSchema.repository configuré |
oui, NODE_ENV=production compris |
Un bundle étant un résultat compilé, il ne contient pas le code source des collections, et le runtime qui le sert désactive l’éditeur source seule. À moins qu’il ne nomme un dépôt, /api/admin/schema/status répond enabled: false avec le code SCHEMA_EDITOR_DISABLED : modifiez les collections en développement, validez (commit) et déployez.
Depuis un bundle compilé
Section intitulée « Depuis un bundle compilé »Un bundle n’a pas de code à configurer : il y souscrit par des variables d’environnement. Rien ne change pour un bundle qui ne les définit pas.
REBASE_LIVE_SCHEMA_REPOSITORY=acme/storefront # turns it onREBASE_LIVE_SCHEMA_GITHUB_TOKEN=github_pat_… # or the three _GITHUB_APP_* variables# REBASE_LIVE_SCHEMA_BRANCH=main # default# REBASE_LIVE_SCHEMA_COLLECTIONS_PATH=config/collections # defaultLa liste complète figure dans Configuration. Un dépôt nommé sans identifiant utilisable (aucun, les deux types, la moitié d’une GitHub App), ou une valeur qui n’est pas de la forme owner/name, refuse de démarrer, plutôt que de servir un éditeur qui échoue quand quelqu’un appuie sur Appliquer.
Ce qui reste identique lorsqu’il est activé :
- C’est une interface
/api/admin: aucune réponse du tout sansschema:read, et/applyexigeschema:writeet une personne (voir Qui peut appliquer). - La seule cible d’écriture est le dépôt que vous avez nommé. L’éditeur source seule, qui écrit des fichiers à côté du processus, reste désactivé.
- Chaque modification est planifiée, présentée, validée (commit), puis appliquée, exactement comme sur votre machine.
Une chose diffère. Le déploiement en cours continue de servir les collections avec lesquelles il a été construit, jusqu’à ce que votre pipeline construise et déploie le commit. La base de données a la modification tout de suite ; le panneau d’administration et l’API voient la nouvelle définition de collection après ce déploiement.
Un déploiement ne fait donc qu’une modification par déploiement. Une fois un commit arrivé, l’application suivante est refusée avec SCHEMA_CHANGE_FAILED, qui nomme le commit, jusqu’à ce qu’un processus exécutant ce commit remplace celui-ci. Une seconde modification planifiée contre les collections que ce déploiement exécute encore validerait des fichiers de schéma générés qui omettent la première. La planification reste disponible entre-temps.
Valider dans un dépôt distant
Section intitulée « Valider dans un dépôt distant »liveSchema.repository fait valider chaque modification par le serveur dans un dépôt GitHub via l’API Git Data, au lieu d’exécuter git commit dans un arbre de travail. Il ne dépend pas de l’activation de l’éditeur source seule : avec un dépôt configuré, l’édition en direct fonctionne avec NODE_ENV=production et avec schemaEditor: false, car rien n’est écrit sur cette machine. Un bundle le définit par les variables ci-dessus.
initializeRebaseBackend({ // …the rest of your config liveSchema: { repository: { kind: "github", owner: "acme", repo: "storefront", branch: "main", // Where the collection source lives in that repository. // Defaults to "config/collections". collectionsPath: "config/collections", auth: { kind: "token", token: process.env.GITHUB_TOKEN! } } }})Le répertoire des collections est alors lu depuis le dépôt, réécrit avec le même éditeur que celui qui s’exécute localement, et ce qui a changé est validé via l’API Git Data : un blob par fichier, une arborescence (tree), un commit et une mise à jour de référence. Rien n’est cloné et rien n’est laissé sur le disque. Tout le répertoire est lu, pas seulement le fichier modifié : une relation peut donc cibler une autre collection (son fichier est importé), et supprimer une collection fonctionne comme en local : refusé tant qu’une autre collection l’importe, et retirée de index.ts dans le même commit.
auth prend un jeton ou une installation de GitHub App :
auth: { kind: "app", appId: "123456", privateKey: process.env.GITHUB_APP_PRIVATE_KEY!, installationId: "987654"}Utilisez le jeton pour un projet unique validant dans un dépôt que vous possédez déjà — mettre en place une App pour que votre propre serveur puisse y valider des modifications représente beaucoup de formalités pour un identifiant d’une ligne. Utilisez l’App pour un plan de contrôle détenant une seule clé sur plusieurs projets, ce que fait Rebase Cloud : une App, une installation par projet, et aucun secret par client à renouveler.
Le jeton a besoin de contents: read and write sur ce dépôt, et rien d’autre.
Sans liveSchema.repository, le commit est un simple git commit dans l’arbre de travail qui contient collectionsDir : rien à authentifier, aucun jeton, aucun réseau.
Deux éléments garantissent la sécurité de l’exécution sur un dépôt dans lequel quelqu’un d’autre travaille :
- Il n’indexe (stage) que les fichiers qu’il a générés. Un commit de schéma qui embarquerait du travail à moitié terminé serait un commit que personne ne pourrait relire, et l’opération est refusée d’emblée si l’arborescence comporte déjà l’un de ses propres fichiers modifié.
- Le chemin distant ne force jamais la mise à jour d’une référence. Si quelque chose a été envoyé pendant la construction du commit, la mise à jour est rejetée — perdre le commit de quelqu’un silencieusement est pire que d’échouer.
- Modifications additives uniquement. Tout le reste est refusé avec un motif, car le chemin d’assurance est la seule chose qui modifie un schéma et il ne peut qu’ajouter.
- Aucun fichier de migration n’est écrit. Un projet provisionné par boot-ensure n’en a pas besoin ; un projet provisionné par migrations doit exécuter
rebase db generate, qui en produit un via Atlas avec le hachage d’intégrité requis par Atlas. - Postgres uniquement. La fonctionnalité est détectée sur le pilote, et les autres moteurs répondent
SCHEMA_EDITING_UNSUPPORTED.
Liens connexes
Section intitulée « Liens connexes »- Génération de schéma — les mêmes modifications depuis la ligne de commande
- Définition des collections — ce que l’éditeur réécrit
- Studio — le panneau derrière lequel se trouvent ces routes