@simplysm/core-common 13.0.69 → 13.0.71

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 (151) hide show
  1. package/README.md +66 -267
  2. package/dist/common.types.d.ts +14 -14
  3. package/dist/errors/argument-error.d.ts +10 -10
  4. package/dist/errors/argument-error.d.ts.map +1 -1
  5. package/dist/errors/argument-error.js +2 -2
  6. package/dist/errors/argument-error.js.map +1 -1
  7. package/dist/errors/not-implemented-error.d.ts +8 -8
  8. package/dist/errors/not-implemented-error.js +2 -2
  9. package/dist/errors/not-implemented-error.js.map +1 -1
  10. package/dist/errors/sd-error.d.ts +10 -10
  11. package/dist/errors/sd-error.d.ts.map +1 -1
  12. package/dist/errors/timeout-error.d.ts +10 -10
  13. package/dist/errors/timeout-error.js +3 -3
  14. package/dist/errors/timeout-error.js.map +1 -1
  15. package/dist/extensions/arr-ext.d.ts +2 -2
  16. package/dist/extensions/arr-ext.helpers.d.ts +8 -8
  17. package/dist/extensions/arr-ext.helpers.js +1 -1
  18. package/dist/extensions/arr-ext.helpers.js.map +1 -1
  19. package/dist/extensions/arr-ext.js +13 -13
  20. package/dist/extensions/arr-ext.js.map +1 -1
  21. package/dist/extensions/arr-ext.types.d.ts +57 -57
  22. package/dist/extensions/arr-ext.types.d.ts.map +1 -1
  23. package/dist/extensions/map-ext.d.ts +16 -16
  24. package/dist/extensions/set-ext.d.ts +11 -11
  25. package/dist/features/debounce-queue.d.ts +17 -15
  26. package/dist/features/debounce-queue.d.ts.map +1 -1
  27. package/dist/features/debounce-queue.js +6 -6
  28. package/dist/features/debounce-queue.js.map +1 -1
  29. package/dist/features/event-emitter.d.ts +20 -20
  30. package/dist/features/event-emitter.js +17 -17
  31. package/dist/features/serial-queue.d.ts +11 -11
  32. package/dist/features/serial-queue.js +5 -5
  33. package/dist/features/serial-queue.js.map +1 -1
  34. package/dist/globals.d.ts +4 -4
  35. package/dist/types/date-only.d.ts +64 -64
  36. package/dist/types/date-only.d.ts.map +1 -1
  37. package/dist/types/date-only.js +63 -63
  38. package/dist/types/date-time.d.ts +37 -37
  39. package/dist/types/date-time.d.ts.map +1 -1
  40. package/dist/types/date-time.js +54 -37
  41. package/dist/types/date-time.js.map +1 -1
  42. package/dist/types/lazy-gc-map.d.ts +26 -26
  43. package/dist/types/lazy-gc-map.d.ts.map +1 -1
  44. package/dist/types/lazy-gc-map.js +26 -26
  45. package/dist/types/lazy-gc-map.js.map +1 -1
  46. package/dist/types/time.d.ts +25 -25
  47. package/dist/types/time.d.ts.map +1 -1
  48. package/dist/types/time.js +25 -25
  49. package/dist/types/time.js.map +1 -1
  50. package/dist/types/uuid.d.ts +11 -11
  51. package/dist/types/uuid.d.ts.map +1 -1
  52. package/dist/types/uuid.js +12 -12
  53. package/dist/types/uuid.js.map +1 -1
  54. package/dist/utils/bytes.d.ts +17 -17
  55. package/dist/utils/bytes.js +4 -4
  56. package/dist/utils/bytes.js.map +1 -1
  57. package/dist/utils/date-format.d.ts +45 -45
  58. package/dist/utils/date-format.js +1 -1
  59. package/dist/utils/date-format.js.map +1 -1
  60. package/dist/utils/error.d.ts +4 -4
  61. package/dist/utils/json.d.ts +17 -17
  62. package/dist/utils/json.js +3 -3
  63. package/dist/utils/json.js.map +1 -1
  64. package/dist/utils/num.d.ts +23 -23
  65. package/dist/utils/obj.d.ts +111 -111
  66. package/dist/utils/obj.d.ts.map +1 -1
  67. package/dist/utils/obj.js +3 -3
  68. package/dist/utils/obj.js.map +1 -1
  69. package/dist/utils/path.d.ts +10 -10
  70. package/dist/utils/primitive.d.ts +5 -5
  71. package/dist/utils/primitive.js +1 -1
  72. package/dist/utils/primitive.js.map +1 -1
  73. package/dist/utils/str.d.ts +46 -46
  74. package/dist/utils/str.d.ts.map +1 -1
  75. package/dist/utils/str.js +5 -5
  76. package/dist/utils/str.js.map +1 -1
  77. package/dist/utils/template-strings.d.ts +26 -26
  78. package/dist/utils/transferable.d.ts +18 -18
  79. package/dist/utils/transferable.js +1 -1
  80. package/dist/utils/transferable.js.map +1 -1
  81. package/dist/utils/wait.d.ts +9 -9
  82. package/dist/utils/xml.d.ts +13 -13
  83. package/dist/utils/xml.d.ts.map +1 -1
  84. package/dist/utils/xml.js +1 -0
  85. package/dist/utils/xml.js.map +1 -1
  86. package/dist/zip/sd-zip.d.ts +22 -22
  87. package/dist/zip/sd-zip.js +16 -16
  88. package/package.json +4 -4
  89. package/src/common.types.ts +17 -17
  90. package/src/errors/argument-error.ts +15 -15
  91. package/src/errors/not-implemented-error.ts +9 -9
  92. package/src/errors/sd-error.ts +12 -12
  93. package/src/errors/timeout-error.ts +12 -12
  94. package/src/extensions/arr-ext.helpers.ts +10 -10
  95. package/src/extensions/arr-ext.ts +57 -57
  96. package/src/extensions/arr-ext.types.ts +59 -59
  97. package/src/extensions/map-ext.ts +16 -16
  98. package/src/extensions/set-ext.ts +11 -11
  99. package/src/features/debounce-queue.ts +21 -19
  100. package/src/features/event-emitter.ts +25 -25
  101. package/src/features/serial-queue.ts +13 -13
  102. package/src/globals.ts +4 -4
  103. package/src/index.ts +1 -1
  104. package/src/types/date-only.ts +83 -83
  105. package/src/types/date-time.ts +64 -44
  106. package/src/types/lazy-gc-map.ts +45 -45
  107. package/src/types/time.ts +34 -34
  108. package/src/types/uuid.ts +17 -17
  109. package/src/utils/bytes.ts +35 -35
  110. package/src/utils/date-format.ts +65 -65
  111. package/src/utils/error.ts +4 -4
  112. package/src/utils/json.ts +39 -39
  113. package/src/utils/num.ts +23 -23
  114. package/src/utils/obj.ts +138 -138
  115. package/src/utils/path.ts +10 -10
  116. package/src/utils/primitive.ts +6 -6
  117. package/src/utils/str.ts +260 -261
  118. package/src/utils/template-strings.ts +29 -29
  119. package/src/utils/transferable.ts +284 -284
  120. package/src/utils/wait.ts +10 -10
  121. package/src/utils/xml.ts +20 -19
  122. package/src/zip/sd-zip.ts +25 -25
  123. package/tests/errors/errors.spec.ts +80 -0
  124. package/tests/extensions/array-extension.spec.ts +796 -0
  125. package/tests/extensions/map-extension.spec.ts +147 -0
  126. package/tests/extensions/set-extension.spec.ts +74 -0
  127. package/tests/types/date-only.spec.ts +638 -0
  128. package/tests/types/date-time.spec.ts +391 -0
  129. package/tests/types/lazy-gc-map.spec.ts +692 -0
  130. package/tests/types/time.spec.ts +559 -0
  131. package/tests/types/uuid.spec.ts +74 -0
  132. package/tests/utils/bytes-utils.spec.ts +230 -0
  133. package/tests/utils/date-format.spec.ts +373 -0
  134. package/tests/utils/debounce-queue.spec.ts +272 -0
  135. package/tests/utils/json.spec.ts +486 -0
  136. package/tests/utils/number.spec.ts +157 -0
  137. package/tests/utils/object.spec.ts +829 -0
  138. package/tests/utils/path.spec.ts +78 -0
  139. package/tests/utils/primitive.spec.ts +43 -0
  140. package/tests/utils/sd-event-emitter.spec.ts +216 -0
  141. package/tests/utils/serial-queue.spec.ts +365 -0
  142. package/tests/utils/string.spec.ts +281 -0
  143. package/tests/utils/template-strings.spec.ts +57 -0
  144. package/tests/utils/transferable.spec.ts +703 -0
  145. package/tests/utils/wait.spec.ts +145 -0
  146. package/tests/utils/xml.spec.ts +146 -0
  147. package/tests/zip/sd-zip.spec.ts +238 -0
  148. package/docs/extensions.md +0 -503
  149. package/docs/features.md +0 -109
  150. package/docs/types.md +0 -486
  151. package/docs/utils.md +0 -780
