SimpleCacheGatewaysProvider

Overview

The SimpleCacheGatewaysProvider wraps another gateway provider and caches the results for improved performance. It reduces the overhead of repeated gateway discovery calls while maintaining the benefits of dynamic gateway providers.

Installation

npm install @ar.io/wayfinder-core

Basic Usage

import {
  SimpleCacheGatewaysProvider,
  NetworkGatewaysProvider,
} from '@ar.io/wayfinder-core'
import { ARIO } from '@ar.io/sdk'

const provider = new SimpleCacheGatewaysProvider({
  gatewaysProvider: new NetworkGatewaysProvider({
    ario: ARIO.mainnet(),
  }),
  ttlSeconds: 60 * 60, // Cache for 1 hour
  maxCacheSize: 1000,
})

const gateways = await provider.getGateways()
console.log('Available gateways:', gateways)

Configuration Options

SimpleCacheGatewaysProviderOptions

interface SimpleCacheGatewaysProviderOptions {
  gatewaysProvider: GatewaysProvider
  ttlSeconds?: number
  maxCacheSize?: number
  enableBackgroundRefresh?: boolean
  refreshInterval?: number
}

Parameters

← Swipe to see more →
ParameterTypeDefaultDescription
gatewaysProviderGatewaysProviderRequiredThe provider to wrap and cache
ttlSecondsnumber300Cache TTL in seconds (5 minutes)
maxCacheSizenumber100Maximum number of cached entries
enableBackgroundRefreshbooleanfalseEnable background cache refresh
refreshIntervalnumber60000Background refresh interval in ms
← Swipe to see more →

Configuration Examples

Production Configuration

import {
  SimpleCacheGatewaysProvider,
  NetworkGatewaysProvider,
} from '@ar.io/wayfinder-core'
import { ARIO } from '@ar.io/sdk'

// Production caching with background refresh
const provider = new SimpleCacheGatewaysProvider({
  gatewaysProvider: new NetworkGatewaysProvider({
    ario: ARIO.mainnet(),
    minStake: 50000,
    maxGateways: 20,
  }),
  ttlSeconds: 30 * 60, // 30 minute cache
  maxCacheSize: 100,
  enableBackgroundRefresh: true,
  refreshInterval: 5 * 60 * 1000, // Refresh every 5 minutes
})

Development Configuration

import {
  SimpleCacheGatewaysProvider,
  StaticGatewaysProvider,
} from '@ar.io/wayfinder-core'

// Development caching with short TTL
const provider = new SimpleCacheGatewaysProvider({
  gatewaysProvider: new StaticGatewaysProvider({
    gateways: ['http://localhost:3000', 'https://arweave.net'],
  }),
  ttlSeconds: 60, // 1 minute cache for development
  maxCacheSize: 10,
})

High-Performance Configuration

import {
  SimpleCacheGatewaysProvider,
  NetworkGatewaysProvider,
} from '@ar.io/wayfinder-core'
import { ARIO } from '@ar.io/sdk'

// Optimized for high-performance applications
const provider = new SimpleCacheGatewaysProvider({
  gatewaysProvider: new NetworkGatewaysProvider({
    ario: ARIO.mainnet(),
    minStake: 100000,
    maxGateways: 10,
  }),
  ttlSeconds: 60 * 60, // 1 hour cache
  maxCacheSize: 50,
  enableBackgroundRefresh: true,
  refreshInterval: 10 * 60 * 1000, // Background refresh every 10 minutes
})

Advanced Usage

Multi-Level Caching

import {
  SimpleCacheGatewaysProvider,
  NetworkGatewaysProvider,
  StaticGatewaysProvider,
} from '@ar.io/wayfinder-core'
import { ARIO } from '@ar.io/sdk'

// Cache with fallback provider
const provider = new SimpleCacheGatewaysProvider({
  gatewaysProvider: new NetworkGatewaysProvider({
    ario: ARIO.mainnet(),
    minStake: 50000,
  }),
  ttlSeconds: 30 * 60,
  fallbackProvider: new StaticGatewaysProvider({
    gateways: ['https://arweave.net', 'https://ar-io.net'],
  }),
})

