import { type ClassValue, clsx } from "clsx"; import { JSONSchema7 } from "json-schema"; import { twMerge } from "tailwind-merge"; import z from "zod"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export const fetcher = async (url: string, options?: RequestInit) => { const res = await fetch(url, { redirect: "follow", cache: "no-store", ...options, }); if (!res.ok) { let errorPayload; try { errorPayload = await res.json(); } catch { errorPayload = { message: `Request failed with status ${res.status}` }; } const error = new Error( errorPayload.message || "An error occurred while fetching the data.", ); Object.assign(error, { info: errorPayload, status: res.status }); throw error; } return res.json(); }; export const createIncrement = (i = 0) => () => i++; export const noop = () => {}; export const wait = (delay = 0) => new Promise((resolve) => setTimeout(resolve, delay)); export const randomRange = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1) + min); export const isString = (value: any): value is string => typeof value === "string"; export const isFunction = < T extends (...args: any[]) => any = (...args: any[]) => any, >( v: unknown, ): v is T => typeof v === "function"; export const isObject = (value: any): value is Record => Object(value) === value; export const isNull = (value: any): value is null | undefined => value == null; export const isPromiseLike = (x: unknown): x is PromiseLike => isFunction((x as any)?.then); export const isJson = (value: any): value is Record => { try { if (typeof value === "string") { const str = value.trim(); JSON.parse(str); return true; } else if (isObject(value)) { return true; } return false; } catch (_e) { return false; } }; export const createDebounce = () => { let timeout: ReturnType; const debounce = (func: (...args: any[]) => any, waitFor = 200) => { clearTimeout(timeout!); timeout = setTimeout(() => func(), waitFor); return timeout; }; debounce.clear = () => { clearTimeout(timeout!); }; return debounce; }; export const createThrottle = () => { let lastCall = 0; let timeout: ReturnType | null = null; const throttle = (func: (...args: any[]) => any, waitFor = 200) => { const now = Date.now(); const timeSinceLastCall = now - lastCall; if (timeSinceLastCall >= waitFor) { lastCall = now; func(); } else { // Schedule the next call if not already scheduled if (!timeout) { const remainingTime = waitFor - timeSinceLastCall; timeout = setTimeout(() => { lastCall = Date.now(); func(); timeout = null; }, remainingTime); } } }; throttle.clear = () => { if (timeout) { clearTimeout(timeout); timeout = null; } lastCall = 0; }; return throttle; }; export const groupBy = (arr: T[], getter: keyof T | ((item: T) => string)) => arr.reduce( (prev, item) => { const key: string = getter instanceof Function ? getter(item) : (item[getter] as string); if (!prev[key]) prev[key] = []; prev[key].push(item); return prev; }, {} as Record, ); export const PromiseChain = () => { let promise: Promise = Promise.resolve(); return (asyncFunction: () => Promise): Promise => { const resultPromise = promise.then(() => asyncFunction()); promise = resultPromise.catch(() => {}); return resultPromise; }; }; export const Deferred = () => { let resolve!: T extends void ? (value?: any) => void : (value: T) => void; let reject!: (reason?: any) => void; const promise = new Promise((rs, rj) => { resolve = rs as T extends void ? (value?: any) => void : (value: T) => void; reject = rj; }); return { promise, reject, resolve, }; }; export class Locker { private promise = Promise.resolve(); private resolve?: () => void; get isLocked() { return !!this.resolve; } lock() { this.promise = new Promise((resolve) => { this.resolve = resolve; }); } unlock() { if (!this.isLocked) return; this.resolve?.(); this.resolve = undefined; } async wait() { await this.promise; } } export function safeJSONParse( json: string, ): | { success: true; value: T; error?: unknown; } | { success: false; error: unknown; value?: T; } { try { const parsed = JSON.parse(json); return { success: true, value: parsed, }; } catch (e) { return { success: false, error: e, }; } } export function generateUUID(): string { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } export function toAny(value: T): any { return value; } export function errorToString(error: unknown) { if (error == null) { return "unknown error"; } if (typeof error === "string") { return error; } if (error instanceof Error) { return error.message; } return JSON.stringify(error); } export function objectFlow>(obj: T) { return { map: ( fn: (value: T[keyof T], key: keyof T) => R, ): Record => { return Object.fromEntries( Object.entries(obj).map(([key, value]) => [key, fn(value, key)]), ) as Record; }, filter: ( fn: (value: T[keyof T], key: keyof T) => boolean, ): Record => { return Object.fromEntries( Object.entries(obj).filter(([key, value]) => fn(value, key)), ) as Record; }, forEach: (fn: (value: T[keyof T], key: keyof T) => void): void => { Object.entries(obj).forEach(([key, value]) => fn(value, key)); }, some: (fn: (value: T[keyof T], key: keyof T) => any): boolean => { return Object.entries(obj).some(([key, value]) => fn(value, key)); }, every: (fn: (value: T[keyof T], key: keyof T) => any): boolean => { return Object.entries(obj).every(([key, value]) => fn(value, key)); }, find(fn: (value: T[keyof T], key: keyof T) => any): T | undefined { return Object.entries(obj).find(([key, value]) => fn(value, key))?.[1]; }, getByPath(path: string[]): U | undefined { let result: any = obj; path.find((p) => { result = result?.[p]; return !result; }); return result; }, setByPath(path: string[], value: any) { path.reduce((acc, cur, i) => { const isLast = i == path.length - 1; if (isLast) { acc[cur] = value; return acc; } acc[cur] ??= {}; return acc[cur]; }, obj as object); return obj; }, }; } export function capitalizeFirstLetter(str: string): string { if (!str || str.length === 0) return str; return str.charAt(0).toUpperCase() + str.slice(1); } export function truncateString(str: string, maxLength: number): string { if (str.length <= maxLength) return str; return str.slice(0, maxLength) + "..."; } export async function nextTick() { return new Promise((resolve) => setTimeout(resolve, 0)); } export function cleanVariableName(input: string = ""): string { if (!input || typeof input !== "string") { return ""; } return input.replace(/[^\w\u0080-\uFFFF-]/g, "").replace(/^[0-9]+/, ""); } export function generateUniqueKey(key: string, existingKeys: string[]) { let newKey = key; let counter = 1; while (existingKeys.includes(newKey)) { const baseKey = key.replace(/\d+$/, ""); const hasOriginalNumber = key !== baseKey; if (hasOriginalNumber) { const originalNumber = parseInt(key.match(/\d+$/)?.[0] || "0"); newKey = baseKey + (originalNumber + counter); } else { newKey = baseKey + counter; } counter++; } return newKey; } export function exclude( obj: T, keys: K[], ): Omit { return Object.fromEntries( Object.entries(obj).filter(([key]) => !keys.includes(key as K)), ) as Omit; } export function validateSchema(key: string, schema: JSONSchema7) { const variableName = cleanVariableName(key); if (variableName.length === 0) { throw new Error("Invalid Variable Name"); } if (variableName.length > 255) { throw new Error("Variable Name is too long"); } if (!schema.type) { throw new Error("Invalid Schema"); } if (schema.type == "array" || schema.type == "object") { const keys = Array.from(Object.keys(schema.properties ?? {})); if (keys.length != new Set(keys).size) { throw new Error("Output data must have unique keys"); } return keys.every((key) => { return validateSchema(key, schema.properties![key] as JSONSchema7); }); } return true; } export const createEmitter = () => { const listeners = new Set<(value: string) => void>(); return { on: (listener: (value: string) => void) => { listeners.add(listener); return () => { listeners.delete(listener); }; }, off: (listener: (value: string) => void) => { listeners.delete(listener); }, emit: (value: string) => { listeners.forEach((listener) => listener(value)); }, }; }; export function deduplicateByKey(arr: T[], key: keyof T): T[] { const seen = new Set(); return arr.filter((item) => { const keyValue = item[key]; if (seen.has(keyValue)) { return false; } else { seen.add(keyValue); return true; } }); } export function withTimeout(promise: Promise, ms: number): Promise { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error("Timeout")); }, ms); promise .then((res) => { clearTimeout(timer); resolve(res); }) .catch((err) => { clearTimeout(timer); reject(err); }); }); } export function parseEnvBoolean(value: string | boolean | undefined): boolean { if (typeof value === "boolean") return value; if (typeof value === "string") { const lowerVal = value.toLowerCase(); return lowerVal === "true" || lowerVal === "1" || lowerVal === "y"; } return false; } const booleans = ["true", "false", true, false, 1, 0, "1", "0"]; export const booleanCoerced = z .any() .refine((val) => booleans.includes(val), { message: "must be boolean" }) .transform((val) => { if (val === "true" || val === true || val === "1" || val === 1) return true; return false; });