This document describes the architecture and design decisions behind KompKit, a cross-platform utility library for TypeScript, Kotlin, and Dart.
KompKit is designed with cross-platform compatibility as the primary goal. Every utility function must:
- Maintain API parity across TypeScript, Kotlin, and Dart implementations
- Provide identical behavior regardless of platform
- Use platform-native patterns while maintaining consistency
- Minimize dependencies to reduce bundle size and complexity
We use a monorepo approach to ensure consistency and simplify development:
KompKit/
├── packages/core/ # Core utility packages
│ ├── web/ # TypeScript/JavaScript implementation
│ ├── android/ # Kotlin JVM implementation
│ └── flutter/ # Dart/Flutter implementation
├── docs/ # Documentation and guides
├── .github/workflows/ # CI/CD pipelines
└── [root configuration] # Lerna, npm, Git configuration
| Module | Platform | Purpose | Technology Stack |
|---|---|---|---|
packages/core/web |
Web/Node.js | TypeScript utilities | TypeScript 5.7+, Vitest, TypeDoc |
packages/core/android |
JVM/Android | Kotlin utilities | Kotlin 2.3.0, JUnit, Dokka |
packages/core/flutter |
Flutter/Dart | Dart utilities | Dart 3.0+, Flutter Test, DartDoc |
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
This section defines the formal contract for cross-platform API consistency in KompKit.
- 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 = "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:
debouncereturns an object with acancel()method on all platforms, allowing callers to discard pending executions (required for safe use in component lifecycles).
- Parameter style: Named parameters (Dart), trailing lambdas (Kotlin), and positional parameters (TypeScript) are idiomatic per language and are not forced to match syntactically.
- Coroutine scope (Kotlin):
debouncerequires aCoroutineScopebecause Kotlin's async model mandates structured concurrency. This is a platform constraint, not an API inconsistency. The scope is the last parameter to allow trailing lambda syntax. - Type system expression: Dart uses
void Function(T)return types, Kotlin uses(T) -> Unit, TypeScript uses a typed wrapper object. All three express the same concept. - Locale string format: All platforms accept BCP 47 locale strings (e.g.,
"en-US"). Kotlin converts internally tojava.util.Localeas required by the JVM.
Every utility follows the same mental model regardless of platform:
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.
Any unavoidable divergence between platforms must be:
- Documented in this section.
- Explained with the platform constraint that necessitates it.
- Kept minimal — divergence is a cost, not a feature.
Current documented divergences:
| Function | Divergence | Reason |
|---|---|---|
debounce |
Kotlin requires CoroutineScope parameter |
Structured concurrency — no global timer API |
debounce |
Kotlin uses trailing lambda for action |
Idiomatic Kotlin; improves call-site readability |
formatCurrency |
Kotlin accepts String locale, converts to Locale internally |
JVM NumberFormat API requires java.util.Locale |
formatCurrency |
TypeScript (V8) and Kotlin (JVM) fall back on unknown locales; Dart throws | Intl.NumberFormat (V8) and Locale.forLanguageTag (JVM) are lenient; intl (Dart) calls verifiedLocale() which throws |
formatCurrency |
Dart validates currency format via regex (3 uppercase letters); Kotlin via Currency.getInstance; TypeScript via Intl.NumberFormat |
Platform APIs differ in how they enforce ISO 4217 — all throw on invalid codes |
We maintain conceptual API parity across platforms:
TypeScript:
interface Debounced<T extends (...args: any[]) => void> {
(...args: Parameters<T>): void;
cancel(): void;
}
export function debounce<T extends (...args: any[]) => void>(
fn: T,
wait?: number, // default: 250
): Debounced<T>;
export function isEmail(value: string): boolean;
export function formatCurrency(
amount: number,
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:
class Debounced<T>(private val action: (T) -> Unit) {
operator fun invoke(value: T): Unit
fun cancel(): Unit
}
fun <T> debounce(
action: (T) -> Unit,
waitMs: Long = 250L,
scope: CoroutineScope, // platform constraint: structured concurrency
): Debounced<T>
fun isEmail(value: String): Boolean
fun formatCurrency(
amount: Double,
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:
class Debounced<T> {
void call(T arg);
void cancel();
}
Debounced<T> debounce<T>(
void Function(T) action, [
Duration wait = const Duration(milliseconds: 250),
]);
bool isEmail(String value);
String formatCurrency(
num amount, {
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
);While maintaining API consistency, we leverage platform strengths:
- Closures for debounce state management
- 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
- 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
- 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)
Lerna + npm workspaces for unified dependency management:
{
"workspaces": ["packages/core/web"],
"devDependencies": {
"lerna": "^8.2.4",
"tsup": "^8.5.0",
"typescript": "^5.7.0",
"typedoc": "^0.28.16",
"vitest": "^3.0.0"
}
}| Platform | Build Tool | Output | Documentation |
|---|---|---|---|
| Web | tsup | ESM + CJS + Types | TypeDoc → docs/api/web/ |
| Kotlin | Gradle | JAR | Dokka → docs/api/android/ |
| Flutter | Flutter/Dart | Dart package | DartDoc → docs/api/flutter/ |
Zero Runtime Dependencies:
- Web utilities use only browser/Node.js built-ins
- Kotlin utilities use only JDK/Kotlin stdlib
- Development dependencies isolated to build process
Path-based optimization to minimize build times:
# Web CI - packages/core/web/**
web.yml:
- Node.js 20 setup
- npm ci (with caching)
- Lerna build/test
- TypeDoc generation
- Artifact upload
# Kotlin CI - packages/core/android/**
android.yml:
- JDK 17 setup
- Gradle build (with caching)
- ktlint + detekt
- JAR generation
- Dokka documentationAutomated quality assurance:
- Code formatting: ktlint (Kotlin), Prettier (TypeScript)
- Static analysis: detekt (Kotlin), ESLint (TypeScript)
- Testing: JUnit (Kotlin), Vitest (TypeScript)
- Documentation: Auto-generated API docs
- Build verification: Cross-platform compatibility
Platform-specific test suites with identical test cases:
packages/core/web/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 (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
├── clamp_test.dart # Clamp unit tests
└── throttle_test.dart # Throttle unit tests
100% coverage requirement across both platforms:
- Unit tests for all public APIs
- Edge case validation
- Error condition handling
- Performance benchmarks
Unified documentation strategy:
docs/
├── README_CI.md # CI/CD processes
├── CONTRIBUTING.md # Development guidelines
├── CHANGELOG.md # Version history
├── ARCHITECTURE.md # This document
└── api/ # Generated API docs
├── web/ # TypeDoc output
├── android/ # Dokka output
└── flutter/ # DartDoc output
Automated documentation pipeline:
- Source comments: JSDoc (TypeScript) + KDoc (Kotlin) + DartDoc (Dart)
- Build process: TypeDoc + Dokka + DartDoc generation
- CI integration: Docs updated on every build
- Artifact storage: 30-day retention for documentation
Standardized process for expanding the library:
- Design phase: Define cross-platform API contract
- Implementation: Parallel development in both platforms
- Testing: Comprehensive test coverage
- Documentation: API docs and usage examples
- Integration: Update exports and build processes
Extensible architecture for additional platforms:
- React Native: Potential TypeScript reuse
- iOS: Swift implementation following same patterns
- Python: Additional server-side support
Built-in performance considerations:
- Tree-shaking support for minimal bundle sizes
- Lazy loading capabilities for large utility sets
- Caching strategies for expensive operations
- Memory management in long-running applications
Minimal attack surface:
- Zero runtime dependencies
- Pinned development dependencies
- Regular security audits via Dependabot
- Automated vulnerability scanning
Secure coding practices:
- Input validation in all utilities
- No eval() or dynamic code execution
- Sanitized regex patterns
- Memory-safe operations
Controlled release pipeline:
- Development: Feature branches →
develop - Integration: CI validation on
develop - Release preparation: Version bump + changelog
- Stable release:
develop→release - Distribution: Package registry publication
Semantic versioning with clear upgrade paths:
- Major: Breaking API changes
- Minor: New features (backward compatible)
- Patch: Bug fixes and improvements
- Alpha/Beta: Pre-release versions
This architecture provides a solid foundation for cross-platform utility development while maintaining simplicity, performance, and developer experience.