feat: add getCachedProviderConnectionById and getCachedProviderNodes with 38-file callers conversion

PR #2 of memory-pressure series — cache the two remaining hot database
access patterns that were uncached:

Core changes:
- readCache.ts: add connectionByIdCache, nodesCache, getCachedProviderConnectionById,
  getCachedProviderNodes, extend invalidateDbCache for "nodes" scope
- nodes.ts: invalidate nodes cache on create/update/delete
- localDb.ts: export new cache functions, remove dead code exports
  (isConnectionRateLimited, getRateLimitedConnections)

Callers converted (38 files):
- open-sse hot paths: chatCore.ts, codexFailover.ts, concurrencyCaps.ts,
  quotaExhaustionCutoff.ts, tokenRefresh.ts
- src hot paths: quotaCache.ts, connectionProvider.ts, quotaCombos.ts,
  quotaKey.ts, saturationSignals.ts, tokenHealthCheck.ts,
  providerHealthAutopilot.ts, claudeAuthFile.ts, codexAuthFile.ts
- Admin routes: providers/[id]/{route,login,models,refresh,sync-models,test}.ts
- Nodes/export/sync: provider-nodes/route.ts, export-json/route.ts,
  sync/bundle.ts, localHealthCheck.ts
- v1 audio/speech/route.ts, transcriptions/route.ts, translations/route.ts
- v1 models/catalog.ts, rerank/route.ts
- embeddings/service.ts, imageRouteModel.ts, memory/embedding/index.ts
- sse/services/auth.ts, model.ts

All cached wrappers use 5s TTL (matching existing pattern).
Cache invalidated on all related writes.

Both core and open-sse typechecks pass (zero errors).
This commit is contained in:
oyi77
2026-07-19 13:08:03 +07:00
parent 698b6eb00d
commit 3b0a3c0637
38 changed files with 139 additions and 91 deletions

View File

