@simplysm/core-common 13.0.29 → 13.0.30
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.md +9 -0
- package/docs/extensions.md +159 -37
- package/docs/features.md +27 -12
- package/docs/types.md +159 -21
- package/docs/utils.md +112 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,8 +52,10 @@ To use extension methods (`getOrCreate()`, `toggle()`, etc.), you must import th
|
|
|
52
52
|
- [`objMerge`](docs/utils.md#objmerge) - Deep merge (source + target)
|
|
53
53
|
- [`objMerge3`](docs/utils.md#objmerge3) - 3-way merge with conflict detection
|
|
54
54
|
- [`objOmit`](docs/utils.md#objomit) - Exclude specific keys
|
|
55
|
+
- [`objOmitByFilter`](docs/utils.md#objomitbyfilter) - Exclude keys matching a predicate
|
|
55
56
|
- [`objPick`](docs/utils.md#objpick) - Select specific keys
|
|
56
57
|
- [`objGetChainValue`](docs/utils.md#objgetchainvalue) - Query value by chain path
|
|
58
|
+
- [`objGetChainValueByDepth`](docs/utils.md#objgetchainvaluebydepth) - Query value by descending the same key repeatedly
|
|
57
59
|
- [`objSetChainValue`](docs/utils.md#objsetchainvalue) - Set value by chain path
|
|
58
60
|
- [`objDeleteChainValue`](docs/utils.md#objdeletechainvalue) - Delete value by chain path
|
|
59
61
|
- [`objKeys`](docs/utils.md#objkeys) - Type-safe `Object.keys`
|
|
@@ -94,6 +96,7 @@ To use extension methods (`getOrCreate()`, `toggle()`, etc.), you must import th
|
|
|
94
96
|
|
|
95
97
|
- [`formatDate`](docs/utils.md#formatdate) - Convert date/time to formatted string
|
|
96
98
|
- [`normalizeMonth`](docs/utils.md#normalizemonth) - Normalize year/month/day when setting month
|
|
99
|
+
- [`convert12To24`](docs/utils.md#convert12to24) - Convert 12-hour to 24-hour format
|
|
97
100
|
|
|
98
101
|
### Byte Utilities
|
|
99
102
|
|
|
@@ -208,9 +211,15 @@ To use extension methods (`getOrCreate()`, `toggle()`, etc.), you must import th
|
|
|
208
211
|
- [`Type`](docs/types.md#type) - Constructor type
|
|
209
212
|
- [`ObjUndefToOptional`](docs/types.md#objundeftooptional) - Convert `undefined` properties to optional
|
|
210
213
|
- [`ObjOptionalToUndef`](docs/types.md#objoptionaltoundef) - Convert optional properties to `required + undefined`
|
|
214
|
+
- [`EqualOptions`](docs/types.md#equaloptions) - Options for `objEqual`
|
|
215
|
+
- [`ObjMergeOptions`](docs/types.md#objmergeoptions) - Options for `objMerge`
|
|
216
|
+
- [`ObjMerge3KeyOptions`](docs/types.md#objmerge3keyoptions) - Per-key options for `objMerge3`
|
|
217
|
+
- [`DtNormalizedMonth`](docs/types.md#dtnormalizedmonth) - Return type of `normalizeMonth`
|
|
218
|
+
- [`ZipArchiveProgress`](docs/types.md#ziparchiveprogress) - Progress info for `ZipArchive.extractAll`
|
|
211
219
|
- [`ArrayDiffsResult`](docs/types.md#arraydiffsresult) - Result type of `Array.diffs()`
|
|
212
220
|
- [`ArrayDiffs2Result`](docs/types.md#arraydiffs2result) - Result type of `Array.oneWayDiffs()`
|
|
213
221
|
- [`TreeArray`](docs/types.md#treearray) - Result type of `Array.toTree()`
|
|
222
|
+
- [`ComparableType`](docs/types.md#comparabletype) - Union of types usable for sorting/comparison
|
|
214
223
|
|
|
215
224
|
## Caveats
|
|
216
225
|
|
package/docs/extensions.md
CHANGED
|
@@ -15,6 +15,7 @@ import "@simplysm/core-common";
|
|
|
15
15
|
|
|
16
16
|
const users = [{ id: 1, name: "Alice" }];
|
|
17
17
|
users.single((u) => u.id === 1); // { id: 1, name: "Alice" }
|
|
18
|
+
users.single(); // Returns the only element (throws if 2+)
|
|
18
19
|
```
|
|
19
20
|
|
|
20
21
|
#### first
|
|
@@ -22,7 +23,8 @@ users.single((u) => u.id === 1); // { id: 1, name: "Alice" }
|
|
|
22
23
|
Return first element.
|
|
23
24
|
|
|
24
25
|
```typescript
|
|
25
|
-
users.first();
|
|
26
|
+
users.first(); // First element (or undefined)
|
|
27
|
+
users.first((u) => u.id > 0); // First matching element
|
|
26
28
|
```
|
|
27
29
|
|
|
28
30
|
#### last
|
|
@@ -30,7 +32,8 @@ users.first(); // { id: 1, name: "Alice" }
|
|
|
30
32
|
Return last element.
|
|
31
33
|
|
|
32
34
|
```typescript
|
|
33
|
-
users.last();
|
|
35
|
+
users.last(); // Last element (or undefined)
|
|
36
|
+
users.last((u) => u.id > 0); // Last matching element
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
---
|
|
@@ -51,14 +54,23 @@ Filter by type (`PrimitiveTypeStr` or constructor).
|
|
|
51
54
|
|
|
52
55
|
```typescript
|
|
53
56
|
import "@simplysm/core-common";
|
|
57
|
+
|
|
58
|
+
// Filter by PrimitiveTypeStr
|
|
59
|
+
mixed.ofType("string"); // string[]
|
|
60
|
+
mixed.ofType("DateTime"); // DateTime[]
|
|
61
|
+
|
|
62
|
+
// Filter by constructor
|
|
63
|
+
mixed.ofType(MyClass); // MyClass[]
|
|
54
64
|
```
|
|
55
65
|
|
|
56
66
|
#### filterAsync
|
|
57
67
|
|
|
58
|
-
Async filter.
|
|
68
|
+
Async filter (sequential execution).
|
|
59
69
|
|
|
60
70
|
```typescript
|
|
61
71
|
import "@simplysm/core-common";
|
|
72
|
+
|
|
73
|
+
const activeUsers = await users.filterAsync(async (u) => await isActive(u.id));
|
|
62
74
|
```
|
|
63
75
|
|
|
64
76
|
---
|
|
@@ -75,18 +87,26 @@ await ids.mapAsync(async (id) => await fetchUser(id));
|
|
|
75
87
|
|
|
76
88
|
#### mapMany
|
|
77
89
|
|
|
78
|
-
|
|
90
|
+
Flatten nested arrays and remove null/undefined.
|
|
79
91
|
|
|
80
92
|
```typescript
|
|
81
93
|
import "@simplysm/core-common";
|
|
94
|
+
|
|
95
|
+
// Flatten existing nested array
|
|
96
|
+
[[1, 2], [3, 4]].mapMany(); // [1, 2, 3, 4]
|
|
97
|
+
|
|
98
|
+
// Map then flatten
|
|
99
|
+
users.mapMany((u) => u.tags); // all tags from all users
|
|
82
100
|
```
|
|
83
101
|
|
|
84
102
|
#### mapManyAsync
|
|
85
103
|
|
|
86
|
-
Async mapMany.
|
|
104
|
+
Async mapMany (sequential execution).
|
|
87
105
|
|
|
88
106
|
```typescript
|
|
89
107
|
import "@simplysm/core-common";
|
|
108
|
+
|
|
109
|
+
await groups.mapManyAsync(async (g) => await fetchMembers(g.id));
|
|
90
110
|
```
|
|
91
111
|
|
|
92
112
|
#### parallelAsync
|
|
@@ -103,7 +123,7 @@ await ids.parallelAsync(async (id) => await fetchUser(id));
|
|
|
103
123
|
|
|
104
124
|
#### groupBy
|
|
105
125
|
|
|
106
|
-
Group by key.
|
|
126
|
+
Group by key. Supports primitive keys (O(n)) and object keys (O(n²)).
|
|
107
127
|
|
|
108
128
|
```typescript
|
|
109
129
|
const users = [
|
|
@@ -114,22 +134,29 @@ const users = [
|
|
|
114
134
|
|
|
115
135
|
users.groupBy((u) => u.dept);
|
|
116
136
|
// [{ key: "dev", values: [...] }, { key: "hr", values: [...] }]
|
|
137
|
+
|
|
138
|
+
// With value selector
|
|
139
|
+
users.groupBy((u) => u.dept, (u) => u.name);
|
|
140
|
+
// [{ key: "dev", values: ["Alice", "Bob"] }, { key: "hr", values: ["Charlie"] }]
|
|
117
141
|
```
|
|
118
142
|
|
|
119
143
|
#### toMap
|
|
120
144
|
|
|
121
|
-
Convert to Map (
|
|
145
|
+
Convert to Map (throws on duplicate key).
|
|
122
146
|
|
|
123
147
|
```typescript
|
|
124
|
-
users.toMap((u) => u.id);
|
|
148
|
+
users.toMap((u) => u.id); // Map<number, User>
|
|
149
|
+
users.toMap((u) => u.id, (u) => u.name); // Map<number, string>
|
|
125
150
|
```
|
|
126
151
|
|
|
127
152
|
#### toMapAsync
|
|
128
153
|
|
|
129
|
-
Async Map conversion.
|
|
154
|
+
Async Map conversion (sequential execution).
|
|
130
155
|
|
|
131
156
|
```typescript
|
|
132
157
|
import "@simplysm/core-common";
|
|
158
|
+
|
|
159
|
+
await items.toMapAsync(async (item) => await resolveKey(item));
|
|
133
160
|
```
|
|
134
161
|
|
|
135
162
|
#### toArrayMap
|
|
@@ -146,30 +173,47 @@ Convert to `Map<K, Set<V>>`.
|
|
|
146
173
|
|
|
147
174
|
```typescript
|
|
148
175
|
import "@simplysm/core-common";
|
|
176
|
+
|
|
177
|
+
users.toSetMap((u) => u.dept); // Map<string, Set<User>>
|
|
149
178
|
```
|
|
150
179
|
|
|
151
180
|
#### toMapValues
|
|
152
181
|
|
|
153
|
-
|
|
182
|
+
Group by key and aggregate each group with a value selector.
|
|
154
183
|
|
|
155
184
|
```typescript
|
|
156
185
|
import "@simplysm/core-common";
|
|
186
|
+
|
|
187
|
+
// Sum scores per department
|
|
188
|
+
users.toMapValues(
|
|
189
|
+
(u) => u.dept,
|
|
190
|
+
(group) => group.sum((u) => u.score),
|
|
191
|
+
); // Map<string, number>
|
|
157
192
|
```
|
|
158
193
|
|
|
159
194
|
#### toObject
|
|
160
195
|
|
|
161
|
-
Convert to `Record<string, V
|
|
196
|
+
Convert to `Record<string, V>` (throws on duplicate key).
|
|
162
197
|
|
|
163
198
|
```typescript
|
|
164
199
|
import "@simplysm/core-common";
|
|
200
|
+
|
|
201
|
+
users.toObject((u) => u.name); // Record<string, User>
|
|
202
|
+
users.toObject((u) => u.name, (u) => u.id); // Record<string, number>
|
|
165
203
|
```
|
|
166
204
|
|
|
167
205
|
#### toTree
|
|
168
206
|
|
|
169
|
-
Convert to tree structure.
|
|
207
|
+
Convert flat array to tree structure.
|
|
170
208
|
|
|
171
209
|
```typescript
|
|
172
210
|
import "@simplysm/core-common";
|
|
211
|
+
|
|
212
|
+
interface Category { id: number; parentId?: number; name: string }
|
|
213
|
+
|
|
214
|
+
const tree = categories.toTree("id", "parentId");
|
|
215
|
+
// Nodes with null/undefined parentId become roots
|
|
216
|
+
// Each node gains a `children: TreeArray<Category>[]` property
|
|
173
217
|
```
|
|
174
218
|
|
|
175
219
|
---
|
|
@@ -181,15 +225,32 @@ import "@simplysm/core-common";
|
|
|
181
225
|
Remove duplicates (return new array).
|
|
182
226
|
|
|
183
227
|
```typescript
|
|
228
|
+
// Primitive values
|
|
184
229
|
[1, 2, 2, 3, 3].distinct(); // [1, 2, 3]
|
|
230
|
+
|
|
231
|
+
// Object deep equality (O(n²))
|
|
232
|
+
objects.distinct();
|
|
233
|
+
|
|
234
|
+
// Reference equality (O(n)) — fastest
|
|
235
|
+
objects.distinct(true);
|
|
236
|
+
objects.distinct({ matchAddress: true });
|
|
237
|
+
|
|
238
|
+
// Custom key function (O(n)) — recommended for large arrays of objects
|
|
239
|
+
objects.distinct({ keyFn: (item) => item.id });
|
|
185
240
|
```
|
|
186
241
|
|
|
187
242
|
#### distinctThis
|
|
188
243
|
|
|
189
|
-
Remove duplicates (modify original).
|
|
244
|
+
Remove duplicates (modify original array).
|
|
190
245
|
|
|
191
246
|
```typescript
|
|
192
247
|
import "@simplysm/core-common";
|
|
248
|
+
|
|
249
|
+
const arr = [1, 2, 2, 3];
|
|
250
|
+
arr.distinctThis(); // arr is now [1, 2, 3]
|
|
251
|
+
|
|
252
|
+
// Same options as distinct
|
|
253
|
+
arr.distinctThis({ keyFn: (item) => item.id });
|
|
193
254
|
```
|
|
194
255
|
|
|
195
256
|
---
|
|
@@ -201,7 +262,8 @@ import "@simplysm/core-common";
|
|
|
201
262
|
Ascending sort (return new array).
|
|
202
263
|
|
|
203
264
|
```typescript
|
|
204
|
-
users.orderBy((u) => u.name);
|
|
265
|
+
users.orderBy((u) => u.name); // Sort by name ascending
|
|
266
|
+
numbers.orderBy(); // Sort numbers ascending
|
|
205
267
|
```
|
|
206
268
|
|
|
207
269
|
#### orderByDesc
|
|
@@ -214,18 +276,22 @@ users.orderByDesc((u) => u.id);
|
|
|
214
276
|
|
|
215
277
|
#### orderByThis
|
|
216
278
|
|
|
217
|
-
Ascending sort (modify original).
|
|
279
|
+
Ascending sort (modify original array).
|
|
218
280
|
|
|
219
281
|
```typescript
|
|
220
282
|
import "@simplysm/core-common";
|
|
283
|
+
|
|
284
|
+
arr.orderByThis((item) => item.name);
|
|
221
285
|
```
|
|
222
286
|
|
|
223
287
|
#### orderByDescThis
|
|
224
288
|
|
|
225
|
-
Descending sort (modify original).
|
|
289
|
+
Descending sort (modify original array).
|
|
226
290
|
|
|
227
291
|
```typescript
|
|
228
292
|
import "@simplysm/core-common";
|
|
293
|
+
|
|
294
|
+
arr.orderByDescThis((item) => item.score);
|
|
229
295
|
```
|
|
230
296
|
|
|
231
297
|
---
|
|
@@ -234,26 +300,57 @@ import "@simplysm/core-common";
|
|
|
234
300
|
|
|
235
301
|
#### diffs
|
|
236
302
|
|
|
237
|
-
Compare differences between two arrays.
|
|
303
|
+
Compare differences between two arrays. Returns INSERT/DELETE/UPDATE entries.
|
|
238
304
|
|
|
239
305
|
```typescript
|
|
240
306
|
import "@simplysm/core-common";
|
|
307
|
+
|
|
308
|
+
const result = oldList.diffs(newList, { keys: ["id"] });
|
|
309
|
+
for (const diff of result) {
|
|
310
|
+
if (diff.source === undefined) { /* target-only: INSERT */ }
|
|
311
|
+
else if (diff.target === undefined) { /* source-only: DELETE */ }
|
|
312
|
+
else { /* both exist: UPDATE */ }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Exclude certain fields from equality check
|
|
316
|
+
oldList.diffs(newList, { keys: ["id"], excludes: ["updatedAt"] });
|
|
241
317
|
```
|
|
242
318
|
|
|
243
319
|
#### oneWayDiffs
|
|
244
320
|
|
|
245
|
-
One-way diff comparison (create/update/same).
|
|
321
|
+
One-way diff comparison (create/update/same). Compares current items against original items.
|
|
246
322
|
|
|
247
323
|
```typescript
|
|
248
324
|
import "@simplysm/core-common";
|
|
325
|
+
|
|
326
|
+
const diffs = currentItems.oneWayDiffs(originalItems, "id");
|
|
327
|
+
// { type: "create", item, orgItem: undefined }
|
|
328
|
+
// { type: "update", item, orgItem }
|
|
329
|
+
// { type: "same", item, orgItem } (only when includeSame: true)
|
|
330
|
+
|
|
331
|
+
// With options
|
|
332
|
+
currentItems.oneWayDiffs(originalItems, "id", {
|
|
333
|
+
includeSame: true, // Include unchanged items in result
|
|
334
|
+
excludes: ["updatedAt"], // Ignore these fields when checking for changes
|
|
335
|
+
includes: ["name"], // Only compare these fields
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Pass a pre-built Map for O(1) lookup
|
|
339
|
+
const orgMap = originalItems.toMap((item) => item.id);
|
|
340
|
+
currentItems.oneWayDiffs(orgMap, "id");
|
|
249
341
|
```
|
|
250
342
|
|
|
251
343
|
#### merge
|
|
252
344
|
|
|
253
|
-
Merge arrays.
|
|
345
|
+
Merge arrays: apply target changes onto source.
|
|
254
346
|
|
|
255
347
|
```typescript
|
|
256
348
|
import "@simplysm/core-common";
|
|
349
|
+
|
|
350
|
+
const merged = source.merge(target, { keys: ["id"] });
|
|
351
|
+
// Items present in both: merged with objMerge
|
|
352
|
+
// Items only in target: appended
|
|
353
|
+
// Items only in source: kept as-is
|
|
257
354
|
```
|
|
258
355
|
|
|
259
356
|
---
|
|
@@ -262,26 +359,35 @@ import "@simplysm/core-common";
|
|
|
262
359
|
|
|
263
360
|
#### sum
|
|
264
361
|
|
|
265
|
-
Sum.
|
|
362
|
+
Sum values.
|
|
266
363
|
|
|
267
364
|
```typescript
|
|
268
365
|
import "@simplysm/core-common";
|
|
366
|
+
|
|
367
|
+
[1, 2, 3].sum(); // 6
|
|
368
|
+
users.sum((u) => u.score); // total score
|
|
269
369
|
```
|
|
270
370
|
|
|
271
371
|
#### min
|
|
272
372
|
|
|
273
|
-
Minimum.
|
|
373
|
+
Minimum value.
|
|
274
374
|
|
|
275
375
|
```typescript
|
|
276
376
|
import "@simplysm/core-common";
|
|
377
|
+
|
|
378
|
+
[3, 1, 2].min(); // 1
|
|
379
|
+
users.min((u) => u.age); // youngest age
|
|
277
380
|
```
|
|
278
381
|
|
|
279
382
|
#### max
|
|
280
383
|
|
|
281
|
-
Maximum.
|
|
384
|
+
Maximum value.
|
|
282
385
|
|
|
283
386
|
```typescript
|
|
284
387
|
import "@simplysm/core-common";
|
|
388
|
+
|
|
389
|
+
[3, 1, 2].max(); // 3
|
|
390
|
+
users.max((u) => u.score); // highest score
|
|
285
391
|
```
|
|
286
392
|
|
|
287
393
|
---
|
|
@@ -290,34 +396,44 @@ import "@simplysm/core-common";
|
|
|
290
396
|
|
|
291
397
|
#### insert
|
|
292
398
|
|
|
293
|
-
Insert at specific position.
|
|
399
|
+
Insert at specific position (modify original).
|
|
294
400
|
|
|
295
401
|
```typescript
|
|
296
402
|
import "@simplysm/core-common";
|
|
403
|
+
|
|
404
|
+
const arr = [1, 2, 3];
|
|
405
|
+
arr.insert(1, 10, 11); // [1, 10, 11, 2, 3]
|
|
297
406
|
```
|
|
298
407
|
|
|
299
408
|
#### remove
|
|
300
409
|
|
|
301
|
-
Remove item.
|
|
410
|
+
Remove item or items matching predicate (modify original).
|
|
302
411
|
|
|
303
412
|
```typescript
|
|
304
413
|
import "@simplysm/core-common";
|
|
414
|
+
|
|
415
|
+
arr.remove(2); // Remove by value
|
|
416
|
+
arr.remove((item) => item > 2); // Remove by predicate
|
|
305
417
|
```
|
|
306
418
|
|
|
307
419
|
#### toggle
|
|
308
420
|
|
|
309
|
-
|
|
421
|
+
Toggle item — remove if exists, add if not (modify original).
|
|
310
422
|
|
|
311
423
|
```typescript
|
|
312
424
|
import "@simplysm/core-common";
|
|
425
|
+
|
|
426
|
+
arr.toggle(3); // removes 3 if present, adds if not
|
|
313
427
|
```
|
|
314
428
|
|
|
315
429
|
#### clear
|
|
316
430
|
|
|
317
|
-
Remove all items.
|
|
431
|
+
Remove all items (modify original).
|
|
318
432
|
|
|
319
433
|
```typescript
|
|
320
434
|
import "@simplysm/core-common";
|
|
435
|
+
|
|
436
|
+
arr.clear(); // arr is now []
|
|
321
437
|
```
|
|
322
438
|
|
|
323
439
|
#### shuffle
|
|
@@ -326,6 +442,8 @@ Shuffle (return new array).
|
|
|
326
442
|
|
|
327
443
|
```typescript
|
|
328
444
|
import "@simplysm/core-common";
|
|
445
|
+
|
|
446
|
+
[1, 2, 3, 4, 5].shuffle(); // random order
|
|
329
447
|
```
|
|
330
448
|
|
|
331
449
|
---
|
|
@@ -339,21 +457,25 @@ If key doesn't exist, set new value and return.
|
|
|
339
457
|
```typescript
|
|
340
458
|
const map = new Map<string, number[]>();
|
|
341
459
|
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
arr.push(1);
|
|
460
|
+
// Direct value
|
|
461
|
+
map.getOrCreate("key", []);
|
|
345
462
|
|
|
346
|
-
//
|
|
347
|
-
map.getOrCreate("
|
|
463
|
+
// Factory function (called only when key is missing)
|
|
464
|
+
map.getOrCreate("key", () => expensiveComputation());
|
|
348
465
|
```
|
|
349
466
|
|
|
467
|
+
> **Note:** If the Map value type is a function (e.g., `Map<string, () => void>`), wrap the function in a factory to store it as a value: `map.getOrCreate("key", () => myFn)`.
|
|
468
|
+
|
|
350
469
|
### update
|
|
351
470
|
|
|
352
|
-
Update value for key using function.
|
|
471
|
+
Update value for key using function. The update function receives `undefined` if the key doesn't exist.
|
|
353
472
|
|
|
354
473
|
```typescript
|
|
355
474
|
const countMap = new Map<string, number>();
|
|
356
475
|
countMap.update("key", (v) => (v ?? 0) + 1); // Increment counter
|
|
476
|
+
|
|
477
|
+
const arrayMap = new Map<string, string[]>();
|
|
478
|
+
arrayMap.update("key", (v) => [...(v ?? []), "item"]); // Append to array
|
|
357
479
|
```
|
|
358
480
|
|
|
359
481
|
---
|
|
@@ -371,11 +493,11 @@ set.adds(4, 5, 6); // {1, 2, 3, 4, 5, 6}
|
|
|
371
493
|
|
|
372
494
|
### toggle
|
|
373
495
|
|
|
374
|
-
Toggle value (remove if exists, add if not).
|
|
496
|
+
Toggle value (remove if exists, add if not). Optionally force add or delete.
|
|
375
497
|
|
|
376
498
|
```typescript
|
|
377
|
-
set.toggle(2);
|
|
378
|
-
set.toggle(7);
|
|
379
|
-
set.toggle(8, "add");
|
|
380
|
-
set.toggle(1, "del");
|
|
499
|
+
set.toggle(2); // 2 exists → remove
|
|
500
|
+
set.toggle(7); // 7 not exists → add
|
|
501
|
+
set.toggle(8, "add"); // Force add regardless of current state
|
|
502
|
+
set.toggle(1, "del"); // Force delete regardless of current state
|
|
381
503
|
```
|
package/docs/features.md
CHANGED
|
@@ -9,27 +9,32 @@ Async debounce queue (executes only last request).
|
|
|
9
9
|
```typescript
|
|
10
10
|
import { DebounceQueue } from "@simplysm/core-common";
|
|
11
11
|
|
|
12
|
-
using queue = new DebounceQueue(300); // 300ms debounce
|
|
12
|
+
using queue = new DebounceQueue(300); // 300ms debounce delay
|
|
13
13
|
|
|
14
14
|
// Error handling
|
|
15
15
|
queue.on("error", (err) => console.error(err));
|
|
16
16
|
|
|
17
|
-
// Only last call is executed
|
|
17
|
+
// Only last call is executed after the delay
|
|
18
18
|
queue.run(() => console.log("1")); // Ignored
|
|
19
19
|
queue.run(() => console.log("2")); // Ignored
|
|
20
20
|
queue.run(() => console.log("3")); // Executed after 300ms
|
|
21
|
+
|
|
22
|
+
// Omit delay for immediate execution (next event loop tick)
|
|
23
|
+
const immediate = new DebounceQueue();
|
|
21
24
|
```
|
|
22
25
|
|
|
26
|
+
> **Note:** A request added while the queue is running is executed immediately after the current run completes (no additional delay). This prevents requests from being lost.
|
|
27
|
+
|
|
23
28
|
---
|
|
24
29
|
|
|
25
30
|
## SerialQueue
|
|
26
31
|
|
|
27
|
-
Async serial queue (sequential execution).
|
|
32
|
+
Async serial queue (sequential execution). Each task starts only after the previous one completes.
|
|
28
33
|
|
|
29
34
|
```typescript
|
|
30
35
|
import { SerialQueue } from "@simplysm/core-common";
|
|
31
36
|
|
|
32
|
-
using queue = new SerialQueue(100); // 100ms
|
|
37
|
+
using queue = new SerialQueue(100); // 100ms gap between tasks (optional, default 0)
|
|
33
38
|
|
|
34
39
|
queue.on("error", (err) => console.error(err));
|
|
35
40
|
|
|
@@ -38,6 +43,8 @@ queue.run(async () => { await fetch("/api/2"); }); // Runs after #1 completes
|
|
|
38
43
|
queue.run(async () => { await fetch("/api/3"); }); // Runs after #2 completes
|
|
39
44
|
```
|
|
40
45
|
|
|
46
|
+
> **Note:** Errors in a task are caught and emitted (or logged). Subsequent tasks continue regardless.
|
|
47
|
+
|
|
41
48
|
---
|
|
42
49
|
|
|
43
50
|
## EventEmitter
|
|
@@ -61,10 +68,11 @@ class MyService extends EventEmitter<MyEvents> {
|
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
const service = new MyService();
|
|
64
|
-
|
|
65
|
-
service.
|
|
66
|
-
service.
|
|
67
|
-
service.
|
|
71
|
+
const listener = (data: string) => console.log(data);
|
|
72
|
+
service.on("data", listener); // Register listener (type: string inferred)
|
|
73
|
+
service.off("data", listener); // Remove specific listener
|
|
74
|
+
service.listenerCount("data"); // Number of registered listeners
|
|
75
|
+
service.dispose(); // Remove all listeners
|
|
68
76
|
```
|
|
69
77
|
|
|
70
78
|
---
|
|
@@ -78,13 +86,18 @@ import { ZipArchive } from "@simplysm/core-common";
|
|
|
78
86
|
|
|
79
87
|
// Read ZIP file
|
|
80
88
|
await using archive = new ZipArchive(zipBytes);
|
|
81
|
-
const content = await archive.get("file.txt");
|
|
82
|
-
const exists = await archive.exists("data.json");
|
|
83
89
|
|
|
84
|
-
//
|
|
90
|
+
// Get single file
|
|
91
|
+
const content = await archive.get("file.txt"); // Uint8Array | undefined
|
|
92
|
+
const exists = await archive.exists("data.json"); // boolean
|
|
93
|
+
|
|
94
|
+
// Extract all files (with optional progress callback)
|
|
85
95
|
const files = await archive.extractAll((progress) => {
|
|
86
|
-
|
|
96
|
+
// progress: { fileName: string; totalSize: number; extractedSize: number }
|
|
97
|
+
const pct = Math.round((progress.extractedSize / progress.totalSize) * 100);
|
|
98
|
+
console.log(`${progress.fileName}: ${pct}%`);
|
|
87
99
|
});
|
|
100
|
+
// files: Map<string, Uint8Array | undefined>
|
|
88
101
|
|
|
89
102
|
// Create ZIP file
|
|
90
103
|
await using newArchive = new ZipArchive();
|
|
@@ -92,3 +105,5 @@ newArchive.write("file.txt", textBytes);
|
|
|
92
105
|
newArchive.write("data.json", jsonBytes);
|
|
93
106
|
const zipBytes = await newArchive.compress();
|
|
94
107
|
```
|
|
108
|
+
|
|
109
|
+
> **Note:** `compress()` loads all files into memory first. Be mindful of memory usage for large archives.
|
package/docs/types.md
CHANGED
|
@@ -46,7 +46,7 @@ switch (type) {
|
|
|
46
46
|
|
|
47
47
|
### TimeoutError
|
|
48
48
|
|
|
49
|
-
Timeout error.
|
|
49
|
+
Timeout error. Thrown automatically by `waitUntil` when max attempts are exceeded.
|
|
50
50
|
|
|
51
51
|
```typescript
|
|
52
52
|
import { TimeoutError } from "@simplysm/core-common";
|
|
@@ -82,23 +82,33 @@ DateTime.parse("2025-01-15 오전 10:30:00"); // Korean AM/PM
|
|
|
82
82
|
DateTime.parse("2025-01-15T10:30:00Z"); // ISO 8601
|
|
83
83
|
|
|
84
84
|
// Properties (read-only)
|
|
85
|
-
dt.year;
|
|
86
|
-
dt.month;
|
|
87
|
-
dt.day;
|
|
88
|
-
dt.hour;
|
|
89
|
-
dt.minute;
|
|
90
|
-
dt.second;
|
|
91
|
-
dt.millisecond;
|
|
92
|
-
dt.tick;
|
|
93
|
-
dt.dayOfWeek;
|
|
94
|
-
dt.
|
|
85
|
+
dt.year; // 2025
|
|
86
|
+
dt.month; // 1 (1-12)
|
|
87
|
+
dt.day; // 15
|
|
88
|
+
dt.hour; // 10
|
|
89
|
+
dt.minute; // 30
|
|
90
|
+
dt.second; // 0
|
|
91
|
+
dt.millisecond; // 0
|
|
92
|
+
dt.tick; // Millisecond timestamp
|
|
93
|
+
dt.dayOfWeek; // Day of week (Sun~Sat: 0~6)
|
|
94
|
+
dt.timezoneOffsetMinutes; // Timezone offset in minutes (e.g. 540 for UTC+9)
|
|
95
|
+
dt.isValid; // Validity check
|
|
95
96
|
|
|
96
97
|
// Immutable transformations (return new instances)
|
|
97
98
|
dt.setYear(2026); // Change year
|
|
98
99
|
dt.setMonth(3); // Change month (day auto-adjusted)
|
|
100
|
+
dt.setDay(1); // Change day
|
|
101
|
+
dt.setHour(9); // Change hour
|
|
102
|
+
dt.setMinute(0); // Change minute
|
|
103
|
+
dt.setSecond(0); // Change second
|
|
104
|
+
dt.setMillisecond(0); // Change millisecond
|
|
105
|
+
dt.addYears(1); // 1 year later
|
|
106
|
+
dt.addMonths(1); // 1 month later
|
|
99
107
|
dt.addDays(7); // 7 days later
|
|
100
108
|
dt.addHours(-2); // 2 hours ago
|
|
101
|
-
dt.
|
|
109
|
+
dt.addMinutes(30); // 30 minutes later
|
|
110
|
+
dt.addSeconds(10); // 10 seconds later
|
|
111
|
+
dt.addMilliseconds(500); // 500ms later
|
|
102
112
|
|
|
103
113
|
// Formatting
|
|
104
114
|
dt.toFormatString("yyyy-MM-dd"); // "2025-01-15"
|
|
@@ -120,20 +130,40 @@ const d = new DateOnly(2025, 1, 15);
|
|
|
120
130
|
DateOnly.parse("2025-01-15"); // No timezone influence
|
|
121
131
|
DateOnly.parse("20250115"); // No timezone influence
|
|
122
132
|
|
|
123
|
-
//
|
|
124
|
-
d.
|
|
125
|
-
d.
|
|
133
|
+
// Properties (read-only)
|
|
134
|
+
d.year; // 2025
|
|
135
|
+
d.month; // 1
|
|
136
|
+
d.day; // 15
|
|
137
|
+
d.tick; // Millisecond timestamp
|
|
138
|
+
d.dayOfWeek; // Day of week (Sun~Sat: 0~6)
|
|
139
|
+
d.isValid; // Validity check
|
|
140
|
+
|
|
141
|
+
// Immutable transformations (return new instances)
|
|
142
|
+
d.setYear(2026);
|
|
126
143
|
d.setMonth(2); // Jan 31 -> Feb 28 (auto-adjusted)
|
|
144
|
+
d.setDay(1);
|
|
145
|
+
d.addYears(1);
|
|
146
|
+
d.addMonths(-1);
|
|
147
|
+
d.addDays(30);
|
|
127
148
|
|
|
128
|
-
// Week calculation (ISO 8601 standard)
|
|
149
|
+
// Week calculation (ISO 8601 standard: Monday start, min 4 days in first week)
|
|
129
150
|
d.getWeekSeqOfYear(); // { year: 2025, weekSeq: 3 }
|
|
130
151
|
d.getWeekSeqOfMonth(); // { year: 2025, monthSeq: 1, weekSeq: 3 }
|
|
131
152
|
|
|
132
153
|
// US-style week (Sunday start, first week with 1+ days)
|
|
133
154
|
d.getWeekSeqOfYear(0, 1);
|
|
155
|
+
d.getWeekSeqOfMonth(0, 1);
|
|
156
|
+
|
|
157
|
+
// Start date of the week containing this date
|
|
158
|
+
d.getWeekSeqStartDate(); // ISO 8601 (Monday start)
|
|
159
|
+
d.getWeekSeqStartDate(0, 1); // US-style (Sunday start)
|
|
134
160
|
|
|
135
|
-
//
|
|
136
|
-
|
|
161
|
+
// Base year/month for week sequence calculations
|
|
162
|
+
d.getBaseYearMonthSeqForWeekSeq(); // { year: 2025, monthSeq: 1 }
|
|
163
|
+
|
|
164
|
+
// Reverse calculate date from week number
|
|
165
|
+
DateOnly.getDateByYearWeekSeq({ year: 2025, weekSeq: 2 }); // 2025-01-06 (Monday)
|
|
166
|
+
DateOnly.getDateByYearWeekSeq({ year: 2025, month: 1, weekSeq: 3 }); // 2025-01-13 (Monday)
|
|
137
167
|
|
|
138
168
|
// Formatting
|
|
139
169
|
d.toFormatString("yyyy년 MM월 dd일"); // "2025년 01월 15일"
|
|
@@ -153,10 +183,25 @@ const t = new Time(14, 30, 0);
|
|
|
153
183
|
Time.parse("14:30:00"); // HH:mm:ss
|
|
154
184
|
Time.parse("14:30:00.123"); // HH:mm:ss.fff
|
|
155
185
|
Time.parse("오후 2:30:00"); // Korean AM/PM
|
|
186
|
+
Time.parse("2025-01-15T14:30:00"); // ISO 8601 (time part only)
|
|
156
187
|
|
|
157
|
-
//
|
|
158
|
-
t.
|
|
188
|
+
// Properties (read-only)
|
|
189
|
+
t.hour; // 14
|
|
190
|
+
t.minute; // 30
|
|
191
|
+
t.second; // 0
|
|
192
|
+
t.millisecond; // 0
|
|
193
|
+
t.tick; // Milliseconds since midnight
|
|
194
|
+
t.isValid; // Validity check
|
|
195
|
+
|
|
196
|
+
// Immutable transformations (return new instances, 24-hour cycle)
|
|
197
|
+
t.setHour(9);
|
|
198
|
+
t.setMinute(0);
|
|
199
|
+
t.setSecond(0);
|
|
200
|
+
t.setMillisecond(0);
|
|
201
|
+
t.addHours(12); // 14:30 + 12 hours = 02:30 (wraps around midnight)
|
|
159
202
|
t.addMinutes(-60); // 14:30 - 60 minutes = 13:30
|
|
203
|
+
t.addSeconds(30);
|
|
204
|
+
t.addMilliseconds(500);
|
|
160
205
|
|
|
161
206
|
// Formatting
|
|
162
207
|
t.toFormatString("tt h:mm"); // "오후 2:30"
|
|
@@ -192,7 +237,7 @@ import { LazyGcMap } from "@simplysm/core-common";
|
|
|
192
237
|
|
|
193
238
|
// using statement (recommended)
|
|
194
239
|
using map = new LazyGcMap<string, object>({
|
|
195
|
-
gcInterval: 10000, // GC execution interval: 10 seconds
|
|
240
|
+
gcInterval: 10000, // GC execution interval: 10 seconds (optional, defaults to expireTime/10)
|
|
196
241
|
expireTime: 60000, // Item expiration time: 60 seconds
|
|
197
242
|
onExpire: (key, value) => {
|
|
198
243
|
console.log(`Expired: ${key}`);
|
|
@@ -203,7 +248,14 @@ map.set("key1", { data: "hello" });
|
|
|
203
248
|
map.get("key1"); // Refreshes access time (LRU)
|
|
204
249
|
map.getOrCreate("key2", () => ({})); // Create and return if not exists
|
|
205
250
|
map.has("key1"); // Does not refresh access time
|
|
251
|
+
map.size; // Number of stored entries
|
|
206
252
|
map.delete("key1");
|
|
253
|
+
map.clear(); // Remove all items (instance remains usable)
|
|
254
|
+
|
|
255
|
+
// Iteration
|
|
256
|
+
for (const [key, value] of map.entries()) { /* ... */ }
|
|
257
|
+
for (const key of map.keys()) { /* ... */ }
|
|
258
|
+
for (const value of map.values()) { /* ... */ }
|
|
207
259
|
```
|
|
208
260
|
|
|
209
261
|
---
|
|
@@ -302,6 +354,78 @@ type Input = { a: string; b?: number };
|
|
|
302
354
|
type Output = ObjOptionalToUndef<Input>; // { a: string; b: number | undefined }
|
|
303
355
|
```
|
|
304
356
|
|
|
357
|
+
### EqualOptions
|
|
358
|
+
|
|
359
|
+
Options for `objEqual`.
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import type { EqualOptions } from "@simplysm/core-common";
|
|
363
|
+
|
|
364
|
+
// topLevelIncludes: only compare these keys (top level only)
|
|
365
|
+
// topLevelExcludes: skip these keys (top level only)
|
|
366
|
+
// ignoreArrayIndex: treat arrays as sets (O(n²))
|
|
367
|
+
// onlyOneDepth: shallow comparison (reference equality for nested values)
|
|
368
|
+
const options: EqualOptions = {
|
|
369
|
+
topLevelExcludes: ["updatedAt"],
|
|
370
|
+
ignoreArrayIndex: true,
|
|
371
|
+
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### ObjMergeOptions
|
|
375
|
+
|
|
376
|
+
Options for `objMerge`.
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import type { ObjMergeOptions } from "@simplysm/core-common";
|
|
380
|
+
|
|
381
|
+
// arrayProcess: "replace" (default) replaces arrays, "concat" merges and deduplicates
|
|
382
|
+
// useDelTargetNull: when true, a null target value deletes the key from the result
|
|
383
|
+
const options: ObjMergeOptions = {
|
|
384
|
+
arrayProcess: "concat",
|
|
385
|
+
useDelTargetNull: true,
|
|
386
|
+
};
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### ObjMerge3KeyOptions
|
|
390
|
+
|
|
391
|
+
Per-key comparison options for `objMerge3`.
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
import type { ObjMerge3KeyOptions } from "@simplysm/core-common";
|
|
395
|
+
|
|
396
|
+
// keys: sub-keys to compare (equivalent to topLevelIncludes in objEqual)
|
|
397
|
+
// excludes: sub-keys to exclude from comparison
|
|
398
|
+
// ignoreArrayIndex: ignore array order when comparing
|
|
399
|
+
const options: ObjMerge3KeyOptions = {
|
|
400
|
+
keys: ["id", "name"],
|
|
401
|
+
ignoreArrayIndex: false,
|
|
402
|
+
};
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### DtNormalizedMonth
|
|
406
|
+
|
|
407
|
+
Return type of `normalizeMonth`. Contains year/month/day after overflow normalization.
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import type { DtNormalizedMonth } from "@simplysm/core-common";
|
|
411
|
+
|
|
412
|
+
// { year: number; month: number; day: number }
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### ZipArchiveProgress
|
|
416
|
+
|
|
417
|
+
Progress information passed to the callback of `ZipArchive.extractAll`.
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import type { ZipArchiveProgress } from "@simplysm/core-common";
|
|
421
|
+
|
|
422
|
+
// { fileName: string; totalSize: number; extractedSize: number }
|
|
423
|
+
await archive.extractAll((progress: ZipArchiveProgress) => {
|
|
424
|
+
const pct = Math.round((progress.extractedSize / progress.totalSize) * 100);
|
|
425
|
+
console.log(`${progress.fileName}: ${pct}%`);
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
305
429
|
### ArrayDiffsResult
|
|
306
430
|
|
|
307
431
|
Result of `Array.diffs()` — insert / delete / update entries.
|
|
@@ -323,6 +447,10 @@ Result of `Array.oneWayDiffs()` — create / update / same entries.
|
|
|
323
447
|
|
|
324
448
|
```typescript
|
|
325
449
|
import type { ArrayDiffs2Result } from "@simplysm/core-common";
|
|
450
|
+
|
|
451
|
+
// { type: "create"; item: T; orgItem: undefined }
|
|
452
|
+
// { type: "update"; item: T; orgItem: T }
|
|
453
|
+
// { type: "same"; item: T; orgItem: T }
|
|
326
454
|
```
|
|
327
455
|
|
|
328
456
|
### TreeArray
|
|
@@ -336,3 +464,13 @@ interface Category { id: number; parentId: number | undefined; name: string }
|
|
|
336
464
|
const tree: TreeArray<Category>[] = categories.toTree("id", "parentId");
|
|
337
465
|
// Each node has a `children` array of the same type
|
|
338
466
|
```
|
|
467
|
+
|
|
468
|
+
### ComparableType
|
|
469
|
+
|
|
470
|
+
Union of types that can be used as sort keys in `orderBy`, `orderByDesc`, etc.
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import type { ComparableType } from "@simplysm/core-common";
|
|
474
|
+
|
|
475
|
+
// string | number | boolean | DateTime | DateOnly | Time | undefined
|
|
476
|
+
```
|
package/docs/utils.md
CHANGED
|
@@ -22,6 +22,8 @@ import { objEqual } from "@simplysm/core-common";
|
|
|
22
22
|
objEqual({ a: 1, b: [2] }, { a: 1, b: [2] }); // true
|
|
23
23
|
objEqual(arr1, arr2, { ignoreArrayIndex: true }); // Ignore array order
|
|
24
24
|
objEqual(obj1, obj2, { topLevelExcludes: ["updatedAt"] }); // Exclude specific keys
|
|
25
|
+
objEqual(obj1, obj2, { topLevelIncludes: ["id", "name"] }); // Only compare these keys
|
|
26
|
+
objEqual(obj1, obj2, { onlyOneDepth: true }); // Shallow (reference) comparison
|
|
25
27
|
```
|
|
26
28
|
|
|
27
29
|
### objMerge
|
|
@@ -33,6 +35,14 @@ import { objMerge } from "@simplysm/core-common";
|
|
|
33
35
|
|
|
34
36
|
objMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 } });
|
|
35
37
|
// { a: 1, b: { c: 2, d: 3 } }
|
|
38
|
+
|
|
39
|
+
// Concat arrays instead of replacing
|
|
40
|
+
objMerge({ tags: ["a"] }, { tags: ["b"] }, { arrayProcess: "concat" });
|
|
41
|
+
// { tags: ["a", "b"] }
|
|
42
|
+
|
|
43
|
+
// Delete key when target value is null
|
|
44
|
+
objMerge({ a: 1, b: 2 }, { b: null }, { useDelTargetNull: true });
|
|
45
|
+
// { a: 1 }
|
|
36
46
|
```
|
|
37
47
|
|
|
38
48
|
### objMerge3
|
|
@@ -60,6 +70,18 @@ import { objOmit } from "@simplysm/core-common";
|
|
|
60
70
|
objOmit(user, ["password", "email"]);
|
|
61
71
|
```
|
|
62
72
|
|
|
73
|
+
### objOmitByFilter
|
|
74
|
+
|
|
75
|
+
Exclude keys matching a predicate function.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { objOmitByFilter } from "@simplysm/core-common";
|
|
79
|
+
|
|
80
|
+
// Remove all keys starting with "_"
|
|
81
|
+
objOmitByFilter(data, (key) => String(key).startsWith("_"));
|
|
82
|
+
// { name: "Alice", age: 30 } (private _internal key removed)
|
|
83
|
+
```
|
|
84
|
+
|
|
63
85
|
### objPick
|
|
64
86
|
|
|
65
87
|
Select specific keys.
|
|
@@ -78,6 +100,23 @@ Query value by chain path (`"a.b[0].c"`).
|
|
|
78
100
|
import { objGetChainValue } from "@simplysm/core-common";
|
|
79
101
|
|
|
80
102
|
objGetChainValue(obj, "a.b[0].c");
|
|
103
|
+
|
|
104
|
+
// Optional: returns undefined instead of throwing when intermediate path is missing
|
|
105
|
+
objGetChainValue(obj, "a.b[0].c", true);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### objGetChainValueByDepth
|
|
109
|
+
|
|
110
|
+
Descend the same key repeatedly to a given depth and return the value.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { objGetChainValueByDepth } from "@simplysm/core-common";
|
|
114
|
+
|
|
115
|
+
const nested = { parent: { parent: { name: "root" } } };
|
|
116
|
+
objGetChainValueByDepth(nested, "parent", 2); // { name: "root" }
|
|
117
|
+
|
|
118
|
+
// Optional: returns undefined instead of throwing when path is missing
|
|
119
|
+
objGetChainValueByDepth(nested, "parent", 5, true); // undefined
|
|
81
120
|
```
|
|
82
121
|
|
|
83
122
|
### objSetChainValue
|
|
@@ -126,6 +165,9 @@ Type-safe `Object.fromEntries`.
|
|
|
126
165
|
|
|
127
166
|
```typescript
|
|
128
167
|
import { objFromEntries } from "@simplysm/core-common";
|
|
168
|
+
|
|
169
|
+
const entries: ["a" | "b", number][] = [["a", 1], ["b", 2]];
|
|
170
|
+
objFromEntries(entries); // { a: number; b: number }
|
|
129
171
|
```
|
|
130
172
|
|
|
131
173
|
### objMap
|
|
@@ -135,7 +177,11 @@ Transform each entry of object and return new object.
|
|
|
135
177
|
```typescript
|
|
136
178
|
import { objMap } from "@simplysm/core-common";
|
|
137
179
|
|
|
138
|
-
|
|
180
|
+
// Transform values only (pass null as new key to keep original key)
|
|
181
|
+
objMap(colors, (key, rgb) => [null, `rgb(${rgb})`]);
|
|
182
|
+
|
|
183
|
+
// Transform both keys and values
|
|
184
|
+
objMap(colors, (key, rgb) => [`${key}Light`, `rgb(${rgb})`]);
|
|
139
185
|
```
|
|
140
186
|
|
|
141
187
|
---
|
|
@@ -165,6 +211,11 @@ const json = jsonStringify(data, { space: 2 });
|
|
|
165
211
|
// For logging: hide binary data
|
|
166
212
|
jsonStringify(data, { redactBytes: true });
|
|
167
213
|
// Uint8Array content replaced with "__hidden__"
|
|
214
|
+
|
|
215
|
+
// Custom replacer (called before built-in type conversion)
|
|
216
|
+
jsonStringify(data, {
|
|
217
|
+
replacer: (key, value) => (key === "secret" ? undefined : value),
|
|
218
|
+
});
|
|
168
219
|
```
|
|
169
220
|
|
|
170
221
|
### jsonParse
|
|
@@ -243,6 +294,9 @@ PascalCase conversion.
|
|
|
243
294
|
|
|
244
295
|
```typescript
|
|
245
296
|
import { strToPascalCase } from "@simplysm/core-common";
|
|
297
|
+
|
|
298
|
+
strToPascalCase("hello-world"); // "HelloWorld"
|
|
299
|
+
strToPascalCase("hello_world"); // "HelloWorld"
|
|
246
300
|
```
|
|
247
301
|
|
|
248
302
|
### strToCamelCase
|
|
@@ -253,6 +307,7 @@ camelCase conversion.
|
|
|
253
307
|
import { strToCamelCase } from "@simplysm/core-common";
|
|
254
308
|
|
|
255
309
|
strToCamelCase("hello-world"); // "helloWorld"
|
|
310
|
+
strToCamelCase("HelloWorld"); // "helloWorld"
|
|
256
311
|
```
|
|
257
312
|
|
|
258
313
|
### strToKebabCase
|
|
@@ -271,6 +326,8 @@ snake_case conversion.
|
|
|
271
326
|
|
|
272
327
|
```typescript
|
|
273
328
|
import { strToSnakeCase } from "@simplysm/core-common";
|
|
329
|
+
|
|
330
|
+
strToSnakeCase("HelloWorld"); // "hello_world"
|
|
274
331
|
```
|
|
275
332
|
|
|
276
333
|
### strIsNullOrEmpty
|
|
@@ -293,6 +350,10 @@ Insert at specific position in string.
|
|
|
293
350
|
|
|
294
351
|
```typescript
|
|
295
352
|
import { strInsert } from "@simplysm/core-common";
|
|
353
|
+
|
|
354
|
+
strInsert("Hello World", 5, ","); // "Hello, World"
|
|
355
|
+
strInsert("abc", 0, "X"); // "Xabc"
|
|
356
|
+
strInsert("abc", 3, "X"); // "abcX"
|
|
296
357
|
```
|
|
297
358
|
|
|
298
359
|
---
|
|
@@ -307,6 +368,7 @@ Parse string to integer (remove non-digit characters).
|
|
|
307
368
|
import { numParseInt } from "@simplysm/core-common";
|
|
308
369
|
|
|
309
370
|
numParseInt("12,345원"); // 12345
|
|
371
|
+
numParseInt(3.7); // 3 (truncated, not rounded)
|
|
310
372
|
```
|
|
311
373
|
|
|
312
374
|
### numParseFloat
|
|
@@ -325,6 +387,9 @@ Round float and return integer.
|
|
|
325
387
|
|
|
326
388
|
```typescript
|
|
327
389
|
import { numParseRoundedInt } from "@simplysm/core-common";
|
|
390
|
+
|
|
391
|
+
numParseRoundedInt("3.7"); // 4
|
|
392
|
+
numParseRoundedInt("3.2"); // 3
|
|
328
393
|
```
|
|
329
394
|
|
|
330
395
|
### numFormat
|
|
@@ -336,6 +401,7 @@ import { numFormat } from "@simplysm/core-common";
|
|
|
336
401
|
|
|
337
402
|
numFormat(1234567, { max: 2 }); // "1,234,567"
|
|
338
403
|
numFormat(1234, { min: 2, max: 2 }); // "1,234.00"
|
|
404
|
+
numFormat(undefined); // undefined
|
|
339
405
|
```
|
|
340
406
|
|
|
341
407
|
### numIsNullOrEmpty
|
|
@@ -368,12 +434,20 @@ Convert date/time to string according to format string. Supports the same format
|
|
|
368
434
|
| `dd` | 0-padded day | 01~31 |
|
|
369
435
|
| `d` | Day | 1~31 |
|
|
370
436
|
| `tt` | AM/PM | 오전, 오후 |
|
|
371
|
-
| `HH` | 0-padded 24-hour | 00~23 |
|
|
372
437
|
| `hh` | 0-padded 12-hour | 01~12 |
|
|
438
|
+
| `h` | 12-hour | 1~12 |
|
|
439
|
+
| `HH` | 0-padded 24-hour | 00~23 |
|
|
440
|
+
| `H` | 24-hour | 0~23 |
|
|
373
441
|
| `mm` | 0-padded minute | 00~59 |
|
|
442
|
+
| `m` | Minute | 0~59 |
|
|
374
443
|
| `ss` | 0-padded second | 00~59 |
|
|
444
|
+
| `s` | Second | 0~59 |
|
|
375
445
|
| `fff` | Millisecond (3 digits) | 000~999 |
|
|
376
|
-
| `
|
|
446
|
+
| `ff` | Millisecond (2 digits) | 00~99 |
|
|
447
|
+
| `f` | Millisecond (1 digit) | 0~9 |
|
|
448
|
+
| `zzz` | Timezone offset (±HH:mm) | +09:00 |
|
|
449
|
+
| `zz` | Timezone offset (±HH) | +09 |
|
|
450
|
+
| `z` | Timezone offset (±H) | +9 |
|
|
377
451
|
|
|
378
452
|
```typescript
|
|
379
453
|
import { formatDate } from "@simplysm/core-common";
|
|
@@ -383,17 +457,34 @@ formatDate("yyyy-MM-dd", { year: 2024, month: 3, day: 15 });
|
|
|
383
457
|
|
|
384
458
|
formatDate("yyyy년 M월 d일 (ddd)", { year: 2024, month: 3, day: 15 });
|
|
385
459
|
// "2024년 3월 15일 (금)"
|
|
460
|
+
|
|
461
|
+
formatDate("tt h:mm:ss", { hour: 14, minute: 30, second: 45 });
|
|
462
|
+
// "오후 2:30:45"
|
|
386
463
|
```
|
|
387
464
|
|
|
388
465
|
### normalizeMonth
|
|
389
466
|
|
|
390
|
-
Normalize year/month/day when setting month.
|
|
467
|
+
Normalize year/month/day when setting month. Handles month overflow and day clamping.
|
|
391
468
|
|
|
392
469
|
```typescript
|
|
393
470
|
import { normalizeMonth } from "@simplysm/core-common";
|
|
394
471
|
|
|
395
472
|
normalizeMonth(2025, 13, 15); // { year: 2026, month: 1, day: 15 }
|
|
396
473
|
normalizeMonth(2025, 2, 31); // { year: 2025, month: 2, day: 28 }
|
|
474
|
+
normalizeMonth(2025, 0, 1); // { year: 2024, month: 12, day: 1 }
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### convert12To24
|
|
478
|
+
|
|
479
|
+
Convert 12-hour (AM/PM) to 24-hour format.
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import { convert12To24 } from "@simplysm/core-common";
|
|
483
|
+
|
|
484
|
+
convert12To24(12, false); // 0 (12 AM = midnight)
|
|
485
|
+
convert12To24(12, true); // 12 (12 PM = noon)
|
|
486
|
+
convert12To24(1, false); // 1 (1 AM)
|
|
487
|
+
convert12To24(1, true); // 13 (1 PM)
|
|
397
488
|
```
|
|
398
489
|
|
|
399
490
|
---
|
|
@@ -475,6 +566,9 @@ import { waitUntil } from "@simplysm/core-common";
|
|
|
475
566
|
// Wait for condition (100ms interval, max 50 attempts = 5 seconds)
|
|
476
567
|
await waitUntil(() => isReady, 100, 50);
|
|
477
568
|
// Throws TimeoutError after 50 attempts
|
|
569
|
+
|
|
570
|
+
// Unlimited wait (omit maxCount)
|
|
571
|
+
await waitUntil(() => isReady, 200);
|
|
478
572
|
```
|
|
479
573
|
|
|
480
574
|
---
|
|
@@ -483,7 +577,7 @@ await waitUntil(() => isReady, 100, 50);
|
|
|
483
577
|
|
|
484
578
|
### transferableEncode
|
|
485
579
|
|
|
486
|
-
Serialize custom types into Worker-transferable form.
|
|
580
|
+
Serialize custom types into Worker-transferable form. Returns `{ result, transferList }` where `transferList` contains `ArrayBuffer` instances for zero-copy transfer.
|
|
487
581
|
|
|
488
582
|
```typescript
|
|
489
583
|
import { transferableEncode } from "@simplysm/core-common";
|
|
@@ -518,6 +612,7 @@ Combine paths (`path.join` replacement).
|
|
|
518
612
|
import { pathJoin } from "@simplysm/core-common";
|
|
519
613
|
|
|
520
614
|
pathJoin("/home", "user", "file.txt"); // "/home/user/file.txt"
|
|
615
|
+
pathJoin("a/", "/b/", "/c"); // "a/b/c"
|
|
521
616
|
```
|
|
522
617
|
|
|
523
618
|
### pathBasename
|
|
@@ -533,19 +628,21 @@ pathBasename("file.txt", ".txt"); // "file"
|
|
|
533
628
|
|
|
534
629
|
### pathExtname
|
|
535
630
|
|
|
536
|
-
Extract extension (`path.extname` replacement).
|
|
631
|
+
Extract extension (`path.extname` replacement). Returns empty string for hidden files (e.g., `.gitignore`).
|
|
537
632
|
|
|
538
633
|
```typescript
|
|
539
634
|
import { pathExtname } from "@simplysm/core-common";
|
|
540
635
|
|
|
541
|
-
pathExtname("file.txt");
|
|
636
|
+
pathExtname("file.txt"); // ".txt"
|
|
637
|
+
pathExtname(".gitignore"); // ""
|
|
638
|
+
pathExtname("archive.tar.gz"); // ".gz"
|
|
542
639
|
```
|
|
543
640
|
|
|
544
641
|
---
|
|
545
642
|
|
|
546
643
|
## Template literal tags (template-strings)
|
|
547
644
|
|
|
548
|
-
Tag functions for IDE code highlighting. Actual behavior is string combination + indentation
|
|
645
|
+
Tag functions for IDE code highlighting. Actual behavior is string combination + leading/trailing blank line removal + common indentation removal.
|
|
549
646
|
|
|
550
647
|
### js
|
|
551
648
|
|
|
@@ -553,6 +650,12 @@ JavaScript code highlighting.
|
|
|
553
650
|
|
|
554
651
|
```typescript
|
|
555
652
|
import { js } from "@simplysm/core-common";
|
|
653
|
+
|
|
654
|
+
const code = js`
|
|
655
|
+
function hello() {
|
|
656
|
+
return "world";
|
|
657
|
+
}
|
|
658
|
+
`;
|
|
556
659
|
```
|
|
557
660
|
|
|
558
661
|
### ts
|
|
@@ -615,6 +718,7 @@ import { getPrimitiveTypeStr } from "@simplysm/core-common";
|
|
|
615
718
|
getPrimitiveTypeStr("hello"); // "string"
|
|
616
719
|
getPrimitiveTypeStr(123); // "number"
|
|
617
720
|
getPrimitiveTypeStr(new DateTime()); // "DateTime"
|
|
721
|
+
getPrimitiveTypeStr(new Uint8Array()); // "Bytes"
|
|
618
722
|
```
|
|
619
723
|
|
|
620
724
|
### env
|