@simplysm/core-common 13.0.28 → 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.
Files changed (72) hide show
  1. package/README.md +9 -0
  2. package/dist/errors/sd-error.d.ts.map +1 -1
  3. package/dist/errors/sd-error.js +4 -1
  4. package/dist/errors/sd-error.js.map +1 -1
  5. package/dist/errors/timeout-error.d.ts.map +1 -1
  6. package/dist/errors/timeout-error.js.map +1 -1
  7. package/dist/extensions/arr-ext.d.ts +1 -1
  8. package/dist/extensions/arr-ext.d.ts.map +1 -1
  9. package/dist/extensions/arr-ext.helpers.d.ts.map +1 -1
  10. package/dist/extensions/arr-ext.helpers.js +4 -1
  11. package/dist/extensions/arr-ext.helpers.js.map +1 -1
  12. package/dist/extensions/arr-ext.js +15 -5
  13. package/dist/extensions/arr-ext.js.map +1 -1
  14. package/dist/extensions/arr-ext.types.d.ts.map +1 -1
  15. package/dist/features/event-emitter.d.ts.map +1 -1
  16. package/dist/features/event-emitter.js.map +1 -1
  17. package/dist/types/date-only.d.ts.map +1 -1
  18. package/dist/types/date-only.js +15 -5
  19. package/dist/types/date-only.js.map +1 -1
  20. package/dist/types/date-time.d.ts.map +1 -1
  21. package/dist/types/date-time.js +69 -9
  22. package/dist/types/date-time.js.map +1 -1
  23. package/dist/types/time.d.ts.map +1 -1
  24. package/dist/types/time.js +12 -2
  25. package/dist/types/time.js.map +1 -1
  26. package/dist/types/uuid.d.ts.map +1 -1
  27. package/dist/types/uuid.js +4 -1
  28. package/dist/types/uuid.js.map +1 -1
  29. package/dist/utils/bytes.d.ts.map +1 -1
  30. package/dist/utils/bytes.js +3 -1
  31. package/dist/utils/bytes.js.map +1 -1
  32. package/dist/utils/date-format.d.ts.map +1 -1
  33. package/dist/utils/date-format.js.map +1 -1
  34. package/dist/utils/json.d.ts.map +1 -1
  35. package/dist/utils/json.js +3 -1
  36. package/dist/utils/json.js.map +1 -1
  37. package/dist/utils/num.d.ts.map +1 -1
  38. package/dist/utils/num.js.map +1 -1
  39. package/dist/utils/obj.d.ts.map +1 -1
  40. package/dist/utils/obj.js +19 -5
  41. package/dist/utils/obj.js.map +1 -1
  42. package/dist/utils/str.d.ts.map +1 -1
  43. package/dist/utils/str.js.map +1 -1
  44. package/dist/utils/transferable.d.ts.map +1 -1
  45. package/dist/utils/transferable.js +18 -4
  46. package/dist/utils/transferable.js.map +1 -1
  47. package/dist/zip/sd-zip.d.ts.map +1 -1
  48. package/dist/zip/sd-zip.js +7 -1
  49. package/dist/zip/sd-zip.js.map +1 -1
  50. package/docs/extensions.md +159 -37
  51. package/docs/features.md +27 -12
  52. package/docs/types.md +159 -21
  53. package/docs/utils.md +112 -8
  54. package/package.json +2 -2
  55. package/src/errors/sd-error.ts +11 -2
  56. package/src/errors/timeout-error.ts +3 -1
  57. package/src/extensions/arr-ext.helpers.ts +4 -1
  58. package/src/extensions/arr-ext.ts +48 -15
  59. package/src/extensions/arr-ext.types.ts +26 -8
  60. package/src/features/event-emitter.ts +7 -2
  61. package/src/types/date-only.ts +19 -6
  62. package/src/types/date-time.ts +70 -9
  63. package/src/types/time.ts +14 -3
  64. package/src/types/uuid.ts +5 -2
  65. package/src/utils/bytes.ts +3 -1
  66. package/src/utils/date-format.ts +4 -2
  67. package/src/utils/json.ts +7 -2
  68. package/src/utils/num.ts +8 -2
  69. package/src/utils/obj.ts +44 -11
  70. package/src/utils/str.ts +13 -4
  71. package/src/utils/transferable.ts +20 -5
  72. package/src/zip/sd-zip.ts +13 -3
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; // 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.isValid; // Validity check
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.addMonths(1); // 1 month later
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
- // Immutable transformations
124
- d.addDays(30);
125
- d.addMonths(-1);
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
- // Reverse calculate date from week
136
- DateOnly.getDateByYearWeekSeq({ year: 2025, weekSeq: 2 }); // 2025-01-06 (Monday)
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
- // 24-hour cycle
158
- t.addHours(12); // 14:30 + 12 hours = 02:30 (cycles, not next day)
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
- objMap(colors, (key, rgb) => [null, `rgb(${rgb})`]); // Transform values only (keep keys)
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
- | `zzz` | Timezone offset | +09:00 |
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"); // ".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 cleanup.
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/core-common",
3
- "version": "13.0.28",
3
+ "version": "13.0.30",
4
4
  "description": "심플리즘 패키지 - 코어 모듈 (common)",