@@ -148,7 +148,7 @@ import {
PROVIDER_ERROR_TYPES,
isEmptyContentResponse,
} from "../services/errorClassifier.ts";
import { updateProviderConnection, getProviderConnectionById } from "@/lib/db/providers";
import { updateProviderConnection } from "@/lib/db/providers";
import { wasRefreshTokenRotated } from "@omniroute/open-sse/services/refreshSerializer.ts";
import { connectionHasExtraKeys } from "../services/apiKeyRotator.ts";
import { recordKeyHealthStatus as recordKeyHealthStatusFor } from "./chatCore/keyHealth.ts";
@@ -228,7 +228,7 @@ import {
normalizeExecutorResult,
executeWithUpstreamStartTimeout,
} from "./chatCore/upstreamTimeouts.ts";
import { getModelNormalizeToolCallId, getModelPreserveOpenAIDeveloperRole } from "@/lib/localDb";
import { getModelNormalizeToolCallId, getModelPreserveOpenAIDeveloperRole, getCachedProviderConnectionById } from "@/lib/localDb";
import { getProviderCredentials, extractSessionAffinityKey } from "@/sse/services/auth";
import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity";
import { getCacheControlSettings } from "@/lib/cacheControlSettings";
@@ -3074,7 +3074,7 @@ export async function handleChatCore({
typeof credentials?.connectionId === "string" ? credentials.connectionId.trim() : "";
const casReread = casConnectionId
? async () => {
const latest = await getProviderConnectionById(casConnectionId);
const latest = await getCachedProviderConnectionById(casConnectionId);
return typeof latest?.refreshToken === "string" ? latest.refreshToken : null;
}
: null;
@@ -3165,7 +3165,7 @@ export async function handleChatCore({
let alreadyRotated = false;
if (typeof connectionId === "string" && connectionId && attemptedRefreshToken) {
try {
const latest = await getProviderConnectionById(connectionId);
const latest = await getCachedProviderConnectionById(connectionId);
if (wasRefreshTokenRotated(attemptedRefreshToken, latest?.refreshToken)) {
alreadyRotated = true;
log?.warn?.(

View File

@@ -1,5 +1,6 @@
import { getCodexModelScope } from "../../config/codexQuotaScopes.ts";
import { getProviderConnectionById, updateProviderConnection } from "@/lib/db/providers";
import { updateProviderConnection } from "@/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
type CodexFailoverCredentials = {
connectionId?: string | null;
@@ -16,7 +17,7 @@ export async function markCodexScopeRateLimited(params: {
rateLimitedUntil: string;
credentials?: CodexFailoverCredentials | null;
}): Promise<void> {
const connection = await getProviderConnectionById(params.failedConnectionId).catch(() => null);
const connection = await getCachedProviderConnectionById(params.failedConnectionId).catch(() => null);
const existingProviderData = connection
? asProviderData(connection.providerSpecificData)
: asProviderData(params.credentials?.providerSpecificData);

View File

@@ -19,14 +19,14 @@
* shrinking (Quality Gate / #3501).
*/
import { getProviderConnectionById } from "../../../src/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { effectiveMaxConcurrency } from "./comboPredicates.ts";
import type { ResolvedComboTarget } from "./types.ts";
/** Read a connection's positive `maxConcurrent`, or null when unset / <= 0 / on error. */
export async function lookupPositiveCap(connectionId: string): Promise<number | null> {
try {
const conn = await getProviderConnectionById(connectionId);
const conn = await getCachedProviderConnectionById(connectionId);
const raw = (conn as { maxConcurrent?: number | null } | null)?.maxConcurrent;
return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
} catch {

View File

@@ -19,7 +19,7 @@ import {
type PreflightQuotaThresholds,
type QuotaInfo,
} from "../quotaPreflight.ts";
import { getProviderConnectionById } from "../../../src/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import {
resolveResilienceSettings,
type ResilienceSettings,
@@ -108,7 +108,7 @@ export async function resolveQuotaExhaustionCutoffForTarget(
let connection: Record<string, unknown> | undefined;
try {
connection = (await getProviderConnectionById(connectionId)) as
connection = (await getCachedProviderConnectionById(connectionId)) as
| Record<string, unknown>
| undefined;
} catch {

View File

@@ -1914,8 +1914,8 @@ async function _getAccessTokenWithStalenessCheck(provider, credentials, log, pro
// We MUST check if the DB has a newer token before proceeding with a network refresh.
if (credentials.connectionId) {
try {
const { getProviderConnectionById } = await import("../../src/lib/db/providers");
const dbConnection = await getProviderConnectionById(credentials.connectionId);
const { getCachedProviderConnectionById } = await import("@/lib/localDb");
const dbConnection = await getCachedProviderConnectionById(credentials.connectionId);
if (dbConnection && dbConnection.refreshToken) {
const now = Date.now();
const dbExpiresAt = dbConnection.expiresAt ? new Date(dbConnection.expiresAt).getTime() : 0;

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { createProviderNode, getProviderNodes } from "@/models";
import { createProviderNode } from "@/models";
import { getCachedProviderNodes } from "@/lib/localDb";
import {
OPENAI_COMPATIBLE_PREFIX,
ANTHROPIC_COMPATIBLE_PREFIX,
@@ -36,7 +37,7 @@ function sanitizeClaudeCodeCompatibleBaseUrl(baseUrl: string) {
// GET /api/provider-nodes - List all provider nodes
export async function GET() {
try {
const nodes = await getProviderNodes();
const nodes = await getCachedProviderNodes();
return NextResponse.json({
nodes,
ccCompatibleProviderEnabled: isCcCompatibleProviderEnabled(),

View File

@@ -7,7 +7,7 @@
*/
import { NextRequest, NextResponse } from "next/server";
import { getProviderConnectionById, updateProviderConnection } from "@/lib/localDb";
import { getCachedProviderConnectionById, updateProviderConnection } from "@/lib/localDb";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
@@ -21,7 +21,7 @@ export async function POST(
if (auth) return auth;
const { id } = await params;
const provider = await getProviderConnectionById(id);
const provider = await getCachedProviderConnectionById(id);
if (!provider) {
return NextResponse.json({ success: false, error: "Provider not found" }, { status: 404 });
}

View File

@@ -10,7 +10,7 @@ import { getModelsByProviderId } from "@/shared/constants/models";
import { getStaticModelsForProvider } from "@/lib/providers/staticModels";
import { isProviderBlockedByIdOrAlias } from "@/shared/utils/noAuthProviders";
import {
getProviderConnectionById,
getCachedProviderConnectionById,
getSettings,
getModelIsHidden,
resolveProxyForProvider,
@@ -124,7 +124,7 @@ export async function GET(
const excludeCustom = searchParams.get("excludeCustom") === "true";
const refresh = searchParams.get("refresh") === "true";
const connection = await getProviderConnectionById(id);
const connection = await getCachedProviderConnectionById(id);
if (!connection) {
// #3047 — no-auth providers have no connection rows; serve their catalog by provider id.

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { getProviderConnectionById, updateProviderConnection } from "@/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { updateProviderConnection } from "@/lib/db/providers";
import { getAccessToken, updateProviderCredentials } from "@/sse/services/tokenRefresh";
import { rotationGroupFor } from "@omniroute/open-sse/services/refreshSerializer.ts";
@@ -21,7 +22,7 @@ export async function POST(_request: Request, { params }: { params: Promise<{ id
try {
const { id } = await params;
const connection = await getProviderConnectionById(id);
const connection = await getCachedProviderConnectionById(id);
if (!connection) {
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
}

View File

@@ -5,11 +5,11 @@ import {
summarizeProviderConnectionForAudit,
} from "@/lib/compliance/providerAudit";
import {
getProviderConnectionById,
getCachedProviderConnectionById,
updateProviderConnection,
deleteProviderConnection,
isCloudEnabled,
} from "@/models";
} from "@/lib/localDb";
import { getConsistentMachineId } from "@/shared/utils/machineId";
import { syncToCloud } from "@/lib/cloudSync";
import { updateProviderConnectionSchema } from "@/shared/validation/schemas";
@@ -57,7 +57,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ id:
try {
const { id } = await params;
const connection = await getProviderConnectionById(id);
const connection = await getCachedProviderConnectionById(id);
if (!connection) {
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
@@ -140,7 +140,7 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
rateLimitOverrides,
} = body;
const existing = (await getProviderConnectionById(id)) as Record<string, any> | null;
const existing = (await getCachedProviderConnectionById(id)) as Record<string, any> | null;
if (!existing) {
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
}
@@ -342,7 +342,7 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
const { id } = await params;
// Fetch connection before deleting to check provider type
const connection = (await getProviderConnectionById(id)) as Record<string, any> | null;
const connection = (await getCachedProviderConnectionById(id)) as Record<string, any> | null;
if (!connection) {
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
}

View File

@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import { getProviderConnectionById } from "@/models";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { getSyncedAvailableModelsForConnection } from "@/lib/db/models";
import { selectModelsForImport } from "@/shared/utils/freeModels";
import {
@@ -385,7 +385,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
);
}
const connection = await getProviderConnectionById(id);
const connection = await getCachedProviderConnectionById(id);
if (!connection) {
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
}

View File

@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
import { z } from "zod";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import {
getProviderConnectionById,
getCachedProviderConnectionById,
updateProviderConnection,
isCloudEnabled,
resolveProxyForConnection,
@@ -741,7 +741,7 @@ async function testApiKeyConnection(connection: any) {
* @returns {Promise<object>} Test result (same shape as the JSON response)
*/
export async function testSingleConnection(connectionId: string, validationModelId?: string) {
const connection = await getProviderConnectionById(connectionId);
const connection = await getCachedProviderConnectionById(connectionId);
if (!connection) {
return { valid: false, error: "Connection not found", diagnosis: null, latencyMs: 0 };

View File

@@ -2,9 +2,9 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { consumeCommandCodeAuthSecret } from "@/lib/db/commandCodeAuth";
import {
createProviderConnection,
getProviderConnectionById,
updateProviderConnection,
} from "@/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { sanitizeProviderSpecificDataForResponse } from "@/lib/providers/requestDefaults";
import { commandCodeApplySchema, noStoreJson, stateHashFromState } from "../shared";
@@ -42,7 +42,7 @@ export async function POST(request: Request) {
let existing: Record<string, unknown> | null = null;
if (parsed.data.connectionId) {
existing = (await getProviderConnectionById(parsed.data.connectionId)) as Record<
existing = (await getCachedProviderConnectionById(parsed.data.connectionId)) as Record<
string,
unknown
> | null;

View File

@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
import {
getSettings,
getProviderConnections,
getProviderNodes,
getCachedProviderNodes,
getCombos,
getApiKeys,
} from "@/lib/localDb";
@@ -69,7 +69,7 @@ export async function GET(request: Request) {
const { password: _pw, requireLogin: _rl, ...safeSettings } = rawSettings;
const providerConnections = await getProviderConnections();
const providerNodes = await getProviderNodes();
const providerNodes = await getCachedProviderNodes();
const combosRaw = await getCombos();
const apiKeys = await getApiKeys();

View File

@@ -13,7 +13,7 @@ import {
import { errorResponse } from "@omniroute/open-sse/utils/error.ts";
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import { enforceApiKeyPolicy } from "@/shared/utils/apiKeyPolicy";
import { getProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/localDb";
import { v1AudioSpeechSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import {
@@ -62,7 +62,7 @@ async function postHandler(request, context) {
// Load local provider_nodes for audio routing (only localhost — prevents auth bypass/SSRF)
let dynamicProviders: ReturnType<typeof buildDynamicAudioProvider>[] = [];
try {
const nodes = await getProviderNodes();
const nodes = await getCachedProviderNodes();
dynamicProviders = (Array.isArray(nodes) ? (nodes as unknown as ProviderNodeRow[]) : [])
.filter((n: ProviderNodeRow) => {
if (n.apiType !== "chat" && n.apiType !== "responses") return false;

View File

@@ -14,7 +14,7 @@ import {
import { errorResponse } from "@omniroute/open-sse/utils/error.ts";
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import { enforceApiKeyPolicy } from "@/shared/utils/apiKeyPolicy";
import { getProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/localDb";
import {
isAllRateLimitedCredentials,
rateLimitedProviderResponse,
@@ -60,7 +60,7 @@ export async function POST(request) {
// Load local provider_nodes for audio routing (only localhost — prevents auth bypass/SSRF)
let dynamicProviders: ReturnType<typeof buildDynamicAudioProvider>[] = [];
try {
const nodes = await getProviderNodes();
const nodes = await getCachedProviderNodes();
dynamicProviders = (Array.isArray(nodes) ? (nodes as unknown as ProviderNodeRow[]) : [])
.filter((n: ProviderNodeRow) => {
if (n.apiType !== "chat" && n.apiType !== "responses") return false;

View File

@@ -14,7 +14,7 @@ import {
import { errorResponse } from "@omniroute/open-sse/utils/error.ts";
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import { enforceApiKeyPolicy } from "@/shared/utils/apiKeyPolicy";
import { getProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/localDb";
import {
isAllRateLimitedCredentials,
rateLimitedProviderResponse,
@@ -62,7 +62,7 @@ export async function POST(request) {
// Load local provider_nodes for audio routing (only localhost — prevents auth bypass/SSRF)
let dynamicProviders: ReturnType<typeof buildDynamicAudioProvider>[] = [];
try {
const nodes = await getProviderNodes();
const nodes = await getCachedProviderNodes();
dynamicProviders = (Array.isArray(nodes) ? (nodes as unknown as ProviderNodeRow[]) : [])
.filter((n: ProviderNodeRow) => {
if (n.apiType !== "chat" && n.apiType !== "responses") return false;

View File

@@ -5,7 +5,7 @@ import {
getCombos,
getAllCustomModels,
getSettings,
getProviderNodes,
getCachedProviderNodes,
getModelIsHidden,
getModelAliases,
} from "@/lib/localDb";
@@ -327,7 +327,7 @@ async function buildUnifiedModelsResponseCore(
// Get provider nodes (for compatible providers with custom prefixes)
let providerNodes = [];
try {
providerNodes = await getProviderNodes();
providerNodes = await getCachedProviderNodes();
} catch (e) {
console.log("Could not fetch provider nodes");
}

View File

@@ -10,7 +10,7 @@ import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import { enforceApiKeyPolicy } from "@/shared/utils/apiKeyPolicy";
import { v1RerankSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { getProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/localDb";
import {
isAllRateLimitedCredentials,
rateLimitedProviderResponse,
@@ -73,7 +73,7 @@ async function postHandler(request, context) {
// Load local provider_nodes for rerank routing (localhost only)
let localProviders: ReturnType<typeof buildDynamicRerankProvider>[] = [];
try {
const nodes = await getProviderNodes();
const nodes = await getCachedProviderNodes();
localProviders = (Array.isArray(nodes) ? nodes : [])
.filter((n: any) => {
try {

View File

@@ -14,7 +14,7 @@
*/
import { getUsageForProvider } from "@omniroute/open-sse/services/usage.ts";
import { getProviderConnectionById, resolveProxyForConnection } from "@/lib/localDb";
import { getCachedProviderConnectionById, resolveProxyForConnection } from "@/lib/localDb";
import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts";
import { safePercentage } from "@/shared/utils/formatting";
import {
@@ -453,7 +453,7 @@ async function refreshEntry(entry: QuotaCacheEntry) {
refreshingSet.add(entry.connectionId);
try {
const connection = await getProviderConnectionById(entry.connectionId);
const connection = await getCachedProviderConnectionById(entry.connectionId);
if (!connection || connection.authType !== "oauth" || !connection.isActive) {
cache.delete(entry.connectionId);
return;

View File

@@ -6,6 +6,7 @@ import { v4 as uuidv4 } from "uuid";
import { getDbInstance, rowToCamel } from "../core";
import { selectProviderNodeForConnection } from "../providerNodeSelect";
import { backupDbFile } from "../backup";
import { invalidateDbCache } from "../readCache";
import { toRecord, type JsonRecord } from "./columns";
interface StatementLike<TRow = unknown> {
@@ -79,6 +80,7 @@ export async function createProviderNode(data: JsonRecord) {
).run(node);
backupDbFile("pre-write");
invalidateDbCache("nodes");
const result: JsonRecord = { ...node };
if (customHeadersJson) {
@@ -142,6 +144,7 @@ export async function updateProviderNode(id: string, data: JsonRecord) {
});
backupDbFile("pre-write");
invalidateDbCache("nodes");
const result: JsonRecord = { ...merged };
const storedJson = merged["customHeadersJson"] as string | null;
@@ -165,5 +168,6 @@ export async function deleteProviderNode(id: string) {
db.prepare("DELETE FROM provider_nodes WHERE id = ?").run(id);
backupDbFile("pre-write");
invalidateDbCache("nodes");
return rowToCamel(existing);
}

View File

@@ -107,6 +107,43 @@ export async function getCachedProviderConnections(
connectionsCache.set("all", value);
return value;
}
const connectionByIdCache = new TTLCache<Record<string, unknown> | null>(CONNECTIONS_TTL_MS);
const nodesCache = new TTLCache<unknown[]>(CONNECTIONS_TTL_MS);
/**
* Cached wrapper for getProviderConnectionById.
* Keyed by connection ID, shared 5s TTL.
* Invalidated on every provider_connections write.
*/
export async function getCachedProviderConnectionById(
id: string
): Promise<Record<string, unknown> | null> {
const cached = connectionByIdCache.get(id);
if (cached !== undefined) return cached;
const { getProviderConnectionById } = await import("@/lib/db/providers");
const value = await getProviderConnectionById(id);
connectionByIdCache.set(id, value);
return value;
}
/**
* Cached wrapper for getProviderNodes.
* Keyed by JSON-serialized filter, shared 5s TTL.
* Invalidated on every provider_nodes write.
*/
export async function getCachedProviderNodes(
filter?: Record<string, unknown>
): Promise<unknown[]> {
const cacheKey = filter ? JSON.stringify(filter) : "all";
const cached = nodesCache.get(cacheKey);
if (cached) return cached;
const { getProviderNodes } = await import("@/lib/db/providers");
const value = await getProviderNodes(filter);
nodesCache.set(cacheKey, value);
return value;
}
// ──────────────── LKGP Cache Wrappers ────────────────
@@ -188,12 +225,18 @@ export function getModelCatalogCacheVersion(): number {
/**
* Invalidate all caches (call after writes to any of: settings, pricing,
* connections, combos).
* connections, combos, nodes).
*/
export function invalidateDbCache(scope?: "settings" | "pricing" | "connections" | "combos"): void {
export function invalidateDbCache(
scope?: "settings" | "pricing" | "connections" | "combos" | "nodes"
): void {
if (!scope || scope === "settings") settingsCache.invalidate();
if (!scope || scope === "pricing") pricingCache.invalidate();
if (!scope || scope === "connections") connectionsCache.invalidate();
if (!scope || scope === "connections") {
connectionsCache.invalidate();
connectionByIdCache.invalidate();
}
if (!scope || scope === "nodes") nodesCache.invalidate();
if (!scope || scope === "combos") combosCacheVersion++;
// Settings/connections/combos all feed the unified model catalog builder
// (blockedProviders + hidePaidModels, provider connections + excludedModels,

View File

@@ -11,7 +11,7 @@ import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import * as log from "@/sse/utils/logger";
import { toJsonErrorPayload } from "@/shared/utils/upstreamError";
import { getProviderCredentials, clearRecoveredProviderState } from "@/sse/services/auth";
import { getProviderNodes, getComboByName, getCombos, getDatabaseSettings } from "@/lib/localDb";
import { getCachedProviderNodes, getComboByName, getCombos, getDatabaseSettings } from "@/lib/localDb";
import { resolveProxyForConnection } from "@/lib/db/settings";
import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts";
import { handleComboChat } from "@omniroute/open-sse/services/combo.ts";
@@ -124,7 +124,7 @@ export async function createEmbeddingResponse(
}
let dynamicProviders: ReturnType<typeof buildDynamicEmbeddingProvider>[] = [];
try {
const nodes = (await getProviderNodes()) as unknown as EmbeddingProviderNodeRow[];
const nodes = (await getCachedProviderNodes()) as unknown as EmbeddingProviderNodeRow[];
dynamicProviders = (Array.isArray(nodes) ? nodes : [])
.filter((n) => {
const validTypes = ["chat", "responses", "embeddings"];
@@ -163,7 +163,7 @@ export async function createEmbeddingResponse(
if (!providerConfig) {
try {
const allNodes = (await getProviderNodes()) as unknown as EmbeddingProviderNodeRow[];
const allNodes = (await getCachedProviderNodes()) as unknown as EmbeddingProviderNodeRow[];
const matchingNode = (Array.isArray(allNodes) ? allNodes : []).find(
(n) =>
n.prefix === provider &&

View File

@@ -18,7 +18,7 @@ import { parseImageModel } from "@omniroute/open-sse/config/imageRegistry.ts";
import { resolveComboTargets } from "@omniroute/open-sse/services/combo.ts";
import { getComboByName, getCombos } from "@/lib/db/combos";
import { getProviderNodes } from "@/lib/db/providers";
import { getCachedProviderNodes } from "@/lib/localDb";
/**
* Rewrite a `prefix/model` custom image model to its internal `<nodeId>/<model>` form.
@@ -36,7 +36,7 @@ export async function resolveImageModelPrefix(modelStr: string): Promise<string>
if (!rest) return modelStr;
try {
const nodes = await getProviderNodes({ type: "openai-compatible" });
const nodes = await getCachedProviderNodes({ type: "openai-compatible" });
// node.id (internal UUID) is already a valid internal id; only rewrite when a
// user-defined prefix differs from the node id.
const matched = nodes.find((node: { prefix?: unknown }) => node.prefix === prefixPart);

View File

@@ -29,10 +29,6 @@ export {
// T05: Rate-limit DB persistence (survives token refresh)
setConnectionRateLimitUntil,
isConnectionRateLimited,
getRateLimitedConnections,
// T05 startup recovery: clear stale transient cooldowns left by a prior crash
clearStaleCrashCooldowns,
// T13: Stale quota display fix (zero out usage after window resets)
@@ -238,6 +234,8 @@ export {
getCachedSettings,
getCachedPricing,
getCachedProviderConnections,
getCachedProviderConnectionById,
getCachedProviderNodes,
getCachedLKGP,
setCachedLKGP,
invalidateDbCache,

View File

@@ -11,7 +11,7 @@
* Uses Promise.allSettled so one slow/down node doesn't block others.
*/
import { getProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/localDb";
// ── Types ────────────────────────────────────────────────────────────────
@@ -159,7 +159,7 @@ export async function sweep(): Promise<void> {
try {
let nodes: Array<{ id: string; prefix: string; baseUrl: string }>;
try {
const raw = await getProviderNodes();
const raw = await getCachedProviderNodes();
nodes = (Array.isArray(raw) ? raw : []).filter(
(n: Record<string, unknown>) =>
typeof n.baseUrl === "string" && isLocalhostUrl(n.baseUrl as string)

View File

@@ -4,7 +4,7 @@ import {
type EmbeddingProviderNodeRow,
} from "@omniroute/open-sse/config/embeddingRegistry.ts";
import { getProviderCredentials } from "@/sse/services/auth";
import { getProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/localDb";
import type { MemorySettingsExtended } from "@/shared/schemas/memory";
import type {
EmbeddingResolution,
@@ -217,7 +217,7 @@ export async function listEmbeddingProviders(): Promise<EmbeddingProviderListing
// Get dynamic local providers
let dynamicProviders: ReturnType<typeof buildDynamicEmbeddingProvider>[] = [];
try {
const nodes = (await getProviderNodes()) as unknown as EmbeddingProviderNodeRow[];
const nodes = (await getCachedProviderNodes()) as unknown as EmbeddingProviderNodeRow[];
dynamicProviders = (Array.isArray(nodes) ? nodes : [])
.filter((n) => {
const validTypes = ["chat", "responses", "embeddings"];

View File

@@ -1,10 +1,10 @@
import { createHash } from "crypto";
import {
getProviderConnectionById,
getProviderConnections,
updateProviderConnection,
} from "@/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { clearProviderFailure, clearModelLock } from "@omniroute/open-sse/services/accountFallback";
type JsonRecord = Record<string, unknown>;
@@ -672,7 +672,7 @@ export async function executeProviderHealthAutopilotAction(
if (!connectionId) {
return { status: 400, body: { success: false, error: "connectionId is required" } };
}
const connection = (await getProviderConnectionById(connectionId)) as JsonRecord | null;
const connection = (await getCachedProviderConnectionById(connectionId)) as JsonRecord | null;
if (!connection || connection.provider !== provider) {
return { status: 404, body: { success: false, error: "connection not found" } };
}
@@ -692,7 +692,7 @@ export async function executeProviderHealthAutopilotAction(
body: { success: false, error: "connectionId and model are required" },
};
}
const connection = (await getProviderConnectionById(connectionId)) as JsonRecord | null;
const connection = (await getCachedProviderConnectionById(connectionId)) as JsonRecord | null;
if (!connection || connection.provider !== provider) {
return { status: 404, body: { success: false, error: "connection not found" } };
}
@@ -715,7 +715,7 @@ export async function executeProviderHealthAutopilotAction(
if (!connectionId) {
return { status: 400, body: { success: false, error: "connectionId is required" } };
}
const connection = (await getProviderConnectionById(connectionId)) as JsonRecord | null;
const connection = (await getCachedProviderConnectionById(connectionId)) as JsonRecord | null;
if (!connection || connection.provider !== provider) {
return { status: 404, body: { success: false, error: "connection not found" } };
}

View File

@@ -1,6 +1,6 @@
import fs from "fs/promises";
import path from "path";
import { getProviderConnectionById } from "@/lib/localDb";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { createBackup } from "@/shared/services/backupService";
import { getCliConfigPaths } from "@/shared/services/cliRuntime";
import {
@@ -172,7 +172,7 @@ export function buildClaudeAuthPayload(connection: ClaudeConnectionLike): Claude
}
async function resolveFreshClaudeConnection(connectionId: string): Promise<ClaudeConnectionLike> {
const connection = (await getProviderConnectionById(connectionId)) as ClaudeConnectionLike | null;
const connection = (await getCachedProviderConnectionById(connectionId)) as ClaudeConnectionLike | null;
if (!connection) {
throw new ClaudeAuthFileError("Connection not found", 404, "not_found");
}

View File

@@ -1,6 +1,6 @@
import fs from "fs/promises";
import path from "path";
import { getProviderConnectionById } from "@/lib/localDb";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { createBackup } from "@/shared/services/backupService";
import { getCliConfigPaths } from "@/shared/services/cliRuntime";
import {
@@ -197,7 +197,7 @@ function buildCodexAuthPayload(connection: CodexConnectionLike): CodexAuthFilePa
}
async function resolveFreshCodexConnection(connectionId: string): Promise<CodexConnectionLike> {
const connection = (await getProviderConnectionById(connectionId)) as CodexConnectionLike | null;
const connection = (await getCachedProviderConnectionById(connectionId)) as CodexConnectionLike | null;
if (!connection) {
throw new CodexAuthFileError("Connection not found", 404, "not_found");
}

View File

@@ -18,9 +18,9 @@
export async function resolveConnectionProvider(connectionId: string): Promise<string> {
try {
// Lazy import — avoids circular deps and keeps the module loadable without a full DB.
const { getProviderConnectionById } = await import("@/lib/localDb");
if (typeof getProviderConnectionById === "function") {
const conn = await getProviderConnectionById(connectionId);
const { getCachedProviderConnectionById } = await import("@/lib/localDb");
if (typeof getCachedProviderConnectionById === "function") {
const conn = await getCachedProviderConnectionById(connectionId);
if (conn && typeof (conn as { provider?: string }).provider === "string") {
return (conn as { provider: string }).provider;
}

View File

@@ -12,7 +12,7 @@
import { getPool } from "@/lib/db/quotaPools";
import { getGroupName } from "@/lib/db/quotaGroups";
import { getProviderConnectionById } from "@/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import {
getCombos,
createCombo,
@@ -152,7 +152,7 @@ export async function syncQuotaCombos(poolId: string): Promise<void> {
for (const connId of pool.connectionIds) {
let connection: Record<string, unknown> | null = null;
try {
connection = (await getProviderConnectionById(connId)) as Record<string, unknown> | null;
connection = (await getCachedProviderConnectionById(connId)) as Record<string, unknown> | null;
} catch {
// Connection lookup failure — skip this connection.
continue;
@@ -361,7 +361,7 @@ export async function removeQuotaCombosForPool(poolId: string): Promise<void> {
let poolProvider: string | undefined;
for (const connId of pool.connectionIds) {
try {
const connection = (await getProviderConnectionById(connId)) as Record<
const connection = (await getCachedProviderConnectionById(connId)) as Record<
string,
unknown
> | null;

View File

@@ -8,7 +8,7 @@
*/
import { getPool, getPoolsByGroup } from "@/lib/db/quotaPools";
import { getProviderConnectionById } from "@/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { getApiKeyById, updateApiKeyPermissions } from "@/lib/db/apiKeys";
import { quotaGroupSlug } from "./quotaModelNaming";
import { getGroupName } from "@/lib/db/quotaGroups";
@@ -114,7 +114,7 @@ export async function resolveQuotaKeyScope(
: [groupPool.connectionId];
for (const connId of connIds) {
const connection = await getProviderConnectionById(connId);
const connection = await getCachedProviderConnectionById(connId);
if (!connection) continue; // missing connection contributes nothing
const provider = (connection as Record<string, unknown>).provider;

View File

@@ -368,13 +368,13 @@ export function __setAnthropicSaturationDepsForTests(
}
async function defaultAnthropicDeps(): Promise<AnthropicSaturationDeps> {
const [providersMod, usageMod] = await Promise.all([
import("@/lib/db/providers"),
const [localDbMod, usageMod] = await Promise.all([
import("@/lib/localDb"),
import("@omniroute/open-sse/services/usage"),
]);
return {
loadConnection: (connectionId) =>
providersMod.getProviderConnectionById(connectionId) as Promise<Record<
localDbMod.getCachedProviderConnectionById(connectionId) as Promise<Record<
string,
unknown
> | null>,

View File

@@ -4,7 +4,7 @@ import {
getCombos,
getModelAliases,
getProviderConnections,
getProviderNodes,
getCachedProviderNodes,
getSettings,
} from "@/lib/localDb";
@@ -159,7 +159,7 @@ export async function buildConfigSyncBundle(): Promise<ConfigSyncBundle> {
await Promise.all([
getSettings(),
getProviderConnections(),
getProviderNodes(),
getCachedProviderNodes(),
getModelAliases(),
getCombos(),
getApiKeys(),

View File

@@ -13,7 +13,7 @@
import {
getProviderConnections,
getProviderConnectionById,
getCachedProviderConnectionById,
updateProviderConnection,
getSettings,
resolveProxyForConnection,
@@ -355,7 +355,7 @@ async function sweep() {
export async function checkConnection(conn) {
if (!conn?.id) return;
const latestConnection = (await getProviderConnectionById(conn.id)) || conn;
const latestConnection = (await getCachedProviderConnectionById(conn.id)) || conn;
conn = latestConnection;
// Per-provider opt-out of proactive refresh (e.g. Codex/OpenAI cascade
@@ -644,7 +644,7 @@ export async function checkConnection(conn) {
// Once used, the old token is permanently invalidated.
// Retrying will never succeed → deactivate and stop the loop.
if (isUnrecoverableRefreshError(result)) {
const currentConnection = await getProviderConnectionById(conn.id);
const currentConnection = await getCachedProviderConnectionById(conn.id);
const credentialsChangedSinceSweep =
!!currentConnection &&
(currentConnection.refreshToken !== attemptedRefreshToken ||
@@ -759,7 +759,7 @@ export async function checkConnection(conn) {
// providerSpecificData.copilotTokenExpiresAt (Unix seconds).
if (String(conn.provider || "").toLowerCase() === "github") {
// Re-read the latest connection after the OAuth refresh (onPersist may have updated it).
const latestConn = (await getProviderConnectionById(conn.id).catch(() => null)) || conn;
const latestConn = (await getCachedProviderConnectionById(conn.id).catch(() => null)) || conn;
const accessTokenForCopilot = result.accessToken || latestConn.accessToken;
if (accessTokenForCopilot) {

View File

@@ -1,7 +1,7 @@
import { randomUUID, createHash } from "crypto";
import {
getProviderConnections,
getProviderNodes,
getCachedProviderNodes,
validateApiKey,
updateProviderConnection,
clearConnectionErrorIfUnchanged,
@@ -984,7 +984,7 @@ async function getProviderSearchPool(provider: string): Promise<string[]> {
// (for example "78code/gpt-5.4"), but live credentials are stored under
// internal provider ids like openai-compatible-responses-<uuid>.
try {
const providerNodes = await getProviderNodes();
const providerNodes = await getCachedProviderNodes();
for (const node of Array.isArray(providerNodes) ? providerNodes : []) {
const nodeRecord = asRecord(node);
const nodePrefix = typeof nodeRecord.prefix === "string" ? nodeRecord.prefix.trim() : "";

View File

@@ -4,7 +4,7 @@ import {
getComboByName,
getComboById,
getComboByNameInsensitive,
getProviderNodes,
getCachedProviderNodes,
getCustomModels,
} from "@/lib/localDb";
import { getCachedSettings } from "@/lib/localDb";
@@ -144,7 +144,7 @@ export async function getModelInfo(modelStr) {
// Match by node.prefix (user-defined alias) OR node.id (internal UUID id stored by
// combo steps), so that combo targets using the internal node id still resolve
// correctly (#2778).
const openaiNodes = await getProviderNodes({ type: "openai-compatible" });
const openaiNodes = await getCachedProviderNodes({ type: "openai-compatible" });
const matchedOpenAI = openaiNodes.find(
(node) => node.prefix === prefixToCheck || node.id === prefixToCheck
);
@@ -167,7 +167,7 @@ export async function getModelInfo(modelStr) {
}
// Check Anthropic Compatible nodes
const anthropicNodes = await getProviderNodes({ type: "anthropic-compatible" });
const anthropicNodes = await getCachedProviderNodes({ type: "anthropic-compatible" });
const matchedAnthropic = anthropicNodes.find(
(node) => node.prefix === prefixToCheck || node.id === prefixToCheck
);