Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 62 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# KompKit

[![Version](https://img.shields.io/badge/version-0.3.0--alpha.0-orange.svg)](https://github.com/Kompkit/KompKit/releases)
[![Version](https://img.shields.io/badge/version-0.4.0--alpha.0-orange.svg)](https://github.com/Kompkit/KompKit/releases)
[![Web CI](https://github.com/Kompkit/KompKit/actions/workflows/web.yml/badge.svg?branch=develop)](https://github.com/Kompkit/KompKit/actions/workflows/web.yml)
[![Kotlin CI](https://github.com/Kompkit/KompKit/actions/workflows/android.yml/badge.svg?branch=develop)](https://github.com/Kompkit/KompKit/actions/workflows/android.yml)
[![Flutter CI](https://github.com/Kompkit/KompKit/actions/workflows/flutter.yml/badge.svg?branch=develop)](https://github.com/Kompkit/KompKit/actions/workflows/flutter.yml)
Expand Down Expand Up @@ -60,6 +60,8 @@ KompKit provides essential utility functions that work seamlessly across Web (Ty
- **🕐 debounce** - Delay function execution until after a wait period (prevents excessive API calls)
- **📧 isEmail** - Validate email addresses with robust regex patterns
- **💰 formatCurrency** - Format numbers as currency with full locale support
- **📐 clamp** - Constrain a number within an inclusive [min, max] range
- **⏱️ throttle** - Limit a function to execute at most once per wait period

### Key Features

Expand Down Expand Up @@ -95,7 +97,7 @@ Add to your `pubspec.yaml`:

```yaml
dependencies:
kompkit_core: ^0.3.0-alpha.1
kompkit_core: ^0.4.0-alpha.0
```

Then run:
Expand Down Expand Up @@ -129,40 +131,80 @@ Once installed, you can import and use KompKit utilities:
**TypeScript/JavaScript:**

```typescript
import { debounce, isEmail, formatCurrency } from "kompkit-core";
import {
debounce,
isEmail,
formatCurrency,
clamp,
throttle,
} from "kompkit-core";

// Delay execution until typing stops
const onSearch = debounce(
(query: string) => console.log("Search:", query),
300,
);

// Validate email
console.log(isEmail("user@example.com")); // true

const search = debounce((query: string) => {
console.log("Searching:", query);
}, 300);
// Format as currency
console.log(formatCurrency(1234.56)); // "$1,234.56"

console.log(isEmail("user@example.com")); // true
console.log(formatCurrency(1234.56)); // "1.234,56 €"
// Constrain a value to a range
console.log(clamp(15, 0, 10)); // 10

// Rate-limit a scroll handler
const onScroll = throttle(() => console.log("scrollY:", window.scrollY), 200);
window.addEventListener("scroll", onScroll);
```

**Kotlin:**

```kotlin
import com.kompkit.core.*

val search = debounce<String>(300L, scope) { query ->
println("Searching: $query")
}
// Delay execution until typing stops
val onSearch = debounce<String>(300L, scope) { query -> println("Search: $query") }

// Validate email
println(isEmail("user@example.com")) // true
println(formatCurrency(1234.56)) // "1.234,56 €"

// Format as currency
println(formatCurrency(1234.56)) // "$1,234.56"

// Constrain a value to a range
println(clamp(15.0, 0.0, 10.0)) // 10.0

// Rate-limit a scroll handler
val onScroll = throttle<Int>(200L, scope) { pos -> println("scroll: $pos") }
```

**Dart/Flutter:**

```dart
import 'package:kompkit_core/kompkit_core.dart';

final search = debounce<String>((String query) {
print('Searching: $query');
}, const Duration(milliseconds: 300));
// Delay execution until typing stops
final onSearch = debounce<String>(
(query) => print('Search: $query'),
const Duration(milliseconds: 300),
);

// Validate email
print(isEmail('user@example.com')); // true
print(formatCurrency(1234.56)); // "1.234,56 €"

// Format as currency
print(formatCurrency(1234.56)); // "$1,234.56"

// Constrain a value to a range
print(clamp(15.0, 0.0, 10.0)); // 10.0

// Rate-limit a scroll handler
final onScroll = throttle<double>(
(offset) => print('scroll: $offset'),
const Duration(milliseconds: 200),
);
```

## Documentation
Expand Down Expand Up @@ -211,7 +253,7 @@ KompKit/

## Version Information

- **Current Version**: `0.3.0-alpha`
- **Current Version**: `0.4.0-alpha.0`
- **Minimum Requirements**:
- Node.js 20+ (Web)
- JDK 17+ (Android)
Expand All @@ -225,7 +267,7 @@ KompKit/
KompKit is currently in **alpha**. This means:

- **APIs may change** between alpha versions without a deprecation period.
- **Pin to exact versions** in production: `"kompkit-core": "0.3.0-alpha.1"` / `kompkit_core: 0.3.0-alpha.1`.
- **Pin to exact versions** in production: `"kompkit-core": "0.4.0-alpha.0"` / `kompkit_core: 0.4.0-alpha.0`.
- **Breaking changes** will be documented in [CHANGELOG.md](./docs/CHANGELOG.md) with migration notes.
- Once `1.0.0` is released, the project will follow strict [Semantic Versioning](https://semver.org/): breaking changes only in major versions.

Expand All @@ -237,6 +279,8 @@ KompKit aims for **conceptual parity**, not syntactic identity. The following di
| ---------------- | -------- | -------------------------------------------------------------- | ----------------------------------------------------------- |
| `debounce` | Kotlin | Requires `CoroutineScope` parameter | Structured concurrency — no global timer API on JVM |
| `debounce` | Kotlin | Action is first parameter, scope is last | Enables idiomatic trailing lambda syntax |
| `throttle` | Kotlin | Requires `CoroutineScope` parameter | Same structured concurrency constraint as `debounce` |
| `throttle` | Dart | `wait` is a `Duration`, not a number | Idiomatic Dart — no bare millisecond integers |
| `formatCurrency` | Kotlin | Accepts `String` locale, converts to `Locale` internally | JVM `NumberFormat` requires `java.util.Locale` |
| `formatCurrency` | Dart | Accepts BCP 47 locale, normalizes hyphen→underscore internally | `intl` package uses underscore-separated locale identifiers |

Expand Down
3 changes: 2 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

| Version | Supported |
| --------------- | ---------------------- |
| `0.3.0-alpha.1` | ✅ Current |
| `0.4.0-alpha.0` | ✅ Current |
| `0.3.0-alpha.1` | ❌ No longer supported |
| `0.2.0-alpha.0` | ❌ No longer supported |
| `0.1.0-alpha` | ❌ No longer supported |

Expand Down
73 changes: 62 additions & 11 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ All modules implement identical functionality:
- **debounce**: Function execution delay with cancellation
- **isEmail**: Email validation using regex patterns
- **formatCurrency**: Localized currency formatting
- **clamp**: Constrain a number within an inclusive [min, max] range
- **throttle**: Limit function execution to at most once per wait period

## API Parity Contract

This section defines the formal contract for cross-platform API consistency in KompKit.

### What is Guaranteed

- **Function names** are identical across all platforms (`debounce`, `isEmail`, `formatCurrency`).
- **Function names** are identical across all platforms (`debounce`, `isEmail`, `formatCurrency`, `clamp`, `throttle`).
- **Behavioral semantics** are identical: given the same inputs, all platforms produce the same observable output.
- **Default values** are identical: `wait = 250ms`, `currency = "EUR"`, `locale = "en-US"`.
- **Default values** are identical: `wait = 250ms`, `currency = "USD"`, `locale = "en-US"`.
- **Error handling philosophy** is consistent: invalid inputs that cannot produce a meaningful result throw/throw-equivalent errors. Silent fallbacks are not permitted.
- **Cancel capability**: `debounce` returns an object with a `cancel()` method on all platforms, allowing callers to discard pending executions (required for safe use in component lifecycles).

Expand All @@ -70,9 +72,11 @@ This section defines the formal contract for cross-platform API consistency in K
Every utility follows the same mental model regardless of platform:

```
debounce(action, options) → Debounced<T> (with .cancel())
isEmail(value) → Boolean
formatCurrency(amount, options) → String
debounce(action, options) → Debounced<T> (with .cancel())
isEmail(value) → Boolean
formatCurrency(amount, options) → String
clamp(value, min, max) → Number
throttle(fn, wait) → Throttled<T> (with .cancel())
```

A developer familiar with the TypeScript API should be able to use the Kotlin or Dart API with only idiomatic adjustments — not conceptual re-learning.
Expand Down Expand Up @@ -120,9 +124,21 @@ export function isEmail(value: string): boolean;

export function formatCurrency(
amount: number,
currency?: string, // default: "EUR"
currency?: string, // default: "USD"
locale?: string, // default: "en-US"
): string;

export function clamp(value: number, min: number, max: number): number;

export interface Throttled<T extends (...args: any[]) => void> {
(...args: Parameters<T>): void;
cancel(): void;
}

export function throttle<T extends (...args: any[]) => void>(
fn: T,
wait: number, // must be > 0
): Throttled<T>;
```

**Kotlin:**
Expand All @@ -143,9 +159,22 @@ fun isEmail(value: String): Boolean

fun formatCurrency(
amount: Double,
currency: String = "EUR",
currency: String = "USD",
locale: String = "en-US", // converted internally to java.util.Locale
): String

fun clamp(value: Double, min: Double, max: Double): Double

class Throttled<T>(private val invoke: (T) -> Unit) {
operator fun invoke(value: T): Unit
fun cancel(): Unit
}

fun <T> throttle(
waitMs: Long, // must be > 0
scope: CoroutineScope, // platform constraint: structured concurrency
action: (T) -> Unit,
): Throttled<T>
```

**Dart:**
Expand All @@ -165,9 +194,21 @@ bool isEmail(String value);

String formatCurrency(
num amount, {
String currency = "EUR",
String currency = "USD",
String locale = "en-US",
});

double clamp(double value, double min, double max);

class Throttled<T> {
void call(T arg);
void cancel();
}

Throttled<T> throttle<T>(
void Function(T) fn,
Duration wait, // must be > Duration.zero
);
```

### Platform-Specific Adaptations
Expand All @@ -180,20 +221,26 @@ While maintaining API consistency, we leverage platform strengths:
- **setTimeout/clearTimeout** for timing control
- **Intl.NumberFormat** for currency formatting
- **RegExp** for email validation
- **Math.min/Math.max** for clamp
- **setTimeout/clearTimeout** for throttle timer

#### Kotlin Implementation

- **Coroutines** for asynchronous debounce operations
- **Job cancellation** for timing control
- **NumberFormat/Currency** for localized formatting
- **Regex** for email validation
- **Double.coerceIn** for clamp
- **Coroutine delay + Job** for throttle wait period

#### Dart/Flutter Implementation

- **Timer** for debounce scheduling and cancellation
- **intl package** (`NumberFormat.currency`) for localized formatting
- **RegExp** for email validation
- **Null safety** with full type-safe APIs
- **num.clamp** for clamp
- **Timer** for throttle scheduling (same as debounce)

## Build System Architecture

Expand Down Expand Up @@ -272,16 +319,20 @@ android.yml:

```
packages/core/web/tests/
└── core.test.ts # All utility tests
├── core.test.ts # debounce, isEmail, formatCurrency tests
├── clamp.test.ts # clamp unit tests
└── throttle.test.ts # throttle unit tests

packages/core/android/src/test/kotlin/com/kompkit/core/
└── CoreTests.kt # All utility tests
└── CoreTests.kt # All utility tests (incl. ThrottleTests, ClampTests)

packages/core/flutter/test/
├── kompkit_core_test.dart # Integration tests
├── debounce_test.dart # Debounce unit tests
├── validate_test.dart # Validation unit tests
└── format_test.dart # Formatting unit tests
├── format_test.dart # Formatting unit tests
├── clamp_test.dart # Clamp unit tests
└── throttle_test.dart # Throttle unit tests
```

### Test Coverage
Expand Down
36 changes: 36 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.0-alpha.0] - 2026-02-28

### Added

- **`clamp` utility** across all platforms — constrain a number within an inclusive `[min, max]` range
- TypeScript: `clamp(value: number, min: number, max: number): number`
- Kotlin: `clamp(value: Double, min: Double, max: Double): Double`
- Dart: `clamp(double value, double min, double max): double`
- Throws `RangeError` / `IllegalArgumentException` / `ArgumentError` if `min > max` or any argument is non-finite
- Exported from all public entry points; full unit test coverage on all platforms

- **`throttle` utility** across all platforms — limit a function to execute at most once per wait period
- TypeScript: `throttle<T>(fn: T, wait: number): T & { cancel(): void }`
- Kotlin: `throttle<T>(waitMs: Long, scope: CoroutineScope, action: (T) -> Unit): Throttled<T>`
- Dart: `throttle<T>(void Function(T) fn, Duration wait): Throttled<T>`
- First call executes immediately; subsequent calls within the wait period are ignored
- `cancel()` resets internal state with no pending execution firing
- Throws `Error` / `IllegalArgumentException` / `ArgumentError` if `wait <= 0`
- Exported from all public entry points; full unit test coverage on all platforms

### Changed

- **`formatCurrency` default currency**: Changed from `EUR` to `USD` across all platforms (TypeScript, Kotlin, Dart)
- **`formatCurrency` finite validation**: Added validation that throws on `NaN` and `Infinity` amounts across all platforms
- TypeScript: throws `RangeError`
- Kotlin: throws `IllegalArgumentException`
- Dart: throws `ArgumentError`
- **Documentation**: Updated all guides (getting-started, web, android, flutter, ARCHITECTURE, recipes, FAQ) to cover `clamp` and `throttle`
- **Documentation**: Corrected `formatCurrency` examples to reflect `USD` default
- **Documentation**: Added Platform Differences section to npm README documenting Dart single-argument debounce limitation and other divergences
- **Packaging**: Clarified that `kompkit-core` ships both ESM and CommonJS builds (not ESM-only)

### Fixed

- Documentation inconsistency where examples showed `USD` but default was `EUR`

## [0.3.0-alpha] - 2026-02-09

### Changed
Expand Down
Loading
Loading