or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

async-config.mdconfig-utilities.mdcore-config.mddeferred-config.mdenvironment-integration.mdindex.mdraw-config.md
tile.json

async-config.mddocs/

Async Configuration

Support for asynchronous configuration loading with promise-based resolution and deferred initialization. Allows configuration values to be resolved asynchronously from external sources like databases, APIs, or files.

Capabilities

Async Configuration Creation

Create configuration values that resolve asynchronously using promises or functions.

/**
 * Create async configuration value from promise or function
 * @param promiseOrFunc - Promise that resolves to value, or function returning promise
 * @returns Async configuration object with resolution capability
 */
function asyncConfig(promiseOrFunc: Promise<any> | ((config: object, original: any) => Promise<any>)): AsyncConfig;

interface AsyncConfig extends Promise<any> {
  async: Symbol;           // Marker identifying async configurations
  prepare: Function;       // Internal preparation function
  release?: Function;      // Release function for function-based async configs
}

Usage Examples:

const { asyncConfig } = require('config/async');

// From promise
const dbConfig = asyncConfig(
  fetch('/api/database-config')
    .then(response => response.json())
);

// From function (also supports deferConfig functionality)
const apiKey = asyncConfig(function(config, original) {
  return fetchSecretFromVault(config.vault.url);
});

// From async function
const cacheConfig = asyncConfig(async function(config, original) {
  const env = config.environment;
  const cacheSettings = await loadCacheSettingsForEnv(env);
  return cacheSettings;
});

Async Configuration Resolution

Resolve all async configurations in a config object, returning a promise that resolves when all async values are loaded.

/**
 * Resolve all async configurations in config object
 * @param config - Configuration object containing async values
 * @returns Promise that resolves to the original config object with all async values resolved
 */
function resolveAsyncConfigs(config: object): Promise<object>;

Usage Examples:

const config = require('config');
const { resolveAsyncConfigs } = require('config/async');

// Resolve all async configurations
resolveAsyncConfigs(config)
  .then(resolvedConfig => {
    console.log('All async configs loaded');
    console.log('Database host:', resolvedConfig.database.host);
    console.log('API key:', resolvedConfig.api.key);
    
    // Start application after config is fully loaded
    startApplication(resolvedConfig);
  })
  .catch(error => {
    console.error('Failed to load async configurations:', error);
    process.exit(1);
  });

// Using async/await
async function initializeApp() {
  try {
    const resolvedConfig = await resolveAsyncConfigs(config);
    console.log('Configuration loaded successfully');
    startApplication(resolvedConfig);
  } catch (error) {
    console.error('Configuration loading failed:', error);
    process.exit(1);
  }
}

Configuration File Integration

Async configurations can be defined directly in configuration files and will be resolved when resolveAsyncConfigs is called.

Usage Examples:

// In config/default.js
const { asyncConfig } = require('config/async');

module.exports = {
  database: {
    host: 'localhost',
    port: 5432,
    // Async configuration for database credentials
    credentials: asyncConfig(async function(config) {
      const vault = config.vault;
      return await fetchCredentialsFromVault(vault.url, vault.path);
    })
  },
  
  api: {
    baseUrl: 'https://api.example.com',
    // Async configuration from promise
    token: asyncConfig(
      fetch('/oauth/token', { method: 'POST', body: '...' })
        .then(response => response.json())
        .then(data => data.access_token)
    )
  },
  
  cache: {
    // Mixed sync and async configuration
    enabled: true,
    ttl: 3600,
    redis: asyncConfig(async function(config) {
      const env = process.env.NODE_ENV;
      return await loadRedisConfigForEnvironment(env);
    })
  }
};

// In application code
const config = require('config');
const { resolveAsyncConfigs } = require('config/async');

async function startApp() {
  // Resolve all async configurations
  await resolveAsyncConfigs(config);
  
  // Now all config values are available synchronously
  const dbCredentials = config.get('database.credentials');
  const apiToken = config.get('api.token');
  const redisConfig = config.get('cache.redis');
  
  console.log('All configurations loaded');
}

Important Usage Notes

Critical considerations when using async configurations.

Usage Examples:

const config = require('config');
const { resolveAsyncConfigs } = require('config/async');

// IMPORTANT: Do NOT use config.get() before resolving async configs
// This will freeze the config object and prevent async resolution

// ❌ WRONG - calling get() before resolveAsyncConfigs
const someValue = config.get('some.property'); // This freezes config
resolveAsyncConfigs(config); // This will fail

// ✅ CORRECT - resolve async configs first
resolveAsyncConfigs(config)
  .then(() => {
    const someValue = config.get('some.property'); // Now it's safe
  });

// ✅ CORRECT - check for async configs before any access
async function safeConfigAccess() {
  await resolveAsyncConfigs(config);
  
  // Now safe to use any config access method
  const dbConfig = config.get('database');
  const hasCache = config.has('cache.enabled');
  const apiUrl = config.api.baseUrl;
}

// Function-based async configs support release mechanism
const delayedConfig = asyncConfig(function(config, original) {
  return new Promise(resolve => {
    setTimeout(() => resolve('delayed-value'), 1000);
  });
});

// Release mechanism is automatically handled during resolution

Error Handling

Proper error handling for async configuration resolution failures.

// Async resolution can fail if any promise rejects
// Handle errors appropriately in application code

Usage Examples:

const { resolveAsyncConfigs } = require('config/async');

// Handle individual async config failures
const resilientAsyncConfig = asyncConfig(async function(config) {
  try {
    return await fetchExternalConfig();
  } catch (error) {
    console.warn('Failed to load external config, using defaults:', error.message);
    return { timeout: 5000, retries: 3 }; // fallback values
  }
});

// Handle overall resolution failures
async function loadConfigWithFallback() {
  try {
    await resolveAsyncConfigs(config);
    console.log('All async configurations loaded successfully');
  } catch (error) {
    console.error('Some async configurations failed to load:', error);
    
    // Decide whether to continue with partial config or exit
    if (isConfigCritical(error)) {
      console.error('Critical configuration missing, exiting');
      process.exit(1);
    } else {
      console.log('Continuing with available configuration');
    }
  }
}

// Timeout handling for slow async configs
function createTimeoutAsyncConfig(promise, timeoutMs = 10000) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Async config timeout')), timeoutMs);
  });
  
  return asyncConfig(Promise.race([promise, timeoutPromise]));
}