Welcome to the final chapter of our Policy Limits series!
In Chapter 4: Resilient API Fetcher, we built a robust system to fetch rules from the server, handling errors and retries gracefully. We have the "courier" ready to deliver the package.
Now we must answer the question of Timing:
In this chapter, we will build the Lifecycle Manager. It is the conductor of the orchestra, ensuring policies are loaded before the show starts and keeping the music playing in sync.
Imagine you are a pilot. Before you take off, you must complete a Pre-Flight Checklist.
Our CLI tool works the same way:
We will use three main programming concepts to manage this lifecycle:
Here is the high-level flow we want to achieve in our application entry point (init.ts).
// Ideally, our app startup looks like this:
async function startApp() {
// 1. Start loading policies immediately (don't await yet!)
initializePolicyLimitsLoadingPromise();
// 2. Do other setup tasks...
setupDatabase();
// 3. Kick off the actual fetch
loadPolicyLimits();
// 4. WAIT. Ensure policies are loaded before running the command
await waitForPolicyLimitsToLoad();
// 5. Run the user's command
runCommand();
}
Let's look at how the Lifecycle Manager orchestrates these events step-by-step.
We will break the implementation down into the Loader, the Waiter, and the Poller.
We need a global variable to track if we have finished loading. We use a standard JavaScript Promise.
// Global variables to track state
let loadingCompletePromise: Promise<void> | null = null;
let loadingCompleteResolve: (() => void) | null = null;
export function initializePolicyLimitsLoadingPromise() {
// If we already started, do nothing
if (loadingCompletePromise) return;
// Create a new Promise that stays pending...
loadingCompletePromise = new Promise(resolve => {
// Save the 'resolve' function so we can call it later
loadingCompleteResolve = resolve;
});
}
Explanation: We create a promise but we don't resolve it immediately. We save the resolve function into a variable (loadingCompleteResolve). This acts like a red light that stays red until we flip the switch.
This function calls the logic we built in Chapter 4. Once the fetch is done, it flips the switch to green.
export async function loadPolicyLimits() {
// 1. Ensure the promise exists
if (!loadingCompletePromise) {
initializePolicyLimitsLoadingPromise();
}
try {
// 2. Perform the actual fetch (from Chapter 4)
await fetchAndLoadPolicyLimits();
// 3. Start the background heartbeat
startBackgroundPolling();
} finally {
// 4. Flip the switch! Unlock the app.
if (loadingCompleteResolve) {
loadingCompleteResolve();
loadingCompleteResolve = null;
}
}
}
Explanation: Even if fetchAndLoadPolicyLimits fails (remember, we "Fail Open"), the finally block ensures we resolve the promise. The app will never hang forever; it will eventually proceed, with or without rules.
Other parts of the application call this function to ensure they don't run too early.
export async function waitForPolicyLimitsToLoad() {
// If the promise exists, wait for it to finish
if (loadingCompletePromise) {
await loadingCompletePromise;
}
// If no promise exists, it means we don't need to check policies. Proceed.
}
Explanation: This is a simple wrapper. If the loading process is active, we await it. If it's already done (or never started), we return immediately.
We don't want the rules to get stale. We set up a timer to refresh them periodically.
let pollingIntervalId: ReturnType<typeof setInterval> | null = null;
const ONE_HOUR = 60 * 60 * 1000;
export function startBackgroundPolling() {
// Prevent starting two timers
if (pollingIntervalId) return;
// Set the timer
pollingIntervalId = setInterval(async () => {
// Silently fetch updates
await fetchAndLoadPolicyLimits();
}, ONE_HOUR);
// IMPORTANT: Allow the program to exit even if this timer is running
pollingIntervalId.unref();
}
Explanation:
setInterval runs our fetch logic every hour..unref() is a special Node.js command. Usually, a running timer keeps the program alive forever. unref() says, "If the user tries to quit the app, don't let this timer stop them."For testing or resetting the state (e.g., when a user logs out), we need to stop everything.
export function stopBackgroundPolling() {
if (pollingIntervalId) {
clearInterval(pollingIntervalId);
pollingIntervalId = null;
}
}
We have now built a complete lifecycle system:
initializePolicyLimitsLoadingPromise() prepares the gate.loadPolicyLimits() fetches data from Chapter 4, caches it using Chapter 3, and opens the gate.startBackgroundPolling() keeps data fresh.waitForPolicyLimitsToLoad() ensures the Policy Enforcer from Chapter 2 has data before making decisions.Congratulations! You have completed the Policy Limits tutorial series.
You have built a professional-grade feature flagging system from scratch. Let's review what you've accomplished:
This architecture ensures that your application is secure, fast, and reliable, providing a great experience for both personal users and enterprise security teams.
Happy coding!
Generated by Code IQ