From 97a67b5d3e32adc60ff0a43c4ec4808a41fb0424 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Thu, 5 Mar 2026 23:22:10 -0300 Subject: [PATCH] fix: persist lastUsedAt in provider_connections schema for round-robin (#218) - Add last_used_at column to provider_connections CREATE TABLE schema - Add ensureProviderConnectionsColumns migration for existing databases - Add last_used_at to INSERT and UPDATE SQL in providers.ts - Add last_used_at to JSON migration INSERT in core.ts - Add zod to serverExternalPackages in next.config.mjs (#217) Fixes #218: Round-robin routing strategy now correctly persists the lastUsedAt timestamp, allowing rotation between accounts. Fixes #217: zod module is now properly included in standalone/Docker builds by declaring it as a server external package. --- next.config.mjs | 2 +- package-lock.json | 4 ++-- src/lib/db/core.ts | 10 ++++++++-- src/lib/db/providers.ts | 7 +++++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/next.config.mjs b/next.config.mjs index ef7c9fb770..f066f1c8ad 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -6,7 +6,7 @@ const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); const nextConfig = { turbopack: {}, output: "standalone", - serverExternalPackages: ["better-sqlite3"], + serverExternalPackages: ["better-sqlite3", "zod"], transpilePackages: ["@omniroute/open-sse"], allowedDevOrigins: ["192.168.*"], typescript: { diff --git a/package-lock.json b/package-lock.json index d7d686ce73..36e120b546 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omniroute", - "version": "2.0.1", + "version": "2.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omniroute", - "version": "2.0.1", + "version": "2.0.3", "hasInstallScript": true, "license": "MIT", "workspaces": [ diff --git a/src/lib/db/core.ts b/src/lib/db/core.ts index 4a86d91be7..7a433f3fff 100644 --- a/src/lib/db/core.ts +++ b/src/lib/db/core.ts @@ -79,6 +79,7 @@ const SCHEMA_SQL = ` token_type TEXT, consecutive_use_count INTEGER DEFAULT 0, rate_limit_protection INTEGER DEFAULT 0, + last_used_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); @@ -311,6 +312,10 @@ function ensureProviderConnectionsColumns(db: SqliteDatabase) { ); console.log("[DB] Added provider_connections.rate_limit_protection column"); } + if (!columnNames.has("last_used_at")) { + db.exec("ALTER TABLE provider_connections ADD COLUMN last_used_at TEXT"); + console.log("[DB] Added provider_connections.last_used_at column"); + } } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); console.warn("[DB] Failed to verify provider_connections schema:", message); @@ -483,7 +488,7 @@ function migrateFromJson(db: SqliteDatabase, jsonPath: string) { rate_limited_until, health_check_interval, last_health_check_at, last_tested, api_key, id_token, provider_specific_data, expires_in, display_name, global_priority, default_model, - token_type, consecutive_use_count, rate_limit_protection, created_at, updated_at + token_type, consecutive_use_count, rate_limit_protection, last_used_at, created_at, updated_at ) VALUES ( @id, @provider, @authType, @name, @email, @priority, @isActive, @accessToken, @refreshToken, @expiresAt, @tokenExpiresAt, @@ -492,7 +497,7 @@ function migrateFromJson(db: SqliteDatabase, jsonPath: string) { @rateLimitedUntil, @healthCheckInterval, @lastHealthCheckAt, @lastTested, @apiKey, @idToken, @providerSpecificData, @expiresIn, @displayName, @globalPriority, @defaultModel, - @tokenType, @consecutiveUseCount, @rateLimitProtection, @createdAt, @updatedAt + @tokenType, @consecutiveUseCount, @rateLimitProtection, @lastUsedAt, @createdAt, @updatedAt ) `); @@ -533,6 +538,7 @@ function migrateFromJson(db: SqliteDatabase, jsonPath: string) { defaultModel: conn.defaultModel || null, tokenType: conn.tokenType || null, consecutiveUseCount: conn.consecutiveUseCount || 0, + lastUsedAt: conn.lastUsedAt || null, rateLimitProtection: conn.rateLimitProtection === true || conn.rateLimitProtection === 1 ? 1 : 0, createdAt: conn.createdAt || new Date().toISOString(), diff --git a/src/lib/db/providers.ts b/src/lib/db/providers.ts index a697f2c6d5..40491b1e28 100644 --- a/src/lib/db/providers.ts +++ b/src/lib/db/providers.ts @@ -217,7 +217,7 @@ function _insertConnectionRow(db: DbLike, conn: JsonRecord) { rate_limited_until, health_check_interval, last_health_check_at, last_tested, api_key, id_token, provider_specific_data, expires_in, display_name, global_priority, default_model, - token_type, consecutive_use_count, rate_limit_protection, created_at, updated_at + token_type, consecutive_use_count, rate_limit_protection, last_used_at, created_at, updated_at ) VALUES ( @id, @provider, @authType, @name, @email, @priority, @isActive, @accessToken, @refreshToken, @expiresAt, @tokenExpiresAt, @@ -226,7 +226,7 @@ function _insertConnectionRow(db: DbLike, conn: JsonRecord) { @rateLimitedUntil, @healthCheckInterval, @lastHealthCheckAt, @lastTested, @apiKey, @idToken, @providerSpecificData, @expiresIn, @displayName, @globalPriority, @defaultModel, - @tokenType, @consecutiveUseCount, @rateLimitProtection, @createdAt, @updatedAt + @tokenType, @consecutiveUseCount, @rateLimitProtection, @lastUsedAt, @createdAt, @updatedAt ) ` ).run({ @@ -267,6 +267,7 @@ function _insertConnectionRow(db: DbLike, conn: JsonRecord) { consecutiveUseCount: conn.consecutiveUseCount || 0, rateLimitProtection: conn.rateLimitProtection === true || conn.rateLimitProtection === 1 ? 1 : 0, + lastUsedAt: conn.lastUsedAt || null, createdAt: conn.createdAt, updatedAt: conn.updatedAt, }); @@ -290,6 +291,7 @@ function _updateConnectionRow(db: DbLike, id: string, data: JsonRecord) { default_model = @defaultModel, token_type = @tokenType, consecutive_use_count = @consecutiveUseCount, rate_limit_protection = @rateLimitProtection, + last_used_at = @lastUsedAt, updated_at = @updatedAt WHERE id = @id ` @@ -331,6 +333,7 @@ function _updateConnectionRow(db: DbLike, id: string, data: JsonRecord) { consecutiveUseCount: data.consecutiveUseCount || 0, rateLimitProtection: data.rateLimitProtection === true || data.rateLimitProtection === 1 ? 1 : 0, + lastUsedAt: data.lastUsedAt || null, updatedAt: now, }); }