0.25 ändert außerdem die Funktionsweise einiger bestehender Aufrufe, ohne diese zu brechen.
Prüfen Sie diese, falls Ihre App auf das alte Verhalten angewiesen ist:
Plugin-Feld-Wrapper sind komponierbar. Das fieldBuilder.wrap jedes Plugins wird gerendert,
jeweils um das Feld herum, das die Plugins davor hinterlassen haben, anstatt nur das letzte.
Komponenten-Overrides sind komponierbar. Der wrap: true-Override einer Collection erhält
den app-weiten Override derselben Komponente als OriginalComponent, nicht die
integrierte Version.
rebase.auth, rebase.admin, rebase.functions und rebase.cron auf dem Server erreichen
die Backend-Routen In-Process, sodass Middleware, die Sie zu Ihrer eigenen Hono-App
hinzugefügt haben, für diese nicht mehr ausgeführt wird.
Der Realtime-Socket antwortet nur noch auf seinem eigenen Pfad (/ für eine API unter /api).
Ein Client, der einen anderen Pfad auf demselben Server anwählt, erhält einen 404 oder
Ihren eigenen upgrade-Listener.
Der Socket erhält sein Token wie HTTP, sodass client.setAuthTokenGetter() ihn nun
authentifiziert und nach einer Abmeldung nicht mehr auf die Option token zurückgreift.
Das Verzeichnis eines benannten lokalen Buckets befindet sich neben STORAGE_PATH, als
<STORAGE_PATH>__<key>, nicht neben dem Basispfad der Runtime selbst. Wenn Sie
STORAGE_PATH setzen und ein benannter lokaler Bucket bereits Dateien enthält, benennt
der Startvorgang das alte Verzeichnis, und STORAGE_PATH__<KEY> stellt es weiterhin bereit.
Eine Compose-Datei von rebase init oder rebase eject bindet ihr Volume unter /uploads ein.
Dieses Volume enthält nur den Standard-Bucket, und der Benutzer des Images kann nicht darauf
schreiben. Binden Sie es stattdessen unter /storage mit STORAGE_PATH: /storage/uploads ein,
wie es die neuen Compose-Dateien tun.
Ein Dienst, den eine Anfrage aufruft und der nicht erreichbar ist, wird mit
503 SERVICE_UNAVAILABLE beantwortet, etwa ein ausgefallener S3-Bucket oder ein Host,
den eine Function per fetch() aufruft. Es war ein 500, dessen Code der Socket-Fehler
(ECONNREFUSED) war, oder INTERNAL_ERROR.
rebase db push behält die CHECK- und EXCLUDE-Constraints auf der Tabelle einer
Collection, wo der erste Push nach rebase schema introspect sie gelöscht hat.
Die Prüfung auf datenzerstörende Änderungen zählt nun auch das Löschen eines solchen Constraints,
sodass rebase db migrate --refuse-destructive bei einer Migration anhält, die einen löscht.
Einen Constraint, den Sie nicht mehr wollen, löschen Sie von Hand.
Diese Namen funktionieren weiterhin, geben zur Laufzeit einmalig eine Warnung aus und haben einen Ersatz:
Veraltet
Verwenden
loadFunctionsFromDirectory, loadFunctionsWithDiagnostics
loadFunctions(dir, { onSkip })
loadCronJobsFromDirectory, loadCronJobsWithDiagnostics
loadCrons(dir, { onSkip }), danach declareCrons(crons), wo das alte Paar sie deklariert hat
client.ws.setAuthTokenGetter()
client.setAuthTokenGetter()
Plugin hooks.modifyCollectionAsync (wurde nie aufgerufen)
hooks.modifyCollection, wobei die Daten vor dem Rendern von <Rebase> abgerufen werden
Plugin lifecycle.onCollectionChange (wurde nie aufgerufen)
context.data.collection(slug).listen(…) aus lifecycle.onMount
DatabaseAdapter.mountRoutes, BackendBootstrapper.mountRoutes (wurden nie aufgerufen)
eine Route-Factory, die Ihre App mountet
:::
?count= bei einer REST-Liste ist nun der Schalter für den Zählwert hinter meta.total:
?count=none überspringt ihn und ?count=exact ist der Standard. Jeder andere Wert wird
mit 400 INVALID_COUNT abgelehnt. Zuvor filterte ?count=5 die Zeilen, deren count-Spalte
den Wert 5 hatte.
Was zu ändern ist: Nichts bei Verwendung des SDKs, da dieses einen Filter auf eine Spalte
namens count bereits innerhalb von ?where= sendet. Ein manuell geschriebener REST-Aufruf,
der nach einer solchen Spalte filtert, muss den Filter dorthin verschieben:
GET /api/data/products?count=5
GET /api/data/products?where={"count":["==",5]}
Neues SDK gegen einen alten Server: iterate() und findAll() senden ?count=none
auf jeder Seite, ebenso find({ count: "none" }). Ein 0.24-Server interpretiert dies als
Filter auf eine Spalte namens count. Sofern die Collection keine solche Spalte besitzt,
antwortet er mit 400 UNKNOWN_FILTER_FIELD, woraufhin das SDK den Lesevorgang erneut
ohne diesen Parameter sendet. Hat die Collection jedoch eine Text -Spalte namens count,
filtert der alte Server stattdessen fehlerfrei nach Zeilen, deren count gleich 'none' ist:
Die Seite wird unvollständig oder leer zurückgegeben und ein Durchlauf bricht vorzeitig ab.
Aktualisieren Sie den Server vor dem SDK, wenn eine Collection eine solche Spalte besitzt.
Siehe Skipping the count .
list_collections sendete die Auflistung bisher doppelt: formatiert im Textblock und
nochmals als structuredContent. Bei einem großen Projekt überstiegen beide zusammen
das Kontextfenster eines Modells. Nun wird die Auflistung nur noch einmal im Textblock
als kompaktes JSON gesendet. Die anderen Tools behalten ihr structuredContent.
Was zu ändern ist: Nichts für Assistenten, die den Text lesen. Ein Programm, das die
Auflistung aus structuredContent ausgelesen hat, parst stattdessen den Textblock;
der Inhalt ist identisch. Siehe The remote endpoint .
Der Einstiegspunkt des Pakets re-exportierte bisher die spanische Übersetzungstabelle es,
wodurch die spanischen Texte in jedem daraus erstellten Bundle landeten. Übersetzungen
außer Englisch werden nun erst geladen, wenn ein Benutzer seine Sprache auswählt, und
es wird nicht mehr exportiert. en wird weiterhin exportiert.
Was zu ändern ist: Nichts, es sei denn, Sie haben es importiert. Um die Texte des
Admins anzupassen, übergeben Sie Overrides an <Rebase translations={…}>; um die Sprache
festzulegen, übergeben Sie locale.
grep -rnE "import \{[^}]*\bes\b[^}]*\} from \" @rebasepro/app \" " --exclude-dir=node_modules .
Die CORS-Middleware beantwortete bisher jeden OPTIONS-Request selbst mit einem leeren
204. Jetzt beantwortet sie nur noch CORS-Preflights (ein OPTIONS-Request mit
Access-Control-Request-Method); jeder andere OPTIONS-Request erreicht seine Route. Eine
Route ohne OPTIONS-Handler antwortet mit 404. OPTIONS /api/storage/tus liefert nun
die TUS-Fähigkeiten (Tus-Version, Tus-Extension, Tus-Max-Size) zurück, wie es die
Dokumentation schon immer beschrieben hat.
Wer betroffen ist: Ein Client, Proxy oder Health-Check, der eine Route mit OPTIONS
abgefragt und den 204-Status als “existiert” interpretiert hat. Die Preflights von
Browsern sind unverändert.
Was zu ändern ist: Führen Sie die Prüfung mit der tatsächlichen Methode der Route
durch (HEAD oder GET).
Eine Tabelle, Junction-Tabelle oder ein Enum-Typ, deren Namen nach Postgres’
63-Byte-Kürzung identisch sind, schlugen zuvor bei db push mit “already exists” fehl.
Zwei Properties, die in dieselbe Spalte aufgelöst werden (myTitle und my_title),
sowie ein Relations-target, das als String geschrieben wurde (target: "authors"),
schlugen später oder mit unklaren Meldungen fehl. Beides wird nun bereits beim Laden
der Konfiguration abgewiesen, und die Fehlermeldung gibt an, was umbenannt oder
geschrieben werden muss.
Wer betroffen ist: Ein Projekt, das ein solches Paar deklariert. Ein db push
konnte davor ebenfalls nicht erfolgreich abgeschlossen werden; der Fehler tritt
nun lediglich früher auf.
Was zu ändern ist: Benennen Sie einen der kollidierenden Namen um und definieren
Sie ein Relations-Target als Collection, nicht als deren Slug:
relation: { kind: "belongsTo", target: "authors" },
relation: { kind: "belongsTo", target: () => authors },
Der Cron-Client des SDKs nutzte standardmäßig /api/cron, den veralteten Alias.
Er ruft nun /api/admin/cron auf, wo sich alle Admin-Oberflächen befinden.
Wer betroffen ist: Ein Server (oder eine Proxy-Regel), der nur den alten Pfad
bedient. Die Cron-Aufrufe des SDKs liefern gegen diesen einen 404.
Was zu ändern ist: Aktualisieren Sie den Server zusammen mit dem SDK, oder behalten
Sie den alten Pfad explizit bei:
const client = createRebaseClient ({ baseUrl, cron: { cronPath: "/api/cron" } });
Im Offline-Modus gaben findAll() und iterate() bisher den Teil der Collection zurück,
den das Gerät zwischengespeichert hatte, als wäre es die gesamte Collection, und count()
zählte lediglich die zwischengespeicherten Zeilen. Eine Seite, die das Gerät nie abgerufen
hat, schlägt nun mit OFFLINE fehl, ebenso wie ein count(), für das kein Zählwert
vom Server zwischengespeichert wurde.
Wer betroffen ist: Eine App, die solche Lesevorgänge offline anzeigt.
Was zu ändern ist: Fangen Sie den Fehler dort ab, wo die App Teilergebnisse dargestellt
hat, und zeigen Sie die vorhandenen Daten mit einem Hinweis an, oder fordern Sie eine
Seite an, die das Gerät bereits vorhält.
rows = await client.data.posts. findAll ();
if (e instanceof RebaseApiError && e.code === "OFFLINE" ) rows = cached;
ClientStorageSourceRegistry.getOrDefault(key) gab zuvor die Standardquelle für einen
unbekannten Schlüssel zurück. Dadurch lud eine Property, die eine Quelle angab, die das
Panel noch nicht geladen hatte, Dateien in den Standard-Bucket hoch. Jetzt wird ein
Fehler geworfen.
Wer betroffen ist: Code, der getOrDefault mit einem nicht registrierten Schlüssel
aufruft. Der Admin selbst tut dies nicht mehr.
Was zu ändern ist: Registrieren Sie die Quelle zuerst, oder verwenden Sie get(key),
das bei einem unbekannten Schlüssel undefined zurückgibt, falls eine fehlende Quelle
erwartet werden kann.
TextField deklarierte bisher maxRows, und MultiSelect deklarierte onChange, name,
position, endAdornment, multiple und padding. Keines davon wurde jemals ausgelesen,
das Übergeben bewirkte also nichts. Sie wurden aus den Typen entfernt, und die Übergabe
führt nun zu einem Typfehler.
Wer betroffen ist: Code, der eine dieser Props übergeben hat. Zur Laufzeit ändert sich nichts.
Was zu ändern ist: Entfernen Sie die entsprechende Prop. Für Änderungen bei einem
Multi-Select verwenden Sie onValueChange.
Die Users-Collection (diejenige mit aktiviertem auth) antwortet bei einem Upsert
nun über jeden Weg mit 400 INVALID_CONFLICT_TARGET: ?on_conflict=, /bulk mit
upsert: true, eine _batch-upsert-Operation, die SDK-Methoden upsert() und
createMany(…, { upsert: true }) sowie der Realtime-Socket. Ein Upsert erfährt erst
während des Schreibvorgangs, ob eine Erstellung oder Aktualisierung stattfindet,
weshalb bisher keines der beiden Regelwerke angewendet wurde: kein Password-Hashing,
keine E-Mail-Normalisierung, kein onCreateUser oder Einladungsversand beim Erstellen
und keine Prüfung auf den letzten Administrator beim Aktualisieren.
Wer betroffen ist: Code, der Upserts auf Benutzer durchführt, beispielsweise ein
Sync-Prozess, der client.data.users.upsert(user, { onConflict: ["email"] }) ausführte.
Was zu ändern ist: Suchen Sie den Benutzer zuerst und führen Sie anschließend
ein Create oder Update durch.
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);
Ein Lesevorgang, der sowohl page als auch offset angibt, wird nun abgelehnt. Der
Server antwortet mit 400 PAGE_WITH_OFFSET, und das SDK verweigert die Kombination
bereits vor der Anfrage mit einem RebaseClientError, der denselben Code enthält: über
HTTP, In-Process (rebase.data) und offline. Zuvor hatte page Vorrang und offset
wurde stillschweigend verworfen.
Wer betroffen ist: Code, der beides sendet, z. B. ein find({ page, offset })
oder ein REST-Aufruf mit ?page=3&offset=40. Ein solcher Lesevorgang hat bisher
ohnehin nur page berücksichtigt.
Was zu ändern ist: Senden Sie nur einen der beiden Parameter. Um das bisherige
Ergebnis beizubehalten, entfernen Sie offset.
await client.data.products. find ({ page: 3 , offset: 40 , limit: 20 });
await client.data.products. find ({ page: 3 , limit: 20 });
Bei einer Tabelle, deren Name 42 Bytes oder länger ist, heißen die drei Standard-Policies
für Admin-Schreibzugriffe (…_default_admin_write_insert, _update und _delete) nun
<head>_<hash8>_<op>, maximal 63 Bytes. Da Postgres Namen auf 63 Bytes kürzt, wurden
die drei alten Namen auf denselben Namen abgeschnitten, sodass nur eine einzige Policy
(die DELETE-Policy) existierte: Admins konnten keine Datensätze einfügen oder aktualisieren.
Wer betroffen ist: Ein Projekt mit Tabellennamen von 42 Bytes oder mehr sowie alles,
was diese Policies namentlich referenziert (wie Migrationen oder Skripte, die sie löschen).
Die alten Namen existierten in der Datenbank nie.
Was zu ändern ist: Für die Datenbank selbst ist nichts zu tun. Der nächste Start
oder rebase db push löscht die abgeschnittene Policy und erstellt eine Policy pro
Operation. Aktualisieren Sie Skripte, die die alten Policy-Namen enthalten.
Die Standard-Lebensdauer des Refresh-Tokens beträgt nun an allen Einstiegspunkten 30 Tage.
Ein Server, der mit der rebase-server-Runtime (oder rebase start) gestartet wurde,
nutzte bisher 400 Tage, wenn JWT_REFRESH_EXPIRES_IN nicht gesetzt war; ein manuell
aufgesetztes initializeRebaseBackend nutzte bereits 30 Tage.
Die Lebensdauer ist gleitend: Jeder Refresh stellt ein Token aus, das für weitere 30
Tage gültig ist, sodass ein Benutzer, der die App mindestens einmal im Monat öffnet,
angemeldet bleibt. Ein Benutzer, der länger inaktiv ist, muss sich erneut anmelden.
Tokens, die vor dem Upgrade ausgestellt wurden, behalten ihre ursprüngliche Ablaufzeit
bis zu ihrem nächsten Refresh.
Wer betroffen ist: Ein Deployment auf der Runtime, bei dem JWT_REFRESH_EXPIRES_IN
nie gesetzt wurde und dessen Benutzer seltener als einmal im Monat wiederkehren.
Was zu ändern ist: Nichts, um den neuen Standard zu übernehmen. Um das vorherige
Verhalten beizubehalten, setzen Sie den Wert explizit:
JWT_REFRESH_EXPIRES_IN = 400d
oder auth: { refreshExpiresIn: "400d" } in einem ejected Backend. Browser begrenzen
das Refresh-Cookie auf 400 Tage, sodass ein längerer Wert im Cookie-Modus keinen
Vorteil bringt.
POST /api/admin/users sowie das Erstellen in der Users-Collection über die Data-API
(POST /api/data/users, /bulk, ein Batch, MCP create_document, ein Socket-Save)
lehnen ein password, das die Richtlinie verletzt, nun mit 400 WEAK_PASSWORD ab,
wie es bei Registrierungen, Resets und PUT /api/admin/users/:uid bereits der Fall war.
Es wird nichts geschrieben und es wird kein Create-Hook ausgeführt. Bei einem /bulk
beginnt die Meldung mit Row <n>: , analog zu den anderen Zeilenfehlern des Bulk-Endpunkts.
Ein Erstellvorgang ohne Passwort bleibt unverändert: Es wird ein Passwort generiert
oder eine Einladung versendet.
Wer betroffen ist: Code, der Benutzer mit einem an anderer Stelle festgelegten
Passwort erstellt, wie etwa bei einem Import oder einer Synchronisierung aus einem
anderen System.
Was zu ändern ist: Um Konten zu importieren, deren Passwörter an anderer Stelle
vergeben wurden, senden Sie als Administrator oder mit dem Service-Key
skipPasswordPolicy: true im Body jedes Create-Aufrufs (bzw. in jeder Zeile eines /bulk):
await client.admin. createUser ({ email, password: legacyPassword, skipPasswordPolicy: true });
Bei jedem anderen Aufrufer setzt das Flag die Richtlinie nicht außer Kraft. Es wird nie
gespeichert und erreicht weder onCreateUser noch onAdminCreateUser. Der Import des
Admin-Panels sendet das Flag automatisch bei Zeilen, die ein password enthalten. Ein
benutzerdefinierter AuthAdapter erhält die Entscheidung als neues drittes Argument von
prepareUserCreation ({ mayWaivePasswordPolicy }); ein Adapter, der dies ignoriert,
behält sein bisheriges Verhalten bei. Um die Richtlinie selbst anzupassen, übergeben Sie
auth.hooks.validatePasswordStrength.
Ein Schreibvorgang, der "" für ein string-Feld mit validation.required sendet,
wird nun mit 400 VALIDATION_CONSTRAINT beantwortet, inklusive eines Eintrags in
details.violations: { field, code: "required" }. Dies ist die gleiche Antwort wie
bei einem fehlenden Wert. Dies gilt für Erstell- und Aktualisierungsvorgänge über alle
Schnittstellen (REST, Bulk, Batch, Upsert, Realtime-Socket, MCP) und selbst dann, wenn
das Feld ein defaultValue hat, da ein Standardwert einen fehlenden Schlüssel füllt,
nicht jedoch einen leeren. Das Admin-Panel schreibt für ein geleertes Feld bereits
null und ist daher nicht betroffen.
Wer betroffen ist: Code, der "" in ein erforderliches String-Feld schreibt, wie
etwa ein Formular oder ein Import, der jede Spalte unabhängig von ihrer Befüllung sendet.
Was zu ändern ist: Senden Sie einen Wert, lassen Sie den Schlüssel beim Erstellen weg,
wenn ein defaultValue greifen soll, oder entfernen Sie required bei Feldern, die
legitimerweise leer sein dürfen. Collections, die einen beforeSave-Hook deklarieren,
werden an der Schnittstelle nicht vorab geprüft (der Hook kann den Wert noch füllen),
wie bisher. Servercode, der In-Process schreibt (rebase.data), ist nicht betroffen.
Beide dienten als interne Engines hinter Client-Aufrufen.
new QueryBuilder(collection).where(…) → client.data.<collection>.where(…);
die Hilfsfunktionen or, and, not und cond werden weiterhin exportiert.
createBackups(transport) → client.backups (identische Methoden list, download,
downloadUrl, stream). CreateBackupsOptions wurde damit ebenfalls entfernt;
BackupStream wird weiterhin exportiert.
client.ws bleibt unverändert.
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
Die Quelle des Clients sendet beim Upload auch public und context (serverseitig
geprüfte Eigenschaftsbeschränkungen) sowie storageId für benannte Quellen, was der
Hook zuvor nicht tat.
Entfernen Sie den Schlüssel aus storage: { type: "s3" | "gcs", … }; TypeScript meldet
ihn als unbekannte Eigenschaft. Er hat nie die Gültigkeitsdauer einer URL bestimmt, die
ein Browser empfangen hat. Um einzustellen, wie lange die URL einer privaten Datei gültig
ist, setzen Sie storageDownloadTokenTtl (in Sekunden; STORAGE_DOWNLOAD_TOKEN_TTL bei
einem Bundle-Deployment). Servercode, der controller.getSignedUrl() direkt aufruft,
erhält eine URL mit einer Gültigkeit von einer Stunde.
Ein Projekt, das in config/resources.ts nur benannte Buckets deklarierte, etwa
bucket("media", { engine: "s3" }), hatte den ersten davon zum Standard befördert:
Uploads, deren Property keine storageSource nennt, gingen dorthin. Der Standard liest
jetzt immer seine eigene Konfiguration, was auch immer deklariert ist: STORAGE_TYPE,
STORAGE_PATH (oder ./uploads) und die S3_* / GCS_* ohne Suffix.
Sollen diese Uploads weiter an den benannten Bucket gehen, sagen Sie das:
export const media = bucket ( "media" , { engine: "s3" , default: true });
Ohne die Markierung antworten diese Uploads in der Produktion mit
501 STORAGE_NOT_CONFIGURED, bis der Standard gebunden ist (STORAGE_TYPE=s3 und
S3_BUCKET oder STORAGE_TYPE=gcs und GCS_BUCKET), und der Start protokolliert eine
Warnung, die beide Lösungswege nennt. Die Dateien, die der beförderte Standard geschrieben
hat, liegen im benannten Bucket; mit der Markierung werden sie wie bisher über den Standard
ausgeliefert. In der Entwicklung werden die Dateien, die bereits in ./uploads liegen,
wieder ausgeliefert.
Der Start verweigert außerdem zwei Quellen, die auf ein Verzeichnis (oder eines darin) oder
auf einen Bucket an einem Endpunkt auflösen. Geben Sie der benannten Quelle einen eigenen
Speicherort (STORAGE_PATH__MEDIA, S3_BUCKET__MEDIA) oder markieren Sie sie als Standard.