@synnaxlabs/x 0.46.1 → 0.46.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synnaxlabs/x",
3
- "version": "0.46.1",
3
+ "version": "0.46.2",
4
4
  "type": "module",
5
5
  "description": "Common Utilities for Synnax Labs",
6
6
  "repository": "https://github.com/synnaxlabs/synnax/tree/main/x/ts",
@@ -27,9 +27,9 @@
27
27
  "typescript": "^5.9.2",
28
28
  "vite": "^7.1.5",
29
29
  "vitest": "^3.2.4",
30
- "eslint-config-synnaxlabs": "^0.43.0",
31
30
  "@synnaxlabs/tsconfig": "^0.43.0",
32
- "@synnaxlabs/vite-plugin": "^0.43.0"
31
+ "@synnaxlabs/vite-plugin": "^0.43.0",
32
+ "eslint-config-synnaxlabs": "^0.43.0"
33
33
  },
34
34
  "main": "./dist/index.cjs",
35
35
  "module": "./dist/index.js",
@@ -0,0 +1,84 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import { deduplicateFileName } from "@/strings/deduplicateFileName";
13
+
14
+ describe("deduplicateFileName", () => {
15
+ it("should return the original name when it does not exist", () =>
16
+ expect(deduplicateFileName("Report", new Set(["Summary"]))).toBe("Report"));
17
+
18
+ it("should append (1) when a duplicate exists", () =>
19
+ expect(deduplicateFileName("Report", new Set(["Report"]))).toBe("Report (1)"));
20
+
21
+ it("should increment to the next available number", () =>
22
+ expect(
23
+ deduplicateFileName("Report", new Set(["Report", "Report (1)", "Report (2)"])),
24
+ ).toBe("Report (3)"));
25
+
26
+ it("should fill gaps in numbering", () =>
27
+ expect(
28
+ deduplicateFileName("Report", new Set(["Report", "Report (1)", "Report (3)"])),
29
+ ).toBe("Report (2)"));
30
+
31
+ it("should handle names already suffixed with a number", () =>
32
+ expect(deduplicateFileName("Report (2)", new Set(["Report (2)"]))).toBe(
33
+ "Report (3)",
34
+ ));
35
+
36
+ it("should not be confused by numbers elsewhere in the name", () =>
37
+ expect(
38
+ deduplicateFileName("Report 2024", new Set(["Report 2024", "Report 2024 (1)"])),
39
+ ).toBe("Report 2024 (2)"));
40
+
41
+ it("should handle names with non-numeric parentheses at the end", () =>
42
+ expect(deduplicateFileName("Report (draft)", new Set(["Report (draft)"]))).toBe(
43
+ "Report (draft) (1)",
44
+ ));
45
+
46
+ it("should escalate correctly when many duplicates exist", () => {
47
+ const existing = new Set<string>(["Report"]);
48
+ for (let i = 1; i <= 100; i++) existing.add(`Report (${i})`);
49
+ expect(deduplicateFileName("Report", existing)).toBe("Report (101)");
50
+ });
51
+
52
+ it("should return empty when empty name does not exist", () =>
53
+ expect(deduplicateFileName("", new Set(["Report"]))).toBe(""));
54
+
55
+ it("should append (1) to empty when empty name exists", () =>
56
+ expect(deduplicateFileName("", new Set([""]))).toBe(" (1)"));
57
+
58
+ // Edge cases
59
+ it("should keep unique names with trailing spaces unchanged", () =>
60
+ expect(deduplicateFileName("Report ", new Set(["Report"]))).toBe("Report "));
61
+
62
+ it("should normalize double spaces before numeric suffix when incrementing", () =>
63
+ expect(deduplicateFileName("Report (1)", new Set(["Report (1)"]))).toBe(
64
+ "Report (2)",
65
+ ));
66
+
67
+ it("should preserve multiple internal spaces when appending suffix", () =>
68
+ expect(deduplicateFileName("Annual Report", new Set(["Annual Report"]))).toBe(
69
+ "Annual Report (1)",
70
+ ));
71
+
72
+ it("should handle unicode names when appending suffix", () =>
73
+ expect(deduplicateFileName("café", new Set(["café"]))).toBe("café (1)"));
74
+
75
+ it("should not treat fullwidth parentheses/digits as numeric suffix", () =>
76
+ expect(deduplicateFileName("Report(1)", new Set(["Report(1)"]))).toBe(
77
+ "Report(1) (1)",
78
+ ));
79
+
80
+ it("should handle emoji in names when appending suffix", () =>
81
+ expect(deduplicateFileName("Report 📄", new Set(["Report 📄"]))).toBe(
82
+ "Report 📄 (1)",
83
+ ));
84
+ });
@@ -0,0 +1,32 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export const deduplicateFileName = (
11
+ name: string,
12
+ existingNames: Set<string>,
13
+ ): string => {
14
+ if (!existingNames.has(name)) return name;
15
+ let baseName = name;
16
+ let i = 1;
17
+ let currentName = name;
18
+ while (existingNames.has(currentName)) {
19
+ const match = currentName.match(filenameEndingRegex);
20
+ if (match) {
21
+ baseName = currentName.slice(0, match.index).trim();
22
+ i = parseInt(match[1]) + 1;
23
+ } else {
24
+ baseName = currentName;
25
+ i = 1;
26
+ }
27
+ currentName = `${baseName} (${i})`;
28
+ }
29
+ return currentName;
30
+ };
31
+
32
+ const filenameEndingRegex = /\((\d+)\)$/;
@@ -0,0 +1,11 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ export * from "@/strings/deduplicateFileName";
11
+ export * from "@/strings/strings";
@@ -7,4 +7,4 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
- export * as strings from "@/strings/strings";
10
+ export * as strings from "@/strings/external";
@@ -103,45 +103,3 @@ describe("trimPrefix", () => {
103
103
  it("should handle numbers in prefix", () =>
104
104
  expect(strings.trimPrefix("123abc", "123")).toBe("abc"));
105
105
  });
