Guia — IStorageProvider
Um backend de blob storage novo, ativado por provider: num spec.storages. Usado pelos steps
storage.* — dois deles (storage.put/storage.get) fazem streaming do corpo da requisição/
resposta HTTP direto para o backend, sem nunca virar JsonNode; os outros (presignUpload/
presignDownload/stat/list/delete) são operação pura de metadata.
Exemplo mínimo
using Pipevine.Abstractions;
using Pipevine.Storage.Abstractions;
// A chave usada no YAML: `provider: acmeblob`
[Provider("acmeblob", ProviderKind.Storage)]
public sealed class AcmeBlobProvider : IStorageProvider
{
public ValueTask<StorageObjectInfo> PutAsync(string key, Stream content, string? contentType, CancellationToken ct)
{
// content.CanSeek + content.Length reportam o tamanho quando o chamador já sabe —
// mesmo que content não seja literalmente seekable (storage.put embrulha o corpo da
// requisição HTTP assim quando o cliente manda Content-Length).
}
public ValueTask<StorageObject?> GetAsync(string key, CancellationToken ct) { /* null se não existe */ }
public ValueTask<StorageObjectInfo?> StatAsync(string key, CancellationToken ct) { /* null se não existe */ }
public ValueTask DeleteAsync(string key, CancellationToken ct) { /* ... */ }
public IAsyncEnumerable<StorageObjectInfo> ListAsync(string? prefix, CancellationToken ct) { /* pagina internamente */ }
public ValueTask<Uri> PresignUploadAsync(string key, TimeSpan ttl, string? contentType, CancellationToken ct) { /* ... */ }
public ValueTask<Uri> PresignDownloadAsync(string key, TimeSpan ttl, CancellationToken ct) { /* ... */ }
}
Ativação
spec:
storages:
- id: docs
provider: acmeblob
config:
endpoint: "${config:AcmeBlob:Endpoint}"
accessKeyId: "${secret:env:ACMEBLOB_ACCESS_KEY}"
routes:
- id: upload
method: POST
path: /docs
pipeline:
- { type: storage.put, id: put, storage: docs, key: input.route.key }
response: { status: 201, body: "steps.put.result" }
services.AddKeyedSingleton<IStorageProvider, AcmeBlobProvider>("acmeblob");
// mais services.AddPipevineStorage() do core
config é uma bag livre — o core não valida seus campos, quem valida é o próprio provider, no
build. Sem fail-open por default: uma falha do backend propaga como exceção, exatamente como
qualquer outra falha de infraestrutura — não é engolida silenciosamente como um cache pode fazer.
Para onde ir daqui
- Visão geral de extensibilidade
- Referência de steps — os sete
storage.*, com exemplo YAML de cada um.