### Quick Start: Initialize and Use Re.This Client Source: https://github.com/vendelieu/re.this/blob/master/README.md Demonstrates basic usage of the Re.This client, including initialization, setting and getting keys, pipelining commands, executing transactions, and subscribing to a Pub/Sub channel. The client defaults to localhost:6379. ```kotlin suspend fun main() { // 1. Initialize client (defaults to localhost:6379) val client = ReThis() // 2. Simple SET/GET client.set("hello", "world") println(client.get("hello")) // → "world" // 3. Pipeline multiple commands val results = client.pipeline { set("foo", "bar") get("foo") } println(results) // → [OK, "bar"] // 4. Transaction val txResults = client.transaction { set("bar", "baz") get("bar") } println(txResults) // → [OK, "baz"] // 5. Pub/Sub example client.subscribe("news") { _, msg -> println("Received: $msg") } } ``` -------------------------------- ### Basic Command Spec Example (GET) Source: https://github.com/vendelieu/re.this/blob/master/client/src/commonMain/kotlin/eu/vendeli/rethis/api/spec/commands/README.md An example of a basic command specification for the Redis GET command. It defines the command name, operation type, expected response types, and the encode function signature. ```APIDOC ## GET Command Specification ### Description Defines the Redis GET command for retrieving a value associated with a key. ### Method GET ### Endpoint N/A (This is an SDK interface definition) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```kotlin @RedisCommand("GET", RedisOperation.READ, [RespCode.BULK, RespCode.NULL]) fun interface GetCommand : RedisCommandSpec { suspend fun encode( key: String, ): CommandRequest } ``` ### Response #### Success Response (200) - **String** - The value associated with the key, or null if the key does not exist. ``` -------------------------------- ### Command Spec with Options Example (SET) Source: https://github.com/vendelieu/re.this/blob/master/client/src/commonMain/kotlin/eu/vendeli/rethis/api/spec/commands/README.md An example of a SET command specification that includes optional parameters using the `@RIgnoreSpecAbsence` annotation. ```APIDOC ## SET Command Specification with Options ### Description Defines the Redis SET command, allowing for optional parameters like expiration, persistence, and conditional updates. ### Method SET ### Endpoint N/A (This is an SDK interface definition) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```kotlin @RedisCommand( "SET", RedisOperation.WRITE, [RespCode.BULK, RespCode.SIMPLE_STRING, RespCode.NULL], ) fun interface SetCommand : RedisCommandSpec { suspend fun encode( key: String, value: String, @RIgnoreSpecAbsence vararg options: SetOption, ): CommandRequest } ``` ### Response #### Success Response (200) - **String** - OK or specific status based on options. ``` -------------------------------- ### Minimal Example of Updating a Tenant with a Distributed Lock Source: https://github.com/vendelieu/re.this/wiki/Distributed-Locks A basic example showing how to acquire a distributed lock for a specific tenant and perform read-modify-write operations within the critical section. Ensure the coroutine context has a Job. ```kotlin @OptIn(ReThisExperimental::class) suspend fun updateTenant(client: ReThis, tenantId: String) { val lock = client.reDistributedLock("locks:tenant:$tenantId") lock.withLock { // 1) read model // 2) compute changes // 3) write updates } } ``` -------------------------------- ### Execute Transaction Commands in Kotlin Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates atomic execution of commands using MULTI/EXEC. Includes examples for optimistic locking with WATCH, nested transactions, and manual control. ```kotlin import eu.vendeli.rethis.command.string.* import eu.vendeli.rethis.command.generic.* import eu.vendeli.rethis.command.transaction.* import eu.vendeli.rethis.shared.types.PlainString import eu.vendeli.rethis.shared.types.Int64 import eu.vendeli.rethis.shared.types.TransactionInvalidStateException suspend fun transactionOperations(client: ReThis) { // Basic transaction val results = client.transaction { set("account:1:balance", "1000") set("account:2:balance", "500") decrBy("account:1:balance", 100) incrBy("account:2:balance", 100) } // Results: [PlainString("OK"), PlainString("OK"), Int64(900), Int64(600)] // Transaction with conditional logic (use WATCH for optimistic locking) // Note: WATCH must be called before transaction starts client.watch("inventory:item:1") val currentStock = client.get("inventory:item:1")?.toInt() ?: 0 if (currentStock >= 5) { val purchaseResults = client.transaction { decrBy("inventory:item:1", 5) incrBy("sales:item:1", 5) } // Returns null if watched key was modified by another client } else { client.unwatch() // Cancel watch if not proceeding } // Nested transactions (inner becomes part of outer) val nestedResults = client.transaction { set("outer:key", "outer:value") client.transaction { set("inner:key", "inner:value") get("inner:key") } } // Transaction with pipeline inside val mixedResults = client.transaction { set("key1", "value1") client.pipeline { set("key2", "value2") get("key2") } } // Error handling in transactions try { client.transaction { set("key", "value") // If any command fails or exception thrown, transaction is discarded throw RuntimeException("Something went wrong") } } catch (e: TransactionInvalidStateException) { println("Transaction was rolled back: ${e.message}") } // Manual transaction control (advanced) client.multi() // Start transaction client.set("manual:key", "manual:value") val execResults = client.exec() // Execute all queued commands // Discard transaction client.multi() client.set("will:discard", "value") client.discard() // Cancel transaction, no commands executed } ``` -------------------------------- ### Blocking Command Spec Example (XREAD) Source: https://github.com/vendelieu/re.this/blob/master/client/src/commonMain/kotlin/eu/vendeli/rethis/api/spec/commands/README.md An example of a blocking command specification for XREAD, demonstrating the `isBlocking` flag and the use of `@RedisOption.Token` for specific parameter mapping. ```APIDOC ## XREAD Command Specification (Blocking) ### Description Defines the blocking Redis XREAD command for reading data from streams. Supports options like COUNT and BLOCK for controlling the read behavior. ### Method XREAD ### Endpoint N/A (This is an SDK interface definition) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```kotlin @RedisCommand( "XREAD", RedisOperation.READ, [RespCode.ARRAY, RespCode.MAP, RespCode.NULL], isBlocking = true, ) fun interface XReadCommand : RedisCommandSpec> { suspend fun encode( @RedisOption.Token("STREAMS") key: List, id: List, @RedisOption.Token("COUNT") count: Long?, @RedisOption.Token("BLOCK") milliseconds: Long?, ): CommandRequest } ``` ### Response #### Success Response (200) - **Map** - A map representing the stream data read. ``` -------------------------------- ### Command Spec with Custom Decoder Example (SCAN) Source: https://github.com/vendelieu/re.this/blob/master/client/src/commonMain/kotlin/eu/vendeli/rethis/api/spec/commands/README.md An example of a SCAN command specification that uses a custom decoder (`StringScanDecoder`) for processing the response. ```APIDOC ## SCAN Command Specification with Custom Decoder ### Description Defines the Redis SCAN command for iterating over keys in a database, utilizing a custom decoder for specialized response handling. ### Method SCAN ### Endpoint N/A (This is an SDK interface definition) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```kotlin @RedisCommand("SCAN", RedisOperation.READ, [RespCode.ARRAY]) @RedisMeta.CustomCodec(decoder = StringScanDecoder::class) fun interface ScanCommand : RedisCommandSpec> { suspend fun encode( cursor: Long, @RIgnoreSpecAbsence vararg option: ScanOption, ): CommandRequest } ``` ### Response #### Success Response (200) - **ScanResult** - The result of the scan operation, processed by `StringScanDecoder`. ``` -------------------------------- ### Define a New Redis Command Spec Source: https://github.com/vendelieu/re.this/blob/master/client/src/commonMain/kotlin/eu/vendeli/rethis/api/spec/commands/README.md Example of defining a new Redis command specification for 'MYCOMMAND'. Ensure to run a clean build after adding new specs. ```kotlin @RedisCommand("MYCOMMAND", RedisOperation.WRITE, [RespCode.SIMPLE_STRING]) fun interface MyCommandCommand : RedisCommandSpec { suspend fun encode( key: String, value: String, ): CommandRequest } ``` -------------------------------- ### Basic Redis GET Command Specification Source: https://github.com/vendelieu/re.this/blob/master/client/src/commonMain/kotlin/eu/vendeli/rethis/api/spec/commands/README.md Defines a basic read-only GET command. The response type is expected to be a Bulk string or Null. This interface is processed by KSP to generate the actual command implementation. ```kotlin @RedisCommand("GET", RedisOperation.READ, [RespCode.BULK, RespCode.NULL]) fun interface GetCommand : RedisCommandSpec { suspend fun encode( key: String, ): CommandRequest } ``` -------------------------------- ### Perform Geospatial Operations Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates various geospatial operations including adding members, getting positions, calculating distances, generating geohashes, and performing radius and bounding box searches. Ensure necessary imports are included. ```kotlin import eu.vendeli.rethis.command.geospatial.* import eu.vendeli.rethis.shared.response.geospatial.GeoMember import eu.vendeli.rethis.shared.response.geospatial.GeoUnit import eu.vendeli.rethis.shared.response.geospatial.GeoSort import eu.vendeli.rethis.shared.request.geospatial.* suspend fun geoOperations(client: ReThis) { // Add locations client.geoAdd( "restaurants", GeoMember(longitude = -122.4194, latitude = 37.7749, member = "pizza_place"), GeoMember(longitude = -122.4089, latitude = 37.7855, member = "sushi_bar"), GeoMember(longitude = -122.4000, latitude = 37.7900, member = "burger_joint") ) // Returns 3 // Get position of members val positions = client.geoPos("restaurants", "pizza_place", "sushi_bar") // Returns list of GeoPosition(longitude, latitude) // Calculate distance between members val distanceKm = client.geoDist("restaurants", "pizza_place", "sushi_bar", GeoUnit.KILOMETERS) val distanceM = client.geoDist("restaurants", "pizza_place", "sushi_bar") // Default meters // Get geohash val hashes = client.geoHash("restaurants", "pizza_place") // Returns ["9q8yyk8yutp"] // Search by radius from coordinates val nearbyRadius = client.geoSearch( "restaurants", from = FromLongitudeLatitude(longitude = -122.41, latitude = 37.78), by = ByRadius(radius = 5.0, unit = GeoUnit.KILOMETERS), order = GeoSort.ASC, // Closest first count = 10, withCoord = true, // Include coordinates withDist = true, // Include distance withHash = true // Include geohash ) // Search by radius from existing member val nearbyMember = client.geoSearch( "restaurants", from = FromMember("pizza_place"), by = ByRadius(radius = 2.0, unit = GeoUnit.KILOMETERS), order = GeoSort.ASC ) // Search within bounding box val inBox = client.geoSearch( "restaurants", from = FromLongitudeLatitude(-122.41, 37.78), by = ByBox(width = 10.0, height = 10.0, unit = GeoUnit.KILOMETERS), order = GeoSort.ASC ) // Store search results in a new sorted set client.geoSearchStore( destination = "nearby:cached", source = "restaurants", from = FromLongitudeLatitude(-122.41, 37.78), by = ByRadius(5.0, GeoUnit.KILOMETERS), storedist = true // Store distances as scores ) } ``` -------------------------------- ### Perform String Operations with ReThis Source: https://context7.com/vendelieu/re.this/llms.txt Shows how to use Re.This's type-safe suspend functions for Redis string commands like SET, GET, MSET, MGET, and atomic operations. ```kotlin import eu.vendeli.rethis.command.string.* import eu.vendeli.rethis.shared.request.string.KeyValue import eu.vendeli.rethis.shared.request.string.SetOption suspend fun stringOperations(client: ReThis) { // Basic SET and GET client.set("greeting", "Hello, World!") // Returns "OK" val value = client.get("greeting") // Returns "Hello, World!" // SET with options (expiration, NX/XX) client.set("session:123", "user_data", SetOption.EX(3600)) // Expires in 1 hour client.set("lock:resource", "owner1", SetOption.NX, SetOption.PX(5000)) // Set if not exists, 5s TTL // Multiple keys at once client.mSet( KeyValue("user:1:name", "Alice"), KeyValue("user:1:email", "alice@example.com"), KeyValue("user:2:name", "Bob") ) val users = client.mGet("user:1:name", "user:2:name") // ["Alice", "Bob"] // Atomic increment/decrement client.set("counter", "10") client.incr("counter") // Returns 11 client.incrBy("counter", 5) // Returns 16 client.decr("counter") // Returns 15 client.incrByFloat("counter", 0.5) // Returns 15.5 // String manipulation client.set("message", "Hello") client.append("message", " World") // Returns 11 (new length) client.strlen("message") // Returns 11 client.getRange("message", 0, 4) // Returns "Hello" client.setRange("message", 6, "Redis") // Replaces "World" with "Redis" // Get and set atomically val oldValue = client.getSet("counter", "0") // Returns old value, sets new val deletedValue = client.getDel("temp:key") // Get and delete in one operation } ``` -------------------------------- ### Initialize ReThis Client Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates various ways to initialize the ReThis client for standalone, cluster, and sentinel deployments, including configuration options for authentication, pooling, and timeouts. ```kotlin import eu.vendeli.rethis.ReThis import eu.vendeli.rethis.types.common.Address import eu.vendeli.rethis.types.common.RespVer // Simple standalone connection (defaults to localhost:6379) val client = ReThis() // Standalone with host and port val client = ReThis(host = "redis.example.com", port = 6379) // Using Redis URL format val client = ReThis("redis://user:password@redis.example.com:6379/0") // Standalone with RESP3 protocol and configuration val client = ReThis.standalone( address = Address("redis.example.com", 6379), protocol = RespVer.V3 ) { auth(password = "secretpassword".toCharArray(), username = "default") db = 1 maxConnections = 1000 connectionAcquireTimeout = 10.seconds pool { minIdleConnections = 10 maxIdleConnections = 100 connectionHealthCheck = true } retry { times = 3 initialDelay = 100.milliseconds maxDelay = 1.seconds factor = 2.0 } socket { timeout = 5000L keepAlive = true noDelay = true } } // Cluster configuration val clusterClient = ReThis.cluster( initialNodes = listOf( Address("node1.example.com", 6379), Address("node2.example.com", 6379), Address("node3.example.com", 6379) ), protocol = RespVer.V2 ) { periodicRefresh = true periodicRefreshInterval = 30.seconds } // Sentinel configuration val sentinelClient = ReThis.sentinel( masterName = "mymaster", sentinelNodes = listOf( Address("sentinel1.example.com", 26379), Address("sentinel2.example.com", 26379) ) ) { periodicRefresh = true periodicRefreshInterval = 30.seconds } // Always close when done client.close() ``` ```kotlin client.close() ``` -------------------------------- ### Perform Generic Key Operations in Kotlin Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates various generic key commands like set, exists, delete, expiration, TTL checks, type inspection, renaming, copying, scanning, and object introspection. Ensure ReThis client is initialized. ```kotlin import eu.vendeli.rethis.command.generic.* import eu.vendeli.rethis.shared.request.common.UpdateStrategyOption import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.hours import kotlinx.datetime.Instant suspend fun genericKeyOperations(client: ReThis) { // Check key existence client.set("mykey", "myvalue") val exists = client.exists("mykey") // Returns 1 val multiExists = client.exists("key1", "key2", "key3") // Count of existing keys // Delete keys client.del("mykey") // Returns 1 (keys deleted) client.del("key1", "key2", "key3") // Delete multiple client.unlink("largekey") // Async delete (non-blocking) // Key expiration client.set("session:123", "data") client.expire("session:123", 3600.seconds) // Expire in 1 hour client.pExpire("session:123", 3600000.milliseconds) // Millisecond precision // Expire at specific timestamp val futureTime = Instant.fromEpochSeconds(System.currentTimeMillis() / 1000 + 3600) client.expireAt("session:123", futureTime) // Conditional expiration (Redis 7.0+) client.expire("session:123", 7200.seconds, UpdateStrategyOption.XX) // Only if key has expiry client.expire("session:123", 1800.seconds, UpdateStrategyOption.GT) // Only if new TTL > current // Check TTL val ttl = client.ttl("session:123") // Returns seconds, -1 if no expiry, -2 if not exists val pttl = client.pTtl("session:123") // Millisecond precision val expireTime = client.expireTime("session:123") // Unix timestamp when key expires // Remove expiration client.persist("session:123") // Remove TTL, key persists forever // Get key type val keyType = client.type("mykey") // Returns "string", "list", "set", "zset", "hash", "stream" // Rename keys client.rename("oldkey", "newkey") client.renameNx("key1", "key2") // Rename only if newkey doesn't exist // Copy keys client.copy("source", "destination") client.copy("source", "destination", replace = true) // Overwrite if exists // Scan keys (iterate without blocking) var cursor = "0" do { val result = client.scan(cursor, match = "user:*", count = 100) cursor = result.cursor result.keys.forEach { key -> println("Found key: $key") } } while (cursor != "0") // Find keys by pattern (use carefully in production) val userKeys = client.keys("user:*") // Returns all matching keys // Random key val randomKey = client.randomKey() // Object introspection val encoding = client.objectEncoding("mykey") // Internal encoding val refCount = client.objectRefCount("mykey") val idleTime = client.objectIdleTime("mykey") // Seconds since last access // Dump and restore (serialization) client.set("data", "important") val serialized = client.dump("data") // Serialized representation client.restore("data:backup", 0, serialized!!) // Touch keys (update last access time) client.touch("key1", "key2", "key3") // Returns count of existing keys // Sort list/set contents val sorted = client.sort("mylist") val sortedDesc = client.sortRo("mylist") // Read-only sort client.sortStore("mylist", "sorted:mylist") // Store sorted result } ``` -------------------------------- ### Manually Deploy Dokka Documentation Source: https://github.com/vendelieu/re.this/blob/master/RELEASE.md If the Dokka documentation deployment fails but other release steps succeed, you can manually deploy the documentation locally. This command generates the Dokka site and prepares it for deployment to gh-pages. ```bash libVersion=x.y.z ./gradlew :docs:dokkaGenerate ``` -------------------------------- ### Execute Pub/Sub Operations in Re.This Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates subscription methods, message publishing, subscription metadata queries, and global handler registration using coroutines. ```kotlin import eu.vendeli.rethis.command.pubsub.* import eu.vendeli.rethis.types.common.SubscribeTarget import eu.vendeli.rethis.types.common.PubSubKind import eu.vendeli.rethis.types.interfaces.PubSubHandler import eu.vendeli.rethis.shared.types.RType suspend fun pubSubOperations(client: ReThis) { // Simple subscription with lambda handler client.subscribe("notifications") { channel, message: String -> println("Received on $channel: $message") } // Subscribe to multiple channels client.subscribe("channel1", "channel2", "channel3") { channel, message: String -> println("[$channel] $message") } // Pattern subscription (glob-style patterns) client.pSubscribe("events:*") { channel, message: String -> println("Event from $channel: $message") } // Shard channel subscription (cluster mode) client.sSubscribe("shard:channel") { channel, message: String -> println("Shard message: $message") } // Full PubSubHandler interface for complete control client.subscribe( "events", callback = object : PubSubHandler { override suspend fun onSubscribe(kind: PubSubKind, target: SubscribeTarget, subscribedChannels: Long) { println("Subscribed to $target, total: $subscribedChannels") } override suspend fun onUnsubscribe(kind: PubSubKind, target: SubscribeTarget, subscribedChannels: Long) { println("Unsubscribed from $target") } override suspend fun onMessage(kind: PubSubKind, channel: String, message: RType, pattern: String?) { println("Message on $channel (pattern: $pattern): $message") } override suspend fun onException(target: SubscribeTarget, ex: Exception) { println("Error on $target: ${ex.message}") } } ) // Publish messages val receivers = client.publish("notifications", "Hello subscribers!") // Returns number of receivers client.sPublish("shard:channel", "Shard message") // Publish to shard channel // Query subscription info val channels = client.pubSubChannels() // List active channels val channelsMatching = client.pubSubChannels("events:*") // Channels matching pattern val numSubscribers = client.pubSubNumSub("notifications") // Subscriber count per channel val numPatterns = client.pubSubNumPat() // Number of pattern subscriptions // Global handler for all subscriptions client.subscriptions.registerGlobalHandler(object : PubSubHandler { override suspend fun onSubscribe(kind: PubSubKind, target: SubscribeTarget, subscribedChannels: Long) {} override suspend fun onUnsubscribe(kind: PubSubKind, target: SubscribeTarget, subscribedChannels: Long) {} override suspend fun onMessage(kind: PubSubKind, channel: String, message: RType, pattern: String?) { // Log all messages globally } override suspend fun onException(target: SubscribeTarget, ex: Exception) { // Handle all subscription errors } }) // Unsubscribe client.unsubscribe("notifications") client.pUnsubscribe("events:*") client.subscriptions.unsubscribe(SubscribeTarget.Channel("channel1")) client.subscriptions.unsubscribeAll() // Unsubscribe from everything } ``` -------------------------------- ### Execute Pipeline Operations in Kotlin Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates batching multiple commands into a single network round-trip. Supports nested pipelines and integration within transactions. ```kotlin import eu.vendeli.rethis.command.string.* import eu.vendeli.rethis.command.hash.* import eu.vendeli.rethis.shared.types.PlainString import eu.vendeli.rethis.shared.types.BulkString suspend fun pipelineOperations(client: ReThis) { // Basic pipeline val results = client.pipeline { set("key1", "value1") set("key2", "value2") get("key1") get("key2") } // Results: [PlainString("OK"), PlainString("OK"), BulkString("value1"), BulkString("value2")] println(results[0]) // PlainString("OK") println(results[2]) // BulkString("value1") // Pipeline with mixed commands val mixedResults = client.pipeline { set("user:name", "Alice") hSet("user:profile", FieldValue("age", "30"), FieldValue("city", "NYC")) get("user:name") hGetAll("user:profile") incr("user:visits") } // Nested pipeline (flattens into single pipeline) val nestedResults = client.pipeline { set("a", "1") get("a") pipeline { set("b", "2") get("b") } } // Results contain all 4 responses // Pipeline within transaction val txWithPipeline = client.transaction { client.pipeline { set("tx:key1", "value1") get("tx:key1") } } // Process results with type casting val processedResults = client.pipeline { set("counter", "0") incr("counter") incr("counter") get("counter") } processedResults.forEachIndexed { index, result -> when (result) { is PlainString -> println("$index: OK response") is BulkString -> println("$index: String = ${result.value}") is Int64 -> println("$index: Integer = ${result.value}") else -> println("$index: Other = $result") } } } ``` -------------------------------- ### Manage Redis Sets with re.this Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates common set operations including adding members, checking membership, performing set math, and incremental scanning. ```kotlin import eu.vendeli.rethis.command.set.* suspend fun setOperations(client: ReThis) { // Add members client.sAdd("tags:article:1", "kotlin", "redis", "database") // Returns 3 client.sAdd("tags:article:2", "kotlin", "coroutines", "async") // Check membership val isMember = client.sIsMember("tags:article:1", "kotlin") // Returns true val members = client.sMembers("tags:article:1") // Returns Set of all members // Get random members val random = client.sRandMember("tags:article:1") // One random member val randomThree = client.sRandMember("tags:article:1", count = 3) // Up to 3 random // Pop random members val popped = client.sPop("tags:article:1") // Remove and return random val poppedMany = client.sPop("tags:article:1", count = 2) // Set operations val union = client.sUnion("tags:article:1", "tags:article:2") // Returns: {"kotlin", "redis", "database", "coroutines", "async"} val intersection = client.sInter("tags:article:1", "tags:article:2") // Returns: {"kotlin"} val difference = client.sDiff("tags:article:1", "tags:article:2") // Returns: {"redis", "database"} // Store results of set operations client.sUnionStore("tags:all", "tags:article:1", "tags:article:2") client.sInterStore("tags:common", "tags:article:1", "tags:article:2") // Move member between sets client.sMove("tags:article:1", "tags:article:2", "redis") // Remove members client.sRem("tags:article:1", "database") // Cardinality val size = client.sCard("tags:article:1") // Number of members // Scan set incrementally val scanResult = client.sScan("tags:article:1", cursor = 0, match = "k*") } ``` -------------------------------- ### Usage Pattern: Idiomatic Structured Locking Source: https://github.com/vendelieu/re.this/wiki/Distributed-Locks Illustrates the recommended way to use `withLock` for simplified and safe lock management. ```APIDOC ## Usage Pattern: Idiomatic Structured Locking ### Description This pattern highlights the use of the `withLock` extension function, which provides a concise and safe way to manage the entire lifecycle of a lock acquisition and release within a structured concurrency scope. ### Method `withLock` extension function. ### Endpoint N/A (Client-side function) ### Parameters - **lock** (`ReDistributedLock`) - The lock instance on which to call `withLock`. - **leaseTime** (Duration) - Optional - The duration for which the lock will be held. If not specified, defaults are used. - **block** (suspend () -> T) - The code block to execute while the lock is held. ### Request Example ```kotlin lock.withLock(leaseTime = 20.seconds) { // Critical section code // All cleanup (unlock) is handled automatically and safely println("Executing critical section...") } ``` ``` -------------------------------- ### Execute SET Command (Kotlin) Source: https://github.com/vendelieu/re.this/blob/master/api-processor/README.md Executes a SET command on a Redis instance, handling slot calculation if configured. Returns the result as a String or null. ```kotlin public suspend fun ReThis.set( key: String, value: String, vararg options: SetOption, ): String? { val request = if (cfg.withSlots) { SetCommandCodec.encodeWithSlot(charset = cfg.charset, key = key, value = value, options = options) } else { SetCommandCodec.encode(charset = cfg.charset, key = key, value = value, options = options) } return SetCommandCodec.decode(topology.handle(request), cfg.charset) } ``` -------------------------------- ### KSP Argument Configuration Source: https://github.com/vendelieu/re.this/blob/master/api-processor/README.md Configures KSP arguments, specifically setting the client project directory. This is typically used during the build process. ```kotlin ksp { arg( "clientProjectDir", rootDir.resolve("client/src/commonMain/kotlin").absolutePath ) } ``` -------------------------------- ### Static ProcessorContext Singleton Source: https://github.com/vendelieu/re.this/blob/master/api-processor/README.md Defines a static singleton instance of ProcessorContext, which manages context elements. Elements can be global or per-command, with some implementing onFinish for post-processing actions. ```kotlin internal companion object { val context = ProcessorContext() } ``` -------------------------------- ### Usage Pattern: Timed Best-Effort Acquisition Source: https://github.com/vendelieu/re.this/wiki/Distributed-Locks Demonstrates how to attempt acquiring a lock with a specified wait time and handle cases where the lock cannot be acquired. ```APIDOC ## Usage Pattern: Timed Best-Effort Acquisition ### Description This pattern shows how to use `tryLock` to acquire a lock within a given time frame and execute a critical section if successful, ensuring the lock is released afterwards. ### Method `tryLock` followed by manual `unlock` in a `finally` block. ### Endpoint N/A (Client-side function) ### Parameters - **lock** (`ReDistributedLock`) - The lock instance to acquire. - **waitTime** (Duration) - The maximum duration to wait for the lock. - **leaseTime** (Duration) - The duration for which the lock will be held. ### Request Example ```kotlin val acquired = lock.tryLock( waitTime = 250.milliseconds, leaseTime = 10.seconds ) if (acquired) { try { // Perform critical work here } finally { lock.unlock() // Ensure unlock is always called } } else { // Handle the case where the lock could not be acquired println("Could not acquire lock.") } ``` ``` -------------------------------- ### Redis SET Command Specification with Options Source: https://github.com/vendelieu/re.this/blob/master/client/src/commonMain/kotlin/eu/vendeli/rethis/api/spec/commands/README.md Defines a SET command that supports optional parameters like expiration or conditional execution using `@RIgnoreSpecAbsence`. The response can be a Bulk string, Simple string, or Null. ```kotlin @RedisCommand( "SET", RedisOperation.WRITE, [RespCode.BULK, RespCode.SIMPLE_STRING, RespCode.NULL], ) fun interface SetCommand : RedisCommandSpec { suspend fun encode( key: String, value: String, @RIgnoreSpecAbsence vararg options: SetOption, ): CommandRequest } ``` -------------------------------- ### Implement Distributed Lock Operations in Kotlin Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates creating, acquiring, and releasing distributed locks using both automatic withLock blocks and manual lock/unlock cycles. ```kotlin import eu.vendeli.rethis.annotations.ReThisExperimental import eu.vendeli.rethis.types.interfaces.ReDistributedLock import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.milliseconds @OptIn(ReThisExperimental::class) suspend fun distributedLockOperations(client: ReThis) { // Create a distributed lock val lock = client.reDistributedLock( key = "locks:resource:123", waitTime = 50.milliseconds, // Backoff between retry attempts leaseTime = 30.seconds // TTL (auto-refreshed by watchdog) ) // Recommended: Use withLock for automatic release val result = lock.withLock(leaseTime = 10.seconds) { // Critical section - only one process can execute this at a time val data = fetchSharedResource() processData(data) saveResults(data) "completed" } // Lock is automatically released even if exception occurs // Manual lock/unlock lock.lock(leaseTime = 10.seconds) try { // Critical section performCriticalOperation() } finally { lock.unlock() // Always unlock in finally block } // Try to acquire with timeout (non-blocking if lock is taken) val acquired = lock.tryLock( waitTime = 500.milliseconds, // Max time to wait for lock leaseTime = 10.seconds ) if (acquired) { try { // Acquired the lock doCriticalWork() } finally { lock.unlock() } } else { // Lock was not acquired within timeout handleLockUnavailable() } // Reentrancy - same owner can acquire multiple times lock.withLock { // Outer lock lock.withLock { // Inner lock (same owner) - reentrant // Depth is tracked, requires matching unlocks } } // Lock across multiple services suspend fun processOrder(orderId: String) { val orderLock = client.reDistributedLock("locks:order:$orderId") orderLock.withLock(leaseTime = 60.seconds) { // Ensure only one service processes this order validateOrder(orderId) chargePayment(orderId) updateInventory(orderId) sendConfirmation(orderId) } } } // Helper functions for example suspend fun fetchSharedResource(): Any = TODO() suspend fun processData(data: Any) = TODO() suspend fun saveResults(data: Any) = TODO() suspend fun performCriticalOperation() = TODO() suspend fun doCriticalWork() = TODO() suspend fun handleLockUnavailable() = TODO() suspend fun validateOrder(id: String) = TODO() suspend fun chargePayment(id: String) = TODO() suspend fun updateInventory(id: String) = TODO() suspend fun sendConfirmation(id: String) = TODO() ``` -------------------------------- ### Load and Execute Lua Script by SHA Source: https://context7.com/vendelieu/re.this/llms.txt Loads a Lua script into Redis cache and executes it using its SHA1 hash for efficiency. Supports read-only execution. ```kotlin // Load script and execute by SHA val sha = client.scriptLoad(script)!! // Returns SHA1 hash val cachedResult = client.evalSha(sha, "counter:visits", arg = listOf("1")) // Read-only script execution val roScript = "return redis.call('GET', KEYS[1])" client.evalRo(roScript, "mykey", arg = emptyList()) client.evalShaRo(client.scriptLoad(roScript)!!, "mykey", arg = emptyList()) ``` -------------------------------- ### Idiomatic structured locking Source: https://github.com/vendelieu/re.this/wiki/Distributed-Locks Concise usage of the withLock helper for standard locking scenarios. ```kotlin lock.withLock(leaseTime = 20.seconds) { // all cleanup is automatic } ``` -------------------------------- ### Manage Redis List Data Structures Source: https://context7.com/vendelieu/re.this/llms.txt Covers push, pop, range queries, and atomic list movement operations for ordered collections. ```kotlin import eu.vendeli.rethis.command.list.* suspend fun listOperations(client: ReThis) { // Push elements to list client.lPush("queue:tasks", "task1", "task2", "task3") // Returns 3 (list length) client.rPush("queue:tasks", "task4", "task5") // Push to right/end // Pop elements val task = client.lPop("queue:tasks") // Returns "task3" (LIFO from left) val lastTask = client.rPop("queue:tasks") // Returns "task5" (from right) // Pop with count val tasks = client.lPop("queue:tasks", count = 2) // Returns list of up to 2 elements // Blocking pop (waits for elements) val item = client.blPop(timeout = 5.0, "queue:tasks", "queue:urgent") // Returns pair of (queue_name, value) or null on timeout // Get list range val allItems = client.lRange("queue:tasks", 0, -1) // All elements val firstThree = client.lRange("queue:tasks", 0, 2) // First 3 elements // Get specific index val first = client.lIndex("queue:tasks", 0) val last = client.lIndex("queue:tasks", -1) // List metadata val length = client.lLen("queue:tasks") // Modify list client.lSet("queue:tasks", 0, "updated_task") // Set element at index client.lInsert("queue:tasks", "BEFORE", "task2", "new_task") // Insert before pivot client.lTrim("queue:tasks", 0, 99) // Keep only first 100 elements // Remove elements client.lRem("queue:tasks", 0, "duplicate") // Remove all occurrences // Move between lists atomically val moved = client.lMove("source:list", "dest:list", "LEFT", "RIGHT") val blockedMove = client.blMove("source:list", "dest:list", "LEFT", "RIGHT", timeout = 5.0) } ``` -------------------------------- ### Load and Call Redis Functions Source: https://context7.com/vendelieu/re.this/llms.txt Loads a Lua function library into Redis and invokes registered functions. Supports read-only function calls. ```kotlin // Redis Functions (Redis 7.0+) val functionCode = """ #!lua name=mylib local function my_hset(keys, args) local hash = keys[1] local field = args[1] local value = args[2] return redis.call('HSET', hash, field, value) end redis.register_function('my_hset', my_hset) """.trimIndent() // Load function library val libName = client.functionLoad(functionCode) // Returns "mylib" // Call function val funcResult = client.fcall("my_hset", "myhash", arg = listOf("field1", "value1")) // Read-only function call val roFuncCode = """ #!lua name=rolib local function my_get(keys, args) return redis.call('GET', keys[1]) end redis.register_function{ function_name='my_get', callback=my_get, flags={'no-writes'} } """.trimIndent() client.functionLoad(roFuncCode) client.fcallRo("my_get", "mykey", arg = emptyList()) ``` -------------------------------- ### Manage Lua Scripts Source: https://context7.com/vendelieu/re.this/llms.txt Manages the script cache by checking script existence, flushing the cache asynchronously or synchronously, and loading scripts. ```kotlin // Check if scripts exist val exists = client.scriptExists(sha) // Returns [true] or [false] // Script management client.scriptFlush() // Clear script cache client.scriptFlush(FlushType.ASYNC) // Async flush ``` -------------------------------- ### Manage Redis Hash Data Structures Source: https://context7.com/vendelieu/re.this/llms.txt Demonstrates operations for setting, retrieving, and modifying fields within a Redis hash, including numeric increments and field-level expiration. ```kotlin import eu.vendeli.rethis.command.hash.* import eu.vendeli.rethis.shared.request.common.FieldValue import kotlin.time.Duration.Companion.seconds suspend fun hashOperations(client: ReThis) { // Set single and multiple fields client.hSet("user:100", FieldValue("name", "John Doe")) // Returns 1 (fields added) client.hSet( "user:100", FieldValue("email", "john@example.com"), FieldValue("age", "30"), FieldValue("city", "New York") ) // Set only if field doesn't exist client.hSetNx("user:100", "name", "Jane") // Returns 0 (field exists) // Get fields val name = client.hGet("user:100", "name") // Returns "John Doe" val fields = client.hMGet("user:100", "name", "email", "age") // Returns list val allFields = client.hGetAll("user:100") // Returns Map // Check existence and get metadata val exists = client.hExists("user:100", "email") // Returns true val fieldCount = client.hLen("user:100") // Returns number of fields val keys = client.hKeys("user:100") // Returns list of field names val values = client.hVals("user:100") // Returns list of values // Numeric operations on hash fields client.hSet("product:1", FieldValue("stock", "100")) client.hIncrBy("product:1", "stock", -5) // Returns 95 client.hIncrByFloat("product:1", "price", 0.99) // Returns new float value // Delete fields client.hDel("user:100", "city") // Returns 1 (fields deleted) // Field expiration (Redis 7.4+) client.hExpire("user:100", 3600.seconds, "session_token") client.hTtl("user:100", "session_token") // Returns TTL in seconds client.hPersist("user:100", "name") // Remove expiration // Scan hash fields incrementally val scanResult = client.hScan("user:100", cursor = 0, match = "*name*", count = 10) // Returns ScanResult(cursor = "0", keys = [("name", "John Doe")]) } ```