june-lab-juna
v0.1.0
Published
Modern TypeScript utility toolkit — ESM-first, zero dependencies, tree-shakeable
Downloads
122
Maintainers
Readme
juna
Modern TypeScript utility toolkit — ESM-first, zero dependencies, tree-shakeable.
juna is a modern alternative to lodash for the ES2022+ era. Every function is individually importable, fully typed, and carries zero dependencies.
Features
- ESM-first with dual CJS/ESM output
- Zero dependencies — nothing to audit
- Tree-shakeable — import only what you use
- TypeScript native — precise types and type-level utilities
- 100% test coverage
Installation
npm install @june-lab/junaUsage
Import everything
import { chunk, pick, camelCase, memoize, isNil, clamp } from "@june-lab/juna";Import by category (best for tree-shaking)
import { chunk, groupBy, unique } from "@june-lab/juna/array";
import { pick, omit, merge } from "@june-lab/juna/object";
import { camelCase, slugify } from "@june-lab/juna/string";
import { memoize, pipe, debounce } from "@june-lab/juna/function";
import { isString, isNil } from "@june-lab/juna/guards";
import { clamp, round } from "@june-lab/juna/math";
import type { DeepReadonly } from "@june-lab/juna/types";API Reference
Array
| Function | Signature | Description |
|----------|-----------|-------------|
| chunk | (arr, size) | Split into chunks |
| flatten | (arr, depth?) | Flatten nested arrays |
| unique | (arr) | Remove duplicates |
| uniqueBy | (arr, fn) | Remove duplicates by key |
| groupBy | (arr, fn) | Group elements by key |
| sortBy | (arr, fn) | Sort by key function |
| compact | (arr) | Remove falsy values |
| range | (start, end, step?) | Create number array |
| zip | (a, b) | Pair two arrays |
| unzip | (pairs) | Split pairs into two arrays |
| first | (arr) | First element or undefined |
| last | (arr) | Last element or undefined |
| take | (arr, n) | First n elements |
| drop | (arr, n) | Drop first n elements |
| intersection | (a, b) | Elements in both arrays |
| difference | (a, b) | Elements in a but not b |
import { chunk, groupBy, range, zip } from "@june-lab/juna/array";
chunk([1, 2, 3, 4, 5], 2); // [[1,2],[3,4],[5]]
groupBy([1,2,3,4], n => n % 2 === 0 ? "even" : "odd");
// { odd: [1, 3], even: [2, 4] }
range(0, 10, 2); // [0, 2, 4, 6, 8]
zip(["a","b"], [1, 2]); // [["a",1],["b",2]]Object
| Function | Signature | Description |
|----------|-----------|-------------|
| pick | (obj, keys) | Select keys |
| omit | (obj, keys) | Exclude keys |
| mapValues | (obj, fn) | Transform values |
| mapKeys | (obj, fn) | Transform keys |
| merge | (target, source) | Deep merge |
| deepEqual | (a, b) | Deep equality check |
| fromEntries | (entries) | Typed Object.fromEntries |
import { pick, merge, deepEqual } from "@june-lab/juna/object";
pick({ a: 1, b: 2, c: 3 }, ["a", "c"]); // { a: 1, c: 3 }
merge({ x: { a: 1 } }, { x: { b: 2 } }); // { x: { a: 1, b: 2 } }
deepEqual({ a: [1, 2] }, { a: [1, 2] }); // trueString
| Function | Signature | Description |
|----------|-----------|-------------|
| capitalize | (str) | Capitalize first letter |
| camelCase | (str) | Convert to camelCase |
| snakeCase | (str) | Convert to snake_case |
| kebabCase | (str) | Convert to kebab-case |
| truncate | (str, length, suffix?) | Truncate string |
| slugify | (str) | Create URL slug |
import { camelCase, slugify, truncate } from "@june-lab/juna/string";
camelCase("hello_world"); // "helloWorld"
slugify("Héllo Wörld!"); // "hello-world"
truncate("Long text here", 10); // "Long te..."Function
| Function | Signature | Description |
|----------|-----------|-------------|
| memoize | (fn, resolver?) | Cache results |
| debounce | (fn, ms) | Delay until quiet |
| throttle | (fn, ms) | Limit call rate |
| pipe | (value, ...fns) | Left-to-right pipeline |
| once | (fn) | Execute only once |
| partial | (fn, ...args) | Partial application |
import { memoize, pipe, debounce } from "@june-lab/juna/function";
const expensiveFn = memoize((n: number) => n ** 2);
const process = pipe(
" hello world ",
(s: string) => s.trim(),
(s) => s.toUpperCase(),
);
// "HELLO WORLD"
const handleInput = debounce((value: string) => search(value), 300);Type Guards
| Function | Signature | Description |
|----------|-----------|-------------|
| isString | (val) | Is string? |
| isNumber | (val) | Is number (non-NaN)? |
| isBoolean | (val) | Is boolean? |
| isArray | (val) | Is array? |
| isObject | (val) | Is plain object? |
| isNil | (val) | Is null or undefined? |
| isDefined | (val) | Is not null/undefined? |
| isFunction | (val) | Is function? |
| isInteger | (val) | Is integer? |
import { isNil, isDefined, isString } from "@june-lab/juna/guards";
const values = [1, null, "a", undefined, 2];
const defined = values.filter(isDefined); // [1, "a", 2]Math
| Function | Signature | Description |
|----------|-----------|-------------|
| clamp | (n, min, max) | Clamp to range |
| round | (n, decimals?) | Round to decimals |
| random | (min, max?) | Random float |
| randomInt | (min, max) | Random integer |
import { clamp, round, randomInt } from "@june-lab/juna/math";
clamp(15, 0, 10); // 10
round(3.14159, 2); // 3.14
randomInt(1, 6); // dice roll: 1–6Type Utilities
import type {
DeepReadonly, DeepPartial,
UnionToIntersection,
Keys, Values,
Nullable, Optional,
RequireKeys, PartialKeys,
ElementOf,
} from "@june-lab/juna/types";
type Config = { db: { host: string; port: number }; debug: boolean };
type FrozenConfig = DeepReadonly<Config>;
type User = { id: number; name: string; email?: string };
type PublicUser = PartialKeys<User, "email">; // email stays optionalWhy juna?
| | lodash | ramda | ts-belt | juna | |---|---|---|---|---| | ESM-first | ❌ | ✅ | ✅ | ✅ | | Tree-shakeable | ⚠️ | ✅ | ✅ | ✅ | | Zero deps | ✅ | ✅ | ✅ | ✅ | | TypeScript-native | ❌ | ❌ | ✅ | ✅ | | Type-level utils | ❌ | ❌ | ⚠️ | ✅ | | Minimal API surface | ❌ | ❌ | ✅ | ✅ |
License
MIT © June Lab
