@simplysm/core-common 13.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cache/typecheck-browser.tsbuildinfo +1 -0
- package/.cache/typecheck-node.tsbuildinfo +1 -0
- package/.cache/typecheck-tests-browser.tsbuildinfo +1 -0
- package/.cache/typecheck-tests-node.tsbuildinfo +1 -0
- package/README.md +887 -0
- package/dist/common.types.d.ts +74 -0
- package/dist/common.types.d.ts.map +1 -0
- package/dist/common.types.js +5 -0
- package/dist/common.types.js.map +7 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +9 -0
- package/dist/env.js.map +7 -0
- package/dist/errors/argument-error.d.ts +25 -0
- package/dist/errors/argument-error.d.ts.map +1 -0
- package/dist/errors/argument-error.js +18 -0
- package/dist/errors/argument-error.js.map +7 -0
- package/dist/errors/not-implemented-error.d.ts +29 -0
- package/dist/errors/not-implemented-error.d.ts.map +1 -0
- package/dist/errors/not-implemented-error.js +14 -0
- package/dist/errors/not-implemented-error.js.map +7 -0
- package/dist/errors/sd-error.d.ts +27 -0
- package/dist/errors/sd-error.d.ts.map +1 -0
- package/dist/errors/sd-error.js +23 -0
- package/dist/errors/sd-error.js.map +7 -0
- package/dist/errors/timeout-error.d.ts +31 -0
- package/dist/errors/timeout-error.d.ts.map +1 -0
- package/dist/errors/timeout-error.js +17 -0
- package/dist/errors/timeout-error.js.map +7 -0
- package/dist/extensions/arr-ext.d.ts +15 -0
- package/dist/extensions/arr-ext.d.ts.map +1 -0
- package/dist/extensions/arr-ext.helpers.d.ts +19 -0
- package/dist/extensions/arr-ext.helpers.d.ts.map +1 -0
- package/dist/extensions/arr-ext.helpers.js +35 -0
- package/dist/extensions/arr-ext.helpers.js.map +7 -0
- package/dist/extensions/arr-ext.js +546 -0
- package/dist/extensions/arr-ext.js.map +7 -0
- package/dist/extensions/arr-ext.types.d.ts +215 -0
- package/dist/extensions/arr-ext.types.d.ts.map +1 -0
- package/dist/extensions/arr-ext.types.js +1 -0
- package/dist/extensions/arr-ext.types.js.map +7 -0
- package/dist/extensions/map-ext.d.ts +57 -0
- package/dist/extensions/map-ext.d.ts.map +1 -0
- package/dist/extensions/map-ext.js +26 -0
- package/dist/extensions/map-ext.js.map +7 -0
- package/dist/extensions/set-ext.d.ts +36 -0
- package/dist/extensions/set-ext.d.ts.map +1 -0
- package/dist/extensions/set-ext.js +29 -0
- package/dist/extensions/set-ext.js.map +7 -0
- package/dist/features/debounce-queue.d.ts +53 -0
- package/dist/features/debounce-queue.d.ts.map +1 -0
- package/dist/features/debounce-queue.js +80 -0
- package/dist/features/debounce-queue.js.map +7 -0
- package/dist/features/event-emitter.d.ts +66 -0
- package/dist/features/event-emitter.d.ts.map +1 -0
- package/dist/features/event-emitter.js +82 -0
- package/dist/features/event-emitter.js.map +7 -0
- package/dist/features/serial-queue.d.ts +47 -0
- package/dist/features/serial-queue.d.ts.map +1 -0
- package/dist/features/serial-queue.js +66 -0
- package/dist/features/serial-queue.js.map +7 -0
- package/dist/globals.d.ts +12 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +7 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +7 -0
- package/dist/types/date-only.d.ts +152 -0
- package/dist/types/date-only.d.ts.map +1 -0
- package/dist/types/date-only.js +251 -0
- package/dist/types/date-only.js.map +7 -0
- package/dist/types/date-time.d.ts +96 -0
- package/dist/types/date-time.d.ts.map +1 -0
- package/dist/types/date-time.js +220 -0
- package/dist/types/date-time.js.map +7 -0
- package/dist/types/lazy-gc-map.d.ts +80 -0
- package/dist/types/lazy-gc-map.d.ts.map +1 -0
- package/dist/types/lazy-gc-map.js +179 -0
- package/dist/types/lazy-gc-map.js.map +7 -0
- package/dist/types/time.d.ts +68 -0
- package/dist/types/time.d.ts.map +1 -0
- package/dist/types/time.js +151 -0
- package/dist/types/time.js.map +7 -0
- package/dist/types/uuid.d.ts +35 -0
- package/dist/types/uuid.d.ts.map +1 -0
- package/dist/types/uuid.js +71 -0
- package/dist/types/uuid.js.map +7 -0
- package/dist/utils/bytes.d.ts +51 -0
- package/dist/utils/bytes.d.ts.map +1 -0
- package/dist/utils/bytes.js +89 -0
- package/dist/utils/bytes.js.map +7 -0
- package/dist/utils/date-format.d.ts +90 -0
- package/dist/utils/date-format.d.ts.map +1 -0
- package/dist/utils/date-format.js +106 -0
- package/dist/utils/date-format.js.map +7 -0
- package/dist/utils/json.d.ts +34 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +152 -0
- package/dist/utils/json.js.map +7 -0
- package/dist/utils/num.d.ts +60 -0
- package/dist/utils/num.d.ts.map +1 -0
- package/dist/utils/num.js +39 -0
- package/dist/utils/num.js.map +7 -0
- package/dist/utils/obj.d.ts +258 -0
- package/dist/utils/obj.d.ts.map +1 -0
- package/dist/utils/obj.js +538 -0
- package/dist/utils/obj.js.map +7 -0
- package/dist/utils/path.d.ts +23 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +21 -0
- package/dist/utils/path.js.map +7 -0
- package/dist/utils/primitive.d.ts +18 -0
- package/dist/utils/primitive.d.ts.map +1 -0
- package/dist/utils/primitive.js +20 -0
- package/dist/utils/primitive.js.map +7 -0
- package/dist/utils/str.d.ts +103 -0
- package/dist/utils/str.d.ts.map +1 -0
- package/dist/utils/str.js +128 -0
- package/dist/utils/str.js.map +7 -0
- package/dist/utils/template-strings.d.ts +84 -0
- package/dist/utils/template-strings.d.ts.map +1 -0
- package/dist/utils/template-strings.js +49 -0
- package/dist/utils/template-strings.js.map +7 -0
- package/dist/utils/transferable.d.ts +47 -0
- package/dist/utils/transferable.d.ts.map +1 -0
- package/dist/utils/transferable.js +153 -0
- package/dist/utils/transferable.js.map +7 -0
- package/dist/utils/wait.d.ts +19 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +19 -0
- package/dist/utils/wait.js.map +7 -0
- package/dist/utils/xml.d.ts +36 -0
- package/dist/utils/xml.d.ts.map +1 -0
- package/dist/utils/xml.js +51 -0
- package/dist/utils/xml.js.map +7 -0
- package/dist/zip/sd-zip.d.ts +80 -0
- package/dist/zip/sd-zip.d.ts.map +1 -0
- package/dist/zip/sd-zip.js +153 -0
- package/dist/zip/sd-zip.js.map +7 -0
- package/package.json +31 -0
- package/src/common.types.ts +91 -0
- package/src/env.ts +11 -0
- package/src/errors/argument-error.ts +40 -0
- package/src/errors/not-implemented-error.ts +32 -0
- package/src/errors/sd-error.ts +53 -0
- package/src/errors/timeout-error.ts +36 -0
- package/src/extensions/arr-ext.helpers.ts +53 -0
- package/src/extensions/arr-ext.ts +777 -0
- package/src/extensions/arr-ext.types.ts +258 -0
- package/src/extensions/map-ext.ts +86 -0
- package/src/extensions/set-ext.ts +68 -0
- package/src/features/debounce-queue.ts +116 -0
- package/src/features/event-emitter.ts +112 -0
- package/src/features/serial-queue.ts +94 -0
- package/src/globals.ts +12 -0
- package/src/index.ts +55 -0
- package/src/types/date-only.ts +329 -0
- package/src/types/date-time.ts +294 -0
- package/src/types/lazy-gc-map.ts +244 -0
- package/src/types/time.ts +210 -0
- package/src/types/uuid.ts +113 -0
- package/src/utils/bytes.ts +160 -0
- package/src/utils/date-format.ts +239 -0
- package/src/utils/json.ts +230 -0
- package/src/utils/num.ts +97 -0
- package/src/utils/obj.ts +956 -0
- package/src/utils/path.ts +40 -0
- package/src/utils/primitive.ts +33 -0
- package/src/utils/str.ts +252 -0
- package/src/utils/template-strings.ts +132 -0
- package/src/utils/transferable.ts +269 -0
- package/src/utils/wait.ts +40 -0
- package/src/utils/xml.ts +105 -0
- package/src/zip/sd-zip.ts +218 -0
- package/tests/errors/errors.spec.ts +196 -0
- package/tests/extensions/array-extension.spec.ts +790 -0
- package/tests/extensions/map-extension.spec.ts +147 -0
- package/tests/extensions/set-extension.spec.ts +74 -0
- package/tests/types/date-only.spec.ts +636 -0
- package/tests/types/date-time.spec.ts +391 -0
- package/tests/types/lazy-gc-map.spec.ts +692 -0
- package/tests/types/time.spec.ts +559 -0
- package/tests/types/types.spec.ts +55 -0
- package/tests/types/uuid.spec.ts +91 -0
- package/tests/utils/bytes-utils.spec.ts +230 -0
- package/tests/utils/date-format.spec.ts +371 -0
- package/tests/utils/debounce-queue.spec.ts +272 -0
- package/tests/utils/json.spec.ts +475 -0
- package/tests/utils/number.spec.ts +184 -0
- package/tests/utils/object.spec.ts +827 -0
- package/tests/utils/path.spec.ts +78 -0
- package/tests/utils/primitive.spec.ts +55 -0
- package/tests/utils/sd-event-emitter.spec.ts +216 -0
- package/tests/utils/serial-queue.spec.ts +365 -0
- package/tests/utils/string.spec.ts +294 -0
- package/tests/utils/template-strings.spec.ts +96 -0
- package/tests/utils/transferable.spec.ts +698 -0
- package/tests/utils/wait.spec.ts +145 -0
- package/tests/utils/xml.spec.ts +146 -0
- package/tests/zip/sd-zip.spec.ts +234 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
transferableEncode as transferEncode,
|
|
4
|
+
transferableDecode as transferDecode,
|
|
5
|
+
DateTime,
|
|
6
|
+
DateOnly,
|
|
7
|
+
Time,
|
|
8
|
+
Uuid,
|
|
9
|
+
} from "@simplysm/core-common";
|
|
10
|
+
|
|
11
|
+
describe("TransferableConvert", () => {
|
|
12
|
+
//#region encode - 특수 타입
|
|
13
|
+
|
|
14
|
+
describe("encode() - 특수 타입", () => {
|
|
15
|
+
it("DateTime을 인코딩한다", () => {
|
|
16
|
+
const dt = new DateTime(2025, 1, 6, 15, 30, 45, 123);
|
|
17
|
+
const { result } = transferEncode(dt);
|
|
18
|
+
|
|
19
|
+
expect(result).toEqual({
|
|
20
|
+
__type__: "DateTime",
|
|
21
|
+
data: dt.tick,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("DateOnly를 인코딩한다", () => {
|
|
26
|
+
const d = new DateOnly(2025, 1, 6);
|
|
27
|
+
const { result } = transferEncode(d);
|
|
28
|
+
|
|
29
|
+
expect(result).toEqual({
|
|
30
|
+
__type__: "DateOnly",
|
|
31
|
+
data: d.tick,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("Time을 인코딩한다", () => {
|
|
36
|
+
const t = new Time(15, 30, 45, 123);
|
|
37
|
+
const { result } = transferEncode(t);
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual({
|
|
40
|
+
__type__: "Time",
|
|
41
|
+
data: t.tick,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("Uuid를 인코딩한다", () => {
|
|
46
|
+
const uuid = Uuid.new();
|
|
47
|
+
const { result } = transferEncode(uuid);
|
|
48
|
+
|
|
49
|
+
expect(result).toEqual({
|
|
50
|
+
__type__: "Uuid",
|
|
51
|
+
data: uuid.toString(),
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("Error를 인코딩한다", () => {
|
|
56
|
+
const err = new Error("test error");
|
|
57
|
+
err.stack = "test stack";
|
|
58
|
+
const { result } = transferEncode(err);
|
|
59
|
+
|
|
60
|
+
expect(result).toEqual({
|
|
61
|
+
__type__: "Error",
|
|
62
|
+
data: {
|
|
63
|
+
name: "Error",
|
|
64
|
+
message: "test error",
|
|
65
|
+
stack: "test stack",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("Error의 cause를 재귀적으로 인코딩한다", () => {
|
|
71
|
+
const cause = new Error("cause error");
|
|
72
|
+
const err = new Error("main error", { cause });
|
|
73
|
+
const { result } = transferEncode(err);
|
|
74
|
+
|
|
75
|
+
const typedResult = result as {
|
|
76
|
+
__type__: string;
|
|
77
|
+
data: {
|
|
78
|
+
name: string;
|
|
79
|
+
message: string;
|
|
80
|
+
cause?: {
|
|
81
|
+
__type__: string;
|
|
82
|
+
data: {
|
|
83
|
+
name: string;
|
|
84
|
+
message: string;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
expect(typedResult.data.cause).toEqual({
|
|
91
|
+
__type__: "Error",
|
|
92
|
+
data: {
|
|
93
|
+
name: "Error",
|
|
94
|
+
message: "cause error",
|
|
95
|
+
stack: cause.stack,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("Error의 code 속성을 인코딩한다", () => {
|
|
101
|
+
const err = new Error("test error") as Error & { code: string };
|
|
102
|
+
err.code = "ERR_CUSTOM";
|
|
103
|
+
const { result } = transferEncode(err);
|
|
104
|
+
|
|
105
|
+
const typedResult = result as {
|
|
106
|
+
__type__: string;
|
|
107
|
+
data: {
|
|
108
|
+
name: string;
|
|
109
|
+
message: string;
|
|
110
|
+
code?: string;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
expect(typedResult.data.code).toBe("ERR_CUSTOM");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("Error의 detail 속성을 인코딩한다", () => {
|
|
118
|
+
const err = new Error("test error") as Error & { detail: unknown };
|
|
119
|
+
err.detail = { userId: 123, action: "delete" };
|
|
120
|
+
const { result } = transferEncode(err);
|
|
121
|
+
|
|
122
|
+
const typedResult = result as {
|
|
123
|
+
__type__: string;
|
|
124
|
+
data: {
|
|
125
|
+
name: string;
|
|
126
|
+
message: string;
|
|
127
|
+
detail?: unknown;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
expect(typedResult.data.detail).toEqual({ userId: 123, action: "delete" });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("Error의 detail에 포함된 특수 타입도 인코딩한다", () => {
|
|
135
|
+
const err = new Error("test error") as Error & { detail: unknown };
|
|
136
|
+
const dt = new DateTime(2025, 1, 6);
|
|
137
|
+
err.detail = { timestamp: dt };
|
|
138
|
+
const { result } = transferEncode(err);
|
|
139
|
+
|
|
140
|
+
const typedResult = result as {
|
|
141
|
+
__type__: string;
|
|
142
|
+
data: {
|
|
143
|
+
name: string;
|
|
144
|
+
message: string;
|
|
145
|
+
detail?: { timestamp: { __type__: string; data: number } };
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
expect(typedResult.data.detail?.timestamp).toEqual({
|
|
150
|
+
__type__: "DateTime",
|
|
151
|
+
data: dt.tick,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("Uint8Array를 인코딩하고 transferList에 추가한다", () => {
|
|
156
|
+
const bytes = new TextEncoder().encode("hello");
|
|
157
|
+
const { result, transferList } = transferEncode(bytes);
|
|
158
|
+
|
|
159
|
+
expect(result).toBe(bytes);
|
|
160
|
+
expect(transferList).toContain(bytes.buffer);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("Date를 인코딩한다", () => {
|
|
164
|
+
const date = new Date(2025, 0, 6, 15, 30, 45, 123);
|
|
165
|
+
const { result } = transferEncode(date);
|
|
166
|
+
|
|
167
|
+
expect(result).toEqual({
|
|
168
|
+
__type__: "Date",
|
|
169
|
+
data: date.getTime(),
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("RegExp를 인코딩한다", () => {
|
|
174
|
+
const regex = /test\d+/gi;
|
|
175
|
+
const { result } = transferEncode(regex);
|
|
176
|
+
|
|
177
|
+
expect(result).toEqual({
|
|
178
|
+
__type__: "RegExp",
|
|
179
|
+
data: { source: "test\\d+", flags: "gi" },
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
//#endregion
|
|
185
|
+
|
|
186
|
+
//#region encode - 컬렉션
|
|
187
|
+
|
|
188
|
+
describe("encode() - 컬렉션", () => {
|
|
189
|
+
it("배열을 재귀적으로 인코딩한다", () => {
|
|
190
|
+
const arr = [new DateTime(2025, 1, 6), Uuid.new(), "string", 123] as const;
|
|
191
|
+
const { result } = transferEncode(arr);
|
|
192
|
+
|
|
193
|
+
expect(Array.isArray(result)).toBe(true);
|
|
194
|
+
const resultArr = result as unknown[];
|
|
195
|
+
expect(resultArr).toHaveLength(4);
|
|
196
|
+
expect(resultArr[0]).toMatchObject({ __type__: "DateTime" });
|
|
197
|
+
expect(resultArr[1]).toMatchObject({ __type__: "Uuid" });
|
|
198
|
+
expect(resultArr[2]).toBe("string");
|
|
199
|
+
expect(resultArr[3]).toBe(123);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("Map을 재귀적으로 인코딩한다", () => {
|
|
203
|
+
const map = new Map<string, DateTime | Uuid>([
|
|
204
|
+
["key1", new DateTime(2025, 1, 6)],
|
|
205
|
+
["key2", Uuid.new()],
|
|
206
|
+
]);
|
|
207
|
+
const { result } = transferEncode(map);
|
|
208
|
+
|
|
209
|
+
expect(result instanceof Map).toBe(true);
|
|
210
|
+
const resultMap = result as Map<string, unknown>;
|
|
211
|
+
expect(resultMap.size).toBe(2);
|
|
212
|
+
expect(resultMap.get("key1")).toMatchObject({ __type__: "DateTime" });
|
|
213
|
+
expect(resultMap.get("key2")).toMatchObject({ __type__: "Uuid" });
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("Set을 재귀적으로 인코딩한다", () => {
|
|
217
|
+
const set = new Set([new DateTime(2025, 1, 6), Uuid.new()]);
|
|
218
|
+
const { result } = transferEncode(set);
|
|
219
|
+
|
|
220
|
+
expect(result instanceof Set).toBe(true);
|
|
221
|
+
const resultSet = result as Set<unknown>;
|
|
222
|
+
expect(resultSet.size).toBe(2);
|
|
223
|
+
const arr = Array.from(resultSet);
|
|
224
|
+
expect(arr[0]).toMatchObject({ __type__: "DateTime" });
|
|
225
|
+
expect(arr[1]).toMatchObject({ __type__: "Uuid" });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("중첩된 객체를 재귀적으로 인코딩한다", () => {
|
|
229
|
+
const obj = {
|
|
230
|
+
dt: new DateTime(2025, 1, 6),
|
|
231
|
+
nested: {
|
|
232
|
+
uuid: Uuid.new(),
|
|
233
|
+
arr: [new DateOnly(2025, 1, 6)],
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
const { result } = transferEncode(obj);
|
|
237
|
+
|
|
238
|
+
const typedResult = result as {
|
|
239
|
+
dt: { __type__: string };
|
|
240
|
+
nested: {
|
|
241
|
+
uuid: { __type__: string };
|
|
242
|
+
arr: { __type__: string }[];
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
expect(typedResult.dt).toMatchObject({ __type__: "DateTime" });
|
|
247
|
+
expect(typedResult.nested.uuid).toMatchObject({ __type__: "Uuid" });
|
|
248
|
+
expect(typedResult.nested.arr[0]).toMatchObject({ __type__: "DateOnly" });
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
253
|
+
|
|
254
|
+
//#region encode - 순환 참조 감지
|
|
255
|
+
|
|
256
|
+
describe("encode() - 순환 참조 감지", () => {
|
|
257
|
+
it("자기 참조 객체를 인코딩하면 TypeError를 던진다", () => {
|
|
258
|
+
const obj: Record<string, unknown> = { a: 1 };
|
|
259
|
+
obj["self"] = obj;
|
|
260
|
+
|
|
261
|
+
expect(() => transferEncode(obj)).toThrow(TypeError);
|
|
262
|
+
expect(() => transferEncode(obj)).toThrow("순환 참조가 감지되었습니다");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("중첩된 순환 참조를 감지한다", () => {
|
|
266
|
+
const a: Record<string, unknown> = { name: "a" };
|
|
267
|
+
const b: Record<string, unknown> = { name: "b", ref: a };
|
|
268
|
+
a["ref"] = b;
|
|
269
|
+
|
|
270
|
+
expect(() => transferEncode(a)).toThrow("순환 참조가 감지되었습니다");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("배열 내 순환 참조를 감지한다", () => {
|
|
274
|
+
const arr: unknown[] = [1, 2, 3];
|
|
275
|
+
arr.push(arr);
|
|
276
|
+
|
|
277
|
+
expect(() => transferEncode(arr)).toThrow("순환 참조가 감지되었습니다");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("Map 내 순환 참조를 감지한다", () => {
|
|
281
|
+
const map = new Map<string, unknown>();
|
|
282
|
+
map.set("self", map);
|
|
283
|
+
|
|
284
|
+
expect(() => transferEncode(map)).toThrow("순환 참조가 감지되었습니다");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("Set 내 순환 참조를 감지한다", () => {
|
|
288
|
+
const set = new Set<unknown>();
|
|
289
|
+
set.add(set);
|
|
290
|
+
|
|
291
|
+
expect(() => transferEncode(set)).toThrow("순환 참조가 감지되었습니다");
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
//#endregion
|
|
296
|
+
|
|
297
|
+
//#region encode - DAG (공유 객체)
|
|
298
|
+
|
|
299
|
+
describe("encode() - DAG (공유 객체)", () => {
|
|
300
|
+
it("동일 객체를 여러 곳에서 참조해도 에러 없이 인코딩된다", () => {
|
|
301
|
+
const shared = { name: "shared" };
|
|
302
|
+
const data = { a: shared, b: shared };
|
|
303
|
+
const { result } = transferEncode(data);
|
|
304
|
+
const decoded = result as Record<string, Record<string, string>>;
|
|
305
|
+
expect(decoded.a.name).toBe("shared");
|
|
306
|
+
expect(decoded.b.name).toBe("shared");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("동일 배열을 여러 곳에서 참조해도 에러 없이 인코딩된다", () => {
|
|
310
|
+
const sharedArr = [1, 2, 3];
|
|
311
|
+
const data = { x: sharedArr, y: sharedArr };
|
|
312
|
+
const { result } = transferEncode(data);
|
|
313
|
+
const decoded = result as Record<string, number[]>;
|
|
314
|
+
expect(decoded.x).toEqual([1, 2, 3]);
|
|
315
|
+
expect(decoded.y).toEqual([1, 2, 3]);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("순환 참조는 여전히 TypeError를 던진다", () => {
|
|
319
|
+
const a: Record<string, unknown> = {};
|
|
320
|
+
const b: Record<string, unknown> = { a };
|
|
321
|
+
a.b = b;
|
|
322
|
+
expect(() => transferEncode(a)).toThrow(TypeError);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
//#endregion
|
|
327
|
+
|
|
328
|
+
//#region decode - 특수 타입
|
|
329
|
+
|
|
330
|
+
describe("decode() - 특수 타입", () => {
|
|
331
|
+
it("DateTime을 디코딩한다", () => {
|
|
332
|
+
const tick = new DateTime(2025, 1, 6, 15, 30, 45, 123).tick;
|
|
333
|
+
const encoded = { __type__: "DateTime", data: tick };
|
|
334
|
+
const decoded = transferDecode(encoded);
|
|
335
|
+
|
|
336
|
+
expect(decoded instanceof DateTime).toBe(true);
|
|
337
|
+
const dt = decoded as DateTime;
|
|
338
|
+
expect(dt.year).toBe(2025);
|
|
339
|
+
expect(dt.month).toBe(1);
|
|
340
|
+
expect(dt.day).toBe(6);
|
|
341
|
+
expect(dt.hour).toBe(15);
|
|
342
|
+
expect(dt.minute).toBe(30);
|
|
343
|
+
expect(dt.second).toBe(45);
|
|
344
|
+
expect(dt.millisecond).toBe(123);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("DateOnly를 디코딩한다", () => {
|
|
348
|
+
const tick = new DateOnly(2025, 1, 6).tick;
|
|
349
|
+
const encoded = { __type__: "DateOnly", data: tick };
|
|
350
|
+
const decoded = transferDecode(encoded);
|
|
351
|
+
|
|
352
|
+
expect(decoded instanceof DateOnly).toBe(true);
|
|
353
|
+
const d = decoded as DateOnly;
|
|
354
|
+
expect(d.year).toBe(2025);
|
|
355
|
+
expect(d.month).toBe(1);
|
|
356
|
+
expect(d.day).toBe(6);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("Time을 디코딩한다", () => {
|
|
360
|
+
const tick = new Time(15, 30, 45, 123).tick;
|
|
361
|
+
const encoded = { __type__: "Time", data: tick };
|
|
362
|
+
const decoded = transferDecode(encoded);
|
|
363
|
+
|
|
364
|
+
expect(decoded instanceof Time).toBe(true);
|
|
365
|
+
const t = decoded as Time;
|
|
366
|
+
expect(t.hour).toBe(15);
|
|
367
|
+
expect(t.minute).toBe(30);
|
|
368
|
+
expect(t.second).toBe(45);
|
|
369
|
+
expect(t.millisecond).toBe(123);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("Uuid를 디코딩한다", () => {
|
|
373
|
+
const uuid = Uuid.new();
|
|
374
|
+
const encoded = { __type__: "Uuid", data: uuid.toString() };
|
|
375
|
+
const decoded = transferDecode(encoded);
|
|
376
|
+
|
|
377
|
+
expect(decoded instanceof Uuid).toBe(true);
|
|
378
|
+
expect((decoded as Uuid).toString()).toBe(uuid.toString());
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("Error를 디코딩한다", () => {
|
|
382
|
+
const encoded = {
|
|
383
|
+
__type__: "Error",
|
|
384
|
+
data: {
|
|
385
|
+
name: "CustomError",
|
|
386
|
+
message: "test error",
|
|
387
|
+
stack: "test stack",
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
const decoded = transferDecode(encoded);
|
|
391
|
+
|
|
392
|
+
expect(decoded instanceof Error).toBe(true);
|
|
393
|
+
const err = decoded as Error;
|
|
394
|
+
expect(err.name).toBe("CustomError");
|
|
395
|
+
expect(err.message).toBe("test error");
|
|
396
|
+
expect(err.stack).toBe("test stack");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("Error의 cause를 재귀적으로 디코딩한다", () => {
|
|
400
|
+
const encoded = {
|
|
401
|
+
__type__: "Error",
|
|
402
|
+
data: {
|
|
403
|
+
name: "Error",
|
|
404
|
+
message: "main error",
|
|
405
|
+
cause: {
|
|
406
|
+
__type__: "Error",
|
|
407
|
+
data: {
|
|
408
|
+
name: "Error",
|
|
409
|
+
message: "cause error",
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
const decoded = transferDecode(encoded);
|
|
415
|
+
|
|
416
|
+
expect(decoded instanceof Error).toBe(true);
|
|
417
|
+
const err = decoded as Error;
|
|
418
|
+
expect(err.message).toBe("main error");
|
|
419
|
+
expect(err.cause instanceof Error).toBe(true);
|
|
420
|
+
expect((err.cause as Error).message).toBe("cause error");
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("Error의 code 속성을 디코딩한다", () => {
|
|
424
|
+
const encoded = {
|
|
425
|
+
__type__: "Error",
|
|
426
|
+
data: {
|
|
427
|
+
name: "Error",
|
|
428
|
+
message: "test error",
|
|
429
|
+
code: "ERR_CUSTOM",
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
const decoded = transferDecode(encoded);
|
|
433
|
+
|
|
434
|
+
expect(decoded instanceof Error).toBe(true);
|
|
435
|
+
const err = decoded as Error & { code?: string };
|
|
436
|
+
expect(err.code).toBe("ERR_CUSTOM");
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("Error의 detail 속성을 디코딩한다", () => {
|
|
440
|
+
const encoded = {
|
|
441
|
+
__type__: "Error",
|
|
442
|
+
data: {
|
|
443
|
+
name: "Error",
|
|
444
|
+
message: "test error",
|
|
445
|
+
detail: { userId: 123, action: "delete" },
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
const decoded = transferDecode(encoded);
|
|
449
|
+
|
|
450
|
+
expect(decoded instanceof Error).toBe(true);
|
|
451
|
+
const err = decoded as Error & { detail?: unknown };
|
|
452
|
+
expect(err.detail).toEqual({ userId: 123, action: "delete" });
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("Error의 detail에 포함된 특수 타입도 디코딩한다", () => {
|
|
456
|
+
const tick = new DateTime(2025, 1, 6).tick;
|
|
457
|
+
const encoded = {
|
|
458
|
+
__type__: "Error",
|
|
459
|
+
data: {
|
|
460
|
+
name: "Error",
|
|
461
|
+
message: "test error",
|
|
462
|
+
detail: { timestamp: { __type__: "DateTime", data: tick } },
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
const decoded = transferDecode(encoded);
|
|
466
|
+
|
|
467
|
+
expect(decoded instanceof Error).toBe(true);
|
|
468
|
+
const err = decoded as Error & { detail?: { timestamp: DateTime } };
|
|
469
|
+
expect(err.detail?.timestamp instanceof DateTime).toBe(true);
|
|
470
|
+
expect(err.detail?.timestamp.tick).toBe(tick);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("Date를 디코딩한다", () => {
|
|
474
|
+
const tick = new Date(2025, 0, 6, 15, 30, 45, 123).getTime();
|
|
475
|
+
const encoded = { __type__: "Date", data: tick };
|
|
476
|
+
const decoded = transferDecode(encoded);
|
|
477
|
+
|
|
478
|
+
expect(decoded instanceof Date).toBe(true);
|
|
479
|
+
const date = decoded as Date;
|
|
480
|
+
expect(date.getFullYear()).toBe(2025);
|
|
481
|
+
expect(date.getMonth()).toBe(0);
|
|
482
|
+
expect(date.getDate()).toBe(6);
|
|
483
|
+
expect(date.getHours()).toBe(15);
|
|
484
|
+
expect(date.getMinutes()).toBe(30);
|
|
485
|
+
expect(date.getSeconds()).toBe(45);
|
|
486
|
+
expect(date.getMilliseconds()).toBe(123);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("RegExp를 디코딩한다", () => {
|
|
490
|
+
const encoded = {
|
|
491
|
+
__type__: "RegExp",
|
|
492
|
+
data: { source: "test\\d+", flags: "gi" },
|
|
493
|
+
};
|
|
494
|
+
const decoded = transferDecode(encoded);
|
|
495
|
+
|
|
496
|
+
expect(decoded instanceof RegExp).toBe(true);
|
|
497
|
+
const regex = decoded as RegExp;
|
|
498
|
+
expect(regex.source).toBe("test\\d+");
|
|
499
|
+
expect(regex.flags).toBe("gi");
|
|
500
|
+
// g 플래그가 있는 정규식은 lastIndex가 stateful이므로 리셋 후 테스트
|
|
501
|
+
expect(regex.test("test123")).toBe(true);
|
|
502
|
+
regex.lastIndex = 0;
|
|
503
|
+
expect(regex.test("TEST456")).toBe(true);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
//#endregion
|
|
508
|
+
|
|
509
|
+
//#region decode - 컬렉션
|
|
510
|
+
|
|
511
|
+
describe("decode() - 컬렉션", () => {
|
|
512
|
+
it("배열을 재귀적으로 디코딩한다", () => {
|
|
513
|
+
const tick = new DateTime(2025, 1, 6).tick;
|
|
514
|
+
const uuidStr = Uuid.new().toString();
|
|
515
|
+
const encoded = [{ __type__: "DateTime", data: tick }, { __type__: "Uuid", data: uuidStr }, "string", 123];
|
|
516
|
+
const decoded = transferDecode(encoded);
|
|
517
|
+
|
|
518
|
+
expect(Array.isArray(decoded)).toBe(true);
|
|
519
|
+
const arr = decoded as unknown[];
|
|
520
|
+
expect(arr[0] instanceof DateTime).toBe(true);
|
|
521
|
+
expect(arr[1] instanceof Uuid).toBe(true);
|
|
522
|
+
expect(arr[2]).toBe("string");
|
|
523
|
+
expect(arr[3]).toBe(123);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it("Map을 재귀적으로 디코딩한다", () => {
|
|
527
|
+
const tick = new DateTime(2025, 1, 6).tick;
|
|
528
|
+
const encoded = new Map<string, unknown>([
|
|
529
|
+
["key1", { __type__: "DateTime", data: tick }],
|
|
530
|
+
["key2", "value"],
|
|
531
|
+
]);
|
|
532
|
+
const decoded = transferDecode(encoded);
|
|
533
|
+
|
|
534
|
+
expect(decoded instanceof Map).toBe(true);
|
|
535
|
+
const map = decoded as Map<string, unknown>;
|
|
536
|
+
expect(map.get("key1") instanceof DateTime).toBe(true);
|
|
537
|
+
expect(map.get("key2")).toBe("value");
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("Set을 재귀적으로 디코딩한다", () => {
|
|
541
|
+
const tick = new DateTime(2025, 1, 6).tick;
|
|
542
|
+
const encoded = new Set([{ __type__: "DateTime", data: tick }, "string"]);
|
|
543
|
+
const decoded = transferDecode(encoded);
|
|
544
|
+
|
|
545
|
+
expect(decoded instanceof Set).toBe(true);
|
|
546
|
+
const set = decoded as Set<unknown>;
|
|
547
|
+
const arr = Array.from(set);
|
|
548
|
+
expect(arr[0] instanceof DateTime).toBe(true);
|
|
549
|
+
expect(arr[1]).toBe("string");
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("중첩된 객체를 재귀적으로 디코딩한다", () => {
|
|
553
|
+
const dtTick = new DateTime(2025, 1, 6).tick;
|
|
554
|
+
const uuidStr = Uuid.new().toString();
|
|
555
|
+
const dTick = new DateOnly(2025, 1, 6).tick;
|
|
556
|
+
const encoded = {
|
|
557
|
+
dt: { __type__: "DateTime", data: dtTick },
|
|
558
|
+
nested: {
|
|
559
|
+
uuid: { __type__: "Uuid", data: uuidStr },
|
|
560
|
+
arr: [{ __type__: "DateOnly", data: dTick }],
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
const decoded = transferDecode(encoded);
|
|
564
|
+
|
|
565
|
+
const obj = decoded as {
|
|
566
|
+
dt: DateTime;
|
|
567
|
+
nested: {
|
|
568
|
+
uuid: Uuid;
|
|
569
|
+
arr: DateOnly[];
|
|
570
|
+
};
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
expect(obj.dt instanceof DateTime).toBe(true);
|
|
574
|
+
expect(obj.nested.uuid instanceof Uuid).toBe(true);
|
|
575
|
+
expect(obj.nested.arr[0] instanceof DateOnly).toBe(true);
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
//#endregion
|
|
580
|
+
|
|
581
|
+
//#region decode 원본 보존
|
|
582
|
+
|
|
583
|
+
describe("decode() - 원본 보존", () => {
|
|
584
|
+
it("원본 배열이 변경되지 않는다", () => {
|
|
585
|
+
const tick = new DateTime(2025, 1, 6).tick;
|
|
586
|
+
const original = [{ __type__: "DateTime", data: tick }, "string", 123];
|
|
587
|
+
const originalCopy = JSON.stringify(original);
|
|
588
|
+
|
|
589
|
+
transferDecode(original);
|
|
590
|
+
|
|
591
|
+
// 원본이 변경되지 않았는지 확인
|
|
592
|
+
expect(JSON.stringify(original)).toBe(originalCopy);
|
|
593
|
+
expect(original[0]).toEqual({ __type__: "DateTime", data: tick });
|
|
594
|
+
expect(original[1]).toBe("string");
|
|
595
|
+
expect(original[2]).toBe(123);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it("원본 객체가 변경되지 않는다", () => {
|
|
599
|
+
const tick = new DateTime(2025, 1, 6).tick;
|
|
600
|
+
const original = {
|
|
601
|
+
dt: { __type__: "DateTime", data: tick },
|
|
602
|
+
value: 123,
|
|
603
|
+
};
|
|
604
|
+
const originalCopy = JSON.stringify(original);
|
|
605
|
+
|
|
606
|
+
transferDecode(original);
|
|
607
|
+
|
|
608
|
+
// 원본이 변경되지 않았는지 확인
|
|
609
|
+
expect(JSON.stringify(original)).toBe(originalCopy);
|
|
610
|
+
expect(original.dt).toEqual({ __type__: "DateTime", data: tick });
|
|
611
|
+
expect(original.value).toBe(123);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it("중첩 배열/객체도 원본이 보존된다", () => {
|
|
615
|
+
const tick = new DateTime(2025, 1, 6).tick;
|
|
616
|
+
const original = {
|
|
617
|
+
nested: {
|
|
618
|
+
arr: [{ __type__: "DateTime", data: tick }],
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
const originalCopy = JSON.stringify(original);
|
|
622
|
+
|
|
623
|
+
transferDecode(original);
|
|
624
|
+
|
|
625
|
+
expect(JSON.stringify(original)).toBe(originalCopy);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it("decode 결과는 새 인스턴스다 (원본과 다름)", () => {
|
|
629
|
+
const tick = new DateTime(2025, 1, 6).tick;
|
|
630
|
+
const original = [{ __type__: "DateTime", data: tick }];
|
|
631
|
+
|
|
632
|
+
const decoded = transferDecode(original);
|
|
633
|
+
|
|
634
|
+
// 결과는 새 배열
|
|
635
|
+
expect(decoded).not.toBe(original);
|
|
636
|
+
// 배열 내용은 변환됨
|
|
637
|
+
expect(Array.isArray(decoded)).toBe(true);
|
|
638
|
+
expect((decoded as unknown[])[0] instanceof DateTime).toBe(true);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
//#endregion
|
|
643
|
+
|
|
644
|
+
//#region 왕복 변환 (round-trip)
|
|
645
|
+
|
|
646
|
+
describe("왕복 변환 (encode → decode)", () => {
|
|
647
|
+
it("Date를 왕복 변환한다", () => {
|
|
648
|
+
const original = new Date(2025, 0, 6, 15, 30, 45, 123);
|
|
649
|
+
const { result } = transferEncode(original);
|
|
650
|
+
const decoded = transferDecode(result) as Date;
|
|
651
|
+
|
|
652
|
+
expect(decoded.getTime()).toBe(original.getTime());
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it("DateTime을 왕복 변환한다", () => {
|
|
656
|
+
const original = new DateTime(2025, 1, 6, 15, 30, 45, 123);
|
|
657
|
+
const { result } = transferEncode(original);
|
|
658
|
+
const decoded = transferDecode(result) as DateTime;
|
|
659
|
+
|
|
660
|
+
expect(decoded.tick).toBe(original.tick);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it("복잡한 객체를 왕복 변환한다", () => {
|
|
664
|
+
const original = {
|
|
665
|
+
dt: new DateTime(2025, 1, 6),
|
|
666
|
+
d: new DateOnly(2025, 1, 6),
|
|
667
|
+
t: new Time(15, 30, 45),
|
|
668
|
+
uuid: Uuid.new(),
|
|
669
|
+
arr: [new DateTime(2024, 12, 31)],
|
|
670
|
+
map: new Map([["key", new DateOnly(2025, 1, 1)]]),
|
|
671
|
+
set: new Set([new Time(12, 0, 0)]),
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
const { result } = transferEncode(original);
|
|
675
|
+
const decoded = transferDecode(result) as typeof original;
|
|
676
|
+
|
|
677
|
+
expect(decoded.dt instanceof DateTime).toBe(true);
|
|
678
|
+
expect(decoded.d instanceof DateOnly).toBe(true);
|
|
679
|
+
expect(decoded.t instanceof Time).toBe(true);
|
|
680
|
+
expect(decoded.uuid instanceof Uuid).toBe(true);
|
|
681
|
+
expect(decoded.arr[0] instanceof DateTime).toBe(true);
|
|
682
|
+
expect(decoded.map.get("key") instanceof DateOnly).toBe(true);
|
|
683
|
+
expect(Array.from(decoded.set)[0] instanceof Time).toBe(true);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it("RegExp를 왕복 변환한다", () => {
|
|
687
|
+
const original = /test\d+/gi;
|
|
688
|
+
const { result } = transferEncode(original);
|
|
689
|
+
const decoded = transferDecode(result) as RegExp;
|
|
690
|
+
|
|
691
|
+
expect(decoded instanceof RegExp).toBe(true);
|
|
692
|
+
expect(decoded.source).toBe(original.source);
|
|
693
|
+
expect(decoded.flags).toBe(original.flags);
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
//#endregion
|
|
698
|
+
});
|