feat: add validation for minimum length on various fields and update type definitions

This commit is contained in:
Abhimanyu Saharan
2026-02-06 16:12:04 +05:30
parent ca614328ac
commit d86fe0a7a6
157 changed files with 12340 additions and 2977 deletions

View File

@@ -1,3 +1,38 @@
type ClerkSession = {
getToken: () => Promise<string>;
};
type ClerkGlobal = {
session?: ClerkSession | null;
};
export class ApiError<TData = unknown> extends Error {
status: number;
data: TData | null;
constructor(status: number, message: string, data: TData | null) {
super(message);
this.name = "ApiError";
this.status = status;
this.data = data;
}
}
const resolveClerkToken = async (): Promise<string | null> => {
if (typeof window === "undefined") {
return null;
}
const clerk = (window as unknown as { Clerk?: ClerkGlobal }).Clerk;
if (!clerk?.session) {
return null;
}
try {
return await clerk.session.getToken();
} catch {
return null;
}
};
export const customFetch = async <T>(
url: string,
options: RequestInit
@@ -7,21 +42,73 @@ export const customFetch = async <T>(
throw new Error("NEXT_PUBLIC_API_URL is not set.");
}
const baseUrl = rawBaseUrl.replace(/\/+$/, "");
const headers = new Headers(options.headers);
const hasBody = options.body !== undefined && options.body !== null;
if (hasBody && !headers.has("Content-Type")) {
headers.set("Content-Type", "application/json");
}
if (!headers.has("Authorization")) {
const token = await resolveClerkToken();
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
}
const response = await fetch(`${baseUrl}${url}`, {
...options,
headers: {
"Content-Type": "application/json",
...(options.headers ?? {}),
},
headers,
});
if (!response.ok) {
throw new Error("Request failed");
const contentType = response.headers.get("content-type") ?? "";
let errorData: unknown = null;
const isJson =
contentType.includes("application/json") || contentType.includes("+json");
if (isJson) {
errorData = (await response.json().catch(() => null)) as unknown;
} else {
errorData = await response.text().catch(() => "");
}
let message =
typeof errorData === "string" && errorData ? errorData : "Request failed";
if (errorData && typeof errorData === "object") {
const detail = (errorData as { detail?: unknown }).detail;
if (typeof detail === "string" && detail) {
message = detail;
} else if (Array.isArray(detail) && detail.length) {
const first = detail[0] as { msg?: unknown };
if (first && typeof first === "object" && typeof first.msg === "string") {
message = first.msg;
}
}
}
throw new ApiError(response.status, message, errorData);
}
if (response.status === 204) {
return undefined as T;
return {
data: undefined,
status: response.status,
headers: response.headers,
} as T;
}
return (await response.json()) as T;
const contentType = response.headers.get("content-type") ?? "";
const isJson =
contentType.includes("application/json") || contentType.includes("+json");
if (isJson) {
const data = (await response.json()) as unknown;
return { data, status: response.status, headers: response.headers } as T;
}
if (contentType.includes("text/event-stream")) {
return {
data: response,
status: response.status,
headers: response.headers,
} as T;
}
const text = await response.text().catch(() => "");
return { data: text, status: response.status, headers: response.headers } as T;
};