Smart Cache with Metrics

import { SimpleCacheGatewaysProvider } from '@ar.io/wayfinder-core'

class MetricsAwareCacheProvider extends SimpleCacheGatewaysProvider {
  constructor(options) {
    super(options)
    this.metrics = {
      hits: 0,
      misses: 0,
      refreshes: 0,
      errors: 0,
    }
  }

  async getGateways() {
    const startTime = Date.now()

    try {
      const gateways = await super.getGateways()

      // Track cache hit/miss
      if (this.isCacheHit()) {
        this.metrics.hits++
      } else {
        this.metrics.misses++
      }

      return gateways
    } catch (error) {
      this.metrics.errors++
      throw error
    }
  }

  async refresh() {
    try {
      await super.refresh()
      this.metrics.refreshes++
    } catch (error) {
      this.metrics.errors++
      throw error
    }
  }

  getMetrics() {
    const total = this.metrics.hits + this.metrics.misses
    return {
      ...this.metrics,
      hitRate: total > 0 ? this.metrics.hits / total : 0,
      totalRequests: total,
    }
  }

  resetMetrics() {
    this.metrics = { hits: 0, misses: 0, refreshes: 0, errors: 0 }
  }
}

// Usage
const metricsProvider = new MetricsAwareCacheProvider({
  gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
  ttlSeconds: 300,
})

// Later, check metrics
console.log('Cache metrics:', metricsProvider.getMetrics())

Persistent Cache

import { SimpleCacheGatewaysProvider } from '@ar.io/wayfinder-core'

class PersistentCacheProvider extends SimpleCacheGatewaysProvider {
  constructor(options) {
    super(options)
    this.storageKey = options.storageKey || 'wayfinder-gateway-cache'
    this.loadFromStorage()
  }

  async getGateways() {
    const gateways = await super.getGateways()
    this.saveToStorage()
    return gateways
  }

  loadFromStorage() {
    if (typeof localStorage === 'undefined') return

    try {
      const stored = localStorage.getItem(this.storageKey)
      if (stored) {
        const { gateways, timestamp } = JSON.parse(stored)
        const now = Date.now()

        // Check if stored data is still valid
        if (now - timestamp < this.options.ttlSeconds * 1000) {
          this.cachedGateways = gateways
          this.cacheTimestamp = timestamp
        }
      }
    } catch (error) {
      console.warn('Failed to load cache from storage:', error.message)
    }
  }

  saveToStorage() {
    if (typeof localStorage === 'undefined') return
    if (!this.cachedGateways) return

    try {
      const data = {
        gateways: this.cachedGateways,
        timestamp: this.cacheTimestamp,
      }
      localStorage.setItem(this.storageKey, JSON.stringify(data))
    } catch (error) {
      console.warn('Failed to save cache to storage:', error.message)
    }
  }

  clearStorage() {
    if (typeof localStorage !== 'undefined') {
      localStorage.removeItem(this.storageKey)
    }
  }
}

// Usage
const persistentProvider = new PersistentCacheProvider({
  gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
  ttlSeconds: 60 * 60, // 1 hour
  storageKey: 'my-app-gateways',
})

Methods

getGateways()

Returns cached gateways or fetches from the underlying provider if cache is expired.

const gateways = await provider.getGateways()
console.log('Gateways:', gateways)

refresh()

Forces a refresh of the cache, bypassing TTL.

await provider.refresh()
console.log('Cache refreshed')

clearCache()

Clears the current cache.

provider.clearCache()
console.log('Cache cleared')

getCacheInfo()

Returns information about the current cache state.

const info = provider.getCacheInfo()
console.log('Cache info:', info)
// { size: 1, lastUpdate: Date, ttl: 300, isExpired: false }

Error Handling

import { SimpleCacheGatewaysProvider } from '@ar.io/wayfinder-core'

const provider = new SimpleCacheGatewaysProvider({
  gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
  ttlSeconds: 300,
})

