diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index a915e8c..2be9c43 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.1.1"
+ ".": "0.2.0"
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..024b1c9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,45 @@
+# Changelog
+
+## [0.2.0](https://github.com/developmentseed/multistore/compare/multistore-v0.1.1...multistore-v0.2.0) (2026-03-19)
+
+
+### Features
+
+* add display_name field to ResolvedBucket ([263d324](https://github.com/developmentseed/multistore/commit/263d324fe417a9bb9a4d04fcac9b029688d7be19))
+* add metering middleware ([20395f7](https://github.com/developmentseed/multistore/commit/20395f74def521b4ee74260e8cc8e399e81be25c))
+* add metering middleware ([73c2d2c](https://github.com/developmentseed/multistore/commit/73c2d2cdc9dd28606b2da0e4217e222881c05600))
+* add OidcDiscoveryRouteHandler for .well-known endpoints ([d5f01a0](https://github.com/developmentseed/multistore/commit/d5f01a0e9b52ce92e9c309f03b48c99adc3ef11a))
+* add RouteHandler trait and RequestInfo for pluggable pre-dispatch routing ([1db06a8](https://github.com/developmentseed/multistore/commit/1db06a873b4af645082899520c14439d5b419202))
+* add StsRouteHandler for AssumeRoleWithWebIdentity interception ([c6d6d66](https://github.com/developmentseed/multistore/commit/c6d6d66fb908d8471f0e607b1f8ded10b916f30e))
+* **cf-workers:** add azure/gcp feature flags for StoreBuilder variants ([61aa2f5](https://github.com/developmentseed/multistore/commit/61aa2f53e4ba6c9ec28c061ed7d1ec7b86e6e6a2))
+* **core:** support percent encoding ([8c69f11](https://github.com/developmentseed/multistore/commit/8c69f118ac71bf088e305d9c9e5db825dc303dcc))
+* create multistore-cf-workers crate with reusable Workers adapters ([73c79b5](https://github.com/developmentseed/multistore/commit/73c79b582d88334fb7a65970905aaa6bda4b3f57))
+* create multistore-path-mapping crate for hierarchical path routing ([19d6626](https://github.com/developmentseed/multistore/commit/19d66268b8d038eae403f51ebb9ca869f4d194ae))
+* get-object ([#4](https://github.com/developmentseed/multistore/issues/4)) ([9f3d8e8](https://github.com/developmentseed/multistore/commit/9f3d8e85ce97a265907e4473401b94829258c992))
+* support pagination on bucket list ([017f22d](https://github.com/developmentseed/multistore/commit/017f22d531d6a54e90f2d560fe46b2b1aebce6ea))
+* support range requests ([4053c5f](https://github.com/developmentseed/multistore/commit/4053c5f8ae6cf5089c863018ac575ba2caba8e25))
+* **workers:** add rate-limiting ([47a5973](https://github.com/developmentseed/multistore/commit/47a5973856f8a4c46167951ce439fe2abf65be59))
+
+
+### Bug Fixes
+
+* add default allowed roles ([35dd823](https://github.com/developmentseed/multistore/commit/35dd82352e7f14fb8f87c71e98862f737cb96a07))
+* apply ListRewrite.add_prefix to Prefix element and fix double-slash in rewrite_key ([3ba9890](https://github.com/developmentseed/multistore/commit/3ba98907a0eea206741afe85c4c042281630709e))
+* **ci:** add --cwd to wrangler commands and fix step reference ([a250cf2](https://github.com/developmentseed/multistore/commit/a250cf2efd0ae0d32a36132c935fec9b1fcce5bf))
+* **ci:** let wrangler output stream directly ([f98780e](https://github.com/developmentseed/multistore/commit/f98780e55380086dae3e2e3ec83bfa34187a0c11))
+* **ci:** pass CLOUDFLARE_ACCOUNT_ID as secret to reusable workflow ([c2ff2a7](https://github.com/developmentseed/multistore/commit/c2ff2a7b70b304e4b99c3b41aa3b45d0332a12bc))
+* correct endpoint ([0d9fd27](https://github.com/developmentseed/multistore/commit/0d9fd27a9f3e55055a1a3723f55a528e9e974cce))
+* correct STS endpoint ([3f6b2be](https://github.com/developmentseed/multistore/commit/3f6b2be6f868957fd6d2b19536ae43a2aae6a990))
+* ensure cloudflare streams data properly ([19053b2](https://github.com/developmentseed/multistore/commit/19053b29ecc41896f05bd062713f36927289d57d))
+* handle Azure and GCS URLs in UnsignedUrlSigner ([3af3845](https://github.com/developmentseed/multistore/commit/3af3845c900843e073dca031a8f15bbe692a1089))
+* match S3 ListObjectsV2 delimiter behavior ([ad0acfb](https://github.com/developmentseed/multistore/commit/ad0acfbe953f1ca9dc57c9f2a5b53844143e969f))
+* pin worker version ([2752efb](https://github.com/developmentseed/multistore/commit/2752efb7e6cea5b74a6f399e4e025b525f4124dd))
+* **rate-limit:** ensure middleware runs before bucket resolution ([b15cd64](https://github.com/developmentseed/multistore/commit/b15cd64664e195c6ed39e3cc8673c62dcd7d4f25))
+* support range HEAD requests ([758c44c](https://github.com/developmentseed/multistore/commit/758c44c13cc59a9f9ee6381b3d6b0573fa084c55))
+* **workers:** support multipart downloads ([7e4c313](https://github.com/developmentseed/multistore/commit/7e4c313e693d02c4b9adfaa36c82f3851504c41c))
+* **workers:** us sqlite durable object storage ([65b7101](https://github.com/developmentseed/multistore/commit/65b71017691d868037815ca8f284bddc6e1c80d8))
+
+
+### Performance Improvements
+
+* use Cow<BucketConfig> in OidcBackendAuth to avoid per-request clones ([4dff450](https://github.com/developmentseed/multistore/commit/4dff4509a87524ce5eb3d3154de6dfa8c39b9256))
diff --git a/Cargo.toml b/Cargo.toml
index c6e639d..7b65c7e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,7 +24,7 @@ default-members = [
resolver = "2"
[workspace.package]
-version = "0.1.1"
+version = "0.2.0"
edition = "2021"
license = "MIT"
@@ -100,10 +100,10 @@ console_error_panic_hook = "0.1.7"
lambda_http = "0.13"
# Internal crates
-multistore = { path = "crates/core", version = "0.1.1" }
-multistore-static-config = { path = "crates/static-config", version = "0.1.1" }
-multistore-sts = { path = "crates/sts", version = "0.1.1" }
-multistore-metering = { path = "crates/metering", version = "0.1.1" }
-multistore-cf-workers = { path = "crates/cf-workers", version = "0.1.1" }
-multistore-oidc-provider = { path = "crates/oidc-provider", version = "0.1.1" }
-multistore-path-mapping = { path = "crates/path-mapping", version = "0.1.1" }
+multistore = { path = "crates/core", version = "0.2.0" }
+multistore-static-config = { path = "crates/static-config", version = "0.2.0" }
+multistore-sts = { path = "crates/sts", version = "0.2.0" }
+multistore-metering = { path = "crates/metering", version = "0.2.0" }
+multistore-cf-workers = { path = "crates/cf-workers", version = "0.2.0" }
+multistore-oidc-provider = { path = "crates/oidc-provider", version = "0.2.0" }
+multistore-path-mapping = { path = "crates/path-mapping", version = "0.2.0" }
diff --git a/crates/core/src/api/list.rs b/crates/core/src/api/list.rs
index b8c576e..529c416 100644
--- a/crates/core/src/api/list.rs
+++ b/crates/core/src/api/list.rs
@@ -147,13 +147,12 @@ pub(crate) fn build_list_xml(
if url_encode {
// S3 URL-encodes per RFC 3986: leave unreserved chars + '/' unencoded.
// Unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
- const S3_ENCODE_SET: &percent_encoding::AsciiSet =
- &percent_encoding::NON_ALPHANUMERIC
- .remove(b'-')
- .remove(b'.')
- .remove(b'_')
- .remove(b'~')
- .remove(b'/');
+ const S3_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC
+ .remove(b'-')
+ .remove(b'.')
+ .remove(b'_')
+ .remove(b'~')
+ .remove(b'/');
percent_encoding::utf8_percent_encode(&s, S3_ENCODE_SET).to_string()
} else {
s
@@ -375,16 +374,36 @@ mod tests {
let xml = build_list_xml(¶ms, &list_result, &config, None).unwrap();
// EncodingType element should be present
- assert!(xml.contains("url"), "Missing EncodingType element: {}", xml);
+ assert!(
+ xml.contains("url"),
+ "Missing EncodingType element: {}",
+ xml
+ );
// Key: spaces encoded, but '/', '.', '-' preserved (RFC 3986 unreserved + '/')
- assert!(xml.contains("dir/file%20with%20spaces.txt"), "Key not encoded correctly: {}", xml);
+ assert!(
+ xml.contains("dir/file%20with%20spaces.txt"),
+ "Key not encoded correctly: {}",
+ xml
+ );
// CommonPrefix: spaces encoded, '/' preserved
- assert!(xml.contains("dir/sub%20dir/"), "CommonPrefix not encoded correctly: {}", xml);
+ assert!(
+ xml.contains("dir/sub%20dir/"),
+ "CommonPrefix not encoded correctly: {}",
+ xml
+ );
// Prefix: '/' preserved
- assert!(xml.contains("dir/") || xml.contains("dir/sub%20dir/"),
- "Prefix not encoded correctly: {}", xml);
+ assert!(
+ xml.contains("dir/")
+ || xml.contains("dir/sub%20dir/"),
+ "Prefix not encoded correctly: {}",
+ xml
+ );
// Delimiter: '/' preserved
- assert!(xml.contains("/"), "Delimiter should not encode '/': {}", xml);
+ assert!(
+ xml.contains("/"),
+ "Delimiter should not encode '/': {}",
+ xml
+ );
}
#[test]
@@ -409,8 +428,11 @@ mod tests {
let xml = build_list_xml(¶ms, &list_result, &config, None).unwrap();
- assert!(xml.contains("test_file%283%29.png"),
- "Expected S3-style encoding of parens: {}", xml);
+ assert!(
+ xml.contains("test_file%283%29.png"),
+ "Expected S3-style encoding of parens: {}",
+ xml
+ );
}
#[test]
@@ -434,9 +456,17 @@ mod tests {
let xml = build_list_xml(¶ms, &list_result, &config, None).unwrap();
// No EncodingType element
- assert!(!xml.contains(""), "EncodingType should not be present: {}", xml);
+ assert!(
+ !xml.contains(""),
+ "EncodingType should not be present: {}",
+ xml
+ );
// Key should NOT be URL-encoded (spaces are XML-safe)
- assert!(xml.contains("dir/file with spaces.txt"), "Key should be raw: {}", xml);
+ assert!(
+ xml.contains("dir/file with spaces.txt"),
+ "Key should be raw: {}",
+ xml
+ );
}
#[test]