@simplysm/core-common 13.0.100 → 14.0.4

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 (184) hide show
  1. package/README.md +86 -92
  2. package/dist/common.types.d.ts +14 -14
  3. package/dist/common.types.js +2 -1
  4. package/dist/common.types.js.map +1 -6
  5. package/dist/env.d.ts +8 -1
  6. package/dist/env.d.ts.map +1 -1
  7. package/dist/env.js +13 -9
  8. package/dist/env.js.map +1 -6
  9. package/dist/errors/argument-error.d.ts +10 -10
  10. package/dist/errors/argument-error.d.ts.map +1 -1
  11. package/dist/errors/argument-error.js +31 -14
  12. package/dist/errors/argument-error.js.map +1 -6
  13. package/dist/errors/not-implemented-error.d.ts +8 -8
  14. package/dist/errors/not-implemented-error.js +30 -12
  15. package/dist/errors/not-implemented-error.js.map +1 -6
  16. package/dist/errors/sd-error.d.ts +10 -10
  17. package/dist/errors/sd-error.d.ts.map +1 -1
  18. package/dist/errors/sd-error.js +45 -24
  19. package/dist/errors/sd-error.js.map +1 -6
  20. package/dist/errors/timeout-error.d.ts +10 -10
  21. package/dist/errors/timeout-error.js +34 -15
  22. package/dist/errors/timeout-error.js.map +1 -6
  23. package/dist/extensions/arr-ext.d.ts +2 -2
  24. package/dist/extensions/arr-ext.helpers.d.ts +10 -10
  25. package/dist/extensions/arr-ext.helpers.js +112 -89
  26. package/dist/extensions/arr-ext.helpers.js.map +1 -6
  27. package/dist/extensions/arr-ext.js +458 -422
  28. package/dist/extensions/arr-ext.js.map +1 -6
  29. package/dist/extensions/arr-ext.types.d.ts +57 -57
  30. package/dist/extensions/arr-ext.types.d.ts.map +1 -1
  31. package/dist/extensions/arr-ext.types.js +6 -1
  32. package/dist/extensions/arr-ext.types.js.map +1 -6
  33. package/dist/extensions/map-ext.d.ts +16 -16
  34. package/dist/extensions/map-ext.js +27 -22
  35. package/dist/extensions/map-ext.js.map +1 -6
  36. package/dist/extensions/set-ext.d.ts +11 -11
  37. package/dist/extensions/set-ext.js +32 -25
  38. package/dist/extensions/set-ext.js.map +1 -6
  39. package/dist/features/debounce-queue.d.ts +17 -17
  40. package/dist/features/debounce-queue.js +98 -70
  41. package/dist/features/debounce-queue.js.map +1 -6
  42. package/dist/features/event-emitter.d.ts +20 -20
  43. package/dist/features/event-emitter.js +101 -78
  44. package/dist/features/event-emitter.js.map +1 -6
  45. package/dist/features/serial-queue.d.ts +11 -11
  46. package/dist/features/serial-queue.js +78 -57
  47. package/dist/features/serial-queue.js.map +1 -6
  48. package/dist/globals.d.ts +4 -4
  49. package/dist/globals.js +9 -1
  50. package/dist/globals.js.map +1 -6
  51. package/dist/index.js +28 -27
  52. package/dist/index.js.map +1 -6
  53. package/dist/types/date-only.d.ts +64 -64
  54. package/dist/types/date-only.d.ts.map +1 -1
  55. package/dist/types/date-only.js +263 -252
  56. package/dist/types/date-only.js.map +1 -6
  57. package/dist/types/date-time.d.ts +36 -36
  58. package/dist/types/date-time.d.ts.map +1 -1
  59. package/dist/types/date-time.js +196 -288
  60. package/dist/types/date-time.js.map +1 -6
  61. package/dist/types/lazy-gc-map.d.ts +26 -26
  62. package/dist/types/lazy-gc-map.d.ts.map +1 -1
  63. package/dist/types/lazy-gc-map.js +202 -159
  64. package/dist/types/lazy-gc-map.js.map +1 -6
  65. package/dist/types/time.d.ts +23 -23
  66. package/dist/types/time.d.ts.map +1 -1
  67. package/dist/types/time.js +169 -158
  68. package/dist/types/time.js.map +1 -6
  69. package/dist/types/uuid.d.ts +11 -11
  70. package/dist/types/uuid.d.ts.map +1 -1
  71. package/dist/types/uuid.js +95 -70
  72. package/dist/types/uuid.js.map +1 -6
  73. package/dist/utils/bytes.d.ts +17 -17
  74. package/dist/utils/bytes.js +137 -81
  75. package/dist/utils/bytes.js.map +1 -6
  76. package/dist/utils/date-format.d.ts +40 -40
  77. package/dist/utils/date-format.js +187 -101
  78. package/dist/utils/date-format.js.map +1 -6
  79. package/dist/utils/error.d.ts +4 -4
  80. package/dist/utils/error.js +11 -6
  81. package/dist/utils/error.js.map +1 -6
  82. package/dist/utils/json.d.ts +19 -19
  83. package/dist/utils/json.js +187 -135
  84. package/dist/utils/json.js.map +1 -6
  85. package/dist/utils/num.d.ts +20 -20
  86. package/dist/utils/num.js +76 -34
  87. package/dist/utils/num.js.map +1 -6
  88. package/dist/utils/obj.d.ts +111 -111
  89. package/dist/utils/obj.d.ts.map +1 -1
  90. package/dist/utils/obj.js +706 -496
  91. package/dist/utils/obj.js.map +1 -6
  92. package/dist/utils/path.d.ts +10 -10
  93. package/dist/utils/path.js +35 -18
  94. package/dist/utils/path.js.map +1 -6
  95. package/dist/utils/primitive.d.ts +5 -5
  96. package/dist/utils/primitive.js +34 -14
  97. package/dist/utils/primitive.js.map +1 -6
  98. package/dist/utils/str.d.ts +38 -38
  99. package/dist/utils/str.js +217 -113
  100. package/dist/utils/str.js.map +1 -6
  101. package/dist/utils/template-strings.d.ts +26 -26
  102. package/dist/utils/template-strings.js +113 -40
  103. package/dist/utils/template-strings.js.map +1 -6
  104. package/dist/utils/transferable.d.ts +18 -18
  105. package/dist/utils/transferable.js +218 -151
  106. package/dist/utils/transferable.js.map +1 -6
  107. package/dist/utils/wait.d.ts +9 -9
  108. package/dist/utils/wait.js +30 -15
  109. package/dist/utils/wait.js.map +1 -6
  110. package/dist/utils/xml.d.ts +13 -13
  111. package/dist/utils/xml.js +84 -46
  112. package/dist/utils/xml.js.map +1 -6
  113. package/dist/utils/zip.d.ts +22 -22
  114. package/dist/utils/zip.js +172 -148
  115. package/dist/utils/zip.js.map +1 -6
  116. package/docs/array-extensions.md +430 -0
  117. package/docs/env.md +52 -0
  118. package/docs/errors.md +41 -56
  119. package/docs/features.md +82 -97
  120. package/docs/type-utilities.md +91 -0
  121. package/docs/types.md +221 -201
  122. package/docs/utils.md +319 -435
  123. package/package.json +7 -5
  124. package/src/common.types.ts +14 -14
  125. package/src/env.ts +12 -3
  126. package/src/errors/argument-error.ts +15 -15
  127. package/src/errors/not-implemented-error.ts +9 -9
  128. package/src/errors/sd-error.ts +12 -12
  129. package/src/errors/timeout-error.ts +12 -12
  130. package/src/extensions/arr-ext.helpers.ts +16 -16
  131. package/src/extensions/arr-ext.ts +35 -35
  132. package/src/extensions/arr-ext.types.ts +57 -57
  133. package/src/extensions/map-ext.ts +16 -16
  134. package/src/extensions/set-ext.ts +11 -11
  135. package/src/features/debounce-queue.ts +23 -23
  136. package/src/features/event-emitter.ts +25 -25
  137. package/src/features/serial-queue.ts +13 -13
  138. package/src/globals.ts +4 -4
  139. package/src/index.ts +5 -5
  140. package/src/types/date-only.ts +84 -83
  141. package/src/types/date-time.ts +43 -42
  142. package/src/types/lazy-gc-map.ts +44 -44
  143. package/src/types/time.ts +29 -29
  144. package/src/types/uuid.ts +15 -15
  145. package/src/utils/bytes.ts +35 -35
  146. package/src/utils/date-format.ts +59 -59
  147. package/src/utils/error.ts +4 -4
  148. package/src/utils/json.ts +41 -41
  149. package/src/utils/num.ts +20 -20
  150. package/src/utils/obj.ts +138 -138
  151. package/src/utils/path.ts +10 -10
  152. package/src/utils/primitive.ts +6 -6
  153. package/src/utils/str.ts +48 -48
  154. package/src/utils/template-strings.ts +29 -29
  155. package/src/utils/transferable.ts +38 -38
  156. package/src/utils/wait.ts +10 -10
  157. package/src/utils/xml.ts +19 -19
  158. package/src/utils/zip.ts +25 -25
  159. package/docs/extensions.md +0 -387
  160. package/tests/errors/errors.spec.ts +0 -80
  161. package/tests/extensions/array-extension.spec.ts +0 -654
  162. package/tests/extensions/map-extension.spec.ts +0 -117
  163. package/tests/extensions/set-extension.spec.ts +0 -67
  164. package/tests/types/date-only.spec.ts +0 -533
  165. package/tests/types/date-time.spec.ts +0 -246
  166. package/tests/types/lazy-gc-map.spec.ts +0 -606
  167. package/tests/types/time.spec.ts +0 -428
  168. package/tests/types/uuid.spec.ts +0 -74
  169. package/tests/utils/bytes-utils.spec.ts +0 -197
  170. package/tests/utils/date-format.spec.ts +0 -350
  171. package/tests/utils/debounce-queue.spec.ts +0 -226
  172. package/tests/utils/json.spec.ts +0 -400
  173. package/tests/utils/number.spec.ts +0 -136
  174. package/tests/utils/object.spec.ts +0 -810
  175. package/tests/utils/path.spec.ts +0 -70
  176. package/tests/utils/primitive.spec.ts +0 -43
  177. package/tests/utils/sd-event-emitter.spec.ts +0 -189
  178. package/tests/utils/serial-queue.spec.ts +0 -305
  179. package/tests/utils/string.spec.ts +0 -265
  180. package/tests/utils/template-strings.spec.ts +0 -48
  181. package/tests/utils/transferable.spec.ts +0 -639
  182. package/tests/utils/wait.spec.ts +0 -123
  183. package/tests/utils/xml.spec.ts +0 -146
  184. package/tests/utils/zip.spec.ts +0 -221
