koyojs
v0.3.0
Published
Koyo JS — essential utilities
Readme
Koyo JS
⚠️ APIs are stable but may still evolve before 1.0.0.
Koyo (肝要, Japanese: essential) — a zero-dependency utility library for TypeScript and JavaScript. v0.3.0
Ships as ESM, CJS, and a native Bun build. Fully typed. Works the same in Node, Bun, browsers, and edge runtimes.
Why
JavaScript has no proper standard library. The language ships with primitives and leaves everything else to the ecosystem — which sounds fine until you realize that the same operation can behave differently depending on where your code runs. A regex that works in Node may not behave the same in a browser. A built-in that exists in one runtime is missing in another. The environment shouldn't be your problem.
The npm ecosystem filled this gap, but fragility came with it. In 2016, the deletion of left-pad — a utility so small it fit in a tweet, maintained by a single unpaid developer — broke thousands of builds overnight and took much of the internet with it. The library wasn't bad code; it was a dependency that the ecosystem had quietly made load-bearing, written by someone who owed it nothing and received nothing in return.
Koyo exists because these essentials shouldn't live at the mercy of a single maintainer's patience or a registry's availability. It provides a stable, self-contained set of utilities that work the same whether your code runs in a browser, Node, Bun, or a serverless edge function — no dependencies, no surprises, no single point of failure.
Install
# npm / pnpm / yarn
npm install koyojs
# bun
bun add koyojsUsage
import { slugify, clamp, isEmail } from "koyojs";
slugify("Hello World!"); // "hello-world"
clamp(150, 0, 100); // 100
isEmail("[email protected]"); // trueYou can also import from a specific module:
import { toCamelCase } from "koyojs/String";
import { randomInt } from "koyojs/Number";
import { chunk } from "koyojs/Array";
import { pick } from "koyojs/Object";
import { gcd } from "koyojs/Math";API
String
| Function | Signature | Description |
|---|---|---|
| capitalize | (input: string) => string | Capitalize the first letter. |
| toCamelCase | (input: string) => string | "hello world" → "helloWorld" |
| toSnakeCase | (input: string) => string | "helloWorld" → "hello_world" |
| toTitleCase | (input: string) => string | "hello world" → "Hello World" |
| truncate | (input: string, maxLength: number) => string | Cut string to maxLength. |
| ellipsify | (input: string, maxLength: number) => string | Truncate to maxLength total (including the "..."). e.g. ellipsify("Hello World", 5) → "He..." |
| slugify | (input: string) => string | "Hello World!" → "hello-world" |
| isEmail | (input: string) => boolean | Validate an email address. |
| isURL | (input: string) => boolean | Validate an HTTP/HTTPS URL. |
| isPhoneNumber | (input: string) => boolean | Validate a phone number (E.164 + common formats). |
Array
| Function | Signature | Description |
|---|---|---|
| createArray | <T>(options: { strict, validate? }) => TypedArray<T> | Typed array that enforces element type. strict: true throws, strict: false warns. |
| unique | <T>(arr: T[]) => T[] | Remove duplicate items. |
| removeDuplicates | <T>(arr: T[]) => T[] | Alias for unique. |
| flatten | <T>(arr: unknown[]) => T[] | Recursively flatten a nested array. e.g. [1,[2,[3]]] → [1,2,3] |
| chunk | <T>(arr: T[], size: number) => T[][] | Split into sub-arrays of size. e.g. chunk([1,2,3,4], 2) → [[1,2],[3,4]] |
| groupBy | <T>(arr: T[], keyFn: (item: T) => string) => Record<string, T[]> | Group items by a derived key. |
Object
| Function | Signature | Description |
|---|---|---|
| pick | <T, K extends keyof T>(obj: T, keys: K[]) => Pick<T, K> | Return a new object with only the selected top-level keys. |
| omit | <T, K extends keyof T>(obj: T, keys: K[]) => Omit<T, K> | Return a new object with the specified top-level keys removed. |
| extract | <T extends object>(obj: T, paths: string[]) => Record<string, unknown> | Like pick, but resolves dot-notation nested paths. e.g. extract(obj, ['a.b.c']) |
| exclude | <T extends object>(obj: T, paths: string[]) => Record<string, unknown> | Like omit, but resolves dot-notation nested paths. e.g. exclude(obj, ['a.b.c']) |
| cloneDeep | <T>(value: T) => T | Deep copy that preserves prototypes, Maps, Sets, getters/setters, and handles circular references. |
| cloneShallow | <T>(value: T) => T | Deep copy via structuredClone. Fast, but throws on non-serializable values and drops prototypes. |
Number
| Function | Signature | Description |
|---|---|---|
| clamp | (value, min, max: number) => number | Restrict a value to [min, max]. |
| randomInt | (min, max: number) => number | Random integer in [min, max] (inclusive). |
| sum | (values: number[]) => number | Sum an array of numbers. |
| average | (values: number[]) => number | Average an array of numbers. |
| isEven | (value: number) => boolean | true if the integer is even. |
| isOdd | (value: number) => boolean | true if the integer is odd. |
Math
A stricter, precision-focused numeric module — C-like exactness with a Python-like API. Functions that need arbitrary-precision results (factorial, comb, perm) return bigint; every other function stays number-only. No function silently mixes the two.
| Function | Signature | Description |
|---|---|---|
| gcd | (a: number, b: number) => number | Greatest common divisor. |
| lcm | (a: number, b: number) => number | Least common multiple. |
| factorial | (n: number) => bigint | Exact factorial, arbitrary precision. |
| factorialUnsafe | (n: number) => number | number-only factorial. Throws once the result would exceed Number.MAX_SAFE_INTEGER rather than returning a silently imprecise value. |
| isPrime | (n: number) => boolean | Primality test via 6k±1 trial division. |
| isqrt | (n: number) => number | Exact integer square root (BigInt Newton's method internally), floored for non-perfect squares. |
| floorDiv | (a: number, b: number) => number | Python-style floor division. |
| mod | (a: number, b: number) => number | Python-style modulo — result takes the sign of the divisor, unlike JS %. |
| comb | (n: number, k: number) => bigint | n choose k, arbitrary precision. |
| combUnsafe | (n: number, k: number) => number | number-only comb. Throws on overflow past Number.MAX_SAFE_INTEGER. |
| perm | (n: number, k?: number) => bigint | n permute k (defaults to k = n), arbitrary precision. |
| permUnsafe | (n: number, k?: number) => number | number-only perm. Throws on overflow past Number.MAX_SAFE_INTEGER. |
| isClose | (a: number, b: number, options?: { relTol?, absTol? }) => boolean | Python's math.isclose — combined relative + absolute tolerance. |
| isCloseAbs | (a: number, b: number, absTol: number) => boolean | Absolute-tolerance-only comparison. No relative term. |
| isCloseRel | (a: number, b: number, relTol: number) => boolean | Relative-tolerance-only comparison. No absolute term. |
| kahanSum | (values: number[]) => number | Compensated summation — lower accumulated error than naive reduce. |
| roundTo | (value: number, decimals?: number) => number | Epsilon-corrected rounding (fixes cases like 1.005 rounding down under naive Math.round). |
| copySign | (x: number, y: number) => number | Magnitude of x with the sign of y. |
| degrees | (radians: number) => number | Radians to degrees. |
| radians | (degrees: number) => number | Degrees to radians. |
| lerp | (a: number, b: number, t: number) => number | Linear interpolation. |
| hypot | (...values: number[]) => number | N-dimensional hypotenuse. |
| dist | (p: number[], q: number[]) => number | Euclidean distance between two equal-dimension points. |
Development
bun install # install dependencies
bun run build # compile to dist/
bun run test # run all tests
bun run check # type-check without emitting
bun run dev # watch modeBenchmarks
bun run benchmark:node # Node — imports from dist/
bun run benchmark:bun # Bun — imports from src/ (native TS)
bun run benchmark:browser # Node — dist/ + happy-dom browser globals
bun run benchmark:all # all three in sequenceCompares koyojs against lodash, remeda, and radash. Requires bun run build before running the Node or browser targets.
Philosophy
One file, one function. Every module lives under src/<Category>/<functionName>.ts and does exactly one thing. No file exceeds 60 lines unless strictly necessary.
Source
The repository is mirrored on two platforms — clone from whichever you prefer:
# GitHub
git clone https://github.com/DebadityaMalakar/koyojs.git
# Codeberg
git clone https://codeberg.org/debaditya/koyojs.gitLicense
MIT
