Files
OmniRoute/src/lib/db/providerLimits.ts
2026-04-01 02:24:53 -04:00

128 lines
3.9 KiB
TypeScript

import { getDbInstance, isBuildPhase, isCloud } from "./core";
type JsonRecord = Record<string, unknown>;
interface StatementLike<TRow = unknown> {
all: (...params: unknown[]) => TRow[];
get: (...params: unknown[]) => TRow | undefined;
run: (...params: unknown[]) => { changes?: number };
}
interface DbLike {
prepare: <TRow = unknown>(sql: string) => StatementLike<TRow>;
transaction: <T extends (...args: any[]) => unknown>(fn: T) => T;
}
interface KeyValueRow {
key: string;
value: string;
}
export interface ProviderLimitsCacheEntry {
quotas: JsonRecord | null;
plan: unknown;
message: string | null;
fetchedAt: string;
source?: string | null;
}
const PROVIDER_LIMITS_CACHE_NAMESPACE = "providerLimitsCache";
function parseJson(raw: string): unknown {
try {
return JSON.parse(raw) as unknown;
} catch {
return null;
}
}
function toRecord(value: unknown): JsonRecord | null {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null;
}
function normalizeCacheEntry(value: unknown): ProviderLimitsCacheEntry | null {
const record = toRecord(value);
if (!record) return null;
const fetchedAt =
typeof record.fetchedAt === "string" && record.fetchedAt.trim() ? record.fetchedAt : null;
if (!fetchedAt) return null;
return {
quotas: toRecord(record.quotas),
plan: record.plan ?? null,
message: typeof record.message === "string" ? record.message : null,
fetchedAt,
source: typeof record.source === "string" ? record.source : null,
};
}
export function getProviderLimitsCache(connectionId: string): ProviderLimitsCacheEntry | null {
if (isBuildPhase || isCloud) return null;
const db = getDbInstance() as unknown as DbLike;
const row = db
.prepare("SELECT value FROM key_value WHERE namespace = ? AND key = ?")
.get(PROVIDER_LIMITS_CACHE_NAMESPACE, connectionId) as KeyValueRow | undefined;
if (!row?.value) return null;
return normalizeCacheEntry(parseJson(row.value));
}
export function getAllProviderLimitsCache(): Record<string, ProviderLimitsCacheEntry> {
if (isBuildPhase || isCloud) return {};
const db = getDbInstance() as unknown as DbLike;
const rows = db
.prepare("SELECT key, value FROM key_value WHERE namespace = ?")
.all(PROVIDER_LIMITS_CACHE_NAMESPACE) as KeyValueRow[];
const result: Record<string, ProviderLimitsCacheEntry> = {};
for (const row of rows) {
const parsed = normalizeCacheEntry(parseJson(row.value));
if (parsed) {
result[row.key] = parsed;
}
}
return result;
}
export function setProviderLimitsCache(
connectionId: string,
entry: ProviderLimitsCacheEntry
): ProviderLimitsCacheEntry {
if (isBuildPhase || isCloud) return entry;
const db = getDbInstance() as unknown as DbLike;
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)").run(
PROVIDER_LIMITS_CACHE_NAMESPACE,
connectionId,
JSON.stringify(entry)
);
return entry;
}
export function setProviderLimitsCacheBatch(
entries: Array<{ connectionId: string; entry: ProviderLimitsCacheEntry }>
): number {
if (isBuildPhase || isCloud || entries.length === 0) return 0;
const db = getDbInstance() as unknown as DbLike;
const insert = db.prepare(
"INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)"
);
const tx = db.transaction(
(items: Array<{ connectionId: string; entry: ProviderLimitsCacheEntry }>) => {
for (const item of items) {
insert.run(PROVIDER_LIMITS_CACHE_NAMESPACE, item.connectionId, JSON.stringify(item.entry));
}
}
);
tx(entries);
return entries.length;
}
export function deleteProviderLimitsCache(connectionId: string): void {
if (isBuildPhase || isCloud) return;
const db = getDbInstance() as unknown as DbLike;
db.prepare("DELETE FROM key_value WHERE namespace = ? AND key = ?").run(
PROVIDER_LIMITS_CACHE_NAMESPACE,
connectionId
);
}