Pular para o conteúdo

Armazenamento e Arquivos

O módulo client.storage fornece métodos para gerenciamento de arquivos — envio, download, listagem e exclusão. Funciona tanto com disco local quanto com backends de armazenamento compatíveis com S3, dependendo da configuração do seu servidor.

Todos os métodos de armazenamento usam o transporte compartilhado, então os tokens de autenticação são injetados automaticamente.

Use putObject() para enviar um arquivo. Aceita um objeto File ou Blob junto com uma chave de armazenamento e metadados opcionais:

const result = await client.storage.putObject({
file: fileObject, // File or Blob
key: "products/images/camera.jpg", // Storage path (optional)
bucket: "uploads", // Bucket name (optional)
public: false, // Store public (permanent token-less URL) — optional, default false
metadata: { // Custom metadata (optional)
description: "Product photo",
uploadedBy: "user-123"
}
});
// result: { key: string; bucket: string; storageUrl: string }

Um arquivo acima de 8 MiB é enviado pelo endpoint retomável (TUS) do servidor, 8 MiB por requisição, em vez de como uma única requisição multipart que o servidor processa por inteiro — de modo que um upload grande custa ao servidor um chunk de memória, não três a cinco vezes o arquivo. Um chunk que falha no caminho é reenviado de onde o servidor parou. O resultado, o objeto armazenado e as verificações do servidor são os mesmos de qualquer forma. Quando o endpoint retomável não consegue receber o arquivo (um servidor mais antigo, ou várias instâncias que não compartilham uploads), ele vai como um único POST /api/storage/upload, e o mesmo vale para todo arquivo seguinte desse cliente. Veja Uploads retomáveis (TUS).

const input = document.querySelector<HTMLInputElement>("#file-input");
const file = input?.files?.[0];
if (file) {
const result = await client.storage.putObject({
file,
key: `avatars/${userId}/${file.name}`
});
console.log("Uploaded to:", result.key);
}

Recupere uma URL de download e os metadados de um arquivo armazenado:

const { url, metadata, fileNotFound } = await client.storage.getSignedUrl(
"products/images/camera.jpg"
);
if (url) {
console.log("Download URL:", url);
console.log("Content type:", metadata?.contentType);
} else {
console.log("File not found");
}

Com um bucket específico:

const { url } = await client.storage.getSignedUrl(
"camera.jpg",
"product-images" // bucket
);

O SDK armazena em cache as URLs assinadas para evitar chamadas redundantes ao servidor, até que o token de download de cada uma expire. As consultas feitas juntas — uma página de pré-visualizações chamando getSignedUrl() no mesmo tick — vão ao servidor como um único POST /api/storage/metadata por 100 arquivos, que o limitador de taxa conta como um décimo das requisições que ele substitui; um arquivo que um chamador não pode ler falha apenas aquela chamada. O cache pertence à credencial que o pediu: quando o token de acesso muda — um logout, outro usuário entrando, uma renovação — ele recomeça do zero, de modo que um usuário nunca recebe uma URL emitida para outro.

  • Arquivos privados recebem uma URL com um token de download de curta duração e restrito ao caminho (?token=…, 5 minutos a menos que o servidor defina STORAGE_DOWNLOAD_TOKEN_TTL, que a 0.23 não lê) — nunca seu token de acesso. Como ele expira, não persista uma URL privada; armazene o caminho do arquivo e chame getSignedUrl() novamente ao renderizá-lo.
  • Arquivos públicos (armazenados sob o prefixo public/ — defina storage: { public: true } na propriedade, ou passe public: true para putObject) recebem uma URL estável, sem token, permanente e cacheável por CDN, sem ida e volta ao servidor. São seguros para armazenar em um banco de dados e vincular diretamente.

Um texto que incorpora um arquivo — as imagens em um campo markdown — também não pode conter uma URL privada, então ele contém uma referência de storage: rebase-storage:posts/cover.png, com ?storageId=media quando o arquivo está em uma fonte nomeada. É isso que o editor markdown do painel de administração grava para uma imagem enviada. Troque as referências por URLs ao renderizar o texto:

import { resolveStorageReferences } from "@rebasepro/client";
const post = await client.data.collection("posts").findById("post-1");
const body = await resolveStorageReferences(String(post?.body ?? ""), client);
// `body` is the same markdown, each reference replaced by a fresh URL.

Cada objeto é assinado uma vez por chamada, a partir de sua própria fonte, então uma imagem privada continua funcionando por mais tempo que tenha passado desde o upload. Uma referência que nomeia um objeto que não existe mais é deixada como está. storageReference(key, storageId?) constrói uma, para texto que você mesmo escreve.

O texto salvo antes de as referências existirem guarda a própria URL de download de um arquivo privado, …/storage/file/posts/cover.png?token=…, cujo token expirou minutos depois de ser escrito. resolveStorageReferences renova essas também: o token ainda nomeia o objeto, então a URL é substituída por uma nova. Para armazenar referências em vez disso, reescreva o texto uma vez com storageReferencesForDownloadUrls(text), que deixa todas as outras URLs como estão.

Recupere um arquivo como um objeto File:

const file = await client.storage.getObject("products/images/camera.jpg");
if (file) {
console.log("File name:", file.name);
console.log("File type:", file.type);
console.log("File size:", file.size);
// Create a download link
const url = URL.createObjectURL(file);
window.open(url);
} else {
console.log("File not found");
}

Com um bucket específico:

const file = await client.storage.getObject("camera.jpg", "product-images");
await client.storage.deleteObject("products/images/camera.jpg");
// With bucket
await client.storage.deleteObject("camera.jpg", "product-images");

Excluir um arquivo inexistente não lança um erro.

Liste arquivos por prefixo, com paginação opcional:

const result = await client.storage.listObjects("products/images/", {
bucket: "uploads",
maxResults: 50,
pageToken: undefined // for pagination
});
for (const item of result.items) {
console.log(item.fullPath, item.name, item.size, item.updated, item.contentType);
}
// Paginate
if (result.nextPageToken) {
const nextPage = await client.storage.listObjects("products/images/", {
pageToken: result.nextPageToken
});
}

Cada item carrega seu size em bytes e updated (um timestamp ISO 8601) no armazenamento local, S3 e GCS, e seu contentType no armazenamento local e no GCS — o suficiente para mostrar uma pasta sem um getSignedUrl() por arquivo. Uma listagem do S3 não tem content type; as pastas (result.prefixes) não trazem nenhum dos três.

O SDK lida de forma transparente com os prefixos das chaves de armazenamento. Você pode passar chaves com ou sem o prefixo de protocolo:

// All equivalent — the SDK strips the prefix internally
await client.storage.getSignedUrl("local://products/image.jpg");
await client.storage.getSignedUrl("s3://products/image.jpg");
await client.storage.getSignedUrl("products/image.jpg");
Método Descrição Retorna
putObject({ file, key?, bucket?, metadata? }) Enviar um arquivo UploadFileResult
getSignedUrl(key, bucket?) Obter URL de download + metadados DownloadConfig
getObject(key, bucket?) Baixar como objeto File File | null
deleteObject(key, bucket?) Excluir um arquivo void
listObjects(prefix, options?) Listar arquivos por prefixo StorageListResult