A powerful and easy-to-use cache system for Emacs, inspired by browser localStorage/sessionStorage and excellent caching libraries from other languages.
-
Multiple Storage Backends
- Memory cache (global and buffer-local)
- File-based cache (persistent)
- Database cache (SQLite, requires Emacs 29+)
-
Flexible Cache Strategies
- TTL (Time To Live) expiration
- LRU (Least Recently Used) eviction policy
- Idle-time based invalidation
- Hook-triggered cache invalidation
-
Simple API
- localStorage-like key-value interface
- Automatic serialization/deserialization
- Support for any Lisp data types
-
Automatic Persistence
- Periodic auto-save of memory cache to disk
- Auto-load on startup
Download ecache.el to your Emacs configuration directory and add to your init.el:
(add-to-list 'load-path "/path/to/ecache")
(require 'ecache)
(ecache-init) ; Initialize cache system;; Customize cache directory
(setq ecache-directory (expand-file-name "cache" user-emacs-directory))
;; Set default TTL (in seconds)
(setq ecache-default-ttl 3600) ; 1 hour
;; Set maximum memory cache size
(setq ecache-max-memory-size 1000)
;; Set idle invalidation time (in seconds)
(setq ecache-idle-invalidation-time 600) ; 10 minutes
;; Set auto-save interval (in seconds)
(setq ecache-auto-save-interval 300) ; 5 minutes;; Set cache
(ecache-set "my-key" "my-value")
;; Get cache
(ecache-get "my-key") ; => "my-value"
;; Get cache with default value
(ecache-get "nonexistent-key" :default "default-value") ; => "default-value"
;; Delete cache
(ecache-delete "my-key")
;; Clear all cache
(ecache-clear)
;; Get all keys
(ecache-keys) ; => ("key1" "key2" ...);; Set cache that expires in 60 seconds
(ecache-set "temp-key" "temp-value" :ttl 60)
;; Valid within 60 seconds
(ecache-get "temp-key") ; => "temp-value"
;; Automatically expired after 60 seconds
(sleep-for 61)
(ecache-get "temp-key") ; => nil;; Set cache in current buffer
(ecache-set "buffer-key" "buffer-value" :buffer-local t)
;; Get cache from current buffer
(ecache-get "buffer-key" :buffer-local t) ; => "buffer-value"
;; Cache is not shared in other buffers
(with-current-buffer other-buffer
(ecache-get "buffer-key" :buffer-local t)) ; => nilFile cache persists data to disk, surviving Emacs restarts.
;; Set file cache
(ecache-set "persistent-key" "persistent-value" :backend 'file)
;; Get file cache
(ecache-get "persistent-key" :backend 'file) ; => "persistent-value"
;; Delete file cache
(ecache-delete "persistent-key" :backend 'file)
;; Clear all file cache
(ecache-clear :backend 'file)
;; Get all file cache keys
(ecache-keys :backend 'file)Database cache uses SQLite, suitable for large datasets.
;; Set database cache
(ecache-set "db-key" "db-value" :backend 'database)
;; Get database cache
(ecache-get "db-key" :backend 'database) ; => "db-value"
;; Delete database cache
(ecache-delete "db-key" :backend 'database)
;; Clear all database cache
(ecache-clear :backend 'database)
;; Get all database cache keys
(ecache-keys :backend 'database)ecache supports any serializable Lisp data types:
;; String
(ecache-set "string" "Hello, World!")
;; Number
(ecache-set "number" 42)
;; List
(ecache-set "list" '(1 2 3 4 5))
;; Association list
(ecache-set "alist" '((name . "John") (age . 30)))
;; Property list
(ecache-set "plist" '(:name "John" :age 30))
;; Hash table
(let ((hash (make-hash-table :test 'equal)))
(puthash "key" "value" hash)
(ecache-set "hash" hash))
;; Vector
(ecache-set "vector" [1 2 3 4 5]);; Clear cache when hook is triggered
(ecache-invalidate-on-hook 'after-save-hook 'memory)
;; Delete only specific keys when hook is triggered
(ecache-invalidate-on-hook 'after-save-hook 'memory '("key1" "key2"))
;; Clear file cache after saving
(ecache-invalidate-on-hook 'after-save-hook 'file);; Auto-clear memory cache after 10 minutes of idle time
(setq ecache-idle-invalidation-time 600)
(ecache-invalidate-on-idle);; Auto-save memory cache to file every 5 minutes
(setq ecache-auto-save-interval 300)
(ecache-setup-auto-save);; Save memory cache to file
(ecache-save-to-file)
;; Load memory cache from file
(ecache-load-from-file)
;; Use custom file path
(ecache-save-to-file "/path/to/cache-backup.el")
(ecache-load-from-file "/path/to/cache-backup.el");; Inspect memory cache contents
(ecache-inspect-memory)
;; Inspect buffer-local cache contents
(ecache-inspect-buffer-local)
;; Display cache statistics
(ecache-stats) ; => "Cache Statistics: Memory=10 File=5 Database=3"(defun my-fetch-data (url)
"Fetch data from URL with caching to avoid duplicate requests."
(let ((cache-key (format "api:%s" url)))
(or (ecache-get cache-key :backend 'file)
(let ((data (my-api-request url)))
(ecache-set cache-key data :backend 'file :ttl 3600)
data))))(defun my-expensive-computation (input)
"Perform expensive computation with result caching."
(let ((cache-key (format "compute:%S" input)))
(or (ecache-get cache-key)
(let ((result (my-compute input)))
(ecache-set cache-key result :ttl 600)
result))))(defun my-buffer-config-set (key value)
"Set buffer-local configuration."
(ecache-set key value :buffer-local t))
(defun my-buffer-config-get (key &optional default)
"Get buffer-local configuration."
(ecache-get key :buffer-local t :default default))(defun my-project-cache-set (key value)
"Set project-level cache."
(let ((project-root (project-root (project-current))))
(ecache-set (format "%s:%s" project-root key)
value
:backend 'file)))
(defun my-project-cache-get (key)
"Get project-level cache."
(let ((project-root (project-root (project-current))))
(ecache-get (format "%s:%s" project-root key)
:backend 'file)))- Memory cache: Fastest, suitable for frequently accessed small data
- File cache: Medium speed, suitable for persistent medium-sized data
- Database cache: Suitable for large datasets, supports complex queries (requires Emacs 29+)
When memory cache reaches ecache-max-memory-size, it automatically evicts the least recently used entries.
;; Set maximum cache entries
(setq ecache-max-memory-size 1000)(ecache-set KEY VALUE &rest ARGS)- Set cache(ecache-get KEY &rest ARGS)- Get cache(ecache-delete KEY &rest ARGS)- Delete cache(ecache-clear &rest ARGS)- Clear cache(ecache-keys &rest ARGS)- Get all keys
ARGS is a plist supporting the following keys:
:backend- Backend type:'memory(default),'file, or'database:ttl- Time to live in seconds:default- Default value (only forecache-get):buffer-local- Whether to use buffer-local cache (overrides:backend)
Memory cache:
ecache-set-memory,ecache-get-memory,ecache-delete-memoryecache-clear-memory,ecache-keys-memory
Buffer-local cache:
ecache-set-buffer-local,ecache-get-buffer-local,ecache-delete-buffer-localecache-clear-buffer-local,ecache-keys-buffer-local
File cache:
ecache-set-file,ecache-get-file,ecache-delete-fileecache-clear-file,ecache-keys-file
Database cache:
ecache-set-database,ecache-get-database,ecache-delete-databaseecache-clear-database,ecache-keys-database
(ecache-init)- Initialize cache system(ecache-save-to-file &optional FILE)- Save memory cache to file(ecache-load-from-file &optional FILE)- Load memory cache from file(ecache-invalidate-on-hook HOOK BACKEND &optional KEYS)- Setup hook-based invalidation(ecache-invalidate-on-idle)- Setup idle-based invalidation(ecache-setup-auto-save)- Setup auto-save
M-x ecache-inspect-memory- Inspect memory cache contentsM-x ecache-inspect-buffer-local- Inspect buffer-local cache contentsM-x ecache-stats- Display cache statisticsM-x ecache-init- Initialize cache system
ecache's design is inspired by:
- Browser Storage API: Simple key-value interface like localStorage/sessionStorage
- Redis: TTL support and multiple backends
- Python functools.lru_cache: LRU eviction policy
- Memcached: Layered caching strategy
Database cache requires Emacs 29+ with built-in SQLite support. Check:
(sqlite-available-p) ; Should return t;; Clear all backend caches
(ecache-clear :backend 'all)Default is ~/.emacs.d/ecache/ directory. Customize via ecache-directory.
GPL-3.0 or later
Issues and pull requests are welcome!