Skip to content

feat(storage): support transition + expiration + filter on lifecycle rules#93

Merged
designcode merged 1 commit into
mainfrom
feat/extend-life-cycle-api
May 7, 2026
Merged

feat(storage): support transition + expiration + filter on lifecycle rules#93
designcode merged 1 commit into
mainfrom
feat/extend-life-cycle-api

Conversation

@designcode
Copy link
Copy Markdown
Collaborator

@designcode designcode commented May 5, 2026

Summary

  • A lifecycle rule now accepts up to one transition (top-level storageClass + days/date), an optional expiration: { days?, date? }, and an optional filter: { prefix } on the same rule. At least one of transition or expiration must be present.
  • Multiple rules per bucket are supported — each input rule is matched to an existing rule by id; unmatched existing rules are preserved unchanged. Single-rule + single-existing auto-matches without an id for back-compat.
  • getBucketInfo returns the complete rule list in lifecycleRules — transitions, expirations, filtered rules, all of them. ttlConfig on the response is marked @deprecated and is no longer populated. Read the bucket-wide TTL from lifecycleRules (the rule with only expiration, no transition, no filter).
  • setBucketTtl continues to work — it derives the existing TTL by scanning lifecycleRules itself.
  • Existing single-rule, transition-only callers keep working without changes. BucketLifecycleExpiration is exported from the public surface.

Example

await setBucketLifecycle('my-bucket', {
  lifecycleRules: [
    {
      filter: { prefix: 'logs/' },
      storageClass: 'GLACIER',
      days: 30,
      expiration: { days: 30 },
    },
    {
      filter: { prefix: 'logs-2/' },
      storageClass: 'GLACIER_IR',
      days: 30,
      expiration: { days: 60 },
    },
  ],
});

Test plan

  • npm run test --workspace=@tigrisdata/storage (134 passing)
  • npm run build --workspace=@tigrisdata/storage (tsc + tsup clean)
  • Smoke against a Tigris bucket: set two prefix-scoped rules each carrying transition + expiration, then getBucketInfo and confirm round-trip; toggle one rule by id via setBucketLifecycle and confirm the other is preserved.

🤖 Generated with Claude Code


Note

Medium Risk
Changes lifecycle rule serialization/merging and getBucketInfo normalization, 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 expiration and optional filter.prefix, and updates are merged into existing bucket rules primarily by id (with a constrained no-id auto-match for back-compat).

getBucketInfo now returns the full lifecycle rule list (including expirations and filters) via settings.lifecycleRules, and settings.ttlConfig is marked deprecated and is no longer populated; setBucketTtl derives the bucket-wide TTL by scanning lifecycleRules and avoids duplicating the TTL-shaped rule.

Updates include new public types (BucketLifecycleExpiration, BucketLifecycleFilter), API shape support for lifecycle rule filters, tightened validation in setBucketLifecycle/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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 5, 2026

Greptile Summary

This PR extends lifecycle rules to support multiple transitions per rule (e.g. STANDARD_IA at 30 days → GLACIER at 90) and adds an optional filter.prefix to scope a rule to a key prefix, while keeping the existing single-transition shorthand fully backward-compatible.

  • BucketLifecycleRule gains a transitions array and a filter field; new types BucketLifecycleTransition and BucketLifecycleFilter are exported.
  • buildLifecycleRules / formatTransitionRule are refactored to resolve and merge transition arrays; getBucketInfo is updated to surface the full transitions array while back-populating the legacy storageClass/days/date fields from transitions[0].
  • Validation in setBucketLifecycle is restructured to catch mixed usage of the two styles and validate each transition entry individually.

Confidence Score: 3/5

A 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 resolveTransitions is sound and well-tested, but ruleHasTransitionContent treating filter as sufficient content creates a gap: a filter-only rule on a bucket with no existing lifecycle rule bypasses all guards and triggers an API call with a lifecycle rule body that has no transitions array, which is likely invalid and will produce an unexpected error or silently malformed state.

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

