Spaces:
Runtime error
Runtime error
| /** | |
| * Domain State Persistence β Phase 5 Foundation | |
| * | |
| * CRUD operations for persisting domain layer state in SQLite. | |
| * Replaces in-memory Map() storage with durable persistence. | |
| * | |
| * Tables: domain_fallback_chains, domain_budgets, domain_cost_history, | |
| * domain_lockout_state, domain_circuit_breakers | |
| * | |
| * @module lib/db/domainState | |
| */ | |
| import { getDbInstance } from "./core"; | |
| type JsonRecord = Record<string, unknown>; | |
| type BudgetResetInterval = "daily" | "weekly" | "monthly"; | |
| interface BudgetConfigRecord { | |
| dailyLimitUsd: number; | |
| weeklyLimitUsd: number; | |
| monthlyLimitUsd: number; | |
| warningThreshold: number; | |
| resetInterval: BudgetResetInterval; | |
| resetTime: string; | |
| budgetResetAt: number | null; | |
| lastBudgetResetAt: number | null; | |
| warningEmittedAt: number | null; | |
| warningPeriodStart: number | null; | |
| } | |
| interface BudgetResetLogRecord { | |
| apiKeyId: string; | |
| resetInterval: BudgetResetInterval; | |
| previousSpend: number; | |
| resetAt: number; | |
| nextResetAt: number; | |
| periodStart: number; | |
| periodEnd: number; | |
| } | |
| interface FallbackChainEntry { | |
| provider: string; | |
| priority: number; | |
| enabled: boolean; | |
| } | |
| interface LockoutStateRecord { | |
| attempts: number[]; | |
| lockedUntil: number | null; | |
| } | |
| interface CircuitBreakerStateRecord { | |
| state: string; | |
| failureCount: number; | |
| lastFailureTime: number | null; | |
| options?: JsonRecord | null; | |
| } | |
| let _budgetSchemaChecked = false; | |
| function asRecord(value: unknown): JsonRecord { | |
| return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; | |
| } | |
| function toNumber(value: unknown, fallback = 0): number { | |
| if (typeof value === "number" && Number.isFinite(value)) return value; | |
| if (typeof value === "string" && value.trim().length > 0) { | |
| const parsed = Number(value); | |
| return Number.isFinite(parsed) ? parsed : fallback; | |
| } | |
| return fallback; | |
| } | |
| function ensureBudgetSchema() { | |
| if (_budgetSchemaChecked) return; | |
| const db = getDbInstance(); | |
| const columns = db.prepare("PRAGMA table_info(domain_budgets)").all(); | |
| const columnNames = new Set( | |
| columns | |
| .map((column) => { | |
| const record = asRecord(column); | |
| return typeof record.name === "string" ? record.name : ""; | |
| }) | |
| .filter(Boolean) | |
| ); | |
| if (!columnNames.has("weekly_limit_usd")) { | |
| db.exec("ALTER TABLE domain_budgets ADD COLUMN weekly_limit_usd REAL DEFAULT 0"); | |
| } | |
| if (!columnNames.has("reset_interval")) { | |
| db.exec("ALTER TABLE domain_budgets ADD COLUMN reset_interval TEXT DEFAULT 'daily'"); | |
| } | |
| if (!columnNames.has("reset_time")) { | |
| db.exec("ALTER TABLE domain_budgets ADD COLUMN reset_time TEXT DEFAULT '00:00'"); | |
| } | |
| if (!columnNames.has("budget_reset_at")) { | |
| db.exec("ALTER TABLE domain_budgets ADD COLUMN budget_reset_at INTEGER"); | |
| } | |
| if (!columnNames.has("last_budget_reset_at")) { | |
| db.exec("ALTER TABLE domain_budgets ADD COLUMN last_budget_reset_at INTEGER"); | |
| } | |
| if (!columnNames.has("warning_emitted_at")) { | |
| db.exec("ALTER TABLE domain_budgets ADD COLUMN warning_emitted_at INTEGER"); | |
| } | |
| if (!columnNames.has("warning_period_start")) { | |
| db.exec("ALTER TABLE domain_budgets ADD COLUMN warning_period_start INTEGER"); | |
| } | |
| db.exec(` | |
| CREATE TABLE IF NOT EXISTS domain_budget_reset_logs ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| api_key_id TEXT NOT NULL, | |
| reset_interval TEXT NOT NULL, | |
| previous_spend REAL NOT NULL DEFAULT 0, | |
| reset_at INTEGER NOT NULL, | |
| next_reset_at INTEGER NOT NULL, | |
| period_start INTEGER NOT NULL, | |
| period_end INTEGER NOT NULL | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_dbrl_key_reset | |
| ON domain_budget_reset_logs(api_key_id, reset_at DESC); | |
| `); | |
| _budgetSchemaChecked = true; | |
| } | |
| // ββββββββββββββββ Fallback Chains ββββββββββββββββ | |
| /** | |
| * Save a fallback chain for a model. | |
| * @param {string} model | |
| * @param {Array<{provider: string, priority: number, enabled: boolean}>} chain | |
| */ | |
| export function saveFallbackChain(model: string, chain: FallbackChainEntry[]) { | |
| const db = getDbInstance(); | |
| db.prepare("INSERT OR REPLACE INTO domain_fallback_chains (model, chain) VALUES (?, ?)").run( | |
| model, | |
| JSON.stringify(chain) | |
| ); | |
| } | |
| /** | |
| * Load a fallback chain for a model. | |
| * @param {string} model | |
| * @returns {Array<{provider: string, priority: number, enabled: boolean}> | null} | |
| */ | |
| export function loadFallbackChain(model: string): FallbackChainEntry[] | null { | |
| const db = getDbInstance(); | |
| const row = db.prepare("SELECT chain FROM domain_fallback_chains WHERE model = ?").get(model); | |
| const chain = asRecord(row).chain; | |
| return typeof chain === "string" ? JSON.parse(chain) : null; | |
| } | |
| /** | |
| * Load all fallback chains. | |
| * @returns {Record<string, Array<{provider: string, priority: number, enabled: boolean}>>} | |
| */ | |
| export function loadAllFallbackChains() { | |
| const db = getDbInstance(); | |
| const rows = db.prepare("SELECT model, chain FROM domain_fallback_chains").all(); | |
| const result: Record<string, unknown> = {}; | |
| for (const row of rows) { | |
| const record = asRecord(row); | |
| const model = typeof record.model === "string" ? record.model : null; | |
| const chain = typeof record.chain === "string" ? record.chain : null; | |
| if (!model || !chain) continue; | |
| result[model] = JSON.parse(chain); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Delete a fallback chain. | |
| * @param {string} model | |
| * @returns {boolean} | |
| */ | |
| export function deleteFallbackChain(model: string) { | |
| const db = getDbInstance(); | |
| const info = db.prepare("DELETE FROM domain_fallback_chains WHERE model = ?").run(model); | |
| return info.changes > 0; | |
| } | |
| /** | |
| * Delete all fallback chains. | |
| */ | |
| export function deleteAllFallbackChains() { | |
| const db = getDbInstance(); | |
| db.prepare("DELETE FROM domain_fallback_chains").run(); | |
| } | |
| // ββββββββββββββββ Budgets ββββββββββββββββ | |
| /** | |
| * Save a budget config for an API key. | |
| * @param {string} apiKeyId | |
| * @param {{ dailyLimitUsd: number, monthlyLimitUsd?: number, warningThreshold?: number }} config | |
| */ | |
| export function saveBudget(apiKeyId: string, config: Partial<BudgetConfigRecord>) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| db.prepare( | |
| `INSERT OR REPLACE INTO domain_budgets ( | |
| api_key_id, | |
| daily_limit_usd, | |
| weekly_limit_usd, | |
| monthly_limit_usd, | |
| warning_threshold, | |
| reset_interval, | |
| reset_time, | |
| budget_reset_at, | |
| last_budget_reset_at, | |
| warning_emitted_at, | |
| warning_period_start | |
| ) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` | |
| ).run( | |
| apiKeyId, | |
| toNumber(config.dailyLimitUsd), | |
| toNumber(config.weeklyLimitUsd), | |
| toNumber(config.monthlyLimitUsd), | |
| toNumber(config.warningThreshold, 0.8), | |
| typeof config.resetInterval === "string" ? config.resetInterval : "daily", | |
| typeof config.resetTime === "string" ? config.resetTime : "00:00", | |
| config.budgetResetAt ?? null, | |
| config.lastBudgetResetAt ?? null, | |
| config.warningEmittedAt ?? null, | |
| config.warningPeriodStart ?? null | |
| ); | |
| } | |
| /** | |
| * Load a budget config. | |
| * @param {string} apiKeyId | |
| * @returns {{ dailyLimitUsd: number, monthlyLimitUsd: number, warningThreshold: number } | null} | |
| */ | |
| export function loadBudget(apiKeyId: string): BudgetConfigRecord | null { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| const row = db.prepare("SELECT * FROM domain_budgets WHERE api_key_id = ?").get(apiKeyId); | |
| const record = asRecord(row); | |
| if (!row) return null; | |
| return { | |
| dailyLimitUsd: toNumber(record.daily_limit_usd), | |
| weeklyLimitUsd: toNumber(record.weekly_limit_usd), | |
| monthlyLimitUsd: toNumber(record.monthly_limit_usd), | |
| warningThreshold: toNumber(record.warning_threshold, 0.8), | |
| resetInterval: | |
| typeof record.reset_interval === "string" | |
| ? (record.reset_interval as BudgetResetInterval) | |
| : "daily", | |
| resetTime: typeof record.reset_time === "string" ? record.reset_time : "00:00", | |
| budgetResetAt: toNumber(record.budget_reset_at, 0) || null, | |
| lastBudgetResetAt: toNumber(record.last_budget_reset_at, 0) || null, | |
| warningEmittedAt: toNumber(record.warning_emitted_at, 0) || null, | |
| warningPeriodStart: toNumber(record.warning_period_start, 0) || null, | |
| }; | |
| } | |
| /** | |
| * Load all budget configs. | |
| * @returns {Record<string, BudgetConfigRecord>} | |
| */ | |
| export function loadAllBudgets() { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| const rows = db.prepare("SELECT * FROM domain_budgets ORDER BY api_key_id").all(); | |
| const result: Record<string, BudgetConfigRecord> = {}; | |
| for (const row of rows) { | |
| const record = asRecord(row); | |
| const apiKeyId = typeof record.api_key_id === "string" ? record.api_key_id : ""; | |
| if (!apiKeyId) continue; | |
| result[apiKeyId] = { | |
| dailyLimitUsd: toNumber(record.daily_limit_usd), | |
| weeklyLimitUsd: toNumber(record.weekly_limit_usd), | |
| monthlyLimitUsd: toNumber(record.monthly_limit_usd), | |
| warningThreshold: toNumber(record.warning_threshold, 0.8), | |
| resetInterval: | |
| typeof record.reset_interval === "string" | |
| ? (record.reset_interval as BudgetResetInterval) | |
| : "daily", | |
| resetTime: typeof record.reset_time === "string" ? record.reset_time : "00:00", | |
| budgetResetAt: toNumber(record.budget_reset_at, 0) || null, | |
| lastBudgetResetAt: toNumber(record.last_budget_reset_at, 0) || null, | |
| warningEmittedAt: toNumber(record.warning_emitted_at, 0) || null, | |
| warningPeriodStart: toNumber(record.warning_period_start, 0) || null, | |
| }; | |
| } | |
| return result; | |
| } | |
| /** | |
| * Persist a budget reset log entry. | |
| * @param {BudgetResetLogRecord} entry | |
| */ | |
| export function saveBudgetResetLog(entry: BudgetResetLogRecord) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| db.prepare( | |
| `INSERT INTO domain_budget_reset_logs | |
| (api_key_id, reset_interval, previous_spend, reset_at, next_reset_at, period_start, period_end) | |
| VALUES (?, ?, ?, ?, ?, ?, ?)` | |
| ).run( | |
| entry.apiKeyId, | |
| entry.resetInterval, | |
| entry.previousSpend, | |
| entry.resetAt, | |
| entry.nextResetAt, | |
| entry.periodStart, | |
| entry.periodEnd | |
| ); | |
| } | |
| /** | |
| * Load recent budget reset logs for an API key. | |
| * @param {string} apiKeyId | |
| * @param {number} [limit=10] | |
| * @returns {Array<BudgetResetLogRecord & { id: number }>} | |
| */ | |
| export function loadBudgetResetLogs(apiKeyId: string, limit = 10) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| return db | |
| .prepare( | |
| `SELECT id, api_key_id, reset_interval, previous_spend, reset_at, next_reset_at, period_start, period_end | |
| FROM domain_budget_reset_logs | |
| WHERE api_key_id = ? | |
| ORDER BY reset_at DESC | |
| LIMIT ?` | |
| ) | |
| .all(apiKeyId, Math.max(1, Math.floor(limit))) | |
| .map((row) => { | |
| const record = asRecord(row); | |
| return { | |
| id: toNumber(record.id), | |
| apiKeyId: typeof record.api_key_id === "string" ? record.api_key_id : "", | |
| resetInterval: | |
| typeof record.reset_interval === "string" | |
| ? (record.reset_interval as BudgetResetInterval) | |
| : "daily", | |
| previousSpend: toNumber(record.previous_spend), | |
| resetAt: toNumber(record.reset_at), | |
| nextResetAt: toNumber(record.next_reset_at), | |
| periodStart: toNumber(record.period_start), | |
| periodEnd: toNumber(record.period_end), | |
| }; | |
| }) | |
| .filter((entry) => entry.apiKeyId.length > 0); | |
| } | |
| /** | |
| * Delete a budget config. | |
| * @param {string} apiKeyId | |
| */ | |
| export function deleteBudget(apiKeyId: string) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| db.prepare("DELETE FROM domain_budgets WHERE api_key_id = ?").run(apiKeyId); | |
| db.prepare("DELETE FROM domain_budget_reset_logs WHERE api_key_id = ?").run(apiKeyId); | |
| } | |
| // ββββββββββββββββ Cost History ββββββββββββββββ | |
| /** | |
| * Record a cost entry. | |
| * @param {string} apiKeyId | |
| * @param {number} cost | |
| * @param {number} [timestamp] | |
| */ | |
| export function saveCostEntry(apiKeyId: string, cost: number, timestamp = Date.now()) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| db.prepare("INSERT INTO domain_cost_history (api_key_id, cost, timestamp) VALUES (?, ?, ?)").run( | |
| apiKeyId, | |
| cost, | |
| timestamp | |
| ); | |
| } | |
| export function batchSaveCostEntries( | |
| entries: Array<{ apiKeyId: string; cost: number; timestamp: number }> | |
| ) { | |
| ensureBudgetSchema(); | |
| if (!Array.isArray(entries) || entries.length === 0) return; | |
| const db = getDbInstance(); | |
| const stmt = db.prepare( | |
| "INSERT INTO domain_cost_history (api_key_id, cost, timestamp) VALUES (?, ?, ?)" | |
| ); | |
| const tx = db.transaction( | |
| (rows: Array<{ apiKeyId: string; cost: number; timestamp: number }>) => { | |
| for (const entry of rows) { | |
| stmt.run(entry.apiKeyId, entry.cost, entry.timestamp); | |
| } | |
| } | |
| ); | |
| tx(entries); | |
| } | |
| export function loadCostTotal(apiKeyId: string, sinceTimestamp: number) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| const row = db | |
| .prepare( | |
| "SELECT COALESCE(SUM(cost), 0) AS total FROM domain_cost_history WHERE api_key_id = ? AND timestamp >= ?" | |
| ) | |
| .get(apiKeyId, sinceTimestamp) as { total?: number } | undefined; | |
| return Number(row?.total || 0); | |
| } | |
| /** | |
| * Load cost entries for an API key within a time window. | |
| * @param {string} apiKeyId | |
| * @param {number} sinceTimestamp | |
| * @returns {Array<{cost: number, timestamp: number}>} | |
| */ | |
| export function loadCostEntries(apiKeyId: string, sinceTimestamp: number) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| return db | |
| .prepare( | |
| "SELECT cost, timestamp FROM domain_cost_history WHERE api_key_id = ? AND timestamp >= ? ORDER BY timestamp" | |
| ) | |
| .all(apiKeyId, sinceTimestamp); | |
| } | |
| /** | |
| * Load cost entries for an API key within a bounded time window. | |
| * @param {string} apiKeyId | |
| * @param {number} sinceTimestamp | |
| * @param {number} untilTimestamp | |
| * @returns {Array<{cost: number, timestamp: number}>} | |
| */ | |
| export function loadCostEntriesInRange( | |
| apiKeyId: string, | |
| sinceTimestamp: number, | |
| untilTimestamp: number | |
| ) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| return db | |
| .prepare( | |
| `SELECT cost, timestamp | |
| FROM domain_cost_history | |
| WHERE api_key_id = ? AND timestamp >= ? AND timestamp < ? | |
| ORDER BY timestamp` | |
| ) | |
| .all(apiKeyId, sinceTimestamp, untilTimestamp); | |
| } | |
| /** | |
| * Delete old cost entries (cleanup). | |
| * @param {number} olderThanTimestamp | |
| * @returns {number} deleted count | |
| */ | |
| export function cleanOldCostEntries(olderThanTimestamp: number) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| const info = db | |
| .prepare("DELETE FROM domain_cost_history WHERE timestamp < ?") | |
| .run(olderThanTimestamp); | |
| return info.changes; | |
| } | |
| /** | |
| * Delete all cost data for an API key. | |
| * @param {string} apiKeyId | |
| */ | |
| export function deleteCostEntries(apiKeyId: string) { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| db.prepare("DELETE FROM domain_cost_history WHERE api_key_id = ?").run(apiKeyId); | |
| } | |
| /** | |
| * Delete all cost data. | |
| */ | |
| export function deleteAllCostData() { | |
| ensureBudgetSchema(); | |
| const db = getDbInstance(); | |
| db.prepare("DELETE FROM domain_cost_history").run(); | |
| db.prepare("DELETE FROM domain_budgets").run(); | |
| db.prepare("DELETE FROM domain_budget_reset_logs").run(); | |
| } | |
| // ββββββββββββββββ Lockout State ββββββββββββββββ | |
| /** | |
| * Save lockout state for an identifier. | |
| * @param {string} identifier | |
| * @param {{ attempts: number[], lockedUntil: number|null }} state | |
| */ | |
| export function saveLockoutState(identifier: string, state: LockoutStateRecord) { | |
| const db = getDbInstance(); | |
| db.prepare( | |
| `INSERT OR REPLACE INTO domain_lockout_state (identifier, attempts, locked_until) | |
| VALUES (?, ?, ?)` | |
| ).run(identifier, JSON.stringify(state.attempts), state.lockedUntil); | |
| } | |
| /** | |
| * Load lockout state for an identifier. | |
| * @param {string} identifier | |
| * @returns {{ attempts: number[], lockedUntil: number|null } | null} | |
| */ | |
| export function loadLockoutState(identifier: string): LockoutStateRecord | null { | |
| const db = getDbInstance(); | |
| const row = db.prepare("SELECT * FROM domain_lockout_state WHERE identifier = ?").get(identifier); | |
| if (!row) return null; | |
| const record = asRecord(row); | |
| const attemptsRaw = typeof record.attempts === "string" ? record.attempts : "[]"; | |
| const lockedUntilRaw = record.locked_until; | |
| return { | |
| attempts: JSON.parse(attemptsRaw), | |
| lockedUntil: typeof lockedUntilRaw === "number" ? lockedUntilRaw : null, | |
| }; | |
| } | |
| /** | |
| * Delete lockout state for an identifier. | |
| * @param {string} identifier | |
| */ | |
| export function deleteLockoutState(identifier: string) { | |
| const db = getDbInstance(); | |
| db.prepare("DELETE FROM domain_lockout_state WHERE identifier = ?").run(identifier); | |
| } | |
| /** | |
| * Get all locked identifiers. | |
| * @returns {Array<{identifier: string, lockedUntil: number}>} | |
| */ | |
| export function loadAllLockedIdentifiers() { | |
| const db = getDbInstance(); | |
| const now = Date.now(); | |
| return db | |
| .prepare( | |
| "SELECT identifier, locked_until FROM domain_lockout_state WHERE locked_until IS NOT NULL AND locked_until > ?" | |
| ) | |
| .all(now) | |
| .map((row) => { | |
| const record = asRecord(row); | |
| return { | |
| identifier: typeof record.identifier === "string" ? record.identifier : "", | |
| lockedUntil: toNumber(record.locked_until), | |
| }; | |
| }) | |
| .filter((row) => row.identifier.length > 0); | |
| } | |
| // ββββββββββββββββ Circuit Breakers ββββββββββββββββ | |
| /** | |
| * Save circuit breaker state. | |
| * @param {string} name | |
| * @param {{ state: string, failureCount: number, lastFailureTime: number|null, options?: object }} cbState | |
| */ | |
| export function saveCircuitBreakerState(name: string, cbState: CircuitBreakerStateRecord) { | |
| const db = getDbInstance(); | |
| db.prepare( | |
| `INSERT OR REPLACE INTO domain_circuit_breakers (name, state, failure_count, last_failure_time, options) | |
| VALUES (?, ?, ?, ?, ?)` | |
| ).run( | |
| name, | |
| cbState.state, | |
| cbState.failureCount, | |
| cbState.lastFailureTime, | |
| cbState.options ? JSON.stringify(cbState.options) : null | |
| ); | |
| } | |
| /** | |
| * Load circuit breaker state. | |
| * @param {string} name | |
| * @returns {{ state: string, failureCount: number, lastFailureTime: number|null, options?: object } | null} | |
| */ | |
| export function loadCircuitBreakerState(name: string): CircuitBreakerStateRecord | null { | |
| const db = getDbInstance(); | |
| const row = db.prepare("SELECT * FROM domain_circuit_breakers WHERE name = ?").get(name); | |
| if (!row) return null; | |
| const record = asRecord(row); | |
| const options = typeof record.options === "string" ? JSON.parse(record.options) : null; | |
| return { | |
| state: typeof record.state === "string" ? record.state : "CLOSED", | |
| failureCount: toNumber(record.failure_count), | |
| lastFailureTime: toNumber(record.last_failure_time, 0) || null, | |
| options, | |
| }; | |
| } | |
| /** | |
| * Load all circuit breaker states. | |
| * @returns {Array<{name: string, state: string, failureCount: number, lastFailureTime: number|null}>} | |
| */ | |
| export function loadAllCircuitBreakerStates() { | |
| const db = getDbInstance(); | |
| return db | |
| .prepare("SELECT name, state, failure_count, last_failure_time FROM domain_circuit_breakers") | |
| .all() | |
| .map((row) => { | |
| const record = asRecord(row); | |
| return { | |
| name: typeof record.name === "string" ? record.name : "", | |
| state: typeof record.state === "string" ? record.state : "CLOSED", | |
| failureCount: toNumber(record.failure_count), | |
| lastFailureTime: toNumber(record.last_failure_time, 0) || null, | |
| }; | |
| }) | |
| .filter((row) => row.name.length > 0); | |
| } | |
| /** | |
| * Delete a circuit breaker state. | |
| * @param {string} name | |
| */ | |
| export function deleteCircuitBreakerState(name: string) { | |
| const db = getDbInstance(); | |
| db.prepare("DELETE FROM domain_circuit_breakers WHERE name = ?").run(name); | |
| } | |
| /** | |
| * Delete all circuit breaker states. | |
| */ | |
| export function deleteAllCircuitBreakerStates() { | |
| const db = getDbInstance(); | |
| db.prepare("DELETE FROM domain_circuit_breakers").run(); | |
| } | |