migrate to monorepo?

This commit is contained in:
2025-12-13 20:20:48 -05:00
parent 7da6408d7a
commit a66b5b2362
29 changed files with 849 additions and 817 deletions

View File

@@ -0,0 +1,8 @@
/**
* Delay execution for a specified number of milliseconds
* @param ms - Milliseconds to delay
* @returns A promise that resolves after the specified delay
*/
export function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -0,0 +1,21 @@
/**
* Format cents to a human-readable currency string
* @param cents - Amount in cents (integer)
* @param locale - Locale string for formatting (e.g., 'en-CA', 'en-US')
* @returns Formatted currency string
*/
export function formatCentsToCurrency(cents: number, locale: string = "en-CA"): string {
try {
const formatter = new Intl.NumberFormat(locale, {
style: "currency",
currency: "CAD",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
return formatter.format(cents / 100);
} catch (error) {
// Fallback if locale is not supported
const dollars = (cents / 100).toFixed(2);
return `$${dollars}`;
}
}

View File

@@ -0,0 +1,87 @@
/** Custom error class for HTTP-related failures */
export class HttpError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
this.name = "HttpError";
}
}
/** Type guard to check if a value is a record (object) */
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/**
* Fetch HTML content from a URL with automatic retries
* @param url - The URL to fetch
* @param delayMs - Delay in milliseconds between retries
* @param opts - Optional fetch options
* @returns The HTML content as a string
* @throws HttpError if all retries are exhausted
*/
export async function fetchHtml(
url: string,
delayMs: number,
opts?: RequestInit
): Promise<string> {
const maxAttempts = 3;
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
const response = await fetch(url, opts);
// Check for rate limiting
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After");
const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : delayMs * (attempt + 1);
console.warn(
`Rate limited. Retrying after ${waitTime}ms...`
);
await new Promise((resolve) => setTimeout(resolve, waitTime));
continue;
}
// Check for server errors
if (response.status >= 500) {
lastError = new HttpError(
response.status,
`Server error: ${response.status}`
);
if (attempt < maxAttempts - 1) {
await new Promise((resolve) =>
setTimeout(resolve, delayMs * (attempt + 1))
);
continue;
}
throw lastError;
}
// Check for successful response
if (!response.ok) {
throw new HttpError(
response.status,
`HTTP ${response.status}: ${response.statusText}`
);
}
return await response.text();
} catch (error) {
lastError =
error instanceof Error
? error
: new Error("Unknown error during fetch");
if (attempt < maxAttempts - 1) {
await new Promise((resolve) =>
setTimeout(resolve, delayMs * (attempt + 1))
);
}
}
}
throw lastError || new HttpError(0, "Failed to fetch after retries");
}