Skip to content

Commit c453df4

Browse files
authored
Merge pull request #2 from JustAGhosT/tembo/migrate-orchestration-utils-repos
Migrate Orchestration Utils
2 parents 572adab + 21f6277 commit c453df4

8 files changed

Lines changed: 294 additions & 1 deletion

File tree

packages/utils/package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414
"./tailwind": {
1515
"import": "./dist/tailwind.js",
1616
"types": "./dist/tailwind.d.ts"
17+
},
18+
"./validation": {
19+
"import": "./dist/validation/index.js",
20+
"types": "./dist/validation/index.d.ts"
21+
},
22+
"./formatting": {
23+
"import": "./dist/formatting/index.js",
24+
"types": "./dist/formatting/index.d.ts"
1725
}
1826
},
1927
"files": [
@@ -47,7 +55,9 @@
4755
"codeflow",
4856
"utils",
4957
"utilities",
50-
"tailwind"
58+
"tailwind",
59+
"validation",
60+
"formatting"
5161
],
5262
"license": "MIT",
5363
"repository": {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Date and time formatting utilities.
3+
*/
4+
5+
/**
6+
* Format datetime to string.
7+
*
8+
* @param date - Date object to format
9+
* @param format - Format string ('iso' for ISO 8601, or custom format)
10+
* @returns Formatted datetime string
11+
*/
12+
export function formatDateTime(date: Date, format: string = "default"): string {
13+
if (format === "iso") {
14+
return date.toISOString();
15+
}
16+
17+
const year = date.getFullYear();
18+
const month = String(date.getMonth() + 1).padStart(2, "0");
19+
const day = String(date.getDate()).padStart(2, "0");
20+
const hours = String(date.getHours()).padStart(2, "0");
21+
const minutes = String(date.getMinutes()).padStart(2, "0");
22+
const seconds = String(date.getSeconds()).padStart(2, "0");
23+
24+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
25+
}
26+
27+
/**
28+
* Format datetime as relative time (e.g., "2 hours ago").
29+
*
30+
* @param date - Date object
31+
* @param now - Current date (defaults to now)
32+
* @returns Relative time string
33+
*/
34+
export function formatRelativeTime(date: Date, now: Date = new Date()): string {
35+
const delta = now.getTime() - date.getTime();
36+
const seconds = Math.floor(Math.abs(delta) / 1000);
37+
const minutes = Math.floor(seconds / 60);
38+
const hours = Math.floor(minutes / 60);
39+
const days = Math.floor(hours / 24);
40+
const months = Math.floor(days / 30);
41+
const years = Math.floor(days / 365);
42+
43+
const isFuture = delta < 0;
44+
const prefix = isFuture ? "in " : "";
45+
const suffix = isFuture ? "" : " ago";
46+
47+
if (years > 0) {
48+
return `${prefix}${years} year${years > 1 ? "s" : ""}${suffix}`;
49+
} else if (months > 0) {
50+
return `${prefix}${months} month${months > 1 ? "s" : ""}${suffix}`;
51+
} else if (days > 0) {
52+
return `${prefix}${days} day${days > 1 ? "s" : ""}${suffix}`;
53+
} else if (hours > 0) {
54+
return `${prefix}${hours} hour${hours > 1 ? "s" : ""}${suffix}`;
55+
} else if (minutes > 0) {
56+
return `${prefix}${minutes} minute${minutes > 1 ? "s" : ""}${suffix}`;
57+
} else {
58+
return isFuture ? "in a moment" : "just now";
59+
}
60+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Formatting utilities.
3+
*/
4+
5+
export * from "./date";
6+
export * from "./number";
7+
export * from "./string";
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Number formatting utilities.
3+
*/
4+
5+
/**
6+
* Format number with thousands separator and decimal places.
7+
*
8+
* @param value - Number to format
9+
* @param decimals - Number of decimal places
10+
* @param thousandsSeparator - Thousands separator character
11+
* @param decimalSeparator - Decimal separator character
12+
* @returns Formatted number string
13+
*/
14+
export function formatNumber(
15+
value: number,
16+
decimals: number = 2,
17+
thousandsSeparator: string = ",",
18+
decimalSeparator: string = "."
19+
): string {
20+
return value.toLocaleString("en-US", {
21+
minimumFractionDigits: decimals,
22+
maximumFractionDigits: decimals,
23+
})
24+
.replace(/,/g, thousandsSeparator)
25+
.replace(/\./g, decimalSeparator);
26+
}
27+
28+
/**
29+
* Format bytes to human-readable string.
30+
*
31+
* @param bytesValue - Number of bytes
32+
* @param binary - Use binary (1024) or decimal (1000) units
33+
* @returns Formatted string (e.g., "1.5 MB")
34+
*/
35+
export function formatBytes(bytesValue: number, binary: boolean = false): string {
36+
const base = binary ? 1024 : 1000;
37+
const units = binary
38+
? ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]
39+
: ["B", "KB", "MB", "GB", "TB", "PB"];
40+
41+
if (bytesValue === 0) {
42+
return "0 B";
43+
}
44+
45+
let unitIndex = 0;
46+
let value = bytesValue;
47+
48+
while (value >= base && unitIndex < units.length - 1) {
49+
value /= base;
50+
unitIndex++;
51+
}
52+
53+
return `${value.toFixed(2)} ${units[unitIndex]}`;
54+
}
55+
56+
/**
57+
* Format number as percentage.
58+
*
59+
* @param value - Number to format (0.0 to 1.0 or 0 to 100)
60+
* @param decimals - Number of decimal places
61+
* @returns Formatted percentage string
62+
*/
63+
export function formatPercentage(value: number, decimals: number = 1): string {
64+
const percentage = value <= 1.0 ? value * 100 : value;
65+
return `${percentage.toFixed(decimals)}%`;
66+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* String formatting utilities.
3+
*/
4+
5+
/**
6+
* Truncate string to maximum length.
7+
*
8+
* @param value - String to truncate
9+
* @param maxLength - Maximum length (including suffix)
10+
* @param suffix - Suffix to add if truncated
11+
* @param preserveWords - Whether to preserve word boundaries
12+
* @returns Truncated string
13+
*/
14+
export function truncateString(
15+
value: string,
16+
maxLength: number,
17+
suffix: string = "...",
18+
preserveWords: boolean = true
19+
): string {
20+
if (value.length <= maxLength) {
21+
return value;
22+
}
23+
24+
if (preserveWords) {
25+
const truncated = value.substring(0, maxLength - suffix.length);
26+
const lastSpace = truncated.lastIndexOf(" ");
27+
if (lastSpace > maxLength * 0.5) {
28+
return truncated.substring(0, lastSpace) + suffix;
29+
}
30+
return truncated + suffix;
31+
}
32+
33+
return value.substring(0, maxLength - suffix.length) + suffix;
34+
}
35+
36+
/**
37+
* Convert string to URL-friendly slug.
38+
*
39+
* @param value - String to slugify
40+
* @param separator - Word separator character
41+
* @returns Slugified string
42+
*/
43+
export function slugify(value: string, separator: string = "-"): string {
44+
return value
45+
.toLowerCase()
46+
.trim()
47+
.replace(/[\s_]+/g, separator)
48+
.replace(/[^\w\-]+/g, "")
49+
.replace(new RegExp(`${separator}+`, "g"), separator)
50+
.replace(new RegExp(`^${separator}|${separator}$`, "g"), "");
51+
}
52+
53+
/**
54+
* Convert camelCase to snake_case.
55+
*
56+
* @param value - CamelCase string
57+
* @returns snake_case string
58+
*/
59+
export function camelToSnake(value: string): string {
60+
return value
61+
.replace(/([A-Z])/g, "_$1")
62+
.toLowerCase()
63+
.replace(/^_/, "");
64+
}
65+
66+
/**
67+
* Convert snake_case to camelCase.
68+
*
69+
* @param value - snake_case string
70+
* @param capitalizeFirst - Whether to capitalize first letter (PascalCase)
71+
* @returns camelCase or PascalCase string
72+
*/
73+
export function snakeToCamel(
74+
value: string,
75+
capitalizeFirst: boolean = false
76+
): string {
77+
const components = value.split("_");
78+
const first = capitalizeFirst
79+
? components[0].charAt(0).toUpperCase() + components[0].slice(1)
80+
: components[0];
81+
const rest = components
82+
.slice(1)
83+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1));
84+
return first + rest.join("");
85+
}