package/dist/utils/obj.js CHANGED
@@ -3,553 +3,763 @@ import { DateOnly } from "../types/date-only.js";
3
3
  import { Time } from "../types/time.js";
4
4
  import { Uuid } from "../types/uuid.js";
5
5
  import { ArgumentError } from "../errors/argument-error.js";
6
- function clone(source) {
7
- return cloneImpl(source);
6
+ //#region clone
7
+ /**
8
+ * 깊은 복사
9
+ * - 순환 참조 지원
10
+ * - 커스텀 타입 복사 지원 (DateTime, DateOnly, Time, Uuid, Uint8Array)
11
+ *
12
+ * @note 함수와 Symbol은 복사되지 않고 참조가 유지됨
13
+ * @note WeakMap과 WeakSet은 지원되지 않음 (일반 객체로 복사되어 빈 객체가 됨)
14
+ * @note 프로토타입 체인이 유지됨 (Object.setPrototypeOf 사용)
15
+ * @note Getter/setter는 현재 값으로 평가되어 복사됨 (접근자 속성 자체는 복사되지 않음)
16
+ */
17
+ export function clone(source) {
18
+ return cloneImpl(source);
8
19
  }
9
20
  function cloneImpl(source, prevClones) {
10
- if (typeof source !== "object" || source === null) {
11
- return source;
12
- }
13
- if (source instanceof Date) {
14
- return new Date(source.getTime());
15
- }
16
- if (source instanceof DateTime) {
17
- return new DateTime(source.tick);
18
- }
19
- if (source instanceof DateOnly) {
20
- return new DateOnly(source.tick);
21
- }
22
- if (source instanceof Time) {
23
- return new Time(source.tick);
24
- }
25
- if (source instanceof Uuid) {
26
- return new Uuid(source.toString());
27
- }
28
- if (source instanceof RegExp) {
29
- return new RegExp(source.source, source.flags);
30
- }
31
- const currPrevClones = prevClones ?? /* @__PURE__ */ new WeakMap();
32
- if (currPrevClones.has(source)) {
33
- return currPrevClones.get(source);
34
- }
35
- if (source instanceof Error) {
36
- const cloned = Object.create(Object.getPrototypeOf(source));
37
- currPrevClones.set(source, cloned);
38
- cloned.message = source.message;
39
- cloned.name = source.name;
40
- cloned.stack = source.stack;
41
- if (source.cause !== void 0) {
42
- cloned.cause = cloneImpl(source.cause, currPrevClones);
21
+ // 원시 값은 그대로 반환
22
+ if (typeof source !== "object" || source === null) {
23
+ return source;
43
24
  }
25
+ // 불변과 유사한 타입 (내부 객체 참조 없음)
26
+ if (source instanceof Date) {
27
+ return new Date(source.getTime());
28
+ }
29
+ if (source instanceof DateTime) {
30
+ return new DateTime(source.tick);
31
+ }
32
+ if (source instanceof DateOnly) {
33
+ return new DateOnly(source.tick);
34
+ }
35
+ if (source instanceof Time) {
36
+ return new Time(source.tick);
37
+ }
38
+ if (source instanceof Uuid) {
39
+ return new Uuid(source.toString());
40
+ }
41
+ // RegExp
42
+ if (source instanceof RegExp) {
43
+ return new RegExp(source.source, source.flags);
44
+ }
45
+ // 순환 참조 검사 (Error를 포함한 모든 객체 타입에 적용)
46
+ const currPrevClones = prevClones ?? new WeakMap();
47
+ if (currPrevClones.has(source)) {
48
+ return currPrevClones.get(source);
49
+ }
50
+ // Error (cause 포함)
51
+ // 생성자 호출 대신 프로토타입 기반 복사 - 커스텀 Error 클래스 호환성 보장
52
+ if (source instanceof Error) {
53
+ const cloned = Object.create(Object.getPrototypeOf(source));
54
+ currPrevClones.set(source, cloned);
55
+ cloned.message = source.message;
56
+ cloned.name = source.name;
57
+ cloned.stack = source.stack;
58
+ if (source.cause !== undefined) {
59
+ cloned.cause = cloneImpl(source.cause, currPrevClones);
60
+ }
61
+ // 커스텀 Error 속성 복사
62
+ for (const key of Object.keys(source)) {
63
+ if (!["message", "name", "stack", "cause"].includes(key)) {
64
+ const desc = Object.getOwnPropertyDescriptor(source, key);
65
+ if (desc !== undefined) {
66
+ Object.defineProperty(cloned, key, {
67
+ ...desc,
68
+ value: "value" in desc ? cloneImpl(desc.value, currPrevClones) : desc.value,
69
+ });
70
+ }
71
+ }
72
+ }
73
+ return cloned;
74
+ }
75
+ if (source instanceof Uint8Array) {
76
+ const result = source.slice();
77
+ currPrevClones.set(source, result);
78
+ return result;
79
+ }
80
+ if (source instanceof Array) {
81
+ const result = [];
82
+ currPrevClones.set(source, result);
83
+ for (const item of source) {
84
+ result.push(cloneImpl(item, currPrevClones));
85
+ }
86
+ return result;
87
+ }
88
+ if (source instanceof Map) {
89
+ const result = new Map();
90
+ currPrevClones.set(source, result);
91
+ for (const [key, value] of source) {
92
+ result.set(cloneImpl(key, currPrevClones), cloneImpl(value, currPrevClones));
93
+ }
94
+ return result;
95
+ }
96
+ if (source instanceof Set) {
97
+ const result = new Set();
98
+ currPrevClones.set(source, result);
99
+ for (const item of source) {
100
+ result.add(cloneImpl(item, currPrevClones));
101
+ }
102
+ return result;
103
+ }
104
+ // 기타 Object 타입
105
+ const result = {};
106
+ Object.setPrototypeOf(result, Object.getPrototypeOf(source));
107
+ currPrevClones.set(source, result);
44
108
  for (const key of Object.keys(source)) {
45
- if (!["message", "name", "stack", "cause"].includes(key)) {
46
- const desc = Object.getOwnPropertyDescriptor(source, key);
47
- if (desc !== void 0) {
48
- Object.defineProperty(cloned, key, {
49
- ...desc,
50
- value: "value" in desc ? cloneImpl(desc.value, currPrevClones) : desc.value
51
- });
52
- }
53
- }
54
- }
55
- return cloned;
56
- }
57
- if (source instanceof Uint8Array) {
58
- const result2 = source.slice();
59
- currPrevClones.set(source, result2);
60
- return result2;
61
- }
62
- if (source instanceof Array) {
63
- const result2 = [];
64
- currPrevClones.set(source, result2);
65
- for (const item of source) {
66
- result2.push(cloneImpl(item, currPrevClones));
67
- }
68
- return result2;
69
- }
70
- if (source instanceof Map) {
71
- const result2 = /* @__PURE__ */ new Map();
72
- currPrevClones.set(source, result2);
73
- for (const [key, value] of source) {
74
- result2.set(cloneImpl(key, currPrevClones), cloneImpl(value, currPrevClones));
75
- }
76
- return result2;
77
- }
78
- if (source instanceof Set) {
79
- const result2 = /* @__PURE__ */ new Set();
80
- currPrevClones.set(source, result2);
81
- for (const item of source) {
82
- result2.add(cloneImpl(item, currPrevClones));
83
- }
84
- return result2;
85
- }
86
- const result = {};
87
- Object.setPrototypeOf(result, Object.getPrototypeOf(source));
88
- currPrevClones.set(source, result);
89
- for (const key of Object.keys(source)) {
90
- const value = source[key];
91
- result[key] = cloneImpl(value, currPrevClones);
92
- }
93
- return result;
109
+ const value = source[key];
110
+ result[key] = cloneImpl(value, currPrevClones);
111
+ }
112
+ return result;
94
113
  }
95
- function equal(source, target, options) {
96
- if (source === target) return true;
97
- if (source == null || target == null) return false;
98
- if (typeof source !== typeof target) return false;
99
- if (source instanceof Date && target instanceof Date) {
100
- return source.getTime() === target.getTime();
101
- }
102
- if (source instanceof DateTime && target instanceof DateTime || source instanceof DateOnly && target instanceof DateOnly || source instanceof Time && target instanceof Time) {
103
- return source.tick === target.tick;
104
- }
105
- if (source instanceof Uuid && target instanceof Uuid) {
106
- return source.toString() === target.toString();
107
- }
108
- if (source instanceof RegExp && target instanceof RegExp) {
109
- return source.source === target.source && source.flags === target.flags;
110
- }
111
- if (source instanceof Array && target instanceof Array) {
112
- return equalArray(source, target, options);
113
- }
114
- if (source instanceof Map && target instanceof Map) {
115
- return equalMap(source, target, options);
116
- }
117
- if (source instanceof Set && target instanceof Set) {
118
- return equalSet(source, target, options);
119
- }
120
- if (typeof source === "object" && typeof target === "object") {
121
- return equalObject(
122
- source,
123
- target,
124
- options
125
- );
126
- }
127
- return false;
114
+ /**
115
+ * 깊은 동등성 비교
116
+ *
117
+ * @param source 비교 대상 1
118
+ * @param target 비교 대상 2
119
+ * @param options 비교 옵션
120
+ * @param options.topLevelIncludes 비교할 key 목록. 지정 시 해당 key만 비교 (최상위 레벨에만 적용)
121
+ * @example `{ topLevelIncludes: ["id", "name"] }` - id와 name key만 비교
122
+ * @param options.topLevelExcludes 비교에서 제외할 key 목록 (최상위 레벨에만 적용)
123
+ * @example `{ topLevelExcludes: ["updatedAt"] }` - updatedAt key를 제외하고 비교
124
+ * @param options.ignoreArrayIndex array 순서를 무시할지 여부. true면 O(n²) 복잡도
125
+ * @param options.shallow 얕은 비교 여부. true면 1단계만 비교 (참조 비교)
126
+ *
127
+ * @note topLevelIncludes/topLevelExcludes 옵션은 객체 속성 key에만 적용됨.
128
+ * Map의 모든 key는 항상 비교에 포함됨.
129
+ * @note 성능 고려사항:
130
+ * - 기본 array 비교: O(n) 시간 복잡도
131
+ * - `ignoreArrayIndex: true` 사용 시: O(n²) 시간 복잡도
132
+ * (대형 array에서 성능 저하 가능)
133
+ * @note `ignoreArrayIndex: true` 동작 특성:
134
+ * - array 순서를 무시하고 요소가 같은 집합의 순열인지 확인
135
+ * - 예: `[1,2,3]`과 `[3,2,1]` → true, `[1,1,1]`과 `[1,2,3]` → false
136
+ */
137
+ export function equal(source, target, options) {
138
+ if (source === target)
139
+ return true;
140
+ if (source == null || target == null)
141
+ return false;
142
+ if (typeof source !== typeof target)
143
+ return false;
144
+ if (source instanceof Date && target instanceof Date) {
145
+ return source.getTime() === target.getTime();
146
+ }
147
+ if ((source instanceof DateTime && target instanceof DateTime) ||
148
+ (source instanceof DateOnly && target instanceof DateOnly) ||
149
+ (source instanceof Time && target instanceof Time)) {
150
+ return source.tick === target.tick;
151
+ }
152
+ if (source instanceof Uuid && target instanceof Uuid) {
153
+ return source.toString() === target.toString();
154
+ }
155
+ if (source instanceof RegExp && target instanceof RegExp) {
156
+ return source.source === target.source && source.flags === target.flags;
157
+ }
158
+ if (source instanceof Array && target instanceof Array) {
159
+ return equalArray(source, target, options);
160
+ }
161
+ if (source instanceof Map && target instanceof Map) {
162
+ return equalMap(source, target, options);
163
+ }
164
+ if (source instanceof Set && target instanceof Set) {
165
+ return equalSet(source, target, options);
166
+ }
167
+ if (typeof source === "object" && typeof target === "object") {
168
+ return equalObject(source, target, options);
169
+ }
170
+ return false;
128
171
  }
129
172
  function equalArray(source, target, options) {
130
- if (source.length !== target.length) {
131
- return false;
132
- }
133
- if (options == null ? void 0 : options.ignoreArrayIndex) {
134
- const matchedIndices = /* @__PURE__ */ new Set();
135
- if (options.shallow) {
136
- return source.every((sourceItem) => {
137
- const idx = target.findIndex((t, i) => !matchedIndices.has(i) && t === sourceItem);
138
- if (idx !== -1) {
139
- matchedIndices.add(idx);
140
- return true;
141
- }
173
+ if (source.length !== target.length) {
142
174
  return false;
143
- });
144
- } else {
145
- const recursiveOptions = {
146
- ignoreArrayIndex: options.ignoreArrayIndex,
147
- shallow: options.shallow
148
- };
149
- return source.every((sourceItem) => {
150
- const idx = target.findIndex(
151
- (t, i) => !matchedIndices.has(i) && equal(t, sourceItem, recursiveOptions)
152
- );
153
- if (idx !== -1) {
154
- matchedIndices.add(idx);
155
- return true;
175
+ }
176
+ if (options?.ignoreArrayIndex) {
177
+ const matchedIndices = new Set();
178
+ if (options.shallow) {
179
+ return source.every((sourceItem) => {
180
+ const idx = target.findIndex((t, i) => !matchedIndices.has(i) && t === sourceItem);
181
+ if (idx !== -1) {
182
+ matchedIndices.add(idx);
183
+ return true;
184
+ }
185
+ return false;
186
+ });
156
187
  }
157
- return false;
158
- });
159
- }
160
- } else {
161
- if (options == null ? void 0 : options.shallow) {
162
- for (let i = 0; i < source.length; i++) {
163
- if (source[i] !== target[i]) {
164
- return false;
165
- }
166
- }
167
- } else {
168
- for (let i = 0; i < source.length; i++) {
169
- if (!equal(source[i], target[i], {
170
- ignoreArrayIndex: options == null ? void 0 : options.ignoreArrayIndex,
171
- shallow: options == null ? void 0 : options.shallow
172
- })) {
173
- return false;
174
- }
175
- }
176
- }
177
- }
178
- return true;
188
+ else {
189
+ // 재귀 호출 시 topLevelIncludes/topLevelExcludes 옵션은 최상위 레벨에만 적용되므로 제외
190
+ const recursiveOptions = {
191
+ ignoreArrayIndex: options.ignoreArrayIndex,
192
+ shallow: options.shallow,
193
+ };
194
+ return source.every((sourceItem) => {
195
+ const idx = target.findIndex((t, i) => !matchedIndices.has(i) && equal(t, sourceItem, recursiveOptions));
196
+ if (idx !== -1) {
197
+ matchedIndices.add(idx);
198
+ return true;
199
+ }
200
+ return false;
201
+ });
202
+ }
203
+ }
204
+ else {
205
+ if (options?.shallow) {
206
+ for (let i = 0; i < source.length; i++) {
207
+ if (source[i] !== target[i]) {
208
+ return false;
209
+ }
210
+ }
211
+ }
212
+ else {
213
+ // 재귀 호출 시 topLevelIncludes/topLevelExcludes 옵션은 최상위 레벨에만 적용되므로 제외
214
+ for (let i = 0; i < source.length; i++) {
215
+ if (!equal(source[i], target[i], {
216
+ ignoreArrayIndex: options?.ignoreArrayIndex,
217
+ shallow: options?.shallow,
218
+ })) {
219
+ return false;
220
+ }
221
+ }
222
+ }
223
+ }
224
+ return true;
179
225
  }
226
+ /**
227
+ * Map 객체 비교
228
+ * @note 비문자열 key(객체, array 등) 처리 시 O(n²) 복잡도
229
+ * @note 대용량 데이터에는 shallow: true 옵션 사용 권장 (참조 비교로 O(n)으로 개선)
230
+ */
180
231
  function equalMap(source, target, options) {
181
- const sourceKeys = Array.from(source.keys()).filter((key) => source.get(key) != null);
182
- const targetKeys = Array.from(target.keys()).filter((key) => target.get(key) != null);
183
- if (sourceKeys.length !== targetKeys.length) {
184
- return false;
185
- }
186
- const usedTargetKeys = /* @__PURE__ */ new Set();
187
- for (const sourceKey of sourceKeys) {
188
- if (typeof sourceKey === "string") {
189
- const sourceValue = source.get(sourceKey);
190
- const targetValue = target.get(sourceKey);
191
- if (options == null ? void 0 : options.shallow) {
192
- if (sourceValue !== targetValue) return false;
193
- } else {
194
- if (!equal(sourceValue, targetValue, {
195
- ignoreArrayIndex: options == null ? void 0 : options.ignoreArrayIndex,
196
- shallow: options == null ? void 0 : options.shallow
197
- })) {
198
- return false;
199
- }
200
- }
201
- } else {
202
- let found = false;
203
- for (let i = 0; i < targetKeys.length; i++) {
204
- const targetKey = targetKeys[i];
205
- if (typeof targetKey === "string" || usedTargetKeys.has(i)) continue;
206
- if ((options == null ? void 0 : options.shallow) ? sourceKey === targetKey : equal(sourceKey, targetKey)) {
207
- usedTargetKeys.add(i);
208
- const sourceValue = source.get(sourceKey);
209
- const targetValue = target.get(targetKey);
210
- if (options == null ? void 0 : options.shallow) {
211
- if (sourceValue !== targetValue) return false;
212
- } else {
213
- if (!equal(sourceValue, targetValue, {
214
- ignoreArrayIndex: options == null ? void 0 : options.ignoreArrayIndex,
215
- shallow: options == null ? void 0 : options.shallow
216
- })) {
217
- return false;
232
+ // Map 비교 시 topLevelIncludes/topLevelExcludes 옵션은 무시됨 (객체 속성 key에만 적용)
233
+ const sourceKeys = Array.from(source.keys()).filter((key) => source.get(key) != null);
234
+ const targetKeys = Array.from(target.keys()).filter((key) => target.get(key) != null);
235
+ if (sourceKeys.length !== targetKeys.length) {
236
+ return false;
237
+ }
238
+ const usedTargetKeys = new Set();
239
+ for (const sourceKey of sourceKeys) {
240
+ // 문자열 key: 직접 비교
241
+ if (typeof sourceKey === "string") {
242
+ const sourceValue = source.get(sourceKey);
243
+ const targetValue = target.get(sourceKey);
244
+ if (options?.shallow) {
245
+ if (sourceValue !== targetValue)
246
+ return false;
218
247
  }
219
- }
220
- found = true;
221
- break;
248
+ else {
249
+ if (!equal(sourceValue, targetValue, {
250
+ ignoreArrayIndex: options?.ignoreArrayIndex,
251
+ shallow: options?.shallow,
252
+ })) {
253
+ return false;
254
+ }
255
+ }
256
+ }
257
+ else {
258
+ // 비문자열 key: targetKeys에서 동등한 key 검색
259
+ let found = false;
260
+ for (let i = 0; i < targetKeys.length; i++) {
261
+ const targetKey = targetKeys[i];
262
+ if (typeof targetKey === "string" || usedTargetKeys.has(i))
263
+ continue;
264
+ if (options?.shallow ? sourceKey === targetKey : equal(sourceKey, targetKey)) {
265
+ usedTargetKeys.add(i);
266
+ const sourceValue = source.get(sourceKey);
267
+ const targetValue = target.get(targetKey);
268
+ if (options?.shallow) {
269
+ if (sourceValue !== targetValue)
270
+ return false;
271
+ }
272
+ else {
273
+ if (!equal(sourceValue, targetValue, {
274
+ ignoreArrayIndex: options?.ignoreArrayIndex,
275
+ shallow: options?.shallow,
276
+ })) {
277
+ return false;
278
+ }
279
+ }
280
+ found = true;
281
+ break;
282
+ }
283
+ }
284
+ if (!found)
285
+ return false;
222
286
  }
223
- }
224
- if (!found) return false;
225
287
  }
226
- }
227
- return true;
288
+ return true;
228
289
  }
229
290
  function equalObject(source, target, options) {
230
- const sourceKeys = Object.keys(source).filter(
231
- (key) => {
232
- var _a;
233
- return ((options == null ? void 0 : options.topLevelIncludes) === void 0 || options.topLevelIncludes.includes(key)) && !((_a = options == null ? void 0 : options.topLevelExcludes) == null ? void 0 : _a.includes(key)) && source[key] != null;
234
- }
235
- );
236
- const targetKeys = Object.keys(target).filter(
237
- (key) => {
238
- var _a;
239
- return ((options == null ? void 0 : options.topLevelIncludes) === void 0 || options.topLevelIncludes.includes(key)) && !((_a = options == null ? void 0 : options.topLevelExcludes) == null ? void 0 : _a.includes(key)) && target[key] != null;
240
- }
241
- );
242
- if (sourceKeys.length !== targetKeys.length) {
243
- return false;
244
- }
245
- for (const key of sourceKeys) {
246
- if (options == null ? void 0 : options.shallow) {
247
- if (source[key] !== target[key]) {
291
+ const sourceKeys = Object.keys(source).filter((key) => (options?.topLevelIncludes === undefined || options.topLevelIncludes.includes(key)) &&
292
+ !options?.topLevelExcludes?.includes(key) &&
293
+ source[key] != null);
294
+ const targetKeys = Object.keys(target).filter((key) => (options?.topLevelIncludes === undefined || options.topLevelIncludes.includes(key)) &&
295
+ !options?.topLevelExcludes?.includes(key) &&
296
+ target[key] != null);
297
+ if (sourceKeys.length !== targetKeys.length) {
248
298
  return false;
249
- }
250
- } else {
251
- if (!equal(source[key], target[key], {
252
- ignoreArrayIndex: options == null ? void 0 : options.ignoreArrayIndex
253
- })) {
254
- return false;
255
- }
256
299
  }
257
- }
258
- return true;
300
+ for (const key of sourceKeys) {
301
+ if (options?.shallow) {
302
+ if (source[key] !== target[key]) {
303
+ return false;
304
+ }
305
+ }
306
+ else {
307
+ if (!equal(source[key], target[key], {
308
+ ignoreArrayIndex: options?.ignoreArrayIndex,
309
+ })) {
310
+ return false;
311
+ }
312
+ }
313
+ }
314
+ return true;
259
315
  }
316
+ /**
317
+ * Set 깊은 동등성 비교
318
+ * @note 깊은 비교(`shallow: false`)는 O(n²) 시간 복잡도.
319
+ * 원시 타입 Set이거나 성능이 중요한 경우 `shallow: true` 사용 권장
320
+ */
260
321
  function equalSet(source, target, options) {
261
- if (source.size !== target.size) {
262
- return false;
263
- }
264
- if (options == null ? void 0 : options.shallow) {
265
- for (const sourceItem of source) {
266
- if (!target.has(sourceItem)) {
322
+ if (source.size !== target.size) {
267
323
  return false;
268
- }
269
- }
270
- } else {
271
- const targetArr = [...target];
272
- const matchedIndices = /* @__PURE__ */ new Set();
273
- for (const sourceItem of source) {
274
- const idx = targetArr.findIndex(
275
- (t, i) => !matchedIndices.has(i) && equal(sourceItem, t, options)
276
- );
277
- if (idx === -1) {
278
- return false;
279
- }
280
- matchedIndices.add(idx);
281
324
  }
282
- }
283
- return true;
325
+ if (options?.shallow) {
326
+ for (const sourceItem of source) {
327
+ if (!target.has(sourceItem)) {
328
+ return false;
329
+ }
330
+ }
331
+ }
332
+ else {
333
+ // 깊은 비교: 루프 외부에서 target array를 한 번만 생성
334
+ // 중복 매칭 방지를 위해 매칭된 인덱스 추적
335
+ const targetArr = [...target];
336
+ const matchedIndices = new Set();
337
+ for (const sourceItem of source) {
338
+ const idx = targetArr.findIndex((t, i) => !matchedIndices.has(i) && equal(sourceItem, t, options));
339
+ if (idx === -1) {
340
+ return false;
341
+ }
342
+ matchedIndices.add(idx);
343
+ }
344
+ }
345
+ return true;
284
346
  }
285
- function merge(source, target, opt) {
286
- if (source == null) {
287
- return clone(target);
288
- }
289
- if (target === void 0) {
290
- return clone(source);
291
- }
292
- if (target === null) {
293
- return (opt == null ? void 0 : opt.useDelTargetNull) ? void 0 : clone(source);
294
- }
295
- if (typeof target !== "object") {
296
- return target;
297
- }
298
- if (target instanceof Date || target instanceof DateTime || target instanceof DateOnly || target instanceof Time || target instanceof Uuid || target instanceof Uint8Array || (opt == null ? void 0 : opt.arrayProcess) === "replace" && target instanceof Array) {
299
- return clone(target);
300
- }
301
- if (typeof source !== "object" || source.constructor !== target.constructor) {
302
- return clone(target);
303
- }
304
- if (source instanceof Map && target instanceof Map) {
305
- const result = clone(source);
306
- for (const key of target.keys()) {
307
- if (result.has(key)) {
308
- result.set(key, merge(result.get(key), target.get(key), opt));
309
- } else {
310
- result.set(key, clone(target.get(key)));
311
- }
347
+ /**
348
+ * 깊은 병합 (source 기반으로 target을 병합)
349
+ *
350
+ * @param source 기본 객체
351
+ * @param target 병합할 객체
352
+ * @param opt 병합 옵션
353
+ * @param opt.arrayProcess Array 처리 방식
354
+ * - `"replace"`: target array로 교체 (기본값)
355
+ * - `"concat"`: source와 target array를 병합 (Set으로 중복 제거)
356
+ * @param opt.useDelTargetNull target 값이 null일 때 해당 key를 삭제할지 여부
357
+ * - `true`: target null이면 결과에서 해당 key 삭제
358
+ * - `false` 또는 미지정: source 값 유지
359
+ *
360
+ * @note 원본 객체를 수정하지 않고 객체를 반환 (불변성 보장)
361
+ * @note arrayProcess="concat" 사용 시 Set으로 중복 제거되며,
362
+ * 객체 array의 경우 참조(주소) 비교로 중복 여부 판단
363
+ * @note 타입이 다르면 target 값으로 덮어씀
364
+ */
365
+ export function merge(source, target, opt) {
366
+ if (source == null) {
367
+ return clone(target);
312
368
  }
313
- return result;
314
- }
315
- if ((opt == null ? void 0 : opt.arrayProcess) === "concat" && source instanceof Array && target instanceof Array) {
316
- let result = [.../* @__PURE__ */ new Set([...source, ...target])];
317
- if (opt.useDelTargetNull) {
318
- result = result.filter((item) => item !== null);
369
+ if (target === undefined) {
370
+ return clone(source);
319
371
  }
320
- return result;
321
- }
322
- const sourceRec = source;
323
- const targetRec = target;
324
- const resultRec = clone(sourceRec);
325
- for (const key of Object.keys(target)) {
326
- resultRec[key] = merge(sourceRec[key], targetRec[key], opt);
327
- if (resultRec[key] === void 0) {
328
- delete resultRec[key];
329
- }
330
- }
331
- return resultRec;
372
+ if (target === null) {
373
+ return opt?.useDelTargetNull
374
+ ? undefined
375
+ : clone(source);
376
+ }
377
+ if (typeof target !== "object") {
378
+ return target;
379
+ }
380
+ if (target instanceof Date ||
381
+ target instanceof DateTime ||
382
+ target instanceof DateOnly ||
383
+ target instanceof Time ||
384
+ target instanceof Uuid ||
385
+ target instanceof Uint8Array ||
386
+ (opt?.arrayProcess === "replace" && target instanceof Array)) {
387
+ return clone(target);
388
+ }
389
+ // source가 객체가 아니거나 source와 target이 다른 타입의 객체이면 target으로 덮어씀
390
+ if (typeof source !== "object" || source.constructor !== target.constructor) {
391
+ return clone(target);
392
+ }
393
+ if (source instanceof Map && target instanceof Map) {
394
+ const result = clone(source);
395
+ for (const key of target.keys()) {
396
+ if (result.has(key)) {
397
+ result.set(key, merge(result.get(key), target.get(key), opt));
398
+ }
399
+ else {
400
+ result.set(key, clone(target.get(key)));
401
+ }
402
+ }
403
+ return result;
404
+ }
405
+ if (opt?.arrayProcess === "concat" && source instanceof Array && target instanceof Array) {
406
+ let result = [...new Set([...source, ...target])];
407
+ if (opt.useDelTargetNull) {
408
+ result = result.filter((item) => item !== null);
409
+ }
410
+ return result;
411
+ }
412
+ const sourceRec = source;
413
+ const targetRec = target;
414
+ const resultRec = clone(sourceRec);
415
+ for (const key of Object.keys(target)) {
416
+ resultRec[key] = merge(sourceRec[key], targetRec[key], opt);
417
+ if (resultRec[key] === undefined) {
418
+ delete resultRec[key];
419
+ }
420
+ }
421
+ return resultRec;
332
422
  }
333
- function merge3(source, origin, target, optionsObj) {
334
- let conflict = false;
335
- const result = clone(origin);
336
- const allKeys = /* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(target), ...Object.keys(origin)]);
337
- for (const key of allKeys) {
338
- if (equal(source[key], result[key], optionsObj == null ? void 0 : optionsObj[key])) {
339
- result[key] = clone(target[key]);
340
- } else if (equal(target[key], result[key], optionsObj == null ? void 0 : optionsObj[key])) {
341
- result[key] = clone(source[key]);
342
- } else if (equal(source[key], target[key], optionsObj == null ? void 0 : optionsObj[key])) {
343
- result[key] = clone(source[key]);
344
- } else {
345
- conflict = true;
346
- }
347
- }
348
- return {
349
- conflict,
350
- result
351
- };
423
+ /**
424
+ * 3-way 병합
425
+ *
426
+ * source, origin, target 객체를 비교하여 병합.
427
+ * - source와 origin이 같고 target이 다르면 → target 값 사용
428
+ * - target과 origin이 같고 source가 다르면 source 사용
429
+ * - source와 target이 같으면 → 해당 값 사용
430
+ * - 값이 모두 다르면 충돌 발생 (origin 유지)
431
+ *
432
+ * @param source 변경된 버전 1
433
+ * @param origin 기본 버전 (공통 조상)
434
+ * @param target 변경된 버전 2
435
+ * @param optionsObj key별 비교 옵션. 각 key에 대해 equal() 비교 옵션을 개별 지정
436
+ * - `keys`: 비교할 하위 key 목록 (equal의 topLevelIncludes와 동일)
437
+ * - `excludes`: 비교에서 제외할 하위 key 목록
438
+ * - `ignoreArrayIndex`: array 순서를 무시할지 여부
439
+ * @returns conflict: 충돌 발생 여부, result: 병합 결과
440
+ *
441
+ * @example
442
+ * const { conflict, result } = merge3(
443
+ * { a: 1, b: 2 }, // source
444
+ * { a: 1, b: 1 }, // origin
445
+ * { a: 2, b: 1 }, // target
446
+ * );
447
+ * // conflict: false, result: { a: 2, b: 2 }
448
+ */
449
+ export function merge3(source, origin, target, optionsObj) {
450
+ let conflict = false;
451
+ const result = clone(origin);
452
+ const allKeys = new Set([...Object.keys(source), ...Object.keys(target), ...Object.keys(origin)]);
453
+ for (const key of allKeys) {
454
+ if (equal(source[key], result[key], optionsObj?.[key])) {
455
+ result[key] = clone(target[key]);
456
+ }
457
+ else if (equal(target[key], result[key], optionsObj?.[key])) {
458
+ result[key] = clone(source[key]);
459
+ }
460
+ else if (equal(source[key], target[key], optionsObj?.[key])) {
461
+ result[key] = clone(source[key]);
462
+ }
463
+ else {
464
+ conflict = true;
465
+ }
466
+ }
467
+ return {
468
+ conflict,
469
+ result: result,
470
+ };
352
471
  }
353
- function omit(item, omitKeys) {
354
- const result = {};
355
- for (const key of Object.keys(item)) {
356
- if (!omitKeys.includes(key)) {
357
- result[key] = item[key];
358
- }
359
- }
360
- return result;
472
+ //#endregion
473
+ //#region omit / pick
474
+ /**
475
+ * 객체에서 특정 key 제외
476
+ * @param item 원본 객체
477
+ * @param omitKeys 제외할 key 배열
478
+ * @returns 지정된 key가 제외된 새 객체
479
+ * @example
480
+ * const user = { name: "Alice", age: 30, email: "alice@example.com" };
481
+ * omit(user, ["email"]);
482
+ * // { name: "Alice", age: 30 }
483
+ */
484
+ export function omit(item, omitKeys) {
485
+ const result = {};
486
+ for (const key of Object.keys(item)) {
487
+ if (!omitKeys.includes(key)) {
488
+ result[key] = item[key];
489
+ }
490
+ }
491
+ return result;
361
492
  }
362
- function omitByFilter(item, omitKeyFn) {
363
- const result = {};
364
- for (const key of Object.keys(item)) {
365
- if (!omitKeyFn(key)) {
366
- result[key] = item[key];
367
- }
368
- }
369
- return result;
493
+ /**
494
+ * 조건에 맞는 key 제외
495
+ * @internal
496
+ * @param item 원본 객체
497
+ * @param omitKeyFn key 받아 제외 여부를 반환하는 함수 (true면 제외)
498
+ * @returns 조건에 맞는 key가 제외된 새 객체
499
+ * @example
500
+ * const data = { name: "Alice", _internal: "secret", age: 30 };
501
+ * omitByFilter(data, (key) => key.startsWith("_"));
502
+ * // { name: "Alice", age: 30 }
503
+ */
504
+ export function omitByFilter(item, omitKeyFn) {
505
+ const result = {};
506
+ for (const key of Object.keys(item)) {
507
+ if (!omitKeyFn(key)) {
508
+ result[key] = item[key];
509
+ }
510
+ }
511
+ return result;
370
512
  }
371
- function pick(item, pickKeys) {
372
- const result = {};
373
- for (const key of pickKeys) {
374
- result[key] = item[key];
375
- }
376
- return result;
513
+ /**
514
+ * 객체에서 특정 key만 선택
515
+ * @param item 원본 객체
516
+ * @param keys 선택할 key 배열
517
+ * @returns 지정된 key만 포함된 새 객체
518
+ * @example
519
+ * const user = { name: "Alice", age: 30, email: "alice@example.com" };
520
+ * pick(user, ["name", "age"]);
521
+ * // { name: "Alice", age: 30 }
522
+ */
523
+ export function pick(item, pickKeys) {
524
+ const result = {};
525
+ for (const key of pickKeys) {
526
+ result[key] = item[key];
527
+ }
528
+ return result;
377
529
  }
530
+ //#endregion
531
+ //#region getChainValue / setChainValue / deleteChainValue
532
+ // 정규식 캐싱 (모듈 로드 시 한 번만 생성)
378
533
  const chainSplitRegex = /[.[\]]/g;
379
534
  const chainCleanRegex = /[?!'"]/g;
380
535
  const chainNumericRegex = /^[0-9]*$/;
381
536
  function getChainSplits(chain) {
382
- const split = chain.split(chainSplitRegex).map((item) => item.replace(chainCleanRegex, "")).filter((item) => Boolean(item));
383
- const result = [];
384
- for (const splitItem of split) {
385
- if (chainNumericRegex.test(splitItem)) {
386
- result.push(Number.parseInt(splitItem));
387
- } else {
388
- result.push(splitItem);
389
- }
390
- }
391
- return result;
537
+ const split = chain
538
+ .split(chainSplitRegex)
539
+ .map((item) => item.replace(chainCleanRegex, ""))
540
+ .filter((item) => Boolean(item));
541
+ const result = [];
542
+ for (const splitItem of split) {
543
+ if (chainNumericRegex.test(splitItem)) {
544
+ result.push(Number.parseInt(splitItem));
545
+ }
546
+ else {
547
+ result.push(splitItem);
548
+ }
549
+ }
550
+ return result;
392
551
  }
393
- function getChainValue(obj, chain, optional) {
394
- const splits = getChainSplits(chain);
395
- let result = obj;
396
- for (const splitItem of splits) {
397
- if (optional && result === void 0) {
398
- result = void 0;
399
- } else {
400
- result = result[splitItem];
401
- }
402
- }
403
- return result;
552
+ export function getChainValue(obj, chain, optional) {
553
+ const splits = getChainSplits(chain);
554
+ let result = obj;
555
+ for (const splitItem of splits) {
556
+ if (optional && result === undefined) {
557
+ result = undefined;
558
+ }
559
+ else {
560
+ result = result[splitItem];
561
+ }
562
+ }
563
+ return result;
404
564
  }
405
- function getChainValueByDepth(obj, key, depth, optional) {
406
- if (depth < 1) {
407
- throw new ArgumentError("depth must be 1 or greater", { depth });
408
- }
409
- let result = obj;
410
- for (let i = 0; i < depth; i++) {
411
- if (optional && result == null) {
412
- result = void 0;
413
- } else {
414
- result = result[key];
415
- }
416
- }
417
- return result;
565
+ export function getChainValueByDepth(obj, key, depth, optional) {
566
+ if (depth < 1) {
567
+ throw new ArgumentError("depth 1 이상이어야 합니다", { depth });
568
+ }
569
+ let result = obj;
570
+ for (let i = 0; i < depth; i++) {
571
+ if (optional && result == null) {
572
+ result = undefined;
573
+ }
574
+ else {
575
+ result = result[key];
576
+ }
577
+ }
578
+ return result;
418
579
  }
419
- function setChainValue(obj, chain, value) {
420
- const splits = getChainSplits(chain);
421
- if (splits.length === 0) {
422
- throw new ArgumentError("Chain is empty", { chain });
423
- }
424
- let curr = obj;
425
- for (const splitItem of splits.slice(0, -1)) {
426
- curr[splitItem] = curr[splitItem] ?? {};
427
- curr = curr[splitItem];
428
- }
429
- const last = splits[splits.length - 1];
430
- curr[last] = value;
580
+ /**
581
+ * 체인 경로로 값 설정
582
+ * @example setChainValue(obj, "a.b[0].c", value)
583
+ */
584
+ export function setChainValue(obj, chain, value) {
585
+ const splits = getChainSplits(chain);
586
+ if (splits.length === 0) {
587
+ throw new ArgumentError("chain이 비어있습니다", { chain });
588
+ }
589
+ let curr = obj;
590
+ for (const splitItem of splits.slice(0, -1)) {
591
+ curr[splitItem] = curr[splitItem] ?? {};
592
+ curr = curr[splitItem];
593
+ }
594
+ const last = splits[splits.length - 1];
595
+ curr[last] = value;
431
596
  }
432
- function deleteChainValue(obj, chain) {
433
- const splits = getChainSplits(chain);
434
- if (splits.length === 0) {
435
- throw new ArgumentError("Chain is empty", { chain });
436
- }
437
- let curr = obj;
438
- for (const splitItem of splits.slice(0, -1)) {
439
- const next = curr[splitItem];
440
- if (next == null || typeof next !== "object") {
441
- return;
442
- }
443
- curr = next;
444
- }
445
- const last = splits[splits.length - 1];
446
- delete curr[last];
597
+ /**
598
+ * 체인 경로로 값 삭제
599
+ * @example deleteChainValue(obj, "a.b[0].c")
600
+ */
601
+ export function deleteChainValue(obj, chain) {
602
+ const splits = getChainSplits(chain);
603
+ if (splits.length === 0) {
604
+ throw new ArgumentError("chain이 비어있습니다", { chain });
605
+ }
606
+ let curr = obj;
607
+ for (const splitItem of splits.slice(0, -1)) {
608
+ const next = curr[splitItem];
609
+ // 중간 경로가 존재하지 않으면 조용히 반환 (삭제할 것이 없음)
610
+ if (next == null || typeof next !== "object") {
611
+ return;
612
+ }
613
+ curr = next;
614
+ }
615
+ const last = splits[splits.length - 1];
616
+ delete curr[last];
447
617
  }
448
- function clearUndefined(obj) {
449
- const record = obj;
450
- for (const key of Object.keys(record)) {
451
- if (record[key] === void 0) {
452
- delete record[key];
453
- }
454
- }
455
- return obj;
618
+ //#endregion
619
+ //#region clearUndefined / clear / nullToUndefined / unflatten
620
+ /**
621
+ * 객체에서 undefined 값을 가진 key 삭제
622
+ * @internal
623
+ *
624
+ * @mutates 원본 객체를 직접 수정
625
+ */
626
+ export function clearUndefined(obj) {
627
+ const record = obj;
628
+ for (const key of Object.keys(record)) {
629
+ if (record[key] === undefined) {
630
+ delete record[key];
631
+ }
632
+ }
633
+ return obj;
456
634
  }
457
- function clear(obj) {
458
- for (const key of Object.keys(obj)) {
459
- delete obj[key];
460
- }
461
- return obj;
635
+ /**
636
+ * 객체의 모든 key 삭제
637
+ * @internal
638
+ *
639
+ * @mutates 원본 객체를 직접 수정
640
+ */
641
+ export function clear(obj) {
642
+ for (const key of Object.keys(obj)) {
643
+ delete obj[key];
644
+ }
645
+ return obj;
462
646
  }
463
- function nullToUndefined(obj) {
464
- return nullToUndefinedImpl(obj, /* @__PURE__ */ new WeakSet());
647
+ /**
648
+ * null을 undefined로 변환 (재귀)
649
+ * @internal
650
+ *
651
+ * @mutates 원본 array/객체를 직접 수정
652
+ */
653
+ export function nullToUndefined(obj) {
654
+ return nullToUndefinedImpl(obj, new WeakSet());
465
655
  }
466
656
  function nullToUndefinedImpl(obj, seen) {
467
- if (obj == null) {
468
- return void 0;
469
- }
470
- if (obj instanceof Date || obj instanceof DateTime || obj instanceof DateOnly || obj instanceof Time || obj instanceof Uuid) {
471
- return obj;
472
- }
473
- if (obj instanceof Array) {
474
- if (seen.has(obj)) return obj;
475
- seen.add(obj);
476
- for (let i = 0; i < obj.length; i++) {
477
- obj[i] = nullToUndefinedImpl(obj[i], seen);
657
+ if (obj == null) {
658
+ return undefined;
478
659
  }
479
- return obj;
480
- }
481
- if (typeof obj === "object") {
482
- if (seen.has(obj)) return obj;
483
- seen.add(obj);
484
- const objRec = obj;
485
- for (const key of Object.keys(obj)) {
486
- objRec[key] = nullToUndefinedImpl(objRec[key], seen);
660
+ if (obj instanceof Date ||
661
+ obj instanceof DateTime ||
662
+ obj instanceof DateOnly ||
663
+ obj instanceof Time ||
664
+ obj instanceof Uuid) {
665
+ return obj;
666
+ }
667
+ if (obj instanceof Array) {
668
+ if (seen.has(obj))
669
+ return obj;
670
+ seen.add(obj);
671
+ for (let i = 0; i < obj.length; i++) {
672
+ obj[i] = nullToUndefinedImpl(obj[i], seen);
673
+ }
674
+ return obj;
675
+ }
676
+ if (typeof obj === "object") {
677
+ if (seen.has(obj))
678
+ return obj;
679
+ seen.add(obj);
680
+ const objRec = obj;
681
+ for (const key of Object.keys(obj)) {
682
+ objRec[key] = nullToUndefinedImpl(objRec[key], seen);
683
+ }
684
+ return obj;
487
685
  }
488
686
  return obj;
489
- }
490
- return obj;
491
687
  }
492
- function unflatten(flatObj) {
493
- const result = {};
494
- for (const key in flatObj) {
495
- const parts = key.split(".");
496
- let current = result;
497
- for (let i = 0; i < parts.length; i++) {
498
- const part = parts[i];
499
- if (i === parts.length - 1) {
500
- current[part] = flatObj[key];
501
- } else {
502
- if (!(part in current)) {
503
- current[part] = {};
504
- }
505
- current = current[part];
506
- }
507
- }
508
- }
509
- return result;
688
+ /**
689
+ * 평탄화된 객체를 중첩 객체로 변환
690
+ * @internal
691
+ * @example unflatten({ "a.b.c": 1 }) => { a: { b: { c: 1 } } }
692
+ */
693
+ export function unflatten(flatObj) {
694
+ const result = {};
695
+ for (const key in flatObj) {
696
+ const parts = key.split(".");
697
+ let current = result;
698
+ for (let i = 0; i < parts.length; i++) {
699
+ const part = parts[i];
700
+ if (i === parts.length - 1) {
701
+ current[part] = flatObj[key];
702
+ }
703
+ else {
704
+ if (!(part in current)) {
705
+ current[part] = {};
706
+ }
707
+ current = current[part];
708
+ }
709
+ }
710
+ }
711
+ return result;
510
712
  }
511
- function keys(obj) {
512
- return Object.keys(obj);
713
+ //#endregion
714
+ /**
715
+ * 타입 안전한 Object.keys
716
+ * @param obj key를 추출할 객체
717
+ * @returns 객체 key 배열
718
+ */
719
+ export function keys(obj) {
720
+ return Object.keys(obj);
513
721
  }
514
- function entries(obj) {
515
- return Object.entries(obj);
722
+ /**
723
+ * 타입 안전한 Object.entries
724
+ * @param obj 엔트리를 추출할 객체
725
+ * @returns [key, value] 튜플 배열
726
+ */
727
+ export function entries(obj) {
728
+ return Object.entries(obj);
516
729
  }
517
- function fromEntries(entryPairs) {
518
- return Object.fromEntries(entryPairs);
730
+ /**
731
+ * 타입 안전한 Object.fromEntries
732
+ * @param entries [key, value] 튜플 배열
733
+ * @returns 생성된 객체
734
+ */
735
+ export function fromEntries(entryPairs) {
736
+ return Object.fromEntries(entryPairs);
519
737
  }
520
- function map(obj, fn) {
521
- return mapImpl(obj, fn);
738
+ /**
739
+ * 객체의 각 엔트리를 변환하여 새 객체 반환
740
+ * @param obj 변환할 객체
741
+ * @param fn 변환 함수 (key, value) => [newKey, newValue]
742
+ * @returns key와 value가 변환된 새 객체
743
+ * @example
744
+ * const colors = { primary: "255, 0, 0", secondary: "0, 255, 0" };
745
+ *
746
+ * // 값만 변환
747
+ * map(colors, (key, rgb) => [null, `rgb(${rgb})`]);
748
+ * // { primary: "rgb(255, 0, 0)", secondary: "rgb(0, 255, 0)" }
749
+ *
750
+ * // key와 값 모두 변환
751
+ * map(colors, (key, rgb) => [`${key}Light`, `rgb(${rgb})`]);
752
+ * // { primaryLight: "rgb(255, 0, 0)", secondaryLight: "rgb(0, 255, 0)" }
753
+ */
754
+ export function map(obj, fn) {
755
+ return mapImpl(obj, fn);
522
756
  }
523
757
  function mapImpl(obj, fn) {
524
- const result = {};
525
- for (const key of Object.keys(obj)) {
526
- const [newKey, newValue] = fn(
527
- key,
528
- obj[key]
529
- );
530
- result[newKey ?? key] = newValue;
531
- }
532
- return result;
758
+ const result = {};
759
+ for (const key of Object.keys(obj)) {
760
+ const [newKey, newValue] = fn(key, obj[key]);
761
+ result[newKey ?? key] = newValue;
762
+ }
763
+ return result;
533
764
  }
534
- export {
535
- clear,
536
- clearUndefined,
537
- clone,
538
- deleteChainValue,
539
- entries,
540
- equal,
541
- fromEntries,
542
- getChainValue,
543
- getChainValueByDepth,
544
- keys,
545
- map,
546
- merge,
547
- merge3,
548
- nullToUndefined,
549
- omit,
550
- omitByFilter,
551
- pick,
552
- setChainValue,
553
- unflatten
554
- };
555
- //# sourceMappingURL=obj.js.map
765
+ //# sourceMappingURL=obj.js.map