refactor(shared): extract shared cache factory to deduplicate cache patterns

This commit is contained in:
YeonGyu-Kim
2026-04-03 19:36:56 +09:00
parent 6b431c5522
commit 3cc9e8bc30
3 changed files with 142 additions and 143 deletions
+29 -93
View File
@@ -1,7 +1,6 @@
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"
import { join } from "path"
import { log } from "./logger" import { log } from "./logger"
import * as dataPath from "./data-path" import * as dataPath from "./data-path"
import { createJsonFileCacheStore } from "./json-file-cache-store"
const CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json" const CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json"
const PROVIDER_MODELS_CACHE_FILE = "provider-models.json" const PROVIDER_MODELS_CACHE_FILE = "provider-models.json"
@@ -47,115 +46,52 @@ function isRecord(value: unknown): value is Record<string, unknown> {
export function createConnectedProvidersCacheStore( export function createConnectedProvidersCacheStore(
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir
) { ) {
function getCacheFilePath(filename: string): string { const connectedProvidersCacheStore = createJsonFileCacheStore<ConnectedProvidersCache>({
return join(getCacheDir(), filename) getCacheDir,
} filename: CONNECTED_PROVIDERS_CACHE_FILE,
logPrefix: "connected-providers-cache",
let memConnected: string[] | null | undefined cacheLabel: "Cache",
let memProviderModels: ProviderModelsCache | null | undefined describe: (value) => ({ count: value.connected.length, updatedAt: value.updatedAt }),
})
function ensureCacheDir(): void { const providerModelsCacheStore = createJsonFileCacheStore<ProviderModelsCache>({
const cacheDir = getCacheDir() getCacheDir,
if (!existsSync(cacheDir)) { filename: PROVIDER_MODELS_CACHE_FILE,
mkdirSync(cacheDir, { recursive: true }) logPrefix: "connected-providers-cache",
} cacheLabel: "Provider-models cache",
} describe: (value) => ({
providerCount: Object.keys(value.models).length,
updatedAt: value.updatedAt,
}),
})
function readConnectedProvidersCache(): string[] | null { function readConnectedProvidersCache(): string[] | null {
if (memConnected !== undefined) return memConnected return connectedProvidersCacheStore.read()?.connected ?? null
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
if (!existsSync(cacheFile)) {
log("[connected-providers-cache] Cache file not found", { cacheFile })
memConnected = null
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const data = JSON.parse(content) as ConnectedProvidersCache
log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt })
memConnected = data.connected
return data.connected
} catch (err) {
log("[connected-providers-cache] Error reading cache", { error: String(err) })
memConnected = null
return null
}
} }
function hasConnectedProvidersCache(): boolean { function hasConnectedProvidersCache(): boolean {
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE) return connectedProvidersCacheStore.has()
return existsSync(cacheFile)
} }
function writeConnectedProvidersCache(connected: string[]): void { function writeConnectedProvidersCache(connected: string[]): void {
ensureCacheDir() connectedProvidersCacheStore.write({
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE)
const data: ConnectedProvidersCache = {
connected, connected,
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
} })
try {
writeFileSync(cacheFile, JSON.stringify(data, null, 2))
memConnected = connected
log("[connected-providers-cache] Cache written", { count: connected.length })
} catch (err) {
log("[connected-providers-cache] Error writing cache", { error: String(err) })
}
} }
function readProviderModelsCache(): ProviderModelsCache | null { function readProviderModelsCache(): ProviderModelsCache | null {
if (memProviderModels !== undefined) return memProviderModels return providerModelsCacheStore.read()
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
if (!existsSync(cacheFile)) {
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile })
memProviderModels = null
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const data = JSON.parse(content) as ProviderModelsCache
log("[connected-providers-cache] Read provider-models cache", {
providerCount: Object.keys(data.models).length,
updatedAt: data.updatedAt,
})
memProviderModels = data
return data
} catch (err) {
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) })
memProviderModels = null
return null
}
} }
function hasProviderModelsCache(): boolean { function hasProviderModelsCache(): boolean {
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE) return providerModelsCacheStore.has()
return existsSync(cacheFile)
} }
function writeProviderModelsCache(data: { models: Record<string, string[] | ModelMetadata[]>; connected: string[] }): void { function writeProviderModelsCache(data: { models: Record<string, string[] | ModelMetadata[]>; connected: string[] }): void {
ensureCacheDir() providerModelsCacheStore.write({
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE)
const cacheData: ProviderModelsCache = {
...data, ...data,
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
} })
try {
writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2))
memProviderModels = cacheData
log("[connected-providers-cache] Provider-models cache written", {
providerCount: Object.keys(data.models).length,
})
} catch (err) {
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) })
}
} }
async function updateConnectedProvidersCache(client: { async function updateConnectedProvidersCache(client: {
@@ -223,8 +159,8 @@ export function createConnectedProvidersCacheStore(
} }
function _resetMemCacheForTesting(): void { function _resetMemCacheForTesting(): void {
memConnected = undefined connectedProvidersCacheStore.resetMemory()
memProviderModels = undefined providerModelsCacheStore.resetMemory()
} }
return { return {
@@ -256,7 +192,7 @@ export function findProviderModelMetadata(
continue continue
} }
if (entry?.id === modelID) { if (entry.id === modelID) {
return entry return entry
} }
} }
+98
View File
@@ -0,0 +1,98 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
import { join } from "node:path"
import { log } from "./logger"
type JsonFileCacheStoreOptions<TValue> = {
getCacheDir: () => string
filename: string
logPrefix: string
cacheLabel: string
describe: (value: TValue) => Record<string, unknown>
serialize?: (value: TValue) => string
}
type JsonFileCacheStore<TValue> = {
read: () => TValue | null
has: () => boolean
write: (value: TValue) => void
resetMemory: () => void
}
function toLogLabel(cacheLabel: string): string {
return cacheLabel.toLowerCase()
}
export function createJsonFileCacheStore<TValue>(
options: JsonFileCacheStoreOptions<TValue>,
): JsonFileCacheStore<TValue> {
let memoryValue: TValue | null | undefined
function getCacheFilePath(): string {
return join(options.getCacheDir(), options.filename)
}
function ensureCacheDir(): void {
const cacheDir = options.getCacheDir()
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true })
}
}
function read(): TValue | null {
if (memoryValue !== undefined) {
return memoryValue
}
const cacheFile = getCacheFilePath()
if (!existsSync(cacheFile)) {
memoryValue = null
log(`[${options.logPrefix}] ${options.cacheLabel} file not found`, { cacheFile })
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const value = JSON.parse(content) as TValue
memoryValue = value
log(`[${options.logPrefix}] Read ${toLogLabel(options.cacheLabel)}`, options.describe(value))
return value
} catch (error) {
memoryValue = null
log(`[${options.logPrefix}] Error reading ${toLogLabel(options.cacheLabel)}`, {
error: String(error),
})
return null
}
}
function has(): boolean {
return existsSync(getCacheFilePath())
}
function write(value: TValue): void {
ensureCacheDir()
const cacheFile = getCacheFilePath()
try {
writeFileSync(cacheFile, options.serialize?.(value) ?? JSON.stringify(value, null, 2))
memoryValue = value
log(`[${options.logPrefix}] ${options.cacheLabel} written`, options.describe(value))
} catch (error) {
log(`[${options.logPrefix}] Error writing ${toLogLabel(options.cacheLabel)}`, {
error: String(error),
})
}
}
function resetMemory(): void {
memoryValue = undefined
}
return {
read,
has,
write,
resetMemory,
}
}
+15 -50
View File
@@ -1,7 +1,5 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
import { join } from "path"
import * as dataPath from "./data-path" import * as dataPath from "./data-path"
import { log } from "./logger" import { createJsonFileCacheStore } from "./json-file-cache-store"
import type { ModelCapabilitiesSnapshot, ModelCapabilitiesSnapshotEntry } from "./model-capabilities" import type { ModelCapabilitiesSnapshot, ModelCapabilitiesSnapshotEntry } from "./model-capabilities"
export const MODELS_DEV_SOURCE_URL = "https://models.dev/api.json" export const MODELS_DEV_SOURCE_URL = "https://models.dev/api.json"
@@ -162,61 +160,28 @@ export async function fetchModelCapabilitiesSnapshot(args: {
export function createModelCapabilitiesCacheStore( export function createModelCapabilitiesCacheStore(
getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir, getCacheDir: () => string = dataPath.getOmoOpenCodeCacheDir,
) { ) {
let memSnapshot: ModelCapabilitiesSnapshot | null | undefined const snapshotCacheStore = createJsonFileCacheStore<ModelCapabilitiesSnapshot>({
getCacheDir,
function getCacheFilePath(): string { filename: MODEL_CAPABILITIES_CACHE_FILE,
return join(getCacheDir(), MODEL_CAPABILITIES_CACHE_FILE) logPrefix: "model-capabilities-cache",
} cacheLabel: "Cache",
describe: (snapshot) => ({
function ensureCacheDir(): void { modelCount: Object.keys(snapshot.models).length,
const cacheDir = getCacheDir() generatedAt: snapshot.generatedAt,
if (!existsSync(cacheDir)) { }),
mkdirSync(cacheDir, { recursive: true }) serialize: (snapshot) => `${JSON.stringify(snapshot, null, 2)}\n`,
} })
}
function readModelCapabilitiesCache(): ModelCapabilitiesSnapshot | null { function readModelCapabilitiesCache(): ModelCapabilitiesSnapshot | null {
if (memSnapshot !== undefined) { return snapshotCacheStore.read()
return memSnapshot
}
const cacheFile = getCacheFilePath()
if (!existsSync(cacheFile)) {
memSnapshot = null
log("[model-capabilities-cache] Cache file not found", { cacheFile })
return null
}
try {
const content = readFileSync(cacheFile, "utf-8")
const snapshot = JSON.parse(content) as ModelCapabilitiesSnapshot
memSnapshot = snapshot
log("[model-capabilities-cache] Read cache", {
modelCount: Object.keys(snapshot.models).length,
generatedAt: snapshot.generatedAt,
})
return snapshot
} catch (error) {
memSnapshot = null
log("[model-capabilities-cache] Error reading cache", { error: String(error) })
return null
}
} }
function hasModelCapabilitiesCache(): boolean { function hasModelCapabilitiesCache(): boolean {
return existsSync(getCacheFilePath()) return snapshotCacheStore.has()
} }
function writeModelCapabilitiesCache(snapshot: ModelCapabilitiesSnapshot): void { function writeModelCapabilitiesCache(snapshot: ModelCapabilitiesSnapshot): void {
ensureCacheDir() snapshotCacheStore.write(snapshot)
const cacheFile = getCacheFilePath()
writeFileSync(cacheFile, JSON.stringify(snapshot, null, 2) + "\n")
memSnapshot = snapshot
log("[model-capabilities-cache] Cache written", {
modelCount: Object.keys(snapshot.models).length,
generatedAt: snapshot.generatedAt,
})
} }
async function refreshModelCapabilitiesCache(args: { async function refreshModelCapabilitiesCache(args: {