Class: Fbe::Middleware::SqliteStore

Inherits:
Object
  • Object
show all
Defined in:
lib/fbe/middleware/sqlite_store.rb

Overview

Persisted SQLite store for Faraday::HttpCache

This class provides a persistent cache store backed by SQLite for use with Faraday::HttpCache middleware. It’s designed to cache HTTP responses from GitHub API calls to reduce API rate limit consumption and improve performance.

Key features:

  • Automatic version management to invalidate cache on version changes

  • Size-based cache eviction (configurable, defaults to 10MB)

  • Thread-safe SQLite transactions

  • JSON serialization for cached values

  • Filtering of non-cacheable requests (non-GET, URLs with query parameters)

Usage example:

store = Fbe::Middleware::SqliteStore.new(
  '/path/to/cache.db',
  '1.0.0',
  loog: logger,
  maxsize: 50 * 1024 * 1024  # 50MB max size
)

# Use with Faraday
Faraday.new do |builder|
  builder.use Faraday::HttpCache, store: store
end

The store automatically manages the SQLite database schema and handles cleanup operations when the database grows too large. Old entries are deleted based on their last access time to maintain the configured size limit.

Author

Yegor Bugayenko ([email protected])

Copyright

Copyright © 2024-2025 Zerocracy

License

MIT

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, version, loog: Loog::NULL, maxsize: 10 * 1024 * 1024) ⇒ SqliteStore

Initialize the SQLite store.

Parameters:

  • path (String)

    Path to the SQLite database file

  • version (String)

    Version identifier for cache compatibility

  • loog (Loog) (defaults to: Loog::NULL)

    Logger instance (optional, defaults to Loog::NULL)

  • maxsize (Integer) (defaults to: 10 * 1024 * 1024)

    Maximum database size in bytes (optional, defaults to 10MB)

Raises:

  • (ArgumentError)

    If path is nil/empty, directory doesn’t exist, or version is nil/empty



55
56
57
58
59
60
61
62
63
64
# File 'lib/fbe/middleware/sqlite_store.rb', line 55

def initialize(path, version, loog: Loog::NULL, maxsize: 10 * 1024 * 1024)
  raise ArgumentError, 'Database path cannot be nil or empty' if path.nil? || path.empty?
  dir = File.dirname(path)
  raise ArgumentError, "Directory #{dir} does not exist" unless File.directory?(dir)
  raise ArgumentError, 'Version cannot be nil or empty' if version.nil? || version.empty?
  @path = File.absolute_path(path)
  @version = version
  @loog = loog
  @maxsize = maxsize
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



47
48
49
# File 'lib/fbe/middleware/sqlite_store.rb', line 47

def path
  @path
end

Instance Method Details

#allArray<Array>

Get all entries from the cache.

Returns:

  • (Array<Array>)

    Array of [key, value] pairs



119
120
121
# File 'lib/fbe/middleware/sqlite_store.rb', line 119

def all
  perform { _1.execute('SELECT key, value FROM cache') }
end

#clearvoid

This method returns an undefined value.

Clear all entries from the cache.



109
110
111
112
113
114
115
# File 'lib/fbe/middleware/sqlite_store.rb', line 109

def clear
  perform do |t|
    t.execute 'DELETE FROM cache;'
    t.execute "UPDATE meta SET value = ? WHERE key = 'version';", [@version]
  end
  @db.execute 'VACUUM;'
end

#delete(key) ⇒ nil

Delete a key from the cache.

Parameters:

  • key (String)

    The cache key to delete

Returns:

  • (nil)


80
81
82
83
# File 'lib/fbe/middleware/sqlite_store.rb', line 80

def delete(key)
  perform { _1.execute('DELETE FROM cache WHERE key = ?', [key]) }
  nil
end

#read(key) ⇒ Object?

Read a value from the cache.

Parameters:

  • key (String)

    The cache key to read

Returns:

  • (Object, nil)

    The cached value parsed from JSON, or nil if not found



69
70
71
72
73
74
75
# File 'lib/fbe/middleware/sqlite_store.rb', line 69

def read(key)
  value = perform do |t|
    t.execute('UPDATE cache SET touched_at = ?2 WHERE key = ?1;', [key, Time.now.utc.iso8601])
    t.execute('SELECT value FROM cache WHERE key = ? LIMIT 1;', [key])
  end.dig(0, 0)
  JSON.parse(value) if value
end

#write(key, value) ⇒ nil

Note:

Values larger than 10KB are not cached

Note:

Non-GET requests and URLs with query parameters are not cached

Write a value to the cache.

Parameters:

  • key (String)

    The cache key to write

  • value (Object)

    The value to cache (will be JSON encoded)

Returns:

  • (nil)


91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/fbe/middleware/sqlite_store.rb', line 91

def write(key, value)
  return if value.is_a?(Array) && value.any? do |vv|
    req = JSON.parse(vv[0])
    req['url'].include?('?') || req['method'] != 'get'
  end
  value = JSON.dump(value)
  return if value.bytesize > 10_000
  perform do |t|
    t.execute("      INSERT INTO cache(key, value, touched_at) VALUES(?1, ?2, ?3)\n      ON CONFLICT(key) DO UPDATE SET value = ?2, touched_at = ?3\n    SQL\n  end\n  nil\nend\n", [key, value, Time.now.utc.iso8601])