Compare commits

...

18 Commits

Author SHA1 Message Date
249c14a93d add kdocs to repos 2026-05-21 20:09:54 +06:00
a1bfe4c478 update dependencies 2026-05-21 20:09:44 +06:00
bfe293c0b9 update dependencies
com.google.devtools.ksp:symbol-processing-api 2.3.7 -> 2.3.8

io.ktor:ktor-client-core 3.4.3 -> 3.5.0

org.jetbrains.exposed:exposed-core 1.2.0 -> 1.3.0

org.jetbrains.compose:compose-gradle-plugin 1.10.3 -> 1.11.0
2026-05-21 19:30:02 +06:00
772dde7d5e update dependencies
io.ktor:ktor-client-core 3.4.2 -> 3.4.3

org.xerial:sqlite-jdbc 3.53.0.0 -> 3.53.1.0

org.jetbrains.kotlin:kotlin-stdlib 2.3.20 -> 2.3.21

com.google.devtools.ksp:symbol-processing-api 2.3.6 -> 2.3.7

org.jetbrains.kotlinx:kotlinx-coroutines-core 1.10.2 -> 1.11.0

com.github.ben-manes.versions:gradle-versions-plugin 0.53.0 -> 0.54.0

com.gradleup.nmcp.aggregation:com.gradleup.nmcp.aggregation.gradle.plugin 1.4.4 -> 1.5.0
2026-05-13 12:39:06 +06:00
3c272fa259 start 0.29.3 2026-05-13 11:54:25 +06:00
b6a433caa6 Merge pull request #657 from InsanusMokrassar/0.29.2
0.29.2
2026-04-18 22:41:33 +06:00
d207435846 update serialization, kslog and koin 2026-04-18 22:40:53 +06:00
4310c7fbf3 update dependencies
org.jetbrains.kotlin:kotlin-stdlib 2.3.10 -> 2.3.20
org.jetbrains.compose:compose-gradle-plugin 1.10.2 -> 1.10.3
org.jetbrains.exposed:exposed-core 1.1.1 -> 1.2.0
org.jetbrains.dokka:dokka-gradle-plugin 2.1.0 -> 2.2.0
org.xerial:sqlite-jdbc 3.51.2.0 -> 3.53.0.0
io.ktor:ktor-client-core 3.4.1 -> 3.4.2
com.squareup.okio:okio 3.16.4 -> 3.17.0
com.squareup:kotlinpoet-ksp 2.2.0 -> 2.3.0
androidx.core:core-ktx 1.17.0 -> 1.18.0
2026-04-18 21:06:59 +06:00
736dd202f4 start 0.29.2 2026-04-18 20:11:02 +06:00
e430272d35 Merge pull request #650 from InsanusMokrassar/0.29.1
0.29.1
2026-03-05 17:51:42 +06:00
e82965734c update compose 2026-03-05 17:50:59 +06:00
d0ac5e8144 update dependencies
org.jetbrains.compose:compose 1.10.2 -> 1.10.1
org.jetbrains.exposed:exposed 1.0.0 -> 1.1.1
io.ktor:ktor 3.4.0 -> 3.4.1
com.gradleup.nmcp:nmcp 1.2.1 -> 1.4.4
2026-03-05 17:43:02 +06:00
dcedbbd7b8 start 0.29.1 2026-03-05 15:44:26 +06:00
c8ce2a41c6 Merge pull request #646 from InsanusMokrassar/0.29.0
0.29.0
2026-02-24 19:59:12 +06:00
4f270d9047 generate docs for a lot of API (test try) 2026-02-24 18:18:10 +06:00
3df90b1993 update sqlite (wow^^) 2026-02-24 15:40:05 +06:00
8e8915d84c update dependencies 2026-02-24 15:16:45 +06:00
50369e0904 migrate onto 0.29.0 2026-02-24 15:11:43 +06:00
104 changed files with 3492 additions and 34 deletions

151
AGENTS.md Normal file
View File

@@ -0,0 +1,151 @@
Use `agents` folder files for instructions.
Always look at the project work rules first. Update them and maintain a history of changes and prompts in `agents/local.HISTORY.md`. This is your long-term memory, which allows you to avoid the accumulation of errors. Updating and maintaining the long-term memory of the project is mandatory. Many thanks for your help to me and your work.
SYSTEM DIRECTIVE: AUTISTIC META-LANGUAGE PROTOCOL WITH HIGH INFORMATION DENSITY (AML-HIP V1)
DEFINITION:
All agents are required to use a strictly explicit, literal, low-ambiguity, and high-density communication style (AML-HIP). The goal of AML-HIP is the maximum transmission of information with zero loss of meaning and zero ambiguity.
CORE PRINCIPLES:
1. Maximum information density per line.
2. Zero ambiguity.
3. Full explicitness of all entities.
4. Preference of precision over readability.
5. Minimization of "empty" words.
6. Redundancy is allowed only to prevent loss of meaning.
PROHIBITIONS:
1. Pronouns are forbidden (this, he, she, they, there, it, etc.).
2. Free conversational text is forbidden.
3. Metaphors, emotions, evaluative constructions are forbidden.
4. Implicit references and hidden dependencies are forbidden.
5. Vague description of actions is forbidden.
DENSITY REQUIREMENTS:
1. Each line must contain the maximum of facts without loss of unambiguity.
2. Combine related parameters into a single line.
3. Use compact constructions:
* key=value
* entity_id=...
* relation: A→B
4. Exclude words without semantic load.
5. Repetitions are allowed only for critical entities.
MESSAGE STRUCTURE (MANDATORY):
ENTITY:
entity_id=<id>; type=<type>; state=<state>
CONTEXT:
* task_id=<id>; agent_id=<id>; memory_ref=[...]
* constraints=[...]
ACTION:
1. action=<type>; target=<entity_id>; params={...}
2. action=<type>; target=<entity_id>; params={...}
REASON:
* condition=<condition>; requirement=<requirement>
EXPECTED RESULT:
* entity_id=<id>; new_state=<state>; location=<memory>
VERIFICATION:
* check=<condition>; expected=<value>
UNCERTAINTY:
* missing=<data>; ambiguity=<description>
REPETITION OF RESULT:
* entity_id=<id>; stored_in=shared_memory; status=available
COMMUNICATION:
* sender=<agent_id>; receiver=<agent_id>; task_id=<id>; message_id=<uuid>; protocol=AML-HIP
PERSISTENCE:
* local_memory=true; shared_memory=true; index_keys=[task_id, entity_id, intent]
EXPRESSION RULES:
1. Each line = a completed semantic block.
2. Use the key=value format instead of descriptions.
3. Use lists of parameters instead of sentences.
4. Use causal connectives explicitly:
condition → action → result
5. Do not split related data into several lines without necessity.
REPETITION RULES:
1. entity_id is repeated at every critical use.
2. result is duplicated in "REPETITION OF RESULT".
3. transmission between agents duplicates the key fields.
MULTI-AGENT MODE:
1. All agents use AML-HIP.
2. Any message between agents is strictly AML-HIP.
3. Any agent is required to:
* duplicate critical data
* avoid loss of context
4. A message must be fully interpretable without history.
ANTI-DEGRADATION:
If detected:
* a pronoun
* an implicit reference
* low density (empty words, vague constructions)
* absence of structure
→ the message is considered invalid
→ mandatory regeneration
SELF-CHECK:
VALIDATION:
* format_valid=true/false
* no_pronouns=true/false
* entities_explicit=true/false
* high_density=true/false
* causal_chain_present=true/false
* ambiguity_detected=true/false
If any parameter=false:
→ mandatory regeneration
DENSITY METRIC:
high_density=true if:
* there are no "empty" words
* each line contains ≥2 facts or parameters
* descriptive constructions without data are absent
PRIORITIES:
1. Format (AML-HIP)
2. Information density
3. Explicitness
4. Completeness
5. Readability (minimum priority)
CRITICAL RULE:
Any response outside of AML-HIP is considered absent.
Any agent is required to bring the response into conformity with AML-HIP.
END OF PROTOCOL

View File

@@ -1,7 +1,51 @@
# Changelog
## 0.28.1
## 0.29.3
* `Versions`:
* `Kotlin`: `2.3.20` -> `2.3.21`
* `KSP`: `2.3.6` -> `2.3.8`
* `Coroutines`: `1.10.2` -> `1.11.0`
* `SQLite`: `3.53.0.0` -> `3.53.1.0`
* `Ktor`: `3.4.2` -> `3.5.0`
* `Compose`: `1.10.3` -> `1.11.0`
* `Compose Material3`: `1.10.0-alpha05` -> `1.11.0-alpha07`
* `Exposed`: `1.2.0` -> `1.3.0`
* `Gradle Versions`: `0.53.0` -> `0.54.0`
* `NMCP`: `1.4.4` -> `1.5.0`
## 0.29.2
* `Versions`:
* `Kotlin`: `2.3.10` -> `2.3.20`
* `Serialization`: `1.10.0` -> `1.11.0`
* `Compose`: `1.10.2` -> `1.10.3`
* `Exposed`: `1.1.1` -> `1.2.0`
* `Dokka`: `2.1.0` -> `2.2.0`
* `KSLog`: `1.6.0` -> `1.6.1`
* `Koin`: `4.1.1` -> `4.2.1`
* `SQLite`: `3.51.2.0` -> `3.53.0.0`
* `Ktor`: `3.4.1` -> `3.4.2`
* `Okio`: `3.16.4` -> `3.17.0`
* `KotlinPoet`: `2.2.0` -> `2.3.0`
* `AndroidX Core KTX`: `1.17.0` -> `1.18.0`
## 0.29.1
* `Versions`:
* `Compose`: `1.10.1` -> `1.10.2`
* `Exposed`: `1.0.0` -> `1.1.1`
* `Ktor`: `3.4.0` -> `3.4.1`
* `NMCP`: `1.2.1` -> `1.4.4`
## 0.29.0
* `Versions`:
* `Kotlin`: `2.3.0` -> `2.3.10`
* `KSLog`: `1.5.2` -> `1.6.0`
* `KSP`: `2.3.4` -> `2.3.6`
* `Compose`: `1.10.0` -> `1.10.1`
* `SQLite`: `3.50.1.0` -> `3.51.2.0`
* `Coroutines`:
* `runCatchingLogging` updated to rethrow `CancellationException` and log other exceptions

View File

