refactor(db): extrai row-parsers + tipos compartilhados de db/apiKeys.ts (#4943)

Integrated into release/v3.8.36
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-24 11:51:33 -03:00
committed by GitHub
parent f1589801ee
commit ca0fdddebf
4 changed files with 292 additions and 171 deletions

View File

@@ -27,6 +27,24 @@ import {
hasClaudeCodeWildcardPermission,
matchesWildcardPattern,
} from "./apiKeys/modelPermissions";
import {
parseAllowedModels,
parseAllowedCombos,
parseNoLog,
parseAutoResolve,
parseDisableNonPublicModels,
parseAllowUsageCommand,
parseIsActive,
parseAccessSchedule,
parseRateLimits,
parseAllowedConnections,
parseAllowedQuotas,
parseStringList,
parseNullableTimestamp,
parseIsBanned,
parseStreamDefaultMode,
} from "./apiKeys/rowParsers";
import type { AccessSchedule, RateLimitRule } from "./apiKeys/types";
// ──────────────── Performance Optimizations ────────────────
@@ -40,18 +58,8 @@ interface CacheEntry<TValue> {
value: TValue;
}
export interface RateLimitRule {
limit: number;
window: number;
}
export interface AccessSchedule {
enabled: boolean;
from: string;
until: string;
days: number[];
tz: string;
}
// Re-exported for the historical public surface (moved to ./apiKeys/types).
export type { AccessSchedule, RateLimitRule } from "./apiKeys/types";
interface ApiKeyMetadata {
id: string;
@@ -277,11 +285,6 @@ function evictIfNeeded<TKey, TValue>(cache: Map<TKey, TValue>) {
}
}
async function getModelPermissionCandidates(modelId: string): Promise<string[]> {
const candidates = new Set<string>();
addModelCandidate(candidates, modelId);
@@ -312,8 +315,6 @@ async function getModelPermissionCandidates(modelId: string): Promise<string[]>
return Array.from(candidates);
}
async function getPublishedModelLookupTarget(
modelId: string
): Promise<{ providerId: string; modelId: string } | null> {
@@ -341,8 +342,6 @@ async function getPublishedModelLookupTarget(
return null;
}
function ensureApiKeyColumn(
db: ApiKeysDbLike,
columnNames: Set<string>,
@@ -486,156 +485,6 @@ export async function getApiKeyById(id: string) {
return camelRow;
}
/**
* Helper function to safely parse allowed_models JSON
*/
function parseAllowedModels(value: unknown): string[] {
if (!value || typeof value !== "string" || value.trim() === "") {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((entry): entry is string => typeof entry === "string")
: [];
} catch {
return [];
}
}
function parseAllowedCombos(value: unknown): string[] {
return parseStringList(value);
}
function parseNoLog(value: unknown): boolean {
return value === true || value === 1 || value === "1";
}
function parseAutoResolve(value: unknown): boolean {
return value === true || value === 1 || value === "1";
}
function parseDisableNonPublicModels(value: unknown): boolean {
return value === true || value === 1 || value === "1";
}
function parseAllowUsageCommand(value: unknown): boolean {
return value === true || value === 1 || value === "1";
}
function parseIsActive(value: unknown): boolean {
// DEFAULT 1 — active unless explicitly set to 0
if (value === 0 || value === "0" || value === false) return false;
return true;
}
function parseAccessSchedule(value: unknown): AccessSchedule | null {
if (!value || typeof value !== "string" || value.trim() === "") return null;
try {
const parsed: unknown = JSON.parse(value);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
const obj = parsed as Record<string, unknown>;
if (
typeof obj["enabled"] !== "boolean" ||
typeof obj["from"] !== "string" ||
typeof obj["until"] !== "string" ||
!Array.isArray(obj["days"]) ||
typeof obj["tz"] !== "string"
) {
return null;
}
const days = (obj["days"] as unknown[]).filter(
(d): d is number => typeof d === "number" && Number.isInteger(d) && d >= 0 && d <= 6
);
return {
enabled: obj["enabled"],
from: obj["from"],
until: obj["until"],
days,
tz: obj["tz"],
};
} catch {
return null;
}
}
function parseRateLimits(value: unknown): RateLimitRule[] | null {
if (!value || typeof value !== "string" || value.trim() === "") return null;
try {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) return null;
return parsed.filter(
(rule: RateLimitRule) =>
typeof rule === "object" &&
rule !== null &&
typeof rule.limit === "number" &&
typeof rule.window === "number"
) as RateLimitRule[];
} catch {
return null;
}
}
/**
* Helper function to safely parse allowed_connections JSON
*/
function parseAllowedConnections(value: unknown): string[] {
if (!value || typeof value !== "string" || value.trim() === "") {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((entry): entry is string => typeof entry === "string")
: [];
} catch {
return [];
}
}
/**
* Helper function to safely parse allowed_quotas JSON
*/
function parseAllowedQuotas(value: unknown): string[] {
if (!value || typeof value !== "string" || value.trim() === "") {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((entry): entry is string => typeof entry === "string")
: [];
} catch {
return [];
}
}
function parseStringList(value: unknown): string[] {
if (!value || typeof value !== "string" || value.trim() === "") return [];
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((entry): entry is string => typeof entry === "string")
: [];
} catch {
return [];
}
}
function parseNullableTimestamp(value: unknown): string | null {
if (typeof value !== "string") return null;
const trimmed = value.trim();
return trimmed === "" ? null : trimmed;
}
function parseIsBanned(value: unknown): boolean {
return value === 1 || value === "1" || value === true;
}
function parseStreamDefaultMode(value: unknown): "legacy" | "json" {
return value === "json" ? "json" : "legacy";
}
async function hashKey(key: string): Promise<string> {
if (!key || typeof key !== "string") return "";
// CodeQL: This is intentionally SHA-256, NOT password hashing. API keys are

View File

@@ -0,0 +1,161 @@
/**
* db/apiKeys/rowParsers.ts — pure column parsers for persisted api_keys rows.
*
* Extracted verbatim from db/apiKeys.ts (god-file decomposition): the family of
* functions that coerce raw SQLite column values (JSON strings, 0/1 flags, nullable
* timestamps) into the typed shapes the host hydrates rows with. Pure — no DB handle,
* no caches, no side effects — so they live as a co-located leaf. Behavior-preserving
* move; apiKeys.ts imports them back for getApiKeys/getApiKeyById/getApiKeyMetadata.
*/
import type { AccessSchedule, RateLimitRule } from "./types";
/**
* Helper function to safely parse allowed_models JSON
*/
export function parseAllowedModels(value: unknown): string[] {
if (!value || typeof value !== "string" || value.trim() === "") {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((entry): entry is string => typeof entry === "string")
: [];
} catch {
return [];
}
}
export function parseAllowedCombos(value: unknown): string[] {
return parseStringList(value);
}
export function parseNoLog(value: unknown): boolean {
return value === true || value === 1 || value === "1";
}
export function parseAutoResolve(value: unknown): boolean {
return value === true || value === 1 || value === "1";
}
export function parseDisableNonPublicModels(value: unknown): boolean {
return value === true || value === 1 || value === "1";
}
export function parseAllowUsageCommand(value: unknown): boolean {
return value === true || value === 1 || value === "1";
}
export function parseIsActive(value: unknown): boolean {
// DEFAULT 1 — active unless explicitly set to 0
if (value === 0 || value === "0" || value === false) return false;
return true;
}
export function parseAccessSchedule(value: unknown): AccessSchedule | null {
if (!value || typeof value !== "string" || value.trim() === "") return null;
try {
const parsed: unknown = JSON.parse(value);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
const obj = parsed as Record<string, unknown>;
if (
typeof obj["enabled"] !== "boolean" ||
typeof obj["from"] !== "string" ||
typeof obj["until"] !== "string" ||
!Array.isArray(obj["days"]) ||
typeof obj["tz"] !== "string"
) {
return null;
}
const days = (obj["days"] as unknown[]).filter(
(d): d is number => typeof d === "number" && Number.isInteger(d) && d >= 0 && d <= 6
);
return {
enabled: obj["enabled"],
from: obj["from"],
until: obj["until"],
days,
tz: obj["tz"],
};
} catch {
return null;
}
}
export function parseRateLimits(value: unknown): RateLimitRule[] | null {
if (!value || typeof value !== "string" || value.trim() === "") return null;
try {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) return null;
return parsed.filter(
(rule: RateLimitRule) =>
typeof rule === "object" &&
rule !== null &&
typeof rule.limit === "number" &&
typeof rule.window === "number"
) as RateLimitRule[];
} catch {
return null;
}
}
/**
* Helper function to safely parse allowed_connections JSON
*/
export function parseAllowedConnections(value: unknown): string[] {
if (!value || typeof value !== "string" || value.trim() === "") {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((entry): entry is string => typeof entry === "string")
: [];
} catch {
return [];
}
}
/**
* Helper function to safely parse allowed_quotas JSON
*/
export function parseAllowedQuotas(value: unknown): string[] {
if (!value || typeof value !== "string" || value.trim() === "") {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((entry): entry is string => typeof entry === "string")
: [];
} catch {
return [];
}
}
export function parseStringList(value: unknown): string[] {
if (!value || typeof value !== "string" || value.trim() === "") return [];
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((entry): entry is string => typeof entry === "string")
: [];
} catch {
return [];
}
}
export function parseNullableTimestamp(value: unknown): string | null {
if (typeof value !== "string") return null;
const trimmed = value.trim();
return trimmed === "" ? null : trimmed;
}
export function parseIsBanned(value: unknown): boolean {
return value === 1 || value === "1" || value === true;
}
export function parseStreamDefaultMode(value: unknown): "legacy" | "json" {
return value === "json" ? "json" : "legacy";
}

