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