mirror of
https://github.com/YuzuZensai/Termix.git
synced 2026-11-12 19:36:41 +00:00
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
/** A GitHub-like public namespace: lowercase, starts alphanumeric, 1-39 chars. */
|
|||
|
|
export const HANDLE_REGEX = /^[a-z0-9][a-z0-9_-]{0,38}$/;
|
||
|
|
|
||
|
|
const RESERVED_HANDLES = new Set(["u", "me", "keys", "check", "admin", "api"]);
|
||
|
|
|
||
|
|
const MAX_DESCRIPTION_LENGTH = 500;
|
||
|
|
|
||
|
|
export function isValidHandle(handle: string): boolean {
|
||
|
|
return HANDLE_REGEX.test(handle) && !RESERVED_HANDLES.has(handle);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function cleanDescription(value: unknown): string | null {
|
||
|
|
if (typeof value !== "string") return null;
|
||
|
|
return value.trim().slice(0, MAX_DESCRIPTION_LENGTH) || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function cleanLabel(value: unknown): string | null {
|
||
|
|
return typeof value === "string" ? value.trim() || null : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Validity in days: a positive whole number, at most ten years. */
|
||
|
|
export function clampValidityDays(value: unknown, fallback: number): number {
|
||
|
|
const days = Number(value);
|
||
|
|
if (!Number.isFinite(days) || days <= 0) return fallback;
|
||
|
|
return Math.min(Math.floor(days), 3650);
|
||
|
|
}
|
||
|
|
|
||
|
|
interface DbError {
|
||
|
|
code?: string;
|
||
|
|
errno?: number;
|
||
|
|
message?: string;
|
||
|
|
cause?: unknown;
|
||
|
|
}
|
||
|
|
|
||
|
|
function errorChain(error: unknown): DbError[] {
|
||
|
|
const chain: DbError[] = [];
|
||
|
|
let current = error as DbError | undefined;
|
||
|
|
while (current && typeof current === "object" && chain.length < 5) {
|
||
|
|
chain.push(current);
|
||
|
|
current = current.cause as DbError | undefined;
|
||
|
|
}
|
||
|
|
return chain;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** A UNIQUE violation on sqlite, Postgres or MySQL. */
|
||
|
|
export function isUniqueViolation(error: unknown): boolean {
|
||
|
|
return errorChain(error).some(
|
||
|
|
(entry) =>
|
||
|
|
entry.code === "SQLITE_CONSTRAINT_UNIQUE" ||
|
||
|
|
entry.code === "23505" ||
|
||
|
|
entry.code === "ER_DUP_ENTRY" ||
|
||
|
|
entry.errno === 1062 ||
|
||
|
|
(typeof entry.message === "string" &&
|
||
|
|
entry.message.includes("UNIQUE constraint failed")),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Whether a UNIQUE violation was on user_id (one handle per user) rather than the handle. */
|
||
|
|
export function isUserIdViolation(error: unknown): boolean {
|
||
|
|
return errorChain(error).some(
|
||
|
|
(entry) =>
|
||
|
|
typeof entry.message === "string" &&
|
||
|
|
(entry.message.includes("user_id") || entry.message.includes("userId")),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function asError(error: unknown): Error {
|
||
|
|
return error instanceof Error ? error : new Error(String(error));
|
||
|
|
}
|