La versione 0.25 modifica anche il comportamento di alcune chiamate esistenti, senza renderle incompatibili. Verifica questi aspetti se la tua app fa affidamento sul vecchio comportamento:
I wrapper di campo dei plugin si compongono. Viene eseguito il render del fieldBuilder.wrap di ogni plugin, ciascuno attorno al campo lasciato dai plugin precedenti, invece del solo ultimo.
Gli override dei componenti si compongono. L’override con wrap: true di una collezione riceve l’override dell’app per lo stesso componente come OriginalComponent, non quello integrato.
rebase.auth, rebase.admin, rebase.functions e rebase.cron sul server raggiungono le route del backend in-process, quindi il middleware aggiunto alla tua applicazione Hono non viene più eseguito su di esse.
Il socket realtime risponde solo al proprio percorso (/ per un’API su /api). Un client che si connette a un altro percorso sullo stesso server riceve un 404, oppure intercetta il tuo listener upgrade.
Il socket ottiene il proprio token come fa HTTP, quindi client.setAuthTokenGetter() ora lo autentica, e dopo un sign-out non ricade più sull’opzione token.
La directory di un bucket locale con nome si trova accanto a STORAGE_PATH, come <STORAGE_PATH>__<key>, non accanto al percorso base del runtime stesso. Se imposti STORAGE_PATH e un bucket locale con nome contiene già dei file, l’avvio indica la vecchia directory e STORAGE_PATH__<KEY> continua a servirla. Un file compose generato da rebase init o rebase eject monta il proprio volume su /uploads. Quel volume contiene solo il bucket predefinito e l’utente dell’immagine non può scriverci. Montalo su /storage con STORAGE_PATH: /storage/uploads, come fanno i nuovi file compose.
Un servizio chiamato da una richiesta e non raggiungibile riceve come risposta 503 SERVICE_UNAVAILABLE, come un bucket S3 non disponibile o un host che una funzione raggiunge con fetch(). Prima era un 500 il cui codice era l’errore del socket (ECONNREFUSED), oppure INTERNAL_ERROR.
rebase db push mantiene i vincoli CHECK ed EXCLUDE sulla tabella di una collezione, mentre il primo push dopo rebase schema introspect li eliminava. Il controllo sulla distruzione di dati ora conta anche l’eliminazione di uno, quindi rebase db migrate --refuse-destructive si ferma su una migrazione che ne elimina uno. Elimina a mano un vincolo che non vuoi più.
Questi nomi continuano a funzionare, generano un avviso una volta a runtime e dispongono di una sostituzione:
Deprecato
Usa
loadFunctionsFromDirectory, loadFunctionsWithDiagnostics
loadFunctions(dir, { onSkip })
loadCronJobsFromDirectory, loadCronJobsWithDiagnostics
loadCrons(dir, { onSkip }), poi declareCrons(crons) dove la vecchia coppia li dichiarava
client.ws.setAuthTokenGetter()
client.setAuthTokenGetter()
hook di plugin hooks.modifyCollectionAsync (mai chiamato)
hooks.modifyCollection, con i dati recuperati prima del render di <Rebase>
hook di plugin lifecycle.onCollectionChange (mai chiamato)
context.data.collection(slug).listen(…) da lifecycle.onMount
DatabaseAdapter.mountRoutes, BackendBootstrapper.mountRoutes (mai chiamato)
una route factory montata dalla tua app
:::
?count= su un elenco REST è ora il parametro di controllo per il conteggio dietro meta.total: ?count=none lo ignora e ?count=exact è l’impostazione predefinita. Qualsiasi altro valore viene rifiutato con 400 INVALID_COUNT. In precedenza, ?count=5 filtrava le righe la cui colonna count era uguale a 5.
Cosa modificare: nulla per quanto riguarda l’SDK, che invia già i filtri su colonne denominate count all’interno di ?where=. Una chiamata REST scritta manualmente che filtra su tale colonna deve spostare il filtro lì:
GET /api/data/products?count=5
GET /api/data/products?where={"count":["==",5]}
Un SDK nuovo con un server vecchio: iterate() e findAll() inviano ?count=none su ogni pagina, così come fa find({ count: "none" }). Un server 0.24 lo interpreta come un filtro su una colonna denominata count. Se la collezione non ha tale colonna risponde con 400 UNKNOWN_FILTER_FIELD, e l’SDK ritenta la lettura senza di esso. Se la collezione ha una colonna testuale denominata count, il vecchio server filtrerà invece restituendo le righe il cui count è 'none', senza generare errori: la pagina risulterà incompleta o vuota e l’iterazione terminerà anticipatamente. Aggiorna il server prima dell’SDK se una collezione possiede una colonna di questo tipo.
Vedi Skipping the count .
list_collections inviava il proprio elenco due volte: formattato nel blocco di testo e nuovamente come structuredContent. Su progetti di grandi dimensioni entrambi insieme superavano il contesto del modello. Ora invia l’elenco una sola volta, nel blocco di testo, sotto forma di JSON compatto. Gli altri tool mantengono il loro structuredContent.
Cosa modificare: nulla per un assistente, che legge il testo. Un programma che leggeva l’elenco da structuredContent deve ora analizzare il blocco di testo; il contenuto è lo stesso. Vedi The remote endpoint .
L’entry point del package riesportava la tabella delle traduzioni in spagnolo, es, includendo le stringhe spagnole in ogni bundle compilato a partire da esso. Le traduzioni diverse dall’inglese vengono ora caricate quando un utente seleziona la propria lingua, ed es non viene più esportato. en continua a esserlo.
Cosa modificare: nulla, a meno che tu non abbia importato es. Per modificare le stringhe dell’admin, passa gli override a <Rebase translations={…}>; per impostare la lingua, passa locale.
grep -rnE "import \{[^}]*\bes\b[^}]*\} from \" @rebasepro/app \" " --exclude-dir=node_modules .
Il middleware CORS rispondeva autonomamente a qualsiasi richiesta OPTIONS con un 204 vuoto. Ora risponde solo ai preflight CORS (una richiesta OPTIONS contenente Access-Control-Request-Method); qualsiasi altra richiesta OPTIONS raggiunge la propria route. Una route senza un handler OPTIONS risponde con 404. OPTIONS /api/storage/tus restituisce ora le capability TUS (Tus-Version, Tus-Extension, Tus-Max-Size), come indicato da sempre nella documentazione.
Chi è interessato: client, proxy o health check che eseguivano un probe su una route con OPTIONS interpretando il 204 come “la route esiste”. I preflight dei browser rimangono invariati.
Cosa modificare: eseguire il probe con il metodo effettivo della route (HEAD o GET).
Una tabella, una tabella di giunzione o un tipo enum i cui nomi coincidono dopo il troncamento a 63 byte di Postgres fallivano in precedenza durante db push con l’errore “already exists”. Due proprietà che si risolvono nella stessa colonna (myTitle e my_title), e un target di relazione scritto come stringa (target: "authors"), fallivano in seguito o in modo poco chiaro. Ciascuno di questi casi viene ora rifiutato durante il caricamento della configurazione, e il messaggio specifica cosa rinominare o riscrivere.
Chi è interessato: i progetti che dichiarano coppie di questo tipo. In precedenza non riuscivano comunque a completare db push, quindi questo anticipa l’errore.
Cosa modificare: rinominare uno dei nomi in conflitto e scrivere il target della relazione come riferimento alla collezione, non al suo slug:
relation: { kind: "belongsTo", target: "authors" },
relation: { kind: "belongsTo", target: () => authors },
Il client cron dell’SDK utilizzava per impostazione predefinita /api/cron, l’alias deprecato. Ora chiama /api/admin/cron, dove risiedono tutte le superfici amministrative.
Chi è interessato: server (o regole di proxy) che gestiscono solo il vecchio percorso. Le chiamate cron dell’SDK risponderanno con 404.
Cosa modificare: aggiornare il server assieme all’SDK, oppure mantenere esplicitamente il vecchio percorso:
const client = createRebaseClient ({ baseUrl, cron: { cronPath: "/api/cron" } });
In modalità offline, findAll() e iterate() restituivano la porzione della collezione memorizzata nella cache del dispositivo come se fosse completa, e count() contava le righe in cache. Una pagina che questo dispositivo non ha mai recuperato ora fallisce con OFFLINE, così come una chiamata a count() senza un conteggio memorizzato nella cache dal server.
Chi è interessato: le applicazioni che mostrano tali letture offline.
Cosa modificare: intercettare l’errore nei punti in cui l’app mostrava risultati parziali, e mostrare i dati disponibili con una nota, oppure richiedere una pagina già presente sul dispositivo.
rows = await client.data.posts. findAll ();
if (e instanceof RebaseApiError && e.code === "OFFLINE" ) rows = cached;
ClientStorageSourceRegistry.getOrDefault(key) restituiva la sorgente predefinita per una chiave sconosciuta; di conseguenza, una proprietà che faceva riferimento a una sorgente non ancora caricata dal pannello eseguiva l’upload nel bucket predefinito. Ora genera un’eccezione.
Chi è interessato: il codice che chiama getOrDefault con una chiave non registrata. Il pannello admin non lo fa più.
Cosa modificare: registrare prima la sorgente oppure usare get(key), che restituisce undefined per chiavi sconosciute, nei casi in cui l’assenza della sorgente sia prevista.
TextField dichiarava maxRows, e MultiSelect dichiarava onChange, name, position, endAdornment, multiple e padding. Nessuna di queste è mai stata letta, quindi passarle non aveva alcun effetto. Sono state rimosse dai tipi e passarle ora genera un errore di tipo.
Chi è interessato: il codice che passava una di queste proprietà. Nulla cambia a runtime.
Cosa modificare: rimuovere la prop. Per le modifiche di un multi-select usa onValueChange.
La collezione degli utenti (quella con auth abilitato) risponde ora con 400 INVALID_CONFLICT_TARGET a qualsiasi operazione di upsert, tramite ogni canale: ?on_conflict=, /bulk con upsert: true, un’operazione di _batch upsert, i metodi dell’SDK upsert() e createMany(…, { upsert: true }), e il socket realtime. Un upsert determina solo all’interno della scrittura se si tratta di una creazione o di un aggiornamento, pertanto non applicava alcuno dei due insiemi di regole: niente hashing della password, normalizzazione dell’email, onCreateUser o invito in caso di creazione, e nessun controllo sull’ultimo amministratore in caso di aggiornamento.
Chi è interessato: il codice che esegue upsert sugli utenti, ad esempio una sincronizzazione che eseguiva client.data.users.upsert(user, { onConflict: ["email"] }).
Cosa modificare: cercare l’utente, poi eseguire la creazione o l’aggiornamento.
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);
Una lettura che specifica sia page che offset viene ora rifiutata. Il server risponde con 400 PAGE_WITH_OFFSET e l’SDK rifiuta la coppia prima di inviare qualsiasi richiesta, con un RebaseClientError contenente lo stesso codice: via HTTP, in-process (rebase.data) e offline. In precedenza, page aveva la precedenza e offset veniva ignorato silenziosamente.
Chi è interessato: il codice che invia entrambi, ad esempio un find({ page, offset }) o una chiamata REST con ?page=3&offset=40. Tale lettura stava già considerando solo page.
Cosa modificare: inviare solo uno dei due parametri. Per mantenere il comportamento precedente della lettura, rimuovere offset.
await client.data.products. find ({ page: 3 , offset: 40 , limit: 20 });
await client.data.products. find ({ page: 3 , limit: 20 });
Su una tabella il cui nome è di 42 byte o superiore, le tre policy predefinite di scrittura admin (…_default_admin_write_insert, _update e _delete) vengono ora denominate <head>_<hash8>_<op>, per un massimo di 63 byte. Postgres mantiene solo 63 byte di un nome, quindi i tre vecchi nomi venivano troncati allo stesso identificatore ed esisteva una sola policy, quella di DELETE: gli amministratori non potevano eseguire inserimenti o aggiornamenti.
Chi è interessato: i progetti con nomi di tabelle di 42 byte o più, e qualsiasi elemento che faccia riferimento a tali policy per nome, come migrazioni o script che le eliminano. I vecchi nomi non sono mai esistiti nel database.
Cosa modificare: nulla a livello di database. Il prossimo avvio o rebase db push eliminerà la policy troncata e creerà una policy per ciascuna operazione. Aggiorna eventuali script che fanno riferimento ai vecchi nomi delle policy.
La durata predefinita del refresh token è ora di 30 giorni su ogni entry point. Un server avviato con il runtime rebase-server (o rebase start) utilizzava 400 giorni quando JWT_REFRESH_EXPIRES_IN non era impostato; un’istanza personalizzata con initializeRebaseBackend utilizzava già 30.
La durata è a scorrimento: ogni refresh genera un token valido per ulteriori 30 giorni, quindi un utente che apre l’app almeno una volta al mese rimane autenticato. Un utente inattivo per un periodo più lungo dovrà accedere nuovamente. I token generati prima dell’aggiornamento mantengono la scadenza con cui sono stati emessi fino al loro prossimo refresh.
Chi è interessato: distribuzioni sul runtime che non hanno mai configurato JWT_REFRESH_EXPIRES_IN, i cui utenti effettuano accessi a distanza di più di un mese.
Cosa modificare: nulla, per accettare il nuovo valore predefinito. Per mantenere il comportamento precedente, impostarlo esplicitamente:
JWT_REFRESH_EXPIRES_IN = 400d
oppure auth: { refreshExpiresIn: "400d" } in un backend dopo l’eject. I browser limitano il cookie di refresh a 400 giorni, quindi un valore superiore non apporta benefici in modalità cookie.
POST /api/admin/users e la creazione sulla collezione utenti tramite la data API (POST /api/data/users, /bulk, una batch, MCP create_document, un salvataggio via socket) ora rifiutano una password che non soddisfa la policy con 400 WEAK_PASSWORD, esattamente come già avveniva per la registrazione, i reset e PUT /api/admin/users/:uid. Non viene scritto nulla e non viene eseguito alcun hook di creazione. Su /bulk, il messaggio inizia con Row <n>: , come gli altri errori di riga delle operazioni bulk. La creazione senza password rimane invariata: viene generata una password o inviato un invito.
Chi è interessato: il codice che crea utenti con una password scelta altrove, ad esempio un’importazione o una sincronizzazione da un altro sistema.
Cosa modificare: per importare account le cui password sono state definite altrove, inviare skipPasswordPolicy: true nel body di ciascuna creazione (su ogni riga di un’operazione /bulk), operando come amministratore o tramite service key:
await client.admin. createUser ({ email, password: legacyPassword, skipPasswordPolicy: true });
Inviato da qualsiasi altro chiamante, il flag non ha alcun effetto. Non viene mai salvato e non raggiunge mai onCreateUser o onAdminCreateUser. L’importazione del pannello admin lo invia automaticamente per le righe che contengono una password. Un AuthAdapter personalizzato riceve questa scelta come nuovo terzo argomento di prepareUserCreation ({ mayWaivePasswordPolicy }); un adapter che la ignora mantiene il comportamento attuale. Per modificare la policy stessa, passare auth.hooks.validatePasswordStrength.
Una scrittura che invia "" per una proprietà string con validation.required ora risponde con 400 VALIDATION_CONSTRAINT, con una voce in details.violations pari a { field, code: "required" }: la stessa risposta restituita per un valore mancante. Ciò si applica alle operazioni di creazione e aggiornamento su qualsiasi canale (REST, bulk, batch, upsert, socket realtime, MCP), anche quando la proprietà ha un defaultValue, poiché un valore predefinito colma una chiave assente, non una vuota. Il pannello admin scrive già null per un campo cancellato, quindi non è interessato.
Chi è interessato: il codice che scrive "" in un campo stringa obbligatorio, ad esempio un form o un’importazione che invia tutte le colonne indipendentemente dal fatto che siano compilate o meno.
Cosa modificare: inviare un valore valido, omettere la chiave in creazione quando deve applicarsi un defaultValue, oppure rimuovere required dai campi che possono legittimamente essere vuoti. Le collezioni che dichiarano un beforeSave non vengono verificate al boundary (l’hook può popolare il valore), come prima. Il codice lato server che scrive in-process (rebase.data) non è interessato.
Entrambi costituivano i motori interni alla base delle chiamate del client.
new QueryBuilder(collection).where(…) → client.data.<collection>.where(…); gli helper or, and, not e cond continuano a essere esportati.
createBackups(transport) → client.backups (stessi metodi list, download, downloadUrl, stream). Anche CreateBackupsOptions è stato rimosso; BackupStream continua a essere esportato.
client.ws rimane invariato.
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 sorgente del client invia inoltre public e context durante gli upload (limiti delle proprietà verificati lato server) e storageId per le sorgenti con nome, cosa che l’hook non faceva.
Eliminare la chiave da storage: { type: "s3" | "gcs", … }; TypeScript la segnala come proprietà sconosciuta. Non ha mai determinato la durata di un URL ricevuto da un browser. Per modificare la durata di validità dell’URL di un file privato, impostare storageDownloadTokenTtl (in secondi; STORAGE_DOWNLOAD_TOKEN_TTL su distribuzioni con bundle). Il codice server che chiama direttamente controller.getSignedUrl() riceve un URL valido per un’ora.
Un progetto che dichiarava solo bucket con nome in config/resources.ts, come
bucket("media", { engine: "s3" }), vedeva promosso a predefinito il primo di essi: gli
upload la cui proprietà non specifica alcuna storageSource andavano lì. Il predefinito ora
legge sempre la propria configurazione, qualunque cosa sia dichiarata: STORAGE_TYPE,
STORAGE_PATH (oppure ./uploads) e le S3_* / GCS_* senza suffisso.
Se quegli upload devono continuare ad andare al bucket con nome, dillo:
export const media = bucket ( "media" , { engine: "s3" , default: true });
Senza il contrassegno, in produzione quegli upload rispondono
501 STORAGE_NOT_CONFIGURED finché il predefinito non viene associato (STORAGE_TYPE=s3 e
S3_BUCKET, oppure STORAGE_TYPE=gcs e GCS_BUCKET), e l’avvio registra un solo avviso che
nomina entrambe le soluzioni. I file scritti dal predefinito promosso si trovano nel bucket
con nome; con il contrassegno vengono serviti attraverso il predefinito come prima. In
sviluppo, i file già presenti in ./uploads tornano a essere serviti.
L’avvio rifiuta anche due sorgenti che si risolvono nella stessa directory (o una dentro
l’altra), o nello stesso bucket sullo stesso endpoint. Dai alla sorgente con nome una
posizione propria (STORAGE_PATH__MEDIA, S3_BUCKET__MEDIA), oppure contrassegnala come
predefinita.