@@ -1753,6 +1753,32 @@ impl Operation {
17531753}
17541754
17551755impl SetCondition {
1756+ /// Creates a condition for compare-and-set operations from an expected
1757+ /// previous value.
1758+ ///
1759+ /// - `None` → [`SetCondition::NotExists`] (create-if-absent)
1760+ /// - `Some(value)` → [`SetCondition::ValueEquals`] (update-if-unchanged)
1761+ ///
1762+ /// # Examples
1763+ ///
1764+ /// ```no_run
1765+ /// use inferadb_ledger_sdk::SetCondition;
1766+ ///
1767+ /// // Insert only if key doesn't exist
1768+ /// let cond = SetCondition::from_expected(None::<Vec<u8>>);
1769+ /// assert!(matches!(cond, SetCondition::NotExists));
1770+ ///
1771+ /// // Update only if current value matches
1772+ /// let cond = SetCondition::from_expected(Some(b"old-value".to_vec()));
1773+ /// assert!(matches!(cond, SetCondition::ValueEquals(_)));
1774+ /// ```
1775+ pub fn from_expected ( expected : Option < impl Into < Vec < u8 > > > ) -> Self {
1776+ match expected {
1777+ None => SetCondition :: NotExists ,
1778+ Some ( value) => SetCondition :: ValueEquals ( value. into ( ) ) ,
1779+ }
1780+ }
1781+
17561782 /// Converts to protobuf set condition.
17571783 fn to_proto ( & self ) -> proto:: SetCondition {
17581784 let condition = match self {
@@ -3191,7 +3217,7 @@ impl LedgerClient {
31913217 // Single-Operation Convenience Methods
31923218 // =============================================================================
31933219
3194- /// Writes a single entity (set).
3220+ /// Writes a single entity (set), optionally with an expiration timestamp .
31953221 ///
31963222 /// Convenience wrapper around [`write`](Self::write) for the common case of
31973223 /// setting a single key-value pair. Generates an idempotency key
@@ -3203,6 +3229,8 @@ impl LedgerClient {
32033229 /// * `vault` - Optional vault slug (omit for organization-level entities).
32043230 /// * `key` - The entity key.
32053231 /// * `value` - The entity value.
3232+ /// * `expires_at` - Optional Unix timestamp (seconds) when the entity expires. Pass `None` for
3233+ /// no expiration.
32063234 ///
32073235 /// # Errors
32083236 ///
@@ -3216,8 +3244,14 @@ impl LedgerClient {
32163244 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
32173245 /// # let client = LedgerClient::connect("http://localhost:50051", "my-service").await?;
32183246 /// # let (organization, vault) = (OrganizationSlug::new(1), VaultSlug::new(1));
3247+ /// // Without expiration:
32193248 /// let result = client
3220- /// .set_entity(organization, Some(vault), "user:123", b"data".to_vec())
3249+ /// .set_entity(organization, Some(vault), "user:123", b"data".to_vec(), None)
3250+ /// .await?;
3251+ ///
3252+ /// // With expiration:
3253+ /// let result = client
3254+ /// .set_entity(organization, Some(vault), "session:abc", b"token".to_vec(), Some(1700000000))
32213255 /// .await?;
32223256 /// # Ok(())
32233257 /// # }
@@ -3228,8 +3262,14 @@ impl LedgerClient {
32283262 vault : Option < VaultSlug > ,
32293263 key : impl Into < String > ,
32303264 value : Vec < u8 > ,
3265+ expires_at : Option < u64 > ,
32313266 ) -> Result < WriteSuccess > {
3232- self . write ( organization, vault, vec ! [ Operation :: set_entity( key, value) ] ) . await
3267+ self . write (
3268+ organization,
3269+ vault,
3270+ vec ! [ Operation :: SetEntity { key: key. into( ) , value, expires_at, condition: None } ] ,
3271+ )
3272+ . await
32333273 }
32343274
32353275 /// Deletes a single entity.
@@ -3268,7 +3308,7 @@ impl LedgerClient {
32683308 self . write ( organization, vault, vec ! [ Operation :: delete_entity( key) ] ) . await
32693309 }
32703310
3271- /// Sets a single entity with a conditional write.
3311+ /// Sets a single entity with a conditional write, optionally with an expiration timestamp .
32723312 ///
32733313 /// Convenience wrapper around [`write`](Self::write) for conditional
32743314 /// set operations (compare-and-swap). The write only succeeds if the
@@ -3281,6 +3321,8 @@ impl LedgerClient {
32813321 /// * `key` - The entity key.
32823322 /// * `value` - The entity value.
32833323 /// * `condition` - The condition that must be satisfied for the write to succeed.
3324+ /// * `expires_at` - Optional Unix timestamp (seconds) when the entity expires. Pass `None` for
3325+ /// no expiration.
32843326 ///
32853327 /// # Errors
32863328 ///
@@ -3301,6 +3343,7 @@ impl LedgerClient {
33013343 /// "user:123",
33023344 /// b"new-data".to_vec(),
33033345 /// SetCondition::NotExists,
3346+ /// None,
33043347 /// )
33053348 /// .await?;
33063349 /// # Ok(())
@@ -3313,61 +3356,17 @@ impl LedgerClient {
33133356 key : impl Into < String > ,
33143357 value : Vec < u8 > ,
33153358 condition : SetCondition ,
3316- ) -> Result < WriteSuccess > {
3317- self . write ( organization, vault, vec ! [ Operation :: set_entity_if( key, value, condition) ] ) . await
3318- }
3319-
3320- /// Sets a single entity with an expiration timestamp.
3321- ///
3322- /// Convenience wrapper around [`write`](Self::write) for setting a
3323- /// key-value pair with a TTL. The entity is automatically removed after
3324- /// the expiration time.
3325- ///
3326- /// # Arguments
3327- ///
3328- /// * `organization` - Organization slug (external identifier).
3329- /// * `vault` - Optional vault slug (omit for organization-level entities).
3330- /// * `key` - The entity key.
3331- /// * `value` - The entity value.
3332- /// * `expires_at` - Unix timestamp (seconds) when the entity expires.
3333- ///
3334- /// # Errors
3335- ///
3336- /// Returns an error if validation fails or the write fails after retry
3337- /// attempts.
3338- ///
3339- /// # Example
3340- ///
3341- /// ```no_run
3342- /// # use inferadb_ledger_sdk::{LedgerClient, OrganizationSlug, VaultSlug};
3343- /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3344- /// # let client = LedgerClient::connect("http://localhost:50051", "my-service").await?;
3345- /// # let (organization, vault) = (OrganizationSlug::new(1), VaultSlug::new(1));
3346- /// let expires_at = 1700000000; // Unix timestamp
3347- /// let result = client
3348- /// .set_entity_with_expiry(
3349- /// organization,
3350- /// Some(vault),
3351- /// "session:abc",
3352- /// b"session-data".to_vec(),
3353- /// expires_at,
3354- /// )
3355- /// .await?;
3356- /// # Ok(())
3357- /// # }
3358- /// ```
3359- pub async fn set_entity_with_expiry (
3360- & self ,
3361- organization : OrganizationSlug ,
3362- vault : Option < VaultSlug > ,
3363- key : impl Into < String > ,
3364- value : Vec < u8 > ,
3365- expires_at : u64 ,
3359+ expires_at : Option < u64 > ,
33663360 ) -> Result < WriteSuccess > {
33673361 self . write (
33683362 organization,
33693363 vault,
3370- vec ! [ Operation :: set_entity_with_expiry( key, value, expires_at) ] ,
3364+ vec ! [ Operation :: SetEntity {
3365+ key: key. into( ) ,
3366+ value,
3367+ expires_at,
3368+ condition: Some ( condition) ,
3369+ } ] ,
33713370 )
33723371 . await
33733372 }
@@ -5769,6 +5768,31 @@ mod tests {
57695768 }
57705769 }
57715770
5771+ #[ test]
5772+ fn test_set_condition_from_expected_none ( ) {
5773+ let cond = SetCondition :: from_expected ( None :: < Vec < u8 > > ) ;
5774+ assert ! ( matches!( cond, SetCondition :: NotExists ) ) ;
5775+ }
5776+
5777+ #[ test]
5778+ fn test_set_condition_from_expected_some_vec ( ) {
5779+ let cond = SetCondition :: from_expected ( Some ( b"old-value" . to_vec ( ) ) ) ;
5780+ match cond {
5781+ SetCondition :: ValueEquals ( v) => assert_eq ! ( v, b"old-value" ) ,
5782+ other => panic ! ( "Expected ValueEquals, got: {other:?}" ) ,
5783+ }
5784+ }
5785+
5786+ #[ test]
5787+ fn test_set_condition_from_expected_some_slice ( ) {
5788+ let slice: & [ u8 ] = b"expected" ;
5789+ let cond = SetCondition :: from_expected ( Some ( slice. to_vec ( ) ) ) ;
5790+ match cond {
5791+ SetCondition :: ValueEquals ( v) => assert_eq ! ( v, b"expected" ) ,
5792+ other => panic ! ( "Expected ValueEquals, got: {other:?}" ) ,
5793+ }
5794+ }
5795+
57725796 // =========================================================================
57735797 // WriteSuccess Tests
57745798 // =========================================================================
0 commit comments