or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

alfred-integration.mdconfiguration-storage.mderror-handling.mdhttp-client.mdindex.mdinput-output.mdstring-matching.md
tile.json

configuration-storage.mddocs/

Configuration and Storage

Persistent configuration and caching system with automatic cleanup and workflow-specific data management. Alfy provides three storage mechanisms: workflow configuration, user configuration, and cached data with TTL support.

Capabilities

Workflow Configuration

Persistent storage for workflow-specific configuration data using the conf library.

/**
 * Workflow configuration storage
 * Conf instance with workflow-specific data directory
 */
config: Conf;

Usage Examples:

import alfy from "alfy";

// Store configuration values
alfy.config.set('apiKey', 'your-api-key-here');
alfy.config.set('theme', 'dark');
alfy.config.set('maxResults', 10);

// Store complex objects
alfy.config.set('preferences', {
  notifications: true,
  autoUpdate: false,
  language: 'en'
});

// Retrieve configuration values
const apiKey = alfy.config.get('apiKey');
const theme = alfy.config.get('theme', 'light'); // With default value
const maxResults = alfy.config.get('maxResults');

console.log(`Using theme: ${theme}, showing ${maxResults} results`);

// Check if configuration exists
if (alfy.config.has('apiKey')) {
  // Use API key...
} else {
  // Prompt user to configure API key...
}

// Remove configuration
alfy.config.delete('oldSetting');

// Clear all configuration
alfy.config.clear();

User Configuration

Access to user-defined workflow configuration variables set through Alfred's workflow configuration interface.

/**
 * User workflow configuration
 * Map instance containing user-configured values
 */
userConfig: Map<string, string>;

Usage Examples:

import alfy from "alfy";

// Get user-configured API token
const apiToken = alfy.userConfig.get('apiToken');
if (!apiToken) {
  alfy.error('Please configure your API token in the workflow settings');
  return;
}

// Get optional configuration with defaults
const baseUrl = alfy.userConfig.get('baseUrl') || 'https://api.example.com';
const timeout = parseInt(alfy.userConfig.get('timeout') || '5000');

// Check if user has configured specific options
if (alfy.userConfig.has('customEndpoint')) {
  const endpoint = alfy.userConfig.get('customEndpoint');
  console.log(`Using custom endpoint: ${endpoint}`);
}

// Iterate through all user configuration
for (const [key, value] of alfy.userConfig) {
  console.log(`User config ${key}: ${value}`);
}

// Use in API requests
const response = await alfy.fetch(`${baseUrl}/data`, {
  headers: {
    'Authorization': `Bearer ${apiToken}`,
    'User-Agent': alfy.userConfig.get('userAgent') || 'Alfy Workflow'
  },
  timeout: { request: timeout }
});

Cache Storage

High-performance caching with TTL (time-to-live) support and automatic cleanup.

/**
 * Cache storage with TTL support
 * CacheConf instance extending Conf with cache-specific methods
 */
cache: CacheConf;

interface CacheConf extends Conf {
  /** Check if a cached item has expired */
  isExpired(key: string): boolean;
  
  /** Get cached value with optional default and maxAge override */
  get<T>(key: string, defaultValue?: T, options?: CacheGetOptions): T;
  
  /** Set cached value with optional TTL */
  set(key: string, value: unknown, options?: CacheSetOptions): void;
}

interface CacheGetOptions {
  /** Ignore maxAge and return expired items */
  ignoreMaxAge?: boolean;
}

interface CacheSetOptions {
  /** Number of milliseconds the cached value is valid */
  maxAge?: number;
}

Basic Caching:

import alfy from "alfy";

// Store data in cache
alfy.cache.set('userList', users);
alfy.cache.set('lastUpdate', Date.now());

// Retrieve cached data
const cachedUsers = alfy.cache.get('userList');
const lastUpdate = alfy.cache.get('lastUpdate', 0); // With default

if (cachedUsers) {
  console.log(`Found ${cachedUsers.length} cached users`);
} else {
  console.log('No cached users found');
}

TTL Caching:

import alfy from "alfy";

// Cache with 5-minute expiration
alfy.cache.set('apiResponse', data, {
  maxAge: 5 * 60 * 1000 // 5 minutes in milliseconds
});

