fix(barrel): migrate src/lib/ off the localDb barrel to direct db imports (#59) (#12052)

Boarded together with Phases 2, 4, 5 (#12051, #12053, #12055) and validated in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles, check-db-rules all green. Mechanical import-path migration only, no behavior change. Thanks for the phased, well-tested cleanup.
This commit is contained in:
Webman
2026-08-30 00:27:25 -05:00
committed by GitHub
parent aa861a80d2
commit 2463781e00
47 changed files with 173 additions and 335 deletions

View File

@@ -1539,11 +1539,6 @@
"count": 1
}
},
"src/lib/api/proxyRegistryRouteHandlers.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/cli-helper/config-generator/claude.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
@@ -1584,16 +1579,6 @@
"count": 2
}
},
"src/lib/cloudSync.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/combos/builderOptions.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/combos/controlCenter.ts": {
"no-restricted-syntax": {
"count": 1
@@ -1604,11 +1589,6 @@
"count": 1
}
},
"src/lib/container.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/copilot/engine.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 2
@@ -1622,9 +1602,6 @@
"src/lib/credentialHealth/scheduler.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
},
"src/lib/db/apiKeys.ts": {
@@ -1747,21 +1724,6 @@
"count": 1
}
},
"src/lib/evals/runtime.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/freeProxyProviders/scheduler.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/freeProxyProviders/syncCycle.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/guardrails/promptInjection.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 2
@@ -1772,26 +1734,11 @@
"count": 1
}
},
"src/lib/idempotencyLayer.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/images/imageRouteModel.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/jobRegistry/index.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
}
},
"src/lib/localHealthCheck.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/memory/__tests__/generic-backend.test.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 4
@@ -1802,21 +1749,11 @@
"count": 1
}
},
"src/lib/memory/embedding/index.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/memory/genericBackend.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
}
},
"src/lib/memory/reindex.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/memory/retrieval.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
@@ -1827,16 +1764,6 @@
"count": 2
}
},
"src/lib/memory/store.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/memory/vectorStore.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/middleware/registry.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 2
@@ -1851,9 +1778,6 @@
"@typescript-eslint/no-unused-vars": {
"count": 1
},
"no-restricted-imports": {
"count": 1
},
"no-restricted-syntax": {
"count": 1
}
@@ -1913,32 +1837,9 @@
"count": 3
}
},
"src/lib/oauth/utils/agyAuthImport.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/oauth/utils/claudeAuthFile.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/oauth/utils/claudeAuthImport.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
},
"src/lib/oauth/utils/codexAuthFile.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/oauth/utils/codexAuthImport.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/obsidian/api.ts": {
@@ -1966,11 +1867,6 @@
"count": 2
}
},
"src/lib/providerModels/managedAvailableModels.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/providers/validation.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
@@ -1986,45 +1882,19 @@
"count": 1
}
},
"src/lib/proxyHealth/scheduler.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/quota/planResolver.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/quota/quotaAdapters.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
}
},
"src/lib/quota/quotaCombos.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/quota/quotaKey.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/quota/redisQuotaStore.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
},
"src/lib/quota/sqliteQuotaStore.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
},
"src/lib/semanticCache.ts": {
@@ -2037,11 +1907,6 @@
"count": 1
}
},
"src/lib/services/quotaAutoPing.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/skills/a2a.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 2
@@ -2082,11 +1947,6 @@
"count": 1
}
},
"src/lib/sync/bundle.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/tailscaleTunnel.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1
@@ -2097,16 +1957,6 @@
"count": 1
}
},
"src/lib/tokenHealthCheck.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/tokenHealthCheckCopilot.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/usage/apiKeySelfService.ts": {
"no-restricted-syntax": {
"count": 1
@@ -2122,11 +1972,6 @@
"count": 1
}
},
"src/lib/usage/codexResetCredits.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/usage/costCalculator.ts": {
"no-restricted-syntax": {
"count": 1
@@ -2170,11 +2015,6 @@
"count": 1
}
},
"src/lib/ws/handshake.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/zed-oauth/keychain-reader.ts": {
"@typescript-eslint/no-unused-vars": {
"count": 1

View File

@@ -120,7 +120,7 @@ export interface ProviderDiscoveryResult {
export async function executeProviderDiscovery(task: A2ATask): Promise<ProviderDiscoveryResult> {
const [{ getProviderConnections }, { getAllCircuitBreakerStatuses }] = await Promise.all([
import("@/lib/localDb"),
import("@/lib/db/providers"),
import("@/shared/utils/circuitBreaker"),
]);

View File

@@ -6,7 +6,7 @@ import {
getProxyWhereUsed,
updateProxy,
updateProxyAndAssign,
} from "@/lib/localDb";
} from "@/lib/db/proxies";
import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse";
import { createProxyRegistrySchema, updateProxyRegistrySchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";

View File

@@ -1,5 +1,5 @@
import crypto from "crypto";
import { getProviderConnections, updateProviderConnection } from "@/lib/localDb";
import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers";
import { buildConfigSyncEnvelope, toLegacyCloudSyncPayload } from "@/lib/sync/bundle";
const CLOUD_URL = process.env.CLOUD_URL || process.env.NEXT_PUBLIC_CLOUD_URL;

View File

@@ -1,12 +1,7 @@
import {
getAllCustomModels,
getAllSyncedAvailableModels,
getCombos,
getModelIsHidden,
getProviderConnections,
getProviderNodes,
getSettings,
} from "@/lib/localDb";
import { getAllCustomModels, getAllSyncedAvailableModels, getModelIsHidden } from "@/lib/db/models";
import { getCombos } from "@/lib/db/combos";
import { getProviderConnections, getProviderNodes } from "@/lib/db/providers";
import { getSettings } from "@/lib/db/settings";
import { getAccountDisplayName, getProviderDisplayName } from "@/lib/display/names";
import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels";
import { getResolvedModelCapabilities } from "@/lib/modelCapabilities";

View File

@@ -23,7 +23,7 @@ import {
encrypt,
encryptConnectionFields,
} from "./db/encryption.ts";
import { getSettings } from "./localDb.ts";
import { getSettings } from "@/lib/db/settings";
import { getCircuitBreaker } from "../shared/utils/circuitBreaker.ts";
import { recordTelemetry, RequestTelemetry } from "../shared/utils/requestTelemetry.ts";

View File

@@ -18,7 +18,7 @@
*/
import { testSingleConnection } from "@/app/api/providers/[id]/test/route";
import { getProviderConnections } from "@/lib/localDb";
import { getProviderConnections } from "@/lib/db/providers";
import {
setCredentialHealth,
removeCredentialHealth,

View File

@@ -1,7 +1,8 @@
import { POST as postChatCompletion } from "@/app/api/v1/chat/completions/route";
import type { PersistedEvalRun, EvalTargetType } from "@/lib/db/evals";
import { saveEvalRun } from "@/lib/db/evals";
import { getApiKeyById, getCombos } from "@/lib/localDb";
import { getApiKeyById } from "@/lib/db/apiKeys";
import { getCombos } from "@/lib/db/combos";
import { getSuite, listSuites, runSuite } from "./evalRunner";
export interface EvalTargetInput {

View File

@@ -22,7 +22,7 @@
*/
import { getEnabledProviders } from "@/lib/freeProxyProviders";
import { getFreeProxyStats } from "@/lib/localDb";
import { getFreeProxyStats } from "@/lib/db/freeProxies";
import { runFreeProxySyncCycle, type FreeProxySyncCycleResult } from "./syncCycle";
const STARTUP_DELAY_MS = 5_000;

View File

@@ -3,7 +3,7 @@ import {
recordFreeProxySync,
clearFreeProxySyncErrors,
recordFreeProxySyncErrors,
} from "@/lib/localDb";
} from "@/lib/db/freeProxies";
import type { FreeProxyProvider } from "@/lib/freeProxyProviders/types";
export interface FreeProxySyncCycleResult {

View File

@@ -53,7 +53,7 @@ export async function getComboVisionBridgeDecision(
model: string
): Promise<ComboVisionBridgeDecision> {
try {
const { getComboByName } = await import("@/lib/localDb");
const { getComboByName } = await import("@/lib/db/combos");
const { resolveComboForModel } = await import("@/lib/db/modelComboMappings");
// 1. Try to find combo by exact name match

View File

@@ -10,7 +10,7 @@
* @module lib/idempotencyLayer
*/
import { getSettings } from "@/lib/localDb";
import { getSettings } from "@/lib/db/settings";
const DEFAULT_WINDOW_MS = 5000;

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 { getCachedProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/db/readCache";
import { assertMicrosoftDesignerWebProviderAvailable } from "@/shared/constants/designerWebRetirement";
import { assertCommonChatGptWebModelAvailable } from "@/shared/constants/chatgptWebRetirement";

View File

@@ -11,7 +11,7 @@
* Uses Promise.allSettled so one slow/down node doesn't block others.
*/
import { getCachedProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/db/readCache";
import { isAutomatedTestProcess } from "@/shared/utils/testProcess";
// ── Types ────────────────────────────────────────────────────────────────
@@ -38,7 +38,6 @@ function isBuildProcess(): boolean {
return typeof process !== "undefined" && process.env.NEXT_PHASE === "phase-production-build";
}
// ── State (globalThis survives HMR re-evaluation) ───────────────────────
declare global {

View File

@@ -5,7 +5,7 @@ import {
type EmbeddingProviderNodeRow,
} from "@omniroute/open-sse/config/embeddingRegistry.ts";
import { getProviderCredentials } from "@/sse/services/auth";
import { getCachedProviderNodes } from "@/lib/localDb";
import { getCachedProviderNodes } from "@/lib/db/readCache";
import type { MemorySettingsExtended } from "@/shared/schemas/memory";
import type {
EmbeddingResolution,

View File

@@ -7,7 +7,7 @@ import {
getMemoryReindexQueue,
countMemoryReindexPending,
markMemoryNeedsReindex,
} from "@/lib/localDb";
} from "@/lib/db/memoryVec";
import { resolveEmbeddingSource, embed } from "./embedding";
import { getVectorStore } from "./vectorStore";
import { getMemorySettings } from "./settings";
@@ -23,9 +23,7 @@ const log = logger("MEMORY_REINDEX");
*
* @returns { processed: number; errors: number }
*/
export async function runReindexBatch(
limit = 100
): Promise<{ processed: number; errors: number }> {
export async function runReindexBatch(limit = 100): Promise<{ processed: number; errors: number }> {
const queue = getMemoryReindexQueue(limit);
if (queue.length === 0) {

View File

@@ -10,7 +10,7 @@ import { sanitizeErrorMessage } from "../../../open-sse/utils/error.ts";
import { resolveEmbeddingSource, embed } from "./embedding";
import { getVectorStore } from "./vectorStore";
import { getMemorySettings } from "./settings";
import { markMemoryNeedsReindex } from "@/lib/localDb";
import { markMemoryNeedsReindex } from "@/lib/db/memoryVec";
const log = logger("MEMORY_STORE");

View File

@@ -14,7 +14,7 @@ import {
setMemoryVecMeta,
markAllMemoriesNeedReindex,
countMemoryReindexPending,
} from "../localDb";
} from "@/lib/db/memoryVec";
import { getDbInstance } from "../db/core";
import { logger } from "../../../open-sse/utils/logger.ts";
import { sanitizeErrorMessage } from "../../../open-sse/utils/error.ts";
@@ -54,7 +54,7 @@ export interface VectorStore {
vector: Float32Array,
queryText: string,
topK: number,
apiKeyId?: string,
apiKeyId?: string
): Promise<HybridRrfHit[]>;
/** Stats for UI Engine status. */
stats(): Promise<{
@@ -151,7 +151,7 @@ function recreateFromMeta(): boolean {
const db = getDbInstance();
const q = storedVecQuantization(meta.embeddingSignature);
db.exec(
`CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(meta.activeDim, q)})`,
`CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(meta.activeDim, q)})`
);
return true;
}
@@ -189,7 +189,7 @@ class VectorStoreImpl implements VectorStore {
const q = storedVecQuantization(meta.embeddingSignature);
try {
db.exec(
`CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(dim, q)})`,
`CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(dim, q)})`
);
setMemoryVecMeta({ vecLoaded: true, activeDim: dim });
return { ready: true, reason: `vec_memories created with dim=${dim} (${q})` };
@@ -207,8 +207,7 @@ class VectorStoreImpl implements VectorStore {
// Map UUID memoryId → INTEGER rowid (the rowid is used as the FK into vec_memories).
const row = db.prepare("SELECT rowid FROM memories WHERE id = ?").get(memoryId) as
| { rowid: number }
| undefined;
{ rowid: number } | undefined;
if (!row) {
throw new Error(`memory not found: ${memoryId}`);
@@ -222,7 +221,7 @@ class VectorStoreImpl implements VectorStore {
db.prepare("DELETE FROM vec_memories WHERE rowid = ?").run(BigInt(row.rowid));
db.prepare(`INSERT INTO vec_memories(rowid, embedding) VALUES (?, ${vecValueExpr(q)})`).run(
BigInt(row.rowid),
encodeVector(vector),
encodeVector(vector)
);
} catch (err: unknown) {
// Self-heal: a concurrent ensureReady()'s reset raced ahead of us and
@@ -232,7 +231,7 @@ class VectorStoreImpl implements VectorStore {
db.prepare("DELETE FROM vec_memories WHERE rowid = ?").run(BigInt(row.rowid));
db.prepare(`INSERT INTO vec_memories(rowid, embedding) VALUES (?, ${vecValueExpr(q)})`).run(
BigInt(row.rowid),
encodeVector(vector),
encodeVector(vector)
);
}
}
@@ -241,7 +240,7 @@ class VectorStoreImpl implements VectorStore {
const db = getDbInstance();
try {
db.prepare(
"DELETE FROM vec_memories WHERE rowid = (SELECT rowid FROM memories WHERE id = ?)",
"DELETE FROM vec_memories WHERE rowid = (SELECT rowid FROM memories WHERE id = ?)"
).run(memoryId);
} catch (err: unknown) {
if (!isMissingVecTableError(err)) throw err;
@@ -254,7 +253,7 @@ class VectorStoreImpl implements VectorStore {
async searchVector(
vector: Float32Array,
topK: number,
apiKeyId?: string,
apiKeyId?: string
): Promise<VectorSearchHit[]> {
const db = getDbInstance();
const k = topK > 0 ? topK : TOP_K_DEFAULT;
@@ -268,12 +267,12 @@ class VectorStoreImpl implements VectorStore {
WHERE v.embedding MATCH ${vecValueExpr(q)}
AND ($apiKeyId IS NULL OR m.api_key_id = $apiKeyId)
AND k = ?
ORDER BY v.distance ASC`,
ORDER BY v.distance ASC`
)
.all(encodeVector(vector), { apiKeyId: apiKeyId ?? null }, k) as Array<{
memory_id: string;
distance: number;
}>;
memory_id: string;
distance: number;
}>;
return rows.map((r) => ({
memoryId: r.memory_id,
@@ -286,7 +285,7 @@ class VectorStoreImpl implements VectorStore {
vector: Float32Array,
queryText: string,
topK: number,
apiKeyId?: string,
apiKeyId?: string
): Promise<HybridRrfHit[]> {
const db = getDbInstance();
const k = topK > 0 ? topK : TOP_K_DEFAULT;
@@ -338,23 +337,16 @@ class VectorStoreImpl implements VectorStore {
SELECT memory_id, vec_rank, fts_rank, vec_distance, fts_score, rrf_score
FROM fused
ORDER BY rrf_score DESC
LIMIT ?`,
LIMIT ?`
)
.all(
encodeVector(vector),
{ apiKeyId: apiKeyId ?? null },
k,
queryText,
k,
k,
) as Array<{
memory_id: string;
vec_rank: number | null;
fts_rank: number | null;
vec_distance: number | null;
fts_score: number | null;
rrf_score: number;
}>;
.all(encodeVector(vector), { apiKeyId: apiKeyId ?? null }, k, queryText, k, k) as Array<{
memory_id: string;
vec_rank: number | null;
fts_rank: number | null;
vec_distance: number | null;
fts_score: number | null;
rrf_score: number;
}>;
return rows.map((r) => ({
memoryId: r.memory_id,
@@ -376,8 +368,7 @@ class VectorStoreImpl implements VectorStore {
try {
const db = getDbInstance();
const row = db.prepare("SELECT COUNT(*) AS cnt FROM vec_memories").get() as
| { cnt: number }
| undefined;
{ cnt: number } | undefined;
rowCount = row?.cnt ?? 0;
} catch {
// vec_memories may not exist yet — not an error, just 0 rows.
@@ -431,7 +422,7 @@ export function getVectorStore(): VectorStore | null {
// Test seam: VECTOR_STORE_DISABLE_VEC=true forces null (simulates cloud/WASM environment).
if (process.env["VECTOR_STORE_DISABLE_VEC"] === "true") {
log.warn(
"VECTOR_STORE_DISABLE_VEC is set — sqlite-vec disabled. Degrading to FTS5 keyword search.",
"VECTOR_STORE_DISABLE_VEC is set — sqlite-vec disabled. Degrading to FTS5 keyword search."
);
_instance = null;
return null;
@@ -445,7 +436,7 @@ export function getVectorStore(): VectorStore | null {
if (!raw || typeof raw.loadExtension !== "function") {
log.warn(
"sqlite-vec not loaded: db driver does not support loadExtension (cloud/WASM backend). " +
"Degrading to FTS5 keyword search.",
"Degrading to FTS5 keyword search."
);
_instance = null;
return null;

View File

@@ -770,7 +770,7 @@ export async function initModelsDevSync(): Promise<void> {
return;
}
const { getSettings } = await import("./localDb");
const { getSettings } = await import("@/lib/db/settings");
const settings = await getSettings();
if (!isModelsDevSyncEnvForcedOn() && settings.modelsDevSyncEnabled !== true) {

View File

@@ -1,7 +1,7 @@
import { createHash } from "crypto";
import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { getCachedProviderConnectionById } from "@/lib/db/readCache";
import { clearProviderFailure, clearModelLock } from "@omniroute/open-sse/services/accountFallback";
import { resolveProviderAlias } from "@omniroute/open-sse/services/model";

View File

@@ -2,7 +2,7 @@ import {
getProviderConnections,
createProviderConnection,
updateProviderConnection,
} from "@/lib/localDb";
} from "@/lib/db/providers";
import { AGY_CONFIG } from "@/lib/oauth/constants/oauth";
import {
getAntigravityContentHeaders,

View File

@@ -1,6 +1,6 @@
import fs from "fs/promises";
import path from "path";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { getCachedProviderConnectionById } from "@/lib/db/readCache";
import { createBackup } from "@/shared/services/backupService";
import { getCliConfigPaths } from "@/shared/services/cliRuntime";
import {
@@ -172,7 +172,9 @@ export function buildClaudeAuthPayload(connection: ClaudeConnectionLike): Claude
}
async function resolveFreshClaudeConnection(connectionId: string): Promise<ClaudeConnectionLike> {
const connection = (await getCachedProviderConnectionById(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

@@ -3,7 +3,7 @@ import {
getProviderConnections,
createProviderConnection,
updateProviderConnection,
} from "@/lib/localDb";
} from "@/lib/db/providers";
import { getClaudeCodeUserAgent } from "@/shared/constants/claudeCodeClient";
import { ClaudeAuthFileError } from "@/lib/oauth/utils/claudeAuthFile";

View File

@@ -1,6 +1,6 @@
import fs from "fs/promises";
import path from "path";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { getCachedProviderConnectionById } from "@/lib/db/readCache";
import { createBackup } from "@/shared/services/backupService";
import { getCliConfigPaths } from "@/shared/services/cliRuntime";
import {
@@ -197,7 +197,9 @@ function buildCodexAuthPayload(connection: CodexConnectionLike): CodexAuthFilePa
}
async function resolveFreshCodexConnection(connectionId: string): Promise<CodexConnectionLike> {
const connection = (await getCachedProviderConnectionById(connectionId)) as CodexConnectionLike | null;
const connection = (await getCachedProviderConnectionById(
connectionId
)) as CodexConnectionLike | null;
if (!connection) {
throw new CodexAuthFileError("Connection not found", 404, "not_found");
}
@@ -377,7 +379,11 @@ export type CodexAuthWriteDecision =
export async function writeCodexAuthFileToLocalCliIfNeeded(
connectionId: string,
options: { force?: boolean } = {}
): Promise<{ decision: CodexAuthWriteDecision; authPath: string | null; result?: Awaited<ReturnType<typeof writeCodexAuthFileToLocalCli>> }> {
): Promise<{
decision: CodexAuthWriteDecision;
authPath: string | null;
result?: Awaited<ReturnType<typeof writeCodexAuthFileToLocalCli>>;
}> {
const paths = getCliConfigPaths("codex");
const authPath = paths?.auth ?? null;

View File

@@ -2,7 +2,7 @@ import {
getProviderConnections,
createProviderConnection,
updateProviderConnection,
} from "@/lib/localDb";
} from "@/lib/db/providers";
import { CodexAuthFileError } from "@/lib/oauth/utils/codexAuthFile";
import { pickCodexConnectionForUser } from "@/lib/oauth/utils/codexConnectionSelection";

View File

@@ -2,9 +2,9 @@ import {
deleteModelAlias,
getModelAliases,
getModelIsHidden,
getProviderNodeById,
setModelAlias,
} from "@/lib/localDb";
} from "@/lib/db/models";
import { getProviderNodeById } from "@/lib/db/providers";
import {
getProviderAlias,
isAnthropicCompatibleProvider,

View File

@@ -313,7 +313,7 @@ export async function diagnoseAllEgressIps(deps?: {
const getConnections =
deps?.getConnections ??
(async () => {
const { getProviderConnections } = await import("./localDb");
const { getProviderConnections } = await import("@/lib/db/providers");
return (await getProviderConnections({ authType: "oauth" })) as Array<{
id: string;
provider: string;

View File

@@ -22,7 +22,7 @@
* same threshold, not independently tunable.
*/
import { deleteProxyById, listProxies, updateProxy } from "@/lib/localDb";
import { deleteProxyById, listProxies, updateProxy } from "@/lib/db/proxies";
import { isProxyLogIncludeIps } from "@/lib/proxyLogger";
import {
getRecentEgressSharingSummary,

View File

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

View File

@@ -12,7 +12,7 @@
* Part of: Group B — Quota Sharing Engine (plan 22, frente F6).
*/
import { getProviderPlan } from "@/lib/localDb";
import { getPlan as getProviderPlan } from "@/lib/db/providerPlans";
import { getKnownPlan } from "./planRegistry";
import type { ProviderPlan } from "./dimensions";

View File

@@ -12,7 +12,7 @@
import { getPool } from "@/lib/db/quotaPools";
import { getGroupName } from "@/lib/db/quotaGroups";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { getCachedProviderConnectionById } from "@/lib/db/readCache";
import {
getCombos,
createCombo,

View File

@@ -8,7 +8,7 @@
*/
import { getPool, getPoolsByGroup } from "@/lib/db/quotaPools";
import { getCachedProviderConnectionById } from "@/lib/localDb";
import { getCachedProviderConnectionById } from "@/lib/db/readCache";
import { getApiKeyById, updateApiKeyPermissions } from "@/lib/db/apiKeys";
import { quotaGroupSlug } from "./quotaModelNaming";
import { getGroupName } from "@/lib/db/quotaGroups";
@@ -163,7 +163,7 @@ export async function reconcilePoolExclusivity(
poolId: string,
prevApiKeyIds: string[],
nextApiKeyIds: string[],
exclusive: boolean,
exclusive: boolean
): Promise<void> {
const affectedIds = new Set([...prevApiKeyIds, ...nextApiKeyIds]);
@@ -173,7 +173,7 @@ export async function reconcilePoolExclusivity(
if (!keyRow) continue;
const currentQuotas: string[] = Array.isArray(
(keyRow as Record<string, unknown>).allowedQuotas,
(keyRow as Record<string, unknown>).allowedQuotas
)
? ((keyRow as Record<string, unknown>).allowedQuotas as string[])
: [];

View File

@@ -16,10 +16,7 @@
* Part of: Group B — Quota Sharing Engine (plan 22, frente F6).
*/
import {
getPool,
listAllocationsForApiKey,
} from "@/lib/localDb";
import { getPool, listAllocationsForApiKey } from "@/lib/db/quotaPools";
import { WINDOW_MS, dimensionKeyToString } from "./dimensions";
import type { DimensionKey } from "./dimensions";
import type { QuotaStore, PoolUsageSnapshot } from "./types";

View File

@@ -306,10 +306,7 @@ async function fetchCodexSaturation(
return Math.min(1, Math.max(0, quota.percentUsed ?? 0));
}
async function fetchBailianSaturation(
connectionId: string,
dim: DimensionSpec
): Promise<number> {
async function fetchBailianSaturation(connectionId: string, dim: DimensionSpec): Promise<number> {
const mod = await import("@omniroute/open-sse/services/bailianQuotaFetcher");
const quota = await mod.fetchBailianQuota(connectionId);
if (!quota) return 0;
@@ -318,13 +315,13 @@ async function fetchBailianSaturation(
let pct = 0;
switch (dim.window) {
case "5h":
pct = (q.window5h as Record<string, unknown>)?.percentUsed as number ?? 0;
pct = ((q.window5h as Record<string, unknown>)?.percentUsed as number) ?? 0;
break;
case "weekly":
pct = (q.windowWeekly as Record<string, unknown>)?.percentUsed as number ?? 0;
pct = ((q.windowWeekly as Record<string, unknown>)?.percentUsed as number) ?? 0;
break;
case "monthly":
pct = (q.windowMonthly as Record<string, unknown>)?.percentUsed as number ?? 0;
pct = ((q.windowMonthly as Record<string, unknown>)?.percentUsed as number) ?? 0;
break;
default:
pct = (q.percentUsed as number) ?? 0;
@@ -361,15 +358,13 @@ interface AnthropicSaturationDeps {
let _anthropicDepsOverride: AnthropicSaturationDeps | null = null;
/** Test-only: inject ({loadConnection, fetchUsage}); pass null to restore. */
export function __setAnthropicSaturationDepsForTests(
deps: AnthropicSaturationDeps | null
): void {
export function __setAnthropicSaturationDepsForTests(deps: AnthropicSaturationDeps | null): void {
_anthropicDepsOverride = deps;
}
async function defaultAnthropicDeps(): Promise<AnthropicSaturationDeps> {
const [localDbMod, usageMod] = await Promise.all([
import("@/lib/localDb"),
import("@/lib/db/readCache"),
import("@omniroute/open-sse/services/usage"),
]);
return {
@@ -423,10 +418,7 @@ function planUtilizationFromUsage(usage: unknown, window: QuotaWindow): number |
return Math.min(1, Math.max(0, used / 100));
}
async function fetchAnthropicSaturation(
connectionId: string,
dim: DimensionSpec
): Promise<number> {
async function fetchAnthropicSaturation(connectionId: string, dim: DimensionSpec): Promise<number> {
// Try the REAL plan-window utilization first (5h / weekly), via the same
// /api/oauth/usage path usage.ts already uses. This is the signal fairShare
// actually needs for Claude Pro/Max — the per-minute request headers do not
@@ -478,19 +470,13 @@ export function __setGenericUsageFetcherForTests(fetcher: GenericUsageFetcher |
_genericUsageFetcherOverride = fetcher;
}
async function defaultGenericUsageFetch(
connectionId: string,
provider: string
): Promise<unknown> {
async function defaultGenericUsageFetch(connectionId: string, provider: string): Promise<unknown> {
const mod = await import("@omniroute/open-sse/services/usage");
const conn = { id: connectionId, provider } as Parameters<typeof mod.getUsageForProvider>[0];
return mod.getUsageForProvider(conn);
}
async function fetchGenericSaturation(
connectionId: string,
provider: string
): Promise<number> {
async function fetchGenericSaturation(connectionId: string, provider: string): Promise<number> {
// 1. Real usage percent is authoritative when present (a provider that
// actually reports utilization beats the burst-window token headers).
try {
@@ -501,9 +487,8 @@ async function fetchGenericSaturation(
// Prefer the normalized quota shape (handles nested `quotas` map for
// Antigravity / Claude / etc.). Fall back to legacy top-level fields.
const { convertUsageToQuotaInfo } = await import(
"@omniroute/open-sse/services/genericQuotaFetcher"
);
const { convertUsageToQuotaInfo } =
await import("@omniroute/open-sse/services/genericQuotaFetcher");
const quota = convertUsageToQuotaInfo(result);
if (quota && Number.isFinite(quota.percentUsed)) {
return Math.min(1, Math.max(0, quota.percentUsed));
@@ -570,7 +555,10 @@ export async function getSaturation(
break;
}
} catch (err) {
log.warn({ err: (err as Error)?.message, connectionId, provider }, "saturation fetch failed — failing open with 0");
log.warn(
{ err: (err as Error)?.message, connectionId, provider },
"saturation fetch failed — failing open with 0"
);
value = 0;
}

View File

@@ -15,7 +15,8 @@
* Part of: Group B — Quota Sharing Engine (plan 22, frente F6).
*/
import { getPool, getBucket, incrementBucket, getPair, sumPoolDimension } from "@/lib/localDb";
import { getPool } from "@/lib/db/quotaPools";
import { getBucket, incrementBucket, getPair, sumPoolDimension } from "@/lib/db/quotaConsumption";
import { WINDOW_MS, dimensionKeyToString } from "./dimensions";
import type { DimensionKey } from "./dimensions";
import type { QuotaStore, PoolUsageSnapshot } from "./types";

View File

@@ -25,7 +25,8 @@ import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
import { getExecutor } from "@omniroute/open-sse/executors/index.ts";
import type { BaseExecutor } from "@omniroute/open-sse/executors/base";
import { getCodexUsage } from "@omniroute/open-sse/services/usage/codex.ts";
import { getSettings, getProviderConnections, updateProviderConnection } from "@/lib/localDb";
import { getSettings } from "@/lib/db/settings";
import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers";
import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation";
import { refreshAndUpdateCredentials } from "@/lib/usage/providerLimits";
import { getCircuitBreaker } from "@/shared/utils/circuitBreaker";
@@ -57,17 +58,12 @@ export interface QuotaAutoPingConnection {
export interface QuotaAutoPingDeps {
getSettings: () => Promise<JsonRecord>;
getProviderConnections: (
filter: JsonRecord
) => Promise<QuotaAutoPingConnection[]>;
getProviderConnections: (filter: JsonRecord) => Promise<QuotaAutoPingConnection[]>;
updateProviderConnection: (id: string, data: JsonRecord) => Promise<unknown>;
refreshAndUpdateCredentials: (
connection: QuotaAutoPingConnection
) => Promise<{ connection: QuotaAutoPingConnection }>;
getCodexUsage: (
accessToken?: string,
providerSpecificData?: JsonRecord
) => Promise<JsonRecord>;
getCodexUsage: (accessToken?: string, providerSpecificData?: JsonRecord) => Promise<JsonRecord>;
getExecutor: (provider: string) => Promise<BaseExecutor>;
canExecuteProvider: (provider: string) => boolean;
isConnectionUnavailableToAuxiliaryActivity: (connectionId: string) => Promise<boolean>;
@@ -152,11 +148,7 @@ function wasPingedRecently(
return Number.isFinite(lastPingAtMs) && nowMs - lastPingAtMs < intervalMs;
}
function shouldSkipAfterFailure(
state: QuotaAutoPingState,
key: string,
nowMs: number
): boolean {
function shouldSkipAfterFailure(state: QuotaAutoPingState, key: string, nowMs: number): boolean {
const failedAt = state.failureCache[key];
return Boolean(failedAt) && nowMs - failedAt < QUOTA_AUTOPING_FAILURE_COOLDOWN_MS;
}
@@ -272,8 +264,8 @@ async function isPingCandidateBlocked(
// resetAt — the guard is preserved here for that case.
return Boolean(
!providerConfig.pingWhenResetAtSlides &&
cachedReset &&
nowMs < new Date(cachedReset).getTime() - QUOTA_AUTOPING_REFRESH_AHEAD_MS
cachedReset &&
nowMs < new Date(cachedReset).getTime() - QUOTA_AUTOPING_REFRESH_AHEAD_MS
);
}
@@ -331,7 +323,18 @@ async function pingConnection(
): Promise<void> {
const key = cacheKey(provider, connection.id);
const cachedReset = state.resetCache[key];
if (await isPingCandidateBlocked(connection, provider, providerConfig, deps, state, key, cachedReset, nowMs)) {
if (
await isPingCandidateBlocked(
connection,
provider,
providerConfig,
deps,
state,
key,
cachedReset,
nowMs
)
) {
return;
}
@@ -346,7 +349,9 @@ async function pingConnection(
state.resetCache[key] = resetAt;
const resetKey = normalizeResetKey(resetAt);
if (!shouldSendPing(providerConfig, quotas, quota, cachedReset, resetAt, current, resetKey, nowMs)) {
if (
!shouldSendPing(providerConfig, quotas, quota, cachedReset, resetAt, current, resetKey, nowMs)
) {
return;
}
@@ -371,8 +376,7 @@ function getEnabledConnectionIds(
): Record<string, boolean> {
return (
((settings[providerConfig.settingsKey] as JsonRecord | undefined)?.connections as
| Record<string, boolean>
| undefined) || {}
Record<string, boolean> | undefined) || {}
);
}

View File

@@ -1,13 +1,11 @@
import { createHash } from "crypto";
import {
getApiKeys,
getCombos,
getModelAliases,
getProviderConnections,
getCachedProviderNodes,
getSettings,
getReasoningRoutingRules,
} from "@/lib/localDb";
import { getApiKeys } from "@/lib/db/apiKeys";
import { getCombos } from "@/lib/db/combos";
import { getModelAliases } from "@/lib/db/models";
import { getProviderConnections } from "@/lib/db/providers";
import { getCachedProviderNodes } from "@/lib/db/readCache";
import { getSettings } from "@/lib/db/settings";
import { getReasoningRoutingRules } from "@/lib/db/reasoningRoutingRules";
type JsonRecord = Record<string, unknown>;

View File

@@ -11,13 +11,9 @@
* updates the DB, and logs the result.
*/
import {
getProviderConnections,
getCachedProviderConnectionById,
updateProviderConnection,
getSettings,
resolveProxyForConnection,
} from "@/lib/localDb";
import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers";
import { getCachedProviderConnectionById } from "@/lib/db/readCache";
import { getSettings, resolveProxyForConnection } from "@/lib/db/settings";
import {
getAccessToken,
getDeprecationNotice,

View File

@@ -10,7 +10,7 @@
* frozen file-size budget (see config/quality/file-size-baseline.json).
*/
import { getProviderConnectionById, updateProviderConnection } from "@/lib/localDb";
import { getProviderConnectionById, updateProviderConnection } from "@/lib/db/providers";
import { refreshCopilotToken } from "@omniroute/open-sse/services/tokenRefresh.ts";
type HealthCheckLogger = {
@@ -30,8 +30,17 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: {
getConnectionLogLabel: (conn: { name?: string; email?: string; id?: string }) => string;
logPrefix: string;
}): Promise<void> {
const { conn, result, proxyConfig, healthCheckLog, log, logWarn, logError, getConnectionLogLabel, logPrefix } =
params;
const {
conn,
result,
proxyConfig,
healthCheckLog,
log,
logWarn,
logError,
getConnectionLogLabel,
logPrefix,
} = params;
if (String(conn.provider || "").toLowerCase() !== "github") return;
@@ -58,7 +67,11 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: {
log(`${logPrefix} Refreshing GitHub Copilot sub-token for ${getConnectionLogLabel(conn)}`);
try {
const copilotResult = await refreshCopilotToken(accessTokenForCopilot, healthCheckLog, proxyConfig);
const copilotResult = await refreshCopilotToken(
accessTokenForCopilot,
healthCheckLog,
proxyConfig
);
if (copilotResult?.token) {
await updateProviderConnection(conn.id, {
providerSpecificData: {
@@ -69,7 +82,9 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: {
});
log(`${logPrefix} ✓ GitHub Copilot sub-token refreshed for ${getConnectionLogLabel(conn)}`);
} else {
logWarn(`${logPrefix} ✗ GitHub Copilot sub-token refresh failed for ${getConnectionLogLabel(conn)}`);
logWarn(
`${logPrefix} ✗ GitHub Copilot sub-token refresh failed for ${getConnectionLogLabel(conn)}`
);
}
} catch (copilotErr) {
logError(`${logPrefix} Error refreshing Copilot sub-token:`, copilotErr?.message || copilotErr);

View File

@@ -1,7 +1,4 @@
import {
hasSelfAccountQuotaScope,
hasSelfUsageScope,
} from "@/shared/constants/selfServiceScopes";
import { hasSelfAccountQuotaScope, hasSelfUsageScope } from "@/shared/constants/selfServiceScopes";
import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
type JsonRecord = Record<string, unknown>;
@@ -363,7 +360,7 @@ async function normalizeDeps(deps: ApiKeySelfServiceDeps): Promise<RequiredDeps>
const localDb =
deps.getProviderConnectionById && deps.getProviderConnections
? null
: await import("@/lib/localDb");
: await import("@/lib/db/providers");
const providerLimits = deps.fetchAndPersistProviderLimits
? null
: await import("@/lib/usage/providerLimits");
@@ -396,11 +393,11 @@ export async function buildApiKeySelfServiceStatus(
const tokens = aggregateTokens(
resolvedDeps.getDbInstance() as DbLike,
metadata.id,
cost.periodStartAt ?? new Date(getCurrentMonthWindow(resolvedDeps.now()).periodStartAt).toISOString()
cost.periodStartAt ??
new Date(getCurrentMonthWindow(resolvedDeps.now()).periodStartAt).toISOString()
);
const accountQuotas = await resolveAccountQuotas(metadata, resolvedDeps);
const accountQuota =
accountQuotas && accountQuotas.length === 1 ? accountQuotas[0] : undefined;
const accountQuota = accountQuotas && accountQuotas.length === 1 ? accountQuotas[0] : undefined;
return {
apiKey: {

View File

@@ -140,7 +140,7 @@ async function resolveAccountName(connectionId: string | null | undefined) {
}
try {
const { getProviderConnections } = await import("@/lib/localDb");
const { getProviderConnections } = await import("@/lib/db/providers");
const connections = await getProviderConnections();
const conn = connections.find((item) => item.id === connectionId);
if (conn) {
@@ -160,7 +160,7 @@ async function resolveAccountName(connectionId: string | null | undefined) {
async function resolveProviderPrefix(providerId: string): Promise<string | null> {
if (!providerId) return null;
try {
const { getProviderNodeById } = await import("@/lib/localDb");
const { getProviderNodeById } = await import("@/lib/db/providers");
const node = await getProviderNodeById(providerId);
if (node && typeof node.prefix === "string" && node.prefix.trim().length > 0) {
return node.prefix.trim();

View File

@@ -1,4 +1,5 @@
import { getProviderConnectionById, resolveProxyForConnection } from "@/lib/localDb";
import { getProviderConnectionById } from "@/lib/db/providers";
import { resolveProxyForConnection } from "@/lib/db/settings";
import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation";
import {
fetchAndPersistProviderLimits,

View File

@@ -187,7 +187,7 @@ export async function calculateCost(
if (exactCostUsd !== null) return exactCostUsd;
try {
const { getPricingForModel } = await import("@/lib/localDb");
const { getPricingForModel } = await import("@/lib/db/settings");
// Try exact match first, then normalized model name
let pricing = await getPricingForModel(provider, model);
@@ -303,7 +303,7 @@ export async function calculateModalCost(
): Promise<number> {
if (!provider || !model) return 0;
try {
const { getPricingForModel } = await import("@/lib/localDb");
const { getPricingForModel } = await import("@/lib/db/settings");
let pricing = await getPricingForModel(provider, model);
if (!pricing) {
const normalized = normalizeModelName(model);

View File

@@ -99,7 +99,7 @@ async function normalizeDeps(deps: InternalUsageCommandDeps = {}): Promise<Requi
async function getDefaultUsageCommandQuotaPolicy(): Promise<UsageCommandQuotaPolicy> {
const [{ getCachedSettings }, { resolveResilienceSettings }] = await Promise.all([
import("@/lib/localDb"),
import("@/lib/db/readCache"),
import("@/lib/resilience/settings"),
]);
const resilience = resolveResilienceSettings(await getCachedSettings());
@@ -829,7 +829,10 @@ export async function handleInternalUsageCommandHttpRequest(
if (!apiKey || !(await resolvedDeps.isValidApiKey(apiKey))) {
if (json) {
return Response.json(
{ allowed: false, error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE } } satisfies UsageCommandJson,
{
allowed: false,
error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE },
} satisfies UsageCommandJson,
{ status: 401 }
);
}
@@ -840,7 +843,10 @@ export async function handleInternalUsageCommandHttpRequest(
if (!metadata?.id) {
if (json) {
return Response.json(
{ allowed: false, error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE } } satisfies UsageCommandJson,
{
allowed: false,
error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE },
} satisfies UsageCommandJson,
{ status: 401 }
);
}
@@ -850,7 +856,10 @@ export async function handleInternalUsageCommandHttpRequest(
if (metadata.allowUsageCommand !== true) {
if (json) {
return Response.json(
{ allowed: false, error: { message: USAGE_COMMAND_DISABLED_MESSAGE } } satisfies UsageCommandJson,
{
allowed: false,
error: { message: USAGE_COMMAND_DISABLED_MESSAGE },
} satisfies UsageCommandJson,
{ status: 403 }
);
}

View File

@@ -285,7 +285,7 @@ export async function getUsageStats() {
const sourceSql = buildUsageSourceSql(aggregationEnabled);
const sourceParams = aggregationEnabled && cutoffDate ? [cutoffDate, cutoffDate] : [];
const { getProviderConnections } = await import("@/lib/localDb");
const { getProviderConnections } = await import("@/lib/db/providers");
let allConnections: unknown[] = [];
try {
const loadedConnections = await getProviderConnections();

View File

@@ -149,7 +149,7 @@ export async function computeAnalytics(
// Pre-fetch pricing for all unique (provider, model) pairs — one DB round-trip
// per unique pair instead of one per entry, then compute costs synchronously.
const { getPricingForModel } = await import("@/lib/localDb");
const { getPricingForModel } = await import("@/lib/db/settings");
const pricingCache = new Map<string, Record<string, unknown> | null>();
const uniquePairs = new Set(entries.map((e) => `${e.provider}|||${e.model}`));
await Promise.all(

View File

@@ -1,5 +1,5 @@
import { jwtVerify } from "jose";
import { getSettings } from "@/lib/localDb";
import { getSettings } from "@/lib/db/settings";
import { validateApiKey } from "@/lib/db/apiKeys";
export const DEFAULT_WS_PATH = "/v1/ws";