@publikit/utils 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Pirimera
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @publikit/utils
2
+
3
+ Framework-agnostic utility functions for Publikit packages and applications.
4
+
5
+ - **No React peer dependency** — safe for scripts, edge workers, and non-UI code
6
+ - **Tree-shakeable** — `sideEffects: false`
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @publikit/utils
12
+ ```
13
+
14
+ ## Exports
15
+
16
+ | Export | Description |
17
+ |--------|-------------|
18
+ | `cn` | Merge Tailwind classes (`clsx` + `tailwind-merge`) |
19
+ | `debounce` | Debounce function calls |
20
+ | `getErrorMessage` | Extract message from unknown errors |
21
+ | `formatBytes` | Human-readable byte sizes |
22
+ | `calculateUsagePercentage` | Percentage capped at 100 |
23
+
24
+ ## Usage
25
+
26
+ ```ts
27
+ import { cn, debounce, getErrorMessage, formatBytes } from '@publikit/utils';
28
+
29
+ const className = cn('px-4', isActive && 'bg-primary');
30
+ const onResize = debounce(() => measure(), 150);
31
+ const message = getErrorMessage(error, 'Something went wrong');
32
+ const size = formatBytes(1_048_576); // "1 MB"
33
+ ```
34
+
35
+ ## Related packages
36
+
37
+ - [`@publikit/hooks`](../hooks) — generic React hooks
38
+ - [`@publikit/base`](../base) — re-exports `cn` for convenience
39
+
40
+ ## License
41
+
42
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ var clsx = require('clsx');
4
+ var tailwindMerge = require('tailwind-merge');
5
+
6
+ // cn.ts
7
+ function cn(...inputs) {
8
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
9
+ }
10
+
11
+ // async.ts
12
+ function debounce(fn, ms) {
13
+ let timeoutId = null;
14
+ let lastArgs = null;
15
+ const debounced = (...args) => {
16
+ lastArgs = args;
17
+ if (timeoutId) clearTimeout(timeoutId);
18
+ timeoutId = setTimeout(() => {
19
+ timeoutId = null;
20
+ if (lastArgs) {
21
+ fn(...lastArgs);
22
+ lastArgs = null;
23
+ }
24
+ }, ms);
25
+ };
26
+ debounced.cancel = () => {
27
+ if (timeoutId) clearTimeout(timeoutId);
28
+ timeoutId = null;
29
+ lastArgs = null;
30
+ };
31
+ debounced.flush = () => {
32
+ if (!timeoutId || !lastArgs) return;
33
+ clearTimeout(timeoutId);
34
+ timeoutId = null;
35
+ fn(...lastArgs);
36
+ lastArgs = null;
37
+ };
38
+ return debounced;
39
+ }
40
+
41
+ // error.ts
42
+ function getErrorMessage(error, defaultMessage) {
43
+ if (error instanceof Error && error.message) {
44
+ return error.message;
45
+ }
46
+ if (typeof error === "string" && error.trim()) {
47
+ return error;
48
+ }
49
+ if (error && typeof error === "object" && "message" in error) {
50
+ const message = error.message;
51
+ if (typeof message === "string" && message.trim()) {
52
+ return message;
53
+ }
54
+ }
55
+ return defaultMessage;
56
+ }
57
+
58
+ // format.ts
59
+ function formatBytes(bytes) {
60
+ if (!Number.isFinite(bytes) || bytes <= 0) {
61
+ return "0 B";
62
+ }
63
+ const k = 1024;
64
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
65
+ const i = Math.min(
66
+ Math.floor(Math.log(bytes) / Math.log(k)),
67
+ sizes.length - 1
68
+ );
69
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
70
+ }
71
+ function calculateUsagePercentage(current, limit) {
72
+ if (!Number.isFinite(current) || !Number.isFinite(limit) || limit <= 0) {
73
+ return 0;
74
+ }
75
+ if (current <= 0) {
76
+ return 0;
77
+ }
78
+ return Math.min(100, Math.round(current / limit * 100));
79
+ }
80
+
81
+ exports.calculateUsagePercentage = calculateUsagePercentage;
82
+ exports.cn = cn;
83
+ exports.debounce = debounce;
84
+ exports.formatBytes = formatBytes;
85
+ exports.getErrorMessage = getErrorMessage;
86
+ //# sourceMappingURL=index.cjs.map
87
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../cn.ts","../async.ts","../error.ts","../format.ts"],"names":["twMerge","clsx"],"mappings":"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","file":"index.cjs","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\r\nimport { twMerge } from 'tailwind-merge';\r\n\r\n/**\r\n * Merge Tailwind CSS class names with conflict resolution.\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n","/**\r\n * Debounced function with cancel and flush helpers.\r\n */\r\nexport interface DebouncedFunction<A extends unknown[], R> {\r\n (...args: A): void;\r\n cancel(): void;\r\n flush(): void;\r\n}\r\n\r\n/**\r\n * Debounce a function — only invoke after `ms` milliseconds of inactivity.\r\n */\r\nexport function debounce<A extends unknown[], R>(\r\n fn: (...args: A) => R,\r\n ms: number\r\n): DebouncedFunction<A, R> {\r\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\r\n let lastArgs: A | null = null;\r\n\r\n const debounced = (...args: A) => {\r\n lastArgs = args;\r\n if (timeoutId) clearTimeout(timeoutId);\r\n timeoutId = setTimeout(() => {\r\n timeoutId = null;\r\n if (lastArgs) {\r\n fn(...lastArgs);\r\n lastArgs = null;\r\n }\r\n }, ms);\r\n };\r\n\r\n debounced.cancel = () => {\r\n if (timeoutId) clearTimeout(timeoutId);\r\n timeoutId = null;\r\n lastArgs = null;\r\n };\r\n\r\n debounced.flush = () => {\r\n if (!timeoutId || !lastArgs) return;\r\n clearTimeout(timeoutId);\r\n timeoutId = null;\r\n fn(...lastArgs);\r\n lastArgs = null;\r\n };\r\n\r\n return debounced;\r\n}\r\n","/**\r\n * Extract a human-readable message from an unknown error value.\r\n */\r\nexport function getErrorMessage(error: unknown, defaultMessage: string): string {\r\n if (error instanceof Error && error.message) {\r\n return error.message;\r\n }\r\n\r\n if (typeof error === 'string' && error.trim()) {\r\n return error;\r\n }\r\n\r\n if (error && typeof error === 'object' && 'message' in error) {\r\n const message = (error as { message: unknown }).message;\r\n if (typeof message === 'string' && message.trim()) {\r\n return message;\r\n }\r\n }\r\n\r\n return defaultMessage;\r\n}\r\n","/**\r\n * Format a byte count as a human-readable string.\r\n */\r\nexport function formatBytes(bytes: number): string {\r\n if (!Number.isFinite(bytes) || bytes <= 0) {\r\n return '0 B';\r\n }\r\n\r\n const k = 1024;\r\n const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];\r\n const i = Math.min(\r\n Math.floor(Math.log(bytes) / Math.log(k)),\r\n sizes.length - 1\r\n );\r\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\r\n}\r\n\r\n/**\r\n * Calculate usage percentage capped at 100.\r\n */\r\nexport function calculateUsagePercentage(current: number, limit: number): number {\r\n if (!Number.isFinite(current) || !Number.isFinite(limit) || limit <= 0) {\r\n return 0;\r\n }\r\n\r\n if (current <= 0) {\r\n return 0;\r\n }\r\n\r\n return Math.min(100, Math.round((current / limit) * 100));\r\n}\r\n"]}
@@ -0,0 +1,35 @@
1
+ import { ClassValue } from 'clsx';
2
+
3
+ /**
4
+ * Merge Tailwind CSS class names with conflict resolution.
5
+ */
6
+ declare function cn(...inputs: ClassValue[]): string;
7
+
8
+ /**
9
+ * Debounced function with cancel and flush helpers.
10
+ */
11
+ interface DebouncedFunction<A extends unknown[], R> {
12
+ (...args: A): void;
13
+ cancel(): void;
14
+ flush(): void;
15
+ }
16
+ /**
17
+ * Debounce a function — only invoke after `ms` milliseconds of inactivity.
18
+ */
19
+ declare function debounce<A extends unknown[], R>(fn: (...args: A) => R, ms: number): DebouncedFunction<A, R>;
20
+
21
+ /**
22
+ * Extract a human-readable message from an unknown error value.
23
+ */
24
+ declare function getErrorMessage(error: unknown, defaultMessage: string): string;
25
+
26
+ /**
27
+ * Format a byte count as a human-readable string.
28
+ */
29
+ declare function formatBytes(bytes: number): string;
30
+ /**
31
+ * Calculate usage percentage capped at 100.
32
+ */
33
+ declare function calculateUsagePercentage(current: number, limit: number): number;
34
+
35
+ export { type DebouncedFunction, calculateUsagePercentage, cn, debounce, formatBytes, getErrorMessage };
@@ -0,0 +1,35 @@
1
+ import { ClassValue } from 'clsx';
2
+
3
+ /**
4
+ * Merge Tailwind CSS class names with conflict resolution.
5
+ */
6
+ declare function cn(...inputs: ClassValue[]): string;
7
+
8
+ /**
9
+ * Debounced function with cancel and flush helpers.
10
+ */
11
+ interface DebouncedFunction<A extends unknown[], R> {
12
+ (...args: A): void;
13
+ cancel(): void;
14
+ flush(): void;
15
+ }
16
+ /**
17
+ * Debounce a function — only invoke after `ms` milliseconds of inactivity.
18
+ */
19
+ declare function debounce<A extends unknown[], R>(fn: (...args: A) => R, ms: number): DebouncedFunction<A, R>;
20
+
21
+ /**
22
+ * Extract a human-readable message from an unknown error value.
23
+ */
24
+ declare function getErrorMessage(error: unknown, defaultMessage: string): string;
25
+
26
+ /**
27
+ * Format a byte count as a human-readable string.
28
+ */
29
+ declare function formatBytes(bytes: number): string;
30
+ /**
31
+ * Calculate usage percentage capped at 100.
32
+ */
33
+ declare function calculateUsagePercentage(current: number, limit: number): number;
34
+
35
+ export { type DebouncedFunction, calculateUsagePercentage, cn, debounce, formatBytes, getErrorMessage };
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ // cn.ts
5
+ function cn(...inputs) {
6
+ return twMerge(clsx(inputs));
7
+ }
8
+
9
+ // async.ts
10
+ function debounce(fn, ms) {
11
+ let timeoutId = null;
12
+ let lastArgs = null;
13
+ const debounced = (...args) => {
14
+ lastArgs = args;
15
+ if (timeoutId) clearTimeout(timeoutId);
16
+ timeoutId = setTimeout(() => {
17
+ timeoutId = null;
18
+ if (lastArgs) {
19
+ fn(...lastArgs);
20
+ lastArgs = null;
21
+ }
22
+ }, ms);
23
+ };
24
+ debounced.cancel = () => {
25
+ if (timeoutId) clearTimeout(timeoutId);
26
+ timeoutId = null;
27
+ lastArgs = null;
28
+ };
29
+ debounced.flush = () => {
30
+ if (!timeoutId || !lastArgs) return;
31
+ clearTimeout(timeoutId);
32
+ timeoutId = null;
33
+ fn(...lastArgs);
34
+ lastArgs = null;
35
+ };
36
+ return debounced;
37
+ }
38
+
39
+ // error.ts
40
+ function getErrorMessage(error, defaultMessage) {
41
+ if (error instanceof Error && error.message) {
42
+ return error.message;
43
+ }
44
+ if (typeof error === "string" && error.trim()) {
45
+ return error;
46
+ }
47
+ if (error && typeof error === "object" && "message" in error) {
48
+ const message = error.message;
49
+ if (typeof message === "string" && message.trim()) {
50
+ return message;
51
+ }
52
+ }
53
+ return defaultMessage;
54
+ }
55
+
56
+ // format.ts
57
+ function formatBytes(bytes) {
58
+ if (!Number.isFinite(bytes) || bytes <= 0) {
59
+ return "0 B";
60
+ }
61
+ const k = 1024;
62
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
63
+ const i = Math.min(
64
+ Math.floor(Math.log(bytes) / Math.log(k)),
65
+ sizes.length - 1
66
+ );
67
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
68
+ }
69
+ function calculateUsagePercentage(current, limit) {
70
+ if (!Number.isFinite(current) || !Number.isFinite(limit) || limit <= 0) {
71
+ return 0;
72
+ }
73
+ if (current <= 0) {
74
+ return 0;
75
+ }
76
+ return Math.min(100, Math.round(current / limit * 100));
77
+ }
78
+
79
+ export { calculateUsagePercentage, cn, debounce, formatBytes, getErrorMessage };
80
+ //# sourceMappingURL=index.js.map
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../cn.ts","../async.ts","../error.ts","../format.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\r\nimport { twMerge } from 'tailwind-merge';\r\n\r\n/**\r\n * Merge Tailwind CSS class names with conflict resolution.\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n","/**\r\n * Debounced function with cancel and flush helpers.\r\n */\r\nexport interface DebouncedFunction<A extends unknown[], R> {\r\n (...args: A): void;\r\n cancel(): void;\r\n flush(): void;\r\n}\r\n\r\n/**\r\n * Debounce a function — only invoke after `ms` milliseconds of inactivity.\r\n */\r\nexport function debounce<A extends unknown[], R>(\r\n fn: (...args: A) => R,\r\n ms: number\r\n): DebouncedFunction<A, R> {\r\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\r\n let lastArgs: A | null = null;\r\n\r\n const debounced = (...args: A) => {\r\n lastArgs = args;\r\n if (timeoutId) clearTimeout(timeoutId);\r\n timeoutId = setTimeout(() => {\r\n timeoutId = null;\r\n if (lastArgs) {\r\n fn(...lastArgs);\r\n lastArgs = null;\r\n }\r\n }, ms);\r\n };\r\n\r\n debounced.cancel = () => {\r\n if (timeoutId) clearTimeout(timeoutId);\r\n timeoutId = null;\r\n lastArgs = null;\r\n };\r\n\r\n debounced.flush = () => {\r\n if (!timeoutId || !lastArgs) return;\r\n clearTimeout(timeoutId);\r\n timeoutId = null;\r\n fn(...lastArgs);\r\n lastArgs = null;\r\n };\r\n\r\n return debounced;\r\n}\r\n","/**\r\n * Extract a human-readable message from an unknown error value.\r\n */\r\nexport function getErrorMessage(error: unknown, defaultMessage: string): string {\r\n if (error instanceof Error && error.message) {\r\n return error.message;\r\n }\r\n\r\n if (typeof error === 'string' && error.trim()) {\r\n return error;\r\n }\r\n\r\n if (error && typeof error === 'object' && 'message' in error) {\r\n const message = (error as { message: unknown }).message;\r\n if (typeof message === 'string' && message.trim()) {\r\n return message;\r\n }\r\n }\r\n\r\n return defaultMessage;\r\n}\r\n","/**\r\n * Format a byte count as a human-readable string.\r\n */\r\nexport function formatBytes(bytes: number): string {\r\n if (!Number.isFinite(bytes) || bytes <= 0) {\r\n return '0 B';\r\n }\r\n\r\n const k = 1024;\r\n const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];\r\n const i = Math.min(\r\n Math.floor(Math.log(bytes) / Math.log(k)),\r\n sizes.length - 1\r\n );\r\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;\r\n}\r\n\r\n/**\r\n * Calculate usage percentage capped at 100.\r\n */\r\nexport function calculateUsagePercentage(current: number, limit: number): number {\r\n if (!Number.isFinite(current) || !Number.isFinite(limit) || limit <= 0) {\r\n return 0;\r\n }\r\n\r\n if (current <= 0) {\r\n return 0;\r\n }\r\n\r\n return Math.min(100, Math.round((current / limit) * 100));\r\n}\r\n"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@publikit/utils",
3
+ "version": "0.1.1",
4
+ "description": "Framework-agnostic utility functions for Publikit packages and applications",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "sideEffects": false,
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "clean": "rimraf dist",
26
+ "prepublishOnly": "npm run clean && npm run build",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "vitest run"
29
+ },
30
+ "keywords": [
31
+ "utilities",
32
+ "cn",
33
+ "tailwind",
34
+ "clsx",
35
+ "publikit"
36
+ ],
37
+ "author": "Pirimera",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/pirimera/publikit.git",
42
+ "directory": "utils"
43
+ },
44
+ "homepage": "https://github.com/pirimera/publikit/tree/main/utils#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/pirimera/publikit/issues"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ },
54
+ "dependencies": {
55
+ "clsx": "^2.1.1",
56
+ "tailwind-merge": "^3.4.0"
57
+ },
58
+ "devDependencies": {
59
+ "rimraf": "^5.0.0",
60
+ "tsup": "^8.0.0",
61
+ "typescript": "^5.8.3",
62
+ "vitest": "^4.0.18"
63
+ }
64
+ }