@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.
- package/README.md +86 -92
- package/dist/common.types.d.ts +14 -14
- package/dist/common.types.js +2 -1
- package/dist/common.types.js.map +1 -6
- package/dist/env.d.ts +8 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +13 -9
- package/dist/env.js.map +1 -6
- package/dist/errors/argument-error.d.ts +10 -10
- package/dist/errors/argument-error.d.ts.map +1 -1
- package/dist/errors/argument-error.js +31 -14
- package/dist/errors/argument-error.js.map +1 -6
- package/dist/errors/not-implemented-error.d.ts +8 -8
- package/dist/errors/not-implemented-error.js +30 -12
- package/dist/errors/not-implemented-error.js.map +1 -6
- package/dist/errors/sd-error.d.ts +10 -10
- package/dist/errors/sd-error.d.ts.map +1 -1
- package/dist/errors/sd-error.js +45 -24
- package/dist/errors/sd-error.js.map +1 -6
- package/dist/errors/timeout-error.d.ts +10 -10
- package/dist/errors/timeout-error.js +34 -15
- package/dist/errors/timeout-error.js.map +1 -6
- package/dist/extensions/arr-ext.d.ts +2 -2
- package/dist/extensions/arr-ext.helpers.d.ts +10 -10
- package/dist/extensions/arr-ext.helpers.js +112 -89
- package/dist/extensions/arr-ext.helpers.js.map +1 -6
- package/dist/extensions/arr-ext.js +458 -422
- package/dist/extensions/arr-ext.js.map +1 -6
- package/dist/extensions/arr-ext.types.d.ts +57 -57
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/arr-ext.types.js +6 -1
- package/dist/extensions/arr-ext.types.js.map +1 -6
- package/dist/extensions/map-ext.d.ts +16 -16
- package/dist/extensions/map-ext.js +27 -22
- package/dist/extensions/map-ext.js.map +1 -6
- package/dist/extensions/set-ext.d.ts +11 -11
- package/dist/extensions/set-ext.js +32 -25
- package/dist/extensions/set-ext.js.map +1 -6
- package/dist/features/debounce-queue.d.ts +17 -17
- package/dist/features/debounce-queue.js +98 -70
- package/dist/features/debounce-queue.js.map +1 -6
- package/dist/features/event-emitter.d.ts +20 -20
- package/dist/features/event-emitter.js +101 -78
- package/dist/features/event-emitter.js.map +1 -6
- package/dist/features/serial-queue.d.ts +11 -11
- package/dist/features/serial-queue.js +78 -57
- package/dist/features/serial-queue.js.map +1 -6
- package/dist/globals.d.ts +4 -4
- package/dist/globals.js +9 -1
- package/dist/globals.js.map +1 -6
- package/dist/index.js +28 -27
- package/dist/index.js.map +1 -6
- package/dist/types/date-only.d.ts +64 -64
- package/dist/types/date-only.d.ts.map +1 -1
- package/dist/types/date-only.js +263 -252
- package/dist/types/date-only.js.map +1 -6
- package/dist/types/date-time.d.ts +36 -36
- package/dist/types/date-time.d.ts.map +1 -1
- package/dist/types/date-time.js +196 -288
- package/dist/types/date-time.js.map +1 -6
- package/dist/types/lazy-gc-map.d.ts +26 -26
- package/dist/types/lazy-gc-map.d.ts.map +1 -1
- package/dist/types/lazy-gc-map.js +202 -159
- package/dist/types/lazy-gc-map.js.map +1 -6
- package/dist/types/time.d.ts +23 -23
- package/dist/types/time.d.ts.map +1 -1
- package/dist/types/time.js +169 -158
- package/dist/types/time.js.map +1 -6
- package/dist/types/uuid.d.ts +11 -11
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +95 -70
- package/dist/types/uuid.js.map +1 -6
- package/dist/utils/bytes.d.ts +17 -17
- package/dist/utils/bytes.js +137 -81
- package/dist/utils/bytes.js.map +1 -6
- package/dist/utils/date-format.d.ts +40 -40
- package/dist/utils/date-format.js +187 -101
- package/dist/utils/date-format.js.map +1 -6
- package/dist/utils/error.d.ts +4 -4
- package/dist/utils/error.js +11 -6
- package/dist/utils/error.js.map +1 -6
- package/dist/utils/json.d.ts +19 -19
- package/dist/utils/json.js +187 -135
- package/dist/utils/json.js.map +1 -6
- package/dist/utils/num.d.ts +20 -20
- package/dist/utils/num.js +76 -34
- package/dist/utils/num.js.map +1 -6
- package/dist/utils/obj.d.ts +111 -111
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +706 -496
- package/dist/utils/obj.js.map +1 -6
- package/dist/utils/path.d.ts +10 -10
- package/dist/utils/path.js +35 -18
- package/dist/utils/path.js.map +1 -6
- package/dist/utils/primitive.d.ts +5 -5
- package/dist/utils/primitive.js +34 -14
- package/dist/utils/primitive.js.map +1 -6
- package/dist/utils/str.d.ts +38 -38
- package/dist/utils/str.js +217 -113
- package/dist/utils/str.js.map +1 -6
- package/dist/utils/template-strings.d.ts +26 -26
- package/dist/utils/template-strings.js +113 -40
- package/dist/utils/template-strings.js.map +1 -6
- package/dist/utils/transferable.d.ts +18 -18
- package/dist/utils/transferable.js +218 -151
- package/dist/utils/transferable.js.map +1 -6
- package/dist/utils/wait.d.ts +9 -9
- package/dist/utils/wait.js +30 -15
- package/dist/utils/wait.js.map +1 -6
- package/dist/utils/xml.d.ts +13 -13
- package/dist/utils/xml.js +84 -46
- package/dist/utils/xml.js.map +1 -6
- package/dist/utils/zip.d.ts +22 -22
- package/dist/utils/zip.js +172 -148
- package/dist/utils/zip.js.map +1 -6
- package/docs/array-extensions.md +430 -0
- package/docs/env.md +52 -0
- package/docs/errors.md +41 -56
- package/docs/features.md +82 -97
- package/docs/type-utilities.md +91 -0
- package/docs/types.md +221 -201
- package/docs/utils.md +319 -435
- package/package.json +7 -5
- package/src/common.types.ts +14 -14
- package/src/env.ts +12 -3
- package/src/errors/argument-error.ts +15 -15
- package/src/errors/not-implemented-error.ts +9 -9
- package/src/errors/sd-error.ts +12 -12
- package/src/errors/timeout-error.ts +12 -12
- package/src/extensions/arr-ext.helpers.ts +16 -16
- package/src/extensions/arr-ext.ts +35 -35
- package/src/extensions/arr-ext.types.ts +57 -57
- package/src/extensions/map-ext.ts +16 -16
- package/src/extensions/set-ext.ts +11 -11
- package/src/features/debounce-queue.ts +23 -23
- package/src/features/event-emitter.ts +25 -25
- package/src/features/serial-queue.ts +13 -13
- package/src/globals.ts +4 -4
- package/src/index.ts +5 -5
- package/src/types/date-only.ts +84 -83
- package/src/types/date-time.ts +43 -42
- package/src/types/lazy-gc-map.ts +44 -44
- package/src/types/time.ts +29 -29
- package/src/types/uuid.ts +15 -15
- package/src/utils/bytes.ts +35 -35
- package/src/utils/date-format.ts +59 -59
- package/src/utils/error.ts +4 -4
- package/src/utils/json.ts +41 -41
- package/src/utils/num.ts +20 -20
- package/src/utils/obj.ts +138 -138
- package/src/utils/path.ts +10 -10
- package/src/utils/primitive.ts +6 -6
- package/src/utils/str.ts +48 -48
- package/src/utils/template-strings.ts +29 -29
- package/src/utils/transferable.ts +38 -38
- package/src/utils/wait.ts +10 -10
- package/src/utils/xml.ts +19 -19
- package/src/utils/zip.ts +25 -25
- package/docs/extensions.md +0 -387
- package/tests/errors/errors.spec.ts +0 -80
- package/tests/extensions/array-extension.spec.ts +0 -654
- package/tests/extensions/map-extension.spec.ts +0 -117
- package/tests/extensions/set-extension.spec.ts +0 -67
- package/tests/types/date-only.spec.ts +0 -533
- package/tests/types/date-time.spec.ts +0 -246
- package/tests/types/lazy-gc-map.spec.ts +0 -606
- package/tests/types/time.spec.ts +0 -428
- package/tests/types/uuid.spec.ts +0 -74
- package/tests/utils/bytes-utils.spec.ts +0 -197
- package/tests/utils/date-format.spec.ts +0 -350
- package/tests/utils/debounce-queue.spec.ts +0 -226
- package/tests/utils/json.spec.ts +0 -400
- package/tests/utils/number.spec.ts +0 -136
- package/tests/utils/object.spec.ts +0 -810
- package/tests/utils/path.spec.ts +0 -70
- package/tests/utils/primitive.spec.ts +0 -43
- package/tests/utils/sd-event-emitter.spec.ts +0 -189
- package/tests/utils/serial-queue.spec.ts +0 -305
- package/tests/utils/string.spec.ts +0 -265
- package/tests/utils/template-strings.spec.ts +0 -48
- package/tests/utils/transferable.spec.ts +0 -639
- package/tests/utils/wait.spec.ts +0 -123
- package/tests/utils/xml.spec.ts +0 -146
- 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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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
|
-
|
|
468
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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
|
-
|
|
515
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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
|
-
|
|
521
|
-
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
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
|