106
-
107
- describe("pluralName", () => {
108
- it("should handle empty string", () => expect(strings.pluralName("")).toBe(""));
109
-
110
- it("should add 's' to regular words", () => {
111
- expect(strings.pluralName("cat")).toBe("cats");
112
- expect(strings.pluralName("dog")).toBe("dogs");
113
- expect(strings.pluralName("regularType")).toBe("regularTypes");
114
- });
115
-
116
- it("should convert 'y' endings to 'ies'", () => {
117
- expect(strings.pluralName("company")).toBe("companies");
118
- expect(strings.pluralName("yEndingType")).toBe("yEndingTypes");
119
- expect(strings.pluralName("baby")).toBe("babies");
120
- });
121
-
122
- it("should add 'es' to words ending in 's'", () => {
123
- expect(strings.pluralName("class")).toBe("classes");
124
- expect(strings.pluralName("bus")).toBe("buses");
125
- });
126
-
127
- it("should add 'es' to words ending in 'x'", () => {
128
- expect(strings.pluralName("box")).toBe("boxes");
129
- expect(strings.pluralName("fox")).toBe("foxes");
130
- });
131
-
132
- it("should add 'es' to words ending in 'ch'", () => {
133
- expect(strings.pluralName("catch")).toBe("catches");
134
- expect(strings.pluralName("church")).toBe("churches");
135
- });
136
-
137
- it("should add 'es' to words ending in 'sh'", () => {
138
- expect(strings.pluralName("bush")).toBe("bushes");
139
- expect(strings.pluralName("brush")).toBe("brushes");
140
- });
141
-
142
- it("should work with built-in type names", () => {
143
- expect(strings.pluralName("string")).toBe("strings");
144
- expect(strings.pluralName("number")).toBe("numbers");
145
- expect(strings.pluralName("object")).toBe("objects");
146
- });
147
- });
@@ -108,17 +108,3 @@ export const trimPrefix = (str: string, prefix: string): string => {
108
108
  if (str.startsWith(prefix)) return str.slice(prefix.length);
109
109
  return str;
110
110
  };
111
-
112
- export const pluralName = (name: string): string => {
113
- if (name.length === 0) return name;
114
- if (name[name.length - 1] === "y") return `${name.slice(0, -1)}ies`;
115
- if (
116
- name[name.length - 1] === "s" ||
117
- name[name.length - 1] === "x" ||
118
- name[name.length - 1] === "z" ||
119
- name.endsWith("ch") ||
120
- name.endsWith("sh")
121
- )
122
- return `${name}es`;
123
- return `${name}s`;
124
- };