@@ -0,0 +1,703 @@
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 - Special types
13
+
14
+ describe("encode() - Special types", () => {
15
+ it("Encodes 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("Encodes 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("Encodes 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("Encodes 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("Encodes 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("Recursively encodes 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("Encodes Error code property", () => {
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("Encodes Error detail property", () => {
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("Encodes special types in 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("Encodes Uint8Array and adds to 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("Encodes 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("Encodes 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 - Collections
187
+
188
+ describe("encode() - Collections", () => {
189
+ it("Recursively encodes array", () => {
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("Recursively encodes 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("Recursively encodes 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("Recursively encodes nested object", () => {
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 - Circular reference detection
255
+
256
+ describe("encode() - Circular reference detection", () => {
257
+ it("Throws TypeError when encoding self-referencing object", () => {
258
+ const obj: Record<string, unknown> = { a: 1 };
259
+ obj["self"] = obj;
260
+
261
+ expect(() => transferEncode(obj)).toThrow(TypeError);
262
+ expect(() => transferEncode(obj)).toThrow("Circular reference detected");
263
+ });
264
+
265
+ it("Detects nested circular references", () => {
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("Circular reference detected");
271
+ });
272
+
273
+ it("Detects circular references in array", () => {
274
+ const arr: unknown[] = [1, 2, 3];
275
+ arr.push(arr);
276
+
277
+ expect(() => transferEncode(arr)).toThrow("Circular reference detected");
278
+ });
279
+
280
+ it("Detects circular references in Map", () => {
281
+ const map = new Map<string, unknown>();
282
+ map.set("self", map);
283
+
284
+ expect(() => transferEncode(map)).toThrow("Circular reference detected");
285
+ });
286
+
287
+ it("Detects circular references in Set", () => {
288
+ const set = new Set<unknown>();
289
+ set.add(set);
290
+
291
+ expect(() => transferEncode(set)).toThrow("Circular reference detected");
292
+ });
293
+ });
294
+
295
+ //#endregion
296
+
297
+ //#region encode - DAG (Shared objects)
298
+
299
+ describe("encode() - DAG (Shared objects)", () => {
300
+ it("Encodes without error when same object is referenced from multiple places", () => {
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("Encodes without error when same array is referenced from multiple places", () => {
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("Still throws TypeError for circular references", () => {
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 - Special types
329
+
330
+ describe("decode() - Special types", () => {
331
+ it("Decodes 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("Decodes 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("Decodes 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("Decodes 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("Decodes 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("Recursively decodes 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("Decodes Error code property", () => {
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("Decodes Error detail property", () => {
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("Decodes special types in 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("Decodes 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("Decodes 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
+ // RegExp with g flag is stateful, so reset lastIndex before testing
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 - Collections
510
+
511
+ describe("decode() - Collections", () => {
512
+ it("Recursively decodes array", () => {
513
+ const tick = new DateTime(2025, 1, 6).tick;
514
+ const uuidStr = Uuid.new().toString();
515
+ const encoded = [
516
+ { __type__: "DateTime", data: tick },
517
+ { __type__: "Uuid", data: uuidStr },
518
+ "string",
519
+ 123,
520
+ ];
521
+ const decoded = transferDecode(encoded);
522
+
523
+ expect(Array.isArray(decoded)).toBe(true);
524
+ const arr = decoded as unknown[];
525
+ expect(arr[0] instanceof DateTime).toBe(true);
526
+ expect(arr[1] instanceof Uuid).toBe(true);
527
+ expect(arr[2]).toBe("string");
528
+ expect(arr[3]).toBe(123);
529
+ });
530
+
531
+ it("Recursively decodes Map", () => {
532
+ const tick = new DateTime(2025, 1, 6).tick;
533
+ const encoded = new Map<string, unknown>([
534
+ ["key1", { __type__: "DateTime", data: tick }],
535
+ ["key2", "value"],
536
+ ]);
537
+ const decoded = transferDecode(encoded);
538
+
539
+ expect(decoded instanceof Map).toBe(true);
540
+ const map = decoded as Map<string, unknown>;
541
+ expect(map.get("key1") instanceof DateTime).toBe(true);
542
+ expect(map.get("key2")).toBe("value");
543
+ });
544
+
545
+ it("Recursively decodes Set", () => {
546
+ const tick = new DateTime(2025, 1, 6).tick;
547
+ const encoded = new Set([{ __type__: "DateTime", data: tick }, "string"]);
548
+ const decoded = transferDecode(encoded);
549
+
550
+ expect(decoded instanceof Set).toBe(true);
551
+ const set = decoded as Set<unknown>;
552
+ const arr = Array.from(set);
553
+ expect(arr[0] instanceof DateTime).toBe(true);
554
+ expect(arr[1]).toBe("string");
555
+ });
556
+
557
+ it("Recursively decodes nested object", () => {
558
+ const dtTick = new DateTime(2025, 1, 6).tick;
559
+ const uuidStr = Uuid.new().toString();
560
+ const dTick = new DateOnly(2025, 1, 6).tick;
561
+ const encoded = {
562
+ dt: { __type__: "DateTime", data: dtTick },
563
+ nested: {
564
+ uuid: { __type__: "Uuid", data: uuidStr },
565
+ arr: [{ __type__: "DateOnly", data: dTick }],
566
+ },
567
+ };
568
+ const decoded = transferDecode(encoded);
569
+
570
+ const obj = decoded as {
571
+ dt: DateTime;
572
+ nested: {
573
+ uuid: Uuid;
574
+ arr: DateOnly[];
575
+ };
576
+ };
577
+
578
+ expect(obj.dt instanceof DateTime).toBe(true);
579
+ expect(obj.nested.uuid instanceof Uuid).toBe(true);
580
+ expect(obj.nested.arr[0] instanceof DateOnly).toBe(true);
581
+ });
582
+ });
583
+
584
+ //#endregion
585
+
586
+ //#region decode - Preserve original
587
+
588
+ describe("decode() - Preserve original", () => {
589
+ it("Original array is not modified", () => {
590
+ const tick = new DateTime(2025, 1, 6).tick;
591
+ const original = [{ __type__: "DateTime", data: tick }, "string", 123];
592
+ const originalCopy = JSON.stringify(original);
593
+
594
+ transferDecode(original);
595
+
596
+ // Verify original is not modified
597
+ expect(JSON.stringify(original)).toBe(originalCopy);
598
+ expect(original[0]).toEqual({ __type__: "DateTime", data: tick });
599
+ expect(original[1]).toBe("string");
600
+ expect(original[2]).toBe(123);
601
+ });
602
+
603
+ it("Original object is not modified", () => {
604
+ const tick = new DateTime(2025, 1, 6).tick;
605
+ const original = {
606
+ dt: { __type__: "DateTime", data: tick },
607
+ value: 123,
608
+ };
609
+ const originalCopy = JSON.stringify(original);
610
+
611
+ transferDecode(original);
612
+
613
+ // Verify original is not modified
614
+ expect(JSON.stringify(original)).toBe(originalCopy);
615
+ expect(original.dt).toEqual({ __type__: "DateTime", data: tick });
616
+ expect(original.value).toBe(123);
617
+ });
618
+
619
+ it("Original is preserved for nested arrays/objects", () => {
620
+ const tick = new DateTime(2025, 1, 6).tick;
621
+ const original = {
622
+ nested: {
623
+ arr: [{ __type__: "DateTime", data: tick }],
624
+ },
625
+ };
626
+ const originalCopy = JSON.stringify(original);
627
+
628
+ transferDecode(original);
629
+
630
+ expect(JSON.stringify(original)).toBe(originalCopy);
631
+ });
632
+
633
+ it("Decode result is a new instance (different from original)", () => {
634
+ const tick = new DateTime(2025, 1, 6).tick;
635
+ const original = [{ __type__: "DateTime", data: tick }];
636
+
637
+ const decoded = transferDecode(original);
638
+
639
+ // Result is a new array
640
+ expect(decoded).not.toBe(original);
641
+ // Array contents are converted
642
+ expect(Array.isArray(decoded)).toBe(true);
643
+ expect((decoded as unknown[])[0] instanceof DateTime).toBe(true);
644
+ });
645
+ });
646
+
647
+ //#endregion
648
+
649
+ //#region Round-trip conversion (encode → decode)
650
+
651
+ describe("Round-trip conversion (encode → decode)", () => {
652
+ it("Round-trips Date", () => {
653
+ const original = new Date(2025, 0, 6, 15, 30, 45, 123);
654
+ const { result } = transferEncode(original);
655
+ const decoded = transferDecode(result) as Date;
656
+
657
+ expect(decoded.getTime()).toBe(original.getTime());
658
+ });
659
+
660
+ it("Round-trips DateTime", () => {
661
+ const original = new DateTime(2025, 1, 6, 15, 30, 45, 123);
662
+ const { result } = transferEncode(original);
663
+ const decoded = transferDecode(result) as DateTime;
664
+
665
+ expect(decoded.tick).toBe(original.tick);
666
+ });
667
+
668
+ it("Round-trips complex object", () => {
669
+ const original = {
670
+ dt: new DateTime(2025, 1, 6),
671
+ d: new DateOnly(2025, 1, 6),
672
+ t: new Time(15, 30, 45),
673
+ uuid: Uuid.new(),
674
+ arr: [new DateTime(2024, 12, 31)],
675
+ map: new Map([["key", new DateOnly(2025, 1, 1)]]),
676
+ set: new Set([new Time(12, 0, 0)]),
677
+ };
678
+
679
+ const { result } = transferEncode(original);
680
+ const decoded = transferDecode(result) as typeof original;
681
+
682
+ expect(decoded.dt instanceof DateTime).toBe(true);
683
+ expect(decoded.d instanceof DateOnly).toBe(true);
684
+ expect(decoded.t instanceof Time).toBe(true);
685
+ expect(decoded.uuid instanceof Uuid).toBe(true);
686
+ expect(decoded.arr[0] instanceof DateTime).toBe(true);
687
+ expect(decoded.map.get("key") instanceof DateOnly).toBe(true);
688
+ expect(Array.from(decoded.set)[0] instanceof Time).toBe(true);
689
+ });
690
+
691
+ it("Round-trips RegExp", () => {
692
+ const original = /test\d+/gi;
693
+ const { result } = transferEncode(original);
694
+ const decoded = transferDecode(result) as RegExp;
695
+
696
+ expect(decoded instanceof RegExp).toBe(true);
697
+ expect(decoded.source).toBe(original.source);
698
+ expect(decoded.flags).toBe(original.flags);
699
+ });
700
+ });
701
+
702
+ //#endregion
703
+ });