@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,145 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { waitTime as time, waitUntil as until, TimeoutError } from "@simplysm/core-common";
3
+
4
+ describe("Wait", () => {
5
+ //#region time
6
+
7
+ describe("time()", () => {
8
+ it("Waits for specified time", async () => {
9
+ const start = Date.now();
10
+ await time(100);
11
+ const elapsed = Date.now() - start;
12
+
13
+ // 100ms ± tolerance - CI environment load and timer precision considered
14
+ expect(elapsed).toBeGreaterThanOrEqual(95);
15
+ expect(elapsed).toBeLessThan(250);
16
+ });
17
+
18
+ it("Works normally with 0ms wait", async () => {
19
+ const start = Date.now();
20
+ await time(0);
21
+ const elapsed = Date.now() - start;
22
+
23
+ expect(elapsed).toBeLessThan(50);
24
+ });
25
+ });
26
+
27
+ //#endregion
28
+
29
+ //#region until
30
+
31
+ describe("until()", () => {
32
+ it("Waits until condition becomes true", async () => {
33
+ let count = 0;
34
+
35
+ await until(() => {
36
+ count++;
37
+ return count >= 3;
38
+ }, 10);
39
+
40
+ expect(count).toBe(3);
41
+ });
42
+
43
+ it("Supports async condition function", async () => {
44
+ let count = 0;
45
+
46
+ await until(async () => {
47
+ await time(10);
48
+ count++;
49
+ return count >= 3;
50
+ }, 10);
51
+
52
+ expect(count).toBe(3);
53
+ });
54
+
55
+ it("Returns immediately if condition already true", async () => {
56
+ const start = Date.now();
57
+ await until(() => true, 100);
58
+ const elapsed = Date.now() - start;
59
+
60
+ expect(elapsed).toBeLessThan(50);
61
+ });
62
+
63
+ it("Throws TimeoutError on max attempts exceeded", async () => {
64
+ let count = 0;
65
+
66
+ await expect(async () => {
67
+ await until(
68
+ () => {
69
+ count++;
70
+ return false;
71
+ },
72
+ 10,
73
+ 5,
74
+ );
75
+ }).rejects.toThrow(TimeoutError);
76
+
77
+ expect(count).toBe(5);
78
+ });
79
+
80
+ it("Waits indefinitely if maxCount undefined", async () => {
81
+ let count = 0;
82
+
83
+ // Unlimited wait but returns when condition true
84
+ await until(
85
+ () => {
86
+ count++;
87
+ return count >= 10;
88
+ },
89
+ 10,
90
+ undefined,
91
+ );
92
+
93
+ expect(count).toBe(10);
94
+ });
95
+
96
+ it("Default milliseconds is 100ms", async () => {
97
+ let count = 0;
98
+ const start = Date.now();
99
+
100
+ await until(() => {
101
+ count++;
102
+ return count >= 3;
103
+ });
104
+
105
+ const elapsed = Date.now() - start;
106
+ // 100ms * 2 waits = 200ms (first check immediate), timer tolerance considered
107
+ expect(elapsed).toBeGreaterThanOrEqual(190);
108
+ expect(elapsed).toBeLessThan(350);
109
+ });
110
+
111
+ it("With maxCount=1, tries once then errors", async () => {
112
+ let count = 0;
113
+
114
+ await expect(async () => {
115
+ await until(
116
+ () => {
117
+ count++;
118
+ return false;
119
+ },
120
+ 10,
121
+ 1,
122
+ );
123
+ }).rejects.toThrow(TimeoutError);
124
+
125
+ expect(count).toBe(1);
126
+ });
127
+
128
+ it("Success if condition true within maxCount", async () => {
129
+ let count = 0;
130
+
131
+ await until(
132
+ () => {
133
+ count++;
134
+ return count >= 3;
135
+ },
136
+ 10,
137
+ 5,
138
+ );
139
+
140
+ expect(count).toBe(3);
141
+ });
142
+ });
143
+
144
+ //#endregion
145
+ });
@@ -0,0 +1,146 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { xmlParse as parse, xmlStringify as stringify } from "@simplysm/core-common";
3
+
4
+ describe("XmlConvert", () => {
5
+ //#region parse
6
+
7
+ describe("parse()", () => {
8
+ it("Parses basic XML", () => {
9
+ const xml = "<root><child>value</child></root>";
10
+ const result = parse(xml) as Record<string, unknown>;
11
+
12
+ expect(result).toHaveProperty("root");
13
+ });
14
+
15
+ it("Parses XML with attributes", () => {
16
+ const xml = '<root id="1"><child name="test">value</child></root>';
17
+ const result = parse(xml) as {
18
+ root: {
19
+ $: { id: string };
20
+ child: Array<{ $: { name: string }; _: string }>;
21
+ };
22
+ };
23
+
24
+ expect(result.root.$.id).toBe("1");
25
+ expect(result.root.child[0].$.name).toBe("test");
26
+ });
27
+
28
+ it("Parses nested XML", () => {
29
+ const xml = "<root><parent><child>value</child></parent></root>";
30
+ const result = parse(xml) as {
31
+ root: { parent: Array<{ child: string[] }> };
32
+ };
33
+
34
+ expect(result.root.parent[0].child[0]).toBe("value");
35
+ });
36
+
37
+ it("Parses text nodes as _ key", () => {
38
+ const xml = '<item id="1">text content</item>';
39
+ const result = parse(xml) as {
40
+ item: { $: { id: string }; _: string };
41
+ };
42
+
43
+ expect(result.item._).toBe("text content");
44
+ });
45
+
46
+ it("Removes namespace prefix (stripTagPrefix: true)", () => {
47
+ const xml = "<ns:root><ns:child>value</ns:child></ns:root>";
48
+ const result = parse(xml, { stripTagPrefix: true }) as {
49
+ root: { child: string[] };
50
+ };
51
+
52
+ expect(result).toHaveProperty("root");
53
+ expect(result.root).toHaveProperty("child");
54
+ });
55
+
56
+ it("Preserves namespace prefix (default)", () => {
57
+ const xml = "<ns:root><ns:child>value</ns:child></ns:root>";
58
+ const result = parse(xml) as Record<string, unknown>;
59
+
60
+ expect(result).toHaveProperty("ns:root");
61
+ });
62
+
63
+ it("Parses multiple same tags as array", () => {
64
+ const xml = "<root><item>1</item><item>2</item><item>3</item></root>";
65
+ const result = parse(xml) as { root: { item: string[] } };
66
+
67
+ expect(result.root.item).toEqual(["1", "2", "3"]);
68
+ });
69
+
70
+ it("Does not remove namespace prefix from attributes", () => {
71
+ const xml = '<ns:root xmlns:ns="http://example.com"><ns:child>value</ns:child></ns:root>';
72
+ const result = parse(xml, { stripTagPrefix: true }) as {
73
+ root: { $: Record<string, string>; child: string[] };
74
+ };
75
+
76
+ expect(result.root.$).toHaveProperty("xmlns:ns");
77
+ });
78
+ });
79
+
80
+ //#endregion
81
+
82
+ //#region stringify
83
+
84
+ describe("stringify()", () => {
85
+ it("Serializes object to XML", () => {
86
+ const obj = { root: { child: "value" } };
87
+ const result = stringify(obj);
88
+
89
+ expect(result).toContain("<root>");
90
+ expect(result).toContain("<child>value</child>");
91
+ expect(result).toContain("</root>");
92
+ });
93
+
94
+ it("Serializes object with attributes", () => {
95
+ const obj = { root: { $: { id: "1" }, child: "value" } };
96
+ const result = stringify(obj);
97
+
98
+ expect(result).toContain('id="1"');
99
+ expect(result).toContain("<child>value</child>");
100
+ });
101
+
102
+ it("Serializes array as multiple tags", () => {
103
+ const obj = { root: { item: ["1", "2", "3"] } };
104
+ const result = stringify(obj);
105
+
106
+ expect(result).toContain("<item>1</item>");
107
+ expect(result).toContain("<item>2</item>");
108
+ expect(result).toContain("<item>3</item>");
109
+ });
110
+
111
+ it("Serializes text node as _ key", () => {
112
+ const obj = { item: { $: { id: "1" }, _: "text content" } };
113
+ const result = stringify(obj);
114
+
115
+ expect(result).toContain('id="1"');
116
+ expect(result).toContain("text content");
117
+ });
118
+
119
+ it("Serializes nested object", () => {
120
+ const obj = { root: { parent: { child: "value" } } };
121
+ const result = stringify(obj);
122
+
123
+ expect(result).toContain("<parent>");
124
+ expect(result).toContain("<child>value</child>");
125
+ expect(result).toContain("</parent>");
126
+ });
127
+ });
128
+
129
+ //#endregion
130
+
131
+ //#region roundtrip
132
+
133
+ describe("parse/stringify roundtrip", () => {
134
+ it("Structure preserved after parse then stringify", () => {
135
+ const xml = "<root><child>value</child></root>";
136
+ const parsed = parse(xml);
137
+ const result = stringify(parsed);
138
+
139
+ expect(result).toContain("<root>");
140
+ expect(result).toContain("<child>value</child>");
141
+ expect(result).toContain("</root>");
142
+ });
143
+ });
144
+
145
+ //#endregion
146
+ });
@@ -0,0 +1,238 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ZipArchive } from "@simplysm/core-common";
3
+
4
+ const encoder = new TextEncoder();
5
+ const decoder = new TextDecoder();
6
+
7
+ describe("ZipArchive", () => {
8
+ //#region write + compress
9
+
10
+ describe("write + compress", () => {
11
+ it("adds files with write and creates ZIP with compress", async () => {
12
+ const zip = new ZipArchive();
13
+ zip.write("file1.txt", encoder.encode("content 1"));
14
+ zip.write("file2.txt", encoder.encode("content 2"));
15
+
16
+ const zipBuffer = await zip.compress();
17
+
18
+ expect(zipBuffer instanceof Uint8Array).toBe(true);
19
+ expect(zipBuffer.length).toBeGreaterThan(0);
20
+ });
21
+
22
+ it("handles filenames with special characters", async () => {
23
+ const zip = new ZipArchive();
24
+ zip.write("파일 이름.txt", encoder.encode("한글 내용"));
25
+ zip.write("file (1).txt", encoder.encode("괄호 포함"));
26
+ zip.write("file@#$.txt", encoder.encode("특수문자"));
27
+
28
+ const zipBuffer = await zip.compress();
29
+ const result = new ZipArchive(zipBuffer);
30
+
31
+ const content1 = await result.get("파일 이름.txt");
32
+ const content2 = await result.get("file (1).txt");
33
+ const content3 = await result.get("file@#$.txt");
34
+
35
+ expect(content1 != null ? decoder.decode(content1) : undefined).toBe("한글 내용");
36
+ expect(content2 != null ? decoder.decode(content2) : undefined).toBe("괄호 포함");
37
+ expect(content3 != null ? decoder.decode(content3) : undefined).toBe("특수문자");
38
+ });
39
+
40
+ it("can compress empty ZIP", async () => {
41
+ const zip = new ZipArchive();
42
+ const zipBuffer = await zip.compress();
43
+
44
+ expect(zipBuffer instanceof Uint8Array).toBe(true);
45
+ });
46
+
47
+ it("overwrites when writing same filename multiple times", async () => {
48
+ const zip = new ZipArchive();
49
+ zip.write("file.txt", encoder.encode("first"));
50
+ zip.write("file.txt", encoder.encode("second"));
51
+
52
+ const zipBuffer = await zip.compress();
53
+ const result = new ZipArchive(zipBuffer);
54
+ const content = await result.get("file.txt");
55
+
56
+ expect(content != null ? decoder.decode(content) : undefined).toBe("second");
57
+ });
58
+ });
59
+
60
+ //#endregion
61
+
62
+ //#region extractAll
63
+
64
+ describe("extractAll", () => {
65
+ it("extracts all compressed files", async () => {
66
+ const zip = new ZipArchive();
67
+ zip.write("file1.txt", encoder.encode("content 1"));
68
+ zip.write("file2.txt", encoder.encode("content 2"));
69
+ const zipBuffer = await zip.compress();
70
+
71
+ const result = new ZipArchive(zipBuffer);
72
+ const files = await result.extractAll();
73
+
74
+ expect(files.size).toBe(2);
75
+ expect(
76
+ files.get("file1.txt") != null ? decoder.decode(files.get("file1.txt")) : undefined,
77
+ ).toBe("content 1");
78
+ expect(
79
+ files.get("file2.txt") != null ? decoder.decode(files.get("file2.txt")) : undefined,
80
+ ).toBe("content 2");
81
+ });
82
+
83
+ it("calls progress callback", async () => {
84
+ const zip = new ZipArchive();
85
+ zip.write("file1.txt", encoder.encode("a".repeat(1000)));
86
+ zip.write("file2.txt", encoder.encode("b".repeat(1000)));
87
+ const zipBuffer = await zip.compress();
88
+
89
+ const result = new ZipArchive(zipBuffer);
90
+ const progressCalls: Array<{ fileName: string; extractedSize: number }> = [];
91
+
92
+ await result.extractAll((progress) => {
93
+ progressCalls.push({
94
+ fileName: progress.fileName,
95
+ extractedSize: progress.extractedSize,
96
+ });
97
+ });
98
+
99
+ expect(progressCalls.length).toBeGreaterThan(0);
100
+ expect(progressCalls.some((p) => p.fileName === "file1.txt")).toBe(true);
101
+ expect(progressCalls.some((p) => p.fileName === "file2.txt")).toBe(true);
102
+ });
103
+
104
+ it("returns empty cache when reader is not available", async () => {
105
+ const zip = new ZipArchive();
106
+ const files = await zip.extractAll();
107
+
108
+ expect(files.size).toBe(0);
109
+ });
110
+ });
111
+
112
+ //#endregion
113
+
114
+ //#region get
115
+
116
+ describe("get", () => {
117
+ it("extracts specific file only", async () => {
118
+ const zip = new ZipArchive();
119
+ zip.write("file1.txt", encoder.encode("content 1"));
120
+ zip.write("file2.txt", encoder.encode("content 2"));
121
+ const zipBuffer = await zip.compress();
122
+
123
+ const result = new ZipArchive(zipBuffer);
124
+ const content = await result.get("file1.txt");
125
+
126
+ expect(content != null ? decoder.decode(content) : undefined).toBe("content 1");
127
+ });
128
+
129
+ it("returns undefined for non-existent file", async () => {
130
+ const zip = new ZipArchive();
131
+ zip.write("file.txt", encoder.encode("content"));
132
+ const zipBuffer = await zip.compress();
133
+
134
+ const result = new ZipArchive(zipBuffer);
135
+ const content = await result.get("nonexistent.txt");
136
+
137
+ expect(content).toBe(undefined);
138
+ });
139
+
140
+ it("does not re-extract cached file", async () => {
141
+ const zip = new ZipArchive();
142
+ zip.write("file.txt", encoder.encode("content"));
143
+ const zipBuffer = await zip.compress();
144
+
145
+ const result = new ZipArchive(zipBuffer);
146
+ const content1 = await result.get("file.txt");
147
+ const content2 = await result.get("file.txt");
148
+
149
+ expect(content1).toBe(content2); // Same reference
150
+ });
151
+
152
+ it("returns undefined when reader is not available", async () => {
153
+ const zip = new ZipArchive();
154
+ const content = await zip.get("file.txt");
155
+
156
+ expect(content).toBe(undefined);
157
+ });
158
+ });
159
+
160
+ //#endregion
161
+
162
+ //#region exists
163
+
164
+ describe("exists", () => {
165
+ it("checks file existence", async () => {
166
+ const zip = new ZipArchive();
167
+ zip.write("file.txt", encoder.encode("content"));
168
+ const zipBuffer = await zip.compress();
169
+
170
+ const result = new ZipArchive(zipBuffer);
171
+ const exists = await result.exists("file.txt");
172
+ const notExists = await result.exists("nonexistent.txt");
173
+
174
+ expect(exists).toBe(true);
175
+ expect(notExists).toBe(false);
176
+ });
177
+
178
+ it("returns true for cached file", async () => {
179
+ const zip = new ZipArchive();
180
+ zip.write("file.txt", encoder.encode("content"));
181
+ const zipBuffer = await zip.compress();
182
+
183
+ const result = new ZipArchive(zipBuffer);
184
+ await result.get("file.txt"); // Load into cache
185
+ const exists = await result.exists("file.txt");
186
+
187
+ expect(exists).toBe(true);
188
+ });
189
+
190
+ it("returns false when reader is not available", async () => {
191
+ const zip = new ZipArchive();
192
+ const exists = await zip.exists("file.txt");
193
+
194
+ expect(exists).toBe(false);
195
+ });
196
+ });
197
+
198
+ //#endregion
199
+
200
+ //#region close
201
+
202
+ describe("close", () => {
203
+ it("closes reader", async () => {
204
+ const zip = new ZipArchive();
205
+ zip.write("file.txt", encoder.encode("content"));
206
+ const zipBuffer = await zip.compress();
207
+
208
+ const result = new ZipArchive(zipBuffer);
209
+ // Call extractAll to load into cache
210
+ await result.extractAll();
211
+ await result.close();
212
+
213
+ // Cached data is still available after close
214
+ const content = await result.get("file.txt");
215
+ expect(content != null ? decoder.decode(content) : undefined).toBe("content");
216
+ });
217
+
218
+ it("works without error when reader is not available", async () => {
219
+ const zip = new ZipArchive();
220
+ await expect(zip.close()).resolves.not.toThrow();
221
+ });
222
+
223
+ it("automatically closes with await using statement", async () => {
224
+ const zip = new ZipArchive();
225
+ zip.write("file.txt", encoder.encode("content"));
226
+ const zipBuffer = await zip.compress();
227
+
228
+ {
229
+ await using result = new ZipArchive(zipBuffer);
230
+ await result.extractAll();
231
+ const content = await result.get("file.txt");
232
+ expect(decoder.decode(content)).toBe("content");
233
+ } // close called automatically when await using block ends
234
+ });
235
+ });
236
+
237
+ //#endregion
238
+ });