5
5
  "author": "김석래",
6
6
  "license": "Apache-2.0",
@@ -28,7 +28,7 @@
28
28
  "./dist/index.js"
29
29
  ],
30
30
  "dependencies": {
31
- "@zip.js/zip.js": "^2.8.20",
31
+ "@zip.js/zip.js": "^2.8.21",
32
32
  "consola": "^3.4.2",
33
33
  "fast-xml-parser": "^5.3.6",
34
34
  "yaml": "^2.8.2"
@@ -26,7 +26,13 @@ export class SdError extends Error {
26
26
  constructor(arg1?: unknown, ...messages: string[]);
27
27
  constructor(arg1?: unknown, ...messages: string[]) {
28
28
  const arg1Message =
29
- arg1 instanceof Error ? arg1.message : typeof arg1 === "string" ? arg1 : arg1 != null ? String(arg1) : undefined;
29
+ arg1 instanceof Error
30
+ ? arg1.message
31
+ : typeof arg1 === "string"
32
+ ? arg1
33
+ : arg1 != null
34
+ ? String(arg1)
35
+ : undefined;
30
36
 
31
37
  const fullMessage = [arg1Message, ...messages]
32
38
  .filter((m) => m != null)
@@ -42,7 +48,10 @@ export class SdError extends Error {
42
48
 
43
49
  // V8 엔진(Node.js, Chrome)에서만 사용 가능한 captureStackTrace
44
50
  if ("captureStackTrace" in Error) {
45
- (Error.captureStackTrace as (targetObject: object, constructorOpt?: Function) => void)(this, new.target);
51
+ (Error.captureStackTrace as (targetObject: object, constructorOpt?: Function) => void)(
52
+ this,
53
+ new.target,
54
+ );
46
55
  }
47
56
 
48
57
  // cause 체인의 stack을 현재 stack에 추가
@@ -29,7 +29,9 @@ export class TimeoutError extends SdError {
29
29
  */
30
30
  constructor(count?: number, message?: string) {
31
31
  super(
32
- "대기 시간이 초과되었습니다" + (count != null ? `(${count}회)` : "") + (message != null ? `: ${message}` : ""),
32
+ "대기 시간이 초과되었습니다" +
33
+ (count != null ? `(${count}회)` : "") +
34
+ (message != null ? `: ${message}` : ""),
33
35
  );
34
36
  this.name = "TimeoutError";
35
37
  }
@@ -49,5 +49,8 @@ export function compareForOrder(pp: ComparableType, pn: ComparableType, desc: bo
49
49
  return cpn ? (desc ? 1 : -1) : desc ? -1 : 1;
50
50
  }
51
51
 
52
- throw new ArgumentError("orderBy를 사용할 수 없는 타입입니다.", { type1: typeof cpp, type2: typeof cpn });
52
+ throw new ArgumentError("orderBy를 사용할 수 없는 타입입니다.", {
53
+ type1: typeof cpp,
54
+ type2: typeof cpn,
55
+ });
53
56
  }