// Cache with 1-hour expiration
alfy.cache.set('configuration', config, {
  maxAge: 60 * 60 * 1000 // 1 hour
});

// Check if cache has expired
if (alfy.cache.isExpired('apiResponse')) {
  console.log('API response cache has expired');
  // Fetch fresh data...
} else {
  const cached = alfy.cache.get('apiResponse');
  console.log('Using cached API response');
}

// Get cached data even if expired
const staleData = alfy.cache.get('apiResponse', null, {
  ignoreMaxAge: true
});

Advanced Caching Patterns:

import alfy from "alfy";

// Cache with fallback and refresh pattern
async function getCachedData(cacheKey, fetchFunction, maxAge = 60000) {
  // Try to get cached data
  if (!alfy.cache.isExpired(cacheKey)) {
    return alfy.cache.get(cacheKey);
  }
  
  try {
    // Fetch fresh data
    const freshData = await fetchFunction();
    
    // Cache the fresh data
    alfy.cache.set(cacheKey, freshData, { maxAge });
    
    return freshData;
  } catch (error) {
    // Return stale cached data if fetch fails
    const staleData = alfy.cache.get(cacheKey, null, { ignoreMaxAge: true });
    if (staleData) {
      console.log('Using stale cached data due to fetch error');
      return staleData;
    }
    throw error;
  }
}

// Usage
const userData = await getCachedData(
  'users',
  () => alfy.fetch('https://api.example.com/users'),
  10 * 60 * 1000 // 10 minutes
);

Cache Management:

import alfy from "alfy";

// Clear specific cache entries
alfy.cache.delete('expiredData');

// Clear all cache
alfy.cache.clear();

// Get cache statistics
const cacheSize = alfy.cache.size;
console.log(`Cache contains ${cacheSize} items`);

// Check what's in cache
for (const [key, value] of alfy.cache) {
  const expired = alfy.cache.isExpired(key);
  console.log(`Cache key "${key}": ${expired ? 'expired' : 'valid'}`);
}

// Cleanup expired entries manually (usually automatic)
const allKeys = [...alfy.cache.keys()];
for (const key of allKeys) {
  if (alfy.cache.isExpired(key)) {
    alfy.cache.delete(key);
  }
}

Storage Locations

All storage uses Alfred's recommended directories:

  • config: ~/Library/Application Support/Alfred/Workflow Data/{bundle-id}/
  • userConfig: Managed by Alfred's workflow configuration system
  • cache: ~/Library/Caches/com.runningwithcrayons.Alfred/Workflow Data/{bundle-id}/

Automatic Cleanup

Alfy automatically handles:

  • Version-based cleanup: Cache is cleared when workflow version changes
  • Expired cache cleanup: Automatically removes expired cache entries
  • Directory management: Creates necessary directories as needed
  • Atomic operations: Safe concurrent access to storage

Best Practices

Configuration Storage:

import alfy from "alfy";

// Use hierarchical configuration
alfy.config.set('api.endpoints.primary', 'https://api.example.com');
alfy.config.set('api.endpoints.fallback', 'https://backup.example.com');
alfy.config.set('ui.theme', 'dark');
alfy.config.set('ui.maxResults', 20);

// Validate configuration on startup
function validateConfig() {
  const required = ['api.token', 'api.endpoints.primary'];
  for (const key of required) {
    if (!alfy.config.has(key)) {
      throw new Error(`Missing required configuration: ${key}`);
    }
  }
}

Smart Caching:

import alfy from "alfy";

// Cache different data with appropriate TTLs
const CACHE_DURATIONS = {
  userProfile: 60 * 60 * 1000,      // 1 hour - relatively stable
  searchResults: 5 * 60 * 1000,     // 5 minutes - can change frequently  
  configuration: 24 * 60 * 60 * 1000, // 24 hours - very stable
  realtimeData: 30 * 1000            // 30 seconds - highly volatile
};

// Use structured cache keys
function cacheKey(type, ...parts) {
  return `${type}:${parts.join(':')}`;
}

// Cache search results with user-specific keys
const searchKey = cacheKey('search', alfy.input, 'page1');
alfy.cache.set(searchKey, results, { maxAge: CACHE_DURATIONS.searchResults });