Filename Overview
packages/storage/src/lib/bucket/set/lifecycle.ts Validation logic restructured to support multi-transition and filter fields; a filter-only rule with no existing transitions can slip past guards and produce an API body with no transitions.
packages/storage/src/lib/bucket/utils/lifecycle.ts Core rule-building logic cleanly refactored to handle multiple transitions and filter merging; the resolveTransitions branching logic is correct for the defined cases.
packages/storage/src/lib/bucket/types.ts New types BucketLifecycleTransition and BucketLifecycleFilter added cleanly; JSDoc on BucketLifecycleRule incorrectly states "transitions wins" when both fields are set, but runtime behaviour is actually an error.
packages/storage/src/lib/bucket/info.ts Mapping updated to expose full transitions array and filter while populating legacy fields from transitions[0] for backward compatibility.
packages/storage/src/lib/bucket/utils/lifecycle.test.ts 10 new test cases covering multi-transition rules, filter handling, and toggle-only preservations; no coverage for the filter-only + no-existing-rule edge case.
packages/storage/src/lib/bucket/utils/api.ts Added filter.prefix field to the API type shape; straightforward, no issues.
packages/storage/src/server.ts New types re-exported from the public surface; no issues.
packages/storage/README.md Documentation updated with new property tables and a multi-transition example; content is accurate and clear.

Reviews (1): Last reviewed commit: "feat(storage): support multiple lifecycl..." | Re-trigger Greptile

Comment thread packages/storage/src/lib/bucket/types.ts Outdated
Comment thread packages/storage/src/lib/bucket/set/lifecycle.ts Outdated
Comment thread packages/storage/src/lib/bucket/set/lifecycle.ts Outdated
Comment thread packages/storage/src/lib/bucket/utils/lifecycle.ts
Comment thread packages/storage/src/lib/bucket/info.ts Outdated
@designcode designcode force-pushed the feat/extend-life-cycle-api branch from fef0a41 to 7ef8fb7 Compare May 6, 2026 12:04
@designcode designcode changed the title feat(storage): support multiple lifecycle transitions and prefix filter feat(storage): support transition + expiration + filter on lifecycle rules May 6, 2026
Comment thread packages/storage/src/lib/bucket/info.ts
Comment thread packages/storage/src/lib/bucket/utils/lifecycle.ts Outdated
Comment thread packages/storage/src/lib/bucket/types.ts
@designcode designcode force-pushed the feat/extend-life-cycle-api branch from 7ef8fb7 to 8686b24 Compare May 6, 2026 13:11
Comment thread packages/storage/src/lib/bucket/set/lifecycle.ts
Comment thread packages/storage/src/lib/bucket/utils/lifecycle.ts
@designcode designcode force-pushed the feat/extend-life-cycle-api branch from 8686b24 to 917647a Compare May 6, 2026 13:16
Comment thread packages/storage/src/lib/bucket/utils/lifecycle.ts Outdated
Comment thread packages/storage/src/lib/bucket/utils/lifecycle.ts
@designcode designcode force-pushed the feat/extend-life-cycle-api branch from 917647a to ef63604 Compare May 6, 2026 13:49
@garrensmith
Copy link
Copy Markdown

@greptileai review and summarise

@garrensmith
Copy link
Copy Markdown

This seems like a valid error:

A 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 resolveTransitions is sound and well-tested, but ruleHasTransitionContent treating filter as sufficient content creates a gap: a filter-only rule on a bucket with no existing lifecycle rule bypasses all guards and triggers an API call with a lifecycle rule body that has no transitions array, which is likely invalid and will produce an unexpected error or silently malformed state.

Comment thread packages/storage/src/lib/bucket/set/lifecycle.ts
Comment thread packages/storage/src/lib/bucket/utils/lifecycle.ts
@designcode designcode force-pushed the feat/extend-life-cycle-api branch from ef63604 to 5551fa7 Compare May 7, 2026 08:28
@designcode
Copy link
Copy Markdown
Collaborator Author

Agreed — fixed. setBucketLifecycle now rejects rules with no transition and no expiration unless they target an existing rule via id, so filter-only and toggle-only inputs without an id error out before the network round-trip rather than reaching buildLifecycleRules with a malformed body. Also added a final-shape check inside buildLifecycleRules that catches partial updates which fail to merge into a complete rule.

Comment thread packages/storage/src/lib/bucket/utils/lifecycle.ts
@designcode designcode force-pushed the feat/extend-life-cycle-api branch from 5551fa7 to 9968a48 Compare May 7, 2026 09:16
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread packages/storage/src/lib/bucket/set/lifecycle.ts
…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>
@designcode designcode force-pushed the feat/extend-life-cycle-api branch from 9968a48 to 076417f Compare May 7, 2026 10:11
@designcode designcode merged commit deaa6f3 into main May 7, 2026
2 checks passed
@designcode designcode deleted the feat/extend-life-cycle-api branch May 7, 2026 10:57
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

🎉 This PR is included in version 3.4.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants