@simplysm/core-common 13.0.0-beta.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.
Files changed (202) hide show
  1. package/.cache/typecheck-browser.tsbuildinfo +1 -0
  2. package/.cache/typecheck-node.tsbuildinfo +1 -0
  3. package/.cache/typecheck-tests-browser.tsbuildinfo +1 -0
  4. package/.cache/typecheck-tests-node.tsbuildinfo +1 -0
  5. package/README.md +887 -0
  6. package/dist/common.types.d.ts +74 -0
  7. package/dist/common.types.d.ts.map +1 -0
  8. package/dist/common.types.js +5 -0
  9. package/dist/common.types.js.map +7 -0
  10. package/dist/env.d.ts +6 -0
  11. package/dist/env.d.ts.map +1 -0
  12. package/dist/env.js +9 -0
  13. package/dist/env.js.map +7 -0
  14. package/dist/errors/argument-error.d.ts +25 -0
  15. package/dist/errors/argument-error.d.ts.map +1 -0
  16. package/dist/errors/argument-error.js +18 -0
  17. package/dist/errors/argument-error.js.map +7 -0
  18. package/dist/errors/not-implemented-error.d.ts +29 -0
  19. package/dist/errors/not-implemented-error.d.ts.map +1 -0
  20. package/dist/errors/not-implemented-error.js +14 -0
  21. package/dist/errors/not-implemented-error.js.map +7 -0
  22. package/dist/errors/sd-error.d.ts +27 -0
  23. package/dist/errors/sd-error.d.ts.map +1 -0
  24. package/dist/errors/sd-error.js +23 -0
  25. package/dist/errors/sd-error.js.map +7 -0
  26. package/dist/errors/timeout-error.d.ts +31 -0
  27. package/dist/errors/timeout-error.d.ts.map +1 -0
  28. package/dist/errors/timeout-error.js +17 -0
  29. package/dist/errors/timeout-error.js.map +7 -0
  30. package/dist/extensions/arr-ext.d.ts +15 -0
  31. package/dist/extensions/arr-ext.d.ts.map +1 -0
  32. package/dist/extensions/arr-ext.helpers.d.ts +19 -0
  33. package/dist/extensions/arr-ext.helpers.d.ts.map +1 -0
  34. package/dist/extensions/arr-ext.helpers.js +35 -0
  35. package/dist/extensions/arr-ext.helpers.js.map +7 -0
  36. package/dist/extensions/arr-ext.js +546 -0
  37. package/dist/extensions/arr-ext.js.map +7 -0
  38. package/dist/extensions/arr-ext.types.d.ts +215 -0
  39. package/dist/extensions/arr-ext.types.d.ts.map +1 -0
  40. package/dist/extensions/arr-ext.types.js +1 -0
  41. package/dist/extensions/arr-ext.types.js.map +7 -0
  42. package/dist/extensions/map-ext.d.ts +57 -0
  43. package/dist/extensions/map-ext.d.ts.map +1 -0
  44. package/dist/extensions/map-ext.js +26 -0
  45. package/dist/extensions/map-ext.js.map +7 -0
  46. package/dist/extensions/set-ext.d.ts +36 -0
  47. package/dist/extensions/set-ext.d.ts.map +1 -0
  48. package/dist/extensions/set-ext.js +29 -0
  49. package/dist/extensions/set-ext.js.map +7 -0
  50. package/dist/features/debounce-queue.d.ts +53 -0
  51. package/dist/features/debounce-queue.d.ts.map +1 -0
  52. package/dist/features/debounce-queue.js +80 -0
  53. package/dist/features/debounce-queue.js.map +7 -0
  54. package/dist/features/event-emitter.d.ts +66 -0
  55. package/dist/features/event-emitter.d.ts.map +1 -0
  56. package/dist/features/event-emitter.js +82 -0
  57. package/dist/features/event-emitter.js.map +7 -0
  58. package/dist/features/serial-queue.d.ts +47 -0
  59. package/dist/features/serial-queue.d.ts.map +1 -0
  60. package/dist/features/serial-queue.js +66 -0
  61. package/dist/features/serial-queue.js.map +7 -0
  62. package/dist/globals.d.ts +12 -0
  63. package/dist/globals.d.ts.map +1 -0
  64. package/dist/globals.js +1 -0
  65. package/dist/globals.js.map +7 -0
  66. package/dist/index.d.ts +32 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +31 -0
  69. package/dist/index.js.map +7 -0
  70. package/dist/types/date-only.d.ts +152 -0
  71. package/dist/types/date-only.d.ts.map +1 -0
  72. package/dist/types/date-only.js +251 -0
  73. package/dist/types/date-only.js.map +7 -0
  74. package/dist/types/date-time.d.ts +96 -0
  75. package/dist/types/date-time.d.ts.map +1 -0
  76. package/dist/types/date-time.js +220 -0
  77. package/dist/types/date-time.js.map +7 -0
  78. package/dist/types/lazy-gc-map.d.ts +80 -0
  79. package/dist/types/lazy-gc-map.d.ts.map +1 -0
  80. package/dist/types/lazy-gc-map.js +179 -0
  81. package/dist/types/lazy-gc-map.js.map +7 -0
  82. package/dist/types/time.d.ts +68 -0
  83. package/dist/types/time.d.ts.map +1 -0
  84. package/dist/types/time.js +151 -0
  85. package/dist/types/time.js.map +7 -0
  86. package/dist/types/uuid.d.ts +35 -0
  87. package/dist/types/uuid.d.ts.map +1 -0
  88. package/dist/types/uuid.js +71 -0
  89. package/dist/types/uuid.js.map +7 -0
  90. package/dist/utils/bytes.d.ts +51 -0
  91. package/dist/utils/bytes.d.ts.map +1 -0
  92. package/dist/utils/bytes.js +89 -0
  93. package/dist/utils/bytes.js.map +7 -0
  94. package/dist/utils/date-format.d.ts +90 -0
  95. package/dist/utils/date-format.d.ts.map +1 -0
  96. package/dist/utils/date-format.js +106 -0
  97. package/dist/utils/date-format.js.map +7 -0
  98. package/dist/utils/json.d.ts +34 -0
  99. package/dist/utils/json.d.ts.map +1 -0
  100. package/dist/utils/json.js +152 -0
  101. package/dist/utils/json.js.map +7 -0
  102. package/dist/utils/num.d.ts +60 -0
  103. package/dist/utils/num.d.ts.map +1 -0
  104. package/dist/utils/num.js +39 -0
  105. package/dist/utils/num.js.map +7 -0
  106. package/dist/utils/obj.d.ts +258 -0
  107. package/dist/utils/obj.d.ts.map +1 -0
  108. package/dist/utils/obj.js +538 -0
  109. package/dist/utils/obj.js.map +7 -0
  110. package/dist/utils/path.d.ts +23 -0
  111. package/dist/utils/path.d.ts.map +1 -0
  112. package/dist/utils/path.js +21 -0
  113. package/dist/utils/path.js.map +7 -0
  114. package/dist/utils/primitive.d.ts +18 -0
  115. package/dist/utils/primitive.d.ts.map +1 -0
  116. package/dist/utils/primitive.js +20 -0
  117. package/dist/utils/primitive.js.map +7 -0
  118. package/dist/utils/str.d.ts +103 -0
  119. package/dist/utils/str.d.ts.map +1 -0
  120. package/dist/utils/str.js +128 -0
  121. package/dist/utils/str.js.map +7 -0
  122. package/dist/utils/template-strings.d.ts +84 -0
  123. package/dist/utils/template-strings.d.ts.map +1 -0
  124. package/dist/utils/template-strings.js +49 -0
  125. package/dist/utils/template-strings.js.map +7 -0
  126. package/dist/utils/transferable.d.ts +47 -0
  127. package/dist/utils/transferable.d.ts.map +1 -0
  128. package/dist/utils/transferable.js +153 -0
  129. package/dist/utils/transferable.js.map +7 -0
  130. package/dist/utils/wait.d.ts +19 -0
  131. package/dist/utils/wait.d.ts.map +1 -0
  132. package/dist/utils/wait.js +19 -0
  133. package/dist/utils/wait.js.map +7 -0
  134. package/dist/utils/xml.d.ts +36 -0
  135. package/dist/utils/xml.d.ts.map +1 -0
  136. package/dist/utils/xml.js +51 -0
  137. package/dist/utils/xml.js.map +7 -0
  138. package/dist/zip/sd-zip.d.ts +80 -0
  139. package/dist/zip/sd-zip.d.ts.map +1 -0
  140. package/dist/zip/sd-zip.js +153 -0
  141. package/dist/zip/sd-zip.js.map +7 -0
  142. package/package.json +31 -0
  143. package/src/common.types.ts +91 -0
  144. package/src/env.ts +11 -0
  145. package/src/errors/argument-error.ts +40 -0
  146. package/src/errors/not-implemented-error.ts +32 -0
  147. package/src/errors/sd-error.ts +53 -0
  148. package/src/errors/timeout-error.ts +36 -0
  149. package/src/extensions/arr-ext.helpers.ts +53 -0
  150. package/src/extensions/arr-ext.ts +777 -0
  151. package/src/extensions/arr-ext.types.ts +258 -0
  152. package/src/extensions/map-ext.ts +86 -0
  153. package/src/extensions/set-ext.ts +68 -0
  154. package/src/features/debounce-queue.ts +116 -0
  155. package/src/features/event-emitter.ts +112 -0
  156. package/src/features/serial-queue.ts +94 -0
  157. package/src/globals.ts +12 -0
  158. package/src/index.ts +55 -0
  159. package/src/types/date-only.ts +329 -0
  160. package/src/types/date-time.ts +294 -0
  161. package/src/types/lazy-gc-map.ts +244 -0
  162. package/src/types/time.ts +210 -0
  163. package/src/types/uuid.ts +113 -0
  164. package/src/utils/bytes.ts +160 -0
  165. package/src/utils/date-format.ts +239 -0
  166. package/src/utils/json.ts +230 -0
  167. package/src/utils/num.ts +97 -0
  168. package/src/utils/obj.ts +956 -0
  169. package/src/utils/path.ts +40 -0
  170. package/src/utils/primitive.ts +33 -0
  171. package/src/utils/str.ts +252 -0
  172. package/src/utils/template-strings.ts +132 -0
  173. package/src/utils/transferable.ts +269 -0
  174. package/src/utils/wait.ts +40 -0
  175. package/src/utils/xml.ts +105 -0
  176. package/src/zip/sd-zip.ts +218 -0
  177. package/tests/errors/errors.spec.ts +196 -0
  178. package/tests/extensions/array-extension.spec.ts +790 -0
  179. package/tests/extensions/map-extension.spec.ts +147 -0
  180. package/tests/extensions/set-extension.spec.ts +74 -0
  181. package/tests/types/date-only.spec.ts +636 -0
  182. package/tests/types/date-time.spec.ts +391 -0
  183. package/tests/types/lazy-gc-map.spec.ts +692 -0
  184. package/tests/types/time.spec.ts +559 -0
  185. package/tests/types/types.spec.ts +55 -0
  186. package/tests/types/uuid.spec.ts +91 -0
  187. package/tests/utils/bytes-utils.spec.ts +230 -0
  188. package/tests/utils/date-format.spec.ts +371 -0
  189. package/tests/utils/debounce-queue.spec.ts +272 -0
  190. package/tests/utils/json.spec.ts +475 -0
  191. package/tests/utils/number.spec.ts +184 -0
  192. package/tests/utils/object.spec.ts +827 -0
  193. package/tests/utils/path.spec.ts +78 -0
  194. package/tests/utils/primitive.spec.ts +55 -0
  195. package/tests/utils/sd-event-emitter.spec.ts +216 -0
  196. package/tests/utils/serial-queue.spec.ts +365 -0
  197. package/tests/utils/string.spec.ts +294 -0
  198. package/tests/utils/template-strings.spec.ts +96 -0
  199. package/tests/utils/transferable.spec.ts +698 -0
  200. package/tests/utils/wait.spec.ts +145 -0
  201. package/tests/utils/xml.spec.ts +146 -0
  202. package/tests/zip/sd-zip.spec.ts +234 -0
package/README.md ADDED
@@ -0,0 +1,887 @@
1
+ # @simplysm/core-common
2
+
3
+ A common utility package for the Simplysm framework.
4
+ As a neutral base module usable in both Node.js and browser environments, it provides date/time types, error classes, object/array/string utilities, JSON serialization, ZIP processing, prototype extensions, and more.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install @simplysm/core-common
10
+ # or
11
+ pnpm add @simplysm/core-common
12
+ ```
13
+
14
+ ## Initialization
15
+
16
+ Import the package at your application entry point (e.g., `index.ts`, `main.ts`):
17
+
18
+ ```typescript
19
+ import "@simplysm/core-common";
20
+ ```
21
+
22
+ This import globally activates Array, Map, and Set prototype extensions.
23
+ To use extension methods (`getOrCreate()`, `toggle()`, etc.), you must import this at app startup.
24
+
25
+ ## Main Modules
26
+
27
+ ### Errors
28
+
29
+ Custom error classes. All are based on `SdError` and support cause chaining.
30
+
31
+ | Class | Description |
32
+ |--------|------|
33
+ | `SdError` | Base error class (error tracking with cause chain, automatic nested stack integration) |
34
+ | `ArgumentError` | Argument validation error (YAML formatting) |
35
+ | `NotImplementedError` | Indicates unimplemented functionality |
36
+ | `TimeoutError` | Timeout error |
37
+
38
+ ```typescript
39
+ import { SdError, ArgumentError, NotImplementedError, TimeoutError } from "@simplysm/core-common";
40
+
41
+ // SdError: track errors with cause chain
42
+ try {
43
+ await fetch(url);
44
+ } catch (err) {
45
+ throw new SdError(err, "API call failed", "Failed to load user");
46
+ // Result message: "Failed to load user => API call failed => original error message"
47
+ }
48
+
49
+ // ArgumentError: output argument object in YAML format
50
+ throw new ArgumentError("Invalid user", { userId: 123 });
51
+ // Result message: "Invalid user\n\nuserId: 123"
52
+
53
+ // NotImplementedError: indicate unimplemented branch
54
+ switch (type) {
55
+ case "A": return handleA();
56
+ case "B": throw new NotImplementedError(`Handling type ${type}`);
57
+ }
58
+
59
+ // TimeoutError: wait time exceeded
60
+ throw new TimeoutError(5, "API response wait exceeded");
61
+ // Result message: "Wait time exceeded(5): API response wait exceeded"
62
+ ```
63
+
64
+ ### Types
65
+
66
+ Immutable custom type classes. All transformation methods return new instances.
67
+
68
+ | Class | Description |
69
+ |--------|------|
70
+ | `DateTime` | Date + time (millisecond precision, local timezone) |
71
+ | `DateOnly` | Date only (no time) |
72
+ | `Time` | Time only (no date, 24-hour cycle) |
73
+ | `Uuid` | UUID v4 (based on `crypto.getRandomValues`) |
74
+ | `LazyGcMap` | Map with auto-expiration (LRU style) |
75
+
76
+ #### DateTime
77
+
78
+ ```typescript
79
+ import { DateTime } from "@simplysm/core-common";
80
+
81
+ // Creation
82
+ const now = new DateTime(); // Current time
83
+ const dt = new DateTime(2025, 1, 15, 10, 30, 0); // Year, month, day, hour, minute, second
84
+ const fromTick = new DateTime(1705312200000); // Tick (milliseconds)
85
+ const fromDate = new DateTime(new Date()); // Date object
86
+
87
+ // Parsing
88
+ DateTime.parse("2025-01-15 10:30:00"); // yyyy-MM-dd HH:mm:ss
89
+ DateTime.parse("2025-01-15 10:30:00.123"); // yyyy-MM-dd HH:mm:ss.fff
90
+ DateTime.parse("20250115103000"); // yyyyMMddHHmmss
91
+ DateTime.parse("2025-01-15 오전 10:30:00"); // Korean AM/PM
92
+ DateTime.parse("2025-01-15T10:30:00Z"); // ISO 8601
93
+
94
+ // Properties (read-only)
95
+ dt.year; // 2025
96
+ dt.month; // 1 (1-12)
97
+ dt.day; // 15
98
+ dt.hour; // 10
99
+ dt.minute; // 30
100
+ dt.second; // 0
101
+ dt.millisecond; // 0
102
+ dt.tick; // Millisecond timestamp
103
+ dt.dayOfWeek; // Day of week (Sun~Sat: 0~6)
104
+ dt.isValid; // Validity check
105
+
106
+ // Immutable transformations (return new instances)
107
+ dt.setYear(2026); // Change year
108
+ dt.setMonth(3); // Change month (day auto-adjusted)
109
+ dt.addDays(7); // 7 days later
110
+ dt.addHours(-2); // 2 hours ago
111
+ dt.addMonths(1); // 1 month later
112
+
113
+ // Formatting
114
+ dt.toFormatString("yyyy-MM-dd"); // "2025-01-15"
115
+ dt.toFormatString("yyyy년 M월 d일 (ddd)"); // "2025년 1월 15일 (수)"
116
+ dt.toFormatString("tt h:mm:ss"); // "오전 10:30:00"
117
+ dt.toString(); // "2025-01-15T10:30:00.000+09:00"
118
+ ```
119
+
120
+ #### DateOnly
121
+
122
+ ```typescript
123
+ import { DateOnly } from "@simplysm/core-common";
124
+
125
+ // Creation and parsing
126
+ const today = new DateOnly();
127
+ const d = new DateOnly(2025, 1, 15);
128
+ DateOnly.parse("2025-01-15"); // No timezone influence
129
+ DateOnly.parse("20250115"); // No timezone influence
130
+
131
+ // Immutable transformations
132
+ d.addDays(30);
133
+ d.addMonths(-1);
134
+ d.setMonth(2); // Jan 31 -> Feb 28 (auto-adjusted)
135
+
136
+ // Week calculation (ISO 8601 standard)
137
+ d.getWeekSeqOfYear(); // { year: 2025, weekSeq: 3 }
138
+ d.getWeekSeqOfMonth(); // { year: 2025, monthSeq: 1, weekSeq: 3 }
139
+
140
+ // US-style week (Sunday start, first week with 1+ days)
141
+ d.getWeekSeqOfYear(0, 1);
142
+
143
+ // Reverse calculate date from week
144
+ DateOnly.getDateByYearWeekSeq({ year: 2025, weekSeq: 2 }); // 2025-01-06 (Monday)
145
+
146
+ // Formatting
147
+ d.toFormatString("yyyy년 MM월 dd일"); // "2025년 01월 15일"
148
+ d.toString(); // "2025-01-15"
149
+ ```
150
+
151
+ #### Time
152
+
153
+ ```typescript
154
+ import { Time } from "@simplysm/core-common";
155
+
156
+ // Creation and parsing
157
+ const now = new Time();
158
+ const t = new Time(14, 30, 0);
159
+ Time.parse("14:30:00"); // HH:mm:ss
160
+ Time.parse("14:30:00.123"); // HH:mm:ss.fff
161
+ Time.parse("오후 2:30:00"); // Korean AM/PM
162
+
163
+ // 24-hour cycle
164
+ t.addHours(12); // 14:30 + 12 hours = 02:30 (cycles, not next day)
165
+ t.addMinutes(-60); // 14:30 - 60 minutes = 13:30
166
+
167
+ // Formatting
168
+ t.toFormatString("tt h:mm"); // "오후 2:30"
169
+ t.toString(); // "14:30:00.000"
170
+ ```
171
+
172
+ #### Uuid
173
+
174
+ ```typescript
175
+ import { Uuid } from "@simplysm/core-common";
176
+
177
+ // Generate new UUID (cryptographically secure)
178
+ const id = Uuid.new();
179
+
180
+ // Create from string
181
+ const fromStr = new Uuid("550e8400-e29b-41d4-a716-446655440000");
182
+
183
+ // Byte conversion
184
+ const bytes = id.toBytes(); // Uint8Array (16 bytes)
185
+ const fromBytes = Uuid.fromBytes(bytes);
186
+
187
+ id.toString(); // "550e8400-e29b-41d4-a716-446655440000"
188
+ ```
189
+
190
+ #### LazyGcMap
191
+
192
+ ```typescript
193
+ import { LazyGcMap } from "@simplysm/core-common";
194
+
195
+ // using statement (recommended)
196
+ using map = new LazyGcMap<string, object>({
197
+ gcInterval: 10000, // GC execution interval: 10 seconds
198
+ expireTime: 60000, // Item expiration time: 60 seconds
199
+ onExpire: (key, value) => {
200
+ console.log(`Expired: ${key}`);
201
+ },
202
+ });
203
+
204
+ map.set("key1", { data: "hello" });
205
+ map.get("key1"); // Refreshes access time (LRU)
206
+ map.getOrCreate("key2", () => ({})); // Create and return if not exists
207
+ map.has("key1"); // Does not refresh access time
208
+ map.delete("key1");
209
+ ```
210
+
211
+ ### Features
212
+
213
+ Async operation control and event handling classes. All support `using` statements or `dispose()`.
214
+
215
+ | Class | Description |
216
+ |--------|------|
217
+ | `DebounceQueue` | Async debounce queue (executes only last request) |
218
+ | `SerialQueue` | Async serial queue (sequential execution) |
219
+ | `EventEmitter` | EventTarget wrapper (type-safe events) |
220
+
221
+ #### DebounceQueue
222
+
223
+ ```typescript
224
+ import { DebounceQueue } from "@simplysm/core-common";
225
+
226
+ using queue = new DebounceQueue(300); // 300ms debounce
227
+
228
+ // Error handling
229
+ queue.on("error", (err) => console.error(err));
230
+
231
+ // Only last call is executed
232
+ queue.run(() => console.log("1")); // Ignored
233
+ queue.run(() => console.log("2")); // Ignored
234
+ queue.run(() => console.log("3")); // Executed after 300ms
235
+ ```
236
+
237
+ #### SerialQueue
238
+
239
+ ```typescript
240
+ import { SerialQueue } from "@simplysm/core-common";
241
+
242
+ using queue = new SerialQueue(100); // 100ms interval between tasks
243
+
244
+ queue.on("error", (err) => console.error(err));
245
+
246
+ queue.run(async () => { await fetch("/api/1"); });
247
+ queue.run(async () => { await fetch("/api/2"); }); // Runs after #1 completes
248
+ queue.run(async () => { await fetch("/api/3"); }); // Runs after #2 completes
249
+ ```
250
+
251
+ #### EventEmitter
252
+
253
+ ```typescript
254
+ import { EventEmitter } from "@simplysm/core-common";
255
+
256
+ interface MyEvents {
257
+ data: string;
258
+ error: Error;
259
+ done: void;
260
+ }
261
+
262
+ class MyService extends EventEmitter<MyEvents> {
263
+ process(): void {
264
+ this.emit("data", "result data");
265
+ this.emit("done"); // void type called without arguments
266
+ }
267
+ }
268
+
269
+ const service = new MyService();
270
+ service.on("data", (data) => console.log(data)); // data: string (type inferred)
271
+ service.off("data", listener); // Remove listener
272
+ service.listenerCount("data"); // Number of registered listeners
273
+ service.dispose(); // Remove all listeners
274
+ ```
275
+
276
+ ### Utils
277
+
278
+ Utility functions.
279
+
280
+ #### Object utilities (obj)
281
+
282
+ | Function | Description |
283
+ |------|------|
284
+ | `objClone` | Deep clone (supports circular references, custom types) |
285
+ | `objEqual` | Deep comparison (include/exclude keys, array order ignore option) |
286
+ | `objMerge` | Deep merge (source + target, array processing option) |
287
+ | `objMerge3` | 3-way merge (conflict detection) |
288
+ | `objOmit` | Exclude specific keys |
289
+ | `objPick` | Select specific keys |
290
+ | `objGetChainValue` | Query value by chain path (`"a.b[0].c"`) |
291
+ | `objSetChainValue` | Set value by chain path |
292
+ | `objDeleteChainValue` | Delete value by chain path |
293
+ | `objKeys` | Type-safe `Object.keys` |
294
+ | `objEntries` | Type-safe `Object.entries` |
295
+ | `objFromEntries` | Type-safe `Object.fromEntries` |
296
+ | `objMap` | Transform each entry of object and return new object |
297
+
298
+ ```typescript
299
+ import {
300
+ objClone, objEqual, objMerge, objMerge3,
301
+ objOmit, objPick, objGetChainValue, objSetChainValue,
302
+ objKeys, objEntries, objMap,
303
+ } from "@simplysm/core-common";
304
+
305
+ // Deep clone (supports custom types like DateTime, Uuid)
306
+ const cloned = objClone({ date: new DateTime(), nested: { arr: [1, 2] } });
307
+
308
+ // Deep comparison
309
+ objEqual({ a: 1, b: [2] }, { a: 1, b: [2] }); // true
310
+ objEqual(arr1, arr2, { ignoreArrayIndex: true }); // Ignore array order
311
+ objEqual(obj1, obj2, { topLevelExcludes: ["updatedAt"] }); // Exclude specific keys
312
+
313
+ // Deep merge
314
+ objMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 } });
315
+ // { a: 1, b: { c: 2, d: 3 } }
316
+
317
+ // 3-way merge (conflict detection)
318
+ const { conflict, result } = objMerge3(
319
+ { a: 1, b: 2 }, // source (change #1)
320
+ { a: 1, b: 1 }, // origin (base)
321
+ { a: 2, b: 1 }, // target (change #2)
322
+ );
323
+ // conflict: false, result: { a: 2, b: 2 }
324
+
325
+ // Key selection/exclusion
326
+ objOmit(user, ["password", "email"]);
327
+ objPick(user, ["name", "age"]);
328
+
329
+ // Chain path
330
+ objGetChainValue(obj, "a.b[0].c");
331
+ objSetChainValue(obj, "a.b[0].c", "value");
332
+
333
+ // Type-safe Object utilities
334
+ objKeys(obj); // (keyof typeof obj)[]
335
+ objEntries(obj); // [keyof typeof obj, typeof obj[keyof typeof obj]][]
336
+ objMap(colors, (key, rgb) => [null, `rgb(${rgb})`]); // Transform values only (keep keys)
337
+ ```
338
+
339
+ #### JSON utilities (json)
340
+
341
+ | Function | Description |
342
+ |------|------|
343
+ | `jsonStringify` | JSON serialization with custom type support |
344
+ | `jsonParse` | JSON deserialization with custom type restoration |
345
+
346
+ Serializes/restores `DateTime`, `DateOnly`, `Time`, `Uuid`, `Date`, `Set`, `Map`, `Error`, `Uint8Array` types using `__type__` metadata.
347
+
348
+ ```typescript
349
+ import { jsonStringify, jsonParse, DateTime, Uuid } from "@simplysm/core-common";
350
+
351
+ const data = {
352
+ createdAt: new DateTime(2025, 1, 15),
353
+ id: Uuid.new(),
354
+ tags: new Set(["a", "b"]),
355
+ meta: new Map([["key", "value"]]),
356
+ file: new Uint8Array([1, 2, 3]),
357
+ };
358
+
359
+ // Serialization (preserves custom types)
360
+ const json = jsonStringify(data, { space: 2 });
361
+
362
+ // Deserialization (restores custom types)
363
+ const parsed = jsonParse(json);
364
+ // parsed.createdAt instanceof DateTime === true
365
+ // parsed.id instanceof Uuid === true
366
+ // parsed.tags instanceof Set === true
367
+
368
+ // For logging: hide binary data
369
+ jsonStringify(data, { redactBytes: true });
370
+ // Uint8Array content replaced with "__hidden__"
371
+ ```
372
+
373
+ #### XML utilities (xml)
374
+
375
+ | Function | Description |
376
+ |------|------|
377
+ | `xmlParse` | Parse XML string to object (attributes: `$`, text: `_`) |
378
+ | `xmlStringify` | Serialize object to XML string |
379
+
380
+ ```typescript
381
+ import { xmlParse, xmlStringify } from "@simplysm/core-common";
382
+
383
+ const obj = xmlParse('<root id="1"><item>hello</item></root>');
384
+ // { root: { $: { id: "1" }, item: [{ _: "hello" }] } }
385
+
386
+ const xml = xmlStringify(obj);
387
+ // '<root id="1"><item>hello</item></root>'
388
+
389
+ // Remove namespace prefix
390
+ xmlParse('<ns:root><ns:item>text</ns:item></ns:root>', { stripTagPrefix: true });
391
+ // { root: { item: [{ _: "text" }] } }
392
+ ```
393
+
394
+ #### String utilities (str)
395
+
396
+ | Function | Description |
397
+ |------|------|
398
+ | `strGetSuffix` | Korean postposition handling (을/를, 은/는, 이/가, 과/와, 이랑/랑, 으로/로, 이라/라) |
399
+ | `strReplaceFullWidth` | Convert full-width characters to half-width |
400
+ | `strToPascalCase` | PascalCase conversion |
401
+ | `strToCamelCase` | camelCase conversion |
402
+ | `strToKebabCase` | kebab-case conversion |
403
+ | `strToSnakeCase` | snake_case conversion |
404
+ | `strIsNullOrEmpty` | Check for undefined/null/empty string (type guard) |
405
+ | `strInsert` | Insert at specific position in string |
406
+
407
+ ```typescript
408
+ import {
409
+ strGetSuffix, strToCamelCase, strToKebabCase,
410
+ strIsNullOrEmpty, strReplaceFullWidth,
411
+ } from "@simplysm/core-common";
412
+
413
+ // Korean postposition
414
+ strGetSuffix("사과", "을"); // "를"
415
+ strGetSuffix("책", "이"); // "이"
416
+ strGetSuffix("서울", "로"); // "로" (ㄹ final consonant uses "로")
417
+
418
+ // Case conversion
419
+ strToCamelCase("hello-world"); // "helloWorld"
420
+ strToKebabCase("HelloWorld"); // "hello-world"
421
+
422
+ // Empty string check (type guard)
423
+ if (strIsNullOrEmpty(name)) {
424
+ // name: "" | undefined
425
+ } else {
426
+ // name: string (non-empty string)
427
+ }
428
+
429
+ // Full-width -> half-width
430
+ strReplaceFullWidth("A123(株)"); // "A123(株)"
431
+ ```
432
+
433
+ #### Number utilities (num)
434
+
435
+ | Function | Description |
436
+ |------|------|
437
+ | `numParseInt` | Parse string to integer (remove non-digit characters) |
438
+ | `numParseFloat` | Parse string to float |
439
+ | `numParseRoundedInt` | Round float and return integer |
440
+ | `numFormat` | Thousands separator formatting |
441
+ | `numIsNullOrEmpty` | Check for undefined/null/0 (type guard) |
442
+
443
+ ```typescript
444
+ import { numParseInt, numParseFloat, numFormat, numIsNullOrEmpty } from "@simplysm/core-common";
445
+
446
+ numParseInt("12,345원"); // 12345
447
+ numParseFloat("3.14%"); // 3.14
448
+ numFormat(1234567, { max: 2 }); // "1,234,567"
449
+ numFormat(1234, { min: 2, max: 2 }); // "1,234.00"
450
+
451
+ if (numIsNullOrEmpty(count)) {
452
+ // count: 0 | undefined
453
+ }
454
+ ```
455
+
456
+ #### Date/time formatting (date-format)
457
+
458
+ | Function | Description |
459
+ |------|------|
460
+ | `formatDate` | Convert date/time to string according to format string |
461
+ | `normalizeMonth` | Normalize year/month/day when setting month |
462
+
463
+ Supports the same format strings as C#:
464
+
465
+ | Format | Description | Example |
466
+ |------|------|------|
467
+ | `yyyy` | 4-digit year | 2024 |
468
+ | `yy` | 2-digit year | 24 |
469
+ | `MM` | 0-padded month | 01~12 |
470
+ | `M` | Month | 1~12 |
471
+ | `ddd` | Day of week (Korean) | 일, 월, 화, 수, 목, 금, 토 |
472
+ | `dd` | 0-padded day | 01~31 |
473
+ | `d` | Day | 1~31 |
474
+ | `tt` | AM/PM | 오전, 오후 |
475
+ | `HH` | 0-padded 24-hour | 00~23 |
476
+ | `hh` | 0-padded 12-hour | 01~12 |
477
+ | `mm` | 0-padded minute | 00~59 |
478
+ | `ss` | 0-padded second | 00~59 |
479
+ | `fff` | Millisecond (3 digits) | 000~999 |
480
+ | `zzz` | Timezone offset | +09:00 |
481
+
482
+ ```typescript
483
+ import { formatDate, normalizeMonth } from "@simplysm/core-common";
484
+
485
+ formatDate("yyyy-MM-dd", { year: 2024, month: 3, day: 15 });
486
+ // "2024-03-15"
487
+
488
+ formatDate("yyyy년 M월 d일 (ddd)", { year: 2024, month: 3, day: 15 });
489
+ // "2024년 3월 15일 (금)"
490
+
491
+ normalizeMonth(2025, 13, 15); // { year: 2026, month: 1, day: 15 }
492
+ normalizeMonth(2025, 2, 31); // { year: 2025, month: 2, day: 28 }
493
+ ```
494
+
495
+ #### Byte utilities (bytes)
496
+
497
+ | Function | Description |
498
+ |------|------|
499
+ | `bytesConcat` | Concatenate multiple Uint8Arrays |
500
+ | `bytesToHex` | Convert Uint8Array to hex string |
501
+ | `bytesFromHex` | Convert hex string to Uint8Array |
502
+ | `bytesToBase64` | Convert Uint8Array to base64 string |
503
+ | `bytesFromBase64` | Convert base64 string to Uint8Array |
504
+
505
+ ```typescript
506
+ import { bytesConcat, bytesToHex, bytesFromHex, bytesToBase64, bytesFromBase64 } from "@simplysm/core-common";
507
+
508
+ bytesConcat([new Uint8Array([1, 2]), new Uint8Array([3, 4])]);
509
+ // Uint8Array([1, 2, 3, 4])
510
+
511
+ bytesToHex(new Uint8Array([255, 0, 127])); // "ff007f"
512
+ bytesFromHex("ff007f"); // Uint8Array([255, 0, 127])
513
+
514
+ bytesToBase64(new Uint8Array([72, 101, 108, 108, 111])); // "SGVsbG8="
515
+ bytesFromBase64("SGVsbG8="); // Uint8Array([72, 101, 108, 108, 111])
516
+ ```
517
+
518
+ #### Async wait (wait)
519
+
520
+ | Function | Description |
521
+ |------|------|
522
+ | `waitTime` | Wait for specified time |
523
+ | `waitUntil` | Wait until condition is true (max attempts limit) |
524
+
525
+ ```typescript
526
+ import { waitTime, waitUntil } from "@simplysm/core-common";
527
+
528
+ await waitTime(1000); // Wait 1 second
529
+
530
+ // Wait for condition (100ms interval, max 50 attempts = 5 seconds)
531
+ await waitUntil(() => isReady, 100, 50);
532
+ // Throws TimeoutError after 50 attempts
533
+ ```
534
+
535
+ #### Worker data conversion (transferable)
536
+
537
+ | Function | Description |
538
+ |------|------|
539
+ | `transferableEncode` | Serialize custom types into Worker-transferable form |
540
+ | `transferableDecode` | Deserialize Worker-received data to custom types |
541
+
542
+ ```typescript
543
+ import { transferableEncode, transferableDecode } from "@simplysm/core-common";
544
+
545
+ // Send to Worker
546
+ const { result, transferList } = transferableEncode(data);
547
+ worker.postMessage(result, transferList);
548
+
549
+ // Receive from Worker
550
+ const decoded = transferableDecode(event.data);
551
+ ```
552
+
553
+ #### Path utilities (path)
554
+
555
+ Replacement for Node.js `path` module. Supports POSIX-style paths (`/`) only.
556
+
557
+ | Function | Description |
558
+ |------|------|
559
+ | `pathJoin` | Combine paths (`path.join` replacement) |
560
+ | `pathBasename` | Extract filename (`path.basename` replacement) |
561
+ | `pathExtname` | Extract extension (`path.extname` replacement) |
562
+
563
+ ```typescript
564
+ import { pathJoin, pathBasename, pathExtname } from "@simplysm/core-common";
565
+
566
+ pathJoin("/home", "user", "file.txt"); // "/home/user/file.txt"
567
+ pathBasename("/home/user/file.txt"); // "file.txt"
568
+ pathBasename("file.txt", ".txt"); // "file"
569
+ pathExtname("file.txt"); // ".txt"
570
+ ```
571
+
572
+ #### Template literal tags (template-strings)
573
+
574
+ Tag functions for IDE code highlighting. Actual behavior is string combination + indentation cleanup.
575
+
576
+ | Function | Description |
577
+ |------|------|
578
+ | `js` | JavaScript code highlighting |
579
+ | `ts` | TypeScript code highlighting |
580
+ | `html` | HTML markup highlighting |
581
+ | `tsql` | MSSQL T-SQL highlighting |
582
+ | `mysql` | MySQL SQL highlighting |
583
+ | `pgsql` | PostgreSQL SQL highlighting |
584
+
585
+ ```typescript
586
+ import { tsql } from "@simplysm/core-common";
587
+
588
+ const query = tsql`
589
+ SELECT TOP 10 *
590
+ FROM Users
591
+ WHERE Name LIKE '%${keyword}%'
592
+ `;
593
+ ```
594
+
595
+ #### Other utilities
596
+
597
+ | Function/Type | Description |
598
+ |-----------|------|
599
+ | `getPrimitiveTypeStr` | Infer `PrimitiveTypeStr` from runtime value |
600
+ | `env` | Environment variable object (`DEV`, `VER`, etc.) |
601
+
602
+ ```typescript
603
+ import { getPrimitiveTypeStr, env } from "@simplysm/core-common";
604
+
605
+ getPrimitiveTypeStr("hello"); // "string"
606
+ getPrimitiveTypeStr(123); // "number"
607
+ getPrimitiveTypeStr(new DateTime()); // "DateTime"
608
+
609
+ if (env.DEV) {
610
+ console.log("Development mode");
611
+ }
612
+ console.log(`Version: ${env.VER}`);
613
+ ```
614
+
615
+ ### Zip
616
+
617
+ ZIP file compression/decompression utility. Resources can be auto-cleaned with `await using`.
618
+
619
+ | Class | Description |
620
+ |--------|------|
621
+ | `ZipArchive` | ZIP file read/write/compress/extract |
622
+
623
+ ```typescript
624
+ import { ZipArchive } from "@simplysm/core-common";
625
+
626
+ // Read ZIP file
627
+ await using archive = new ZipArchive(zipBytes);
628
+ const content = await archive.get("file.txt");
629
+ const exists = await archive.exists("data.json");
630
+
631
+ // Extract all (with progress)
632
+ const files = await archive.extractAll((progress) => {
633
+ console.log(`${progress.fileName}: ${progress.extractedSize}/${progress.totalSize}`);
634
+ });
635
+
636
+ // Create ZIP file
637
+ await using newArchive = new ZipArchive();
638
+ newArchive.write("file.txt", textBytes);
639
+ newArchive.write("data.json", jsonBytes);
640
+ const zipBytes = await newArchive.compress();
641
+ ```
642
+
643
+ ### Type Utilities
644
+
645
+ TypeScript utility types.
646
+
647
+ | Type | Description |
648
+ |------|------|
649
+ | `Bytes` | Alias for `Uint8Array` (`Buffer` replacement) |
650
+ | `PrimitiveTypeStr` | Primitive type string keys (`"string"`, `"number"`, `"boolean"`, `"DateTime"`, `"DateOnly"`, `"Time"`, `"Uuid"`, `"Bytes"`) |
651
+ | `PrimitiveTypeMap` | Mapping from `PrimitiveTypeStr` to actual type |
652
+ | `PrimitiveType` | Union of all Primitive types |
653
+ | `DeepPartial<T>` | Recursively convert all properties to optional |
654
+ | `Type<T>` | Constructor type (for dependency injection, factory patterns) |
655
+ | `ObjUndefToOptional<T>` | Convert properties with `undefined` to optional |
656
+ | `ObjOptionalToUndef<T>` | Convert optional properties to `required + undefined` union |
657
+
658
+ ```typescript
659
+ import type { DeepPartial, Type, Bytes } from "@simplysm/core-common";
660
+
661
+ // DeepPartial: deep Partial
662
+ interface Config {
663
+ db: { host: string; port: number };
664
+ }
665
+ const partial: DeepPartial<Config> = { db: { host: "localhost" } };
666
+
667
+ // Type: constructor type
668
+ function create<T>(ctor: Type<T>): T {
669
+ return new ctor();
670
+ }
671
+
672
+ // Bytes: Buffer replacement
673
+ const data: Bytes = new Uint8Array([1, 2, 3]);
674
+ ```
675
+
676
+ ### Extensions
677
+
678
+ Array, Map, Set prototype extensions. Activated by `import "@simplysm/core-common"`.
679
+
680
+ #### Array extension methods
681
+
682
+ **Query**:
683
+
684
+ | Method | Description |
685
+ |--------|------|
686
+ | `single(predicate?)` | Return single element (error if 2+) |
687
+ | `first(predicate?)` | Return first element |
688
+ | `last(predicate?)` | Return last element |
689
+
690
+ **Filtering**:
691
+
692
+ | Method | Description |
693
+ |--------|------|
694
+ | `filterExists()` | Remove `null`/`undefined` |
695
+ | `ofType(type)` | Filter by type (`PrimitiveTypeStr` or constructor) |
696
+ | `filterAsync(predicate)` | Async filter |
697
+
698
+ **Mapping/Transformation**:
699
+
700
+ | Method | Description |
701
+ |--------|------|
702
+ | `mapAsync(selector)` | Async mapping (sequential execution) |
703
+ | `mapMany(selector?)` | flat + filterExists |
704
+ | `mapManyAsync(selector?)` | Async mapMany |
705
+ | `parallelAsync(fn)` | Parallel async mapping (`Promise.all`) |
706
+
707
+ **Grouping/Transformation**:
708
+
709
+ | Method | Description |
710
+ |--------|------|
711
+ | `groupBy(keySelector, valueSelector?)` | Group by key |
712
+ | `toMap(keySelector, valueSelector?)` | Convert to Map (error on duplicate key) |
713
+ | `toMapAsync(keySelector, valueSelector?)` | Async Map conversion |
714
+ | `toArrayMap(keySelector, valueSelector?)` | Convert to `Map<K, V[]>` |
715
+ | `toSetMap(keySelector, valueSelector?)` | Convert to `Map<K, Set<V>>` |
716
+ | `toMapValues(keySelector, valueSelector)` | Aggregate Map by group |
717
+ | `toObject(keySelector, valueSelector?)` | Convert to `Record<string, V>` |
718
+ | `toTree(key, parentKey)` | Convert to tree structure |
719
+
720
+ **Deduplication**:
721
+
722
+ | Method | Description |
723
+ |--------|------|
724
+ | `distinct(options?)` | Remove duplicates (return new array) |
725
+ | `distinctThis(options?)` | Remove duplicates (modify original) |
726
+
727
+ **Sorting**:
728
+
729
+ | Method | Description |
730
+ |--------|------|
731
+ | `orderBy(selector?)` | Ascending sort (return new array) |
732
+ | `orderByDesc(selector?)` | Descending sort (return new array) |
733
+ | `orderByThis(selector?)` | Ascending sort (modify original) |
734
+ | `orderByDescThis(selector?)` | Descending sort (modify original) |
735
+
736
+ **Comparison/Merging**:
737
+
738
+ | Method | Description |
739
+ |--------|------|
740
+ | `diffs(target, options?)` | Compare differences between two arrays |
741
+ | `oneWayDiffs(orgItems, keyProp, options?)` | One-way diff comparison (create/update/same) |
742
+ | `merge(target, options?)` | Merge arrays |
743
+
744
+ **Aggregation**:
745
+
746
+ | Method | Description |
747
+ |--------|------|
748
+ | `sum(selector?)` | Sum |
749
+ | `min(selector?)` | Minimum |
750
+ | `max(selector?)` | Maximum |
751
+
752
+ **Mutation (modify original)**:
753
+
754
+ | Method | Description |
755
+ |--------|------|
756
+ | `insert(index, ...items)` | Insert at specific position |
757
+ | `remove(itemOrSelector)` | Remove item |
758
+ | `toggle(item)` | Remove if exists, add if not |
759
+ | `clear()` | Remove all items |
760
+ | `shuffle()` | Shuffle (return new array) |
761
+
762
+ ```typescript
763
+ import "@simplysm/core-common";
764
+
765
+ const users = [
766
+ { id: 1, name: "Alice", dept: "dev" },
767
+ { id: 2, name: "Bob", dept: "dev" },
768
+ { id: 3, name: "Charlie", dept: "hr" },
769
+ ];
770
+
771
+ // Query
772
+ users.single((u) => u.id === 1); // { id: 1, ... }
773
+ users.first(); // { id: 1, ... }
774
+ users.last(); // { id: 3, ... }
775
+
776
+ // Grouping
777
+ users.groupBy((u) => u.dept);
778
+ // [{ key: "dev", values: [...] }, { key: "hr", values: [...] }]
779
+
780
+ // Map conversion
781
+ users.toMap((u) => u.id); // Map<number, User>
782
+ users.toArrayMap((u) => u.dept); // Map<string, User[]>
783
+
784
+ // Sorting
785
+ users.orderBy((u) => u.name);
786
+ users.orderByDesc((u) => u.id);
787
+
788
+ // Filtering
789
+ [1, null, 2, undefined, 3].filterExists(); // [1, 2, 3]
790
+
791
+ // Deduplication
792
+ [1, 2, 2, 3, 3].distinct(); // [1, 2, 3]
793
+
794
+ // Async mapping (sequential execution)
795
+ await ids.mapAsync(async (id) => await fetchUser(id));
796
+
797
+ // Parallel async mapping
798
+ await ids.parallelAsync(async (id) => await fetchUser(id));
799
+ ```
800
+
801
+ #### Map extension methods
802
+
803
+ | Method | Description |
804
+ |--------|------|
805
+ | `getOrCreate(key, value)` | If key doesn't exist, set new value and return |
806
+ | `update(key, updateFn)` | Update value for key using function |
807
+
808
+ ```typescript
809
+ const map = new Map<string, number[]>();
810
+
811
+ // Create and return if value doesn't exist
812
+ const arr = map.getOrCreate("key", []);
813
+ arr.push(1);
814
+
815
+ // Create with factory function (when computation is expensive)
816
+ map.getOrCreate("key2", () => expensiveComputation());
817
+
818
+ // Update value
819
+ const countMap = new Map<string, number>();
820
+ countMap.update("key", (v) => (v ?? 0) + 1); // Increment counter
821
+ ```
822
+
823
+ #### Set extension methods
824
+
825
+ | Method | Description |
826
+ |--------|------|
827
+ | `adds(...values)` | Add multiple values at once |
828
+ | `toggle(value, addOrDel?)` | Toggle value (remove if exists, add if not) |
829
+
830
+ ```typescript
831
+ const set = new Set<number>([1, 2, 3]);
832
+
833
+ set.adds(4, 5, 6); // {1, 2, 3, 4, 5, 6}
834
+ set.toggle(2); // 2 exists so remove -> {1, 3, 4, 5, 6}
835
+ set.toggle(7); // 7 doesn't exist so add -> {1, 3, 4, 5, 6, 7}
836
+ set.toggle(8, "add"); // Force add
837
+ set.toggle(1, "del"); // Force delete
838
+ ```
839
+
840
+ ## Caveats
841
+
842
+ ### Prototype Extension Conflicts
843
+
844
+ This package extends Array, Map, and Set prototypes.
845
+ Conflicts may occur when used with other libraries that extend the same method names.
846
+ In case of conflict, the last defined implementation is applied based on load order.
847
+
848
+ ### Timezone Handling
849
+
850
+ When using `DateOnly.parse()`, `DateTime.parse()`:
851
+ - `yyyy-MM-dd`, `yyyyMMdd` format: parse directly from string (no timezone influence)
852
+ - ISO 8601 format (`2024-01-15T00:00:00Z`): interpret as UTC then convert to local
853
+
854
+ When server and client timezones differ, actively use `yyyy-MM-dd` format.
855
+
856
+ ### Memory Management (LazyGcMap)
857
+
858
+ `LazyGcMap` has an internal GC timer, so it must be cleaned up.
859
+
860
+ ```typescript
861
+ // using statement (recommended)
862
+ // gcInterval: GC execution interval (ms), expireTime: item expiration time (ms)
863
+ using map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 }); // GC every 10 seconds, expire after 60 seconds
864
+
865
+ // Or explicit dispose() call
866
+ const map = new LazyGcMap({ gcInterval: 10000, expireTime: 60000 }); // GC every 10 seconds, expire after 60 seconds
867
+ try {
868
+ // ... use
869
+ } finally {
870
+ map.dispose();
871
+ }
872
+ ```
873
+
874
+ ### jsonStringify's __type__ Reserved Word
875
+
876
+ `jsonStringify`/`jsonParse` uses objects with `__type__` and `data` keys for type restoration.
877
+ Be careful as user data in the form `{ __type__: "DateTime", data: "..." }` may be unintentionally type-converted.
878
+
879
+ ### Circular References
880
+
881
+ - `objClone`: supports circular references (tracked with WeakMap)
882
+ - `jsonStringify`: throws TypeError on circular reference
883
+ - `transferableEncode`: throws TypeError on circular reference (includes path information)
884
+
885
+ ## License
886
+
887
+ Apache-2.0