@ooneex/utils 0.0.3 → 0.0.4

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": "@ooneex/utils",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "compile": "tsc ./src/index.ts --declaration --emitDeclarationOnly --target ES2022 --moduleResolution bundler --outfile dist/index.d.ts",
11
11
  "build": "rm -rf dist && bun run compile && bun build ./src/index.ts --outdir ./dist --target browser --format esm --packages external --minify",
12
- "publish": "bun run build && cp package.json dist && cp README.md dist && cd dist && bun publish --access public"
12
+ "publish": "bun run build && bun publish --access public"
13
13
  },
14
14
  "devDependencies": {},
15
15
  "peerDependencies": {},
@@ -0,0 +1,3 @@
1
+ export const capitalizeWord = (word: string): string => {
2
+ return word ? word[0]?.toUpperCase() + word.slice(1).toLowerCase() : word;
3
+ };
@@ -0,0 +1,12 @@
1
+ export const dataURLtoFile = (dataurl: string, filename: string) => {
2
+ const arr = dataurl.split(",");
3
+ const mimeMatch = arr[0]?.match(/:(.*?);/);
4
+ const mime = mimeMatch ? mimeMatch[1] : "";
5
+ const bstr = atob(arr[1] as string);
6
+ let n = bstr.length;
7
+ const u8arr = new Uint8Array(n);
8
+ while (n--) {
9
+ u8arr[n] = bstr.charCodeAt(n);
10
+ }
11
+ return new File([u8arr], filename, { type: mime });
12
+ };
@@ -0,0 +1,7 @@
1
+ export const formatRelativeNumber = (num: number, config?: { precision?: number; lang?: string }): string => {
2
+ return new Intl.NumberFormat(config?.lang ?? "en-GB", {
3
+ notation: "compact",
4
+ compactDisplay: "short",
5
+ maximumFractionDigits: config?.precision ?? 1,
6
+ }).format(num);
7
+ };
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export { capitalizeWord } from "./capitalizeWord";
2
+ export { dataURLtoFile } from "./dataURLtoFile";
3
+ export { formatRelativeNumber } from "./formatRelativeNumber";
4
+ export { millisecondsToHMS } from "./millisecondsToHMS";
5
+ export { parseEnvVars } from "./parseEnvVars";
6
+ export { parseString } from "./parseString";
7
+ export { random } from "./random";
8
+ export { secondsToHMS } from "./secondsToHMS";
9
+ export { secondsToMS } from "./secondsToMS";
10
+ export { sleep } from "./sleep";
11
+ export { splitToWords } from "./splitToWords";
12
+ export { toCamelCase } from "./toCamelCase";
13
+ export { toKebabCase } from "./toKebabCase";
14
+ export { toPascalCase } from "./toPascalCase";
15
+ export { trim } from "./trim";
@@ -0,0 +1,16 @@
1
+ export const millisecondsToHMS = (ms: number): string => {
2
+ const totalSeconds = Math.floor(ms / 1000);
3
+ const hours = Math.floor(totalSeconds / 3600);
4
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
5
+ const seconds = totalSeconds % 60;
6
+
7
+ if (hours > 0) {
8
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
9
+ }
10
+
11
+ if (minutes > 0) {
12
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`;
13
+ }
14
+
15
+ return seconds.toString();
16
+ };
@@ -0,0 +1,14 @@
1
+ import { parseString } from "./parseString";
2
+ import { toCamelCase } from "./toCamelCase";
3
+
4
+ export const parseEnvVars = <T = Record<string, unknown>>(envs: Record<string, unknown>): T => {
5
+ const vars: Record<string, unknown> = {};
6
+
7
+ for (const key in envs) {
8
+ const k = toCamelCase(key);
9
+ const value = parseString(envs[key] as string) as unknown;
10
+ vars[k] = value;
11
+ }
12
+
13
+ return vars as T;
14
+ };
@@ -0,0 +1,47 @@
1
+ import { trim } from "./trim";
2
+
3
+ export const parseString = <T = unknown>(text: string): T => {
4
+ if (/^[0-9]+$/.test(text)) {
5
+ return Number.parseInt(text, 10) as T;
6
+ }
7
+
8
+ if (/^[0-9]+[.][0-9]+$/.test(text)) {
9
+ return Number.parseFloat(text) as T;
10
+ }
11
+
12
+ if (/^true$/i.test(text)) {
13
+ return true as T;
14
+ }
15
+
16
+ if (/^false$/i.test(text)) {
17
+ return false as T;
18
+ }
19
+
20
+ if (/^null$/i.test(text)) {
21
+ return null as T;
22
+ }
23
+
24
+ if (/^\[/.test(text) && /]$/.test(text)) {
25
+ const trimmedText = trim(text, "\\[|\\]");
26
+
27
+ let values: unknown[] = trimmedText.split(/, */);
28
+
29
+ values = values.map((value) => {
30
+ return parseString(value as string);
31
+ });
32
+
33
+ return values as T;
34
+ }
35
+
36
+ try {
37
+ const result = JSON.parse(text);
38
+
39
+ if (result === Number.POSITIVE_INFINITY || result === Number.NEGATIVE_INFINITY) {
40
+ return text as T;
41
+ }
42
+
43
+ return result;
44
+ } catch {
45
+ return text as T;
46
+ }
47
+ };
package/src/random.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { customAlphabet } from "nanoid";
2
+
3
+ export const random = {
4
+ nanoid(size?: number): string {
5
+ return customAlphabet("1234567890abcdef", size ?? 10)();
6
+ },
7
+ stringInt(size?: number): string {
8
+ return customAlphabet("1234567890", size ?? 10)();
9
+ },
10
+ nanoidFactory(size?: number): (size?: number) => string {
11
+ return customAlphabet("1234567890abcdef", size ?? 10);
12
+ },
13
+ };
@@ -0,0 +1,16 @@
1
+ export const secondsToHMS = (seconds: number): string => {
2
+ const totalSeconds = Math.floor(seconds);
3
+ const hours = Math.floor(totalSeconds / 3600);
4
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
5
+ const remainingSeconds = totalSeconds % 60;
6
+
7
+ if (hours > 0) {
8
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
9
+ }
10
+
11
+ if (minutes > 0) {
12
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
13
+ }
14
+
15
+ return remainingSeconds.toString();
16
+ };
@@ -0,0 +1,5 @@
1
+ export const secondsToMS = (seconds: number): string => {
2
+ const minutes = Math.floor(seconds / 60);
3
+ const remainingSeconds = seconds % 60;
4
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
5
+ };
package/src/sleep.ts ADDED
@@ -0,0 +1,3 @@
1
+ export const sleep = (ms: number): Promise<void> => {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ };
@@ -0,0 +1,14 @@
1
+ const CAPITALIZED_WORD_REGEXP = /\p{Lu}\p{Ll}+/u; // e.g. Apple
2
+ const ACRONYM_REGEXP = /\p{Lu}+(?=(\p{Lu}\p{Ll})|\P{L}|\b)/u; // e.g. ID, URL, handles an acronym followed by a capitalized word e.g. HTMLElement
3
+ const LOWERCASED_WORD_REGEXP = /(\p{Ll}+)/u; // e.g. apple
4
+ const ANY_LETTERS = /\p{L}+/u; // will match any sequence of letters, including in languages without a concept of upper/lower case
5
+ const DIGITS_REGEXP = /\p{N}+/u; // e.g. 123
6
+
7
+ const WORD_OR_NUMBER_REGEXP = new RegExp(
8
+ `${CAPITALIZED_WORD_REGEXP.source}|${ACRONYM_REGEXP.source}|${LOWERCASED_WORD_REGEXP.source}|${ANY_LETTERS.source}|${DIGITS_REGEXP.source}`,
9
+ "gu",
10
+ );
11
+
12
+ export const splitToWords = (input: string) => {
13
+ return input.match(WORD_OR_NUMBER_REGEXP) ?? [];
14
+ };
@@ -0,0 +1,8 @@
1
+ import { capitalizeWord } from "./capitalizeWord";
2
+ import { splitToWords } from "./splitToWords";
3
+
4
+ export const toCamelCase = (input: string): string => {
5
+ input = input.trim();
6
+ const [first = "", ...rest] = splitToWords(input);
7
+ return [first.toLowerCase(), ...rest.map(capitalizeWord)].join("");
8
+ };
@@ -0,0 +1,6 @@
1
+ import { splitToWords } from "./splitToWords";
2
+
3
+ export const toKebabCase = (input: string): string => {
4
+ input = input.trim();
5
+ return splitToWords(input).join("-").toLowerCase();
6
+ };
@@ -0,0 +1,7 @@
1
+ import { capitalizeWord } from "./capitalizeWord";
2
+ import { splitToWords } from "./splitToWords";
3
+
4
+ export const toPascalCase = (input: string): string => {
5
+ input = input.trim();
6
+ return splitToWords(input).map(capitalizeWord).join("");
7
+ };
package/src/trim.ts ADDED
@@ -0,0 +1,8 @@
1
+ export const trim = (text: string, char = " "): string => {
2
+ if ([".", "[", "]", "(", ")", "+", "*", "^", "$", "?", "/", "\\"].includes(char)) {
3
+ char = `\\${char}`;
4
+ }
5
+
6
+ const reg = new RegExp(`^${char}+|${char}+$`, "g");
7
+ return text.replace(reg, "");
8
+ };
@@ -0,0 +1,163 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { capitalizeWord } from "@/index";
3
+
4
+ describe("capitalizeWord", () => {
5
+ describe("basic functionality", () => {
6
+ test("should capitalize first letter of lowercase word", () => {
7
+ expect(capitalizeWord("hello")).toBe("Hello");
8
+ });
9
+
10
+ test("should capitalize first letter and lowercase the rest", () => {
11
+ expect(capitalizeWord("hELLO")).toBe("Hello");
12
+ });
13
+
14
+ test("should handle single character", () => {
15
+ expect(capitalizeWord("a")).toBe("A");
16
+ });
17
+
18
+ test("should handle uppercase single character", () => {
19
+ expect(capitalizeWord("A")).toBe("A");
20
+ });
21
+
22
+ test("should handle already capitalized word", () => {
23
+ expect(capitalizeWord("Hello")).toBe("Hello");
24
+ });
25
+
26
+ test("should handle mixed case word", () => {
27
+ expect(capitalizeWord("hElLo")).toBe("Hello");
28
+ });
29
+ });
30
+
31
+ describe("edge cases", () => {
32
+ test("should return empty string when input is empty", () => {
33
+ expect(capitalizeWord("")).toBe("");
34
+ });
35
+
36
+ test("should handle words with numbers", () => {
37
+ expect(capitalizeWord("hello123")).toBe("Hello123");
38
+ });
39
+
40
+ test("should handle words starting with numbers", () => {
41
+ expect(capitalizeWord("123hello")).toBe("123hello");
42
+ });
43
+
44
+ test("should handle special characters", () => {
45
+ expect(capitalizeWord("!hello")).toBe("!hello");
46
+ });
47
+
48
+ test("should handle words with special characters", () => {
49
+ expect(capitalizeWord("hello-world")).toBe("Hello-world");
50
+ });
51
+
52
+ test("should handle accented characters", () => {
53
+ expect(capitalizeWord("école")).toBe("École");
54
+ });
55
+
56
+ test("should handle non-latin characters", () => {
57
+ expect(capitalizeWord("привет")).toBe("Привет");
58
+ });
59
+ });
60
+
61
+ describe("whitespace handling", () => {
62
+ test("should handle words with leading spaces", () => {
63
+ expect(capitalizeWord(" hello")).toBe(" hello");
64
+ });
65
+
66
+ test("should handle words with trailing spaces", () => {
67
+ expect(capitalizeWord("hello ")).toBe("Hello ");
68
+ });
69
+
70
+ test("should handle single space", () => {
71
+ expect(capitalizeWord(" ")).toBe(" ");
72
+ });
73
+
74
+ test("should handle multiple spaces", () => {
75
+ expect(capitalizeWord(" ")).toBe(" ");
76
+ });
77
+
78
+ test("should handle tab character", () => {
79
+ expect(capitalizeWord("\t")).toBe("\t");
80
+ });
81
+
82
+ test("should handle newline character", () => {
83
+ expect(capitalizeWord("\n")).toBe("\n");
84
+ });
85
+ });
86
+
87
+ describe("parametrized tests", () => {
88
+ test.each([
89
+ ["word", "Word"],
90
+ ["WORD", "Word"],
91
+ ["wORD", "Word"],
92
+ ["test", "Test"],
93
+ ["javascript", "Javascript"],
94
+ ["typescript", "Typescript"],
95
+ ["react", "React"],
96
+ ])("capitalizeWord(%s) should return %s", (input, expected) => {
97
+ expect(capitalizeWord(input)).toBe(expected);
98
+ });
99
+ });
100
+
101
+ describe("type safety", () => {
102
+ test("should handle very long strings", () => {
103
+ const longString = "a".repeat(1000);
104
+ const result = capitalizeWord(longString);
105
+ expect(result).toHaveLength(1000);
106
+ expect(result[0]).toBe("A");
107
+ expect(result.slice(1)).toBe("a".repeat(999));
108
+ });
109
+
110
+ test("should handle unicode characters", () => {
111
+ expect(capitalizeWord("émile")).toBe("Émile");
112
+ expect(capitalizeWord("ñoño")).toBe("Ñoño");
113
+ expect(capitalizeWord("café")).toBe("Café");
114
+ });
115
+
116
+ test("should handle emoji", () => {
117
+ expect(capitalizeWord("😀hello")).toBe("😀hello");
118
+ expect(capitalizeWord("hello😀")).toBe("Hello😀");
119
+ });
120
+ });
121
+
122
+ describe("function behavior", () => {
123
+ test("should not mutate original string", () => {
124
+ const original = "hello";
125
+ const result = capitalizeWord(original);
126
+ expect(original).toBe("hello");
127
+ expect(result).toBe("Hello");
128
+ });
129
+
130
+ test("should return string type", () => {
131
+ const result = capitalizeWord("test");
132
+ expect(typeof result).toBe("string");
133
+ });
134
+
135
+ test("should handle consecutive calls consistently", () => {
136
+ const word = "hello";
137
+ const result1 = capitalizeWord(word);
138
+ const result2 = capitalizeWord(word);
139
+ expect(result1).toBe(result2);
140
+ expect(result1).toBe("Hello");
141
+ });
142
+ });
143
+
144
+ describe("real world examples", () => {
145
+ test("should capitalize common names", () => {
146
+ expect(capitalizeWord("john")).toBe("John");
147
+ expect(capitalizeWord("mary")).toBe("Mary");
148
+ expect(capitalizeWord("david")).toBe("David");
149
+ });
150
+
151
+ test("should handle programming terms", () => {
152
+ expect(capitalizeWord("function")).toBe("Function");
153
+ expect(capitalizeWord("variable")).toBe("Variable");
154
+ expect(capitalizeWord("method")).toBe("Method");
155
+ });
156
+
157
+ test("should handle common words", () => {
158
+ expect(capitalizeWord("the")).toBe("The");
159
+ expect(capitalizeWord("and")).toBe("And");
160
+ expect(capitalizeWord("but")).toBe("But");
161
+ });
162
+ });
163
+ });