try {
  const gateways = await provider.getGateways()
  console.log('Available gateways:', gateways)
} catch (error) {
  switch (error.constructor.name) {
    case 'CacheError':
      console.error('Cache operation failed:', error.message)
      break
    case 'ProviderError':
      console.error('Underlying provider failed:', error.message)
      // Cache may still return stale data
      break
    default:
      console.error('Unknown error:', error.message)
  }
}

Performance Benefits

Cache Hit Rates

// Measure cache performance
const provider = new SimpleCacheGatewaysProvider({
  gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
  ttlSeconds: 300,
})

let hits = 0
let misses = 0

for (let i = 0; i < 10; i++) {
  const startTime = Date.now()
  const gateways = await provider.getGateways()
  const duration = Date.now() - startTime

  if (duration < 10) {
    // Likely a cache hit
    hits++
    console.log(`Request ${i + 1}: Cache hit (${duration}ms)`)
  } else {
    misses++
    console.log(`Request ${i + 1}: Cache miss (${duration}ms)`)
  }

  // Wait between requests
  await new Promise((resolve) => setTimeout(resolve, 1000))
}

console.log(`Cache hit rate: ${(hits / (hits + misses)) * 100}%`)

Testing

Unit Tests

import { SimpleCacheGatewaysProvider } from '@ar.io/wayfinder-core'

describe('SimpleCacheGatewaysProvider', () => {
  let mockProvider
  let cacheProvider

  beforeEach(() => {
    mockProvider = {
      getGateways: jest.fn().mockResolvedValue(['https://gateway.com']),
    }

    cacheProvider = new SimpleCacheGatewaysProvider({
      gatewaysProvider: mockProvider,
      ttlSeconds: 60,
    })
  })

  test('should cache gateway results', async () => {
    // First call should hit the underlying provider
    await cacheProvider.getGateways()
    expect(mockProvider.getGateways).toHaveBeenCalledTimes(1)

    // Second call should use cache
    await cacheProvider.getGateways()
    expect(mockProvider.getGateways).toHaveBeenCalledTimes(1)
  })

  test('should refresh cache when TTL expires', async () => {
    // Mock Date.now to control time
    const originalNow = Date.now
    let currentTime = 1000000
    Date.now = jest.fn(() => currentTime)

    // First call
    await cacheProvider.getGateways()
    expect(mockProvider.getGateways).toHaveBeenCalledTimes(1)

    // Advance time beyond TTL
    currentTime += 70000 // 70 seconds

    // Second call should refresh cache
    await cacheProvider.getGateways()
    expect(mockProvider.getGateways).toHaveBeenCalledTimes(2)

    Date.now = originalNow
  })

  test('should handle provider errors gracefully', async () => {
    mockProvider.getGateways.mockRejectedValue(new Error('Provider failed'))

    await expect(cacheProvider.getGateways()).rejects.toThrow('Provider failed')
  })
})

Best Practices

  1. Use in Production: Caching significantly improves performance for network providers
  2. Set Appropriate TTL: Balance between freshness and performance
  3. Enable Background Refresh: Keeps cache warm without blocking requests
  4. Monitor Cache Performance: Track hit rates and adjust TTL accordingly
  5. Handle Provider Failures: Implement fallback strategies for when providers fail
  6. Consider Memory Usage: Set appropriate maxCacheSize for your application
  7. Use Persistent Storage: For web applications, consider localStorage for cache persistence

Comparison with Other Providers

← Swipe to see more →
FeatureSimpleCacheGatewaysProviderNetworkGatewaysProviderStaticGatewaysProvider
PerformanceHigh (cached)Medium (network calls)High (no network)
Data FreshnessMedium (cached with TTL)High (real-time)Low (static)
Network DependencyMedium (periodic refresh)High (every call)None
Memory UsageMedium (cache storage)LowLow
ComplexityMedium (cache management)LowLow
Use CaseProduction (performance)Production (freshness)Development/Testing
← Swipe to see more →

Was this page helpful?