View File

@@ -0,0 +1,21 @@
/**
* db/apiKeys/types.ts — shared API-key value types.
*
* Extracted from db/apiKeys.ts (god-file decomposition): the persisted-row shapes
* that both the host module and the row-parser leaf need. Kept as a neutral leaf so
* apiKeys.ts and apiKeys/rowParsers.ts can import them without a cycle. apiKeys.ts
* re-exports both interfaces to preserve its historical public surface.
*/
export interface RateLimitRule {
limit: number;
window: number;
}
export interface AccessSchedule {
enabled: boolean;
from: string;
until: string;
days: number[];
tz: string;
}

View File

@@ -0,0 +1,90 @@
// Characterization of the db/apiKeys.ts row-parsers split (god-file decomposition): the pure column
// parsers that coerce raw SQLite values into typed shapes moved into db/apiKeys/rowParsers.ts, and the
// two shared row types (AccessSchedule / RateLimitRule) into db/apiKeys/types.ts. Behavior-preserving
// move — these locks pin the coercion semantics (JSON lists, 0/1 flags, nullable timestamps, schedule
// validation) and that the host still re-exports the two public types. DB-backed hydration stays
// covered by api-key-policy / combo-provider-wildcard.
import { test } from "node:test";
import assert from "node:assert/strict";
const P = await import("../../src/lib/db/apiKeys/rowParsers.ts");
test("module exposes all fifteen parsers", () => {
for (const name of [
"parseAllowedModels",
"parseAllowedCombos",
"parseNoLog",
"parseAutoResolve",
"parseDisableNonPublicModels",
"parseAllowUsageCommand",
"parseIsActive",
"parseAccessSchedule",
"parseRateLimits",
"parseAllowedConnections",
"parseAllowedQuotas",
"parseStringList",
"parseNullableTimestamp",
"parseIsBanned",
"parseStreamDefaultMode",
]) {
assert.equal(typeof (P as Record<string, unknown>)[name], "function", `missing ${name}`);
}
});
test("parseAllowedModels keeps only string entries, tolerates junk", () => {
assert.deepEqual(P.parseAllowedModels('["a","b",1,null]'), ["a", "b"]);
assert.deepEqual(P.parseAllowedModels(""), []);
assert.deepEqual(P.parseAllowedModels("not json"), []);
assert.deepEqual(P.parseAllowedModels(null), []);
});
test("flag parsers honor the 0/1/true/false matrix", () => {
assert.equal(P.parseNoLog(1), true);
assert.equal(P.parseNoLog("1"), true);
assert.equal(P.parseNoLog(0), false);
// isActive defaults to active unless explicitly falsy
assert.equal(P.parseIsActive(undefined), true);
assert.equal(P.parseIsActive(0), false);
assert.equal(P.parseIsActive("0"), false);
assert.equal(P.parseIsBanned(1), true);
assert.equal(P.parseIsBanned(0), false);
});
test("parseStreamDefaultMode collapses to legacy unless json", () => {
assert.equal(P.parseStreamDefaultMode("json"), "json");
assert.equal(P.parseStreamDefaultMode("legacy"), "legacy");
assert.equal(P.parseStreamDefaultMode("anything"), "legacy");
});
test("parseNullableTimestamp trims and nulls empties", () => {
assert.equal(P.parseNullableTimestamp(" 2026-01-01 "), "2026-01-01");
assert.equal(P.parseNullableTimestamp(" "), null);
assert.equal(P.parseNullableTimestamp(42), null);
});
test("parseAccessSchedule validates shape + clamps days, else null", () => {
const ok = P.parseAccessSchedule(
JSON.stringify({ enabled: true, from: "08:00", until: "18:00", days: [0, 3, 9], tz: "UTC" })
);
assert.ok(ok);
assert.deepEqual(ok?.days, [0, 3]); // 9 dropped (out of 0..6)
assert.equal(P.parseAccessSchedule('{"enabled":true}'), null);
assert.equal(P.parseAccessSchedule("[]"), null);
assert.equal(P.parseAccessSchedule(""), null);
});
test("parseRateLimits keeps well-formed numeric rules only", () => {
const out = P.parseRateLimits(
JSON.stringify([{ limit: 10, window: 60 }, { limit: "x", window: 1 }, null])
);
assert.deepEqual(out, [{ limit: 10, window: 60 }]);
assert.equal(P.parseRateLimits("not array"), null);
assert.equal(P.parseRateLimits(""), null);
});
test("host re-exports the two public row types (compile-time) and the parsers stay wired", async () => {
// type-only re-export can't be probed at runtime; assert the host module still loads + exposes its API
const HOST = await import("../../src/lib/db/apiKeys.ts");
assert.equal(typeof (HOST as Record<string, unknown>).getApiKeys, "function");
assert.equal(typeof (HOST as Record<string, unknown>).isModelAllowedForKey, "function");
});