ansuko 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +343 -0
- package/README.md +353 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +506 -0
- package/dist/plugins/geo.d.ts +25 -0
- package/dist/plugins/geo.js +566 -0
- package/dist/plugins/ja.d.ts +13 -0
- package/dist/plugins/ja.js +149 -0
- package/dist/plugins/prototype.d.ts +22 -0
- package/dist/plugins/prototype.js +9 -0
- package/dist/util.d.ts +23 -0
- package/dist/util.js +104 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +8 -0
- package/package.json +84 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# ansuko
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sera4am/ansuko/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
lodashを拡張した、実用的で直感的な動作を提供するモダンなJavaScript/TypeScriptユーティリティライブラリ。
|
|
6
|
+
|
|
7
|
+
[English](./README.md) | [日本語](./README.ja.md)
|
|
8
|
+
|
|
9
|
+
## インストール
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install ansuko
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
または `package.json` に追加:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"ansuko"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 基本思想
|
|
26
|
+
|
|
27
|
+
ansukoは直感的な動作でJavaScriptのよくある不満を解消します:
|
|
28
|
+
|
|
29
|
+
### lodashの癖を修正
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// ❌ lodash(直感的でない)
|
|
33
|
+
_.isEmpty(0) // true - 0は本当に「空」?
|
|
34
|
+
_.isEmpty(true) // true - trueは「空」?
|
|
35
|
+
_.castArray(null) // [null] - なぜnullを残す?
|
|
36
|
+
|
|
37
|
+
// ✅ ansuko(直感的)
|
|
38
|
+
_.isEmpty(0) // false - 数値は空ではない
|
|
39
|
+
_.isEmpty(true) // false - 真偽値は空ではない
|
|
40
|
+
_.castArray(null) // [] - クリーンな空配列
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 安全なJSON処理
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// ❌ 標準JSON(面倒)
|
|
47
|
+
JSON.stringify('hello') // '"hello"' - 余計な引用符!
|
|
48
|
+
JSON.parse(badJson) // throws - try-catchが必要
|
|
49
|
+
|
|
50
|
+
// ✅ ansuko(スムーズ)
|
|
51
|
+
_.jsonStringify('hello') // null - オブジェクトではない
|
|
52
|
+
_.jsonStringify({ a: 1 }) // '{"a":1}' - クリーン
|
|
53
|
+
_.parseJSON(badJson) // null - 例外なし
|
|
54
|
+
_.parseJSON('{ a: 1, }') // {a:1} - JSON5対応!
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Promise対応のフォールバック
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// ❌ 冗長なパターン
|
|
61
|
+
const data = await fetchData()
|
|
62
|
+
const result = data ? data : await fetchBackup()
|
|
63
|
+
|
|
64
|
+
// ✅ ansuko(簡潔)
|
|
65
|
+
const result = await _.valueOr(
|
|
66
|
+
() => fetchData(),
|
|
67
|
+
() => fetchBackup()
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### スマートな比較
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// ❌ 冗長な三項演算子地獄
|
|
75
|
+
const value = a === b ? a : (a == null && b == null ? a : defaultValue)
|
|
76
|
+
|
|
77
|
+
// ✅ ansuko(読みやすい)
|
|
78
|
+
const value = _.equalsOr(a, b, defaultValue) // null == undefined
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## プラグインアーキテクチャ
|
|
82
|
+
|
|
83
|
+
ansukoは最小限のコア + プラグインアーキテクチャを採用し、バンドルサイズを小さく保ちます:
|
|
84
|
+
|
|
85
|
+
- **コア** (~20KB): lodashを改善する必須ユーティリティ
|
|
86
|
+
- **日本語プラグイン** (~5KB): 日本語テキスト処理が必要な場合のみ読み込み
|
|
87
|
+
- **Geoプラグイン** (~100KB、@turf/turf含む): GISアプリケーション用
|
|
88
|
+
- **Prototypeプラグイン** (~1KB): Array prototypeの拡張が必要な場合のみ
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// 最小バンドル - コアのみ
|
|
93
|
+
import _ from 'ansuko' // ~20KB
|
|
94
|
+
|
|
95
|
+
// 必要に応じて日本語サポートを追加
|
|
96
|
+
import jaPlugin from 'ansuko/plugins/ja'
|
|
97
|
+
const extended = _.extend(jaPlugin) // +5KB
|
|
98
|
+
|
|
99
|
+
// マッピングアプリ用にGIS機能を追加
|
|
100
|
+
import geoPlugin from 'ansuko/plugins/geo'
|
|
101
|
+
const full = extended.extend(geoPlugin) // +100KB
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## クイックスタート
|
|
105
|
+
|
|
106
|
+
### 基本的な使い方
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import _ from 'ansuko'
|
|
110
|
+
|
|
111
|
+
// 拡張されたlodash関数
|
|
112
|
+
_.isEmpty(0) // false(lodashのようにtrueではない!)
|
|
113
|
+
_.isEmpty([]) // true
|
|
114
|
+
_.castArray(null) // []([null]ではない!)
|
|
115
|
+
_.toNumber('1,234.5') // 1234.5
|
|
116
|
+
|
|
117
|
+
// Promise対応の値処理
|
|
118
|
+
const value = await _.valueOr(
|
|
119
|
+
() => cache.get(id),
|
|
120
|
+
() => api.fetch(id)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// 安全なJSONパース
|
|
124
|
+
const data = _.parseJSON('{ "a": 1, /* comment */ }') // JSON5対応!
|
|
125
|
+
|
|
126
|
+
// データベース更新用のオブジェクト変更追跡
|
|
127
|
+
const diff = _.changes(
|
|
128
|
+
original,
|
|
129
|
+
updated,
|
|
130
|
+
['name', 'email', 'profile.bio']
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### プラグインの使用
|
|
135
|
+
|
|
136
|
+
#### 日本語テキストプラグイン
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import _ from 'ansuko'
|
|
140
|
+
import jaPlugin from 'ansuko/plugins/ja'
|
|
141
|
+
|
|
142
|
+
const extended = _.extend(jaPlugin)
|
|
143
|
+
|
|
144
|
+
extended.kanaToFull('ガギ') // 'ガギ'
|
|
145
|
+
extended.kanaToHira('アイウ') // 'あいう'
|
|
146
|
+
extended.toHalfWidth('ABCー123', '-') // 'ABC-123'
|
|
147
|
+
extended.haifun('test‐data', '-') // 'test-data'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Geoプラグイン
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import _ from 'ansuko'
|
|
154
|
+
import geoPlugin from 'ansuko/plugins/geo'
|
|
155
|
+
|
|
156
|
+
const extended = _.extend(geoPlugin)
|
|
157
|
+
|
|
158
|
+
// 様々な形式をGeoJSONに変換
|
|
159
|
+
extended.toPointGeoJson([139.7671, 35.6812])
|
|
160
|
+
// => { type: 'Point', coordinates: [139.7671, 35.6812] }
|
|
161
|
+
|
|
162
|
+
extended.toPointGeoJson({ lat: 35.6895, lng: 139.6917 })
|
|
163
|
+
// => { type: 'Point', coordinates: [139.6917, 35.6895] }
|
|
164
|
+
|
|
165
|
+
// 複数のポリゴンを結合
|
|
166
|
+
const unified = extended.unionPolygon([polygon1, polygon2])
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Prototypeプラグイン
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import _ from 'ansuko'
|
|
173
|
+
import prototypePlugin from 'ansuko/plugins/prototype'
|
|
174
|
+
|
|
175
|
+
_.extend(prototypePlugin)
|
|
176
|
+
|
|
177
|
+
// Array.prototypeが拡張される
|
|
178
|
+
[1, 2, 3].notMap(n => n > 1) // [true, false, false]
|
|
179
|
+
[1, 2, 3].notFilter(n => n % 2) // [2](偶数)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### プラグインのチェーン
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import _ from 'ansuko'
|
|
186
|
+
import jaPlugin from 'ansuko/plugins/ja'
|
|
187
|
+
import geoPlugin from 'ansuko/plugins/geo'
|
|
188
|
+
|
|
189
|
+
const extended = _
|
|
190
|
+
.extend(jaPlugin)
|
|
191
|
+
.extend(geoPlugin)
|
|
192
|
+
|
|
193
|
+
// 日本語とGeoユーティリティの両方が使える!
|
|
194
|
+
extended.kanaToHira('アイウ')
|
|
195
|
+
extended.toPointGeoJson([139.7, 35.6])
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 主な機能
|
|
199
|
+
|
|
200
|
+
### 拡張されたlodash関数
|
|
201
|
+
|
|
202
|
+
- **`isEmpty`** - 空かどうかチェック(数値と真偽値は空ではない)
|
|
203
|
+
- **`castArray`** - 配列に変換、null/undefinedは `[]` を返す
|
|
204
|
+
- すべてのlodash関数が利用可能: `size`, `isNil`, `debounce`, `isEqual`, `keys`, `values`, `has` など
|
|
205
|
+
|
|
206
|
+
### 値処理とフロー制御
|
|
207
|
+
|
|
208
|
+
- **`valueOr`** - Promise/関数対応で値またはデフォルトを取得
|
|
209
|
+
- **`emptyOr`** - 空ならnullを返し、それ以外はコールバックを適用または値を返す
|
|
210
|
+
- **`hasOr`** - パスの存在確認、なければデフォルトを返す(深いパス & Promise対応)
|
|
211
|
+
- **`equalsOr`** - Promise対応の比較とフォールバック、直感的なnil処理
|
|
212
|
+
- **`changes`** - DB更新用のオブジェクト差分追跡(`profile.tags[1]` のような深いパス & 除外モード対応)
|
|
213
|
+
|
|
214
|
+
### 型変換と検証
|
|
215
|
+
|
|
216
|
+
- **`toNumber`** - カンマ・全角対応の数値パース、無効時は `null`
|
|
217
|
+
- **`toBool`** - スマートな真偽値変換("yes"/"no"/"true"/"false"/数値)、未検出時の動作を設定可能
|
|
218
|
+
- **`boolIf`** - フォールバック付き安全な真偽値変換
|
|
219
|
+
- **`isValidStr`** - 非空文字列検証
|
|
220
|
+
|
|
221
|
+
### JSON処理
|
|
222
|
+
|
|
223
|
+
- **`parseJSON`** - try-catch不要の安全なJSON/JSON5パース(コメント & 末尾カンマ対応)
|
|
224
|
+
- **`jsonStringify`** - 有効なオブジェクトのみを文字列化、文字列のラップを防止
|
|
225
|
+
|
|
226
|
+
### 配列ユーティリティ
|
|
227
|
+
|
|
228
|
+
- **`arrayDepth`** - 配列のネスト深さを返す(非配列: 0、空配列: 1)
|
|
229
|
+
- **`castArray`** - 配列に変換、nilは `[]` になる(`[null]` にならない)
|
|
230
|
+
|
|
231
|
+
### 日本語テキスト(プラグイン: `ansuko/plugins/ja`)
|
|
232
|
+
|
|
233
|
+
- **`kanaToFull`** - 半角カナ → 全角カナ(例: `ガギ` → `ガギ`)
|
|
234
|
+
- **`kanaToHalf`** - 全角 → 半角カナ(濁点分割: `ガギ` → `ガギ`)
|
|
235
|
+
- **`kanaToHira`** - カナ → ひらがな(半角は自動的に全角化)
|
|
236
|
+
- **`hiraToKana`** - ひらがな → カナ
|
|
237
|
+
- **`toHalfWidth`** - 全角 → 半角、オプションでハイフン正規化
|
|
238
|
+
- **`toFullWidth`** - 半角 → 全角、オプションでハイフン正規化
|
|
239
|
+
- **`haifun`** - 様々なハイフンを単一文字に正規化
|
|
240
|
+
|
|
241
|
+
### Geoユーティリティ(プラグイン: `ansuko/plugins/geo`)
|
|
242
|
+
|
|
243
|
+
- **`toGeoJson`** - 自動検出付きの汎用GeoJSONコンバーター(高次元から順に試行)
|
|
244
|
+
- **`toPointGeoJson`** - 座標/オブジェクトをPoint GeoJSONに変換
|
|
245
|
+
- **`toPolygonGeoJson`** - 外周リングをPolygonに変換(閉じたリングを検証)
|
|
246
|
+
- **`toLineStringGeoJson`** - 座標をLineStringに変換(自己交差をチェック)
|
|
247
|
+
- **`toMultiPointGeoJson`** - 複数の点をMultiPointに変換
|
|
248
|
+
- **`toMultiPolygonGeoJson`** - 複数のポリゴンをMultiPolygonに変換
|
|
249
|
+
- **`toMultiLineStringGeoJson`** - 複数の線をMultiLineStringに変換
|
|
250
|
+
- **`unionPolygon`** - 複数のPolygon/MultiPolygonを単一のジオメトリに結合
|
|
251
|
+
- **`parseToTerraDraw`** - GeoJSONをTerra Draw互換のフィーチャーに変換
|
|
252
|
+
|
|
253
|
+
### Prototype拡張(プラグイン: `ansuko/plugins/prototype`)
|
|
254
|
+
|
|
255
|
+
- **`Array.prototype.notMap`** - 否定された述語でmap → boolean配列
|
|
256
|
+
- **`Array.prototype.notFilter`** - 否定された述語でfilter(一致しない項目)
|
|
257
|
+
|
|
258
|
+
### タイミングユーティリティ
|
|
259
|
+
|
|
260
|
+
- **`waited`** - N個のアニメーションフレーム後に実行を遅延(React/DOMには `setTimeout` より良い)
|
|
261
|
+
|
|
262
|
+
## ドキュメント
|
|
263
|
+
|
|
264
|
+
詳細情報については、以下を参照してください:
|
|
265
|
+
|
|
266
|
+
- **[APIリファレンス](./API.ja.md)** - 例付きの完全なAPIドキュメント
|
|
267
|
+
- **[使用ガイド](./Guide.ja.md)** - 実際の使用例とパターン
|
|
268
|
+
|
|
269
|
+
## TypeScriptサポート
|
|
270
|
+
|
|
271
|
+
型定義を含む完全なTypeScriptサポート。すべての関数はジェネリック対応で完全に型付けされています。
|
|
272
|
+
|
|
273
|
+
## なぜlodashだけでは不十分なのか?
|
|
274
|
+
|
|
275
|
+
lodashは優れていますが、[コミュニティで批判されている](https://github.com/lodash/lodash/issues)いくつかの癖があります:
|
|
276
|
+
|
|
277
|
+
### 修正された動作
|
|
278
|
+
|
|
279
|
+
1. **`_.isEmpty(true)` が `true` を返す** - 真偽値は本当に「空」?
|
|
280
|
+
2. **`_.isEmpty(1)` が `true` を返す** - 数値1は「空」?
|
|
281
|
+
3. **`_.castArray(null)` が `[null]` を返す** - なぜnullを配列に含める?
|
|
282
|
+
|
|
283
|
+
### lodashにない追加ユーティリティ
|
|
284
|
+
|
|
285
|
+
4. **安全なJSONパースがない** - 常にtry-catchブロックが必要
|
|
286
|
+
5. **フォールバック付き組み込み比較がない** - 冗長な三項演算子パターンがいたるところに
|
|
287
|
+
6. **Promise対応の値解決がない** - 手動のPromise処理が面倒
|
|
288
|
+
7. **オブジェクト差分追跡がない** - DB更新に外部ライブラリが必要
|
|
289
|
+
8. **`JSON.stringify("hello")` が引用符を追加** - `'"hello"'` という引用符が厄介
|
|
290
|
+
|
|
291
|
+
### 実際の使用例
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// lodashでの一般的なパターン(冗長でエラーが起きやすい)
|
|
295
|
+
let data
|
|
296
|
+
try {
|
|
297
|
+
const cached = cache.get(id)
|
|
298
|
+
if (cached && !_.isEmpty(cached)) {
|
|
299
|
+
data = cached
|
|
300
|
+
} else {
|
|
301
|
+
const fetched = await api.fetch(id)
|
|
302
|
+
data = fetched || defaultValue
|
|
303
|
+
}
|
|
304
|
+
} catch (e) {
|
|
305
|
+
data = defaultValue
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ansukoでの同じロジック(簡潔で安全)
|
|
309
|
+
const data = await _.valueOr(
|
|
310
|
+
() => cache.get(id),
|
|
311
|
+
() => api.fetch(id),
|
|
312
|
+
defaultValue
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
ansukoは、これらの問題を修正しながら、モダンなJavaScript開発のための強力なユーティリティを追加し、lodashとの **100%互換性** を維持しています。
|
|
317
|
+
|
|
318
|
+
## 依存関係
|
|
319
|
+
|
|
320
|
+
- **`lodash`** - コアユーティリティ関数
|
|
321
|
+
- **`json5`** - コメントと末尾カンマ対応の拡張JSONパース
|
|
322
|
+
- **`@turf/turf`** - 地理空間解析(geoプラグインで使用)
|
|
323
|
+
|
|
324
|
+
## ソースからのビルド
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npm install
|
|
328
|
+
npm run build
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
これにより、`dist` ディレクトリにコンパイルされたJavaScriptと型定義が生成されます。
|
|
332
|
+
|
|
333
|
+
## 開発
|
|
334
|
+
|
|
335
|
+
Seraによって開発され、ドキュメント、コードレビュー、技術的な議論でClaude(Anthropic)の支援を受けました。
|
|
336
|
+
|
|
337
|
+
## ライセンス
|
|
338
|
+
|
|
339
|
+
MIT
|
|
340
|
+
|
|
341
|
+
## 作者
|
|
342
|
+
|
|
343
|
+
世来 直人
|
package/README.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# ansuko
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sera4am/ansuko/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A modern JavaScript/TypeScript utility library that extends lodash with practical, intuitive behaviors.
|
|
6
|
+
|
|
7
|
+
[English](./README.md) | [日本語](./README.ja.md)
|
|
8
|
+
|
|
9
|
+
## Why ansuko?
|
|
10
|
+
|
|
11
|
+
The name "ansuko" comes from multiple meanings:
|
|
12
|
+
|
|
13
|
+
- **アンスコ (ansuko)** - Japanese abbreviation for "underscore" (アンダースコア)
|
|
14
|
+
- **ansuko = underscore → low dash → lodash** - Continuing the lineage
|
|
15
|
+
- **スコ (suko)** - Japanese slang meaning "like" or "favorite"
|
|
16
|
+
|
|
17
|
+
This library fixes lodash's unintuitive behaviors and adds powerful utilities that eliminate common JavaScript frustrations.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install ansuko
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or add to your `package.json`:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"ansuko"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Core Philosophy
|
|
36
|
+
|
|
37
|
+
ansuko eliminates common JavaScript frustrations with intuitive behaviors:
|
|
38
|
+
|
|
39
|
+
### Fixed lodash Quirks
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// ❌ lodash (unintuitive)
|
|
43
|
+
_.isEmpty(0) // true - Is 0 really "empty"?
|
|
44
|
+
_.isEmpty(true) // true - Is true "empty"?
|
|
45
|
+
_.castArray(null) // [null] - Why keep null?
|
|
46
|
+
|
|
47
|
+
// ✅ ansuko (intuitive)
|
|
48
|
+
_.isEmpty(0) // false - Numbers are not empty
|
|
49
|
+
_.isEmpty(true) // false - Booleans are not empty
|
|
50
|
+
_.castArray(null) // [] - Clean empty array
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Safe JSON Handling
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// ❌ Standard JSON (annoying)
|
|
57
|
+
JSON.stringify('hello') // '"hello"' - Extra quotes!
|
|
58
|
+
JSON.parse(badJson) // throws - Need try-catch
|
|
59
|
+
|
|
60
|
+
// ✅ ansuko (smooth)
|
|
61
|
+
_.jsonStringify('hello') // null - Not an object
|
|
62
|
+
_.jsonStringify({ a: 1 }) // '{"a":1}' - Clean
|
|
63
|
+
_.parseJSON(badJson) // null - No exceptions
|
|
64
|
+
_.parseJSON('{ a: 1, }') // {a:1} - JSON5 support!
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Promise-Aware Fallbacks
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// ❌ Verbose pattern
|
|
71
|
+
const data = await fetchData()
|
|
72
|
+
const result = data ? data : await fetchBackup()
|
|
73
|
+
|
|
74
|
+
// ✅ ansuko (concise)
|
|
75
|
+
const result = await _.valueOr(
|
|
76
|
+
() => fetchData(),
|
|
77
|
+
() => fetchBackup()
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Smart Comparisons
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// ❌ Verbose ternary hell
|
|
85
|
+
const value = a === b ? a : (a == null && b == null ? a : defaultValue)
|
|
86
|
+
|
|
87
|
+
// ✅ ansuko (readable)
|
|
88
|
+
const value = _.equalsOr(a, b, defaultValue) // null == undefined
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Key Features
|
|
92
|
+
|
|
93
|
+
### Enhanced lodash Functions
|
|
94
|
+
|
|
95
|
+
- **`isEmpty`** - Check if empty (numbers and booleans are NOT empty)
|
|
96
|
+
- **`castArray`** - Convert to array, returns `[]` for null/undefined
|
|
97
|
+
- All lodash functions remain available: `size`, `isNil`, `debounce`, `isEqual`, `keys`, `values`, `has`, etc.
|
|
98
|
+
|
|
99
|
+
### Value Handling & Control Flow
|
|
100
|
+
|
|
101
|
+
- **`valueOr`** - Get value or default with Promise/function support
|
|
102
|
+
- **`emptyOr`** - Return null if empty, otherwise apply callback or return value
|
|
103
|
+
- **`hasOr`** - Check if paths exist, return default if missing (supports deep paths & Promises)
|
|
104
|
+
- **`equalsOr`** - Compare and fallback with intuitive nil handling (Promises supported)
|
|
105
|
+
- **`changes`** - Track object differences for DB updates (supports deep paths like `profile.tags[1]` & excludes mode)
|
|
106
|
+
|
|
107
|
+
### Type Conversion & Validation
|
|
108
|
+
|
|
109
|
+
- **`toNumber`** - Parse numbers with comma/full-width support, returns `null` for invalid
|
|
110
|
+
- **`toBool`** - Smart boolean conversion ("yes"/"no"/"true"/"false"/numbers) with configurable undetected handling
|
|
111
|
+
- **`boolIf`** - Safe boolean conversion with fallback
|
|
112
|
+
- **`isValidStr`** - Non-empty string validation
|
|
113
|
+
|
|
114
|
+
### JSON Processing
|
|
115
|
+
|
|
116
|
+
- **`parseJSON`** - Safe JSON/JSON5 parsing without try-catch (supports comments & trailing commas)
|
|
117
|
+
- **`jsonStringify`** - Stringify only valid objects, prevents accidental string wrapping
|
|
118
|
+
|
|
119
|
+
### Array Utilities
|
|
120
|
+
|
|
121
|
+
- **`arrayDepth`** - Returns nesting depth of arrays (non-array: 0, empty array: 1)
|
|
122
|
+
- **`castArray`** - Convert to array, nil becomes `[]` (not `[null]`)
|
|
123
|
+
|
|
124
|
+
### Japanese Text (plugin: `ansuko/plugins/ja`)
|
|
125
|
+
|
|
126
|
+
- **`kanaToFull`** - Half-width katakana → Full-width (e.g., `ガギ` → `ガギ`)
|
|
127
|
+
- **`kanaToHalf`** - Full-width → Half-width katakana (dakuten splits: `ガギ` → `ガギ`)
|
|
128
|
+
- **`kanaToHira`** - Katakana → Hiragana (auto-converts half-width first)
|
|
129
|
+
- **`hiraToKana`** - Hiragana → Katakana
|
|
130
|
+
- **`toHalfWidth`** - Full-width → Half-width with optional hyphen normalization
|
|
131
|
+
- **`toFullWidth`** - Half-width → Full-width with optional hyphen normalization
|
|
132
|
+
- **`haifun`** - Normalize various hyphens to single character
|
|
133
|
+
|
|
134
|
+
### Geo Utilities (plugin: `ansuko/plugins/geo`)
|
|
135
|
+
|
|
136
|
+
- **`toGeoJson`** - Universal GeoJSON converter with auto-detection (tries dimensions from high to low)
|
|
137
|
+
- **`toPointGeoJson`** - Convert coords/object to Point GeoJSON
|
|
138
|
+
- **`toPolygonGeoJson`** - Convert outer ring to Polygon (validates closed ring)
|
|
139
|
+
- **`toLineStringGeoJson`** - Convert coords to LineString (checks self-intersection)
|
|
140
|
+
- **`toMultiPointGeoJson`** - Convert multiple points to MultiPoint
|
|
141
|
+
- **`toMultiPolygonGeoJson`** - Convert multiple polygons to MultiPolygon
|
|
142
|
+
- **`toMultiLineStringGeoJson`** - Convert multiple lines to MultiLineString
|
|
143
|
+
- **`unionPolygon`** - Union multiple Polygon/MultiPolygon into single geometry
|
|
144
|
+
- **`parseToTerraDraw`** - Convert GeoJSON to Terra Draw compatible features
|
|
145
|
+
|
|
146
|
+
### Prototype Extensions (plugin: `ansuko/plugins/prototype`)
|
|
147
|
+
|
|
148
|
+
- **`Array.prototype.notMap`** - Map with negated predicate → boolean array
|
|
149
|
+
- **`Array.prototype.notFilter`** - Filter by negated predicate (items that do NOT match)
|
|
150
|
+
|
|
151
|
+
### Timing Utilities
|
|
152
|
+
|
|
153
|
+
- **`waited`** - Delay execution by N animation frames (better than `setTimeout` for React/DOM)
|
|
154
|
+
|
|
155
|
+
## Plugin Architecture
|
|
156
|
+
|
|
157
|
+
ansuko uses a minimal core + plugin architecture to keep your bundle size small:
|
|
158
|
+
|
|
159
|
+
- **Core** (~20KB): Essential utilities that improve lodash
|
|
160
|
+
- **Japanese plugin** (~5KB): Only load if you need Japanese text processing
|
|
161
|
+
- **Geo plugin** (~100KB with @turf/turf): Only load for GIS applications
|
|
162
|
+
- **Prototype plugin** (~1KB): Only load if you want Array prototype extensions
|
|
163
|
+
|
|
164
|
+
This means you only pay for what you use!
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Minimal bundle - just core
|
|
168
|
+
import _ from 'ansuko' // ~20KB
|
|
169
|
+
|
|
170
|
+
// Add Japanese support when needed
|
|
171
|
+
import jaPlugin from 'ansuko/plugins/ja'
|
|
172
|
+
_.extend(jaPlugin) // +5KB
|
|
173
|
+
|
|
174
|
+
// Add GIS features for mapping apps
|
|
175
|
+
import geoPlugin from 'ansuko/plugins/geo'
|
|
176
|
+
_.extend(geoPlugin) // +100KB
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Quick Start
|
|
180
|
+
|
|
181
|
+
### Basic Usage
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import _ from 'ansuko'
|
|
185
|
+
|
|
186
|
+
// Enhanced lodash functions
|
|
187
|
+
_.isEmpty(0) // false (not true like lodash!)
|
|
188
|
+
_.isEmpty([]) // true
|
|
189
|
+
_.castArray(null) // [] (not [null]!)
|
|
190
|
+
_.toNumber('1,234.5') // 1234.5
|
|
191
|
+
|
|
192
|
+
// Value handling with Promise support
|
|
193
|
+
const value = await _.valueOr(
|
|
194
|
+
() => cache.get(id),
|
|
195
|
+
() => api.fetch(id)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
// Safe JSON parsing
|
|
199
|
+
const data = _.parseJSON('{ "a": 1, /* comment */ }') // Works with JSON5!
|
|
200
|
+
|
|
201
|
+
// Track object changes for database updates
|
|
202
|
+
const diff = _.changes(
|
|
203
|
+
original,
|
|
204
|
+
updated,
|
|
205
|
+
['name', 'email', 'profile.bio']
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Using Plugins
|
|
210
|
+
|
|
211
|
+
#### Japanese Text Plugin
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import _ from 'ansuko'
|
|
215
|
+
import jaPlugin from 'ansuko/plugins/ja'
|
|
216
|
+
|
|
217
|
+
_.extend(jaPlugin)
|
|
218
|
+
|
|
219
|
+
_.kanaToFull('ガギ') // 'ガギ'
|
|
220
|
+
_.kanaToHira('アイウ') // 'あいう'
|
|
221
|
+
_.toHalfWidth('ABCー123', '-') // 'ABC-123'
|
|
222
|
+
_.haifun('test‐data', '-') // 'test-data'
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### Geo Plugin
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import _ from 'ansuko'
|
|
229
|
+
import geoPlugin from 'ansuko/plugins/geo'
|
|
230
|
+
|
|
231
|
+
const extended = _.extend(geoPlugin)
|
|
232
|
+
|
|
233
|
+
// Convert various formats to GeoJSON
|
|
234
|
+
extended.toPointGeoJson([139.7671, 35.6812])
|
|
235
|
+
// => { type: 'Point', coordinates: [139.7671, 35.6812] }
|
|
236
|
+
|
|
237
|
+
extended.toPointGeoJson({ lat: 35.6895, lng: 139.6917 })
|
|
238
|
+
// => { type: 'Point', coordinates: [139.6917, 35.6895] }
|
|
239
|
+
|
|
240
|
+
// Union multiple polygons
|
|
241
|
+
const unified = extended.unionPolygon([polygon1, polygon2])
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Prototype Plugin
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import _ from 'ansuko'
|
|
248
|
+
import prototypePlugin from 'ansuko/plugins/prototype'
|
|
249
|
+
|
|
250
|
+
_.extend(prototypePlugin)
|
|
251
|
+
|
|
252
|
+
// Now Array.prototype is extended
|
|
253
|
+
[1, 2, 3].notMap(n => n > 1) // [true, false, false]
|
|
254
|
+
[1, 2, 3].notFilter(n => n % 2) // [2] (even numbers)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Chaining Plugins
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import _ from 'ansuko'
|
|
261
|
+
import jaPlugin from 'ansuko/plugins/ja'
|
|
262
|
+
import geoPlugin from 'ansuko/plugins/geo'
|
|
263
|
+
|
|
264
|
+
const extended = _
|
|
265
|
+
.extend(jaPlugin)
|
|
266
|
+
.extend(geoPlugin)
|
|
267
|
+
|
|
268
|
+
// Now you have both Japanese and Geo utilities!
|
|
269
|
+
extended.kanaToHira('アイウ')
|
|
270
|
+
extended.toPointGeoJson([139.7, 35.6])
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Documentation
|
|
274
|
+
|
|
275
|
+
For detailed information, see:
|
|
276
|
+
|
|
277
|
+
- **[API Reference](./docs/API.md)** - Complete API documentation with examples
|
|
278
|
+
- **[Usage Guide](./docs/Guide.md)** - Real-world examples and patterns
|
|
279
|
+
|
|
280
|
+
## TypeScript Support
|
|
281
|
+
|
|
282
|
+
Full TypeScript support with type definitions included. All functions are fully typed with generic support.
|
|
283
|
+
|
|
284
|
+
## Why not just use lodash?
|
|
285
|
+
|
|
286
|
+
lodash is excellent, but has some quirks that have been [criticized by the community](https://github.com/lodash/lodash/issues):
|
|
287
|
+
|
|
288
|
+
### Fixed Behaviors
|
|
289
|
+
|
|
290
|
+
1. **`_.isEmpty(true)` returns `true`** - Is a boolean really "empty"?
|
|
291
|
+
2. **`_.isEmpty(1)` returns `true`** - Is the number 1 "empty"?
|
|
292
|
+
3. **`_.castArray(null)` returns `[null]`** - Why include null in the array?
|
|
293
|
+
|
|
294
|
+
### Added Utilities Missing in lodash
|
|
295
|
+
|
|
296
|
+
4. **No safe JSON parsing** - Always need try-catch blocks
|
|
297
|
+
5. **No built-in comparison with fallback** - Verbose ternary patterns everywhere
|
|
298
|
+
6. **No Promise-aware value resolution** - Manual Promise handling gets messy
|
|
299
|
+
7. **No object diff tracking** - Need external libs for DB updates
|
|
300
|
+
8. **`JSON.stringify("hello")` adds quotes** - Those `'"hello"'` quotes are annoying
|
|
301
|
+
|
|
302
|
+
### Real-World Example
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// Common pattern with lodash (verbose & error-prone)
|
|
306
|
+
let data
|
|
307
|
+
try {
|
|
308
|
+
const cached = cache.get(id)
|
|
309
|
+
if (cached && !_.isEmpty(cached)) {
|
|
310
|
+
data = cached
|
|
311
|
+
} else {
|
|
312
|
+
const fetched = await api.fetch(id)
|
|
313
|
+
data = fetched || defaultValue
|
|
314
|
+
}
|
|
315
|
+
} catch (e) {
|
|
316
|
+
data = defaultValue
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Same logic with ansuko (concise & safe)
|
|
320
|
+
const data = await _.valueOr(
|
|
321
|
+
() => cache.get(id),
|
|
322
|
+
() => api.fetch(id),
|
|
323
|
+
defaultValue
|
|
324
|
+
)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
ansuko maintains **100% compatibility** with lodash while fixing these issues and adding powerful utilities for modern JavaScript development.
|
|
328
|
+
|
|
329
|
+
## Dependencies
|
|
330
|
+
|
|
331
|
+
- **`lodash`** - Core utility functions
|
|
332
|
+
- **`json5`** - Enhanced JSON parsing with comments and trailing commas support
|
|
333
|
+
- **`@turf/turf`** - Geospatial analysis (used by geo plugin)
|
|
334
|
+
|
|
335
|
+
## Building from Source
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
npm install
|
|
339
|
+
npm run build
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
This will generate the compiled JavaScript and type definitions in the `dist` directory.
|
|
343
|
+
|
|
344
|
+
## Development
|
|
345
|
+
Developed by Sera with assistance from Claude (Anthropic) for documentation, code review, and technical discussions.
|
|
346
|
+
|
|
347
|
+
## License
|
|
348
|
+
|
|
349
|
+
MIT
|
|
350
|
+
|
|
351
|
+
## Author
|
|
352
|
+
|
|
353
|
+
Naoto Sera
|