packages/utils/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@
44

55
// Re-export tailwind utilities
66
export { cn, type ClassValue } from "./tailwind";
7+
8+
// Export validation utilities
9+
export * from "./validation";
10+
11+
// Export formatting utilities
12+
export * from "./formatting";
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Validation utilities.
3+
*/
4+
5+
export * from "./url";
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* URL validation utilities.
3+
*/
4+
5+
export interface UrlValidationResult {
6+
valid: boolean;
7+
error?: string;
8+
}
9+
10+
/**
11+
* Validate URL format.
12+
*
13+
* @param url - URL string to validate
14+
* @param schemes - Allowed URL schemes (optional)
15+
* @returns Validation result with valid flag and optional error message
16+
*/
17+
export function validateUrl(
18+
url: string,
19+
schemes?: string[]
20+
): UrlValidationResult {
21+
if (!url || typeof url !== "string") {
22+
return { valid: false, error: "URL must be a non-empty string" };
23+
}
24+
25+
try {
26+
const parsed = new URL(url);
27+
const scheme = parsed.protocol.slice(0, -1); // Remove trailing ':'
28+
29+
if (schemes && !schemes.includes(scheme)) {
30+
return {
31+
valid: false,
32+
error: `URL scheme must be one of: ${schemes.join(", ")}`,
33+
};
34+
}
35+
36+
return { valid: true };
37+
} catch (error) {
38+
return {
39+
valid: false,
40+
error: `Invalid URL format: ${error instanceof Error ? error.message : String(error)}`,
41+
};
42+
}
43+
}
44+
45+
/**
46+
* Check if URL is valid.
47+
*
48+
* @param url - URL string to check
49+
* @param schemes - Allowed URL schemes (optional)
50+
* @returns True if URL is valid, false otherwise
51+
*/
52+
export function isValidUrl(url: string, schemes?: string[]): boolean {
53+
return validateUrl(url, schemes).valid;
54+
}

0 commit comments

Comments
 (0)