Release v4.1.0
v4.1.0
This is the first published 4.x release. 4.0.0 was never shipped to npm, so if
you are upgrading from 3.x this is a major, breaking release — read the
"Breaking changes" section below. Full details and code examples are in
MIGRATION.md.
s3proxy still does the same thing: stream S3 objects to HTTP responses without
buffering them on your server. v4 fixes v3's error contract and splits the
288-line class into a parser, a gateway, and an orchestrator.
Breaking changes (3.x -> 4.x)
- Typed errors instead of empty 200 streams. In v3 a missing key returned a
fully-formed empty stream with status 200; AccessDenied, InvalidRange, and
NoSuchBucket were absorbed the same way. v4 throws typed errors —
S3NotFound(404),S3Forbidden(403),S3InvalidRange(416),
InvalidRequest(400) — all extendingS3ProxyError(withstatusCodeand
the underlying SDK error ascause). These throw fromfetch()before
res.writeHead, so a v3stream.on('error')handler will no longer see
them; wrap theawaitin try/catch instead. proxy.get(req, res)/proxy.head(req, res)/proxy.healthCheckStream(res)
removed. These mutated the response as a side effect. v4 ships one pure
entry point,proxy.fetch(req), returning{ stream, status, headers }.
Usereq.method = 'HEAD'for HEAD, andproxy.healthCheck()(throws on
failure) for health endpoints.- Response shape renamed: v3's
s3stream/statusCodeare nowstream/
status(headersunchanged). - Parser helpers are free functions.
S3Proxy.parseRequest/
mapHeaderToParam/stripLeadingSlashmoved to package-root exports:
import { parseRequest } from 's3proxy'.parseRequestnow throws
InvalidRequestfor malformed percent-encoding and null-byte keys. S3Proxy.isNonFatalErrorremoved — useinstanceof S3ProxyError.HttpRequestis now a structural type ({ url, method?, headers, path?, query? }), no longer extendsIncomingMessage.
Added
verifyOnInit: false— skip the health check oninit()so a transient
S3 hiccup can't crashloop a pod before readiness probes take over.proxy.pipe(req, res)— fetch an object and stream it straight to an HTTP
response. Recovers v3's one-callproxy.get(req, res)ergonomics without
the empty-200 bug: a missing or forbidden object renders an honest
404/403/416.proxy.middleware()— an Express/Connect-style request handler built on
pipe():app.get('/*splat', proxy.middleware()). Forwards unexpected
errors tonextwhen present.proxy.staticSite(options)— replicates S3 static website hosting:
index-document resolution (/and/dir/->index.html) and, when
errorDocumentis set, serving that key under the original 4xx status for
missing/forbidden objects. An opt-in layer overfetch()— it can't mask a
real failure.
Fixed
- Header passthrough. v4.0 rebuilt response headers from the typed SDK
output and silently droppedx-amz-meta-*custom metadata plus several
S3-specific headers (server-side encryption, KMS key id, version id, storage
class, website redirect, expiration, restore). All are forwarded again, so
callers that round-trip custom metadata work as they did in v3.
Health checks
The v3 healthCheckStream(res) wrapper is gone; v4 exposes the primitive
proxy.healthCheck() (pings the bucket, throws S3ProxyError on failure) and
you render the response. For a load-balancer /health endpoint, try/catch and
return 200 or the error's status. For orchestrators (ECS/Kubernetes), construct
with verifyOnInit: false so init() does not ping S3 at boot — a transient
hiccup then can't crashloop the pod before dashboards surface why — and let a
/ready handler call healthCheck() to drive readiness. See the README
"Health Checks" and "verifyOnInit for orchestrators" sections for full
examples.
Requirements
- ESM-only, Node.js 22.13+, AWS SDK v3.
Staying on v3
npm install s3proxy@^3.0.0 keeps the old behavior; it just doesn't get the
typed-error contract, the parser/gateway split, or verifyOnInit.