feat(storage): support transition + expiration + filter on lifecycle rules#93
Conversation
Greptile SummaryThis PR extends lifecycle rules to support multiple transitions per rule (e.g.
Confidence Score: 3/5A filter-only rule with no existing lifecycle rule passes all guards and reaches the API with a body containing a filter but no transitions, which is likely invalid. The core multi-transition and merge logic in packages/storage/src/lib/bucket/set/lifecycle.ts — the filter-only validation gap; packages/storage/src/lib/bucket/types.ts — the inaccurate JSDoc. Important Files Changed
Reviews (1): Last reviewed commit: "feat(storage): support multiple lifecycl..." | Re-trigger Greptile |
fef0a41 to
7ef8fb7
Compare
7ef8fb7 to
8686b24
Compare
8686b24 to
917647a
Compare
917647a to
ef63604
Compare
|
@greptileai review and summarise |
|
This seems like a valid error:
|
ef63604 to
5551fa7
Compare
|
Agreed — fixed. |
5551fa7 to
9968a48
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9968a48. Configure here.
…rules
Lifecycle rules now accept up to one transition (top-level
`storageClass` + `days`/`date`), an optional `expiration: { days?, date? }`,
and an optional `filter: { prefix }` on the same rule. Multiple rules per
bucket are supported — each input rule is matched to an existing rule by
`id`; unmatched existing rules are preserved unchanged.
A single no-id update auto-matches the only shape-compatible existing
rule (when exactly one exists), so legacy single-transition updates keep
working on buckets that also carry a TTL rule. Auto-match never crosses
shape boundaries — a transition update can't silently merge into a
TTL-only rule, and vice versa. Idless existing rules are preserved by
tracking matches via reference identity rather than by id.
Validation:
- `setBucketLifecycle` rejects rules with no transition and no expiration
unless they target an existing rule via `id`, catching filter-only or
toggle-only inputs before the network round-trip.
- `buildLifecycleRules` validates every emitted rule: transitions must
have both `storage_class` and `days`/`date`; expirations must have
`days`/`date`; each rule must carry at least one of the two.
- When transition fields are supplied but the merge can't resolve a
`storage_class`, the error surfaces explicitly instead of silently
dropping the user's `days`/`date`.
`getBucketInfo` returns the complete rule list in `lifecycleRules`. The
old partition into `lifecycleRules` and `ttlConfig` is gone:
`ttlConfig` on the response is `@deprecated` and no longer populated.
Read the bucket-wide TTL from `lifecycleRules` (the rule with only
`expiration`, no transition, no filter). `setBucketTtl` keeps working —
it derives the existing TTL by scanning `lifecycleRules` and strips the
TTL-shaped rule before handing the rest to `buildLifecycleRules`.
`setBucketLifecycle` no longer threads `ttlConfig` through, treating any
TTL-shaped rule as just another lifecycle rule. `BucketTtl` is annotated
as a back-compat shim slated for removal in the next major.
Existing single-rule, transition-only callers continue to work without
changes. `BucketLifecycleExpiration` is exported from the public surface.
Assisted-by: Claude Opus 4.7 via Claude Code
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9968a48 to
076417f
Compare
|
🎉 This PR is included in version 3.4.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |

Summary
storageClass+days/date), an optionalexpiration: { days?, date? }, and an optionalfilter: { prefix }on the same rule. At least one of transition or expiration must be present.id; unmatched existing rules are preserved unchanged. Single-rule + single-existing auto-matches without anidfor back-compat.getBucketInforeturns the complete rule list inlifecycleRules— transitions, expirations, filtered rules, all of them.ttlConfigon the response is marked@deprecatedand is no longer populated. Read the bucket-wide TTL fromlifecycleRules(the rule with onlyexpiration, no transition, no filter).setBucketTtlcontinues to work — it derives the existing TTL by scanninglifecycleRulesitself.BucketLifecycleExpirationis exported from the public surface.Example
Test plan
npm run test --workspace=@tigrisdata/storage(134 passing)npm run build --workspace=@tigrisdata/storage(tsc + tsup clean)getBucketInfoand confirm round-trip; toggle one rule byidviasetBucketLifecycleand confirm the other is preserved.🤖 Generated with Claude Code
Note
Medium Risk
Changes lifecycle rule serialization/merging and
getBucketInfonormalization, which could affect how bucket retention/transition policies are applied and preserved across updates. Extensive tests reduce risk but behavior changes for existing lifecycle configurations merit caution.Overview
Extends bucket lifecycle support to multiple rules per bucket, where each rule can include a single transition plus optional
expirationand optionalfilter.prefix, and updates are merged into existing bucket rules primarily byid(with a constrained no-idauto-match for back-compat).getBucketInfonow returns the full lifecycle rule list (including expirations and filters) viasettings.lifecycleRules, andsettings.ttlConfigis marked deprecated and is no longer populated;setBucketTtlderives the bucket-wide TTL by scanninglifecycleRulesand avoids duplicating the TTL-shaped rule.Updates include new public types (
BucketLifecycleExpiration,BucketLifecycleFilter), API shape support for lifecycle rule filters, tightened validation insetBucketLifecycle/buildLifecycleRules, and a large expansion of lifecycle merging test coverage.Reviewed by Cursor Bugbot for commit 076417f. Bugbot is set up for automated code reviews on this repo. Configure here.