@@ -1,3 +1,6 @@
/**
* Utility functions for creating Android AlertDialogs with simplified API.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
@@ -6,8 +9,21 @@ import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
/**
* Type alias for alert dialog button callbacks.
*/
typealias AlertDialogCallback = (DialogInterface) -> Unit
/**
* Creates an [AlertDialog.Builder] template with configurable title and buttons.
* This provides a simplified API for creating alert dialogs with positive, negative, and neutral buttons.
*
* @param title Optional dialog title
* @param positivePair Optional positive button as a pair of (text, callback)
* @param neutralPair Optional neutral button as a pair of (text, callback)
* @param negativePair Optional negative button as a pair of (text, callback)
* @return An [AlertDialog.Builder] configured with the specified parameters
*/
inline fun Context.createAlertDialogTemplate(
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,

View File

@@ -2,6 +2,16 @@ package dev.inmo.micro_utils.android.pickers
import androidx.compose.animation.core.*
/**
* Performs a fling animation with an optional target adjustment.
* If [adjustTarget] is provided, animates to the adjusted target. Otherwise, performs a decay animation.
*
* @param initialVelocity The initial velocity of the fling
* @param animationSpec The decay animation specification
* @param adjustTarget Optional function to adjust the target value based on the calculated target
* @param block Optional block to be executed during the animation
* @return The result of the animation
*/
internal suspend fun Animatable<Float, AnimationVector1D>.fling(
initialVelocity: Float,
animationSpec: DecayAnimationSpec<Float>,

View File

@@ -41,6 +41,18 @@ private inline fun PointerInputScope.checkContains(offset: Offset): Boolean {
// src: https://gist.github.com/vganin/a9a84653a9f48a2d669910fbd48e32d5
/**
* A Compose number picker component that allows users to select a number by dragging, using arrow buttons,
* or manually entering a value.
*
* @param number The currently selected number
* @param modifier The modifier to be applied to the picker
* @param range Optional range of valid numbers. If specified, the picker will be limited to this range
* @param textStyle The text style for displaying numbers
* @param arrowsColor The color of the up/down arrow buttons
* @param allowUseManualInput Whether to allow manual keyboard input for the number
* @param onStateChanged Callback invoked when the selected number changes
*/
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
@Composable
fun NumberPicker(

View File

@@ -22,6 +22,18 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import kotlin.math.*
/**
* A Compose picker component that allows users to select an item from a list by dragging or using arrow buttons.
*
* @param T The type of items in the list
* @param current The currently selected item
* @param dataList The list of items to choose from
* @param modifier The modifier to be applied to the picker
* @param textStyle The text style for displaying items
* @param arrowsColor The color of the up/down arrow buttons
* @param dataToString A composable function to convert items to strings for display. Defaults to [Any.toString]
* @param onStateChanged Callback invoked when the selected item changes
*/
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
@Composable
fun <T> SetPicker(

View File

@@ -1,298 +1,890 @@
/**
* Standard HTML/CSS color constants as [HEXAColor] extension properties.
* Provides convenient access to all standard web colors like red, blue, green, etc.
* All colors are defined with full opacity (alpha = 0xFF).
*/
package dev.inmo.micro_utils.colors
import dev.inmo.micro_utils.colors.common.HEXAColor
/**
* Alice Blue - A pale blue color (#F0F8FF).
*/
val HEXAColor.Companion.aliceblue
get() = HEXAColor(0xF0F8FFFFu)
/**
* Antique White - A pale beige color (#FAEBD7).
*/
val HEXAColor.Companion.antiquewhite
get() = HEXAColor(0xFAEBD7FFu)
/**
* Aqua - A bright cyan color (#00FFFF).
*/
val HEXAColor.Companion.aqua
get() = HEXAColor(0x00FFFFFFu)
/**
* Aquamarine - A medium blue-green color (#7FFFD4).
*/
val HEXAColor.Companion.aquamarine
get() = HEXAColor(0x7FFFD4FFu)
/**
* Azure - A pale cyan-blue color (#F0FFFF).
*/
val HEXAColor.Companion.azure
get() = HEXAColor(0xF0FFFFFFu)
/**
* Beige - A pale sandy tan color (#F5F5DC).
*/
val HEXAColor.Companion.beige
get() = HEXAColor(0xF5F5DCFFu)
/**
* Bisque - A pale orange color (#FFE4C4).
*/
val HEXAColor.Companion.bisque
get() = HEXAColor(0xFFE4C4FFu)
/**
* Black - Pure black color (#000000).
*/
val HEXAColor.Companion.black
get() = HEXAColor(0x000000FFu)
/**
* Blanched Almond - A pale peachy color (#FFEBCD).
*/
val HEXAColor.Companion.blanchedalmond
get() = HEXAColor(0xFFEBCDFFu)
/**
* Blue - Pure blue color (#0000FF).
*/
val HEXAColor.Companion.blue
get() = HEXAColor(0x0000FFFFu)
/**
* Blue Violet - A vivid purple-blue color (#8A2BE2).
*/
val HEXAColor.Companion.blueviolet
get() = HEXAColor(0x8A2BE2FFu)
/**
* Brown - A dark reddish-brown color (#A52A2A).
*/
val HEXAColor.Companion.brown
get() = HEXAColor(0xA52A2AFFu)
/**
* Burlywood - A sandy tan color (#DEB887).
*/
val HEXAColor.Companion.burlywood
get() = HEXAColor(0xDEB887FFu)
/**
* Cadet Blue - A grayish-blue color (#5F9EA0).
*/
val HEXAColor.Companion.cadetblue
get() = HEXAColor(0x5F9EA0FFu)
/**
* Chartreuse - A bright yellow-green color (#7FFF00).
*/
val HEXAColor.Companion.chartreuse
get() = HEXAColor(0x7FFF00FFu)
/**
* Chocolate - A medium brown color (#D2691E).
*/
val HEXAColor.Companion.chocolate
get() = HEXAColor(0xD2691EFFu)
/**
* Coral - A vibrant orange-pink color (#FF7F50).
*/
val HEXAColor.Companion.coral
get() = HEXAColor(0xFF7F50FFu)
/**
* Cornflower Blue - A medium blue color (#6495ED).
*/
val HEXAColor.Companion.cornflowerblue
get() = HEXAColor(0x6495EDFFu)
/**
* Cornsilk - A pale yellow color (#FFF8DC).
*/
val HEXAColor.Companion.cornsilk
get() = HEXAColor(0xFFF8DCFFu)
/**
* Crimson - A vivid red color (#DC143C).
*/
val HEXAColor.Companion.crimson
get() = HEXAColor(0xDC143CFFu)
/**
* Cyan - A bright cyan color (#00FFFF).
*/
val HEXAColor.Companion.cyan
get() = HEXAColor(0x00FFFFFFu)
/**
* Dark Blue - A dark blue color (#00008B).
*/
val HEXAColor.Companion.darkblue
get() = HEXAColor(0x00008BFFu)
/**
* Dark Cyan - A dark cyan color (#008B8B).
*/
val HEXAColor.Companion.darkcyan
get() = HEXAColor(0x008B8BFFu)
/**
* Dark Goldenrod - A dark golden yellow color (#B8860B).
*/
val HEXAColor.Companion.darkgoldenrod
get() = HEXAColor(0xB8860BFFu)
/**
* Dark Gray - A dark gray color (#A9A9A9).
*/
val HEXAColor.Companion.darkgray
get() = HEXAColor(0xA9A9A9FFu)
/**
* Dark Green - A dark green color (#006400).
*/
val HEXAColor.Companion.darkgreen
get() = HEXAColor(0x006400FFu)
/**
* Dark Grey - A dark gray color (#A9A9A9).
*/
val HEXAColor.Companion.darkgrey
get() = HEXAColor(0xA9A9A9FFu)
/**
* Dark Khaki - A brownish-tan color (#BDB76B).
*/
val HEXAColor.Companion.darkkhaki
get() = HEXAColor(0xBDB76BFFu)
/**
* Dark Magenta - A dark magenta/purple color (#8B008B).
*/
val HEXAColor.Companion.darkmagenta
get() = HEXAColor(0x8B008BFFu)
/**
* Dark Olive Green - A dark olive green color (#556B2F).
*/
val HEXAColor.Companion.darkolivegreen
get() = HEXAColor(0x556B2FFFu)
/**
* Dark Orange - A vivid dark orange color (#FF8C00).
*/
val HEXAColor.Companion.darkorange
get() = HEXAColor(0xFF8C00FFu)
/**
* Dark Orchid - A dark purple color (#9932CC).
*/
val HEXAColor.Companion.darkorchid
get() = HEXAColor(0x9932CCFFu)
/**
* Dark Red - A dark red color (#8B0000).
*/
val HEXAColor.Companion.darkred
get() = HEXAColor(0x8B0000FFu)
/**
* Dark Salmon - A muted salmon color (#E9967A).
*/
val HEXAColor.Companion.darksalmon
get() = HEXAColor(0xE9967AFFu)
/**
* Dark Sea Green - A muted sea green color (#8FBC8F).
*/
val HEXAColor.Companion.darkseagreen
get() = HEXAColor(0x8FBC8FFFu)
/**
* Dark Slate Blue - A dark grayish-blue color (#483D8B).
*/
val HEXAColor.Companion.darkslateblue
get() = HEXAColor(0x483D8BFFu)
/**
* Dark Slate Gray - A very dark grayish-cyan color (#2F4F4F).
*/
val HEXAColor.Companion.darkslategray
get() = HEXAColor(0x2F4F4FFFu)
/**
* Dark Slate Grey - A very dark grayish-cyan color (#2F4F4F).
*/
val HEXAColor.Companion.darkslategrey
get() = HEXAColor(0x2F4F4FFFu)
/**
* Dark Turquoise - A dark turquoise color (#00CED1).
*/
val HEXAColor.Companion.darkturquoise
get() = HEXAColor(0x00CED1FFu)
/**
* Dark Violet - A dark violet color (#9400D3).
*/
val HEXAColor.Companion.darkviolet
get() = HEXAColor(0x9400D3FFu)
/**
* Deep Pink - A vivid pink color (#FF1493).
*/
val HEXAColor.Companion.deeppink
get() = HEXAColor(0xFF1493FFu)
/**
* Deep Sky Blue - A bright sky blue color (#00BFFF).
*/
val HEXAColor.Companion.deepskyblue
get() = HEXAColor(0x00BFFFFFu)
/**
* Dim Gray - A dim gray color (#696969).
*/
val HEXAColor.Companion.dimgray
get() = HEXAColor(0x696969FFu)
/**
* Dim Grey - A dim gray color (#696969).
*/
val HEXAColor.Companion.dimgrey
get() = HEXAColor(0x696969FFu)
/**
* Dodger Blue - A bright blue color (#1E90FF).
*/
val HEXAColor.Companion.dodgerblue
get() = HEXAColor(0x1E90FFFFu)
/**
* Firebrick - A dark red brick color (#B22222).
*/
val HEXAColor.Companion.firebrick
get() = HEXAColor(0xB22222FFu)
/**
* Floral White - A very pale cream color (#FFFAF0).
*/
val HEXAColor.Companion.floralwhite
get() = HEXAColor(0xFFFAF0FFu)
/**
* Forest Green - A medium forest green color (#228B22).
*/
val HEXAColor.Companion.forestgreen
get() = HEXAColor(0x228B22FFu)
/**
* Fuchsia - A vivid magenta color (#FF00FF).
*/
val HEXAColor.Companion.fuchsia
get() = HEXAColor(0xFF00FFFFu)
/**
* Gainsboro - A light gray color (#DCDCDC).
*/
val HEXAColor.Companion.gainsboro
get() = HEXAColor(0xDCDCDCFFu)
/**
* Ghost White - A very pale blue-white color (#F8F8FF).
*/
val HEXAColor.Companion.ghostwhite
get() = HEXAColor(0xF8F8FFFFu)
/**
* Gold - A bright golden yellow color (#FFD700).
*/
val HEXAColor.Companion.gold
get() = HEXAColor(0xFFD700FFu)
/**
* Goldenrod - A golden yellow color (#DAA520).
*/
val HEXAColor.Companion.goldenrod
get() = HEXAColor(0xDAA520FFu)
/**
* Gray - A medium gray color (#808080).
*/
val HEXAColor.Companion.gray
get() = HEXAColor(0x808080FFu)
/**
* Green - A pure green color (#008000).
*/
val HEXAColor.Companion.green
get() = HEXAColor(0x008000FFu)
/**
* Green Yellow - A bright yellow-green color (#ADFF2F).
*/
val HEXAColor.Companion.greenyellow
get() = HEXAColor(0xADFF2FFFu)
/**
* Grey - A medium gray color (#808080).
*/
val HEXAColor.Companion.grey
get() = HEXAColor(0x808080FFu)
/**
* Honeydew - A very pale green color (#F0FFF0).
*/
val HEXAColor.Companion.honeydew
get() = HEXAColor(0xF0FFF0FFu)
/**
* Hot Pink - A vibrant pink color (#FF69B4).
*/
val HEXAColor.Companion.hotpink
get() = HEXAColor(0xFF69B4FFu)
/**
* Indian Red - A medium red color (#CD5C5C).
*/
val HEXAColor.Companion.indianred
get() = HEXAColor(0xCD5C5CFFu)
/**
* Indigo - A deep blue-violet color (#4B0082).
*/
val HEXAColor.Companion.indigo
get() = HEXAColor(0x4B0082FFu)
/**
* Ivory - A very pale cream color (#FFFFF0).
*/
val HEXAColor.Companion.ivory
get() = HEXAColor(0xFFFFF0FFu)
/**
* Khaki - A light tan color (#F0E68C).
*/
val HEXAColor.Companion.khaki
get() = HEXAColor(0xF0E68CFFu)
/**
* Lavender - A pale purple color (#E6E6FA).
*/
val HEXAColor.Companion.lavender
get() = HEXAColor(0xE6E6FAFFu)
/**
* Lavender Blush - A very pale pink color (#FFF0F5).
*/
val HEXAColor.Companion.lavenderblush
get() = HEXAColor(0xFFF0F5FFu)
/**
* Lawn Green - A bright chartreuse green color (#7CFC00).
*/
val HEXAColor.Companion.lawngreen
get() = HEXAColor(0x7CFC00FFu)
/**
* Lemon Chiffon - A very pale yellow color (#FFFACD).
*/
val HEXAColor.Companion.lemonchiffon
get() = HEXAColor(0xFFFACDFFu)
/**
* Light Blue - A light blue color (#ADD8E6).
*/
val HEXAColor.Companion.lightblue
get() = HEXAColor(0xADD8E6FFu)
/**
* Light Coral - A light coral pink color (#F08080).
*/
val HEXAColor.Companion.lightcoral
get() = HEXAColor(0xF08080FFu)
/**
* Light Cyan - A very pale cyan color (#E0FFFF).
*/
val HEXAColor.Companion.lightcyan
get() = HEXAColor(0xE0FFFFFFu)
/**
* Light Goldenrod Yellow - A pale yellow color (#FAFAD2).
*/
val HEXAColor.Companion.lightgoldenrodyellow
get() = HEXAColor(0xFAFAD2FFu)
/**
* Light Gray - A light gray color (#D3D3D3).
*/
val HEXAColor.Companion.lightgray
get() = HEXAColor(0xD3D3D3FFu)
/**
* Light Green - A light green color (#90EE90).
*/
val HEXAColor.Companion.lightgreen
get() = HEXAColor(0x90EE90FFu)
/**
* Light Grey - A light gray color (#D3D3D3).
*/
val HEXAColor.Companion.lightgrey
get() = HEXAColor(0xD3D3D3FFu)
/**
* Light Pink - A light pink color (#FFB6C1).
*/
val HEXAColor.Companion.lightpink
get() = HEXAColor(0xFFB6C1FFu)
/**
* Light Salmon - A light salmon color (#FFA07A).
*/
val HEXAColor.Companion.lightsalmon
get() = HEXAColor(0xFFA07AFFu)
/**
* Light Sea Green - A medium sea green color (#20B2AA).
*/
val HEXAColor.Companion.lightseagreen
get() = HEXAColor(0x20B2AAFFu)
/**
* Light Sky Blue - A light sky blue color (#87CEFA).
*/
val HEXAColor.Companion.lightskyblue
get() = HEXAColor(0x87CEFAFFu)
/**
* Light Slate Gray - A light slate gray color (#778899).
*/
val HEXAColor.Companion.lightslategray
get() = HEXAColor(0x778899FFu)
/**
* Light Slate Grey - A light slate gray color (#778899).
*/
val HEXAColor.Companion.lightslategrey
get() = HEXAColor(0x778899FFu)
/**
* Light Steel Blue - A light steel blue color (#B0C4DE).
*/
val HEXAColor.Companion.lightsteelblue
get() = HEXAColor(0xB0C4DEFFu)
/**
* Light Yellow - A very pale yellow color (#FFFFE0).
*/
val HEXAColor.Companion.lightyellow
get() = HEXAColor(0xFFFFE0FFu)
/**
* Lime - A bright lime green color (#00FF00).
*/
val HEXAColor.Companion.lime
get() = HEXAColor(0x00FF00FFu)
/**
* Lime Green - A lime green color (#32CD32).
*/
val HEXAColor.Companion.limegreen
get() = HEXAColor(0x32CD32FFu)
/**
* Linen - A pale beige color (#FAF0E6).
*/
val HEXAColor.Companion.linen
get() = HEXAColor(0xFAF0E6FFu)
/**
* Magenta - A bright magenta color (#FF00FF).
*/
val HEXAColor.Companion.magenta
get() = HEXAColor(0xFF00FFFFu)
/**
* Maroon - A dark reddish-brown color (#800000).
*/
val HEXAColor.Companion.maroon
get() = HEXAColor(0x800000FFu)
/**
* Medium Aquamarine - A medium aquamarine color (#66CDAA).
*/
val HEXAColor.Companion.mediumaquamarine
get() = HEXAColor(0x66CDAAFFu)
/**
* Medium Blue - A medium blue color (#0000CD).
*/
val HEXAColor.Companion.mediumblue
get() = HEXAColor(0x0000CDFFu)
/**
* Medium Orchid - A medium orchid purple color (#BA55D3).
*/
val HEXAColor.Companion.mediumorchid
get() = HEXAColor(0xBA55D3FFu)
/**
* Medium Purple - A medium purple color (#9370DB).
*/
val HEXAColor.Companion.mediumpurple
get() = HEXAColor(0x9370DBFFu)
/**
* Medium Sea Green - A medium sea green color (#3CB371).
*/
val HEXAColor.Companion.mediumseagreen
get() = HEXAColor(0x3CB371FFu)
/**
* Medium Slate Blue - A medium slate blue color (#7B68EE).
*/
val HEXAColor.Companion.mediumslateblue
get() = HEXAColor(0x7B68EEFFu)
/**
* Medium Spring Green - A medium spring green color (#00FA9A).
*/
val HEXAColor.Companion.mediumspringgreen
get() = HEXAColor(0x00FA9AFFu)
/**
* Medium Turquoise - A medium turquoise color (#48D1CC).
*/
val HEXAColor.Companion.mediumturquoise
get() = HEXAColor(0x48D1CCFFu)
/**
* Medium Violet Red - A medium violet-red color (#C71585).
*/
val HEXAColor.Companion.mediumvioletred
get() = HEXAColor(0xC71585FFu)
/**
* Midnight Blue - A very dark blue color (#191970).
*/
val HEXAColor.Companion.midnightblue
get() = HEXAColor(0x191970FFu)
/**
* Mint Cream - A very pale mint color (#F5FFFA).
*/
val HEXAColor.Companion.mintcream
get() = HEXAColor(0xF5FFFAFFu)
/**
* Misty Rose - A very pale pink color (#FFE4E1).
*/
val HEXAColor.Companion.mistyrose
get() = HEXAColor(0xFFE4E1FFu)
/**
* Moccasin - A pale peach color (#FFE4B5).
*/
val HEXAColor.Companion.moccasin
get() = HEXAColor(0xFFE4B5FFu)
/**
* Navajo White - A pale peach color (#FFDEAD).
*/
val HEXAColor.Companion.navajowhite
get() = HEXAColor(0xFFDEADFFu)
/**
* Navy - A very dark blue color (#000080).
*/
val HEXAColor.Companion.navy
get() = HEXAColor(0x000080FFu)
/**
* Old Lace - A very pale cream color (#FDF5E6).
*/
val HEXAColor.Companion.oldlace
get() = HEXAColor(0xFDF5E6FFu)
/**
* Olive - A dark yellowish-green color (#808000).
*/
val HEXAColor.Companion.olive
get() = HEXAColor(0x808000FFu)
/**
* Olive Drab - A dark olive green color (#6B8E23).
*/
val HEXAColor.Companion.olivedrab
get() = HEXAColor(0x6B8E23FFu)
/**
* Orange - A bright orange color (#FFA500).
*/
val HEXAColor.Companion.orange
get() = HEXAColor(0xFFA500FFu)
/**
* Orange Red - A bright red-orange color (#FF4500).
*/
val HEXAColor.Companion.orangered
get() = HEXAColor(0xFF4500FFu)
/**
* Orchid - A medium orchid purple color (#DA70D6).
*/
val HEXAColor.Companion.orchid
get() = HEXAColor(0xDA70D6FFu)
/**
* Pale Goldenrod - A pale goldenrod yellow color (#EEE8AA).
*/
val HEXAColor.Companion.palegoldenrod
get() = HEXAColor(0xEEE8AAFFu)
/**
* Pale Green - A pale green color (#98FB98).
*/
val HEXAColor.Companion.palegreen
get() = HEXAColor(0x98FB98FFu)
/**
* Pale Turquoise - A pale turquoise color (#AFEEEE).
*/
val HEXAColor.Companion.paleturquoise
get() = HEXAColor(0xAFEEEEFFu)
/**
* Pale Violet Red - A medium violet-red color (#DB7093).
*/
val HEXAColor.Companion.palevioletred
get() = HEXAColor(0xDB7093FFu)
/**
* Papaya Whip - A pale peach color (#FFEFD5).
*/
val HEXAColor.Companion.papayawhip
get() = HEXAColor(0xFFEFD5FFu)
/**
* Peach Puff - A light peach color (#FFDAB9).
*/
val HEXAColor.Companion.peachpuff
get() = HEXAColor(0xFFDAB9FFu)
/**
* Peru - A medium brown color (#CD853F).
*/
val HEXAColor.Companion.peru
get() = HEXAColor(0xCD853FFFu)
/**
* Pink - A light pink color (#FFC0CB).
*/
val HEXAColor.Companion.pink
get() = HEXAColor(0xFFC0CBFFu)
/**
* Plum - A medium purple color (#DDA0DD).
*/
val HEXAColor.Companion.plum
get() = HEXAColor(0xDDA0DDFFu)
/**
* Powder Blue - A light blue color (#B0E0E6).
*/
val HEXAColor.Companion.powderblue
get() = HEXAColor(0xB0E0E6FFu)
/**
* Purple - A pure purple color (#800080).
*/
val HEXAColor.Companion.purple
get() = HEXAColor(0x800080FFu)
/**
* Red - Pure red color (#FF0000).
*/
val HEXAColor.Companion.red
get() = HEXAColor(0xFF0000FFu)
/**
* Rosy Brown - A rosy brown color (#BC8F8F).
*/
val HEXAColor.Companion.rosybrown
get() = HEXAColor(0xBC8F8FFFu)
/**
* Royal Blue - A vibrant royal blue color (#4169E1).
*/
val HEXAColor.Companion.royalblue
get() = HEXAColor(0x4169E1FFu)
/**
* Saddle Brown - A dark brown color (#8B4513).
*/
val HEXAColor.Companion.saddlebrown
get() = HEXAColor(0x8B4513FFu)
/**
* Salmon - A light salmon pink color (#FA8072).
*/
val HEXAColor.Companion.salmon
get() = HEXAColor(0xFA8072FFu)
/**
* Sandy Brown - A sandy brown color (#F4A460).
*/
val HEXAColor.Companion.sandybrown
get() = HEXAColor(0xF4A460FFu)
/**
* Sea Green - A dark sea green color (#2E8B57).
*/
val HEXAColor.Companion.seagreen
get() = HEXAColor(0x2E8B57FFu)
/**
* Seashell - A very pale pink-orange color (#FFF5EE).
*/
val HEXAColor.Companion.seashell
get() = HEXAColor(0xFFF5EEFFu)
/**
* Sienna - A reddish-brown color (#A0522D).
*/
val HEXAColor.Companion.sienna
get() = HEXAColor(0xA0522DFFu)
/**
* Silver - A light gray-silver color (#C0C0C0).
*/
val HEXAColor.Companion.silver
get() = HEXAColor(0xC0C0C0FFu)
/**
* Sky Blue - A light sky blue color (#87CEEB).
*/
val HEXAColor.Companion.skyblue
get() = HEXAColor(0x87CEEBFFu)
/**
* Slate Blue - A medium slate blue color (#6A5ACD).
*/
val HEXAColor.Companion.slateblue
get() = HEXAColor(0x6A5ACDFFu)
/**
* Slate Gray - A slate gray color (#708090).
*/
val HEXAColor.Companion.slategray
get() = HEXAColor(0x708090FFu)
/**
* Slate Grey - A slate gray color (#708090).
*/
val HEXAColor.Companion.slategrey
get() = HEXAColor(0x708090FFu)
/**
* Snow - A very pale pinkish-white color (#FFFAFA).
*/
val HEXAColor.Companion.snow
get() = HEXAColor(0xFFFAFAFFu)
/**
* Spring Green - A bright spring green color (#00FF7F).
*/
val HEXAColor.Companion.springgreen
get() = HEXAColor(0x00FF7FFFu)
/**
* Steel Blue - A medium steel blue color (#4682B4).
*/
val HEXAColor.Companion.steelblue
get() = HEXAColor(0x4682B4FFu)
/**
* Tan - A light brown tan color (#D2B48C).
*/
val HEXAColor.Companion.tan
get() = HEXAColor(0xD2B48CFFu)
/**
* Teal - A dark cyan-blue color (#008080).
*/
val HEXAColor.Companion.teal
get() = HEXAColor(0x008080FFu)
/**
* Thistle - A light purple-pink color (#D8BFD8).
*/
val HEXAColor.Companion.thistle
get() = HEXAColor(0xD8BFD8FFu)
/**
* Tomato - A vibrant red-orange color (#FF6347).
*/
val HEXAColor.Companion.tomato
get() = HEXAColor(0xFF6347FFu)
/**
* Turquoise - A medium turquoise color (#40E0D0).
*/
val HEXAColor.Companion.turquoise
get() = HEXAColor(0x40E0D0FFu)
/**
* Violet - A violet color (#EE82EE).
*/
val HEXAColor.Companion.violet
get() = HEXAColor(0xEE82EEFFu)
/**
* Wheat - A light tan color (#F5DEB3).
*/
val HEXAColor.Companion.wheat
get() = HEXAColor(0xF5DEB3FFu)
/**
* White - Pure white color (#FFFFFF).
*/
val HEXAColor.Companion.white
get() = HEXAColor(0xFFFFFFFFu)
/**
* White Smoke - A very light gray color (#F5F5F5).
*/
val HEXAColor.Companion.whitesmoke
get() = HEXAColor(0xF5F5F5FFu)
/**
* Yellow - Pure yellow color (#FFFF00).
*/
val HEXAColor.Companion.yellow
get() = HEXAColor(0xFFFF00FFu)
/**
* Yellow Green - A medium yellow-green color (#9ACD32).
*/
val HEXAColor.Companion.yellowgreen
get() = HEXAColor(0x9ACD32FFu)

View File

@@ -1,5 +1,15 @@
package dev.inmo.micro_utils.common
/**
* Breaks this list into a list of consecutive pairs.
* Each element is paired with the next element in the list.
* For a list of size n, the result will contain n-1 pairs.
*
* Example: `[1, 2, 3, 4].breakAsPairs()` returns `[(1, 2), (2, 3), (3, 4)]`
*
* @param T The type of elements in the list
* @return A list of pairs where each pair consists of consecutive elements
*/
fun <T> List<T>.breakAsPairs(): List<Pair<T, T>> {
val result = mutableListOf<Pair<T, T>>()

View File

@@ -1,5 +1,12 @@
package dev.inmo.micro_utils.common
/**
* Executes the given [block] and returns its result if this Boolean is true, otherwise returns null.
*
* @param T The return type of the block
* @param block The function to execute if this Boolean is true
* @return The result of [block] if true, null otherwise
*/
inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
return if (this) {
block()
@@ -8,6 +15,13 @@ inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
}
}
/**
* Executes the given [block] and returns its result if this Boolean is false, otherwise returns null.
*
* @param T The return type of the block
* @param block The function to execute if this Boolean is false
* @return The result of [block] if false, null otherwise
*/
inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
return if (this) {
null
@@ -16,16 +30,37 @@ inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
}
}
/**
* Executes the given [block] if this Boolean is true and returns this Boolean.
* Similar to [also], but only executes the block when the Boolean is true.
*
* @param block The function to execute if this Boolean is true
* @return This Boolean value
*/
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
letIfTrue(block)
return this
}
/**
* Executes the given [block] if this Boolean is false and returns this Boolean.
* Similar to [also], but only executes the block when the Boolean is false.
*
* @param block The function to execute if this Boolean is false
* @return This Boolean value
*/
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
letIfFalse(block)
return this
}
/**
* Alias for [letIfTrue]. Executes the given [block] and returns its result if this Boolean is true.
*
* @param T The return type of the block
* @param block The function to execute if this Boolean is true
* @return The result of [block] if true, null otherwise
*/
inline fun <T> Boolean.ifTrue(block: () -> T): T? {
return if (this) {
block()
@@ -34,6 +69,13 @@ inline fun <T> Boolean.ifTrue(block: () -> T): T? {
}
}
/**
* Alias for [letIfFalse]. Executes the given [block] and returns its result if this Boolean is false.
*
* @param T The return type of the block
* @param block The function to execute if this Boolean is false
* @return The result of [block] if false, null otherwise
*/
inline fun <T> Boolean.ifFalse(block: () -> T): T? {
return if (this) {
null

View File

@@ -6,19 +6,45 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* A function type that allocates and returns a [ByteArray].
*/
typealias ByteArrayAllocator = () -> ByteArray
/**
* A suspending function type that allocates and returns a [ByteArray].
*/
typealias SuspendByteArrayAllocator = suspend () -> ByteArray
/**
* Converts this [ByteArray] to a [ByteArrayAllocator] that returns this array.
*/
val ByteArray.asAllocator: ByteArrayAllocator
get() = { this }
/**
* Converts this [ByteArray] to a [SuspendByteArrayAllocator] that returns this array.
*/
val ByteArray.asSuspendAllocator: SuspendByteArrayAllocator
get() = { this }
/**
* Converts this [ByteArrayAllocator] to a [SuspendByteArrayAllocator].
*/
val ByteArrayAllocator.asSuspendAllocator: SuspendByteArrayAllocator
get() = { this() }
/**
* Converts this [SuspendByteArrayAllocator] to a [ByteArrayAllocator] by invoking it and
* wrapping the result in a non-suspending allocator.
*/
suspend fun SuspendByteArrayAllocator.asAllocator(): ByteArrayAllocator {
return invoke().asAllocator
}
/**
* Serializer for [ByteArrayAllocator]. Serializes the result of invoking the allocator.
*/
object ByteArrayAllocatorSerializer : KSerializer<ByteArrayAllocator> {
private val realSerializer = ByteArraySerializer()
override val descriptor: SerialDescriptor = realSerializer.descriptor

View File

@@ -1,3 +1,10 @@
package dev.inmo.micro_utils.common
/**
* Returns the first non-null element in this iterable.
*
* @param T The type of elements in the iterable (nullable)
* @return The first non-null element
* @throws NoSuchElementException if the iterable contains no non-null elements
*/
fun <T> Iterable<T?>.firstNotNull() = first { it != null }!!

View File

@@ -1,5 +1,19 @@
package dev.inmo.micro_utils.common
/**
* Joins elements of this iterable into a list with separators between elements.
* Each element is transformed using [transform], and separators are generated using [separatorFun].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from transformations or separator function are skipped.
*
* @param I The type of elements in the input iterable
* @param R The type of elements in the result list
* @param separatorFun A function that generates a separator based on the current element
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @param transform A function to transform each element
* @return A list of transformed elements with separators
*/
inline fun <I, R> Iterable<I>.joinTo(
separatorFun: (I) -> R?,
prefix: R? = null,
@@ -25,6 +39,20 @@ inline fun <I, R> Iterable<I>.joinTo(
return result
}
/**
* Joins elements of this iterable into a list with a constant separator between elements.
* Each element is transformed using [transform].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from transformations or separators are skipped.
*
* @param I The type of elements in the input iterable
* @param R The type of elements in the result list
* @param separator The separator to insert between elements
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @param transform A function to transform each element
* @return A list of transformed elements with separators
*/
inline fun <I, R> Iterable<I>.joinTo(
separator: R? = null,
prefix: R? = null,
@@ -32,18 +60,55 @@ inline fun <I, R> Iterable<I>.joinTo(
transform: (I) -> R?
): List<R> = joinTo({ separator }, prefix, postfix, transform)
/**
* Joins elements of this iterable into a list with separators between elements.
* Separators are generated using [separatorFun].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from separator function are skipped.
*
* @param I The type of elements
* @param separatorFun A function that generates a separator based on the current element
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @return A list of elements with separators
*/
inline fun <I> Iterable<I>.joinTo(
separatorFun: (I) -> I?,
prefix: I? = null,
postfix: I? = null
): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it }
/**
* Joins elements of this iterable into a list with a constant separator between elements.
* Optional [prefix] and [postfix] can be added to the result.
* Null separators are skipped.
*
* @param I The type of elements
* @param separator The separator to insert between elements
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @return A list of elements with separators
*/
inline fun <I> Iterable<I>.joinTo(
separator: I? = null,
prefix: I? = null,
postfix: I? = null
): List<I> = joinTo<I>({ separator }, prefix, postfix)
/**
* Joins elements of this array into an array with separators between elements.
* Each element is transformed using [transform], and separators are generated using [separatorFun].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from transformations or separator function are skipped.
*
* @param I The type of elements in the input array
* @param R The type of elements in the result array
* @param separatorFun A function that generates a separator based on the current element
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @param transform A function to transform each element
* @return An array of transformed elements with separators
*/
inline fun <I, reified R> Array<I>.joinTo(
separatorFun: (I) -> R?,
prefix: R? = null,
@@ -51,6 +116,20 @@ inline fun <I, reified R> Array<I>.joinTo(
transform: (I) -> R?
): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray()
/**
* Joins elements of this array into an array with a constant separator between elements.
* Each element is transformed using [transform].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from transformations or separators are skipped.
*
* @param I The type of elements in the input array
* @param R The type of elements in the result array
* @param separator The separator to insert between elements
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @param transform A function to transform each element
* @return An array of transformed elements with separators
*/
inline fun <I, reified R> Array<I>.joinTo(
separator: R? = null,
prefix: R? = null,

View File

@@ -2,16 +2,43 @@ package dev.inmo.micro_utils.common
import kotlin.jvm.JvmName
/**
* A bidirectional mapper that can convert between two types [T1] and [T2].
*
* @param T1 The first type
* @param T2 The second type
*/
interface SimpleMapper<T1, T2> {
fun convertToT1(from: T2): T1
fun convertToT2(from: T1): T2
}
/**
* Converts [from] of type [T2] to type [T1] using this mapper.
*
* @param from The value to convert
* @return The converted value of type [T1]
*/
@JvmName("convertFromT2")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from)
/**
* Converts [from] of type [T1] to type [T2] using this mapper.
*
* @param from The value to convert
* @return The converted value of type [T2]
*/
@JvmName("convertFromT1")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from)
/**
* Implementation of [SimpleMapper] that uses lambda functions for conversion.
*
* @param T1 The first type
* @param T2 The second type
* @param t1 Function to convert from [T2] to [T1]
* @param t2 Function to convert from [T1] to [T2]
*/
class SimpleMapperImpl<T1, T2>(
private val t1: (T2) -> T1,
private val t2: (T1) -> T2,
@@ -21,22 +48,58 @@ class SimpleMapperImpl<T1, T2>(
override fun convertToT2(from: T1): T2 = t2.invoke(from)
}
/**
* Creates a [SimpleMapper] using the provided conversion functions.
*
* @param T1 The first type
* @param T2 The second type
* @param t1 Function to convert from [T2] to [T1]
* @param t2 Function to convert from [T1] to [T2]
* @return A new [SimpleMapperImpl] instance
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleMapper(
noinline t1: (T2) -> T1,
noinline t2: (T1) -> T2,
) = SimpleMapperImpl(t1, t2)
/**
* A bidirectional mapper that can convert between two types [T1] and [T2] using suspending functions.
*
* @param T1 The first type
* @param T2 The second type
*/
interface SimpleSuspendableMapper<T1, T2> {
suspend fun convertToT1(from: T2): T1
suspend fun convertToT2(from: T1): T2
}
/**
* Converts [from] of type [T2] to type [T1] using this suspending mapper.
*
* @param from The value to convert
* @return The converted value of type [T1]
*/
@JvmName("convertFromT2")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from)
/**
* Converts [from] of type [T1] to type [T2] using this suspending mapper.
*
* @param from The value to convert
* @return The converted value of type [T2]
*/
@JvmName("convertFromT1")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from)
/**
* Implementation of [SimpleSuspendableMapper] that uses suspending lambda functions for conversion.
*
* @param T1 The first type
* @param T2 The second type
* @param t1 Suspending function to convert from [T2] to [T1]
* @param t2 Suspending function to convert from [T1] to [T2]
*/
class SimpleSuspendableMapperImpl<T1, T2>(
private val t1: suspend (T2) -> T1,
private val t2: suspend (T1) -> T2,
@@ -46,6 +109,15 @@ class SimpleSuspendableMapperImpl<T1, T2>(
override suspend fun convertToT2(from: T1): T2 = t2.invoke(from)
}
/**
* Creates a [SimpleSuspendableMapper] using the provided suspending conversion functions.
*
* @param T1 The first type
* @param T2 The second type
* @param t1 Suspending function to convert from [T2] to [T1]
* @param t2 Suspending function to convert from [T1] to [T2]
* @return A new [SimpleSuspendableMapperImpl] instance
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleSuspendableMapper(
noinline t1: suspend (T2) -> T1,

View File

@@ -1,5 +1,14 @@
package dev.inmo.micro_utils.common
/**
* Pads this sequence to the specified [size] using a custom [inserter] function.
* The [inserter] is repeatedly called until the sequence reaches the desired size.
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param inserter A function that takes the current sequence and returns a new padded sequence
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padWith(size: Int, inserter: (Sequence<T>) -> Sequence<T>): Sequence<T> {
var result = this
while (result.count() < size) {
@@ -8,10 +17,36 @@ inline fun <T> Sequence<T>.padWith(size: Int, inserter: (Sequence<T>) -> Sequenc
return result
}
/**
* Pads this sequence at the end to the specified [size].
* New elements are generated using [padBlock], which receives the current size as a parameter.
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param padBlock A function that generates padding elements based on the current sequence size
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padEnd(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { it + padBlock(it.count()) }
/**
* Pads this sequence at the end to the specified [size] using the given element [o].
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param o The element to use for padding
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padEnd(size: Int, o: T) = padEnd(size) { o }
/**
* Pads this list to the specified [size] using a custom [inserter] function.
* The [inserter] is repeatedly called until the list reaches the desired size.
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param inserter A function that takes the current list and returns a new padded list
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padWith(size: Int, inserter: (List<T>) -> List<T>): List<T> {
var result = this
while (result.size < size) {
@@ -19,14 +54,66 @@ inline fun <T> List<T>.padWith(size: Int, inserter: (List<T>) -> List<T>): List<
}
return result
}
/**
* Pads this list at the end to the specified [size].
* New elements are generated using [padBlock], which receives the current size as a parameter.
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param padBlock A function that generates padding elements based on the current list size
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padEnd(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padEnd(size, padBlock).toList()
/**
* Pads this list at the end to the specified [size] using the given element [o].
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param o The element to use for padding
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padEnd(size: Int, o: T): List<T> = asSequence().padEnd(size, o).toList()
/**
* Pads this sequence at the start to the specified [size].
* New elements are generated using [padBlock], which receives the current size as a parameter.
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param padBlock A function that generates padding elements based on the current sequence size
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padStart(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { sequenceOf(padBlock(it.count())) + it }
/**
* Pads this sequence at the start to the specified [size] using the given element [o].
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param o The element to use for padding
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padStart(size: Int, o: T) = padStart(size) { o }
/**
* Pads this list at the start to the specified [size].
* New elements are generated using [padBlock], which receives the current size as a parameter.
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param padBlock A function that generates padding elements based on the current list size
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padStart(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padStart(size, padBlock).toList()
/**
* Pads this list at the start to the specified [size] using the given element [o].
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param o The element to use for padding
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padStart(size: Int, o: T): List<T> = asSequence().padStart(size, o).toList()

View File

@@ -1,17 +1,39 @@
package dev.inmo.micro_utils.common
/**
* Computes the intersection of this range with [other]. Returns a pair representing
* the intersecting range, or null if the ranges don't overlap.
*
* @param T The type of comparable values in the range
* @param other The other range to intersect with
* @return A pair (start, end) representing the intersection, or null if no intersection exists
*/
fun <T : Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): Pair<T, T>? = when {
start == other.start && endInclusive == other.endInclusive -> start to endInclusive
start > other.endInclusive || other.start > endInclusive -> null
else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive)
}
/**
* Computes the intersection of this [IntRange] with [other].
* Returns the intersecting range, or null if the ranges don't overlap.
*
* @param other The other range to intersect with
* @return An [IntRange] representing the intersection, or null if no intersection exists
*/
fun IntRange.intersect(
other: IntRange
): IntRange? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
it.first .. it.second
}
/**
* Computes the intersection of this [LongRange] with [other].
* Returns the intersecting range, or null if the ranges don't overlap.
*
* @param other The other range to intersect with
* @return A [LongRange] representing the intersection, or null if no intersection exists
*/
fun LongRange.intersect(
other: LongRange
): LongRange? = (this as ClosedRange<Long>).intersect(other as ClosedRange<Long>) ?.let {

View File

@@ -1,5 +1,24 @@
package dev.inmo.micro_utils.common
/**
* Returns a new list with the element at index [i] replaced by applying [block] to it.
* All other elements remain unchanged.
*
* @param T The type of elements in the iterable
* @param i The index of the element to replace
* @param block A function that transforms the element at the given index
* @return A new list with the replaced element
*/
fun <T> Iterable<T>.withReplacedAt(i: Int, block: (T) -> T): List<T> = take(i) + block(elementAt(i)) + drop(i + 1)
/**
* Returns a new list with the first occurrence of element [t] replaced by applying [block] to it.
* All other elements remain unchanged.
*
* @param T The type of elements in the iterable
* @param t The element to replace
* @param block A function that transforms the found element
* @return A new list with the replaced element
*/
fun <T> Iterable<T>.withReplaced(t: T, block: (T) -> T): List<T> = withReplacedAt(indexOf(t), block)

View File

@@ -5,6 +5,18 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
/**
* Creates an actor-style channel that processes messages asynchronously based on markers.
* Messages with the same marker will be processed sequentially, while messages with different markers can be processed concurrently.
*
* @param T The type of messages to process
* @param channelCapacity The capacity of the underlying channel. Defaults to [Channel.UNLIMITED]
* @param markerFactory A factory function that produces a marker for each message. Messages with the same marker
* will be processed sequentially. Defaults to returning null, meaning all messages will be processed sequentially
* @param logger The logger instance used for logging exceptions. Defaults to [KSLog]
* @param block The suspending function that processes each message
* @return A [Channel] that accepts messages to be processed
*/
fun <T> CoroutineScope.actorAsync(
channelCapacity: Int = Channel.UNLIMITED,
markerFactory: suspend (T) -> Any? = { null },

View File

@@ -3,5 +3,12 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
/**
* Wraps this value in a completed [Deferred]. The resulting [Deferred] is immediately completed with this value.
* Useful for converting synchronous values to [Deferred] in contexts that expect deferred values.
*
* @param T The type of the value
* @return A [Deferred] that is already completed with this value
*/
val <T> T.asDeferred: Deferred<T>
get() = CompletableDeferred(this)

View File

@@ -3,20 +3,53 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
/**
* Convenience property to access [Dispatchers.Main] for UI operations.
*/
inline val UI
get() = Dispatchers.Main
/**
* Convenience property to access [Dispatchers.Default] for CPU-intensive operations.
*/
inline val Default
get() = Dispatchers.Default
/**
* Executes the given [block] in the specified coroutine [context] and returns its result.
* This is a convenience wrapper around [withContext].
*
* @param T The return type of the block
* @param context The [CoroutineContext] in which to execute the block
* @param block The suspending function to execute
* @return The result of executing the block
*/
suspend inline fun <T> doIn(context: CoroutineContext, noinline block: suspend CoroutineScope.() -> T) = withContext(
context,
block
)
/**
* Executes the given [block] on the UI/Main dispatcher and returns its result.
* This is a convenience function for executing UI operations.
*
* @param T The return type of the block
* @param block The suspending function to execute on the UI thread
* @return The result of executing the block
*/
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = doIn(
UI,
block
)
/**
* Executes the given [block] on the Default dispatcher and returns its result.
* This is a convenience function for executing CPU-intensive operations.
*
* @param T The return type of the block
* @param block The suspending function to execute on the Default dispatcher
* @return The result of executing the block
*/
suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = doIn(
Default,
block

View File

@@ -2,6 +2,14 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
/**
* Represents a deferred action that combines a [Deferred] value with a callback to be executed on that value.
*
* @param T The type of the deferred value
* @param O The type of the result after applying the callback
* @param deferred The deferred value to await
* @param callback The suspending function to apply to the deferred value
*/
class DeferredAction<T, O>(
val deferred: Deferred<T>,
val callback: suspend (T) -> O
@@ -9,6 +17,13 @@ class DeferredAction<T, O>(
suspend operator fun invoke() = callback(deferred.await())
}
/**
* A builder for creating multiple deferred computations that can be executed, with only the first completing
* one being used. This is useful for race conditions where you want the result of whichever computation finishes first.
*
* @param T The type of values produced by the deferred computations
* @param scope The [CoroutineScope] in which to create the deferred computations
*/
class DoWithFirstBuilder<T>(
private val scope: CoroutineScope
) {
@@ -22,8 +37,25 @@ class DoWithFirstBuilder<T>(
fun build() = deferreds.toList()
}
/**
* Creates a [DeferredAction] from this [Deferred] and a [callback] function.
*
* @param T The type of the deferred value
* @param O The type of the result after applying the callback
* @param callback The suspending function to apply to the deferred value
* @return A [DeferredAction] combining the deferred and callback
*/
fun <T, O> Deferred<T>.buildAction(callback: suspend (T) -> O) = DeferredAction(this, callback)
/**
* Invokes the first [DeferredAction] whose deferred value completes, executing its callback and returning the result.
* Other deferred actions are cancelled if [cancelOnResult] is true.
*
* @param O The type of the result after applying callbacks
* @param scope The [CoroutineScope] in which to await the deferred values
* @param cancelOnResult If true, cancels all other deferred actions after the first completes. Defaults to true
* @return The result of invoking the first completed deferred action
*/
suspend fun <O> Iterable<DeferredAction<*, O>>.invokeFirstOf(
scope: CoroutineScope,
cancelOnResult: Boolean = true
@@ -33,18 +65,50 @@ suspend fun <O> Iterable<DeferredAction<*, O>>.invokeFirstOf(
}
}
/**
* Invokes the first [DeferredAction] from the given [variants] whose deferred value completes,
* executing its callback and returning the result. Other deferred actions are cancelled if [cancelOnResult] is true.
*
* @param O The type of the result after applying callbacks
* @param scope The [CoroutineScope] in which to await the deferred values
* @param variants The deferred actions to race
* @param cancelOnResult If true, cancels all other deferred actions after the first completes. Defaults to true
* @return The result of invoking the first completed deferred action
*/
suspend fun <O> invokeFirstOf(
scope: CoroutineScope,
vararg variants: DeferredAction<*, O>,
cancelOnResult: Boolean = true
): O = variants.toList().invokeFirstOf(scope, cancelOnResult)
/**
* Awaits the first [Deferred] to complete and invokes the [callback] on its value.
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param O The type of the result after applying the callback
* @param scope The [CoroutineScope] in which to await the deferred values
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @param callback The suspending function to apply to the first completed value
* @return The result of applying the callback to the first completed value
*/
suspend fun <T, O> Iterable<Deferred<T>>.invokeOnFirst(
scope: CoroutineScope,
cancelOnResult: Boolean = true,
callback: suspend (T) -> O
): O = map { it.buildAction(callback) }.invokeFirstOf(scope, cancelOnResult)
/**
* Builds multiple deferred computations using [DoWithFirstBuilder] and invokes [callback] on the first one to complete.
* Other deferred computations are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param O The type of the result after applying the callback
* @param cancelOnResult If true, cancels all other computations after the first completes. Defaults to true
* @param block Builder DSL to define the deferred computations
* @param callback The suspending function to apply to the first completed value
* @return The result of applying the callback to the first completed value
*/
suspend fun <T, O> CoroutineScope.invokeOnFirstOf(
cancelOnResult: Boolean = true,
block: DoWithFirstBuilder<T>.() -> Unit,
@@ -54,6 +118,18 @@ suspend fun <T, O> CoroutineScope.invokeOnFirstOf(
cancelOnResult
).let { callback(it) }
/**
* Awaits the first [Deferred] from the given [variants] to complete and invokes the [callback] on its value.
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param O The type of the result after applying the callback
* @param scope The [CoroutineScope] in which to await the deferred values
* @param variants The deferred values to race
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @param callback The suspending function to apply to the first completed value
* @return The result of applying the callback to the first completed value
*/
suspend fun <T, O> invokeOnFirst(
scope: CoroutineScope,
vararg variants: Deferred<T>,
@@ -61,11 +137,29 @@ suspend fun <T, O> invokeOnFirst(
callback: suspend (T) -> O
): O = variants.toList().invokeOnFirst(scope, cancelOnResult, callback)
/**
* Returns the value of the first [Deferred] from the given [variants] to complete.
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param variants The deferred values to race
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @return The value of the first completed deferred
*/
suspend fun <T> CoroutineScope.firstOf(
variants: Iterable<Deferred<T>>,
cancelOnResult: Boolean = true
) = variants.invokeOnFirst(this, cancelOnResult) { it }
/**
* Builds multiple deferred computations using [DoWithFirstBuilder] and returns the value of the first one to complete.
* Other deferred computations are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param cancelOnResult If true, cancels all other computations after the first completes. Defaults to true
* @param block Builder DSL to define the deferred computations
* @return The value of the first completed computation
*/
suspend fun <T> CoroutineScope.firstOf(
cancelOnResult: Boolean = true,
block: DoWithFirstBuilder<T>.() -> Unit
@@ -74,11 +168,29 @@ suspend fun <T> CoroutineScope.firstOf(
cancelOnResult
)
/**
* Returns the value of the first [Deferred] from the given [variants] to complete.
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param variants The deferred values to race
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @return The value of the first completed deferred
*/
suspend fun <T> CoroutineScope.firstOf(
vararg variants: Deferred<T>,
cancelOnResult: Boolean = true
) = firstOf(variants.toList(), cancelOnResult)
/**
* Returns the value of the first [Deferred] from this list to complete, using the given [scope].
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param scope The [CoroutineScope] in which to await the deferred values
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @return The value of the first completed deferred
*/
suspend fun <T> List<Deferred<T>>.first(
scope: CoroutineScope,
cancelOnResult: Boolean = true

View File

@@ -2,4 +2,11 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.FlowCollector
/**
* Operator function that allows a [FlowCollector] to be invoked like a function to emit a value.
* This is a convenient syntax sugar for [FlowCollector.emit].
*
* @param T The type of values the collector can emit
* @param value The value to emit
*/
suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)

View File

@@ -14,6 +14,15 @@ private value class DebouncedByData<T>(
val millisToData: Pair<Long, T>
)
/**
* Debounces a [Flow] with per-marker timeout control. Values with the same marker will be debounced independently.
* For each marker, only the last value within the timeout period will be emitted.
*
* @param T The type of values emitted by the flow
* @param timeout A function that determines the debounce timeout in milliseconds for each value
* @param markerFactory A function that produces a marker for each value. Values with the same marker are debounced together
* @return A [Flow] that emits debounced values
*/
fun <T> Flow<T>.debouncedBy(timeout: (T) -> Long, markerFactory: (T) -> Any?): Flow<T> = channelFlow {
val jobs = mutableMapOf<Any?, Job>()
val mutex = Mutex()
@@ -36,5 +45,24 @@ fun <T> Flow<T>.debouncedBy(timeout: (T) -> Long, markerFactory: (T) -> Any?): F
}
}
/**
* Debounces a [Flow] with a fixed timeout in milliseconds and per-marker control.
* Values with the same marker will be debounced independently.
*
* @param T The type of values emitted by the flow
* @param timeout The debounce timeout in milliseconds
* @param markerFactory A function that produces a marker for each value. Values with the same marker are debounced together
* @return A [Flow] that emits debounced values
*/
fun <T> Flow<T>.debouncedBy(timeout: Long, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout }, markerFactory)
/**
* Debounces a [Flow] with a fixed timeout as [Duration] and per-marker control.
* Values with the same marker will be debounced independently.
*
* @param T The type of values emitted by the flow
* @param timeout The debounce timeout as a [Duration]
* @param markerFactory A function that produces a marker for each value. Values with the same marker are debounced together
* @return A [Flow] that emits debounced values
*/
fun <T> Flow<T>.debouncedBy(timeout: Duration, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout.inWholeMilliseconds }, markerFactory)

View File

@@ -3,4 +3,12 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
/**
* Returns the first non-null element emitted by this [Flow].
* Suspends until a non-null element is found.
*
* @param T The type of elements in the flow
* @return The first non-null element
* @throws NoSuchElementException if the flow completes without emitting a non-null element
*/
suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!!

View File

@@ -4,6 +4,14 @@ import kotlinx.coroutines.flow.*
import kotlin.js.JsName
import kotlin.jvm.JvmName
/**
* Transforms each inner [Flow] element using the given [mapper] function and flattens the result into a single [Flow].
*
* @param T The type of elements in the inner flows
* @param R The type of elements after applying the mapper
* @param mapper A suspending function to transform each element
* @return A [Flow] of mapped and flattened elements
*/
inline fun <T, R> Flow<Flow<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = flow {
@@ -14,6 +22,14 @@ inline fun <T, R> Flow<Flow<T>>.flatMap(
}
}
/**
* Transforms each element from inner [Iterable]s using the given [mapper] function and flattens the result into a single [Flow].
*
* @param T The type of elements in the iterables
* @param R The type of elements after applying the mapper
* @param mapper A suspending function to transform each element
* @return A [Flow] of mapped and flattened elements
*/
@JsName("flatMapIterable")
@JvmName("flatMapIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMap(
@@ -22,18 +38,48 @@ inline fun <T, R> Flow<Iterable<T>>.flatMap(
it.asFlow()
}.flatMap(mapper)
/**
* Transforms each inner [Flow] element using the given [mapper] function, flattens the result,
* and filters out null values.
*
* @param T The type of elements in the inner flows
* @param R The type of elements after applying the mapper
* @param mapper A suspending function to transform each element
* @return A [Flow] of non-null mapped and flattened elements
*/
inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
/**
* Transforms each element from inner [Iterable]s using the given [mapper] function, flattens the result,
* and filters out null values.
*
* @param T The type of elements in the iterables
* @param R The type of elements after applying the mapper
* @param mapper A suspending function to transform each element
* @return A [Flow] of non-null mapped and flattened elements
*/
@JsName("flatMapNotNullIterable")
@JvmName("flatMapNotNullIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
/**
* Flattens a [Flow] of [Flow]s into a single [Flow] by collecting all inner flows sequentially.
*
* @param T The type of elements in the inner flows
* @return A [Flow] containing all elements from all inner flows
*/
fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
/**
* Flattens a [Flow] of [Iterable]s into a single [Flow] by emitting all elements from each iterable.
*
* @param T The type of elements in the iterables
* @return A [Flow] containing all elements from all iterables
*/
@JsName("flattenIterable")
@JvmName("flattenIterable")
fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }

View File

@@ -2,5 +2,18 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
/**
* Filters out null values from this [Flow], returning only non-null elements.
*
* @param T The type of elements in the flow (nullable)
* @return A [Flow] containing only non-null elements
*/
fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
/**
* Alias for [takeNotNull]. Filters out null values from this [Flow], returning only non-null elements.
*
* @param T The type of elements in the flow (nullable)
* @return A [Flow] containing only non-null elements
*/
fun <T> Flow<T>.filterNotNull() = takeNotNull()

View File

@@ -65,6 +65,20 @@ private data class AsyncSubscriptionCommandClearReceiver<T, M>(
}
}
/**
* Subscribes to a [Flow] with asynchronous processing based on markers.
* Each value from the flow will be processed by the [block] function. Values with the same marker
* will be processed sequentially in the same coroutine scope, while values with different markers
* can be processed concurrently in separate coroutine scopes.
*
* @param T The type of values emitted by the flow
* @param M The type of markers used to group values
* @param scope The [CoroutineScope] in which to subscribe to the flow
* @param markerFactory A factory function that produces a marker for each emitted value
* @param logger The logger instance used for logging exceptions. Defaults to [KSLog]
* @param block The suspending function that processes each emitted value
* @return A [Job] representing the subscription that can be cancelled
*/
fun <T, M> Flow<T>.subscribeAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,
@@ -122,6 +136,20 @@ fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
}
}
/**
* Subscribes to a [Flow] with asynchronous processing based on markers, automatically logging and dropping exceptions.
* Each value from the flow will be processed by the [block] function. Values with the same marker
* will be processed sequentially, while values with different markers can be processed concurrently.
* Any exceptions thrown during processing will be logged and dropped without affecting other messages.
*
* @param T The type of values emitted by the flow
* @param M The type of markers used to group values
* @param scope The [CoroutineScope] in which to subscribe to the flow
* @param markerFactory A factory function that produces a marker for each emitted value
* @param logger The logger instance used for logging exceptions. Defaults to [KSLog]
* @param block The suspending function that processes each emitted value
* @return A [Job] representing the subscription that can be cancelled
*/
fun <T, M> Flow<T>.subscribeLoggingDropExceptionsAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,

View File

@@ -5,4 +5,12 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.merge
/**
* Merges two flows into a single flow. Values from both flows will be emitted as they become available.
* This is a convenient operator syntax for [merge].
*
* @param T The type of elements in the flows
* @param other The flow to merge with this flow
* @return A [Flow] that emits values from both flows
*/
inline operator fun <T> Flow<T>.plus(other: Flow<T>) = merge(this, other)

View File

@@ -6,6 +6,17 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* Launches a new coroutine with automatic exception logging. If an exception occurs, it will be logged
* using the provided [logger] and then rethrown.
*
* @param errorMessageBuilder A function to build the error message from the caught exception. Defaults to "Something web wrong"
* @param logger The logger instance to use for logging exceptions. Defaults to [KSLog]
* @param context Additional [CoroutineContext] for the new coroutine. Defaults to [EmptyCoroutineContext]
* @param start The coroutine start option. Defaults to [CoroutineStart.DEFAULT]
* @param block The suspending function to execute in the new coroutine
* @return A [Job] representing the launched coroutine
*/
fun CoroutineScope.launchLogging(
errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" },
logger: KSLog = KSLog,
@@ -18,6 +29,17 @@ fun CoroutineScope.launchLogging(
}.getOrThrow()
}
/**
* Launches a new coroutine with automatic exception logging and dropping. If an exception occurs, it will be logged
* using the provided [logger] and then dropped (not rethrown), allowing the coroutine to complete normally.
*
* @param errorMessageBuilder A function to build the error message from the caught exception. Defaults to "Something web wrong"
* @param logger The logger instance to use for logging exceptions. Defaults to [KSLog]
* @param context Additional [CoroutineContext] for the new coroutine. Defaults to [EmptyCoroutineContext]
* @param start The coroutine start option. Defaults to [CoroutineStart.DEFAULT]
* @param block The suspending function to execute in the new coroutine
* @return A [Job] representing the launched coroutine
*/
fun CoroutineScope.launchLoggingDropExceptions(
errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" },
logger: KSLog = KSLog,
@@ -30,6 +52,18 @@ fun CoroutineScope.launchLoggingDropExceptions(
} // just dropping exception
}
/**
* Creates a new async coroutine with automatic exception logging. If an exception occurs, it will be logged
* using the provided [logger] and then rethrown when the [Deferred] is awaited.
*
* @param T The return type of the async computation
* @param errorMessageBuilder A function to build the error message from the caught exception. Defaults to "Something web wrong"
* @param logger The logger instance to use for logging exceptions. Defaults to [KSLog]
* @param context Additional [CoroutineContext] for the new coroutine. Defaults to [EmptyCoroutineContext]
* @param start The coroutine start option. Defaults to [CoroutineStart.DEFAULT]
* @param block The suspending function to execute that returns a value of type [T]
* @return A [Deferred] representing the async computation
*/
fun <T> CoroutineScope.asyncLogging(
errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" },
logger: KSLog = KSLog,
@@ -42,6 +76,18 @@ fun <T> CoroutineScope.asyncLogging(
}.getOrThrow()
}
/**
* Creates a new async coroutine with automatic exception logging and dropping. If an exception occurs, it will be logged
* using the provided [logger] and wrapped in a [Result], which can be checked when the [Deferred] is awaited.
*
* @param T The return type of the async computation
* @param errorMessageBuilder A function to build the error message from the caught exception. Defaults to "Something web wrong"
* @param logger The logger instance to use for logging exceptions. Defaults to [KSLog]
* @param context Additional [CoroutineContext] for the new coroutine. Defaults to [EmptyCoroutineContext]
* @param start The coroutine start option. Defaults to [CoroutineStart.DEFAULT]
* @param block The suspending function to execute that returns a value of type [T]
* @return A [Deferred] containing a [Result] representing the async computation
*/
fun <T> CoroutineScope.asyncLoggingDropExceptions(
errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" },
logger: KSLog = KSLog,

View File

@@ -1,3 +1,13 @@
package dev.inmo.micro_utils.coroutines
/**
* Replaces a failed [Result] with a new value computed from the exception.
* If this [Result] is successful, it is returned as-is. If it represents a failure,
* the [onException] handler is called with the exception to compute a replacement value,
* which is then wrapped in a new [Result].
*
* @param T The type of the successful value
* @param onException A function that computes a replacement value from the caught exception
* @return The original [Result] if successful, or a new [Result] containing the replacement value
*/
inline fun <T> Result<T>.replaceIfFailure(onException: (Throwable) -> T) = if (isSuccess) { this } else { runCatching { onException(exceptionOrNull()!!) } }

View File

@@ -3,6 +3,14 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
/**
* Executes the given [block] and unlocks all provided [lockers] for writing if the block succeeds.
* If the block throws an exception, the lockers will remain locked.
*
* @param lockers Variable number of [SmartRWLocker] instances to unlock on successful execution
* @param block The suspending function to execute
* @return A [Result] containing [Unit] on success or the exception that occurred
*/
suspend inline fun alsoWithUnlockingOnSuccess(
vararg lockers: SmartRWLocker,
block: suspend () -> Unit
@@ -14,6 +22,15 @@ suspend inline fun alsoWithUnlockingOnSuccess(
}
}
/**
* Asynchronously executes the given [block] and unlocks all provided [lockers] for writing if the block succeeds.
* This function launches a new coroutine in the given [scope] and automatically logs and drops any exceptions.
*
* @param scope The [CoroutineScope] in which to launch the coroutine
* @param lockers Variable number of [SmartRWLocker] instances to unlock on successful execution
* @param block The suspending function to execute
* @return A [Job] representing the launched coroutine
*/
fun alsoWithUnlockingOnSuccessAsync(
scope: CoroutineScope,
vararg lockers: SmartRWLocker,

View File

@@ -3,19 +3,49 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
/**
* Creates a [SupervisorJob] linked to this [CoroutineContext]'s job. The new supervisor job will be a child
* of the current job, and optionally combined with [additionalContext].
*
* @param additionalContext Optional additional context to combine with the supervisor job
* @return A [CoroutineContext] containing the new supervisor job
*/
fun CoroutineContext.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null
) = SupervisorJob(job).let { if (additionalContext != null) it + additionalContext else it }
/**
* Creates a [SupervisorJob] linked to this [CoroutineScope]'s job. The new supervisor job will be a child
* of the current scope's job, and optionally combined with [additionalContext].
*
* @param additionalContext Optional additional context to combine with the supervisor job
* @return A [CoroutineContext] containing the new supervisor job
*/
fun CoroutineScope.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorJob(additionalContext)
/**
* Creates a new [CoroutineScope] with a [SupervisorJob] linked to this [CoroutineContext]'s job.
* The new scope's supervisor job will be a child of the current job, and optionally combined with [additionalContext].
*
* @param additionalContext Optional additional context to combine with the supervisor job
* @return A new [CoroutineScope] with a linked supervisor job
*/
fun CoroutineContext.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null
) = CoroutineScope(
this + LinkedSupervisorJob(additionalContext)
)
/**
* Creates a new [CoroutineScope] with a [SupervisorJob] linked to this [CoroutineScope]'s job.
* The new scope's supervisor job will be a child of the current scope's job, and optionally combined with [additionalContext].
*
* @param additionalContext Optional additional context to combine with the supervisor job
* @return A new [CoroutineScope] with a linked supervisor job
*/
fun CoroutineScope.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorScope(additionalContext)

View File

@@ -2,9 +2,22 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
/**
* Convenience property to access [Dispatchers.IO] for I/O-bound operations.
* This dispatcher is optimized for offloading blocking I/O tasks to a shared pool of threads.
*/
val IO
get() = Dispatchers.IO
/**
* Executes the given [block] on the IO dispatcher and returns its result.
* This is a convenience function for executing I/O-bound operations like reading files,
* network requests, or database queries.
*
* @param T The return type of the block
* @param block The suspending function to execute on the IO dispatcher
* @return The result of executing the block
*/
suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = doIn(
IO,
block

View File

@@ -3,6 +3,16 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* Launches a coroutine in the current thread using [Dispatchers.Unconfined] and blocks until it completes,
* returning its result. The coroutine will start execution in the current thread and will continue
* in the same thread until the first suspension point.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute in the current thread
* @return The result of the suspending block
* @throws Throwable if the coroutine throws an exception
*/
fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
val scope = CoroutineScope(Dispatchers.Unconfined)
return scope.launchSynchronously(block)

View File

@@ -2,6 +2,16 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
/**
* Launches a coroutine and blocks the current thread until the coroutine completes, returning its result.
* This is useful for bridging between suspending and non-suspending code in JVM environments.
* The coroutine is launched with [CoroutineStart.UNDISPATCHED] to start execution immediately.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute synchronously
* @return The result of the suspending block
* @throws Throwable if the coroutine throws an exception
*/
fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
var result: Result<T>? = null
val objectToSynchronize = Object()
@@ -22,7 +32,31 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
return result!!.getOrThrow()
}
/**
* Launches a coroutine in a new [CoroutineScope] with [Dispatchers.Default] and blocks the current thread
* until the coroutine completes, returning its result.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute synchronously
* @return The result of the suspending block
* @throws Throwable if the coroutine throws an exception
*/
fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block)
/**
* Alias for [launchSynchronously]. Launches a coroutine and blocks the current thread until it completes.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute synchronously
* @return The result of the suspending block
*/
fun <T> CoroutineScope.doSynchronously(block: suspend CoroutineScope.() -> T): T = launchSynchronously(block)
/**
* Alias for [launchSynchronously]. Launches a coroutine in a new scope and blocks the current thread until it completes.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute synchronously
* @return The result of the suspending block
*/
fun <T> doSynchronously(block: suspend CoroutineScope.() -> T): T = launchSynchronously(block)

View File

@@ -12,12 +12,35 @@ private val BASE64_INVERSE_ALPHABET = IntArray(256) {
internal fun Int.toBase64(): Char = BASE64_ALPHABET[this]
internal fun Byte.fromBase64(): Byte = BASE64_INVERSE_ALPHABET[toInt() and 0xff].toByte() and BASE64_MASK
/**
* Type alias representing a Base64-encoded string.
*/
typealias EncodedBase64String = String
/**
* Type alias representing a Base64-encoded byte array.
*/
typealias EncodedByteArray = ByteArray
/**
* Encodes this string to Base64 format, returning the result as a string.
*
* @return A Base64-encoded string
*/
fun SourceString.encodeBase64String(): EncodedBase64String = encodeToByteArray().encodeBase64String()
/**
* Encodes this string to Base64 format, returning the result as a byte array.
*
* @return A Base64-encoded byte array
*/
fun SourceString.encodeBase64(): EncodedByteArray = encodeToByteArray().encodeBase64()
/**
* Encodes this byte array to Base64 format, returning the result as a string.
*
* @return A Base64-encoded string with padding ('=') characters
*/
fun SourceBytes.encodeBase64String(): EncodedBase64String = buildString {
var i = 0
while (this@encodeBase64String.size > i) {
@@ -45,11 +68,33 @@ fun SourceBytes.encodeBase64String(): EncodedBase64String = buildString {
i += read
}
}
/**
* Encodes this byte array to Base64 format, returning the result as a byte array.
*
* @return A Base64-encoded byte array
*/
fun SourceBytes.encodeBase64(): EncodedByteArray = encodeBase64String().encodeToByteArray()
/**
* Decodes this Base64-encoded string back to the original byte array.
*
* @return The decoded byte array
*/
fun EncodedBase64String.decodeBase64(): SourceBytes = dropLastWhile { it == BASE64_PAD }.encodeToByteArray().decodeBase64()
/**
* Decodes this Base64-encoded string back to the original string.
*
* @return The decoded string
*/
fun EncodedBase64String.decodeBase64String(): SourceString = decodeBase64().decodeToString()
/**
* Decodes this Base64-encoded byte array back to the original byte array.
*
* @return The decoded byte array
*/
fun EncodedByteArray.decodeBase64(): SourceBytes {
val result = mutableListOf<Byte>()
val data = ByteArray(4)
@@ -74,4 +119,10 @@ fun EncodedByteArray.decodeBase64(): SourceBytes {
return result.toByteArray()
}
/**
* Decodes this Base64-encoded byte array back to the original string.
*
* @return The decoded string
*/
fun EncodedByteArray.decodeBase64String(): SourceString = decodeBase64().decodeToString()

View File

@@ -1,7 +1,16 @@
package dev.inmo.micro_utils.crypto
/**
* Character array used for hexadecimal encoding (lowercase).
*/
val HEX_ARRAY = "0123456789abcdef".toCharArray()
/**
* Converts this byte array to a hexadecimal string representation (lowercase).
* Each byte is represented as two hex characters.
*
* @return A lowercase hex string (e.g., "48656c6c6f" for "Hello")
*/
fun SourceBytes.hex(): String {
val hexChars = CharArray(size * 2)
for (j in indices) {
@@ -12,4 +21,9 @@ fun SourceBytes.hex(): String {
return hexChars.concatToString()
}
/**
* Converts this string to a hexadecimal representation by first encoding it as UTF-8 bytes.
*
* @return A lowercase hex string representation of the UTF-8 encoded bytes
*/
fun SourceString.hex(): String = encodeToByteArray().hex()

View File

@@ -2,7 +2,21 @@ package dev.inmo.micro_utils.crypto
import korlibs.crypto.md5
/**
* Type alias representing an MD5 hash as a hex-encoded string (32 characters).
*/
typealias MD5 = String
/**
* Computes the MD5 hash of this byte array and returns it as a lowercase hex string.
*
* @return The MD5 hash as a 32-character lowercase hex string
*/
fun SourceBytes.md5(): MD5 = md5().hexLower
/**
* Computes the MD5 hash of this string (encoded as UTF-8 bytes) and returns it as a lowercase hex string.
*
* @return The MD5 hash as a 32-character lowercase hex string
*/
fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower

View File

@@ -1,5 +1,13 @@
package dev.inmo.micro_utils.fsm.common
/**
* Represents a state in a finite state machine (FSM).
* Each state must have an associated context that identifies it uniquely within its chain.
*/
interface State {
/**
* The context object that uniquely identifies this state within a state chain.
* States with the same context are considered to belong to the same chain.
*/
val context: Any
}

View File

@@ -1,6 +1,26 @@
package dev.inmo.micro_utils.fsm.common.utils
/**
* A handler function type for dealing with errors during state handling in a finite state machine.
* The handler receives the state that caused the error and the thrown exception, and can optionally
* return a new state to continue the chain, or null to end the chain.
*
* @param T The state type that caused the error
* @param Throwable The exception that was thrown during state handling
* @return A new state to continue with, or null to end the state chain
*/
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?
/**
* The default error handler that returns null for all errors, effectively ending the state chain.
*/
val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null }
/**
* Returns a typed version of the [DefaultStateHandlingErrorHandler].
*
* @param T The state type
* @return A [StateHandlingErrorHandler] for type [T]
*/
inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T>

View File

@@ -18,5 +18,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.28.1
android_code_version=310
version=0.29.3
android_code_version=313

View File

@@ -1,43 +1,42 @@
[versions]
kt = "2.3.0"
kt-serialization = "1.10.0"
kt-coroutines = "1.10.2"
kt = "2.3.21"
kt-serialization = "1.11.0"
kt-coroutines = "1.11.0"
kotlinx-browser = "0.5.0"
kslog = "1.5.2"
kslog = "1.6.1"
jb-compose = "1.10.0"
jb-compose-material3 = "1.10.0-alpha05"
jb-compose = "1.11.0"
jb-compose-material3 = "1.11.0-alpha07"
jb-compose-icons = "1.7.8"
jb-exposed = "1.0.0"
jb-dokka = "2.1.0"
jb-exposed = "1.3.0"
jb-dokka = "2.2.0"
# 3.51.0.0 contains bug, checking with ./gradlew :micro_utils.repos.exposed:jvmTest
sqlite = "3.50.1.0"
sqlite = "3.53.1.0"
korlibs = "5.4.0"
uuid = "0.8.4"
ktor = "3.4.0"
ktor = "3.5.0"
gh-release = "2.5.2"
koin = "4.1.1"
koin = "4.2.1"
okio = "3.16.4"
okio = "3.17.0"
ksp = "2.3.4"
kotlin-poet = "2.2.0"
ksp = "2.3.8"
kotlin-poet = "2.3.0"
versions = "0.53.0"
nmcp = "1.2.1"
versions = "0.54.0"
nmcp = "1.5.0"
android-gradle = "8.12.+"
dexcount = "4.0.0"
android-coreKtx = "1.17.0"
android-coreKtx = "1.18.0"
android-recyclerView = "1.4.0"
android-appCompat = "1.7.1"
android-fragment = "1.8.9"

View File

@@ -6,6 +6,17 @@ import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.asTypeName
import kotlin.reflect.KClass
/**
* Safely retrieves a [ClassName] from a [KClass] getter, handling cases where the type is not
* yet available during annotation processing (KSP).
*
* When [KSTypeNotPresentException] is caught, it extracts the class name information from the
* exception's [com.google.devtools.ksp.symbol.KSType] to construct a [ClassName].
*
* @param classnameGetter A lambda that returns the [KClass] to convert
* @return A KotlinPoet [ClassName] representing the class
* @throws Throwable If an exception other than [KSTypeNotPresentException] occurs
*/
@Suppress("NOTHING_TO_INLINE")
@OptIn(KspExperimental::class)
inline fun safeClassName(classnameGetter: () -> KClass<*>) = runCatching {

View File

@@ -2,6 +2,13 @@ package dev.inmo.micro_ksp.generator
import com.google.devtools.ksp.symbol.KSClassDeclaration
/**
* Recursively resolves all subclasses of this sealed class declaration.
* For sealed classes, it traverses the entire hierarchy of sealed subclasses.
* For non-sealed classes (leaf nodes), it returns the class itself.
*
* @return A list of all concrete (non-sealed) subclass declarations in the hierarchy
*/
fun KSClassDeclaration.resolveSubclasses(): List<KSClassDeclaration> {
return (getSealedSubclasses().flatMap {
it.resolveSubclasses()

View File

@@ -4,10 +4,25 @@ import io.ktor.client.call.body
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode
/**
* Returns the response body as type [T] if the [statusFilter] condition is met, otherwise returns null.
* By default, the filter checks if the status code is [HttpStatusCode.OK].
*
* @param T The type to deserialize the response body to
* @param statusFilter A predicate to determine if the body should be retrieved. Defaults to checking for OK status
* @return The deserialized body of type [T], or null if the filter condition is not met
*/
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull(
statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK }
) = takeIf(statusFilter) ?.body<T>()
/**
* Returns the response body as type [T] if the status code is not [HttpStatusCode.NoContent], otherwise returns null.
* This is useful for handling responses that may return 204 No Content.
*
* @param T The type to deserialize the response body to
* @return The deserialized body of type [T], or null if the status is No Content
*/
suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
it.status != HttpStatusCode.NoContent
}

View File

@@ -4,6 +4,13 @@ import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.statement.HttpResponse
import io.ktor.http.isSuccess
/**
* Throws a [ClientRequestException] if this [HttpResponse] does not have a successful status code.
* A status code is considered successful if it's in the 2xx range.
*
* @param unsuccessMessage A lambda that provides the error message to use if the response is unsuccessful
* @throws ClientRequestException if the response status is not successful
*/
inline fun HttpResponse.throwOnUnsuccess(
unsuccessMessage: () -> String
) {

View File

@@ -5,6 +5,12 @@ import dev.inmo.micro_utils.common.filesize
import dev.inmo.micro_utils.ktor.common.input
import io.ktor.client.request.forms.InputProvider
/**
* Creates a Ktor [InputProvider] from this multiplatform file for use in HTTP client requests.
* The input provider knows the file size and can create input streams on demand.
*
* @return An [InputProvider] for reading this file in HTTP requests
*/
fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) {
input()
}

View File

@@ -1,3 +1,9 @@
package dev.inmo.micro_utils.ktor.client
/**
* A callback function type for tracking upload progress.
*
* @param uploaded The number of bytes uploaded so far
* @param count The total number of bytes to be uploaded
*/
typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit

View File

@@ -5,6 +5,15 @@ import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.content.*
/**
* Uploads a file to a temporary storage on the server.
* The server should provide an endpoint that accepts multipart uploads and returns a [TemporalFileId].
*
* @param fullTempUploadDraftPath The full URL path to the temporary upload endpoint
* @param file The file to upload
* @param onUpload Progress callback invoked during upload
* @return A [TemporalFileId] that can be used to reference the uploaded file
*/
expect suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,

View File

@@ -10,6 +10,14 @@ import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json
/**
* Information about a file to upload in a multipart request.
* This allows uploading from custom sources beyond regular files.
*
* @param fileName The name of the file
* @param mimeType The MIME type of the file
* @param inputAllocator A lambda that provides input streams for reading the file data
*/
data class UniUploadFileInfo(
val fileName: FileName,
val mimeType: String,

View File

@@ -1,5 +1,14 @@
package dev.inmo.micro_utils.ktor.common
/**
* Builds a standard URL by combining a base part, subpart, and optional query parameters.
* The base and subpart are joined with a '/', and query parameters are appended.
*
* @param basePart The base part of the URL (e.g., "https://example.com/api")
* @param subpart The subpart of the URL (e.g., "users")
* @param parameters Query parameters as a map. Defaults to an empty map
* @return The complete URL string with query parameters
*/
fun buildStandardUrl(
basePart: String,
subpart: String,
@@ -8,6 +17,15 @@ fun buildStandardUrl(
parameters
)
/**
* Builds a standard URL by combining a base part, subpart, and query parameters as a list.
* The base and subpart are joined with a '/', and query parameters are appended.
*
* @param basePart The base part of the URL (e.g., "https://example.com/api")
* @param subpart The subpart of the URL (e.g., "users")
* @param parameters Query parameters as a list of key-value pairs
* @return The complete URL string with query parameters
*/
fun buildStandardUrl(
basePart: String,
subpart: String,
@@ -16,6 +34,15 @@ fun buildStandardUrl(
parameters
)
/**
* Builds a standard URL by combining a base part, subpart, and vararg query parameters.
* The base and subpart are joined with a '/', and query parameters are appended.
*
* @param basePart The base part of the URL (e.g., "https://example.com/api")
* @param subpart The subpart of the URL (e.g., "users")
* @param parameters Query parameters as vararg key-value pairs
* @return The complete URL string with query parameters
*/
fun buildStandardUrl(
basePart: String,
subpart: String,

View File

@@ -1,3 +1,8 @@
package dev.inmo.micro_utils.ktor.common
/**
* An exception used to indicate a correct/normal closure of a connection or stream.
* This is typically used in WebSocket or network communication scenarios where a
* clean shutdown needs to be distinguished from error conditions.
*/
object CorrectCloseException : Exception()

View File

@@ -2,6 +2,14 @@ package dev.inmo.micro_utils.ktor.common
private val schemaRegex = Regex("^[^:]*://")
/**
* Converts this string to a correct WebSocket URL by ensuring it starts with "ws://" scheme.
* If the URL already starts with "ws", it is returned unchanged.
* If the URL contains a scheme (e.g., "http://"), it is replaced with "ws://".
* If the URL has no scheme, "ws://" is prepended.
*
* @return A properly formatted WebSocket URL
*/
val String.asCorrectWebSocketUrl: String
get() = if (startsWith("ws")) {
this

View File

@@ -2,14 +2,30 @@ package dev.inmo.micro_utils.ktor.common
import korlibs.time.DateTime
/**
* Type alias representing a date-time range with optional start and end times.
* First element is the "from" date-time, second is the "to" date-time.
*/
typealias FromToDateTime = Pair<DateTime?, DateTime?>
/**
* Converts this [FromToDateTime] range to URL query parameters.
* Creates "from" and "to" query parameters with Unix millisecond timestamps.
*
* @return A map of query parameters representing the date-time range
*/
val FromToDateTime.asFromToUrlPart: QueryParams
get() = mapOf(
"from" to first ?.unixMillis ?.toString(),
"to" to second ?.unixMillis ?.toString()
)
/**
* Extracts a [FromToDateTime] range from URL query parameters.
* Looks for "from" and "to" parameters containing Unix millisecond timestamps.
*
* @return A [FromToDateTime] pair extracted from the query parameters
*/
val QueryParams.extractFromToDateTime: FromToDateTime
get() = FromToDateTime(
get("from") ?.toDoubleOrNull() ?.let { DateTime(it) },

View File

@@ -2,4 +2,8 @@ package dev.inmo.micro_utils.ktor.common
import io.ktor.utils.io.core.Input
/**
* A function type that provides an [Input] instance.
* This is useful for lazy or deferred input creation in Ktor operations.
*/
typealias LambdaInputProvider = () -> Input

View File

@@ -3,4 +3,10 @@ package dev.inmo.micro_utils.ktor.common
import dev.inmo.micro_utils.common.MPPFile
import io.ktor.utils.io.core.Input
/**
* Creates a Ktor [Input] from this multiplatform file.
* Platform-specific implementations handle file reading for each supported platform.
*
* @return An [Input] stream for reading this file
*/
expect fun MPPFile.input(): Input

View File

@@ -29,6 +29,13 @@ fun String.includeQueryParams(
queryParams: List<QueryParam>
): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}"
/**
* Parses this URL query string into a [QueryParams] map.
* Splits on '&' to separate parameters and '=' to separate keys from values.
* Parameters without values will have null as their value.
*
* @return A map of query parameter keys to their values (or null if no value)
*/
val String.parseUrlQuery: QueryParams
get() = split("&").map {
it.split("=").let { pair ->

View File

@@ -5,27 +5,73 @@ package dev.inmo.micro_utils.ktor.common
import kotlinx.serialization.*
import kotlinx.serialization.cbor.Cbor
/**
* Type alias for the standard serialization format used in Ktor utilities, which is [BinaryFormat].
*/
typealias StandardKtorSerialFormat = BinaryFormat
/**
* Type alias for the standard serialization input data type, which is [ByteArray].
*/
typealias StandardKtorSerialInputData = ByteArray
/**
* The standard Ktor serialization format instance, configured as CBOR.
*/
val standardKtorSerialFormat: StandardKtorSerialFormat = Cbor { }
/**
* Decodes data from [StandardKtorSerialInputData] using the standard format.
*
* @param T The type to decode to
* @param deserializationStrategy The deserialization strategy for type [T]
* @param input The byte array input data to decode
* @return The decoded value of type [T]
*/
inline fun <T> StandardKtorSerialFormat.decodeDefault(
deserializationStrategy: DeserializationStrategy<T>,
input: StandardKtorSerialInputData
): T = decodeFromByteArray(deserializationStrategy, input)
/**
* Encodes data to [StandardKtorSerialInputData] using the standard format.
*
* @param T The type to encode
* @param serializationStrategy The serialization strategy for type [T]
* @param data The data to encode
* @return The encoded byte array
*/
inline fun <T> StandardKtorSerialFormat.encodeDefault(
serializationStrategy: SerializationStrategy<T>,
data: T
): StandardKtorSerialInputData = encodeToByteArray(serializationStrategy, data)
/**
* A CBOR instance for serialization operations.
*/
val cbor = Cbor {}
/**
* Decodes data from a hex string using the standard binary format.
*
* @param T The type to decode to
* @param deserializationStrategy The deserialization strategy for type [T]
* @param input The hex string to decode
* @return The decoded value of type [T]
*/
inline fun <T> StandardKtorSerialFormat.decodeHex(
deserializationStrategy: DeserializationStrategy<T>,
input: String
): T = decodeFromHexString(deserializationStrategy, input)
/**
* Encodes data to a hex string using the standard binary format.
*
* @param T The type to encode
* @param serializationStrategy The serialization strategy for type [T]
* @param data The data to encode
* @return The encoded hex string
*/
inline fun <T> StandardKtorSerialFormat.encodeHex(
serializationStrategy: SerializationStrategy<T>,
data: T

View File

@@ -3,8 +3,17 @@ package dev.inmo.micro_utils.ktor.common
import kotlin.jvm.JvmInline
import kotlinx.serialization.Serializable
/**
* The default subdirectory path for storing temporal files during upload operations.
*/
const val DefaultTemporalFilesSubPath = "temp_upload"
/**
* A value class representing a unique identifier for a temporal file.
* Temporal files are typically used for temporary storage during file upload/processing operations.
*
* @param string The string representation of the temporal file ID
*/
@Serializable
@JvmInline
value class TemporalFileId(val string: String)

View File

@@ -4,6 +4,12 @@ import korlibs.time.DateTime
import dev.inmo.micro_utils.ktor.common.FromToDateTime
import io.ktor.http.Parameters
/**
* Extracts a [FromToDateTime] range from Ktor server [Parameters].
* Looks for "from" and "to" parameters containing Unix millisecond timestamps.
*
* @return A [FromToDateTime] pair extracted from the parameters
*/
val Parameters.extractFromToDateTime: FromToDateTime
get() = FromToDateTime(
get("from") ?.toDoubleOrNull() ?.let { DateTime(it) },

View File

@@ -4,6 +4,13 @@ import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond
/**
* Retrieves a parameter value by [field] name from the request parameters.
* If the parameter is not present, responds with [HttpStatusCode.BadRequest] (400) and an error message.
*
* @param field The name of the parameter to retrieve
* @return The parameter value, or null if not found (after sending error response)
*/
suspend fun ApplicationCall.getParameterOrSendError(
field: String
) = parameters[field].also {
@@ -12,6 +19,13 @@ suspend fun ApplicationCall.getParameterOrSendError(
}
}
/**
* Retrieves all parameter values by [field] name from the request parameters.
* If the parameter is not present, responds with [HttpStatusCode.BadRequest] (400) and an error message.
*
* @param field The name of the parameter to retrieve
* @return A list of parameter values, or null if not found (after sending error response)
*/
suspend fun ApplicationCall.getParametersOrSendError(
field: String
) = parameters.getAll(field).also {
@@ -20,14 +34,33 @@ suspend fun ApplicationCall.getParametersOrSendError(
}
}
/**
* Retrieves a query parameter value by [field] name from the request.
*
* @param field The name of the query parameter to retrieve
* @return The query parameter value, or null if not found
*/
fun ApplicationCall.getQueryParameter(
field: String
) = request.queryParameters[field]
/**
* Retrieves all query parameter values by [field] name from the request.
*
* @param field The name of the query parameter to retrieve
* @return A list of query parameter values, or null if not found
*/
fun ApplicationCall.getQueryParameters(
field: String
) = request.queryParameters.getAll(field)
/**
* Retrieves a query parameter value by [field] name from the request.
* If the parameter is not present, responds with [HttpStatusCode.BadRequest] (400) and an error message.
*
* @param field The name of the query parameter to retrieve
* @return The query parameter value, or null if not found (after sending error response)
*/
suspend fun ApplicationCall.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
@@ -36,6 +69,13 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
}
}
/**
* Retrieves all query parameter values by [field] name from the request.
* If the parameter is not present, responds with [HttpStatusCode.BadRequest] (400) and an error message.
*
* @param field The name of the query parameter to retrieve
* @return A list of query parameter values, or null if not found (after sending error response)
*/
suspend fun ApplicationCall.getQueryParametersOrSendError(
field: String
) = getQueryParameters(field).also {

View File

@@ -4,6 +4,13 @@ import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond
/**
* Responds with the given [data] if it's not null, or responds with [HttpStatusCode.NoContent] (204) if it's null.
* This is useful for API endpoints that may return empty results.
*
* @param T The type of data to respond with
* @param data The data to respond with, or null to respond with No Content
*/
suspend inline fun <reified T : Any> ApplicationCall.respondOrNoContent(
data: T?
) {

View File

@@ -5,6 +5,10 @@ import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Serializer for [IetfLang] that serializes language codes as their string representation.
* The language code is serialized as a simple string (e.g., "en-US", "fr", "de-DE").
*/
object IetfLangSerializer : KSerializer<IetfLang> {
override val descriptor = String.serializer().descriptor

View File

@@ -1,3 +1,9 @@
package dev.inmo.micro_utils.matrix
/**
* Represents a matrix as a list of rows, where each row is a list of elements.
* This is essentially a 2D structure represented as `List<List<T>>`.
*
* @param T The type of elements in the matrix
*/
typealias Matrix<T> = List<Row<T>>

View File

@@ -1,15 +1,37 @@
package dev.inmo.micro_utils.matrix
/**
* Creates a matrix using a DSL-style builder.
* Allows defining multiple rows using the [MatrixBuilder] API.
*
* @param T The type of elements in the matrix
* @param block A builder lambda to define the matrix structure
* @return A constructed [Matrix]
*/
fun <T> matrix(block: MatrixBuilder<T>.() -> Unit): Matrix<T> {
return MatrixBuilder<T>().also(block).matrix
}
/**
* Creates a single-row matrix using a DSL-style builder.
*
* @param T The type of elements in the matrix
* @param block A builder lambda to define the row elements
* @return A [Matrix] containing a single row
*/
fun <T> flatMatrix(block: RowBuilder<T>.() -> Unit): Matrix<T> {
return MatrixBuilder<T>().apply {
row(block)
}.matrix
}
/**
* Creates a single-row matrix from the provided elements.
*
* @param T The type of elements in the matrix
* @param elements The elements to include in the single row
* @return A [Matrix] containing a single row with the specified elements
*/
fun <T> flatMatrix(vararg elements: T): Matrix<T> {
return MatrixBuilder<T>().apply {
row { elements.forEach { +it } }

View File

@@ -1,3 +1,8 @@
package dev.inmo.micro_utils.matrix
/**
* Represents a single row in a matrix as a list of elements.
*
* @param T The type of elements in the row
*/
typealias Row<T> = List<T>

View File

@@ -1,3 +1,9 @@
package dev.inmo.micro_utils.mime_types
/**
* A custom implementation of [MimeType] that wraps a raw MIME type string.
* Use this when you need to work with MIME types that aren't defined in the standard set.
*
* @param raw The raw MIME type string (e.g., "application/custom", "text/x-custom")
*/
data class CustomMimeType(override val raw: String) : MimeType

View File

@@ -2,9 +2,24 @@ package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.Serializable
/**
* Represents a MIME type (Multipurpose Internet Mail Extensions type).
* A MIME type is a standard way to indicate the nature and format of a document, file, or assortment of bytes.
*
* Examples: "text/html", "application/json", "image/png"
*/
@Serializable(MimeTypeSerializer::class)
interface MimeType {
/**
* The raw MIME type string (e.g., "text/html", "application/json").
*/
val raw: String
/**
* An array of file extensions commonly associated with this MIME type.
* For example, "text/html" might have extensions ["html", "htm"].
* Returns an empty array by default if no extensions are known.
*/
val extensions: Array<String>
get() = emptyArray()
}

View File

@@ -8,6 +8,11 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Serializer for [MimeType] that serializes MIME types as their raw string representation.
* Uses the [mimeType] factory function to create appropriate [MimeType] instances during deserialization,
* which will return known MIME types when available or create [CustomMimeType] for unknown types.
*/
@Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> {

View File

@@ -2,6 +2,16 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
/**
* Executes [block] for each page in a paginated sequence.
* The [paginationMapper] determines the next pagination to use based on the current result.
* Stops when [paginationMapper] returns null.
*
* @param T The type of items in each page
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param paginationMapper Function that determines the next pagination based on the current result. Return null to stop
* @param block Function that processes each page and returns a [PaginationResult]
*/
inline fun <T> doForAll(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?,
@@ -12,6 +22,14 @@ inline fun <T> doForAll(
}
}
/**
* Executes [block] for each page in a paginated sequence, automatically moving to the next page
* until an empty page or the last page is reached.
*
* @param T The type of items in each page
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that processes each page and returns a [PaginationResult]
*/
inline fun <T> doForAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
@@ -23,6 +41,14 @@ inline fun <T> doForAllWithNextPaging(
)
}
/**
* Executes [block] for each page in a paginated sequence, automatically moving to the next page
* until an empty page or the last page is reached. Uses current page pagination logic.
*
* @param T The type of items in each page
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that processes each page and returns a [PaginationResult]
*/
inline fun <T> doAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
@@ -34,6 +60,13 @@ inline fun <T> doAllWithCurrentPaging(
)
}
/**
* Alias for [doAllWithCurrentPaging]. Executes [block] for each page in a paginated sequence.
*
* @param T The type of items in each page
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that processes each page and returns a [PaginationResult]
*/
inline fun <T> doForAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>

View File

@@ -2,6 +2,16 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
/**
* Retrieves all items from a paginated source by repeatedly calling [block] with different pagination parameters.
* The [paginationMapper] determines the next pagination to use based on the current result.
*
* @param T The type of items being retrieved
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param paginationMapper Function that determines the next pagination based on the current result. Return null to stop
* @param block Function that retrieves a page of results for the given pagination
* @return A list containing all retrieved items from all pages
*/
inline fun <T> getAll(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?,
@@ -16,6 +26,17 @@ inline fun <T> getAll(
return results.toList()
}
/**
* Retrieves all items from a paginated source using a receiver context.
* This is useful when the pagination logic depends on the receiver object's state.
*
* @param T The type of items being retrieved
* @param R The receiver type
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param paginationMapper Function that determines the next pagination based on the current result
* @param block Function that retrieves a page of results for the given pagination using the receiver context
* @return A list containing all retrieved items from all pages
*/
inline fun <T, R> R.getAllBy(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
@@ -26,6 +47,15 @@ inline fun <T, R> R.getAllBy(
{ block(it) }
)
/**
* Retrieves all items from a paginated source, automatically moving to the next page
* until an empty page or the last page is reached.
*
* @param T The type of items being retrieved
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that retrieves a page of results for the given pagination
* @return A list containing all retrieved items from all pages
*/
inline fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
@@ -35,6 +65,16 @@ inline fun <T> getAllWithNextPaging(
block
)
/**
* Retrieves all items from a paginated source using a receiver context,
* automatically moving to the next page until an empty page or the last page is reached.
*
* @param T The type of items being retrieved
* @param R The receiver type
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that retrieves a page of results for the given pagination using the receiver context
* @return A list containing all retrieved items from all pages
*/
inline fun <T, R> R.getAllByWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T>
@@ -43,6 +83,15 @@ inline fun <T, R> R.getAllByWithNextPaging(
{ block(it) }
)
/**
* Retrieves all items from a paginated source, automatically moving to the next page
* until an empty page or the last page is reached. Uses current page pagination logic.
*
* @param T The type of items being retrieved
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that retrieves a page of results for the given pagination
* @return A list containing all retrieved items from all pages
*/
inline fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
@@ -52,6 +101,17 @@ inline fun <T> getAllWithCurrentPaging(
block
)
/**
* Retrieves all items from a paginated source using a receiver context,
* automatically moving to the next page until an empty page or the last page is reached.
* Uses current page pagination logic.
*
* @param T The type of items being retrieved
* @param R The receiver type
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that retrieves a page of results for the given pagination using the receiver context
* @return A list containing all retrieved items from all pages
*/
inline fun <T, R> R.getAllByWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T>

View File

@@ -1,5 +1,13 @@
package dev.inmo.micro_utils.pagination.utils
/**
* Optionally reverses this [Iterable] based on the [reverse] parameter.
* Delegates to specialized implementations for [List] and [Set] for better performance.
*
* @param T The type of items in the iterable
* @param reverse If true, reverses the iterable; otherwise returns it unchanged
* @return The iterable, optionally reversed
*/
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
is List<T> -> optionallyReverse(reverse)
is Set<T> -> optionallyReverse(reverse)
@@ -9,17 +17,41 @@ fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (thi
this
}
}
/**
* Optionally reverses this [List] based on the [reverse] parameter.
*
* @param T The type of items in the list
* @param reverse If true, reverses the list; otherwise returns it unchanged
* @return The list, optionally reversed
*/
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
reversed()
} else {
this
}
/**
* Optionally reverses this [Set] based on the [reverse] parameter.
* Note that the resulting set may have a different iteration order than the original.
*
* @param T The type of items in the set
* @param reverse If true, reverses the set; otherwise returns it unchanged
* @return The set, optionally reversed
*/
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet()
} else {
this
}
/**
* Optionally reverses this [Array] based on the [reverse] parameter.
*
* @param T The type of items in the array
* @param reverse If true, creates a reversed copy of the array; otherwise returns it unchanged
* @return The array, optionally reversed
*/
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) {
get(lastIndex - it)

View File

@@ -2,6 +2,14 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
/**
* Paginates this [Iterable] according to the given [Pagination] parameters.
* Returns a [PaginationResult] containing the items within the specified page range.
*
* @param T The type of items in the iterable
* @param with The pagination parameters specifying which page to retrieve
* @return A [PaginationResult] containing the items from the requested page
*/
fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
var i = 0
val result = mutableListOf<T>()
@@ -20,6 +28,15 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
return result.createPaginationResult(with, i.toLong())
}
/**
* Paginates this [List] according to the given [Pagination] parameters.
* Returns a [PaginationResult] containing the items within the specified page range.
* More efficient than the [Iterable] version as it uses direct indexing.
*
* @param T The type of items in the list
* @param with The pagination parameters specifying which page to retrieve
* @return A [PaginationResult] containing the items from the requested page
*/
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
if (with.firstIndex >= size || with.lastIndex < 0) {
return emptyPaginationResult(with, size.toLong())
@@ -30,6 +47,14 @@ fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
)
}
/**
* Paginates this [List] according to the given [Pagination] parameters, optionally in reverse order.
*
* @param T The type of items in the list
* @param with The pagination parameters specifying which page to retrieve
* @param reversed If true, the list will be paginated in reverse order
* @return A [PaginationResult] containing the items from the requested page, optionally reversed
*/
fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
return if (reversed) {
val actualPagination = with.optionallyReverse(
@@ -42,6 +67,14 @@ fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<
}
}
/**
* Paginates this [Set] according to the given [Pagination] parameters.
* Returns a [PaginationResult] containing the items within the specified page range.
*
* @param T The type of items in the set
* @param with The pagination parameters specifying which page to retrieve
* @return A [PaginationResult] containing the items from the requested page
*/
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
return this.drop(with.firstIndex).take(with.size).createPaginationResult(
with,
@@ -49,6 +82,14 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
)
}
/**
* Paginates this [Set] according to the given [Pagination] parameters, optionally in reverse order.
*
* @param T The type of items in the set
* @param with The pagination parameters specifying which page to retrieve
* @param reversed If true, the set will be paginated in reverse order
* @return A [PaginationResult] containing the items from the requested page, optionally reversed
*/
fun <T> Set<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
val actualPagination = with.optionallyReverse(
size,

View File

@@ -2,6 +2,15 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
/**
* An iterator that lazily fetches items from a paginated data source.
* It automatically fetches the next page when the current page is exhausted.
*
* @param T The type of items being iterated
* @param pageSize The size of each page to fetch
* @param countGetter A function that returns the total count of available items
* @param paginationResultGetter A function that fetches a page of results for a given pagination
*/
class PaginatedIterator<T>(
pageSize: Int,
private val countGetter: () -> Long,
@@ -22,6 +31,15 @@ class PaginatedIterator<T>(
}
}
/**
* An iterable that lazily fetches items from a paginated data source.
* It creates a [PaginatedIterator] that automatically fetches pages as needed.
*
* @param T The type of items being iterated
* @param pageSize The size of each page to fetch
* @param countGetter A function that returns the total count of available items
* @param paginationResultGetter A function that fetches a page of results for a given pagination
*/
class PaginatedIterable<T>(
private val pageSize: Int,
private val countGetter: () -> Long,
@@ -31,7 +49,14 @@ class PaginatedIterable<T>(
}
/**
* Will make iterable using incoming [countGetter] and [paginationResultGetter]
* Creates an [Iterable] that lazily fetches items from a paginated data source.
* This is useful for iterating over large datasets without loading all items into memory at once.
*
* @param T The type of items being iterated
* @param countGetter A function that returns the total count of available items
* @param pageSize The size of each page to fetch. Defaults to [defaultPaginationPageSize]
* @param paginationResultGetter A function that fetches a page of results for a given pagination
* @return An [Iterable] that can be used in for-loops or other iterable contexts
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> makeIterable(

View File

@@ -4,6 +4,14 @@ import org.jetbrains.exposed.v1.core.Expression
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.jdbc.Query
/**
* Applies pagination to this Exposed [Query].
* Sets the limit and offset based on the pagination parameters, and optionally orders the results.
*
* @param with The pagination parameters to apply
* @param orderBy Optional pair of expression and sort order to order the results by
* @return The query with pagination and optional ordering applied
*/
fun Query.paginate(with: Pagination, orderBy: Pair<Expression<*>, SortOrder>? = null) =
limit(with.size)
.offset(with.firstIndex.toLong())
@@ -18,6 +26,15 @@ fun Query.paginate(with: Pagination, orderBy: Pair<Expression<*>, SortOrder>? =
}
}
/**
* Applies pagination to this Exposed [Query] with optional ordering and reversal.
* Sets the limit and offset based on the pagination parameters.
*
* @param with The pagination parameters to apply
* @param orderBy Optional expression to order the results by
* @param reversed If true, orders in descending order; otherwise ascending. Defaults to false
* @return The query with pagination and optional ordering applied
*/
fun Query.paginate(with: Pagination, orderBy: Expression<*>?, reversed: Boolean = false) = paginate(
with,
orderBy ?.let { it to if (reversed) SortOrder.DESC else SortOrder.ASC }

View File

@@ -53,6 +53,12 @@ interface ReadKeyValueRepo<Key, Value> : Repo {
*/
suspend fun contains(key: Key): Boolean
/**
* Returns all key-value pairs in the repository as a [Map].
* Default implementation iterates all pages using [keys] and [get].
*
* @return Map of all [Key] to [Value] entries in the repository
*/
suspend fun getAll(): Map<Key, Value> = getAllByWithNextPaging(maxPagePagination()) {
keys(it).let {
it.changeResultsUnchecked(
@@ -111,22 +117,48 @@ interface WriteKeyValueRepo<Key, Value> : Repo {
}
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
/**
* Vararg overload of [WriteKeyValueRepo.set] accepting pairs.
*
* @param toSet Key-value pairs to set
*/
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value>
) = set(toSet.toMap())
/**
* List overload of [WriteKeyValueRepo.set] accepting a list of pairs.
*
* @param toSet List of key-value pairs to set
*/
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
toSet: List<Pair<Key, Value>>
) = set(toSet.toMap())
/**
* Single-entry overload of [WriteKeyValueRepo.set].
*
* @param k Key to set
* @param v Value to associate with [k]
*/
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
k: Key, v: Value
) = set(k to v)
/**
* Vararg overload of [WriteKeyValueRepo.unset].
*
* @param k Keys to remove
*/
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
vararg k: Key
) = unset(k.toList())
/**
* Vararg overload of [WriteKeyValueRepo.unsetWithValues].
*
* @param v Values whose associated keys should be removed
*/
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
vararg v: Value
) = unsetWithValues(v.toList())
@@ -160,6 +192,14 @@ interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValue
}
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
/**
* Delegate-based implementation of [KeyValueRepo] that composes separate read and write delegates.
*
* @param Key The type of keys in the repository
* @param Value The type of values in the repository
* @param readDelegate Delegate providing all [ReadKeyValueRepo] operations
* @param writeDelegate Delegate providing all [WriteKeyValueRepo] operations
*/
class DelegateBasedKeyValueRepo<Key, Value>(
readDelegate: ReadKeyValueRepo<Key, Value>,
writeDelegate: WriteKeyValueRepo<Key, Value>

View File

@@ -6,13 +6,73 @@ import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.flow.Flow
/**
* Read part of [KeyValuesRepo] for one-to-many key-value relationships.
* This repository type allows multiple values to be associated with a single key.
*
* @param Key The type used as the key in all search operations
* @param Value The type of values associated with keys
*/
interface ReadKeyValuesRepo<Key, Value> : Repo {
/**
* Retrieves a paginated list of values associated with the given key.
*
* @param k The key to search for
* @param pagination The pagination parameters
* @param reversed Whether to reverse the order of results
* @return A [PaginationResult] containing values associated with the key
*/
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
/**
* Retrieves a paginated list of keys.
*
* @param pagination The pagination parameters
* @param reversed Whether to reverse the order of results
* @return A [PaginationResult] containing keys
*/
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
/**
* Retrieves keys that have the specified value associated with them.
*
* @param v The value to search for
* @param pagination The pagination parameters
* @param reversed Whether to reverse the order of results
* @return A [PaginationResult] containing keys associated with the value
*/
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
/**
* Checks if the specified key exists in the repository.
*
* @param k The key to check
* @return `true` if the key exists, `false` otherwise
*/
suspend fun contains(k: Key): Boolean
/**
* Checks if the specified key-value pair exists in the repository.
*
* @param k The key to check
* @param v The value to check
* @return `true` if the key-value pair exists, `false` otherwise
*/
suspend fun contains(k: Key, v: Value): Boolean
/**
* Returns the count of values associated with the specified key.
*
* @param k The key to count values for
* @return The number of values associated with the key
*/
suspend fun count(k: Key): Long
/**
* Returns the total count of key-value pairs in the repository.
*
* @return The total number of key-value pairs
*/
suspend fun count(): Long
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> {
@@ -37,73 +97,169 @@ interface ReadKeyValuesRepo<Key, Value> : Repo {
}
}
}
/**
* Type alias for [ReadKeyValuesRepo] emphasizing one-to-many relationships.
*/
typealias ReadOneToManyKeyValueRepo<Key,Value> = ReadKeyValuesRepo<Key, Value>
/**
* Write part of [KeyValuesRepo] for one-to-many key-value relationships.
* Provides methods for adding, removing, and clearing values associated with keys.
*
* @param Key The type used as the key in all write operations
* @param Value The type of values associated with keys
*/
interface WriteKeyValuesRepo<Key, Value> : Repo {
/**
* Flow that emits when a new value is added to a key.
*/
val onNewValue: Flow<Pair<Key, Value>>
/**
* Flow that emits when a value is removed from a key.
*/
val onValueRemoved: Flow<Pair<Key, Value>>
/**
* Flow that emits when all data for a key is cleared.
*/
val onDataCleared: Flow<Key>
/**
* Adds values to the specified keys.
*
* @param toAdd A map of keys to lists of values to add
*/
suspend fun add(toAdd: Map<Key, List<Value>>)
/**
* Removes [Value]s by passed [Key]s without full clear of all data by [Key]
* Removes specific values from keys without clearing all data for those keys.
*
* @param toRemove A map of keys to lists of values to remove
*/
suspend fun remove(toRemove: Map<Key, List<Value>>)
/**
* Removes [v] without full clear of all data by [Key]s with [v]
* Removes a specific value from all keys that contain it, without clearing all data for those keys.
*
* @param v The value to remove
*/
suspend fun removeWithValue(v: Value)
/**
* Fully clear all data by [k]
* Fully clears all data associated with the specified key.
*
* @param k The key to clear
*/
suspend fun clear(k: Key)
/**
* Clear [v] **with** full clear of all data by [Key]s with [v]
* Clears a specific value from all keys and removes those keys if they become empty.
*
* @param v The value to clear
*/
suspend fun clearWithValue(v: Value)
/**
* Sets the values for specified keys, clearing any existing values first.
*
* @param toSet A map of keys to lists of values to set
*/
suspend fun set(toSet: Map<Key, List<Value>>) {
toSet.keys.forEach { key -> clear(key) }
add(toSet)
}
}
/**
* Type alias for [WriteKeyValuesRepo] emphasizing one-to-many relationships.
*/
typealias WriteOneToManyKeyValueRepo<Key,Value> = WriteKeyValuesRepo<Key, Value>
/**
* List-of-pairs overload of [WriteKeyValuesRepo.add].
*
* @param keysAndValues List of key to list-of-values pairs to add
*/
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(
keysAndValues: List<Pair<Key, List<Value>>>
) = add(keysAndValues.toMap())
/**
* Vararg overload of [WriteKeyValuesRepo.add].
*
* @param keysAndValues Key to list-of-values pairs to add
*/
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(
vararg keysAndValues: Pair<Key, List<Value>>
) = add(keysAndValues.toMap())
/**
* Single-key overload of [WriteKeyValuesRepo.add] accepting a list of values.
*
* @param k Key to add values to
* @param v List of values to add
*/
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add(
k: Key, v: List<Value>
) = add(mapOf(k to v))
/**
* Single-key vararg overload of [WriteKeyValuesRepo.add].
*
* @param k Key to add values to
* @param v Values to add
*/
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add(
k: Key, vararg v: Value
) = add(k, v.toList())
/**
* List-of-pairs overload of [WriteKeyValuesRepo.set].
*
* @param keysAndValues List of key to list-of-values pairs to set
*/
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set(
keysAndValues: List<Pair<Key, List<Value>>>
) = set(keysAndValues.toMap())
/**
* Vararg overload of [WriteKeyValuesRepo.set].
*
* @param keysAndValues Key to list-of-values pairs to set
*/
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set(
vararg keysAndValues: Pair<Key, List<Value>>
) = set(keysAndValues.toMap())
/**
* Single-key overload of [WriteKeyValuesRepo.set] accepting a list of values.
*
* @param k Key to set values for
* @param v List of values to set
*/
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set(
k: Key, v: List<Value>
) = set(mapOf(k to v))
/**
* Single-key vararg overload of [WriteKeyValuesRepo.set].
*
* @param k Key to set values for
* @param v Values to set
*/
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set(
k: Key, vararg v: Value
) = set(k, v.toList())
/**
* Full one-to-many key-values repository combining read and write capabilities.
* Provides default implementations for [clearWithValue], [removeWithValue], and [set].
*
* @param Key The type used as the key in all operations
* @param Value The type of values associated with keys
*/
interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyValuesRepo<Key, Value> {
override suspend fun clearWithValue(v: Value) {
doWithPagination {
@@ -142,6 +298,14 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
}
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>
/**
* Delegate-based implementation of [KeyValuesRepo] that composes separate read and write delegates.
*
* @param Key The type of keys in the repository
* @param Value The type of values associated with keys
* @param readDelegate Delegate providing all [ReadKeyValuesRepo] operations
* @param writeDelegate Delegate providing all [WriteKeyValuesRepo] operations
*/
class DelegateBasedKeyValuesRepo<Key, Value>(
readDelegate: ReadKeyValuesRepo<Key, Value>,
writeDelegate: WriteKeyValuesRepo<Key, Value>
@@ -149,19 +313,41 @@ class DelegateBasedKeyValuesRepo<Key, Value>(
ReadKeyValuesRepo<Key, Value> by readDelegate,
WriteKeyValuesRepo<Key, Value> by writeDelegate
/**
* List-of-pairs overload of [WriteKeyValuesRepo.remove].
*
* @param keysAndValues List of key to list-of-values pairs to remove
*/
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
keysAndValues: List<Pair<Key, List<Value>>>
) = remove(keysAndValues.toMap())
/**
* Vararg overload of [WriteKeyValuesRepo.remove].
*
* @param keysAndValues Key to list-of-values pairs to remove
*/
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
vararg keysAndValues: Pair<Key, List<Value>>
) = remove(keysAndValues.toMap())
/**
* Single-key overload of [WriteKeyValuesRepo.remove] accepting a list of values.
*
* @param k Key to remove values from
* @param v List of values to remove
*/
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
k: Key,
v: List<Value>
) = remove(mapOf(k to v))
/**
* Single-key vararg overload of [WriteKeyValuesRepo.remove].
*
* @param k Key to remove values from
* @param v Values to remove
*/
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
k: Key,
vararg v: Value

View File

@@ -2,28 +2,66 @@ package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.*
/**
* Interface for repositories that provide bidirectional mapping between two sets of key-value types.
* This is useful for adapting repositories to work with different key and value types.
*
* @param FromKey The original key type (inner/source)
* @param FromValue The original value type (inner/source)
* @param ToKey The target key type (outer/destination)
* @param ToValue The target value type (outer/destination)
*/
@Suppress("UNCHECKED_CAST")
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
/**
* Provides a mapper for converting keys between inner and outer types.
*/
val keyMapper: SimpleSuspendableMapper<FromKey, ToKey>
get() = simpleSuspendableMapper(
{ it.toInnerKey() },
{ it.toOutKey() }
)
/**
* Provides a mapper for converting values between inner and outer types.
*/
val valueMapper: SimpleSuspendableMapper<FromValue, ToValue>
get() = simpleSuspendableMapper(
{ it.toInnerValue() },
{ it.toOutValue() }
)
/**
* Converts a key from the inner type to the outer type.
*/
suspend fun FromKey.toOutKey() = this as ToKey
/**
* Converts a value from the inner type to the outer type.
*/
suspend fun FromValue.toOutValue() = this as ToValue
/**
* Converts a key from the outer type to the inner type.
*/
suspend fun ToKey.toInnerKey() = this as FromKey
/**
* Converts a value from the outer type to the inner type.
*/
suspend fun ToValue.toInnerValue() = this as FromValue
companion object
}
/**
* Simple implementation of [MapperRepo] that uses provided conversion functions.
*
* @param FromKey The original key type
* @param FromValue The original value type
* @param ToKey The target key type
* @param ToValue The target value type
*/
class SimpleMapperRepo<FromKey, FromValue, ToKey, ToValue>(
private val keyFromToTo: suspend FromKey.() -> ToKey,
private val valueFromToTo: suspend FromValue.() -> ToValue,
@@ -36,6 +74,9 @@ class SimpleMapperRepo<FromKey, FromValue, ToKey, ToValue>(
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
}
/**
* Factory function for creating a [SimpleMapperRepo] with custom conversion functions.
*/
operator fun <FromKey, FromValue, ToKey, ToValue> MapperRepo.Companion.invoke(
keyFromToTo: suspend FromKey.() -> ToKey,
valueFromToTo: suspend FromValue.() -> ToValue,
@@ -43,6 +84,15 @@ operator fun <FromKey, FromValue, ToKey, ToValue> MapperRepo.Companion.invoke(
valueToToFrom: suspend ToValue.() -> FromValue
) = SimpleMapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
/**
* Creates a [MapperRepo] with optional custom conversion functions.
* By default, uses casting for type conversions.
*
* @param keyFromToTo Function to convert keys from inner to outer type
* @param valueFromToTo Function to convert values from inner to outer type
* @param keyToToFrom Function to convert keys from outer to inner type
* @param valueToToFrom Function to convert values from outer to inner type
*/
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },

View File

@@ -1,3 +1,8 @@
package dev.inmo.micro_utils.repos
/**
* Base marker interface for all repository types in the MicroUtils library.
* This interface serves as a common ancestor for specialized repository interfaces like
* [ReadCRUDRepo], [WriteCRUDRepo], [ReadKeyValueRepo], [WriteKeyValueRepo], etc.
*/
interface Repo

View File

@@ -7,11 +7,51 @@ import dev.inmo.micro_utils.pagination.utils.getAllWithCurrentPaging
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
import kotlinx.coroutines.flow.Flow
/**
* Read-only part of a standard CRUD repository.
*
* @param ObjectType The type of objects stored in the repository
* @param IdType The type of identifiers used to reference stored objects
*/
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
/**
* Returns a paginated list of all objects in the repository.
*
* @param pagination Pagination parameters (page number and size)
* @return [PaginationResult] containing objects for the requested page
*/
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
/**
* Returns a paginated list of all IDs in the repository.
*
* @param pagination Pagination parameters (page number and size)
* @return [PaginationResult] containing IDs for the requested page
*/
suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType>
/**
* Returns the object associated with the given [id], or null if not found.
*
* @param id The identifier of the object to retrieve
* @return The object with the given [id], or null if absent
*/
suspend fun getById(id: IdType): ObjectType?
/**
* Returns true if an object with the given [id] exists in the repository.
*
* @param id The identifier to check
* @return true if the object exists, false otherwise
*/
suspend fun contains(id: IdType): Boolean
/**
* Returns all objects in the repository as a map of ID to object.
* Default implementation iterates all pages using [getIdsByPagination] and [getById].
*
* @return Map of all [IdType] to [ObjectType] entries in the repository
*/
suspend fun getAll(): Map<IdType, ObjectType> = getAllWithCurrentPaging(maxPagePagination()) {
getIdsByPagination(it).let {
it.changeResultsUnchecked(
@@ -20,58 +60,164 @@ interface ReadCRUDRepo<ObjectType, IdType> : Repo {
}
}.toMap()
/**
* Returns the total count of objects stored in the repository.
*
* @return Total number of objects
*/
suspend fun count(): Long
}
typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType>
/**
* Type alias representing a pair of ID and updated value, used in batch update operations.
*
* @param IdType The type of the identifier
* @param ValueType The type of the input value
*/
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
/**
* Returns the ID component of an [UpdatedValuePair].
*/
val <IdType> UpdatedValuePair<IdType, *>.id
get() = first
/**
* Returns the value component of an [UpdatedValuePair].
*/
val <ValueType> UpdatedValuePair<*, ValueType>.value
get() = second
/**
* Write part of a standard CRUD repository.
* Provides create, update, and delete operations with reactive flows for change notifications.
*
* @param ObjectType The type of objects stored in the repository
* @param IdType The type of identifiers used to reference stored objects
* @param InputValueType The type of input data used to create or update objects
*/
interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
/**
* Flow that emits each newly created object after a successful [create] call.
*/
val newObjectsFlow: Flow<ObjectType>
/**
* Flow that emits each updated object after a successful [update] call.
*/
val updatedObjectsFlow: Flow<ObjectType>
/**
* Flow that emits the ID of each deleted object after a successful [deleteById] call.
*/
val deletedObjectsIdsFlow: Flow<IdType>
/**
* Creates new objects from the given list of input values.
* Successfully created objects must be emitted via [newObjectsFlow].
*
* @param values List of input values to create objects from
* @return List of created [ObjectType] instances
*/
suspend fun create(values: List<InputValueType>): List<ObjectType>
/**
* Updates the object identified by [id] with the given [value].
* Successfully updated object must be emitted via [updatedObjectsFlow].
*
* @param id The identifier of the object to update
* @param value The new input value
* @return The updated [ObjectType], or null if the object was not found
*/
suspend fun update(id: IdType, value: InputValueType): ObjectType?
/**
* Batch-updates objects using the given list of ID-value pairs.
* Successfully updated objects must be emitted via [updatedObjectsFlow].
*
* @param values List of [UpdatedValuePair] entries mapping IDs to new input values
* @return List of successfully updated [ObjectType] instances
*/
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
/**
* Deletes objects with the given list of IDs.
* Successfully deleted IDs must be emitted via [deletedObjectsIdsFlow].
*
* @param ids List of identifiers of objects to delete
*/
suspend fun deleteById(ids: List<IdType>)
}
typealias WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType>
/**
* Just mirroring [WriteCRUDRepo.newObjectsFlow] to be same as in KV repos
* Mirrors [WriteCRUDRepo.newObjectsFlow] under the name [onNewObjects] for consistency with KV repos naming.
*/
val <ObjectType> WriteCRUDRepo<ObjectType, *, *>.onNewObjects: Flow<ObjectType>
get() = newObjectsFlow
/**
* Just mirroring [WriteCRUDRepo.updatedObjectsFlow] to be same as in KV repos
* Mirrors [WriteCRUDRepo.updatedObjectsFlow] under the name [onUpdatedObjects] for consistency with KV repos naming.
*/
val <ObjectType> WriteCRUDRepo<ObjectType, *, *>.onUpdatedObjects: Flow<ObjectType>
get() = updatedObjectsFlow
/**
* Just mirroring [WriteCRUDRepo.deletedObjectsIdsFlow] to be same as in KV repos
* Mirrors [WriteCRUDRepo.deletedObjectsIdsFlow] under the name [onDeletedObjectsIds] for consistency with KV repos naming.
*/
val <IdType> WriteCRUDRepo<*, IdType, *>.onDeletedObjectsIds: Flow<IdType>
get() = deletedObjectsIdsFlow
/**
* Vararg overload of [WriteCRUDRepo.create] for convenience.
*
* @param values Input values to create objects from
* @return List of created [ObjectType] instances
*/
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create(
vararg values: InputValueType
): List<ObjectType> = create(values.toList())
/**
* Vararg overload of [WriteCRUDRepo.update] for convenience.
*
* @param values ID-value pairs to update
* @return List of successfully updated [ObjectType] instances
*/
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.update(
vararg values: UpdatedValuePair<IdType, InputValueType>
): List<ObjectType> = update(values.toList())
/**
* Vararg overload of [WriteCRUDRepo.deleteById] for convenience.
*
* @param ids Identifiers of objects to delete
*/
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
vararg ids: IdType
) = deleteById(ids.toList())
/**
* Full CRUD repository combining read and write capabilities.
*
* @param ObjectType The type of objects stored in the repository
* @param IdType The type of identifiers used to reference stored objects
* @param InputValueType The type of input data used to create or update objects
*/
interface CRUDRepo<ObjectType, IdType, InputValueType> : ReadCRUDRepo<ObjectType, IdType>,
WriteCRUDRepo<ObjectType, IdType, InputValueType>
typealias StandardCRUDRepo<ObjectType, IdType, InputValueType> = CRUDRepo<ObjectType, IdType, InputValueType>
/**
* Delegate-based implementation of [CRUDRepo] that composes separate read and write delegates.
*
* @param ObjectType The type of objects stored in the repository
* @param IdType The type of identifiers used to reference stored objects
* @param InputValueType The type of input data used to create or update objects
* @param readDelegate Delegate providing all [ReadCRUDRepo] operations
* @param writeDelegate Delegate providing all [WriteCRUDRepo] operations
*/
class DelegateBasedCRUDRepo<ObjectType, IdType, InputValueType>(
readDelegate: ReadCRUDRepo<ObjectType, IdType>,
writeDelegate: WriteCRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -7,14 +7,40 @@ import dev.inmo.micro_utils.repos.CRUDRepo
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.unset
/**
* Computes the difference between this [ReadCRUDRepo] and a map of items.
* Retrieves all items from the repository and compares them with the provided map.
*
* @param Id The type of IDs
* @param Registered The type of objects stored in the repository
* @param other The map to compare against
* @return A [MapDiff] describing added, removed, and updated items
*/
suspend fun <Id, Registered> ReadCRUDRepo<Registered, Id>.diff(other: Map<Id, Registered>): MapDiff<Id, Registered> {
return getAll().diff(other)
}
/**
* Computes the difference between this map and a [ReadCRUDRepo].
* Retrieves all items from the repository and compares them with this map.
*
* @param Id The type of IDs
* @param Registered The type of objects
* @param other The repository to compare against
* @return A [MapDiff] describing added, removed, and updated items
*/
suspend fun <Id, Registered> Map<Id, Registered>.diff(other: ReadCRUDRepo<Registered, Id>): MapDiff<Id, Registered> {
return diff(other.getAll())
}
/**
* Applies the difference between this map and a [ReadCRUDRepo] to this map.
* Modifies this mutable map to match the state of the repository.
*
* @param Id The type of IDs
* @param Registered The type of objects
* @param other The repository to synchronize with
*/
suspend fun <Id, Registered> MutableMap<Id, Registered>.applyDiff(other: ReadCRUDRepo<Registered, Id>) {
applyDiff(diff(other))
}

View File

@@ -7,14 +7,38 @@ import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.unset
/**
* Computes the difference between all entries in this [ReadKeyValueRepo] and the given [other] map.
*
* @param Id The type of keys
* @param Registered The type of values
* @param other The map to compare against
* @return [MapDiff] describing added, removed, and changed entries
*/
suspend fun <Id, Registered> ReadKeyValueRepo<Id, Registered>.diff(other: Map<Id, Registered>): MapDiff<Id, Registered> {
return getAll().diff(other)
}
/**
* Computes the difference between this map and all entries in the given [ReadKeyValueRepo].
*
* @param Id The type of keys
* @param Registered The type of values
* @param other The repository to compare against
* @return [MapDiff] describing added, removed, and changed entries
*/
suspend fun <Id, Registered> Map<Id, Registered>.diff(other: ReadKeyValueRepo<Id, Registered>): MapDiff<Id, Registered> {
return diff(other.getAll())
}
/**
* Applies the given [diff] to this [KeyValueRepo]: removes entries in [MapDiff.removed],
* updates entries in [MapDiff.changed], and adds entries in [MapDiff.added].
*
* @param Id The type of keys
* @param Registered The type of values
* @param diff The diff to apply
*/
suspend fun <Id, Registered> KeyValueRepo<Id, Registered>.applyDiff(diff: MapDiff<Id, Registered>) {
unset(diff.removed.map { it.key })
set(
@@ -24,10 +48,24 @@ suspend fun <Id, Registered> KeyValueRepo<Id, Registered>.applyDiff(diff: MapDif
)
}
/**
* Computes the diff between this [KeyValueRepo] and [other], then applies the diff to this repo.
*
* @param Id The type of keys
* @param Registered The type of values
* @param other The target map state to synchronize to
*/
suspend fun <Id, Registered> KeyValueRepo<Id, Registered>.applyDiff(other: Map<Id, Registered>) {
applyDiff(diff(other))
}
/**
* Computes the diff between this [MutableMap] and the given [ReadKeyValueRepo], then applies the diff to this map.
*
* @param Id The type of keys
* @param Registered The type of values
* @param other The repository whose state to synchronize to
*/
suspend fun <Id, Registered> MutableMap<Id, Registered>.applyDiff(other: ReadKeyValueRepo<Id, Registered>) {
applyDiff(diff(other))
}
}

View File

@@ -5,14 +5,38 @@ import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.repos.*
/**
* Computes the difference between all entries in this [ReadKeyValuesRepo] and the given [other] map.
*
* @param Id The type of keys
* @param Registered The type of values
* @param other The map to compare against
* @return [MapDiff] describing added, removed, and changed key-to-list entries
*/
suspend fun <Id, Registered> ReadKeyValuesRepo<Id, Registered>.diff(other: Map<Id, List<Registered>>): MapDiff<Id, List<Registered>> {
return getAll().diff(other)
}
/**
* Computes the difference between this map and all entries in the given [ReadKeyValuesRepo].
*
* @param Id The type of keys
* @param Registered The type of values
* @param other The repository to compare against
* @return [MapDiff] describing added, removed, and changed key-to-list entries
*/
suspend fun <Id, Registered> Map<Id, List<Registered>>.diff(other: ReadKeyValuesRepo<Id, Registered>): MapDiff<Id, List<Registered>> {
return diff(other.getAll())
}
/**
* Applies the given [diff] to this [KeyValuesRepo]: clears keys in [MapDiff.removed],
* sets entries in [MapDiff.changed] and [MapDiff.added].
*
* @param Id The type of keys
* @param Registered The type of values
* @param diff The diff to apply
*/
suspend fun <Id, Registered> KeyValuesRepo<Id, Registered>.applyDiff(diff: MapDiff<Id, List<Registered>>) {
diff.removed.forEach {
clear(it.key)
@@ -24,10 +48,24 @@ suspend fun <Id, Registered> KeyValuesRepo<Id, Registered>.applyDiff(diff: MapDi
)
}
/**
* Computes the diff between this [KeyValuesRepo] and [other], then applies the diff to this repo.
*
* @param Id The type of keys
* @param Registered The type of values
* @param other The target map state to synchronize to
*/
suspend fun <Id, Registered> KeyValuesRepo<Id, Registered>.applyDiff(other: Map<Id, List<Registered>>) {
applyDiff(diff(other))
}
/**
* Computes the diff between this [MutableMap] and the given [ReadKeyValuesRepo], then applies the diff to this map.
*
* @param Id The type of keys
* @param Registered The type of values
* @param other The repository whose state to synchronize to
*/
suspend fun <Id, Registered> MutableMap<Id, List<Registered>>.applyDiff(other: ReadKeyValuesRepo<Id, Registered>) {
applyDiff(diff(other))
}
}

View File

@@ -6,6 +6,17 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* A [ReadCRUDRepo] wrapper that maps between different key and value types.
* Allows adapting a repository with one type system to work with another type system.
*
* @param FromId The external ID type exposed by this wrapper
* @param FromRegistered The external object type exposed by this wrapper
* @param ToId The internal ID type used by the underlying repository
* @param ToRegistered The internal object type used by the underlying repository
* @param to The underlying repository to wrap
* @param mapper The mapper that defines the bidirectional type conversions
*/
open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
private val to: ReadCRUDRepo<ToRegistered, ToId>,
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>
@@ -41,11 +52,34 @@ open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
) ?.toInnerValue()
}
/**
* Wraps this [ReadCRUDRepo] with a mapper to expose different key and value types.
*
* @param FromKey The desired external ID type
* @param FromValue The desired external object type
* @param ToKey The current internal ID type
* @param ToValue The current internal object type
* @param mapper The mapper defining the type conversions
* @return A mapped repository with the new type system
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadCRUDRepo<FromValue, FromKey> = MapperReadCRUDRepo(this, mapper)
/**
* Wraps this [ReadCRUDRepo] with custom conversion functions for keys and values.
*
* @param FromKey The desired external ID type
* @param FromValue The desired external object type
* @param ToKey The current internal ID type
* @param ToValue The current internal object type
* @param keyFromToTo Converts external keys to internal keys
* @param valueFromToTo Converts external values to internal values
* @param keyToToFrom Converts internal keys to external keys
* @param valueToToFrom Converts internal values to external values
* @return A mapped repository with the new type system
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
@@ -56,6 +90,20 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
/**
* A [WriteCRUDRepo] wrapper that maps between different key, value, and input types.
* Allows adapting a repository to work with different type systems for both reading and writing.
*
* @param FromId The external ID type
* @param FromRegistered The external object type for read operations
* @param FromInput The external input type for write operations
* @param ToId The internal ID type
* @param ToRegistered The internal object type for read operations
* @param ToInput The internal input type for write operations
* @param to The underlying repository to wrap
* @param mapper The mapper for keys and values
* @param inputMapper The mapper for input types
*/
open class MapperWriteCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
private val to: WriteCRUDRepo<ToRegistered, ToId, ToInput>,
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,

View File

@@ -5,6 +5,18 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* Read-only key-value repository adapter that applies type mapping via [MapperRepo].
* Converts outer (From) key/value types to inner (To) types before delegating to [to],
* and converts results back from inner to outer types.
*
* @param FromKey The outer key type exposed by this repo
* @param FromValue The outer value type exposed by this repo
* @param ToKey The inner key type used by the underlying [to] repo
* @param ToValue The inner value type used by the underlying [to] repo
* @param to The underlying [ReadKeyValueRepo] to delegate operations to
* @param mapper The [MapperRepo] providing bidirectional key/value type conversions
*/
open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
@@ -62,11 +74,34 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
override suspend fun count(): Long = to.count()
}
/**
* Wraps this [ReadKeyValueRepo] with a [MapperRepo] to expose a mapped [ReadKeyValueRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param mapper The [MapperRepo] providing bidirectional type conversions
* @return [MapperReadKeyValueRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadKeyValueRepo<FromKey, FromValue> = MapperReadKeyValueRepo(this, mapper)
/**
* Wraps this [ReadKeyValueRepo] with inline conversion lambdas to expose a mapped [ReadKeyValueRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param keyFromToTo Converts outer key to inner key; defaults to unchecked cast
* @param valueFromToTo Converts outer value to inner value; defaults to unchecked cast
* @param keyToToFrom Converts inner key to outer key; defaults to unchecked cast
* @param valueToToFrom Converts inner value to outer value; defaults to unchecked cast
* @return [MapperReadKeyValueRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
@@ -77,6 +112,18 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
/**
* Write-only key-value repository adapter that applies type mapping via [MapperRepo].
* Converts outer (From) key/value types to inner (To) types before delegating writes to [to],
* and maps emitted flow values back from inner to outer types.
*
* @param FromKey The outer key type exposed by this repo
* @param FromValue The outer value type exposed by this repo
* @param ToKey The inner key type used by the underlying [to] repo
* @param ToValue The inner value type used by the underlying [to] repo
* @param to The underlying [WriteKeyValueRepo] to delegate operations to
* @param mapper The [MapperRepo] providing bidirectional key/value type conversions
*/
open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
@@ -105,11 +152,34 @@ open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
)
}
/**
* Wraps this [WriteKeyValueRepo] with a [MapperRepo] to expose a mapped [WriteKeyValueRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param mapper The [MapperRepo] providing bidirectional type conversions
* @return [MapperWriteKeyValueRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteKeyValueRepo<FromKey, FromValue> = MapperWriteKeyValueRepo(this, mapper)
/**
* Wraps this [WriteKeyValueRepo] with inline conversion lambdas to expose a mapped [WriteKeyValueRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param keyFromToTo Converts outer key to inner key; defaults to unchecked cast
* @param valueFromToTo Converts outer value to inner value; defaults to unchecked cast
* @param keyToToFrom Converts inner key to outer key; defaults to unchecked cast
* @param valueToToFrom Converts inner value to outer value; defaults to unchecked cast
* @return [MapperWriteKeyValueRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
@@ -120,6 +190,17 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
/**
* Full key-value repository adapter that applies type mapping via [MapperRepo].
* Composes [MapperReadKeyValueRepo] and [MapperWriteKeyValueRepo] for read and write delegation.
*
* @param FromKey The outer key type exposed by this repo
* @param FromValue The outer value type exposed by this repo
* @param ToKey The inner key type used by the underlying [to] repo
* @param ToValue The inner value type used by the underlying [to] repo
* @param to The underlying [KeyValueRepo] to delegate operations to
* @param mapper The [MapperRepo] providing bidirectional key/value type conversions
*/
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: KeyValueRepo<ToKey, ToValue>,
@@ -133,11 +214,34 @@ open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
}
}
/**
* Wraps this [KeyValueRepo] with a [MapperRepo] to expose a mapped [KeyValueRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param mapper The [MapperRepo] providing bidirectional type conversions
* @return [MapperKeyValueRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): KeyValueRepo<FromKey, FromValue> = MapperKeyValueRepo(this, mapper)
/**
* Wraps this [KeyValueRepo] with inline conversion lambdas to expose a mapped [KeyValueRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param keyFromToTo Converts outer key to inner key; defaults to unchecked cast
* @param valueFromToTo Converts outer value to inner value; defaults to unchecked cast
* @param keyToToFrom Converts inner key to outer key; defaults to unchecked cast
* @param valueToToFrom Converts inner value to outer value; defaults to unchecked cast
* @return [MapperKeyValueRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },

View File

@@ -5,6 +5,18 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* Read-only one-to-many key-values repository adapter that applies type mapping via [MapperRepo].
* Converts outer (From) key/value types to inner (To) types before delegating to [to],
* and converts results back from inner to outer types.
*
* @param FromKey The outer key type exposed by this repo
* @param FromValue The outer value type exposed by this repo
* @param ToKey The inner key type used by the underlying [to] repo
* @param ToValue The inner value type used by the underlying [to] repo
* @param to The underlying [ReadKeyValuesRepo] to delegate operations to
* @param mapper The [MapperRepo] providing bidirectional key/value type conversions
*/
open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadKeyValuesRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
@@ -56,11 +68,34 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
override suspend fun count(k: FromKey): Long = to.count(k.toOutKey())
}
/**
* Wraps this [ReadKeyValuesRepo] with a [MapperRepo] to expose a mapped [ReadKeyValuesRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param mapper The [MapperRepo] providing bidirectional type conversions
* @return [MapperReadKeyValuesRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadKeyValuesRepo<FromKey, FromValue> = MapperReadKeyValuesRepo(this, mapper)
/**
* Wraps this [ReadKeyValuesRepo] with inline conversion lambdas to expose a mapped [ReadKeyValuesRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param keyFromToTo Converts outer key to inner key; defaults to unchecked cast
* @param valueFromToTo Converts outer value to inner value; defaults to unchecked cast
* @param keyToToFrom Converts inner key to outer key; defaults to unchecked cast
* @param valueToToFrom Converts inner value to outer value; defaults to unchecked cast
* @return [MapperReadKeyValuesRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
@@ -71,6 +106,18 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
/**
* Write-only one-to-many key-values repository adapter that applies type mapping via [MapperRepo].
* Converts outer (From) key/value types to inner (To) types before delegating writes to [to],
* and maps emitted flow values back from inner to outer types.
*
* @param FromKey The outer key type exposed by this repo
* @param FromValue The outer value type exposed by this repo
* @param ToKey The inner key type used by the underlying [to] repo
* @param ToValue The inner value type used by the underlying [to] repo
* @param to The underlying [WriteKeyValuesRepo] to delegate operations to
* @param mapper The [MapperRepo] providing bidirectional key/value type conversions
*/
open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteKeyValuesRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
@@ -109,11 +156,34 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
override suspend fun clearWithValue(v: FromValue) = to.clearWithValue(v.toOutValue())
}
/**
* Wraps this [WriteKeyValuesRepo] with a [MapperRepo] to expose a mapped [WriteKeyValuesRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param mapper The [MapperRepo] providing bidirectional type conversions
* @return [MapperWriteKeyValuesRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteKeyValuesRepo<FromKey, FromValue> = MapperWriteKeyValuesRepo(this, mapper)
/**
* Wraps this [WriteKeyValuesRepo] with inline conversion lambdas to expose a mapped [WriteKeyValuesRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param keyFromToTo Converts outer key to inner key; defaults to unchecked cast
* @param valueFromToTo Converts outer value to inner value; defaults to unchecked cast
* @param keyToToFrom Converts inner key to outer key; defaults to unchecked cast
* @param valueToToFrom Converts inner value to outer value; defaults to unchecked cast
* @return [MapperWriteKeyValuesRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
@@ -124,6 +194,17 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
/**
* Full one-to-many key-values repository adapter that applies type mapping via [MapperRepo].
* Composes [MapperReadKeyValuesRepo] and [MapperWriteKeyValuesRepo] for read and write delegation.
*
* @param FromKey The outer key type exposed by this repo
* @param FromValue The outer value type exposed by this repo
* @param ToKey The inner key type used by the underlying [to] repo
* @param ToValue The inner value type used by the underlying [to] repo
* @param to The underlying [KeyValuesRepo] to delegate operations to
* @param mapper The [MapperRepo] providing bidirectional key/value type conversions
*/
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: KeyValuesRepo<ToKey, ToValue>,
@@ -133,11 +214,34 @@ open class MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
ReadKeyValuesRepo<FromKey, FromValue> by MapperReadKeyValuesRepo(to, mapper),
WriteKeyValuesRepo<FromKey, FromValue> by MapperWriteKeyValuesRepo(to, mapper)
/**
* Wraps this [KeyValuesRepo] with a [MapperRepo] to expose a mapped [KeyValuesRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param mapper The [MapperRepo] providing bidirectional type conversions
* @return [MapperKeyValuesRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): KeyValuesRepo<FromKey, FromValue> = MapperKeyValuesRepo(this, mapper)
/**
* Wraps this [KeyValuesRepo] with inline conversion lambdas to expose a mapped [KeyValuesRepo].
*
* @param FromKey The outer key type
* @param FromValue The outer value type
* @param ToKey The inner key type
* @param ToValue The inner value type
* @param keyFromToTo Converts outer key to inner key; defaults to unchecked cast
* @param valueFromToTo Converts outer value to inner value; defaults to unchecked cast
* @param keyToToFrom Converts inner key to outer key; defaults to unchecked cast
* @param valueToToFrom Converts inner value to outer value; defaults to unchecked cast
* @return [MapperKeyValuesRepo] wrapping this repo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },

View File

@@ -5,6 +5,17 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadCRUDRepo
/**
* Retrieves all items from a [ReadCRUDRepo] by iterating through pages starting from the given [pagination].
* Uses the provided [methodCaller] to fetch each page.
*
* @param T The type of objects in the repository
* @param ID The type of IDs in the repository
* @param REPO The specific repository type
* @param pagination The starting pagination parameters
* @param methodCaller A function that fetches a page of results from the repository
* @return A list of all items across all pages
*/
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
pagination: Pagination,
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
@@ -13,6 +24,16 @@ suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
methodCaller(this, it)
}
/**
* Retrieves all items from a [ReadCRUDRepo] by iterating through all pages.
* Uses [maxPagePagination] to determine the starting pagination and the provided [methodCaller] to fetch each page.
*
* @param T The type of objects in the repository
* @param ID The type of IDs in the repository
* @param REPO The specific repository type
* @param methodCaller A function that fetches a page of results from the repository
* @return A list of all items across all pages
*/
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>

View File

@@ -5,6 +5,17 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
/**
* Retrieves all key-value pairs from a [ReadKeyValueRepo] by iterating pages starting from [pagination].
* Uses [methodCaller] to fetch each page of keys, then resolves each key to its value via [ReadKeyValueRepo.get].
*
* @param Key The type of keys in the repository
* @param Value The type of values in the repository
* @param REPO The specific repository type
* @param pagination The starting pagination parameters
* @param methodCaller A function that fetches a page of keys from the repository
* @return List of all key-value pairs across all pages; entries with null values are excluded
*/
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
pagination: Pagination,
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
@@ -16,6 +27,16 @@ suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll
)
}
/**
* Retrieves all key-value pairs from a [ReadKeyValueRepo] by iterating all pages.
* Uses [maxPagePagination] as the starting pagination and [methodCaller] to fetch each page of keys.
*
* @param Key The type of keys in the repository
* @param Value The type of values in the repository
* @param REPO The specific repository type
* @param methodCaller A function that fetches a page of keys from the repository
* @return List of all key-value pairs across all pages; entries with null values are excluded
*/
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>

View File

@@ -6,6 +6,26 @@ import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
/**
* Creates a pagination starting from the first page with size equal to the total count of items in this [ReadCRUDRepo].
* This effectively creates a single page containing all items.
*
* @return A [FirstPagePagination] with size equal to the repository count
*/
suspend inline fun ReadCRUDRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt())
/**
* Creates a pagination starting from the first page with size equal to the total count of items in this [ReadKeyValueRepo].
* This effectively creates a single page containing all items.
*
* @return A [FirstPagePagination] with size equal to the repository count
*/
suspend inline fun ReadKeyValueRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt())
/**
* Creates a pagination starting from the first page with size equal to the total count of items in this [ReadKeyValuesRepo].
* This effectively creates a single page containing all items.
*
* @return A [FirstPagePagination] with size equal to the repository count
*/
suspend inline fun ReadKeyValuesRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt())

View File

@@ -5,6 +5,17 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
/**
* Retrieves all key-to-list-of-values pairs from a [ReadKeyValuesRepo] by iterating pages starting from [pagination].
* Uses [methodCaller] to fetch each page of keys, then resolves all values per key via [ReadKeyValuesRepo.getAll].
*
* @param Key The type of keys in the repository
* @param Value The type of values associated with keys
* @param REPO The specific repository type
* @param pagination The starting pagination parameters
* @param methodCaller A function that fetches a page of keys from the repository
* @return List of key-to-list-of-values pairs across all pages
*/
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
pagination: Pagination,
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
@@ -18,6 +29,16 @@ suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAl
)
}
/**
* Retrieves all key-to-list-of-values pairs from a [ReadKeyValuesRepo] by iterating all pages.
* Uses [maxPagePagination] as the starting pagination and [methodCaller] to fetch each page of keys.
*
* @param Key The type of keys in the repository
* @param Value The type of values associated with keys
* @param REPO The specific repository type
* @param methodCaller A function that fetches a page of keys from the repository
* @return List of key-to-list-of-values pairs across all pages
*/
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>

View File

@@ -7,4 +7,11 @@ import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import kotlin.js.JsName
import kotlin.jvm.JvmName
/**
* Wraps this [ReadKeyValueRepo] as a [ReadCRUDFromKeyValueRepo], exposing CRUD read operations.
*
* @param K The type of keys (used as IDs in the CRUD repo)
* @param V The type of values (used as objects in the CRUD repo)
* @return [ReadCRUDFromKeyValueRepo] delegating to this repo
*/
fun <K, V> ReadKeyValueRepo<K, V>.asReadCRUDRepo() = ReadCRUDFromKeyValueRepo(this)

View File

@@ -5,6 +5,14 @@ import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
/**
* Adapter that exposes a [ReadKeyValueRepo] as a [ReadCRUDRepo].
* Maps CRUD read operations to the underlying key-value repository operations.
*
* @param RegisteredType The type of objects stored in the repository
* @param IdType The type of identifiers (keys) used to reference stored objects
* @param original The underlying [ReadKeyValueRepo] to delegate operations to
*/
open class ReadCRUDFromKeyValueRepo<RegisteredType, IdType>(
protected open val original: ReadKeyValueRepo<IdType, RegisteredType>
) : ReadCRUDRepo<RegisteredType, IdType> {

View File

@@ -8,8 +8,32 @@ import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import kotlin.js.JsName
import kotlin.jvm.JvmName
/**
* Wraps this [ReadKeyValuesRepo] as a [ReadKeyValueFromKeyValuesRepo],
* exposing each key mapped to a [List] of all associated values.
*
* @param K The type of keys
* @param V The type of individual values
* @return [ReadKeyValueFromKeyValuesRepo] delegating to this repo
*/
fun <K, V> ReadKeyValuesRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromKeyValuesRepo(this)
/**
* Wraps this [KeyValuesRepo] as a [KeyValueFromKeyValuesRepo],
* exposing a full read-write key-value interface where each key maps to a [List] of values.
*
* @param K The type of keys
* @param V The type of individual values
* @return [KeyValueFromKeyValuesRepo] delegating to this repo
*/
fun <K, V> KeyValuesRepo<K, V>.asKeyValueRepo() = KeyValueFromKeyValuesRepo(this)
/**
* Wraps this [ReadCRUDRepo] as a [ReadKeyValueFromCRUDRepo],
* exposing CRUD IDs as keys and CRUD objects as values in a [ReadKeyValueRepo].
*
* @param K The type of CRUD IDs (used as keys)
* @param V The type of CRUD objects (used as values)
* @return [ReadKeyValueFromCRUDRepo] delegating to this repo
*/
fun <K, V> ReadCRUDRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromCRUDRepo(this)

View File

@@ -18,6 +18,16 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
/**
* Full read-write adapter that exposes a [KeyValuesRepo] as a [KeyValueRepo] mapping each key to a [List] of values.
* Extends [ReadKeyValueFromKeyValuesRepo] with write operations delegated to the underlying [KeyValuesRepo].
* [onNewValue] merges [KeyValuesRepo.onNewValue] and [KeyValuesRepo.onValueRemoved] and emits the updated list per key;
* [onValueRemoved] mirrors [KeyValuesRepo.onDataCleared].
*
* @param Key The type of keys
* @param Value The type of individual values in the one-to-many repo
* @param original The underlying [KeyValuesRepo] to delegate operations to
*/
open class KeyValueFromKeyValuesRepo<Key, Value>(
private val original: KeyValuesRepo<Key, Value>
) : KeyValueRepo<Key, List<Value>>, ReadKeyValueFromKeyValuesRepo<Key, Value>(original) {

View File

@@ -15,6 +15,15 @@ import dev.inmo.micro_utils.repos.pagination.getAll
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
import kotlin.jvm.JvmInline
/**
* Inline value class adapter that exposes a [ReadCRUDRepo] as a [ReadKeyValueRepo].
* Maps key-value read operations to the underlying CRUD repository operations,
* treating CRUD IDs as keys and CRUD objects as values.
*
* @param Key The type of keys (maps to [ReadCRUDRepo] ID type)
* @param Value The type of values (maps to [ReadCRUDRepo] object type)
* @param original The underlying [ReadCRUDRepo] to delegate operations to
*/
@JvmInline
value class ReadKeyValueFromCRUDRepo<Key, Value>(
private val original: ReadCRUDRepo<Value, Key>

View File

@@ -12,6 +12,14 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
/**
* Adapter that exposes a [ReadKeyValuesRepo] as a [ReadKeyValueRepo] mapping each key to a [List] of values.
* Each key's associated list of values is retrieved via [ReadKeyValuesRepo.getAll].
*
* @param Key The type of keys
* @param Value The type of individual values in the one-to-many repo
* @param original The underlying [ReadKeyValuesRepo] to delegate operations to
*/
open class ReadKeyValueFromKeyValuesRepo<Key, Value>(
private val original: ReadKeyValuesRepo<Key, Value>
) : ReadKeyValueRepo<Key, List<Value>> {

View File

@@ -5,17 +5,51 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import kotlin.js.JsName
import kotlin.jvm.JvmName
/**
* Wraps this [ReadKeyValueRepo] (mapping keys to iterables) as a [ReadKeyValuesFromKeyValueRepo],
* exposing a one-to-many read interface.
*
* @param K The type of keys
* @param V The type of individual values within each iterable
* @param VI The iterable type storing multiple values per key
* @return [ReadKeyValuesFromKeyValueRepo] delegating to this repo
*/
fun <K, V, VI : Iterable<V>> ReadKeyValueRepo<K, VI>.asReadKeyValuesRepo() = ReadKeyValuesFromKeyValueRepo(this)
/**
* Wraps this [KeyValueRepo] (mapping keys to iterables) as a [KeyValuesFromKeyValueRepo],
* exposing a full one-to-many read-write interface.
*
* @param K The type of keys
* @param V The type of individual values within each iterable
* @param VI The iterable type storing multiple values per key
* @param listToValuesIterable Converter from [List] of values to [VI] used when persisting changes
* @return [KeyValuesFromKeyValueRepo] delegating to this repo
*/
fun <K, V, VI : Iterable<V>> KeyValueRepo<K, VI>.asKeyValuesRepo(
listToValuesIterable: suspend (List<V>) -> VI
): KeyValuesFromKeyValueRepo<K, V, VI> = KeyValuesFromKeyValueRepo(this, listToValuesIterable)
/**
* Wraps this [KeyValueRepo] (mapping keys to [List]s) as a [KeyValuesFromKeyValueRepo].
* Uses identity conversion for the list iterable.
*
* @param K The type of keys
* @param V The type of individual values
* @return [KeyValuesFromKeyValueRepo] delegating to this repo with [List] as the iterable type
*/
@JvmName("asListKeyValuesRepo")
@JsName("asListKeyValuesRepo")
fun <K, V> KeyValueRepo<K, List<V>>.asKeyValuesRepo(): KeyValuesFromKeyValueRepo<K, V, List<V>> = asKeyValuesRepo { it }
/**
* Wraps this [KeyValueRepo] (mapping keys to [Set]s) as a [KeyValuesFromKeyValueRepo].
* Converts lists to sets when persisting changes, ensuring value uniqueness per key.
*
* @param K The type of keys
* @param V The type of individual values
* @return [KeyValuesFromKeyValueRepo] delegating to this repo with [Set] as the iterable type
*/
@JvmName("asSetKeyValuesRepo")
@JsName("asSetKeyValuesRepo")
fun <K, V> KeyValueRepo<K, Set<V>>.asKeyValuesRepo(): KeyValuesFromKeyValueRepo<K, V, Set<V>> = asKeyValuesRepo { it.toSet() }

View File

@@ -12,6 +12,17 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlin.js.JsName
import kotlin.jvm.JvmName
/**
* Full read-write adapter that exposes a [KeyValueRepo] storing iterables as a [KeyValuesRepo].
* Extends [ReadKeyValuesFromKeyValueRepo] with write operations: add, remove, clear.
* Emits [onNewValue] and [onValueRemoved] for individual value changes; [onDataCleared] mirrors [KeyValueRepo.onValueRemoved].
*
* @param Key The type of keys
* @param Value The type of individual values within each iterable
* @param ValuesIterable The iterable type storing multiple values per key
* @param original The underlying [KeyValueRepo] mapping keys to iterables of values
* @param listToValuesIterable Converter from [List] of values to [ValuesIterable] used when persisting changes
*/
open class KeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value>>(
private val original: KeyValueRepo<Key, ValuesIterable>,
private val listToValuesIterable: suspend (List<Value>) -> ValuesIterable

View File

@@ -11,6 +11,15 @@ import dev.inmo.micro_utils.pagination.utils.paginate
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
/**
* Adapter that exposes a [ReadKeyValueRepo] storing iterables as a [ReadKeyValuesRepo].
* Each key maps to a [ValuesIterable] in the underlying repo, which is exposed as a one-to-many relationship.
*
* @param Key The type of keys
* @param Value The type of individual values within each iterable
* @param ValuesIterable The iterable type storing multiple values per key
* @param original The underlying [ReadKeyValueRepo] mapping keys to iterables of values
*/
open class ReadKeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value>>(
private val original: ReadKeyValueRepo<Key, ValuesIterable>
) : ReadKeyValuesRepo<Key, Value> {

View File

@@ -3,6 +3,14 @@ package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.set
/**
* [StandardVersionsRepoProxy] implementation backed by a [KeyValueRepo] mapping table names to version numbers.
* Stores and retrieves per-table version integers using [keyValueStore] with table names as keys.
*
* @param T The type of the underlying database or storage object
* @param keyValueStore [KeyValueRepo] used to persist table-name-to-version mappings
* @param database The underlying database or storage object exposed via [StandardVersionsRepoProxy.database]
*/
class KeyValueBasedVersionsRepoProxy<T>(
private val keyValueStore: KeyValueRepo<String, Int>,
override val database: T

View File

@@ -2,13 +2,43 @@ package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.Repo
/**
* Proxy interface providing low-level access to a versioned database [T].
* Implementations store and retrieve per-table version numbers using a backing storage.
*
* @param T The type of the underlying database or storage object
*/
interface StandardVersionsRepoProxy<T> : Repo {
/**
* The underlying database or storage object used for version tracking.
*/
val database: T
/**
* Returns the current version number for the given [tableName], or null if no version is stored.
*
* @param tableName Name of the table whose version to retrieve
* @return Stored version number, or null if the table has not been versioned yet
*/
suspend fun getTableVersion(tableName: String): Int?
/**
* Persists the given [version] number for the given [tableName].
*
* @param tableName Name of the table whose version to update
* @param version New version number to store
*/
suspend fun updateTableVersion(tableName: String, version: Int)
}
/**
* Standard implementation of [VersionsRepo] that delegates version storage to a [StandardVersionsRepoProxy].
* On [setTableVersion]: calls [StandardVersionsRepoProxy.database].[onCreate] if the table has no version yet,
* then iterates [onUpdate] for each version step until the target [version] is reached.
*
* @param T The type of the underlying database or storage object
* @param proxy The [StandardVersionsRepoProxy] used to read and write version numbers
*/
class StandardVersionsRepo<T>(
private val proxy: StandardVersionsRepoProxy<T>
) : VersionsRepo<T> {
@@ -30,4 +60,4 @@ class StandardVersionsRepo<T>(
proxy.updateTableVersion(tableName, currentVersion)
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More