generate docs for a lot of API (test try)

This commit is contained in:
2026-02-24 18:18:10 +06:00
parent 3df90b1993
commit 4f270d9047
81 changed files with 2519 additions and 6 deletions

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

@@ -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

@@ -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,39 +97,84 @@ 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>
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(

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,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

@@ -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,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

@@ -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

@@ -2,16 +2,77 @@ package dev.inmo.micro_utils.safe_wrapper
import dev.inmo.micro_utils.coroutines.runCatchingSafely
/**
* A wrapper interface that provides safe and unsafe access to a target object.
* Safe methods wrap operations in [Result] to catch and handle exceptions,
* while unsafe methods execute directly and may throw exceptions.
*
* @param T The type of the wrapped target object
*/
interface SafeWrapper<T> {
/**
* Executes a synchronous block on the target, catching any exceptions and returning a [Result].
*
* @param R The return type of the block
* @param block The operation to execute on the target
* @return A [Result] containing either the successful result or the caught exception
*/
fun <R> safe(block: T.() -> R): Result<R> = unsafeTarget().runCatching(block)
/**
* Executes a synchronous block on the target without exception handling.
*
* @param R The return type of the block
* @param block The operation to execute on the target
* @return The direct result of the block
* @throws Throwable If the block throws an exception
*/
fun <R> unsafe(block: T.() -> R): R = unsafeTarget().block()
/**
* Executes a suspending block on the target, catching any exceptions (except [kotlinx.coroutines.CancellationException])
* and returning a [Result].
*
* @param R The return type of the block
* @param block The suspending operation to execute on the target
* @return A [Result] containing either the successful result or the caught exception
*/
suspend fun <R> safeS(block: suspend T.() -> R): Result<R> = unsafeTarget().runCatchingSafely(block = block)
/**
* Executes a suspending block on the target without exception handling.
*
* @param R The return type of the block
* @param block The suspending operation to execute on the target
* @return The direct result of the block
* @throws Throwable If the block throws an exception
*/
suspend fun <R> unsafeS(block: suspend T.() -> R): R = unsafeTarget().block()
/**
* Provides access to the underlying wrapped target object.
*
* @return The target object
*/
fun unsafeTarget(): T
class Default<T>(private val t: T) : SafeWrapper<T> { override fun unsafeTarget(): T = t }
/**
* Default implementation of [SafeWrapper] that wraps a provided target.
*
* @param T The type of the wrapped target
*/
class Default<T>(private val t: T) : SafeWrapper<T> {
override fun unsafeTarget(): T = t
}
companion object {
/**
* Creates a [SafeWrapper] instance wrapping the provided target.
*
* @param T The type of the target
* @param t The target object to wrap
* @return A [SafeWrapper.Default] instance
*/
operator fun <T> invoke(t: T) = Default(t)
}
}

View File

@@ -5,6 +5,14 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* A serializer that encodes values to Base64 strings and decodes them back.
* Uses custom conversion functions to transform between type [T] and string representation.
*
* @param T The type to serialize/deserialize
* @param converterFrom Converts from type [T] to a string representation
* @param converterTo Converts from a string representation back to type [T]
*/
open class Base64Serializer<T>(
private val converterFrom: (T) -> String,
private val converterTo: (String) -> T,
@@ -14,5 +22,12 @@ open class Base64Serializer<T>(
override fun serialize(encoder: Encoder, value: T) = Base64BytesToFromStringSerializer.serialize(encoder, converterFrom(value).encodeToByteArray())
}
/**
* Serializer for [String] values encoded as Base64.
*/
object Base64StringSerializer : Base64Serializer<String>({ it }, { it })
/**
* Serializer for [ByteArray] values encoded as Base64.
*/
object Base64ByteArraySerializer : Base64Serializer<ByteArray>({ it.decodeToString() }, { it.encodeToByteArray() })

View File

@@ -6,6 +6,17 @@ import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlin.reflect.KClass
/**
* A serializer that includes type information in the serialized output.
* This allows polymorphic serialization where the exact type is preserved in the output
* as a "type" field alongside the "value" field.
*
* The serialized format is: `{"type": "TypeName", "value": {...}}`
*
* @param T The base type that this serializer handles
* @param kClass The Kotlin class of the base type
* @param presetSerializers A map of type names to their serializers for known subtypes
*/
open class TypedSerializer<T : Any>(
kClass: KClass<T>,
presetSerializers: Map<String, KSerializer<out T>> = emptyMap(),
@@ -60,29 +71,67 @@ open class TypedSerializer<T : Any>(
}
/**
* Adds a new type to this serializer with its associated serializer.
*
* @param O The specific subtype to include
* @param type The type name to use in serialized output
* @param serializer The serializer for this type
*/
open fun <O: T> include(type: String, serializer: KSerializer<O>) {
serializers[type] = serializer
}
/**
* Removes a type from this serializer by its name.
*
* @param type The type name to remove
*/
open fun exclude(type: String) {
serializers.remove(type)
}
}
/**
* Adds a type to this [TypedSerializer] using its class name as the type identifier.
*
* @param T The type to add
* @param kClass The Kotlin class to add
*/
@InternalSerializationApi
operator fun <T : Any> TypedSerializer<T>.plusAssign(kClass: KClass<T>) {
include(kClass.simpleName!!, kClass.serializer())
}
/**
* Removes a type from this [TypedSerializer] using its class name.
*
* @param T The type to remove
* @param kClass The Kotlin class to remove
*/
@InternalSerializationApi
operator fun <T : Any> TypedSerializer<T>.minusAssign(kClass: KClass<T>) {
exclude(kClass.simpleName!!)
}
/**
* Creates a [TypedSerializer] for the reified type [T].
*
* @param T The base type to serialize
* @param presetSerializers A map of type names to serializers for known subtypes
* @return A new [TypedSerializer] instance
*/
inline fun <reified T : Any> TypedSerializer(
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
) = TypedSerializer(T::class, presetSerializers)
/**
* Creates a [TypedSerializer] for the reified type [T] with vararg preset serializers.
*
* @param T The base type to serialize
* @param presetSerializers Pairs of type names to serializers for known subtypes
* @return A new [TypedSerializer] instance
*/
inline fun <reified T : Any> TypedSerializer(
vararg presetSerializers: Pair<String, KSerializer<out T>>
) = TypedSerializer(presetSerializers.toMap())

View File

@@ -1,6 +1,14 @@
package dev.inmo.micro_utils.transactions
/**
* Type alias for a suspending rollback lambda that receives the error that triggered the rollback.
*/
typealias TransactionDSLRollbackLambda = suspend (Throwable) -> Unit
/**
* DSL context for defining transactional operations with automatic rollback on failure.
* This class manages a list of rollback actions that will be executed in reverse order if an error occurs.
*/
class TransactionsDSL internal constructor() {
internal val rollbackActions = ArrayList<TransactionDSLRollbackLambda>()
@@ -8,6 +16,15 @@ class TransactionsDSL internal constructor() {
rollbackActions.add(rollbackAction)
}
}
/**
* Context provided to rollback actions, containing both the successful result of the action
* and the error that triggered the rollback.
*
* @param T The type of the action result
* @param actionResult The result of the action that succeeded before the transaction failed
* @param error The throwable that caused the transaction to fail
*/
class RollbackContext<T> internal constructor (
val actionResult: T,
val error: Throwable