La version 0.25 modifie également le comportement de quelques appels existants, sans toutefois les casser. Vérifiez ces points si votre application dépend de l’ancien comportement :
Les wrappers de champs de plugins se composent. Le fieldBuilder.wrap de chaque plugin est rendu, chacun enveloppant le champ laissé par les plugins précédents, au lieu de seulement le dernier.
Les surcharges de composants se composent. La surcharge avec wrap: true d’une collection reçoit la surcharge du même composant définie par l’application en tant qu’OriginalComponent, et non le composant intégré de base.
rebase.auth, rebase.admin, rebase.functions et rebase.cron sur le serveur atteignent les routes du backend dans le même processus (in-process), de sorte que les middlewares ajoutés à votre propre application Hono ne s’exécutent plus dessus.
Le socket temps réel ne répond qu’à son propre chemin (/ pour une API sur /api). Un client qui contactait un autre chemin sur le même serveur reçoit une 404, ou atteint votre propre écouteur upgrade.
Le socket obtient son jeton de la même manière que HTTP, de sorte que client.setAuthTokenGetter() l’authentifie désormais, et après une déconnexion, il ne bascule plus vers l’option token.
Le répertoire d’un bucket local nommé se trouve à côté de STORAGE_PATH, sous la forme <STORAGE_PATH>__<key>, et non à côté du chemin de base du runtime lui-même. Si vous définissez STORAGE_PATH et qu’un bucket local nommé contient déjà des fichiers, le démarrage désigne l’ancien répertoire, et STORAGE_PATH__<KEY> continue de le servir. Un fichier compose issu de rebase init ou rebase eject monte son volume sur /uploads. Ce volume ne contient que le bucket par défaut, et l’utilisateur de l’image ne peut pas y écrire. Montez-le sur /storage avec STORAGE_PATH: /storage/uploads, comme le font les nouveaux fichiers compose.
Un service qu’appelle une requête et qui est injoignable reçoit pour réponse 503 SERVICE_UNAVAILABLE, comme un bucket S3 hors service ou un hôte qu’une fonction appelle avec fetch(). C’était une 500 dont le code était l’erreur de socket (ECONNREFUSED), ou INTERNAL_ERROR.
rebase db push conserve les contraintes CHECK et EXCLUDE de la table d’une collection, alors que le premier push après rebase schema introspect les supprimait. La vérification de destruction de données compte désormais aussi la suppression de l’une d’elles, de sorte que rebase db migrate --refuse-destructive s’arrête sur une migration qui en supprime une. Supprimez à la main une contrainte dont vous ne voulez plus.
Ces noms fonctionnent toujours, émettent un avertissement unique à l’exécution et disposent d’un remplaçant :
Obsolète
Utiliser
loadFunctionsFromDirectory, loadFunctionsWithDiagnostics
loadFunctions(dir, { onSkip })
loadCronJobsFromDirectory, loadCronJobsWithDiagnostics
loadCrons(dir, { onSkip }), puis declareCrons(crons) là où l’ancienne paire les déclarait
client.ws.setAuthTokenGetter()
client.setAuthTokenGetter()
plugin hooks.modifyCollectionAsync (jamais appelé)
hooks.modifyCollection, avec les données récupérées avant le rendu de <Rebase>
plugin lifecycle.onCollectionChange (jamais appelé)
context.data.collection(slug).listen(…) depuis lifecycle.onMount
DatabaseAdapter.mountRoutes, BackendBootstrapper.mountRoutes (jamais appelés)
une fabrique de routes que votre application monte
:::
?count= sur une liste REST sert désormais de commutateur pour le comptage sous-jacent à meta.total : ?count=none l’ignore et ?count=exact est la valeur par défaut. Toute autre valeur est refusée avec une erreur 400 INVALID_COUNT. Auparavant, ?count=5 filtrait les lignes dont la colonne count valait 5.
Ce qu’il faut changer : rien avec le SDK, qui envoie déjà les filtres sur une colonne nommée count à l’intérieur de ?where=. Un appel REST écrit à la main qui filtre sur une telle colonne doit déplacer le filtre à cet endroit :
GET /api/data/products?count=5
GET /api/data/products?where={"count":["==",5]}
Un nouveau SDK face à un ancien serveur : iterate() et findAll() envoient ?count=none sur chaque page, tout comme find({ count: "none" }). Un serveur en 0.24 l’interprète comme un filtre sur une colonne nommée count. Si la collection ne possède pas une telle colonne, il répond 400 UNKNOWN_FILTER_FIELD, et le SDK réémet la lecture sans ce paramètre. Si la collection possède une colonne texte nommée count, l’ancien serveur filtre à la place les lignes dont le count vaut 'none', sans erreur : la page revient incomplète ou vide et le parcours s’arrête prématurément. Mettez à niveau le serveur avant le SDK lorsqu’une collection comporte une telle colonne.
Consultez Skipping the count .
list_collections envoyait son listing en double : mis en forme dans le bloc de texte, et une seconde fois sous forme de structuredContent. Sur un projet volumineux, les deux réunis dépassaient le contexte d’un modèle. Il envoie désormais le listing une seule fois, dans le bloc de texte, sous forme de JSON compact. Les autres outils conservent leur structuredContent.
Ce qu’il faut changer : rien pour un assistant, qui lit le texte. Un programme qui lisait le listing depuis structuredContent doit désormais analyser le bloc de texte ; son contenu est identique. Consultez The remote endpoint .
Le point d’entrée du package réexportait la table de traduction espagnole, es, ce qui incluait les chaînes espagnoles dans chaque bundle généré à partir de celui-ci. Les traductions autres que l’anglais sont désormais récupérées lorsqu’un utilisateur sélectionne sa langue, et es n’est plus exporté. en l’est toujours.
Ce qu’il faut changer : rien, sauf si vous importiez es. Pour modifier les chaînes de l’interface d’administration, passez des surcharges à <Rebase translations={…}> ; pour figer la langue, passez locale.
grep -rnE "import \{[^}]*\bes\b[^}]*\} from \" @rebasepro/app \" " --exclude-dir=node_modules .
Le middleware CORS répondait auparavant lui-même à toutes les requêtes OPTIONS avec une réponse vide 204. Il ne répond désormais qu’aux requêtes préalables (preflights) CORS (une requête OPTIONS contenant Access-Control-Request-Method) ; toute autre requête OPTIONS atteint sa route. Une route sans gestionnaire d’OPTIONS renvoie une 404. OPTIONS /api/storage/tus retourne désormais les capacités TUS (Tus-Version, Tus-Extension, Tus-Max-Size), comme la documentation l’a toujours indiqué.
Qui est concerné : un client, un proxy ou un health check qui sondait une route avec OPTIONS et interprétait le 204 comme “elle existe”. Les pré-vérifications des navigateurs restent inchangées.
Ce qu’il faut changer : sonder avec la vraie méthode de la route (HEAD ou GET).
Une table, table de jonction ou type enum dont les noms sont identiques après la troncature à 63 octets de Postgres échouait auparavant lors de db push avec l’erreur “already exists”. Deux propriétés qui se résolvent vers la même colonne (myTitle et my_title), ainsi qu’une cible de relation target écrite sous forme de chaîne (target: "authors"), échouaient plus tard ou de façon confuse. Chacun de ces cas est désormais refusé dès le chargement de la configuration, et le message précise ce qu’il convient de renommer ou d’écrire.
Qui est concerné : un projet qui déclare une telle paire. Il ne pouvait pas non plus mener db push à terme auparavant, cette modification avance donc simplement l’échec plus tôt.
Ce qu’il faut changer : renommer l’un des noms en conflit, et écrire la cible de relation comme étant la collection, et non son slug :
relation: { kind: "belongsTo", target: "authors" },
relation: { kind: "belongsTo", target: () => authors },
Le client cron du SDK utilisait par défaut /api/cron, l’alias obsolète. Il appelle désormais /api/admin/cron, où réside toute la surface d’administration.
Qui est concerné : un serveur (ou une règle de proxy) qui ne sert que l’ancien chemin. Les appels cron du SDK renvoient une 404 face à lui.
Ce qu’il faut changer : mettre à niveau le serveur en même temps que le SDK, ou conserver explicitement l’ancien chemin :
const client = createRebaseClient ({ baseUrl, cron: { cronPath: "/api/cron" } });
En mode hors ligne, findAll() et iterate() renvoyaient la portion de la collection que cet appareil avait mise en cache comme s’il s’agissait de l’intégralité, et count() comptait les lignes en cache. Une page que cet appareil n’a jamais récupérée échoue désormais avec OFFLINE, tout comme un count() sans comptage mis en cache depuis le serveur.
Qui est concerné : une application qui affiche ces lectures en mode hors ligne.
Ce qu’il faut changer : intercepter l’erreur là où l’application affichait des résultats partiels, et afficher ce dont elle dispose accompagné d’une mention, ou demander une page que l’appareil possède déjà.
rows = await client.data.posts. findAll ();
if (e instanceof RebaseApiError && e.code === "OFFLINE" ) rows = cached;
ClientStorageSourceRegistry.getOrDefault(key) renvoyait la source par défaut pour une clé qu’il ne connaissait pas ; ainsi, une propriété désignant une source que le panneau n’avait pas encore chargée effectuait son téléversement vers le bucket par défaut. Il lève désormais une exception.
Qui est concerné : le code qui appelle getOrDefault avec une clé qu’il n’a pas enregistrée. L’interface d’administration elle-même ne le fait plus.
Ce qu’il faut changer : enregistrer la source au préalable, ou utiliser get(key), qui renvoie undefined pour une clé inconnue, lorsqu’une source manquante est attendue.
TextField déclarait maxRows, et MultiSelect déclarait onChange, name, position, endAdornment, multiple et padding. Aucune de ces props n’a jamais été lue, en passer une n’avait donc aucun effet. Elles ont été retirées des types, et en passer une constitue désormais une erreur de type.
Qui est concerné : le code qui passait l’une d’entre elles. Rien ne change au moment de l’exécution.
Ce qu’il faut changer : supprimer la prop. Pour les changements d’un multi-select, utilisez onValueChange.
La collection des utilisateurs (celle avec auth activé) répond désormais par une erreur 400 INVALID_CONFLICT_TARGET lors d’un upsert, sur tous les points d’entrée : ?on_conflict=, /bulk avec upsert: true, une opération upsert dans _batch, les méthodes upsert() et createMany(…, { upsert: true }) du SDK, et le socket temps réel. Un upsert ne découvre qu’au cours de l’écriture s’il s’agit d’une création ou d’une mise à jour, de sorte qu’il n’appliquait aucun des deux ensembles de règles : aucun hachage de mot de passe, aucune normalisation d’e-mail, pas de onCreateUser ni d’invitation lors d’une création, et aucune vérification du dernier administrateur lors d’une mise à jour.
Qui est concerné : le code qui effectue des opérations d’upsert sur les utilisateurs, par exemple une synchronisation exécutant client.data.users.upsert(user, { onConflict: ["email"] }).
Ce qu’il faut changer : rechercher l’utilisateur, puis créer ou mettre à jour.
const user = { email: "ada@example.com" , displayName: "Ada" };
const { data : [ found ] } = await client.data.users. find ({ where: { email: [ "==" , user.email] }, limit: 1 });
if ( ! found) await client.data.users. create (user);
else await client.data.users. update ( String (found.id), user);
Une lecture spécifiant à la fois page et offset est désormais refusée. Le serveur répond par 400 PAGE_WITH_OFFSET, et le SDK refuse la combinaison avant toute requête, avec une RebaseClientError portant le même code : par HTTP, in-process (rebase.data) et hors ligne. Auparavant, page l’emportait et offset était ignoré sans avertissement.
Qui est concerné : le code qui envoie les deux, par exemple un find({ page, offset }) ou un appel REST avec ?page=3&offset=40. Une telle lecture ne tenait déjà compte que de page.
Ce qu’il faut changer : envoyer l’un des deux. Pour conserver ce que la lecture renvoyait auparavant, supprimez offset.
await client.data.products. find ({ page: 3 , offset: 40 , limit: 20 });
await client.data.products. find ({ page: 3 , limit: 20 });
Sur une table dont le nom comporte 42 octets ou plus, les trois stratégies admin-write par défaut (…_default_admin_write_insert, _update et _delete) sont désormais nommées <head>_<hash8>_<op>, d’au plus 63 octets. Postgres ne conservant que 63 octets d’un nom, les trois anciens noms étaient tronqués au même nom et une seule stratégie, celle de DELETE, existait : les administrateurs ne pouvaient ni insérer ni mettre à jour.
Qui est concerné : un projet possédant un nom de table de 42 octets ou plus, ainsi que tout élément faisant référence à ces stratégies par leur nom, comme une migration ou un script qui les supprime. Les anciens noms n’ont jamais existé dans la base de données.
Ce qu’il faut changer : rien pour la base de données. Le prochain démarrage ou rebase db push supprime la stratégie tronquée et crée une stratégie par opération. Mettez à jour tout script qui mentionne les anciens noms de stratégies.
La durée de vie par défaut du refresh token est désormais de 30 jours sur tous les points d’entrée. Un serveur démarré avec le runtime rebase-server (ou rebase start) utilisait 400 jours dès lors que JWT_REFRESH_EXPIRES_IN n’était pas défini ; une configuration manuelle avec initializeRebaseBackend utilisait déjà 30 jours.
La durée de vie est glissante : chaque rafraîchissement émet un jeton valable pour 30 jours supplémentaires, de sorte qu’un utilisateur qui ouvre l’application au moins une fois par mois reste connecté. Un utilisateur inactif pendant plus longtemps doit se reconnecter. Les jetons émis avant la mise à niveau conservent la date d’expiration avec laquelle ils ont été émis jusqu’à leur prochain rafraîchissement.
Qui est concerné : un déploiement utilisant le runtime qui n’a jamais configuré JWT_REFRESH_EXPIRES_IN, et dont les utilisateurs reviennent moins d’une fois par mois.
Ce qu’il faut changer : rien pour adopter la nouvelle valeur par défaut. Pour conserver le comportement précédent, définissez-la explicitement :
JWT_REFRESH_EXPIRES_IN = 400d
ou auth: { refreshExpiresIn: "400d" } dans un backend éjecté. Les navigateurs plafonnent le cookie de rafraîchissement à 400 jours, une valeur plus longue n’apporte donc rien en mode cookie.
POST /api/admin/users ainsi qu’une création sur la collection des utilisateurs via l’API de données (POST /api/data/users, /bulk, un batch, le MCP create_document, un enregistrement via socket) refusent désormais un password non conforme à la politique avec l’erreur 400 WEAK_PASSWORD, comme le faisaient déjà l’inscription, les réinitialisations et PUT /api/admin/users/:uid. Rien n’est écrit et aucun hook de création ne s’exécute. Sur un /bulk, le message commence par Row <n>: , à l’instar des autres erreurs de ligne d’un bulk. Une création sans mot de passe reste inchangée : un mot de passe est généré, ou une invitation est envoyée.
Qui est concerné : le code qui crée des utilisateurs avec un mot de passe choisi ailleurs, comme un import ou une synchronisation depuis un autre système.
Ce qu’il faut changer : pour importer des comptes dont les mots de passe ont été définis ailleurs, envoyez skipPasswordPolicy: true dans chaque corps de création (sur chaque ligne d’un /bulk), en tant qu’administrateur ou à l’aide de la clé de service :
await client.admin. createUser ({ email, password: legacyPassword, skipPasswordPolicy: true });
Pour tout autre appelant, cet indicateur n’exempte de rien. Il n’est jamais stocké et n’atteint jamais onCreateUser ni onAdminCreateUser. L’import du panneau d’administration l’envoie pour vous sur les lignes comportant un password. Un AuthAdapter personnalisé reçoit la décision sous la forme du nouvel argument en troisième position de prepareUserCreation ({ mayWaivePasswordPolicy }) ; un adaptateur qui l’ignore conserve son comportement actuel. Pour modifier la politique elle-même, transmettez auth.hooks.validatePasswordStrength.
Une écriture envoyant "" pour une propriété string dotée de validation.required renvoie désormais 400 VALIDATION_CONSTRAINT, avec une entrée dans details.violations valant { field, code: "required" } : la même réponse qu’en cas de valeur manquante. Cela s’applique aux créations et aux mises à jour sur tous les points d’entrée (REST, bulk, batch, upsert, socket temps réel, MCP), et même lorsque la propriété dispose d’une defaultValue, dans la mesure où une valeur par défaut complète une clé absente, et non une clé vide. Le panneau d’administration écrit déjà null pour un champ effacé, il n’est donc pas affecté.
Qui est concerné : le code qui écrit "" dans un champ texte obligatoire, par exemple un formulaire ou un import qui transmet chaque colonne, qu’elle soit remplie ou non.
Ce qu’il faut changer : envoyer une valeur, omettre la clé lors de la création lorsqu’une defaultValue doit s’appliquer, ou retirer required des champs qui peuvent légitimement être vides. Les collections qui déclarent un beforeSave ne sont pas vérifiées à la frontière (le hook pouvant renseigner la valeur), comme auparavant. Le code serveur écrivant in-process (rebase.data) n’est pas concerné.
Tous deux étaient les moteurs sous-jacents aux appels clients.
new QueryBuilder(collection).where(…) → client.data.<collection>.where(…);
les utilitaires or, and, not et cond sont toujours exportés.
createBackups(transport) → client.backups (mêmes list, download,
downloadUrl, stream). CreateBackupsOptions a disparu en même temps ;
BackupStream est toujours exporté.
client.ws est inchangé.
const storageSource = useBackendStorageSource ({ apiUrl, getAuthToken });
// after — the client you already pass to the admin
const storageSource = client.storage; // default source
const media = client. createStorageSource ( "media" ); // a named source
La source du client transmet également public et context lors des téléversements (limites de propriétés vérifiées côté serveur) ainsi que storageId pour les sources nommées, ce que le hook ne faisait pas.
Supprimez la clé de storage: { type: "s3" | "gcs", … } ; TypeScript la signale comme une propriété inconnue. Elle n’a jamais défini la durée de vie d’une URL reçue par un navigateur. Pour modifier la durée de validité de l’URL d’un fichier privé, définissez storageDownloadTokenTtl (en secondes ; STORAGE_DOWNLOAD_TOKEN_TTL lors d’un déploiement en bundle). Le code serveur qui appelle directement controller.getSignedUrl() obtient une URL valable une heure.
Un projet qui ne déclarait que des buckets nommés dans config/resources.ts, comme
bucket("media", { engine: "s3" }), voyait le premier promu bucket par défaut :
les téléversements dont la propriété ne nomme aucun storageSource allaient vers lui. Le bucket par défaut
lit désormais toujours sa propre configuration, quoi qu’il soit déclaré : STORAGE_TYPE,
STORAGE_PATH (ou ./uploads), et les S3_* / GCS_* sans suffixe.
Si ces téléversements doivent continuer d’aller vers le bucket nommé, dites-le :
export const media = bucket ( "media" , { engine: "s3" , default: true });
Sans cette marque, en production, ces téléversements répondent
501 STORAGE_NOT_CONFIGURED tant que le bucket par défaut n’est pas lié (STORAGE_TYPE=s3 et
S3_BUCKET, ou STORAGE_TYPE=gcs et GCS_BUCKET), et le démarrage journalise un avertissement
qui nomme les deux solutions. Les fichiers qu’a écrits le bucket par défaut promu se trouvent dans le bucket nommé ;
avec la marque, ils sont servis par le bucket par défaut comme avant. En développement, les
fichiers déjà présents dans ./uploads sont de nouveau servis.
Le démarrage refuse aussi deux sources qui se résolvent vers un même répertoire (ou l’un dans
l’autre), ou vers un même bucket sur un même point de terminaison. Donnez à la source nommée un emplacement
qui lui soit propre (STORAGE_PATH__MEDIA, S3_BUCKET__MEDIA), ou marquez-la comme bucket par défaut.