Package Exports
- ansuko
- ansuko/plugins/geo
- ansuko/plugins/ja
- ansuko/plugins/prototype
Readme
ansuko
A modern JavaScript/TypeScript utility library that extends lodash with practical, intuitive behaviors.
Why ansuko?
The name "ansuko" comes from multiple meanings:
- アンスコ (ansuko) - Japanese abbreviation for "underscore" (アンダースコア)
- ansuko = underscore → low dash → lodash - Continuing the lineage
- スコ (suko) - Japanese slang meaning "like" or "favorite"
This library fixes lodash's unintuitive behaviors and adds powerful utilities that eliminate common JavaScript frustrations.
Installation
npm install ansukoOr add to your package.json:
{
"dependencies": {
"ansuko"
}
}Core Philosophy
ansuko eliminates common JavaScript frustrations with intuitive behaviors:
Fixed lodash Quirks
// ❌ lodash (unintuitive)
_.isEmpty(0) // true - Is 0 really "empty"?
_.isEmpty(true) // true - Is true "empty"?
_.castArray(null) // [null] - Why keep null?
// ✅ ansuko (intuitive)
_.isEmpty(0) // false - Numbers are not empty
_.isEmpty(true) // false - Booleans are not empty
_.castArray(null) // [] - Clean empty arraySafe JSON Handling
// ❌ Standard JSON (annoying)
JSON.stringify('hello') // '"hello"' - Extra quotes!
JSON.parse(badJson) // throws - Need try-catch
// ✅ ansuko (smooth)
_.jsonStringify('hello') // null - Not an object
_.jsonStringify({ a: 1 }) // '{"a":1}' - Clean
_.parseJSON(badJson) // null - No exceptions
_.parseJSON('{ a: 1, }') // {a:1} - JSON5 support!Promise-Aware Fallbacks
// ❌ Verbose pattern
const data = await fetchData()
const result = data ? data : await fetchBackup()
// ✅ ansuko (concise)
const result = await _.valueOr(
() => fetchData(),
() => fetchBackup()
)Smart Comparisons
// ❌ Verbose ternary hell
const value = a === b ? a : (a == null && b == null ? a : defaultValue)
// ✅ ansuko (readable)
const value = _.equalsOr(a, b, defaultValue) // null == undefinedKey Features
Enhanced lodash Functions
isEmpty- Check if empty (numbers and booleans are NOT empty)castArray- Convert to array, returns[]for null/undefined- All lodash functions remain available:
size,isNil,debounce,isEqual,keys,values,has, etc.
Value Handling & Control Flow
valueOr- Get value or default with Promise/function supportemptyOr- Return null if empty, otherwise apply callback or return valuehasOr- Check if paths exist, return default if missing (supports deep paths & Promises)equalsOr- Compare and fallback with intuitive nil handling (Promises supported)changes- Track object differences for DB updates (supports deep paths likeprofile.tags[1]& excludes mode)swallow- Execute function and return undefined on error (sync/async)swallowMap- Map over array, treating errors as undefined (with optional compact mode)
Type Conversion & Validation
toNumber- Parse numbers with comma/full-width support, optionaltoFixedrounding, returnsnullfor invalidtoBool- Smart boolean conversion ("yes"/"no"/"true"/"false"/numbers) with configurable undetected handlingboolIf- Safe boolean conversion with fallbackisValidStr- Non-empty string validationisValidEmail- Email format validation (strict: values with surrounding spaces are invalid)
JSON Processing
parseJSON- Safe JSON/JSON5 parsing without try-catch (supports comments & trailing commas)jsonStringify- Stringify only valid objects, prevents accidental string wrapping
Array Utilities
arrayDepth- Returns nesting depth of arrays (non-array: 0, empty array: 1)castArray- Convert to array, nil becomes[](not[null])
Japanese Text (plugin: ansuko/plugins/ja)
kanaToFull- Half-width katakana → Full-width (e.g.,ガギ→ガギ)kanaToHalf- Full-width → Half-width katakana (dakuten splits:ガギ→ガギ)kanaToHira- Katakana → Hiragana (auto-converts half-width first)hiraToKana- Hiragana → KatakanatoHalfWidth- Full-width → Half-width with optional hyphen normalizationtoFullWidth- Half-width → Full-width with optional hyphen normalizationhaifun- Normalize various hyphens to single character
Geo Utilities (plugin: ansuko/plugins/geo)
toGeoJson- Universal GeoJSON converter with auto-detection (tries dimensions from high to low)toPointGeoJson- Convert coords/object to Point GeoJSONtoPolygonGeoJson- Convert outer ring to Polygon (validates closed ring)toLineStringGeoJson- Convert coords to LineString (checks self-intersection)toMultiPointGeoJson- Convert multiple points to MultiPointtoMultiPolygonGeoJson- Convert multiple polygons to MultiPolygontoMultiLineStringGeoJson- Convert multiple lines to MultiLineStringunionPolygon- Union multiple Polygon/MultiPolygon into single geometryparseToTerraDraw- Convert GeoJSON to Terra Draw compatible featuresmZoomInterpolate- Convert zoom object to MapBox interpolation expressionmProps- Convert camelCase properties to MapBox-compatible format (handles special cases like minzoom, visibility)
Prototype Extensions (plugin: ansuko/plugins/prototype)
Array.prototype.notMap- Map with negated predicate → boolean arrayArray.prototype.notFilter- Filter by negated predicate (items that do NOT match)
Timing Utilities
waited- Delay execution by N animation frames (better thansetTimeoutfor React/DOM)
Plugin Architecture
ansuko uses a minimal core + plugin architecture to keep your bundle size small:
- Core (~20KB): Essential utilities that improve lodash
- Japanese plugin (~5KB): Only load if you need Japanese text processing
- Geo plugin (~100KB with @turf/turf): Only load for GIS applications
- Prototype plugin (~1KB): Only load if you want Array prototype extensions
This means you only pay for what you use!
// Minimal bundle - just core
import _ from 'ansuko' // ~20KB
// Add Japanese support when needed
import 'ansuko/plugins/ja' // +5KB (side-effect import)
// Add GIS features for mapping apps
import 'ansuko/plugins/geo' // +100KBv2 Note: Plugins are now loaded as side-effect imports. Just
import 'ansuko/plugins/<name>'once and_is automatically augmented in both runtime and type system (via TypeScript'sdeclare modulemerging). The legacy_.extend(plugin)API has been removed in v2 — see Migration from v1 below.
Quick Start
Basic Usage
import _ from 'ansuko'
// Enhanced lodash functions
_.isEmpty(0) // false (not true like lodash!)
_.isEmpty([]) // true
_.castArray(null) // [] (not [null]!)
_.toNumber('1,234.5') // 1234.5
// Value handling with Promise support
const value = await _.valueOr(
() => cache.get(id),
() => api.fetch(id)
)
// Safe JSON parsing
const data = _.parseJSON('{ "a": 1, /* comment */ }') // Works with JSON5!
// Email validation
_.isValidEmail('user@example.com') // true
_.isValidEmail(' user@example.com ') // false
// Track object changes for database updates
const diff = _.changes(
original,
updated,
['name', 'email', 'profile.bio']
)
// Error handling without try-catch
const result = _.swallow(() => riskyOperation()) // undefined on error
const items = _.swallowMap([1, 2, 3], item => processItem(item), true) // filter errorsUsing Plugins
Japanese Text Plugin
import _ from 'ansuko'
import 'ansuko/plugins/ja'
_.kanaToFull('ガギ') // 'ガギ'
_.kanaToHira('アイウ') // 'あいう'
_.toHalfWidth('ABCー123', '-') // 'ABC-123'
_.haifun('test‐data', '-') // 'test-data'Geo Plugin
import _ from 'ansuko'
import 'ansuko/plugins/geo'
// Convert various formats to GeoJSON
_.toPointGeoJson([139.7671, 35.6812])
// => { type: 'Point', coordinates: [139.7671, 35.6812] }
_.toPointGeoJson({ lat: 35.6895, lng: 139.6917 })
// => { type: 'Point', coordinates: [139.6917, 35.6895] }
// Union multiple polygons
const unified = _.unionPolygon([polygon1, polygon2])
// MapBox utilities
_.mZoomInterpolate({ 10: 1, 15: 5, 20: 10 })
// => ["interpolate", ["linear"], ["zoom"], 10, 1, 15, 5, 20, 10]
_.mProps({
fillColor: "#ff0000",
sourceLayer: "buildings",
visibility: true
})
// => { "fill-color": "#ff0000", "source-layer": "buildings", "visibility": "visible" }Prototype Plugin
import 'ansuko/plugins/prototype'
// Now Array.prototype is extended
[1, 2, 3].notMap(n => n > 1) // [true, false, false]
[1, 2, 3].notFilter(n => n % 2) // [2] (even numbers)Combining Plugins
import _ from 'ansuko'
import 'ansuko/plugins/ja'
import 'ansuko/plugins/geo'
// Now you have both Japanese and Geo utilities on `_`
_.kanaToHira('アイウ')
_.toPointGeoJson([139.7, 35.6])Each plugin registers itself exactly once, even if imported from multiple files (a duplicate-registration guard is built in).
Migration from v1
v2 removes _.extend(plugin) in favor of side-effect imports. The migration is mechanical:
// v1
import _ from 'ansuko'
import jaPlugin from 'ansuko/plugins/ja'
const ansuko = _.extend(jaPlugin)
ansuko.kanaToFull('ガ')
// v2
import _ from 'ansuko'
import 'ansuko/plugins/ja'
_.kanaToFull('ガ')Why this change:
- TypeScript autocompletion now actually works. v1's
_.extend()returned a new type, but the original_reference's type stayed the same — so IDE suggestions never appeared on_. v2 uses TypeScriptdeclare modulemerging so_itself is augmented at the type level. - Tree-shaking is honest. Don't import a plugin = its code is not bundled.
- No more 330-line manual lodash type list.
AnsukoTypenow extendsOmit<LoDashStatic, ...>, so lodash's own types are reused (with proper generics).
If you previously did chained _.extend(a).extend(b), replace it with two side-effect imports.
Documentation
For detailed information, see:
- API Reference - Complete API documentation with examples
- Usage Guide - Real-world examples and patterns
TypeScript Support
Full TypeScript support with type definitions included. All functions are fully typed with generic support.
Why not just use lodash?
lodash is excellent, but has some quirks that have been criticized by the community:
Fixed Behaviors
_.isEmpty(true)returnstrue- Is a boolean really "empty"?_.isEmpty(1)returnstrue- Is the number 1 "empty"?_.castArray(null)returns[null]- Why include null in the array?
Added Utilities Missing in lodash
- No safe JSON parsing - Always need try-catch blocks
- No built-in comparison with fallback - Verbose ternary patterns everywhere
- No Promise-aware value resolution - Manual Promise handling gets messy
- No object diff tracking - Need external libs for DB updates
JSON.stringify("hello")adds quotes - Those'"hello"'quotes are annoying
Real-World Example
// Common pattern with lodash (verbose & error-prone)
let data
try {
const cached = cache.get(id)
if (cached && !_.isEmpty(cached)) {
data = cached
} else {
const fetched = await api.fetch(id)
data = fetched || defaultValue
}
} catch (e) {
data = defaultValue
}
// Same logic with ansuko (concise & safe)
const data = await _.valueOr(
() => cache.get(id),
() => api.fetch(id),
defaultValue
)ansuko maintains 100% compatibility with lodash while fixing these issues and adding powerful utilities for modern JavaScript development.
Dependencies
lodash- Core utility functionsjson5- Enhanced JSON parsing with comments and trailing commas support@turf/turf- Geospatial analysis (used by geo plugin)
Building from Source
npm install
npm run buildThis will generate the compiled JavaScript and type definitions in the dist directory.
Development
Developed by Sera with assistance from Claude (Anthropic) for documentation, code review, and technical discussions.
License
MIT
Author
Naoto Sera