Configuración de almacenamiento
Descripción general
Sección titulada «Descripción general»Rebase admite tres backends de almacenamiento:
- Sistema de archivos local — Archivos almacenados en disco (ideal para desarrollo)
- Compatible con S3 — AWS S3, MinIO, Cloudflare R2, DigitalOcean Spaces
- Google Cloud Storage / Firebase Storage — Soporte nativo de GCS mediante
@google-cloud/storage
Configuración
Sección titulada «Configuración»El almacenamiento se configura en el bloque storage de initializeRebaseBackend:
Almacenamiento local
Sección titulada «Almacenamiento local»const backend = await initializeRebaseBackend({ // ... storage: { type: "local", basePath: "./uploads" // Directory for file storage }});Almacenamiento S3
Sección titulada «Almacenamiento S3»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
Sección titulada «GCS / Firebase Storage»const backend = await initializeRebaseBackend({ // ... storage: { type: "gcs", bucket: env.GCS_BUCKET!, projectId: env.GCS_PROJECT_ID, }});En GCP (Cloud Run, GCE, GKE), las credenciales de la cuenta de servicio predeterminada se utilizan automáticamente. Fuera de GCP, configure la variable de entorno GOOGLE_APPLICATION_CREDENTIALS con la ruta al archivo de clave de su cuenta de servicio.
Múltiples backends de almacenamiento
Sección titulada «Múltiples backends de almacenamiento»Puede configurar múltiples backends con nombre y enrutar diferentes campos a distintos almacenamientos:
storage: { "(default)": { type: "local", basePath: "./uploads" }, "media": { type: "s3", bucket: "media-bucket", region: "us-east-1", ... }}Luego, en las propiedades de su colección, haga referencia a un backend específico:
image: { type: "string", name: "Image", storage: { storagePath: "products", storageSource: "media" // Routes to the "media" S3 backend }}Endpoints de almacenamiento
Sección titulada «Endpoints de almacenamiento»| Método | Ruta | Descripción |
|---|---|---|
POST |
/api/storage/upload |
Subida directa de archivos |
POST |
/api/storage/upload?storageId=<key> |
Subir a un backend con nombre específico |
GET |
/api/storage/file/* |
Recuperar un archivo — todo lo que va después de /file/ es la clave del objeto |
GET |
/api/storage/file/*?storageId=<key> |
Recuperar un archivo de un backend específico |
GET |
/api/storage/metadata/* |
Tamaño, tipo de contenido y última modificación de un objeto, sin sus bytes |
DELETE |
/api/storage/file/* |
Eliminar un archivo |
GET |
/api/storage/list |
Listar objetos bajo un prefijo (prefix, bucket, maxResults, pageToken, storageId). Un maxResults menor que 1, o un pageToken que el origen nunca emitió, es 400 INVALID_LIST_OPTIONS |
POST |
/api/storage/folder |
Crear un marcador de carpeta vacía |
GET |
/api/storage/sources |
Los orígenes de almacenamiento que sirve este backend, por clave |
OPTIONS |
/api/storage/tus |
Consultar las capacidades admitidas del protocolo TUS |
POST |
/api/storage/tus |
Iniciar una sesión de subida reanudable TUS |
HEAD |
/api/storage/tus/:id |
Comprobar el progreso de la subida (desplazamiento en bytes / byte offset) |
PATCH |
/api/storage/tus/:id |
Añadir un fragmento de datos al archivo temporal |
DELETE |
/api/storage/tus/:id |
Terminar/cancelar la sesión de subida TUS |
Qué responden. Un único envoltorio, el mismo que utiliza /api/data: la carga útil
está bajo data, y un fallo es { "error": { message, code, requestId } }
con los códigos de la referencia de errores. /api/storage/file/*
es la excepción, porque su carga útil es el archivo en sí: responde con los bytes, con
Content-Type, Content-Length y las cabeceras de almacenamiento en caché.
// GET /api/storage/list?prefix=products/images/{ "data": { "items": [ { "bucket": "default", "fullPath": "products/images/a.jpg", "name": "a.jpg" } ], "prefixes": [] } }POST /api/storage/upload responde 201 con { key, bucket, storageUrl }
del objeto almacenado bajo data; GET /api/storage/metadata/* devuelve los metadatos
del objeto y, para un objeto privado, el token de corta duración;
GET /api/storage/sources devuelve el array de orígenes configurados, con configured: true. En un despliegue sin ningún almacenamiento, responde 200 {"data": [], "configured": false}, mientras que todas las demás rutas responden 501 STORAGE_NOT_CONFIGURED. GET /api/auth/config informa de lo mismo como storage: false, así que un cliente puede ahorrarse la pregunta.
DELETE /api/storage/file/* y POST /api/storage/folder solo contienen un
message, ya que no hay nada que devolver.
En S3 y GCS, un bucket tiene que ser uno que el origen sirva, tanto en
escrituras como en lecturas: una subida, un POST /api/storage/folder o una
subida TUS que nombre cualquier otro bucket responde
404 UNKNOWN_STORAGE_SOURCE, igual que el listado. El almacenamiento local
sigue creando un bucket en su primera escritura.
Cómo se autoriza la lectura de un archivo. Las rutas de lectura — /api/storage/file/* y
/api/storage/metadata/* — aceptan el token firmado de corta duración que
emite getSignedUrl(), pasado como ?token=<token> o como
un Bearer. Un JWT de acceso ordinario se rechaza en /file/* con 401 Unauthorized: Access JWT not allowed on file routes: el token que funciona en
cualquier otra ruta no funciona allí, a propósito, porque la URL de un archivo es
algo que se entrega a un navegador, a una CDN o a una etiqueta <img>. Cada una de las demás filas anteriores
acepta el JWT de acceso de forma habitual.
Transformaciones de imágenes al vuelo
Sección titulada «Transformaciones de imágenes al vuelo»Rebase incluye una canalización de procesamiento de imágenes integrada impulsada por Sharp. Al servir recursos de imagen desde el almacenamiento, puede aplicar operaciones dinámicas mediante parámetros de consulta:
# Serve image scaled to 300px width in webp formatGET /api/storage/file/products/laptop.jpg?width=300&format=webpParámetros admitidos
Sección titulada «Parámetros admitidos»width,height: Límites de cambio de tamaño,1–4096(la imagen nunca se amplía).quality:1–100.format: Convierte el formato de la imagen. Formatos admitidos:webp,jpeg,png,avif.fit:cover,contain,fill,insideooutside.
Un parámetro fuera de estos límites devuelve un 400, en lugar de ajustarse silenciosamente:
anteriormente, ?width=99999 devolvía una imagen de 4096px y ?format=tiff una en webp, sin
indicarlo explícitamente.
Rendimiento y almacenamiento en caché LRU
Sección titulada «Rendimiento y almacenamiento en caché LRU»La transformación consume mucha CPU y memoria, y en un objeto público se puede acceder al endpoint de forma anónima, por lo que el trabajo está acotado en lugar de simplemente almacenado en caché:
- Capacidad: una caché LRU limitada a 500 entradas globalmente, identificadas por origen de almacenamiento, bucket y clave canónica.
- TTL (Tiempo de vida): Las variantes en caché expiran después de 1 hora.
- Las solicitudes simultáneas para la misma variante no almacenada en caché producen una sola transformación, no una por cada solicitud.
- Se ejecuta un número reducido de transformaciones al mismo tiempo; más allá de una cola acumulada limitada, el servidor
responde 503
TRANSFORM_OVERLOADEDen lugar de aceptar trabajo que no podrá procesar.
Esa caché reside en el proceso, lo que significa que no se comparte entre instancias y no sobrevive a un reinicio. Dos réplicas calculan cada una todas las variantes, y un despliegue descarta todo.
Rendiciones que sobreviven a un reinicio
Sección titulada «Rendiciones que sobreviven a un reinicio»storageRenditionCache vuelve a escribir cada imagen derivada en el mismo bucket que su
origen, por lo que el trabajo se realiza una sola vez para todo el despliegue en lugar de una vez por
instancia y por lanzamiento:
storageRenditionCache: { enabled: true }o STORAGE_RENDITION_CACHE=true para un despliegue de bundle. Las rendiciones se almacenan
bajo el prefijo reservado _rebase/renditions/, identificadas por la versión
del objeto de origen; por lo tanto, reemplazar una imagen sirve la nueva de inmediato.
Tres cosas que debe saber antes de activarlo:
- Una lectura ahora escribe. Cada nueva variante cuesta un
PUTen su bucket. Por esa razón está desactivado por defecto. - Una escritura fallida no es una solicitud fallida. Las credenciales de solo lectura, o una política de bucket que rechace el prefijo, recurren a la caché en proceso; la imagen se sigue sirviendo y el motivo se registra en los logs una sola vez.
- Las rendiciones sustituidas no se recolectan automáticamente. Reemplazar un objeto de origen
deja huérfanas sus antiguas rendiciones. Configure una regla de ciclo de vida en
_rebase/renditions/— ese prefijo es fijo, no configurable, precisamente para que una regla pueda nombrarlo.
El prefijo no es direccionable desde la API. Leerlo o escribirlo directamente
responde 400 INVALID_STORAGE_KEY: cada regla de acceso en el producto —
tanto storageAuthorize como las políticas declarativas — está escrita contra la
clave de origen, y una rendición servida bajo su propia ruta respondería a una pregunta
que nadie hizo.
Qué se sirve
Sección titulada «Qué se sirve»El tipo de contenido almacenado es el que haya declarado quien lo subió — nada analiza los
bytes internamente —, por lo que /api/storage/file/* solo representará en línea una lista de permitidos estricta:
imágenes, vídeo, audio, application/pdf y text/plain. Todo lo
demás, incluido text/html, se sirve como
application/octet-stream con Content-Disposition: attachment, y cada
respuesta incluye X-Content-Type-Options: nosniff. El almacenamiento no es un alojamiento web:
una página subida que se procese en el origen de la API puede leer las cookies de ese origen y
llamar a sus endpoints.
Un SVG es una imagen que también es un documento, y puede llevar <script>.
Se sirve como image/svg+xml — de modo que se renderiza en un <img> y en
las miniaturas del panel de administración — con
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
en cada respuesta, incluidas las URLs de transformaciones y las de token de
descarga. Abierto directamente, se renderiza con los scripts deshabilitados y
un origen opaco, por lo que no puede llegar a las cookies ni a los endpoints
de la API.
Protocolo de subidas reanudables TUS
Sección titulada «Protocolo de subidas reanudables TUS»Para subir archivos grandes o manejar condiciones de red inestables, Rebase implementa el protocolo abierto TUS v1.0.0, incluidas las extensiones Creation y Termination.
Cuán grande puede ser un archivo
Sección titulada «Cuán grande puede ser un archivo»Cada origen de almacenamiento acepta archivos hasta su propio límite: 50 MB
salvo que STORAGE_MAX_FILE_SIZE (bytes, con el sufijo __<KEY> para un
origen con nombre) o el maxFileSize del origen indiquen otra cosa. Todas las
puertas respetan el mismo número. POST /api/storage/upload responde
413 PAYLOAD_TOO_LARGE por encima de él; una subida reanudable se rechaza en
el momento de crearse, antes de su primer fragmento; y
OPTIONS /api/storage/tus lo anuncia como Tus-Max-Size — para el origen
nombrado por ?storageId=, o el predeterminado en su defecto.
para STORAGE_MAX_FILE_SIZE: en 0.23 solo el maxFileSize del
origen fija el límite, y la variable no se lee.
STORAGE_MAX_FILE_SIZE=209715200 # (por defecto): 200 MBSTORAGE_MAX_FILE_SIZE__MEDIA=1073741824 # media: 1 GBUn archivo completo se mantiene en la memoria del servidor mientras se escribe en el store, en todas las vías de subida, así que ajusta el límite a la memoria que tenga el servidor.
Client Rebase Server │ │ │─── POST /api/storage/tus (Upload-Length: 50000000) ──────>│ (Generates session ID) │<── 201 Created (Location: /api/storage/tus/uuid-abc) ────│ │ │ │─── PATCH /api/storage/tus/uuid-abc (Upload-Offset: 0) ───>│ (Appends chunk via open/write) │<── 204 No Content (Upload-Offset: 1500000) ───────────────│ │ │ │─── PATCH /api/storage/tus/uuid-abc (Upload-Offset: 1.5M) ─>│ (Upload finishes) │<── 204 No Content (Upload-Offset: 50000000) ──────────────│ (Copies to storage, unlinks temp)Mecánica del ciclo de vida de la subida
Sección titulada «Mecánica del ciclo de vida de la subida»- Inicialización de la sesión (
POST): El cliente envía el tamaño total del archivo en la cabeceraUpload-Lengthy metadatos en base64 medianteUpload-Metadata. El servidor crea un archivo marcador de posición vacío en un directorio temporal oculto.tus-uploads/y devuelve la URL de subida. - Consultas de progreso (
HEAD): Si una subida se interrumpe, el cliente consulta la URL de subida mediante una solicitudHEAD. El servidor devuelve la posición actual en bytes en la cabeceraUpload-Offset. - Anexión de datos (
PATCH): El cliente reanuda el envío de datos binarios comenzando en el desplazamiento devuelto conContent-Type: application/offset+octet-stream. El servidor escribe cada fragmento en el archivo temporal en el desplazamiento de bytes que declara la solicitud, que debe ser el desplazamiento que tiene el servidor (409en caso contrario). Se recibe un fragmento a la vez: unPATCHque llega mientras otro de la misma subida aún se está recibiendo — normalmente un cliente reenviando un fragmento que se estancó — se responde con423 UPLOAD_LOCKED, y el cliente reanuda desde el desplazamiento que reportaHEAD. - Finalización: Cuando el
Upload-Offsetacumulado coincide con elUpload-Lengthdeclarado, Rebase lee el archivo temporal completado, lo envuelve como un objeto estándarFilede JavaScript y lo guarda en el backend de almacenamiento configurado (disco local o S3). A continuación, se elimina el archivo temporal. - Limpieza periódica: Un limpiador en segundo plano se ejecuta cada 60 segundos para eliminar subidas temporales huérfanas e incompletas que hayan superado el umbral de retención de 24 horas.
Variables de entorno
Sección titulada «Variables de entorno»| Variable | Descripción |
|---|---|
STORAGE_TYPE |
"local", "s3" o "gcs" |
STORAGE_PATH |
Directorio de almacenamiento local (por defecto: ./uploads) |
S3_BUCKET |
Nombre del bucket S3 |
S3_REGION |
Región de AWS (por defecto: "auto") |
S3_ACCESS_KEY_ID |
Clave de acceso de AWS |
S3_SECRET_ACCESS_KEY |
Clave secreta de AWS |
S3_ENDPOINT |
Endpoint S3 personalizado (para MinIO, R2) |
S3_FORCE_PATH_STYLE |
Usar URLs de estilo de ruta (path-style) (obligatorio para MinIO) |
GCS_BUCKET |
Nombre del bucket de Google Cloud Storage |
GCS_PROJECT_ID |
ID de proyecto de GCP para GCS |
GCS_KEY_FILENAME |
Ruta a un archivo de clave de cuenta de servicio de GCP (omitir en GKE; Workload Identity/ADC proporciona las credenciales) |
GOOGLE_APPLICATION_CREDENTIALS |
Variable estándar de ADC, leída por el propio SDK de Google (no es necesaria en GCP con credenciales predeterminadas) |
STORAGE_DOWNLOAD_TOKEN_TTL |
Durante cuánto tiempo funciona la URL de descarga de un archivo privado, en segundos — la vida del token que acuña /api/storage/metadata/* (por defecto 300, como máximo 604800, una semana). Aumenta este valor para vídeo y audio privados, que siguen solicitando rangos después de que se renderiza la página. La variante en variable de entorno de storageDownloadTokenTtl |
STORAGE_MAX_FILE_SIZE |
El archivo más grande que acepta el origen, en bytes (por defecto 52428800, 50 MB). Sufijo __<KEY> para un origen con nombre. Un valor que no sea un número entero de bytes rechaza el arranque. Consulta Cuán grande puede ser un archivo |
FORCE_LOCAL_STORAGE |
Permitir STORAGE_TYPE=local en producción — ver a continuación |
STORAGE_PUBLIC_READ |
Servir objetos almacenados a lectores no autenticados. La variante en variable de entorno de storagePublicRead, y una de las tres formas de satisfacer la protección de arranque en producción. |
STORAGE_ALLOW_ANY_AUTHENTICATED |
Desactivar la protección de arranque, restaurando el comportamiento en el que cualquier usuario autenticado puede leer, sobrescribir, eliminar o listar cualquier clave. La variante en variable de entorno de storageInsecureAllowAnyAuthenticated. Solo justificable cuando se confía cada archivo a cada usuario autenticado. |
Varios buckets
Sección titulada «Varios buckets»Un proyecto puede tener más de un bucket. Declare cada uno en config/resources.ts
— el único lugar que leen la plataforma, el runtime y la consola:
import { bucket } from "@rebasepro/types";
export const uploads = bucket({ engine: "s3" }); // the default oneexport const media = bucket("media", { engine: "s3", label: "Media" });Luego ejecute rebase resources --write, lo que regenera rebase.resources.json
para que un host pueda leer su topología sin ejecutar una compilación. Consulte
Múltiples orígenes para bases de datos, buckets y
temas en conjunto.
Cada origen se configura a partir de los mismos nombres de variable que llevan su propio sufijo. El origen predeterminado no lleva sufijo, por lo que un proyecto con un solo bucket sigue utilizando los nombres simples anteriores y no necesita declarar nada en absoluto:
S3_BUCKET=app-uploads # (default)S3_BUCKET__MEDIA=app-media # mediaS3_ACCESS_KEY_ID__MEDIA=…S3_SECRET_ACCESS_KEY__MEDIA=…El sufijo se deriva de la clave: en mayúsculas, los caracteres no alfanuméricos se convierten en
guiones bajos, tras un doble guion bajo (media-cdn → __MEDIA_CDN). Un
guion bajo simple colisionaría con nombres de variables reales — S3_BUCKET_NAME
se interpretaría como el bucket name.
Enrute una propiedad a un origen con storageSource:
{ name: "Cover", dataType: "string", storage: { storageSource: "media", acceptedFiles: ["image/*"] }}Un origen que usted declare pero nunca configure se omite, no es fatal: las subidas
enrutadas hacia él responden 501 STORAGE_SOURCE_NOT_CONFIGURED. Declarar un bucket suele
ocurrir antes de que alguien le asocie almacenamiento, y un error de arranque en ese punto
provocaría un bucle de reinicios en el backend hasta que alguien lo hiciera. Un origen que el entorno configure
incorrectamente —un tipo sin bucket, o un bucket sin credenciales— se rechaza
en el arranque, porque se trata de un error y no de una ausencia.
Buckets que comparten una cuenta
Sección titulada «Buckets que comparten una cuenta»Las credenciales suelen describir al proveedor, no al bucket. De lo contrario, quince buckets en una sola instalación de MinIO supondrían quince copias de la misma clave de acceso, y una rotación requeriría quince ediciones dobles. En su lugar, asigne un nombre a una cuenta:
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.internalSolo las variables con ámbito de cuenta se reutilizan como valor alternativo: S3_ACCESS_KEY_ID,
S3_SECRET_ACCESS_KEY, S3_ENDPOINT, S3_REGION, S3_FORCE_PATH_STYLE, y
el par de GCS GCS_PROJECT_ID / GCS_KEY_FILENAME. El nombre del bucket nunca lo hace:
es lo que distingue a un origen de otro. Un valor específico por bucket sigue teniendo prioridad,
por lo que un origen puede cambiar de proveedor sin afectar al resto.
Fuentes de almacenamiento del frontend
Sección titulada «Fuentes de almacenamiento del frontend»Cuando utilice múltiples backends de almacenamiento, pase storageSources al proveedor <Rebase> para que el frontend sepa cómo enrutar las subidas directamente:
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>La propiedad key de cada origen debe coincidir con una clave de backend registrada en el mapa storage del servidor. El contexto de React StorageSourcesContext resuelve el origen activo para cada campo de subida.
Caché y CDN
Sección titulada «Caché y CDN»Cada objeto se reenvía a través del servidor mediante proxy, con un ETag
para una revalidación económica, un Cache-Control que depende de quién
puede leerlo, y rangos de bytes para la búsqueda y el desplazamiento en audio
y vídeo. Caché de almacenamiento y CDN tiene
las cabeceras, y qué configurar en una CDN por delante.
Consejos para producción
Sección titulada «Consejos para producción»- Monte un volumen persistente si utiliza almacenamiento local en Docker/Kubernetes y configure
FORCE_LOCAL_STORAGE=true - Utilice S3 o compatibles (R2, MinIO), o GCS, para despliegues en producción
- Configure una CDN (CloudFront, Cloudflare) por delante para mejorar el rendimiento — consulte Colocar una CDN por delante
- Cualquier aplicación con almacenamiento en producción debe declarar un modelo de acceso — consulte a continuación. No solo las aplicaciones multitenant: el servidor se niega a arrancar sin uno.
Autorización por objeto
Sección titulada «Autorización por objeto»Políticas
Sección titulada «Políticas»La forma declarativa. Una lista de patrones de ruta, evaluados sin ejecutar código:
storagePolicies: [ { path: "public/**", operations: ["read"], allow: "public" }, { path: "users/:uid/**", allow: ({ params, user }) => user?.uid === params.uid }]Una clave que no coincida con ninguna política se rechaza. Cada ampliación de permisos es una línea explícita, y un error deniega el acceso en lugar de concederlo.
Los patrones coinciden por segmento, nunca por subcadena — public/** no coincide con
publicity/secret.png:
| Patrón | Coincide con |
|---|---|
avatars/logo.png |
esa clave exactamente |
users/*/avatar.png |
exactamente un segmento donde se encuentra * |
users/:uid/** |
un segmento capturado y luego el resto — incluso nada |
** solo es válido como el segmento final. :name captura un segmento y nunca
abarca una /; las capturas llegan como params en el predicado.
allow puede ser "public" (cualquiera), "authenticated" (cualquier usuario con un uid) o un
predicado que recibe los parámetros capturados, el usuario, la operación y el bucket.
operations toma por defecto las cuatro operaciones — read, write, delete, list.
Las políticas satisfacen la protección de arranque en producción por sí solas, y un patrón mal formado hace fallar el arranque en lugar de la primera subida.
El hook
Sección titulada «El hook»requireAuth y publicRead son modificadores globales: deciden si quien llama debe haber iniciado sesión, no qué puede tocar. Sin un hook de autorización, cualquier usuario autenticado puede leer cualquier clave que pueda nombrar — lo único que separa los archivos de dos inquilinos es la imposibilidad de adivinar la clave, lo cual no es un modelo de control de acceso. Peor aún, pueden ejecutar GET /storage/list?prefix= primero, de modo que ni siquiera necesitan adivinar las claves.
storageAuthorize es el equivalente en almacenamiento de las reglas de seguridad de una colección, y se ejecuta tras la autenticación en cada ruta de almacenamiento:
await initializeRebaseBackend({ storage: { type: "s3", bucket: "app-files", /* ... */ }, storageAuthorize: async ({ key, bucket, operation, user }) => { if (!user) return false; // Keys are laid out as `{teamId}/{docId}/...` const [teamId] = key.split("/"); return isTeamMember(user.uid, teamId); }});| Campo | Descripción |
|---|---|
key |
Clave del objeto, con el prefijo del bucket eliminado y el recorrido de directorios saneado |
bucket |
Bucket resuelto ("default" cuando no se especifica) |
operation |
"read", "write", "delete" o "list" |
user |
{ uid, email?, roles? }, o null donde la ruta permite acceso anónimo |
storageId |
El backend con nombre, cuando la solicitud apuntaba a uno: su clave canónica (storageId=%20media es "media"). Ausente para el origen predeterminado, lo nombre como lo nombre la solicitud (sin parámetro, vacío o (default)) |
data |
Acceso de lectura de confianza que omite RLS — data.collection(slug).find(query) / .findById(id). La propiedad reside en una fila, no en el prefijo de una clave, por lo que el hook necesita un mecanismo de lectura para responder “¿a quién pertenece este objeto?”. Omite la seguridad a nivel de fila deliberadamente: este hook es la decisión de autorización, y tomarla a través de un lector ya restringido por los permisos del propio solicitante sería circular. De solo lectura por diseño. |
Devuelva false para denegar con un 403. Lanzar una excepción también deniega el acceso — una búsqueda de propiedad que falla no queda abierta por defecto.
Conviene saber:
- La ruta de metadatos es donde realmente se decide el acceso de lectura. Esta emite el token de descarga de corta duración y delimitado a la ruta en el que confía la ruta del archivo, por lo que el hook lo restringe allí. Las solicitudes que ya llevan dicho token, o que acceden a una ruta declarada pública, omiten el hook: el token se emitió bajo su control y solo es válido para su propia ruta.
- Una ruta es pública cuando lo es la clave que nombra. El prefijo
public/se comprueba sobre la clave canónica que sirve la ruta —después de leer un segmento de bucketdefault/inicial y de plegar//,./y una/inicial—, nunca sobre la URL tal como se escribió./api/storage/file/notes://public/x.txtnombra la clave privadanotes:/public/x.txt, y la clavedefault/public/x.txt(a la que se llega como/api/storage/file/default/default/public/x.txt) está en una carpeta llamadadefault; ninguna de las dos es pública. En un hook, compruebe lakeyque recibe conisPublicStorageKeyde@rebasepro/types:isPublicStoragePathlee una ruta o URL del lado del cliente, y considera públicas a ambas. listse restringe en función del prefijo. Listar es la forma de descubrir claves de las que nadie le informó.- Las subidas reanudables (TUS) se restringen en el momento de la creación, de modo que una subida denegada no deja ningún archivo temporal atrás.
- Omitir el hook conserva el comportamiento anterior, por lo que las aplicaciones de inquilino único no se ven afectadas.
Claves de API y tokens
Sección titulada «Claves de API y tokens»Una persona con la sesión iniciada tiene todos los alcances de almacenamiento, así que
el hook y las políticas anteriores deciden por ella. Una clave de API
necesita además el alcance de almacenamiento de la operación, en el origen que nombra
la solicitud: storage:read para descargas, metadatos y listados, storage:write para
subidas, carpetas y cada paso de una subida reanudable, storage:delete para
eliminaciones. El destino es el id del origen, y el del origen predeterminado es
(default): storage:read:(default), storage:write:avatars. Sin él, la respuesta es
403 SCOPE_MISSING. Con él, el hook sigue ejecutándose, como quien sea que represente
la clave.
Reaccionar a una subida
Sección titulada «Reaccionar a una subida»Cualquier otra escritura en Rebase admite reacciones — una fila tiene beforeSave y
afterSave, una programación tiene una tarea cron —, mientras que una subida no tenía nada. Cualquier acción que
implicara una subida debía ser realizada por el cliente en una segunda llamada, lo que significa que
no se ejecutaba en absoluto si el cliente se desconectaba entre medias.
storageTriggers: [ { path: "uploads/:uid/**", events: ["finalize"], handler: async ({ key, params, size, user }) => { await jobs.enqueue("index-upload", { key, uid: params.uid, size }); } }]El lenguaje de patrones es el mismo que en storagePolicies — segmentos literales, *
para un segmento, :name para capturar uno, ** para el resto —, y un patrón
mal formado hace fallar el arranque en lugar de no coincidir silenciosamente con nada.
| Evento | Cuándo |
|---|---|
finalize |
después de que el objeto se haya escrito de forma duradera; nunca para una escritura que haya fallado |
delete |
después de que el objeto haya sido eliminado |
finalize se dispara tanto para la vía multipart como para la reanudable (TUS) — una vez
por subida, no una vez por fragmento —, y una subida reanudable reporta al usuario que
la creó, ya que ese es el sujeto que comprobó la autorización.
Lo que un handler no debe asumir:
- Un handler que lanza una excepción no hace fallar la solicitud. El objeto ya está almacenado para cuando se ejecuta, por lo que responder al cliente con un error indicaría que la subida falló cuando no fue así, y los clientes reintentan las subidas. Los fallos se registran y la respuesta permanece inalterada. Si el trabajo debe realizarse obligatoriamente, póngalo en cola como un job.
- Los handlers se esperan con await, en orden de declaración, antes de que se envíe la respuesta; ejecutar en modo “disparar y olvidar” dejaría una promesa que un runtime serverless podría congelar en mitad de la ejecución. Por lo tanto, un handler lento es una subida lenta, que es la otra razón para encolar tareas en lugar de procesarlas aquí.
- Las escrituras internas no disparan triggers. La caché de rendiciones de imágenes escribe
los objetos derivados directamente en el controlador de almacenamiento; un trigger
**que se disparase sobre esos objetos estaría disparándose sobre su propia salida.
Próximos pasos
Sección titulada «Próximos pasos»- Almacenamiento en frontend y subida de archivos — Campos y hooks para la subida de archivos
- Propiedades — Configuración de propiedades de almacenamiento