In the previous chapter, Live Change Detection, we built a system that watches for file changes and triggers updates instantly.
However, we created a potential performance problem. If the application checks the configuration 100 times a second (e.g., "Is debug mode on?"), we do not want to read the hard drive 100 times a second. Reading from a disk is thousands of times slower than reading from memory.
Imagine you are writing a research paper.
The Settings Caching Strategy is that notepad. It stores the parsed, validated, and merged settings in the computer's RAM (Random Access Memory) so the application runs smoothly.
We use three levels of caching to ensure speed and consistency.
This is the "Top Sheet" we learned about in Chapter 1. It is the final, merged configuration object ready for use. 99% of the time, the application reads from here.
Sometimes, we need to know specifically what is in the userSettings file versus the projectSettings file (perhaps to display a UI showing where a setting comes from). We cache these individual layers separately.
The most important part of caching is knowing when to delete the cache. If the user edits a file (detected in Chapter 5), our "notepad" is now outdated. We must throw it away and go back to the library.
As a consumer of the settings module, you don't manually write to the cache. You simply ask for settings, and the system handles the rest.
When you call getInitialSettings(), the system performs this check:
sessionSettingsCache full?The cache is stored in a simple variable.
import { getSessionSettingsCache } from './settingsCache'
// 1. Try to get cached settings
const cached = getSessionSettingsCache()
if (cached) {
console.log("Read from memory! Fast!")
} else {
console.log("Cache empty. Must read from disk.")
}
Let's look at the lifecycle of a setting request, from the first load to a file update.
The implementation is surprisingly simple. It relies on module-level variables in src/settings/settingsCache.ts.
This variable holds the final result.
// settingsCache.ts
import type { SettingsWithErrors } from './validation.js'
// The "Notepad" - initially empty
let sessionSettingsCache: SettingsWithErrors | null = null
export function getSessionSettingsCache() {
return sessionSettingsCache
}
When the Settings Engine finishes the hard work of merging files, it saves the result here.
// settingsCache.ts
export function setSessionSettingsCache(value: SettingsWithErrors): void {
// Save the result for next time
sessionSettingsCache = value
}
We use a Map to store individual layers (like userSettings or projectSettings). This prevents us from re-reading the User Settings file if only the Project Settings file changed.
import type { SettingSource } from './constants.js'
// Maps a source name (e.g. 'userSettings') to its JSON content
const perSourceCache = new Map<SettingSource, SettingsJson | null>()
export function getCachedSettingsForSource(source: SettingSource) {
// Returns undefined if we haven't loaded this source yet
return perSourceCache.has(source)
? perSourceCache.get(source)
: undefined
}
This is the function called by the Watcher from Chapter 5. When a file changes, we wipe everything clean.
// settingsCache.ts
export function resetSettingsCache(): void {
// 1. Wipe the final merged result
sessionSettingsCache = null
// 2. Wipe the individual layer caches
perSourceCache.clear()
// 3. Wipe the raw file parse cache
parseFileCache.clear()
}
parseFileCacheThere is one more optimization layer. Sometimes, two different logical sources might point to the same physical file.
For example, loadSettingsFromDisk (the main loader) and getSettingsForSource (a specific helper) both need to read the file. We cache the Raw Parsing step to ensure we don't parse the same JSON string twice during the same operation.
// settingsCache.ts
type ParsedSettings = {
settings: SettingsJson | null
errors: ValidationError[]
}
const parseFileCache = new Map<string, ParsedSettings>()
Note: This maps a File Path (string) to the Parsed Result.
Congratulations! You have completed the Settings project tutorial.
You have built a robust, enterprise-grade configuration system from scratch:
You now possess the knowledge to manage complex application configurations that are both user-friendly and developer-friendly. Happy coding!
Generated by Code IQ