Speicher-Konfiguration
Übersicht
Abschnitt betitelt „Übersicht“Rebase unterstützt drei Speicher-Backends:
- Lokales Dateisystem — Auf der Festplatte gespeicherte Dateien (ideal für die Entwicklung)
- S3-kompatibel — AWS S3, MinIO, Cloudflare R2, DigitalOcean Spaces
- Google Cloud Storage / Firebase Storage — Native GCS-Unterstützung über
@google-cloud/storage
Konfiguration
Abschnitt betitelt „Konfiguration“Der Speicher wird im storage-Block von initializeRebaseBackend konfiguriert:
Lokaler Speicher
Abschnitt betitelt „Lokaler Speicher“const backend = await initializeRebaseBackend({ // ... storage: { type: "local", basePath: "./uploads" // Directory for file storage }});Der lokale Speicher legt die Metadaten jedes Objekts direkt daneben ab, als <key>.metadata.json. Ein Schlüssel, dessen letztes Segment auf .metadata.json endet, wird daher auf jeder Route mit 400 INVALID_STORAGE_KEY abgelehnt. S3 und GCS speichern Metadaten am Objekt selbst und können beliebige Schlüssel speichern. Auf einem Dateisystem ohne Berücksichtigung der Groß-/Kleinschreibung (standardmäßig unter macOS und Windows) sind Avatar.png und avatar.png dieselbe lokale Datei, aber zwei unterschiedliche Objekte auf S3, GCS oder einem Linux-Laufwerk. Daher warnt der Server beim Systemstart, wenn der Speicherordner auf einem solchen Dateisystem liegt.
S3-Speicher
Abschnitt betitelt „S3-Speicher“const backend = await initializeRebaseBackend({ // ... storage: { type: "s3", bucket: env.S3_BUCKET!, region: env.S3_REGION || "auto", accessKeyId: env.S3_ACCESS_KEY_ID || "", secretAccessKey: env.S3_SECRET_ACCESS_KEY || "", endpoint: env.S3_ENDPOINT, // For MinIO, R2, etc. forcePathStyle: env.S3_FORCE_PATH_STYLE // Required for MinIO }});GCS / Firebase Storage
Abschnitt betitelt „GCS / Firebase Storage“const backend = await initializeRebaseBackend({ // ... storage: { type: "gcs", bucket: env.GCS_BUCKET!, projectId: env.GCS_PROJECT_ID, }});Auf der GCP (Cloud Run, GCE, GKE) werden die Standard-Anmeldedaten des Dienstkontos automatisch verwendet. Außerhalb der GCP setzen Sie die Umgebungsvariable GOOGLE_APPLICATION_CREDENTIALS auf den Pfad Ihrer Dienstkonto-Schlüsseldatei.
Mehrere Speicher-Backends
Abschnitt betitelt „Mehrere Speicher-Backends“Sie können mehrere benannte Backends konfigurieren und verschiedene Felder an unterschiedliche Speicher weiterleiten:
storage: { "(default)": { type: "local", basePath: "./uploads" }, "media": { type: "s3", bucket: "media-bucket", region: "us-east-1", ... }}Verweisen Sie anschließend in Ihren Collection-Eigenschaften auf ein bestimmtes Backend:
image: { type: "string", name: "Image", storage: { storagePath: "products", storageSource: "media" // Routes to the "media" S3 backend }}Speicher-Endpunkte
Abschnitt betitelt „Speicher-Endpunkte“| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/api/storage/upload |
Direkter Datei-Upload, ein einziger multipart/form-data-Body: file (Pflichtfeld); key, der Objektschlüssel samt Ordnern (author_pictures/ada.jpg), sonst der Dateiname im Wurzelverzeichnis; bucket, sonst der Standard-Bucket; storageId; collection und property, die die Datei an maxSize und acceptedFiles dieser Property binden; und metadata_<name> für eigene Metadaten. Jedes andere Feld wird ignoriert: Ein Feld path gibt es nicht |
POST |
/api/storage/upload?storageId=<key> |
Upload in ein bestimmtes benanntes Backend |
GET |
/api/storage/file/* |
Eine Datei abrufen — alles nach /file/ ist der Objektschlüssel |
GET |
/api/storage/file/*?storageId=<key> |
Eine Datei von einem bestimmten Backend abrufen |
GET |
/api/storage/metadata/* |
Größe, Content-Type und benutzerdefinierte Metadaten eines Objekts ohne dessen Bytes, plus ein kurzlebiges Download-Token, falls es privat ist |
POST |
/api/storage/metadata |
Dasselbe für bis zu 100 Objekte einer Quelle: Body { keys, storageId? } |
DELETE |
/api/storage/file/* |
Eine Datei löschen |
GET |
/api/storage/list |
Objekte unter einem Präfix auflisten (prefix, bucket, maxResults, pageToken, storageId). Ein maxResults kleiner als 1 oder ein pageToken, den die Quelle nie ausgestellt hat, ergibt 400 INVALID_LIST_OPTIONS |
POST |
/api/storage/folder |
Eine leere Ordnermarkierung erstellen |
GET |
/api/storage/sources |
Die Speicherquellen, die dieses Backend bedient, nach Schlüssel |
OPTIONS |
/api/storage/tus |
Unterstützte TUS-Protokollfunktionen abfragen |
POST |
/api/storage/tus |
Eine fortsetzbare TUS-Upload-Sitzung initiieren |
HEAD |
/api/storage/tus/:id |
Upload-Fortschritt prüfen (Byte-Offset) |
PATCH |
/api/storage/tus/:id |
Daten-Chunk an temporäre Datei anhängen |
DELETE |
/api/storage/tus/:id |
TUS-Upload-Sitzung beenden/abbrechen |
Was sie antworten. Ein einheitlicher Umschlag (Envelope), derselbe, den auch /api/data verwendet: Die Nutzlast
liegt unter data, und ein Fehler hat das Format { "error": { message, code, requestId } }
mit den Codes aus der Fehlerreferenz. /api/storage/file/*
ist die Ausnahme, da dessen Nutzlast die Datei selbst ist — es antwortet mit den reinen Bytes sowie
Content-Type, Content-Length und den Caching-Headern.
// GET /api/storage/list?prefix=products/images/{ "data": { "items": [ { "bucket": "default", "fullPath": "products/images/a.jpg", "name": "a.jpg", "size": 48213, "updated": "2026-10-01T12:00:00.000Z", "contentType": "image/jpeg" } ], "prefixes": [] } }Eine Auflistung beschreibt jede Datei, die sie zurückgibt: ihre size in Bytes und
updated, wann sie zuletzt geschrieben wurde (im lokalen Speicher, auf S3 und GCS), sowie ihren
contentType im lokalen Speicher und auf GCS. Ein S3-Listing enthält den Typ nicht,
daher fehlt er bei S3 und /api/storage/metadata/* liefert ihn stattdessen. Ein Ordner in
prefixes enthält keines dieser Felder, ebenso wie ein benutzerdefinierter StorageController,
der sie nicht setzt.
POST /api/storage/upload antwortet mit 201 und { key, bucket, storageUrl } des gespeicherten Objekts unter data;
GET /api/storage/metadata/* mit den Metadaten des Objekts und für private Objekte mit dem kurzlebigen token; POST /api/storage/metadata mit einem Objekt, dessen Schlüssel die übergebenen Pfade sind und die jeweils das enthalten, was der GET-Endpunkt dafür
geantwortet hätte (siehe unten). Ein Ordner ist kein Objekt: Ein /metadata-Aufruf auf einen Schlüssel, der auf / endet, liefert auf jedem Speicher 404,
selbst wenn S3 oder GCS eine folder/-Markierung vorhalten; es wird daher kein Download-Token für ein Präfix erzeugt.
GET /api/storage/sources liefert das Array der konfigurierten Quellen mit configured: true. In einem Deployment ganz ohne Speicher
antwortet es mit 200 {"data": [], "configured": false}, während jede andere Route mit 501 STORAGE_NOT_CONFIGURED antwortet. GET /api/auth/config meldet
dieselbe Tatsache als storage: false, sodass ein Client die Anfrage überspringen kann. GET /api/storage/sources ist öffentlich: Es antwortet
ohne Token, selbst wenn requireAuth aktiv ist. Es gibt lediglich die Quellen an (Schlüssel, Engine, Transport und Label) und niemals
eine Datei; das Client-SDK liest diesen Endpunkt aus, um seine Quellen zu verdrahten, was geschehen kann, bevor sich jemand angemeldet hat. Eine Anwendung,
die eine direct-Quelle anonym ausliest, benötigt dies ebenfalls. Jede Route, die eine Datei auflistet, liest oder schreibt, behält ihre Authentifizierung bei.
DELETE /api/storage/file/* und POST /api/storage/folder enthalten lediglich eine message, da es nichts zurückzugeben gibt.
Auf S3 und GCS muss ein bucket eines sein, das die Quelle bedient, sowohl bei Schreib- als auch bei
Leseoperationen: Ein Upload, ein POST /api/storage/folder oder ein TUS-Upload mit Angabe eines anderen
Buckets antwortet mit 404 UNKNOWN_STORAGE_SOURCE, genau wie das Listing. Der lokale Speicher
erstellt ein Bucket weiterhin beim ersten Schreibvorgang automatisch.
Viele Objekte auf einmal. POST /api/storage/metadata akzeptiert
{ "keys": ["photos/a.png", "photos/b.png"], "storageId": "media" } — bis zu 100
Objektpfade einer einzelnen Quelle, jeweils so formatiert, wie GET /api/storage/metadata/<key>
sie erwartet — und liefert einen Eintrag pro Schlüssel zurück:
{ "data": { "photos/a.png": { "fullPath": "photos/a.png", "size": 48213, "contentType": "image/png", "token": "…", "tokenExpiresIn": 300 }, "photos/b.png": { "error": { "code": "FORBIDDEN", "message": "Not authorized for this object", "status": 403 } }} }Jeder Schlüssel wird exakt so beurteilt wie beim GET-Endpunkt — derselbe storageAuthorize-Hook,
derselbe Speicher-Scope, dieselbe Public-Regel, ein eigenes Download-Token. Ein Schlüssel,
der abgelehnt wird, fehlt oder fehlerhaft ist, liefert seinen eigenen error zurück, ohne die anderen
abzubrechen oder preiszugeben. Ein Aufrufer ohne Sitzung in einem Deployment, das eine solche erfordert,
erhält den 401, den auch der GET-Endpunkt liefern würde, es sei denn, jeder Schlüssel ist öffentlich. Mehr als
100 Schlüssel oder ein Body ohne keys führen zu 400 BAD_REQUEST. Der Rate Limiter zählt
die Anfrage als ⌈N/10⌉ Anfragen für N Schlüssel; eine Seite mit 100 Thumbnails kostet also 10 Einheiten.
client.storage.getSignedUrl() und das Admin-Panel bündeln die Abfragen einer Seite
auf diese Weise und fallen auf einzelne GET-Aufrufe pro Datei zurück, falls der Server die
Route nicht unterstützt.
Wie ein Lesezugriff auf eine Datei autorisiert wird. Die Lese-Routen — /api/storage/file/* und
/api/storage/metadata/* — erwarten das kurzlebige, signierte Token, das
getSignedUrl() generiert, übergeben als ?token=<token> oder als
Bearer. Ein reguläres Zugriffs-JWT wird auf /file/* mit 401 Unauthorized: Access JWT not allowed on file routes abgelehnt: Das Token, das auf
jeder anderen Route funktioniert, funktioniert dort bewusst nicht, da eine Datei-URL
an einen Browser, ein CDN oder ein <img>-Tag weitergegeben wird. Alle anderen oben aufgeführten
Routen akzeptieren wie gewohnt das Zugriffs-JWT.
Bildtransformationen zur Laufzeit (On-the-Fly)
Abschnitt betitelt „Bildtransformationen zur Laufzeit (On-the-Fly)“Rebase enthält eine integrierte Bildverarbeitungspipeline auf Basis von Sharp. Beim Ausliefern von Bild-Assets aus dem Speicher können Sie dynamische Operationen über Abfrageparameter anwenden:
# Serve image scaled to 300px width in webp formatGET /api/storage/file/products/laptop.jpg?width=300&format=webpUnterstützte Parameter
Abschnitt betitelt „Unterstützte Parameter“width,height: Abmessungen für die Größenänderung,1–4096(das Bild wird niemals vergrößert).quality:1–100.format: Konvertiert das Bildformat. Unterstützte Formate:webp,jpeg,png,avif.fit:cover,contain,fill,insideoderoutside.
Ein Parameter außerhalb dieser Grenzen führt zu einem 400-Fehler und wird nicht stillschweigend begrenzt —
?width=99999 lieferte früher ein Bild mit 4096px und ?format=tiff ein WebP-Bild zurück,
ohne Rückmeldung darüber zu geben.
Die Thumbnails gespeicherter Bilder im Admin-Panel nutzen diesen Endpunkt: Eine Tabellen- oder Karten-Vorschau
fordert eine WebP-Version an, die etwa doppelt so groß wie ihre dargestellte Größe ist (96–800px),
anstatt des Originals. SVG- und GIF-Dateien werden immer unverändert angezeigt.
sharp ist optional:
ohne es erhält eine Transformationsanfrage das Original, und das Backend warnt einmal,
pnpm add sharp in backend/ auszuführen (unter 0.24 antwortet es mit 500, und das Panel zeigt die Originale).
Performance & LRU-Caching
Abschnitt betitelt „Performance & LRU-Caching“Das Transformieren ist CPU- und speicherintensiv, und bei einem öffentlichen Objekt ist der Endpunkt anonym erreichbar. Daher ist die Arbeitslast begrenzt und nicht nur zwischengespeichert:
- Kapazität: Ein LRU-Cache, der global auf 500 Einträge limitiert ist, aufgeschlüsselt nach Speicherquelle, Bucket und kanonischem Schlüssel.
- TTL (Time to Live): Zwischengespeicherte Varianten laufen nach 1 Stunde ab.
- Gleichzeitige Anfragen für dieselbe nicht zwischengespeicherte Variante erzeugen eine Transformation, nicht jeweils eine eigene.
- Eine kleine Anzahl von Transformationen läuft gleichzeitig; jenseits eines begrenzten Backlogs antwortet der Server mit
503
TRANSFORM_OVERLOADED, anstatt Arbeit anzunehmen, die er nicht bewältigen kann.
Dieser Cache lebt im Prozess, was bedeutet, dass er nicht zwischen Instanzen geteilt wird und einen Neustart nicht übersteht. Zwei Replikate berechnen jede Variante separat, und ein Deployment verwirft den gesamten Cache.
Renderings, die einen Neustart überstehen
Abschnitt betitelt „Renderings, die einen Neustart überstehen“storageRenditionCache schreibt jedes abgeleitete Bild zurück in dasselbe Bucket wie seine
Quelle, sodass die Arbeit einmal für das gesamte Deployment statt einmal pro
Instanz und Release ausgeführt wird:
storageRenditionCache: { enabled: true }oder STORAGE_RENDITION_CACHE=true für ein Bundle-Deployment. Renderings werden unter dem
reservierten Präfix _rebase/renditions/ gespeichert, verschlüsselt nach der Version des Quellobjekts
— das Ersetzen eines Bildes liefert also sofort das neue aus.
Drei Dinge, die Sie vor dem Aktivieren beachten sollten:
- Ein Lesevorgang führt nun zu einem Schreibvorgang. Jede neue Variante kostet einen
PUT-Aufruf in Ihrem Bucket. Aus diesem Grund ist die Funktion standardmäßig deaktiviert. - Ein fehlgeschlagener Schreibvorgang ist keine fehlgeschlagene Anfrage. Schreibgeschützte Anmeldedaten oder eine Bucket-Richtlinie, die das Präfix ablehnt, fallen auf den In-Process-Cache zurück; das Bild wird dennoch ausgeliefert und der Grund wird einmalig protokolliert.
- Ersetzte Renderings werden nicht bereinigt. Das Ersetzen eines Quellobjekts
lässt alte Renderings verwaist zurück. Richten Sie eine Lifecycle-Regel für
_rebase/renditions/ein — dieses Präfix ist fest vorgegeben und nicht konfigurierbar, damit eine Regel exakt darauf verweisen kann.
Das Präfix kann nicht über die API adressiert werden. Direktes Lesen oder Schreiben darauf
wird mit 400 INVALID_STORAGE_KEY beantwortet: Jede Zugriffsregel im Produkt —
sowohl storageAuthorize als auch die deklarativen Richtlinien — bezieht sich auf den
Quell-Schlüssel, und ein Rendering, das unter seinem eigenen Pfad ausgeliefert wird, würde eine Anfrage beantworten,
die niemand gestellt hat.
_rebase/maintenance/ ist auf dieselbe Weise reserviert. rebase storage orphans legt dort den
Datensatz jedes Scans ab, in der Quelle, die er gescannt hat, damit der Prozess, der den Scan
ausführt, und der, der seinen Status beantwortet, denselben Datensatz lesen. Ein
Aufrufer, der einen Datensatz schreiben könnte, könnte bestimmen, was ein späteres Löschen entfernt, daher
beantwortet auch das Lesen, Schreiben oder Auflisten des Präfixes 400. Datensätze werden 7
Tage nach ihrem letzten Schreibvorgang gelöscht, wenn der nächste Scan dieser Quelle beginnt.
Was ausgeliefert wird
Abschnitt betitelt „Was ausgeliefert wird“Der gespeicherte Content-Type entspricht dem, was der Uploader angegeben hat — die Bytes werden
nicht analysiert. Daher rendert /api/storage/file/* inline nur eine enge Allowlist:
Bilder, Video, Audio, application/pdf und text/plain. Alles andere,
einschließlich text/html, wird als application/octet-stream mit
Content-Disposition: attachment ausgeliefert, und jede Antwort enthält
X-Content-Type-Options: nosniff. Der Speicher ist kein Webserver: Eine hochgeladene HTML-Seite,
die auf dem API-Origin gerendert wird, könnte Cookies dieses Origins auslesen und dessen
Endpunkte aufrufen.
Ein SVG ist ein Bild, das gleichzeitig ein Dokument ist und <script> enthalten kann. Es wird
als image/svg+xml ausgeliefert — sodass es in einem <img> und in den Thumbnails des Admin-Panels gerendert wird —,
jedoch mit Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
bei jeder Antwort, einschließlich transformierter URLs und URLs mit Download-Token. Wird es
direkt geöffnet, rendert es mit deaktivierten Skripten und einem opaken Origin, sodass es weder auf die
Cookies noch auf die Endpunkte der API zugreifen kann.
Maximale Dateigröße
Abschnitt betitelt „Maximale Dateigröße“Jede Speicherquelle akzeptiert Dateien bis zu ihrem eigenen Limit: 50 MB, sofern nicht
STORAGE_MAX_FILE_SIZE (in Bytes, mit dem Suffix __<KEY> für eine benannte Quelle) oder das
maxFileSize-Feld der Quelle etwas anderes festlegt. Jeder Zugangsweg bindet eine Datei an denselben
Wert. POST /api/storage/upload antwortet bei Überschreitung mit 413 PAYLOAD_TOO_LARGE; ein
fortsetzbarer Upload wird bereits bei der Erstellung abgelehnt, noch vor seinem ersten Chunk; und
OPTIONS /api/storage/tus meldet ihn als Tus-Max-Size — für die durch ?storageId= benannte
Quelle bzw. andernfalls für die Standardquelle.
In Version 0.23 legt ausschließlich das Feld maxFileSize der Quelle das Limit fest; STORAGE_MAX_FILE_SIZE wird nicht ausgewertet.
STORAGE_MAX_FILE_SIZE=209715200 # (default): 200 MBSTORAGE_MAX_FILE_SIZE__MEDIA=1073741824 # media: 1 GBWie viel einer Datei der Server gleichzeitig im Arbeitsspeicher hält, hängt vom Eingangsweg ab.
POST /api/storage/upload ist ein einzelner Multipart-Body, den der Server vollständig parst:
das Drei- bis Fünffache der Dateigröße im Arbeitsspeicher. Ein fortsetzbarer Upload (TUS) verarbeitet
jeweils nur einen Chunk. client.storage.putObject() und das Admin-Panel senden Dateien
über 8 MiB über das fortsetzbare Verfahren. Unter
Fortsetzbare Uploads (TUS) finden Sie Informationen zum
Protokoll, zu den Antworten und zum Verhalten über mehrere Instanzen hinweg.
Umgebungsvariablen
Abschnitt betitelt „Umgebungsvariablen“| Variable | Beschreibung |
|---|---|
STORAGE_TYPE |
"local", "s3" oder "gcs" |
STORAGE_PATH |
Verzeichnis für lokalen Speicher (Standard: <project>/uploads, unter rebase dev und in einem von rebase eject geschriebenen Server gleichermaßen). Ein relativer Pfad wird von dem Verzeichnis aus gelesen, in dem der Server läuft; nach rebase eject ist das backend/. Ein benanntes lokales Bucket speichert seine Dateien daneben in <STORAGE_PATH>__<key>, es sei denn, STORAGE_PATH__<KEY> definiert einen eigenen Pfad |
S3_BUCKET |
Name des S3-Buckets |
S3_REGION |
AWS-Region (Standard: "auto") |
S3_ACCESS_KEY_ID |
AWS-Access-Key |
S3_SECRET_ACCESS_KEY |
AWS-Secret-Key |
S3_ENDPOINT |
Benutzerdefinierter S3-Endpunkt (für MinIO, R2) |
S3_FORCE_PATH_STYLE |
Path-Style-URLs erzwingen (erforderlich für MinIO) |
GCS_BUCKET |
Name des Google Cloud Storage Buckets |
GCS_PROJECT_ID |
GCP-Projekt-ID für GCS |
GCS_KEY_FILENAME |
Pfad zu einer GCP-Dienstkonto-Schlüsseldatei (auf GKE weglassen — Workload Identity/ADC stellt Zugangsdaten bereit) |
GOOGLE_APPLICATION_CREDENTIALS |
Standard-ADC-Variable, die vom Google-SDK selbst gelesen wird (auf GCP mit Standardanmeldedaten nicht erforderlich) |
STORAGE_DOWNLOAD_TOKEN_TTL |
Wie lange die Download-URL einer privaten Datei in Sekunden gültig ist — die Lebensdauer des Tokens, das /api/storage/metadata/* generiert (Standard 300, maximal 604800, d. h. eine Woche). Erhöhen Sie diesen Wert für private Videos und Audios, die nach dem Rendern der Seite weiterhin Byte-Ranges anfragen. Entspricht der Option storageDownloadTokenTtl |
STORAGE_MAX_FILE_SIZE |
Größte Datei, die die Quelle akzeptiert, in Bytes (Standard 52428800, 50 MB). Suffix __<KEY> für eine benannte Quelle. Ein Wert, der keine ganze Byte-Zahl ist, verhindert den Systemstart. Siehe Maximale Dateigröße |
FORCE_LOCAL_STORAGE |
Erlaubt STORAGE_TYPE=local in Produktionsumgebungen — siehe unten |
STORAGE_PUBLIC_READ |
Gespeicherte Objekte für unauthentifizierte Leser bereitstellen. Entspricht storagePublicRead und ist eine der drei Möglichkeiten, den Boot-Guard für die Produktion zu erfüllen. |
STORAGE_ALLOW_ANY_AUTHENTICATED |
Deaktiviert den Boot-Guard und stellt das Verhalten wieder her, bei dem jeder angemeldete Benutzer jeden Schlüssel lesen, überschreiben, löschen oder auflisten darf. Entspricht storageInsecureAllowAnyAuthenticated. Nur vertretbar, wenn jedem angemeldeten Benutzer jede Datei anvertraut werden kann. |
Mehrere Buckets
Abschnitt betitelt „Mehrere Buckets“Ein Projekt kann mehr als ein Bucket haben. Deklarieren Sie jedes davon in config/resources.ts
— dem zentralen Ort, den die Plattform, die Runtime und die Konsole gemeinsam lesen:
import { bucket } from "@rebasepro/types";
export const uploads = bucket({ engine: "s3" }); // the default oneexport const media = bucket("media", { engine: "s3", label: "Media" });Führen Sie anschließend rebase resources --write aus. Dadurch wird rebase.resources.json neu generiert,
sodass ein Host Ihre Topologie ohne Build-Vorgang auslesen kann. Siehe
Mehrere Quellen für Datenbanken, Buckets und
Topics im Zusammenspiel.
Jede Quelle wird über dieselben Variablennamen mit ihrem eigenen Suffix konfiguriert. Die Standardquelle verwendet kein Suffix, sodass ein Projekt mit nur einem Bucket weiterhin die einfachen Namen von oben nutzen kann und gar nichts deklarieren muss, und benannte Buckets daneben zu deklarieren ändert sie nicht (Details):
S3_BUCKET=app-uploads # (default)S3_BUCKET__MEDIA=app-media # mediaS3_ACCESS_KEY_ID__MEDIA=…S3_SECRET_ACCESS_KEY__MEDIA=…Das Suffix wird aus dem Schlüssel abgeleitet: Großbuchstaben, nicht-alphanumerische Zeichen durch
Unterstriche ersetzt, hinter einem doppelten Unterstrich (media-cdn → __MEDIA_CDN). Ein
einfacher Unterstrich würde mit echten Variablennamen kollidieren — S3_BUCKET_NAME
würde sonst fälschlicherweise als Bucket name interpretiert werden.
Leiten Sie eine Eigenschaft über storageSource an eine Quelle weiter:
{ name: "Cover", type: "string", storage: { storagePath: "covers", storageSource: "media", acceptedFiles: ["image/*"] }}Eine Quelle, die Sie deklarieren, aber nie konfigurieren, wird übersprungen und führt nicht zu einem Abbruch: Uploads,
die an sie weitergeleitet werden, antworten mit 501 STORAGE_SOURCE_NOT_CONFIGURED. Das Deklarieren eines Buckets
erfolgt meist, bevor jemand Speicher dafür anhängt; ein Startfehler an dieser Stelle würde
das Backend in einer Crash-Schleife halten. Eine Quelle, die die Umgebung falsch konfiguriert
— ein Typ ohne Bucket oder ein Bucket ohne Anmeldedaten — führt hingegen zum Abbruch beim Start,
da es sich hierbei um einen Konfigurationsfehler und nicht um ein bewusstes Fehlen handelt.
Buckets, die sich ein Konto teilen
Abschnitt betitelt „Buckets, die sich ein Konto teilen“Anmeldedaten beschreiben üblicherweise den Provider, nicht das Bucket. Fünfzehn Buckets auf einer MinIO-Installation würden andernfalls fünfzehn Kopien desselben Zugriffsschlüssels bedeuten, und eine Rotation erforderte fünfzehn synchrone Änderungen. Benennen Sie stattdessen ein Konto:
export const media = bucket("media", { engine: "s3", account: "minio" });export const avatars = bucket("avatars", { engine: "s3", account: "minio" });S3_BUCKET__MEDIA=b-media # per bucket, alwaysS3_BUCKET__AVATARS=b-avatarsS3_ACCESS_KEY_ID__MINIO=… # shared by bothS3_SECRET_ACCESS_KEY__MINIO=…S3_ENDPOINT__MINIO=https://minio.internalNur Variablen mit Konto-Geltungsbereich fallen auf den Fallback zurück — S3_ACCESS_KEY_ID,
S3_SECRET_ACCESS_KEY, S3_ENDPOINT, S3_REGION, S3_FORCE_PATH_STYLE sowie
das GCS-Paar GCS_PROJECT_ID / GCS_KEY_FILENAME. Der Bucket-Name tut dies nie:
Er unterscheidet eine Quelle von der anderen. Ein Wert auf Bucket-Ebene hat immer Vorrang,
sodass eine Quelle den Provider wechseln kann, ohne die anderen zu beeinträchtigen.
Frontend-Speicherquellen
Abschnitt betitelt „Frontend-Speicherquellen“Das Frontend erfährt die Quellen des Servers über GET /api/storage/sources, eine server-Quelle braucht also keinen Eintrag. Übergeben Sie storageSources an den <Rebase>-Provider für eine direct-Quelle, die direkt zu ihrem Provider hochlädt, oder um ein Label oder den Transport selbst festzulegen:
import { Rebase } from "@rebasepro/app";
<Rebase apiUrl="https://api.example.com" storageSources={[ // `engine` names the provider, `transport` says who talks to it: // "server" proxies through the Rebase backend, "direct" goes // client-to-provider (and needs a `source` implementation). { key: "media", engine: "s3", transport: "server", label: "Media CDN" }, { key: "firebase", engine: "firebase", transport: "server", label: "Firebase Storage" }, ]}> {() => <MyApp />}</Rebase>Der key einer server-Quelle muss mit einem Backend-Schlüssel übereinstimmen, der in der storage-Map des Servers registriert ist. Der React-Kontext StorageSourcesContext löst die aktive Quelle für jedes Upload-Feld auf.
Caching und CDNs
Abschnitt betitelt „Caching und CDNs“Jedes Objekt wird über den Server weitergeleitet — mit einem ETag für kostengünstige
Neuvalidierung, einem Cache-Control, das davon abhängt, wer die Datei lesen darf, und Byte-Ranges
für das Vor- und Zurückspulen in Audio- und Videodateien. Unter Speicher-Caching und CDNs
finden Sie die Header und Hinweise zur Konfiguration eines vorgelagerten CDNs.
Tipps für die Produktion
Abschnitt betitelt „Tipps für die Produktion“- Binden Sie ein persistentes Volume über das übergeordnete Verzeichnis von
STORAGE_PATHein, wenn Sie lokalen Speicher unter Docker/Kubernetes verwenden, und setzen SieFORCE_LOCAL_STORAGE=true— siehe Self-Hosting - Verwenden Sie S3 oder kompatible Dienste (R2, MinIO) bzw. GCS für Produktions-Deployments
- Schalten Sie aus Performancegründen ein CDN (CloudFront, Cloudflare) vor — siehe Ein CDN vorschalten
- Jede Anwendung mit Speicher in der Produktion muss ein Zugriffsmodell deklarieren — siehe Speicher-Autorisierung. Nicht nur mandantenfähige Anwendungen: Der Server verweigert den Start ohne ein solches Modell.
Objektbezogene Autorisierung
Abschnitt betitelt „Objektbezogene Autorisierung“Wer jeden Schlüssel lesen, schreiben, löschen oder auflisten darf, entscheiden storagePolicies
und der Hook storageAuthorize, und ein API-Schlüssel braucht zusätzlich den Speicher-Scope für
die Operation. Die Produktion verweigert den Start mit Speicher und ohne Zugriffsmodell. Siehe
Speicher-Autorisierung.
Auf einen Upload reagieren
Abschnitt betitelt „Auf einen Upload reagieren“Auf jeden anderen Schreibvorgang in Rebase kann reagiert werden — eine Datenzeile bietet beforeSave und
afterSave, ein Zeitplan einen Cron-Job — für Uploads gab es bisher nichts. Alles, was ein
Upload nach sich zog, musste vom Client in einem zweiten Aufruf erledigt werden. Ging die Verbindung
des Clients dazwischen verloren, unterblieb die Aktion vollständig.
storageTriggers: [ { path: "uploads/:uid/**", events: ["finalize"], handler: async ({ key, params, size, user }) => { await indexUpload.enqueue({ key, uid: params.uid, size }); // a `queue()` in config/resources.ts } }]Die Mustersyntax entspricht der von storagePolicies — literale Segmente, *
für ein einzelnes Segment, :name zur Erfassung eines Segments, ** für den Rest. Ein fehlerhaftes
Muster verhindert den Systemstart, anstatt stillschweigend auf nichts zuzutreffen.
| Ereignis | Wann |
|---|---|
finalize |
Nachdem das Objekt dauerhaft geschrieben wurde; niemals bei einem fehlgeschlagenen Schreibvorgang |
delete |
Nachdem das Objekt gelöscht wurde |
finalize wird sowohl für Multipart- als auch für fortsetzbare (TUS) Upload-Pfade ausgelöst — einmal
pro Upload, nicht einmal pro Chunk. Ein fortsetzbarer Upload meldet den Benutzer, der ihn
erstellt hat, da dies der Principal ist, den die Autorisierung geprüft hat.
Was ein Handler nicht annehmen darf:
- Ein Handler, der einen Fehler wirft, lässt die Anfrage nicht fehlschlagen. Das Objekt ist zu dem Zeitpunkt, an dem der Handler läuft, bereits gespeichert. Eine Fehlermeldung an den Client würde fälschlicherweise signalisieren, dass der Upload gescheitert sei, woraufhin Clients Uploads wiederholen. Fehler werden protokolliert und die Antwort bleibt unberührt. Wenn die Aufgabe zwingend ausgeführt werden muss, reihen Sie einen Job in eine Warteschlange (Queue) ein.
- Handler werden mit
awaitblockierend ausgeführt, in der Reihenfolge ihrer Deklaration, bevor die Antwort gesendet wird — Fire-and-Forget würde ein Promise hinterlassen, das eine Serverless-Laufzeitumgebung mitten in der Ausführung einfrieren könnte. Ein langsamer Handler führt daher zu einem langsamen Upload; dies ist ein weiterer Grund, Arbeiten in eine Queue auszulagern. - Interne Schreibvorgänge lösen keine Trigger aus. Der Cache für Bildtransformationen schreibt
abgeleitete Objekte direkt an den Speicher-Controller; ein
**-Trigger, der darauf anspringen würde, würde auf seine eigene Ausgabe reagieren.
Nächste Schritte
Abschnitt betitelt „Nächste Schritte“- Frontend-Speicher & Datei-Uploads — Felder und Hooks für Datei-Uploads
- Eigenschaften — Konfiguration von Speicher-Eigenschaften