|
1 | 1 | import { writeClient } from "@/lib/sanity-write-client"; |
2 | | -import type { ConfigTable, ConfigTypeMap } from "@/lib/types/config"; |
| 2 | +import type { EngineConfig } from "@/lib/types/engine-config"; |
3 | 3 |
|
4 | | -/** |
5 | | - * Sanity config module with in-memory caching. |
6 | | - * |
7 | | - * Each config "table" maps to a Sanity singleton document type. |
8 | | - * Uses writeClient.fetch for server-side reads. |
9 | | - * |
10 | | - * Caching: 5-minute TTL with stale-while-revalidate. |
11 | | - * Sanity changes propagate on next cache miss. |
12 | | - */ |
| 4 | +const DEFAULT_TTL_MS = 5 * 60 * 1000; |
13 | 5 |
|
14 | | -const DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes |
15 | | - |
16 | | -interface CacheEntry<T> { |
17 | | - data: T; |
| 6 | +interface CacheEntry { |
| 7 | + data: EngineConfig; |
18 | 8 | fetchedAt: number; |
19 | 9 | refreshing: boolean; |
20 | 10 | } |
21 | 11 |
|
22 | | -const cache = new Map<string, CacheEntry<unknown>>(); |
23 | | - |
24 | | -// Map config table names to Sanity document type names |
25 | | -const TABLE_TO_TYPE: Record<ConfigTable, string> = { |
26 | | - pipeline_config: "pipelineConfig", |
27 | | - remotion_config: "remotionConfig", |
28 | | - content_config: "contentConfig", |
29 | | - sponsor_config: "sponsorConfig", |
30 | | - distribution_config: "distributionConfig", |
31 | | - gcs_config: "gcsConfig", |
32 | | -}; |
| 12 | +let cache: CacheEntry | null = null; |
33 | 13 |
|
34 | | -async function refreshConfig<T extends ConfigTable>( |
35 | | - table: T, |
36 | | -): Promise<ConfigTypeMap[T]> { |
37 | | - const sanityType = TABLE_TO_TYPE[table]; |
38 | | - const data = await writeClient.fetch( |
39 | | - `*[_type == $type][0]`, |
40 | | - { type: sanityType } as Record<string, unknown>, |
| 14 | +async function refreshConfig(): Promise<EngineConfig> { |
| 15 | + const data = await writeClient.fetch<EngineConfig>( |
| 16 | + `*[_type == "engineConfig"][0]` |
41 | 17 | ); |
42 | | - |
43 | 18 | if (!data) { |
44 | | - throw new Error(`Config not found for ${sanityType} — create the singleton document in Sanity Studio`); |
| 19 | + throw new Error( |
| 20 | + "engineConfig singleton not found — create it in Sanity Studio" |
| 21 | + ); |
45 | 22 | } |
46 | | - |
47 | | - cache.set(table, { |
48 | | - data, |
49 | | - fetchedAt: Date.now(), |
50 | | - refreshing: false, |
51 | | - }); |
52 | | - |
53 | | - return data as ConfigTypeMap[T]; |
| 23 | + cache = { data, fetchedAt: Date.now(), refreshing: false }; |
| 24 | + return data; |
54 | 25 | } |
55 | 26 |
|
56 | | -export async function getConfig<T extends ConfigTable>( |
57 | | - table: T, |
58 | | - ttlMs = DEFAULT_TTL_MS, |
59 | | -): Promise<ConfigTypeMap[T]> { |
60 | | - const cached = cache.get(table) as CacheEntry<ConfigTypeMap[T]> | undefined; |
| 27 | +export async function getEngineConfig( |
| 28 | + ttlMs = DEFAULT_TTL_MS |
| 29 | +): Promise<EngineConfig> { |
61 | 30 | const now = Date.now(); |
62 | | - |
63 | | - // Fresh cache — return immediately |
64 | | - if (cached && now - cached.fetchedAt < ttlMs) { |
65 | | - return cached.data; |
66 | | - } |
67 | | - |
68 | | - // Stale cache — return stale, refresh in background |
69 | | - if (cached && !cached.refreshing) { |
70 | | - cached.refreshing = true; |
71 | | - refreshConfig(table).catch((err) => { |
72 | | - console.error(`[config] Background refresh failed for ${table}:`, err); |
73 | | - const entry = cache.get(table) as CacheEntry<unknown> | undefined; |
74 | | - if (entry) entry.refreshing = false; |
| 31 | + if (cache && now - cache.fetchedAt < ttlMs) return cache.data; |
| 32 | + if (cache && !cache.refreshing) { |
| 33 | + cache.refreshing = true; |
| 34 | + refreshConfig().catch((err) => { |
| 35 | + console.error("[config] Background refresh failed:", err); |
| 36 | + if (cache) cache.refreshing = false; |
75 | 37 | }); |
76 | | - return cached.data; |
| 38 | + return cache.data; |
77 | 39 | } |
78 | | - |
79 | | - // No cache — must fetch synchronously |
80 | | - return refreshConfig(table); |
| 40 | + return refreshConfig(); |
81 | 41 | } |
82 | 42 |
|
83 | | -/** |
84 | | - * Get a single config value with optional env var fallback. |
85 | | - * Useful during migration period. |
86 | | - */ |
87 | | -export async function getConfigValue< |
88 | | - T extends ConfigTable, |
89 | | - K extends keyof ConfigTypeMap[T], |
90 | | ->( |
91 | | - table: T, |
| 43 | +export function getEngineConfigValue<K extends keyof EngineConfig>( |
| 44 | + config: EngineConfig, |
92 | 45 | key: K, |
93 | | - fallback?: ConfigTypeMap[T][K], |
94 | | -): Promise<ConfigTypeMap[T][K]> { |
95 | | - try { |
96 | | - const config = await getConfig(table); |
97 | | - const value = config[key]; |
98 | | - // Use fallback when field is undefined/null (not yet set in Sanity) |
99 | | - if (value === undefined || value === null) { |
100 | | - if (fallback !== undefined) return fallback; |
101 | | - } |
102 | | - return value; |
103 | | - } catch { |
| 46 | + fallback?: EngineConfig[K] |
| 47 | +): EngineConfig[K] { |
| 48 | + const value = config[key]; |
| 49 | + if (value === undefined || value === null) { |
104 | 50 | if (fallback !== undefined) return fallback; |
105 | | - throw new Error(`Config value ${String(key)} not found in ${table}`); |
106 | 51 | } |
| 52 | + return value; |
107 | 53 | } |
108 | 54 |
|
109 | | -/** |
110 | | - * Force-clear cached config. Called when config is known to have changed. |
111 | | - */ |
112 | | -export function invalidateConfig(table?: ConfigTable) { |
113 | | - if (table) { |
114 | | - cache.delete(table); |
115 | | - } else { |
116 | | - cache.clear(); |
117 | | - } |
| 55 | +export function invalidateEngineConfig() { |
| 56 | + cache = null; |
| 57 | +} |
| 58 | + |
| 59 | +// Backward compatibility wrapper — old code calls getConfig('pipeline_config'). |
| 60 | +// Ignores the table name and returns the unified engineConfig. |
| 61 | +// Remove after all callers are migrated to getEngineConfig() (Task 1F). |
| 62 | +export async function getConfig(_tableName?: string): Promise<EngineConfig> { |
| 63 | + return getEngineConfig(); |
| 64 | +} |
| 65 | + |
| 66 | +// Backward compatibility wrapper — old code calls getConfigValue('pipeline_config', 'geminiModel'). |
| 67 | +// Ignores the table name and reads from the unified engineConfig. |
| 68 | +// Remove after all callers are migrated (Task 1F). |
| 69 | +export async function getConfigValue<K extends keyof EngineConfig>( |
| 70 | + _tableName: string, |
| 71 | + key: K, |
| 72 | + fallback?: EngineConfig[K], |
| 73 | +): Promise<EngineConfig[K]> { |
| 74 | + const config = await getEngineConfig(); |
| 75 | + return getEngineConfigValue(config, key, fallback); |
118 | 76 | } |
0 commit comments