@umituz/react-native-ai-fal-provider 3.1.7 → 3.2.0
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 +1 -1
- package/src/exports/infrastructure.ts +0 -45
- package/src/exports/presentation.ts +2 -11
- package/src/index.ts +2 -3
- package/src/infrastructure/services/fal-queue-operations.ts +1 -1
- package/src/infrastructure/services/index.ts +0 -2
- package/src/infrastructure/utils/index.ts +7 -53
- package/src/infrastructure/utils/input-validator.util.ts +1 -1
- package/src/infrastructure/utils/parsers/index.ts +1 -4
- package/src/init/createAiProviderInitModule.ts +0 -56
- package/src/init/initializeFalProvider.ts +34 -0
- package/src/presentation/hooks/index.ts +0 -3
- package/src/infrastructure/services/fal-models.service.ts +0 -40
- package/src/infrastructure/utils/base-builders.util.ts +0 -28
- package/src/infrastructure/utils/collections/array-filters.util.ts +0 -63
- package/src/infrastructure/utils/collections/array-sorters.util.ts +0 -94
- package/src/infrastructure/utils/collections/index.ts +0 -7
- package/src/infrastructure/utils/date-format.util.ts +0 -30
- package/src/infrastructure/utils/error-categorizer.ts +0 -9
- package/src/infrastructure/utils/job-metadata/index.ts +0 -26
- package/src/infrastructure/utils/job-metadata/job-metadata-format.util.ts +0 -78
- package/src/infrastructure/utils/job-metadata/job-metadata-lifecycle.util.ts +0 -66
- package/src/infrastructure/utils/job-metadata/job-metadata-queries.util.ts +0 -57
- package/src/infrastructure/utils/job-metadata/job-metadata.types.ts +0 -19
- package/src/infrastructure/utils/job-storage/index.ts +0 -19
- package/src/infrastructure/utils/job-storage/job-storage-crud.util.ts +0 -64
- package/src/infrastructure/utils/job-storage/job-storage-interface.ts +0 -44
- package/src/infrastructure/utils/job-storage/job-storage-queries.util.ts +0 -81
- package/src/infrastructure/utils/number-format.util.ts +0 -86
- package/src/infrastructure/utils/parsers/object-validators.util.ts +0 -38
- package/src/infrastructure/utils/parsers/value-parsers.util.ts +0 -45
- package/src/infrastructure/utils/string-format.util.ts +0 -72
- package/src/infrastructure/validators/README.md +0 -290
- package/src/init/registerWithWizard.ts +0 -28
- package/src/presentation/hooks/README.md +0 -626
- package/src/presentation/hooks/use-models.ts +0 -56
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Metadata Formatting
|
|
3
|
-
* Duration, progress calculation, and formatting utilities
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { FalJobMetadata } from "./job-metadata.types";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Get job duration in milliseconds
|
|
10
|
-
* Returns null if job not completed or if dates are invalid
|
|
11
|
-
*/
|
|
12
|
-
export function getJobDuration(metadata: FalJobMetadata): number | null {
|
|
13
|
-
if (!metadata.completedAt) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const completedTime = new Date(metadata.completedAt).getTime();
|
|
18
|
-
const createdTime = new Date(metadata.createdAt).getTime();
|
|
19
|
-
|
|
20
|
-
// Validate both dates parsed correctly
|
|
21
|
-
if (isNaN(completedTime) || isNaN(createdTime)) {
|
|
22
|
-
console.warn(
|
|
23
|
-
'[job-metadata] Invalid date(s) in metadata:',
|
|
24
|
-
{ completedAt: metadata.completedAt, createdAt: metadata.createdAt }
|
|
25
|
-
);
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const duration = completedTime - createdTime;
|
|
30
|
-
|
|
31
|
-
// Sanity check: duration should be positive
|
|
32
|
-
if (duration < 0) {
|
|
33
|
-
console.warn(
|
|
34
|
-
'[job-metadata] Negative duration detected (completedAt < createdAt):',
|
|
35
|
-
{ duration, completedAt: metadata.completedAt, createdAt: metadata.createdAt }
|
|
36
|
-
);
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return duration;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Format job duration for display
|
|
45
|
-
*/
|
|
46
|
-
export function formatJobDuration(metadata: FalJobMetadata): string {
|
|
47
|
-
const duration = getJobDuration(metadata);
|
|
48
|
-
if (!duration) {
|
|
49
|
-
return "In progress";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const seconds = Math.floor(duration / 1000);
|
|
53
|
-
if (seconds < 60) {
|
|
54
|
-
return `${seconds}s`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const minutes = Math.floor(seconds / 60);
|
|
58
|
-
const remainingSeconds = seconds % 60;
|
|
59
|
-
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Calculate job progress percentage
|
|
64
|
-
*/
|
|
65
|
-
export function calculateJobProgress(metadata: FalJobMetadata): number {
|
|
66
|
-
switch (metadata.status) {
|
|
67
|
-
case "IN_QUEUE":
|
|
68
|
-
return 10;
|
|
69
|
-
case "IN_PROGRESS":
|
|
70
|
-
return 50;
|
|
71
|
-
case "COMPLETED":
|
|
72
|
-
return 100;
|
|
73
|
-
case "FAILED":
|
|
74
|
-
return 0;
|
|
75
|
-
default:
|
|
76
|
-
return 0;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Metadata Lifecycle
|
|
3
|
-
* Create, update, and status check operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { JobStatus } from "../../../domain/types";
|
|
7
|
-
import type { FalJobMetadata } from "./job-metadata.types";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Create job metadata
|
|
11
|
-
*/
|
|
12
|
-
export function createJobMetadata(
|
|
13
|
-
requestId: string,
|
|
14
|
-
model: string,
|
|
15
|
-
timeout?: number
|
|
16
|
-
): FalJobMetadata {
|
|
17
|
-
const now = new Date().toISOString();
|
|
18
|
-
return {
|
|
19
|
-
requestId,
|
|
20
|
-
model,
|
|
21
|
-
status: "IN_QUEUE",
|
|
22
|
-
createdAt: now,
|
|
23
|
-
updatedAt: now,
|
|
24
|
-
timeout,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Update job metadata status
|
|
30
|
-
*/
|
|
31
|
-
export function updateJobMetadata(
|
|
32
|
-
metadata: FalJobMetadata,
|
|
33
|
-
status: JobStatus["status"],
|
|
34
|
-
error?: string
|
|
35
|
-
): FalJobMetadata {
|
|
36
|
-
return {
|
|
37
|
-
...metadata,
|
|
38
|
-
status,
|
|
39
|
-
updatedAt: new Date().toISOString(),
|
|
40
|
-
...(status === "COMPLETED" || status === "FAILED" ? { completedAt: new Date().toISOString() } : {}),
|
|
41
|
-
...(error ? { error } : {}),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Check if job is completed (success or failure)
|
|
47
|
-
*/
|
|
48
|
-
export function isJobCompleted(metadata: FalJobMetadata): boolean {
|
|
49
|
-
return metadata.status === "COMPLETED" || metadata.status === "FAILED";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Check if job is running
|
|
54
|
-
*/
|
|
55
|
-
export function isJobRunning(metadata: FalJobMetadata): boolean {
|
|
56
|
-
return metadata.status === "IN_QUEUE" || metadata.status === "IN_PROGRESS";
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Check if job is stale (older than specified minutes)
|
|
61
|
-
*/
|
|
62
|
-
export function isJobStale(metadata: FalJobMetadata, maxAgeMinutes: number = 60): boolean {
|
|
63
|
-
const age = Date.now() - new Date(metadata.createdAt).getTime();
|
|
64
|
-
const maxAgeMs = maxAgeMinutes * 60 * 1000;
|
|
65
|
-
return age > maxAgeMs;
|
|
66
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Metadata Queries
|
|
3
|
-
* Serialization and array operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { FalJobMetadata } from "./job-metadata.types";
|
|
7
|
-
import { isJobStale, isJobRunning, isJobCompleted } from "./job-metadata-lifecycle.util";
|
|
8
|
-
import { sortByDateDescending, filterByPredicate } from "../collections";
|
|
9
|
-
import { safeJsonParseOrNull, validateObjectStructure } from "../parsers";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Serialize job metadata for storage
|
|
13
|
-
*/
|
|
14
|
-
export function serializeJobMetadata(metadata: FalJobMetadata): string {
|
|
15
|
-
return JSON.stringify(metadata);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Deserialize job metadata from storage
|
|
20
|
-
*/
|
|
21
|
-
export function deserializeJobMetadata(data: string): FalJobMetadata | null {
|
|
22
|
-
const parsed = safeJsonParseOrNull<Record<string, unknown>>(data);
|
|
23
|
-
|
|
24
|
-
if (!parsed || !validateObjectStructure<Partial<FalJobMetadata>>(parsed, ["requestId", "model", "status"] as const)) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return parsed as FalJobMetadata;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Filter valid job metadata from array
|
|
33
|
-
*/
|
|
34
|
-
export function filterValidJobs(jobs: FalJobMetadata[]): FalJobMetadata[] {
|
|
35
|
-
return filterByPredicate(jobs, (job) => !isJobStale(job));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Sort jobs by creation time (newest first)
|
|
40
|
-
*/
|
|
41
|
-
export function sortJobsByCreation(jobs: FalJobMetadata[]): FalJobMetadata[] {
|
|
42
|
-
return sortByDateDescending(jobs, "createdAt");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get active jobs (not completed and not stale)
|
|
47
|
-
*/
|
|
48
|
-
export function getActiveJobs(jobs: FalJobMetadata[]): FalJobMetadata[] {
|
|
49
|
-
return filterByPredicate(jobs, (job) => isJobRunning(job) && !isJobStale(job));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get completed jobs
|
|
54
|
-
*/
|
|
55
|
-
export function getCompletedJobs(jobs: FalJobMetadata[]): FalJobMetadata[] {
|
|
56
|
-
return filterByPredicate(jobs, isJobCompleted);
|
|
57
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Metadata Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { JobStatus } from "../../../domain/types";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Job metadata for tracking and persistence
|
|
9
|
-
*/
|
|
10
|
-
export interface FalJobMetadata {
|
|
11
|
-
readonly requestId: string;
|
|
12
|
-
readonly model: string;
|
|
13
|
-
readonly status: JobStatus["status"];
|
|
14
|
-
readonly createdAt: string;
|
|
15
|
-
readonly updatedAt: string;
|
|
16
|
-
readonly completedAt?: string;
|
|
17
|
-
readonly timeout?: number;
|
|
18
|
-
readonly error?: string;
|
|
19
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Storage Utilities
|
|
3
|
-
* Exports all job storage functionality
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type { IJobStorage } from "./job-storage-interface";
|
|
7
|
-
export { InMemoryJobStorage } from "./job-storage-interface";
|
|
8
|
-
export {
|
|
9
|
-
saveJobMetadata,
|
|
10
|
-
loadJobMetadata,
|
|
11
|
-
deleteJobMetadata,
|
|
12
|
-
updateJobStatus,
|
|
13
|
-
} from "./job-storage-crud.util";
|
|
14
|
-
export {
|
|
15
|
-
loadAllJobs,
|
|
16
|
-
cleanupOldJobs,
|
|
17
|
-
getJobsByModel,
|
|
18
|
-
getJobsByStatus,
|
|
19
|
-
} from "./job-storage-queries.util";
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Storage CRUD Operations
|
|
3
|
-
* Basic create, read, update, delete operations for job metadata
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { FalJobMetadata } from "../job-metadata";
|
|
7
|
-
import { updateJobMetadata } from "../job-metadata";
|
|
8
|
-
import type { IJobStorage } from "./job-storage-interface";
|
|
9
|
-
import { safeJsonParseOrNull, safeJsonStringify } from "../parsers";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Save job metadata to storage
|
|
13
|
-
*/
|
|
14
|
-
export async function saveJobMetadata(
|
|
15
|
-
storage: IJobStorage,
|
|
16
|
-
metadata: FalJobMetadata
|
|
17
|
-
): Promise<void> {
|
|
18
|
-
const key = `fal_job:${metadata.requestId}`;
|
|
19
|
-
const value = safeJsonStringify(metadata, "{}");
|
|
20
|
-
await storage.setItem(key, value);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Load job metadata from storage
|
|
25
|
-
*/
|
|
26
|
-
export async function loadJobMetadata(
|
|
27
|
-
storage: IJobStorage,
|
|
28
|
-
requestId: string
|
|
29
|
-
): Promise<FalJobMetadata | null> {
|
|
30
|
-
const key = `fal_job:${requestId}`;
|
|
31
|
-
const value = await storage.getItem(key);
|
|
32
|
-
if (!value) return null;
|
|
33
|
-
|
|
34
|
-
return safeJsonParseOrNull<FalJobMetadata>(value);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Delete job metadata from storage
|
|
39
|
-
*/
|
|
40
|
-
export async function deleteJobMetadata(
|
|
41
|
-
storage: IJobStorage,
|
|
42
|
-
requestId: string
|
|
43
|
-
): Promise<void> {
|
|
44
|
-
const key = `fal_job:${requestId}`;
|
|
45
|
-
await storage.removeItem(key);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Update job status in storage
|
|
50
|
-
*/
|
|
51
|
-
export async function updateJobStatus(
|
|
52
|
-
storage: IJobStorage,
|
|
53
|
-
requestId: string,
|
|
54
|
-
status: FalJobMetadata["status"],
|
|
55
|
-
error?: string
|
|
56
|
-
): Promise<void> {
|
|
57
|
-
const metadata = await loadJobMetadata(storage, requestId);
|
|
58
|
-
if (!metadata) {
|
|
59
|
-
throw new Error(`Job not found: ${requestId}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const updated = updateJobMetadata(metadata, status, error);
|
|
63
|
-
await saveJobMetadata(storage, updated);
|
|
64
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Storage Interface
|
|
3
|
-
* Generic storage interface for job persistence
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generic storage interface for job persistence
|
|
8
|
-
* Implement this interface with your preferred storage backend
|
|
9
|
-
*/
|
|
10
|
-
export interface IJobStorage {
|
|
11
|
-
setItem(key: string, value: string): Promise<void>;
|
|
12
|
-
getItem(key: string): Promise<string | null>;
|
|
13
|
-
removeItem(key: string): Promise<void>;
|
|
14
|
-
getAllKeys?(): Promise<readonly string[]>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Simple in-memory storage for testing
|
|
19
|
-
*/
|
|
20
|
-
export class InMemoryJobStorage implements IJobStorage {
|
|
21
|
-
private store = new Map<string, string>();
|
|
22
|
-
|
|
23
|
-
setItem(key: string, value: string): Promise<void> {
|
|
24
|
-
this.store.set(key, value);
|
|
25
|
-
return Promise.resolve();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getItem(key: string): Promise<string | null> {
|
|
29
|
-
return Promise.resolve(this.store.get(key) ?? null);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
removeItem(key: string): Promise<void> {
|
|
33
|
-
this.store.delete(key);
|
|
34
|
-
return Promise.resolve();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
getAllKeys(): Promise<readonly string[]> {
|
|
38
|
-
return Promise.resolve(Array.from(this.store.keys()));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
clear(): void {
|
|
42
|
-
this.store.clear();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Storage Query Operations
|
|
3
|
-
* Query and batch operations for job metadata
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { FalJobMetadata } from "../job-metadata";
|
|
7
|
-
import type { IJobStorage } from "./job-storage-interface";
|
|
8
|
-
import { deleteJobMetadata } from "./job-storage-crud.util";
|
|
9
|
-
import { safeJsonParseOrNull } from "../parsers";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Load all jobs from storage
|
|
13
|
-
*/
|
|
14
|
-
export async function loadAllJobs(
|
|
15
|
-
storage: IJobStorage
|
|
16
|
-
): Promise<FalJobMetadata[]> {
|
|
17
|
-
if (!storage.getAllKeys) {
|
|
18
|
-
return [];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const keys = await storage.getAllKeys();
|
|
22
|
-
const jobKeys = keys.filter((key) => key.startsWith("fal_job:"));
|
|
23
|
-
|
|
24
|
-
const jobs: FalJobMetadata[] = [];
|
|
25
|
-
for (const key of jobKeys) {
|
|
26
|
-
const value = await storage.getItem(key);
|
|
27
|
-
if (value) {
|
|
28
|
-
const metadata = safeJsonParseOrNull<FalJobMetadata>(value);
|
|
29
|
-
if (metadata) {
|
|
30
|
-
jobs.push(metadata);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return jobs;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Clean up old jobs from storage
|
|
40
|
-
*/
|
|
41
|
-
export async function cleanupOldJobs(
|
|
42
|
-
storage: IJobStorage,
|
|
43
|
-
maxAgeMinutes: number = 60
|
|
44
|
-
): Promise<number> {
|
|
45
|
-
const jobs = await loadAllJobs(storage);
|
|
46
|
-
const now = Date.now();
|
|
47
|
-
const maxAgeMs = maxAgeMinutes * 60 * 1000;
|
|
48
|
-
let cleanedCount = 0;
|
|
49
|
-
|
|
50
|
-
for (const job of jobs) {
|
|
51
|
-
const jobAge = now - new Date(job.createdAt).getTime();
|
|
52
|
-
if (jobAge > maxAgeMs) {
|
|
53
|
-
await deleteJobMetadata(storage, job.requestId);
|
|
54
|
-
cleanedCount++;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return cleanedCount;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get jobs by model
|
|
63
|
-
*/
|
|
64
|
-
export async function getJobsByModel(
|
|
65
|
-
storage: IJobStorage,
|
|
66
|
-
model: string
|
|
67
|
-
): Promise<FalJobMetadata[]> {
|
|
68
|
-
const jobs = await loadAllJobs(storage);
|
|
69
|
-
return jobs.filter((job) => job.model === model);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get jobs by status
|
|
74
|
-
*/
|
|
75
|
-
export async function getJobsByStatus(
|
|
76
|
-
storage: IJobStorage,
|
|
77
|
-
status: FalJobMetadata["status"]
|
|
78
|
-
): Promise<FalJobMetadata[]> {
|
|
79
|
-
const jobs = await loadAllJobs(storage);
|
|
80
|
-
return jobs.filter((job) => job.status === status);
|
|
81
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Number Formatting Utilities
|
|
3
|
-
* Functions for formatting numbers and quantities
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Format number with decimal places
|
|
8
|
-
*/
|
|
9
|
-
export function formatNumber(value: number, decimals: number = 2): string {
|
|
10
|
-
if (Number.isNaN(value) || !Number.isFinite(value)) {
|
|
11
|
-
return "0";
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Return integer if no decimal part
|
|
15
|
-
if (value % 1 === 0) {
|
|
16
|
-
return value.toString();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return value.toFixed(decimals);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Format bytes to human-readable size
|
|
24
|
-
* Handles edge cases: negative bytes, NaN, Infinity, extremely large values
|
|
25
|
-
*/
|
|
26
|
-
export function formatBytes(bytes: number, decimals: number = 2): string {
|
|
27
|
-
// Handle invalid inputs
|
|
28
|
-
if (!Number.isFinite(bytes) || Number.isNaN(bytes)) {
|
|
29
|
-
return "0 Bytes";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Handle negative bytes
|
|
33
|
-
if (bytes < 0) {
|
|
34
|
-
return `-${formatBytes(-bytes, decimals)}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Handle zero
|
|
38
|
-
if (bytes === 0) {
|
|
39
|
-
return "0 Bytes";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const k = 1024;
|
|
43
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
44
|
-
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
45
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
46
|
-
|
|
47
|
-
// Clamp index to valid range
|
|
48
|
-
const index = Math.min(i, sizes.length - 1);
|
|
49
|
-
|
|
50
|
-
// For extremely large values beyond our size array
|
|
51
|
-
if (index >= sizes.length - 1 && i > sizes.length - 1) {
|
|
52
|
-
const exponent = i - (sizes.length - 1);
|
|
53
|
-
const value = bytes / Math.pow(k, sizes.length - 1);
|
|
54
|
-
return `${parseFloat(value.toFixed(dm))} ${sizes[sizes.length - 1]} × ${k}^${exponent}`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return `${parseFloat((bytes / Math.pow(k, index)).toFixed(dm))} ${sizes[index]}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Format duration in milliseconds to human-readable string
|
|
62
|
-
*/
|
|
63
|
-
export function formatDuration(milliseconds: number): string {
|
|
64
|
-
if (milliseconds < 1000) {
|
|
65
|
-
return `${milliseconds}ms`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const seconds = Math.floor(milliseconds / 1000);
|
|
69
|
-
if (seconds < 60) {
|
|
70
|
-
return `${seconds}s`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const minutes = Math.floor(seconds / 60);
|
|
74
|
-
const remainingSeconds = seconds % 60;
|
|
75
|
-
if (minutes < 60) {
|
|
76
|
-
return remainingSeconds > 0
|
|
77
|
-
? `${minutes}m ${remainingSeconds}s`
|
|
78
|
-
: `${minutes}m`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const hours = Math.floor(minutes / 60);
|
|
82
|
-
const remainingMinutes = minutes % 60;
|
|
83
|
-
return remainingMinutes > 0
|
|
84
|
-
? `${hours}h ${remainingMinutes}m`
|
|
85
|
-
: `${hours}h`;
|
|
86
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Object Validator Utilities
|
|
3
|
-
* Runtime object structure validation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Validate object structure
|
|
8
|
-
*/
|
|
9
|
-
export function validateObjectStructure<T extends Record<string, unknown>>(
|
|
10
|
-
data: unknown,
|
|
11
|
-
requiredKeys: readonly (keyof T)[]
|
|
12
|
-
): data is T {
|
|
13
|
-
if (!data || typeof data !== "object") {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
for (const key of requiredKeys) {
|
|
18
|
-
if (!(key in data)) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Validate array of objects
|
|
28
|
-
*/
|
|
29
|
-
export function validateObjectArray<T>(
|
|
30
|
-
data: unknown,
|
|
31
|
-
validator: (item: unknown) => item is T
|
|
32
|
-
): data is T[] {
|
|
33
|
-
if (!Array.isArray(data)) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return data.every(validator);
|
|
38
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Value Parser Utilities
|
|
3
|
-
* Parse primitive values with fallbacks
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Parse number with fallback
|
|
8
|
-
*/
|
|
9
|
-
export function parseNumber(value: unknown, fallback: number): number {
|
|
10
|
-
if (typeof value === "number") {
|
|
11
|
-
return value;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (typeof value === "string") {
|
|
15
|
-
const parsed = Number.parseFloat(value);
|
|
16
|
-
return Number.isNaN(parsed) ? fallback : parsed;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return fallback;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Parse boolean with fallback
|
|
24
|
-
*/
|
|
25
|
-
export function parseBoolean(value: unknown, fallback: boolean): boolean {
|
|
26
|
-
if (typeof value === "boolean") {
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (typeof value === "string") {
|
|
31
|
-
const lower = value.toLowerCase().trim();
|
|
32
|
-
if (lower === "true" || lower === "yes" || lower === "1") {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
if (lower === "false" || lower === "no" || lower === "0") {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (typeof value === "number") {
|
|
41
|
-
return value !== 0;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return fallback;
|
|
45
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* String Formatting Utilities
|
|
3
|
-
* Functions for formatting and manipulating strings
|
|
4
|
-
*
|
|
5
|
-
* Consolidates text truncation and prompt sanitization utilities.
|
|
6
|
-
* Previously duplicated across string-format.util.ts and prompt-helpers.util.ts.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Truncate text with customizable ellipsis
|
|
11
|
-
*
|
|
12
|
-
* @param text - The text to truncate
|
|
13
|
-
* @param maxLength - Maximum length including ellipsis
|
|
14
|
-
* @param ellipsis - String to append when truncating (default: "...")
|
|
15
|
-
* @returns Truncated text with ellipsis or original if under max length
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```typescript
|
|
19
|
-
* truncateText("Hello World", 8); // "Hello..."
|
|
20
|
-
* truncateText("Hello World", 8, "…"); // "Hello W…"
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export function truncateText(
|
|
24
|
-
text: string,
|
|
25
|
-
maxLength: number,
|
|
26
|
-
ellipsis: string = "..."
|
|
27
|
-
): string {
|
|
28
|
-
if (text.length <= maxLength) {
|
|
29
|
-
return text;
|
|
30
|
-
}
|
|
31
|
-
return text.slice(0, maxLength - ellipsis.length) + ellipsis;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Truncate prompt to maximum length
|
|
36
|
-
* Specialized wrapper for prompt truncation with 5000 char default
|
|
37
|
-
*
|
|
38
|
-
* @param prompt - The prompt text to truncate
|
|
39
|
-
* @param maxLength - Maximum length (default: 5000)
|
|
40
|
-
* @returns Truncated prompt
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```typescript
|
|
44
|
-
* truncatePrompt(longPrompt); // Truncates to 5000 chars
|
|
45
|
-
* truncatePrompt(longPrompt, 1000); // Truncates to 1000 chars
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export function truncatePrompt(prompt: string, maxLength: number = 5000): string {
|
|
49
|
-
return truncateText(prompt, maxLength);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Sanitize prompt by removing excessive whitespace and control characters
|
|
54
|
-
* Also enforces maximum length of 5000 characters
|
|
55
|
-
*
|
|
56
|
-
* @param prompt - The prompt text to sanitize
|
|
57
|
-
* @returns Sanitized prompt
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```typescript
|
|
61
|
-
* sanitizePrompt("Hello \n\n World\x00"); // "Hello World"
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
export function sanitizePrompt(prompt: string): string {
|
|
65
|
-
return prompt
|
|
66
|
-
.trim()
|
|
67
|
-
.replace(/\s+/g, " ")
|
|
68
|
-
// Remove control characters except tab, newline, carriage return
|
|
69
|
-
// eslint-disable-next-line no-control-regex
|
|
70
|
-
.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '')
|
|
71
|
-
.slice(0, 5000);
|
|
72
|
-
}
|