@simplysm/core-common 13.0.75 → 13.0.77
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 +64 -21
- package/dist/extensions/arr-ext.d.ts +1 -1
- package/dist/extensions/arr-ext.d.ts.map +1 -1
- package/dist/extensions/arr-ext.helpers.d.ts +8 -0
- package/dist/extensions/arr-ext.helpers.d.ts.map +1 -1
- package/dist/extensions/arr-ext.helpers.js +65 -0
- package/dist/extensions/arr-ext.helpers.js.map +2 -2
- package/dist/extensions/arr-ext.js +16 -124
- package/dist/extensions/arr-ext.js.map +2 -2
- package/dist/extensions/arr-ext.types.d.ts +40 -32
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/map-ext.js.map +1 -1
- package/dist/extensions/set-ext.js.map +1 -1
- package/dist/features/event-emitter.d.ts +4 -4
- package/dist/features/event-emitter.d.ts.map +1 -1
- package/dist/features/event-emitter.js.map +1 -1
- package/dist/features/serial-queue.js +2 -2
- package/dist/features/serial-queue.js.map +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -13
- package/dist/index.js.map +1 -1
- package/dist/types/date-only.js +2 -2
- package/dist/types/date-only.js.map +1 -1
- package/dist/types/date-time.js +2 -2
- package/dist/types/date-time.js.map +1 -1
- package/dist/types/time.js +2 -2
- package/dist/types/time.js.map +1 -1
- package/dist/types/uuid.d.ts +2 -2
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +1 -1
- package/dist/types/uuid.js.map +1 -1
- package/dist/utils/bytes.d.ts +10 -10
- package/dist/utils/bytes.d.ts.map +1 -1
- package/dist/utils/bytes.js +10 -10
- package/dist/utils/bytes.js.map +1 -1
- package/dist/utils/date-format.d.ts +1 -1
- package/dist/utils/date-format.d.ts.map +1 -1
- package/dist/utils/date-format.js +2 -2
- package/dist/utils/date-format.js.map +1 -1
- package/dist/utils/error.d.ts +1 -1
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.js +2 -2
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/json.d.ts +4 -2
- package/dist/utils/json.d.ts.map +1 -1
- package/dist/utils/json.js +9 -9
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/num.d.ts +10 -10
- package/dist/utils/num.d.ts.map +1 -1
- package/dist/utils/num.js +11 -11
- package/dist/utils/num.js.map +1 -1
- package/dist/utils/obj.d.ts +40 -40
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +102 -99
- package/dist/utils/obj.js.map +1 -1
- package/dist/utils/path.d.ts +3 -3
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +6 -6
- package/dist/utils/path.js.map +1 -1
- package/dist/utils/primitive.d.ts +1 -1
- package/dist/utils/primitive.d.ts.map +1 -1
- package/dist/utils/primitive.js +2 -2
- package/dist/utils/primitive.js.map +1 -1
- package/dist/utils/str.d.ts +16 -16
- package/dist/utils/str.d.ts.map +1 -1
- package/dist/utils/str.js +16 -16
- package/dist/utils/str.js.map +1 -1
- package/dist/utils/transferable.d.ts +3 -3
- package/dist/utils/transferable.d.ts.map +1 -1
- package/dist/utils/transferable.js +10 -10
- package/dist/utils/transferable.js.map +1 -1
- package/dist/utils/wait.d.ts +2 -2
- package/dist/utils/wait.d.ts.map +1 -1
- package/dist/utils/wait.js +5 -5
- package/dist/utils/wait.js.map +1 -1
- package/dist/utils/xml.d.ts +2 -2
- package/dist/utils/xml.d.ts.map +1 -1
- package/dist/utils/xml.js +4 -4
- package/dist/utils/xml.js.map +1 -1
- package/dist/{zip/sd-zip.d.ts → utils/zip.d.ts} +1 -1
- package/dist/utils/zip.d.ts.map +1 -0
- package/dist/{zip/sd-zip.js → utils/zip.js} +1 -1
- package/dist/{zip/sd-zip.js.map → utils/zip.js.map} +1 -1
- package/package.json +3 -3
- package/src/extensions/arr-ext.helpers.ts +86 -0
- package/src/extensions/arr-ext.ts +22 -170
- package/src/extensions/arr-ext.types.ts +76 -48
- package/src/extensions/map-ext.ts +3 -3
- package/src/extensions/set-ext.ts +2 -2
- package/src/features/event-emitter.ts +6 -6
- package/src/features/serial-queue.ts +2 -2
- package/src/index.ts +16 -16
- package/src/types/date-only.ts +2 -2
- package/src/types/date-time.ts +2 -2
- package/src/types/time.ts +2 -2
- package/src/types/uuid.ts +2 -2
- package/src/utils/bytes.ts +15 -15
- package/src/utils/date-format.ts +1 -1
- package/src/utils/error.ts +1 -1
- package/src/utils/json.ts +9 -7
- package/src/utils/num.ts +15 -15
- package/src/utils/obj.ts +119 -116
- package/src/utils/path.ts +3 -3
- package/src/utils/primitive.ts +1 -1
- package/src/utils/str.ts +16 -16
- package/src/utils/transferable.ts +9 -9
- package/src/utils/wait.ts +3 -3
- package/src/utils/xml.ts +2 -2
- package/tests/extensions/array-extension.spec.ts +7 -149
- package/tests/extensions/map-extension.spec.ts +0 -30
- package/tests/extensions/set-extension.spec.ts +0 -7
- package/tests/types/date-only.spec.ts +0 -105
- package/tests/types/date-time.spec.ts +0 -145
- package/tests/types/lazy-gc-map.spec.ts +0 -86
- package/tests/types/time.spec.ts +0 -131
- package/tests/types/uuid.spec.ts +4 -4
- package/tests/utils/bytes-utils.spec.ts +42 -75
- package/tests/utils/date-format.spec.ts +89 -112
- package/tests/utils/debounce-queue.spec.ts +3 -49
- package/tests/utils/json.spec.ts +61 -147
- package/tests/utils/number.spec.ts +41 -62
- package/tests/utils/object.spec.ts +120 -139
- package/tests/utils/path.spec.ts +19 -27
- package/tests/utils/primitive.spec.ts +12 -12
- package/tests/utils/sd-event-emitter.spec.ts +0 -27
- package/tests/utils/serial-queue.spec.ts +0 -60
- package/tests/utils/string.spec.ts +66 -82
- package/tests/utils/template-strings.spec.ts +0 -9
- package/tests/utils/transferable.spec.ts +55 -119
- package/tests/utils/wait.spec.ts +10 -32
- package/tests/utils/xml.spec.ts +25 -25
- package/tests/{zip/sd-zip.spec.ts → utils/zip.spec.ts} +0 -17
- package/dist/zip/sd-zip.d.ts.map +0 -1
- /package/src/{zip/sd-zip.ts → utils/zip.ts} +0 -0
package/src/utils/path.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Combine paths (path.join replacement)
|
|
12
12
|
* @note Supports POSIX style paths only (slash `/`)
|
|
13
13
|
*/
|
|
14
|
-
export function
|
|
14
|
+
export function join(...segments: string[]): string {
|
|
15
15
|
return segments
|
|
16
16
|
.map((s, i) => (i === 0 ? s.replace(/\/+$/, "") : s.replace(/^\/+|\/+$/g, "")))
|
|
17
17
|
.filter(Boolean)
|
|
@@ -21,7 +21,7 @@ export function pathJoin(...segments: string[]): string {
|
|
|
21
21
|
/**
|
|
22
22
|
* Extract filename (path.basename replacement)
|
|
23
23
|
*/
|
|
24
|
-
export function
|
|
24
|
+
export function basename(filePath: string, ext?: string): string {
|
|
25
25
|
const name = filePath.split("/").pop() ?? "";
|
|
26
26
|
if (ext != null && ext !== "" && name.endsWith(ext)) {
|
|
27
27
|
return name.slice(0, -ext.length);
|
|
@@ -33,7 +33,7 @@ export function pathBasename(filePath: string, ext?: string): string {
|
|
|
33
33
|
* Extract file extension (path.extname replacement)
|
|
34
34
|
* @note Hidden files (e.g., `.gitignore`) return empty string (same as Node.js path.extname)
|
|
35
35
|
*/
|
|
36
|
-
export function
|
|
36
|
+
export function extname(filePath: string): string {
|
|
37
37
|
const name = filePath.split("/").pop() ?? "";
|
|
38
38
|
const dotIndex = name.lastIndexOf(".");
|
|
39
39
|
return dotIndex > 0 ? name.slice(dotIndex) : "";
|
package/src/utils/primitive.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type { PrimitiveTypeMap, PrimitiveTypeStr } from "../common.types";
|
|
|
20
20
|
* getPrimitiveTypeStr(new DateTime()) // "DateTime"
|
|
21
21
|
* getPrimitiveTypeStr(new Uint8Array()) // "Bytes"
|
|
22
22
|
*/
|
|
23
|
-
export function
|
|
23
|
+
export function typeStr(value: PrimitiveTypeMap[PrimitiveTypeStr]): PrimitiveTypeStr {
|
|
24
24
|
if (typeof value === "string") return "string";
|
|
25
25
|
if (typeof value === "number") return "number";
|
|
26
26
|
if (typeof value === "boolean") return "boolean";
|
package/src/utils/str.ts
CHANGED
|
@@ -28,10 +28,10 @@ const suffixTable = {
|
|
|
28
28
|
* - `"라"`: 이라/라 (ira/ra - copula particle)
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* getKoreanSuffix("Apple", "을") // "를"
|
|
32
|
+
* getKoreanSuffix("책", "이") // "이"
|
|
33
33
|
*/
|
|
34
|
-
export function
|
|
34
|
+
export function getKoreanSuffix(
|
|
35
35
|
text: string,
|
|
36
36
|
type: "을" | "은" | "이" | "와" | "랑" | "로" | "라",
|
|
37
37
|
): string {
|
|
@@ -147,10 +147,10 @@ const fullWidthCharRegex = new RegExp(`[${Object.keys(fullWidthCharMap).join("")
|
|
|
147
147
|
* - Full-width parentheses (() → ())
|
|
148
148
|
*
|
|
149
149
|
* @example
|
|
150
|
-
*
|
|
151
|
-
*
|
|
150
|
+
* replaceFullWidth("A123") // "A123"
|
|
151
|
+
* replaceFullWidth("(株)") // "(株)"
|
|
152
152
|
*/
|
|
153
|
-
export function
|
|
153
|
+
export function replaceFullWidth(str: string): string {
|
|
154
154
|
return str.replace(fullWidthCharRegex, (char) => fullWidthCharMap[char] ?? char);
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -164,7 +164,7 @@ export function strReplaceFullWidth(str: string): string {
|
|
|
164
164
|
* @example "hello_world" → "HelloWorld"
|
|
165
165
|
* @example "hello.world" → "HelloWorld"
|
|
166
166
|
*/
|
|
167
|
-
export function
|
|
167
|
+
export function toPascalCase(str: string): string {
|
|
168
168
|
return str
|
|
169
169
|
.replace(/[-._][a-z]/g, (m) => m[1].toUpperCase())
|
|
170
170
|
.replace(/^[a-z]/, (m) => m.toUpperCase());
|
|
@@ -176,7 +176,7 @@ export function strToPascalCase(str: string): string {
|
|
|
176
176
|
* @example "hello_world" → "helloWorld"
|
|
177
177
|
* @example "HelloWorld" → "helloWorld"
|
|
178
178
|
*/
|
|
179
|
-
export function
|
|
179
|
+
export function toCamelCase(str: string): string {
|
|
180
180
|
return str
|
|
181
181
|
.replace(/[-._][a-z]/g, (m) => m[1].toUpperCase())
|
|
182
182
|
.replace(/^[A-Z]/, (m) => m.toLowerCase());
|
|
@@ -192,7 +192,7 @@ export function strToCamelCase(str: string): string {
|
|
|
192
192
|
* @example "Hello-World" → "hello--world" (existing separators are preserved)
|
|
193
193
|
* @example "XMLParser" → "x-m-l-parser" (consecutive uppercase letters are separated)
|
|
194
194
|
*/
|
|
195
|
-
export function
|
|
195
|
+
export function toKebabCase(str: string): string {
|
|
196
196
|
return toCaseWithSeparator(str, "-");
|
|
197
197
|
}
|
|
198
198
|
|
|
@@ -206,7 +206,7 @@ export function strToKebabCase(str: string): string {
|
|
|
206
206
|
* @example "Hello_World" → "hello__world" (existing separators are preserved)
|
|
207
207
|
* @example "XMLParser" → "x_m_l_parser" (consecutive uppercase letters are separated)
|
|
208
208
|
*/
|
|
209
|
-
export function
|
|
209
|
+
export function toSnakeCase(str: string): string {
|
|
210
210
|
return toCaseWithSeparator(str, "_");
|
|
211
211
|
}
|
|
212
212
|
|
|
@@ -228,7 +228,7 @@ function toCaseWithSeparator(str: string, separator: string): string {
|
|
|
228
228
|
*
|
|
229
229
|
* @example
|
|
230
230
|
* const name: string | undefined = getValue();
|
|
231
|
-
* if (
|
|
231
|
+
* if (isNullOrEmpty(name)) {
|
|
232
232
|
* // name: "" | undefined
|
|
233
233
|
* console.log("Name is empty");
|
|
234
234
|
* } else {
|
|
@@ -236,7 +236,7 @@ function toCaseWithSeparator(str: string, separator: string): string {
|
|
|
236
236
|
* console.log(`Name: ${name}`);
|
|
237
237
|
* }
|
|
238
238
|
*/
|
|
239
|
-
export function
|
|
239
|
+
export function isNullOrEmpty(str: string | undefined): str is "" | undefined {
|
|
240
240
|
return str == null || str === "";
|
|
241
241
|
}
|
|
242
242
|
|
|
@@ -249,11 +249,11 @@ export function strIsNullOrEmpty(str: string | undefined): str is "" | undefined
|
|
|
249
249
|
* @returns A new string with the insertion applied
|
|
250
250
|
*
|
|
251
251
|
* @example
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
252
|
+
* insert("Hello World", 5, ","); // "Hello, World"
|
|
253
|
+
* insert("abc", 0, "X"); // "Xabc"
|
|
254
|
+
* insert("abc", 3, "X"); // "abcX"
|
|
255
255
|
*/
|
|
256
|
-
export function
|
|
256
|
+
export function insert(str: string, index: number, insertString: string): string {
|
|
257
257
|
return str.substring(0, index) + insertString + str.substring(index);
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -32,7 +32,7 @@ type Transferable = ArrayBuffer;
|
|
|
32
32
|
* worker.postMessage(result, transferList);
|
|
33
33
|
*
|
|
34
34
|
* // Receive data from Worker
|
|
35
|
-
* const decoded =
|
|
35
|
+
* const decoded = decode(event.data);
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
//#region encode
|
|
@@ -43,7 +43,7 @@ type Transferable = ArrayBuffer;
|
|
|
43
43
|
*
|
|
44
44
|
* @throws TypeError if circular reference is detected
|
|
45
45
|
*/
|
|
46
|
-
export function
|
|
46
|
+
export function encode(obj: unknown): {
|
|
47
47
|
result: unknown;
|
|
48
48
|
transferList: Transferable[];
|
|
49
49
|
} {
|
|
@@ -204,7 +204,7 @@ function encodeImpl(
|
|
|
204
204
|
* Convert serialized objects to objects using Simplysm types
|
|
205
205
|
* Deserialize data received from a Worker
|
|
206
206
|
*/
|
|
207
|
-
export function
|
|
207
|
+
export function decode(obj: unknown): unknown {
|
|
208
208
|
if (obj == null) return obj;
|
|
209
209
|
|
|
210
210
|
// 1. Restore special types from tagged objects
|
|
@@ -239,22 +239,22 @@ export function transferableDecode(obj: unknown): unknown {
|
|
|
239
239
|
err.stack = errorData.stack;
|
|
240
240
|
|
|
241
241
|
if (errorData.code !== undefined) err.code = errorData.code;
|
|
242
|
-
if (errorData.cause !== undefined) (err as Error).cause =
|
|
243
|
-
if (errorData.detail !== undefined) err.detail =
|
|
242
|
+
if (errorData.cause !== undefined) (err as Error).cause = decode(errorData.cause);
|
|
243
|
+
if (errorData.detail !== undefined) err.detail = decode(errorData.detail);
|
|
244
244
|
return err;
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// 2. Array recursion
|
|
249
249
|
if (Array.isArray(obj)) {
|
|
250
|
-
return obj.map((item) =>
|
|
250
|
+
return obj.map((item) => decode(item));
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
// 3. Map recursion
|
|
254
254
|
if (obj instanceof Map) {
|
|
255
255
|
const newMap = new Map<unknown, unknown>();
|
|
256
256
|
for (const [k, v] of obj) {
|
|
257
|
-
newMap.set(
|
|
257
|
+
newMap.set(decode(k), decode(v));
|
|
258
258
|
}
|
|
259
259
|
return newMap;
|
|
260
260
|
}
|
|
@@ -263,7 +263,7 @@ export function transferableDecode(obj: unknown): unknown {
|
|
|
263
263
|
if (obj instanceof Set) {
|
|
264
264
|
const newSet = new Set<unknown>();
|
|
265
265
|
for (const v of obj) {
|
|
266
|
-
newSet.add(
|
|
266
|
+
newSet.add(decode(v));
|
|
267
267
|
}
|
|
268
268
|
return newSet;
|
|
269
269
|
}
|
|
@@ -273,7 +273,7 @@ export function transferableDecode(obj: unknown): unknown {
|
|
|
273
273
|
const record = obj as Record<string, unknown>;
|
|
274
274
|
const result: Record<string, unknown> = {};
|
|
275
275
|
for (const key of Object.keys(record)) {
|
|
276
|
-
result[key] =
|
|
276
|
+
result[key] = decode(record[key]);
|
|
277
277
|
}
|
|
278
278
|
return result;
|
|
279
279
|
}
|
package/src/utils/wait.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { TimeoutError } from "../errors/timeout-error";
|
|
|
15
15
|
* await waitUntil(() => someCondition, 100, 3);
|
|
16
16
|
* @throws TimeoutError when maximum number of attempts is exceeded
|
|
17
17
|
*/
|
|
18
|
-
export async function
|
|
18
|
+
export async function until(
|
|
19
19
|
forwarder: () => boolean | Promise<boolean>,
|
|
20
20
|
milliseconds?: number,
|
|
21
21
|
maxCount?: number,
|
|
@@ -27,7 +27,7 @@ export async function waitUntil(
|
|
|
27
27
|
throw new TimeoutError(count);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
await
|
|
30
|
+
await time(milliseconds ?? 100);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -35,6 +35,6 @@ export async function waitUntil(
|
|
|
35
35
|
* Wait for a specified amount of time
|
|
36
36
|
* @param millisecond Wait time (ms)
|
|
37
37
|
*/
|
|
38
|
-
export async function
|
|
38
|
+
export async function time(millisecond: number): Promise<void> {
|
|
39
39
|
return new Promise<void>((resolve) => setTimeout(resolve, millisecond));
|
|
40
40
|
}
|
package/src/utils/xml.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
|
19
19
|
* xmlParse('<root id="1"><item>hello</item></root>');
|
|
20
20
|
* // { root: { $: { id: "1" }, item: [{ _: "hello" }] } }
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
22
|
+
export function parse(str: string, options?: { stripTagPrefix?: boolean }): unknown {
|
|
23
23
|
const result = new XMLParser({
|
|
24
24
|
ignoreAttributes: false,
|
|
25
25
|
attributeNamePrefix: "",
|
|
@@ -53,7 +53,7 @@ export function xmlParse(str: string, options?: { stripTagPrefix?: boolean }): u
|
|
|
53
53
|
* });
|
|
54
54
|
* // '<root id="1"><item>hello</item><item>world</item></root>'
|
|
55
55
|
*/
|
|
56
|
-
export function
|
|
56
|
+
export function stringify(obj: unknown, options?: XmlBuilderOptions): string {
|
|
57
57
|
return new XMLBuilder({
|
|
58
58
|
ignoreAttributes: false,
|
|
59
59
|
attributeNamePrefix: "",
|
|
@@ -2,65 +2,6 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import "@simplysm/core-common"; // Enable $ extension
|
|
3
3
|
|
|
4
4
|
describe("Array prototype extensions", () => {
|
|
5
|
-
//#region Basic chaining
|
|
6
|
-
|
|
7
|
-
describe("Basic chaining", () => {
|
|
8
|
-
it("Can chain existing array methods", () => {
|
|
9
|
-
const result = [1, 2, 3, 4, 5].filter((x) => x > 2).map((x) => x * 10);
|
|
10
|
-
|
|
11
|
-
expect(result).toEqual([30, 40, 50]);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("Can chain extension methods", () => {
|
|
15
|
-
const result = [
|
|
16
|
-
{ id: 1, name: "a" },
|
|
17
|
-
{ id: 2, name: "b" },
|
|
18
|
-
].toMap((x) => x.id);
|
|
19
|
-
|
|
20
|
-
expect(result.get(1)).toEqual({ id: 1, name: "a" });
|
|
21
|
-
expect(result.get(2)).toEqual({ id: 2, name: "b" });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("Can mix array and extension methods chaining", () => {
|
|
25
|
-
const users = [
|
|
26
|
-
{ id: 1, name: "Kim", active: true },
|
|
27
|
-
{ id: 2, name: "Lee", active: false },
|
|
28
|
-
{ id: 3, name: "Park", active: true },
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
const result = users.filter((u) => u.active).toMap((u) => u.id);
|
|
32
|
-
|
|
33
|
-
expect(result.size).toBe(2);
|
|
34
|
-
expect(result.has(1)).toBe(true);
|
|
35
|
-
expect(result.has(3)).toBe(true);
|
|
36
|
-
expect(result.has(2)).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("Can chain multiple steps", () => {
|
|
40
|
-
const result = [1, 2, 3, 4, 5]
|
|
41
|
-
.filter((x) => x > 1)
|
|
42
|
-
.map((x) => x * 2)
|
|
43
|
-
.filter((x) => x < 10)
|
|
44
|
-
.toMap((x) => x);
|
|
45
|
-
|
|
46
|
-
expect(result.size).toBe(3);
|
|
47
|
-
expect(result.has(4)).toBe(true);
|
|
48
|
-
expect(result.has(6)).toBe(true);
|
|
49
|
-
expect(result.has(8)).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("Can access array properties", () => {
|
|
53
|
-
const arr = [1, 2, 3];
|
|
54
|
-
|
|
55
|
-
expect(arr.length).toBe(3);
|
|
56
|
-
expect(arr[0]).toBe(1);
|
|
57
|
-
expect(arr[1]).toBe(2);
|
|
58
|
-
expect(arr[2]).toBe(3);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
//#endregion
|
|
63
|
-
|
|
64
5
|
//#region single
|
|
65
6
|
|
|
66
7
|
describe("single()", () => {
|
|
@@ -85,12 +26,6 @@ describe("Array prototype extensions", () => {
|
|
|
85
26
|
expect(([] as number[]).single()).toBe(undefined);
|
|
86
27
|
expect(() => [1, 2].single()).toThrow();
|
|
87
28
|
});
|
|
88
|
-
|
|
89
|
-
it("Can use single after chaining", () => {
|
|
90
|
-
const result = [1, 2, 3, 4, 5].filter((x) => x > 3).single((x) => x === 4);
|
|
91
|
-
|
|
92
|
-
expect(result).toBe(4);
|
|
93
|
-
});
|
|
94
29
|
});
|
|
95
30
|
|
|
96
31
|
//#endregion
|
|
@@ -111,14 +46,6 @@ describe("Array prototype extensions", () => {
|
|
|
111
46
|
|
|
112
47
|
expect(result).toEqual([2, 4, 6]);
|
|
113
48
|
});
|
|
114
|
-
|
|
115
|
-
it("Can use mapAsync after chaining", async () => {
|
|
116
|
-
const result = await [1, 2, 3, 4, 5]
|
|
117
|
-
.filter((x) => x > 2)
|
|
118
|
-
.mapAsync(async (x) => Promise.resolve(x * 10));
|
|
119
|
-
|
|
120
|
-
expect(result).toEqual([30, 40, 50]);
|
|
121
|
-
});
|
|
122
49
|
});
|
|
123
50
|
|
|
124
51
|
describe("filterAsync()", () => {
|
|
@@ -269,6 +196,7 @@ describe("Array prototype extensions", () => {
|
|
|
269
196
|
describe("diffs()", () => {
|
|
270
197
|
it("Analyzes differences between arrays", () => {
|
|
271
198
|
interface Item {
|
|
199
|
+
[key: string]: unknown;
|
|
272
200
|
id: number;
|
|
273
201
|
value: string;
|
|
274
202
|
}
|
|
@@ -287,13 +215,13 @@ describe("Array prototype extensions", () => {
|
|
|
287
215
|
|
|
288
216
|
const result = source.diffs(target, { keys: ["id"] });
|
|
289
217
|
|
|
290
|
-
const deleted = result.find((d) => d.source?.id === 1);
|
|
218
|
+
const deleted = result.find((d) => d.source?.["id"] === 1);
|
|
291
219
|
expect(deleted?.target).toBe(undefined);
|
|
292
220
|
|
|
293
|
-
const updated = result.find((d) => d.source?.id === 3);
|
|
294
|
-
expect(updated?.target?.value).toBe("changed");
|
|
221
|
+
const updated = result.find((d) => d.source?.["id"] === 3);
|
|
222
|
+
expect(updated?.target?.["value"]).toBe("changed");
|
|
295
223
|
|
|
296
|
-
const inserted = result.find((d) => d.target?.id === 4);
|
|
224
|
+
const inserted = result.find((d) => d.target?.["id"] === 4);
|
|
297
225
|
expect(inserted?.source).toBe(undefined);
|
|
298
226
|
});
|
|
299
227
|
});
|
|
@@ -353,6 +281,7 @@ describe("Array prototype extensions", () => {
|
|
|
353
281
|
describe("merge()", () => {
|
|
354
282
|
it("Merges modified items", () => {
|
|
355
283
|
interface Item {
|
|
284
|
+
[key: string]: unknown;
|
|
356
285
|
id: number;
|
|
357
286
|
value: string;
|
|
358
287
|
}
|
|
@@ -369,62 +298,7 @@ describe("Array prototype extensions", () => {
|
|
|
369
298
|
const result = source.merge(target, { keys: ["id"] });
|
|
370
299
|
|
|
371
300
|
expect(result).toHaveLength(2);
|
|
372
|
-
expect(result.find((r) => r
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
//#endregion
|
|
377
|
-
|
|
378
|
-
//#region ReadonlyArray support
|
|
379
|
-
|
|
380
|
-
describe("ReadonlyArray support", () => {
|
|
381
|
-
it("$ can be used with readonly array", () => {
|
|
382
|
-
const arr: readonly number[] = [1, 2, 3];
|
|
383
|
-
const result = arr.filter((x) => x > 1).toMap((x) => x);
|
|
384
|
-
|
|
385
|
-
expect(result.size).toBe(2);
|
|
386
|
-
expect(result.has(2)).toBe(true);
|
|
387
|
-
expect(result.has(3)).toBe(true);
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
//#endregion
|
|
392
|
-
|
|
393
|
-
//#region Various array method chaining
|
|
394
|
-
|
|
395
|
-
describe("Various array method chaining", () => {
|
|
396
|
-
it("flatMap can be chained", () => {
|
|
397
|
-
const result = [
|
|
398
|
-
[1, 2],
|
|
399
|
-
[3, 4],
|
|
400
|
-
]
|
|
401
|
-
.flatMap((x) => x)
|
|
402
|
-
.toMap((x) => x);
|
|
403
|
-
|
|
404
|
-
expect(result.size).toBe(4);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it("slice can be chained", () => {
|
|
408
|
-
const result = [1, 2, 3, 4, 5].slice(1, 4).toMap((x) => x);
|
|
409
|
-
|
|
410
|
-
expect(result.size).toBe(3);
|
|
411
|
-
expect(result.has(2)).toBe(true);
|
|
412
|
-
expect(result.has(3)).toBe(true);
|
|
413
|
-
expect(result.has(4)).toBe(true);
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
it("concat can be chained", () => {
|
|
417
|
-
const result = [1, 2].concat([3, 4]).toMap((x) => x);
|
|
418
|
-
|
|
419
|
-
expect(result.size).toBe(4);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it("sort can be chained", () => {
|
|
423
|
-
const result = [3, 1, 2].sort((a, b) => a - b).toMap((x, i) => i);
|
|
424
|
-
|
|
425
|
-
expect(result.get(0)).toBe(1);
|
|
426
|
-
expect(result.get(1)).toBe(2);
|
|
427
|
-
expect(result.get(2)).toBe(3);
|
|
301
|
+
expect(result.find((r) => r["id"] === 2)?.["value"]).toBe("changed");
|
|
428
302
|
});
|
|
429
303
|
});
|
|
430
304
|
|
|
@@ -470,12 +344,6 @@ describe("Array prototype extensions", () => {
|
|
|
470
344
|
const result = arr.filterExists();
|
|
471
345
|
expect(result).toEqual([1, 2, 3]);
|
|
472
346
|
});
|
|
473
|
-
|
|
474
|
-
it("Can be chained", () => {
|
|
475
|
-
const arr = [1, null, 2, undefined, 3];
|
|
476
|
-
const result = arr.filterExists().map((x) => x * 2);
|
|
477
|
-
expect(result).toEqual([2, 4, 6]);
|
|
478
|
-
});
|
|
479
347
|
});
|
|
480
348
|
|
|
481
349
|
describe("ofType()", () => {
|
|
@@ -582,11 +450,6 @@ describe("Array prototype extensions", () => {
|
|
|
582
450
|
expect(result).toHaveLength(2);
|
|
583
451
|
});
|
|
584
452
|
|
|
585
|
-
it("Can be chained", () => {
|
|
586
|
-
const result = [1, 2, 2, 3].distinct().map((x) => x * 2);
|
|
587
|
-
expect(result).toEqual([2, 4, 6]);
|
|
588
|
-
});
|
|
589
|
-
|
|
590
453
|
it("Can use custom key with keyFn", () => {
|
|
591
454
|
const arr = [
|
|
592
455
|
{ id: 1, name: "a" },
|
|
@@ -626,11 +489,6 @@ describe("Array prototype extensions", () => {
|
|
|
626
489
|
const result = items.orderBy((x) => x.age);
|
|
627
490
|
expect(result.map((x) => x.age)).toEqual([20, 25, 30]);
|
|
628
491
|
});
|
|
629
|
-
|
|
630
|
-
it("Can be chained", () => {
|
|
631
|
-
const result = [3, 1, 2].orderBy().map((x) => x * 2);
|
|
632
|
-
expect(result).toEqual([2, 4, 6]);
|
|
633
|
-
});
|
|
634
492
|
});
|
|
635
493
|
|
|
636
494
|
describe("orderByDesc()", () => {
|
|
@@ -44,16 +44,6 @@ describe("Map prototype extensions", () => {
|
|
|
44
44
|
expect(map.get("key")).toBe(50);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it("Returns existing value if key exists (direct value)", () => {
|
|
48
|
-
const map = new Map<string, number>();
|
|
49
|
-
map.set("key", 50);
|
|
50
|
-
|
|
51
|
-
const result = map.getOrCreate("key", 100);
|
|
52
|
-
|
|
53
|
-
expect(result).toBe(50);
|
|
54
|
-
expect(map.size).toBe(1);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
47
|
it("Can set empty array as default value", () => {
|
|
58
48
|
const map = new Map<string, number[]>();
|
|
59
49
|
|
|
@@ -121,26 +111,6 @@ describe("Map prototype extensions", () => {
|
|
|
121
111
|
|
|
122
112
|
expect(map.get("key")).toBe("hello world");
|
|
123
113
|
});
|
|
124
|
-
|
|
125
|
-
it("Can update object value", () => {
|
|
126
|
-
const map = new Map<string, { count: number }>();
|
|
127
|
-
map.set("key", { count: 5 });
|
|
128
|
-
|
|
129
|
-
map.update("key", (v) => ({ count: (v?.count ?? 0) + 1 }));
|
|
130
|
-
|
|
131
|
-
expect(map.get("key")).toEqual({ count: 6 });
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("Can update multiple times sequentially", () => {
|
|
135
|
-
const map = new Map<string, number>();
|
|
136
|
-
map.set("counter", 0);
|
|
137
|
-
|
|
138
|
-
map.update("counter", (v) => (v ?? 0) + 1);
|
|
139
|
-
map.update("counter", (v) => (v ?? 0) + 1);
|
|
140
|
-
map.update("counter", (v) => (v ?? 0) + 1);
|
|
141
|
-
|
|
142
|
-
expect(map.get("counter")).toBe(3);
|
|
143
|
-
});
|
|
144
114
|
});
|
|
145
115
|
|
|
146
116
|
//#endregion
|
|
@@ -22,13 +22,6 @@ describe("Set prototype extensions", () => {
|
|
|
22
22
|
|
|
23
23
|
expect(set.size).toBe(4); // 1, 2, 3, 4
|
|
24
24
|
});
|
|
25
|
-
|
|
26
|
-
it("Adds multiple items to empty Set", () => {
|
|
27
|
-
const set = new Set<number>();
|
|
28
|
-
set.adds(1, 2, 3);
|
|
29
|
-
|
|
30
|
-
expect(set.size).toBe(3);
|
|
31
|
-
});
|
|
32
25
|
});
|
|
33
26
|
|
|
34
27
|
//#endregion
|
|
@@ -109,22 +109,6 @@ describe("DateOnly", () => {
|
|
|
109
109
|
expect(() => DateOnly.parse("invalid-date")).toThrow("Failed to parse date format");
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
it("Parses year-end boundary (December 31)", () => {
|
|
113
|
-
const dateOnly = DateOnly.parse("2024-12-31");
|
|
114
|
-
|
|
115
|
-
expect(dateOnly.year).toBe(2024);
|
|
116
|
-
expect(dateOnly.month).toBe(12);
|
|
117
|
-
expect(dateOnly.day).toBe(31);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("Parses year-start boundary (January 1)", () => {
|
|
121
|
-
const dateOnly = DateOnly.parse("2025-01-01");
|
|
122
|
-
|
|
123
|
-
expect(dateOnly.year).toBe(2025);
|
|
124
|
-
expect(dateOnly.month).toBe(1);
|
|
125
|
-
expect(dateOnly.day).toBe(1);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
112
|
it("Parses February 29 in leap year", () => {
|
|
129
113
|
const dateOnly = DateOnly.parse("2024-02-29");
|
|
130
114
|
|
|
@@ -132,14 +116,6 @@ describe("DateOnly", () => {
|
|
|
132
116
|
expect(dateOnly.month).toBe(2);
|
|
133
117
|
expect(dateOnly.day).toBe(29);
|
|
134
118
|
});
|
|
135
|
-
|
|
136
|
-
it("Parses February 28 in leap year", () => {
|
|
137
|
-
const dateOnly = DateOnly.parse("2024-02-28");
|
|
138
|
-
|
|
139
|
-
expect(dateOnly.year).toBe(2024);
|
|
140
|
-
expect(dateOnly.month).toBe(2);
|
|
141
|
-
expect(dateOnly.day).toBe(28);
|
|
142
|
-
});
|
|
143
119
|
});
|
|
144
120
|
|
|
145
121
|
//#endregion
|
|
@@ -147,37 +123,6 @@ describe("DateOnly", () => {
|
|
|
147
123
|
//#region Getters
|
|
148
124
|
|
|
149
125
|
describe("Getters", () => {
|
|
150
|
-
it("Returns year", () => {
|
|
151
|
-
const dateOnly = new DateOnly(2025, 1, 6);
|
|
152
|
-
expect(dateOnly.year).toBe(2025);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("Returns month (1-12)", () => {
|
|
156
|
-
const dateOnly = new DateOnly(2025, 1, 6);
|
|
157
|
-
expect(dateOnly.month).toBe(1);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("Returns day", () => {
|
|
161
|
-
const dateOnly = new DateOnly(2025, 1, 6);
|
|
162
|
-
expect(dateOnly.day).toBe(6);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("Returns tick", () => {
|
|
166
|
-
const dateOnly = new DateOnly(2025, 1, 6);
|
|
167
|
-
expect(dateOnly.tick).toBe(new Date(2025, 0, 6).getTime());
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("Returns dayOfWeek (Sunday-Saturday: 0-6)", () => {
|
|
171
|
-
// 2025-01-06 is Monday (1)
|
|
172
|
-
const dateOnly = new DateOnly(2025, 1, 6);
|
|
173
|
-
expect(dateOnly.dayOfWeek).toBe(1);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("Returns isValid", () => {
|
|
177
|
-
const dateOnly = new DateOnly(2025, 1, 6);
|
|
178
|
-
expect(dateOnly.isValid).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
126
|
it("Invalid date returns isValid as false", () => {
|
|
182
127
|
const dateOnly = new DateOnly(NaN);
|
|
183
128
|
expect(dateOnly.isValid).toBe(false);
|
|
@@ -257,18 +202,6 @@ describe("DateOnly", () => {
|
|
|
257
202
|
});
|
|
258
203
|
});
|
|
259
204
|
|
|
260
|
-
describe("setDay()", () => {
|
|
261
|
-
it("Returns new instance with day changed", () => {
|
|
262
|
-
const dateOnly = new DateOnly(2025, 1, 6);
|
|
263
|
-
const newDateOnly = dateOnly.setDay(15);
|
|
264
|
-
|
|
265
|
-
expect(newDateOnly.year).toBe(2025);
|
|
266
|
-
expect(newDateOnly.month).toBe(1);
|
|
267
|
-
expect(newDateOnly.day).toBe(15);
|
|
268
|
-
expect(dateOnly.day).toBe(6); // original immutable
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
205
|
//#endregion
|
|
273
206
|
|
|
274
207
|
//#region addX methods (immutable)
|
|
@@ -379,44 +312,6 @@ describe("DateOnly", () => {
|
|
|
379
312
|
|
|
380
313
|
//#endregion
|
|
381
314
|
|
|
382
|
-
//#region tick comparison
|
|
383
|
-
|
|
384
|
-
describe("tick comparison", () => {
|
|
385
|
-
it("Same dates have same tick", () => {
|
|
386
|
-
const d1 = new DateOnly(2025, 3, 15);
|
|
387
|
-
const d2 = new DateOnly(2025, 3, 15);
|
|
388
|
-
|
|
389
|
-
expect(d1.tick).toBe(d2.tick);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it("Different dates have different ticks", () => {
|
|
393
|
-
const d1 = new DateOnly(2025, 3, 15);
|
|
394
|
-
const d2 = new DateOnly(2025, 3, 16);
|
|
395
|
-
|
|
396
|
-
expect(d1.tick).not.toBe(d2.tick);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
it("Can compare date order by tick", () => {
|
|
400
|
-
const d1 = new DateOnly(2025, 1, 1);
|
|
401
|
-
const d2 = new DateOnly(2025, 6, 15);
|
|
402
|
-
const d3 = new DateOnly(2025, 12, 31);
|
|
403
|
-
|
|
404
|
-
expect(d1.tick).toBeLessThan(d2.tick);
|
|
405
|
-
expect(d2.tick).toBeLessThan(d3.tick);
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
it("Can compare dates with different years by tick", () => {
|
|
409
|
-
const d2024 = new DateOnly(2024, 12, 31);
|
|
410
|
-
const d2025 = new DateOnly(2025, 1, 1);
|
|
411
|
-
|
|
412
|
-
expect(d2024.tick).toBeLessThan(d2025.tick);
|
|
413
|
-
});
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
//#endregion
|
|
417
|
-
|
|
418
|
-
//#endregion
|
|
419
|
-
|
|
420
315
|
//#region Week calculation
|
|
421
316
|
|
|
422
317
|
describe("getWeekSeqOfYear()", () => {
|