Cache API - GDScript SDK
BosBase caches combine in-memory storage with persistent database copies. Each cache instance is safe to use in single-node or multi-node (cluster) mode: nodes read from first, fall back to the database if an item is missing or expired, and then reload automatically.
The GDScript SDK exposes the cache endpoints through pb.caches. Typical use cases include:
- Caching AI prompts/responses that must survive restarts.
- Quickly sharing feature flags and configuration between workers.
- Preloading expensive vector search results for short periods.
Timeouts & TTLs: Each cache defines a default TTL (in seconds). Individual entries may provide their own
ttlSeconds. A value of0keeps the entry until it is manually deleted.
List Available Caches
The list() function allows you to query and retrieve all currently available caches, including their names and capacities. This is particularly useful for AI systems to discover existing caches before creating new ones, avoiding duplicate cache creation.
var BosBase = preload("res://gdscript-sdk/src/bosbase.gd")
var pb = BosBase.new("http://127.0.0.1:8090")
# Authenticate as superuser
var auth = await pb.admins().auth_with_password("root@example.com", "hunter2")
if auth is ClientResponseError:
push_error("Authentication failed: " + auth.to_string())
return
# Query all available caches
var caches = await pb.caches.list()
if caches is ClientResponseError:
push_error("Failed to list caches: " + caches.to_string())
return
# Each cache object contains:
# - name: String - The cache identifier
# - sizeBytes: int - The cache capacity in bytes
# - defaultTTLSeconds: int - Default expiration time
# - readTimeoutMs: int - Read timeout in milliseconds
# - created: String - Creation timestamp (RFC3339)
# - updated: String - Last update timestamp (RFC3339)
# Example: Find a cache by name and check its capacity
var target_cache = null
for cache in caches:
if cache.name == "ai-session":
target_cache = cache
break
if target_cache:
print("Cache \"%s\" has capacity of %d bytes" % [target_cache.name, target_cache.sizeBytes])
# Use the existing cache directly
else:
print("Cache not found, create a new one if needed")
Manage Cache Configurations
var BosBase = preload("res://gdscript-sdk/src/bosbase.gd")
var pb = BosBase.new("http://127.0.0.1:8090")
# Authenticate as superuser
var auth = await pb.admins().auth_with_password("root@example.com", "hunter2")
if auth is ClientResponseError:
push_error("Authentication failed: " + auth.to_string())
return
# List all available caches (including name and capacity).
# This is useful for AI to discover existing caches before creating new ones.
var caches = await pb.caches.list()
if not caches is ClientResponseError:
print("Available caches: ", caches)
# Output example:
# [
# {
# "name": "ai-session",
# "sizeBytes": 67108864,
# "defaultTTLSeconds": 300,
# "readTimeoutMs": 25,
# "created": "2024-01-15T10:30:00Z",
# "updated": "2024-01-15T10:30:00Z",
# },
# {
# "name": "query-cache",
# "sizeBytes": 33554432,
# "defaultTTLSeconds": 600,
# "readTimeoutMs": 50,
# "created": "2024-01-14T08:00:00Z",
# "updated": "2024-01-14T08:00:00Z",
# }
# ]
# Find an existing cache by name
var existing_cache = null
for cache in caches:
if cache.name == "ai-session":
existing_cache = cache
break
if existing_cache:
print("Found cache \"%s\" with capacity %d bytes" % [existing_cache.name, existing_cache.sizeBytes])
# Use the existing cache directly without creating a new one
else:
# Create a new cache only if it doesn't exist
var create_result = await pb.caches.create({
"name": "ai-session",
"sizeBytes": 64 * 1024 * 1024, # 64 MB
"defaultTTLSeconds": 300,
"readTimeoutMs": 25, # optional concurrency guard
})
if create_result is ClientResponseError:
push_error("Failed to create cache: " + create_result.to_string())
return
# Update limits later (e.g., shrink TTL to 2 minutes).
var update_result = await pb.caches.update("ai-session", {
"defaultTTLSeconds": 120,
})
if update_result is ClientResponseError:
push_error("Failed to update cache: " + update_result.to_string())
return
# Delete the cache (DB rows + FreeCache).
var delete_result = await pb.caches.delete("ai-session")
if delete_result is ClientResponseError:
push_error("Failed to delete cache: " + delete_result.to_string())
return
Field Reference:
| Field | Description |
|---|---|
sizeBytes |
Approximate size. Values too small (<512KB) or too large (>512MB) are clamped. |
defaultTTLSeconds |
Default expiration for entries. 0 means no expiration. |
readTimeoutMs |
Optional lock timeout while reading FreeCache. When exceeded, the value is fetched from the database instead. |
Work with Cache Entries
# Store an object in cache. The same payload is serialized into the DB.
var set_result = await pb.caches.set_entry("ai-session", "dialog:42", {
"prompt": "describe Saturn",
"embedding": [0.1, 0.2, 0.3], # vector array
}, 90) # per-entry TTL in seconds
if set_result is ClientResponseError:
push_error("Failed to set cache entry: " + set_result.to_string())
return
# Read from cache. "source" indicates where the hit came from.
var entry = await pb.caches.get_entry("ai-session", "dialog:42")
if entry is ClientResponseError:
push_error("Failed to get cache entry: " + entry.to_string())
return
print(entry.source) # "cache" or "database"
print(entry.expiresAt) # RFC3339 timestamp or null
print(entry.value) # The cached value
# Renew an entry's TTL without changing its value.
# This extends the expiration time by the specified TTL (or uses the cache's default TTL if omitted).
var renewed = await pb.caches.renew_entry("ai-session", "dialog:42", 120) # extend by 120 seconds
if not renewed is ClientResponseError:
print(renewed.expiresAt) # new expiration time
# Delete an entry.
var delete_result = await pb.caches.delete_entry("ai-session", "dialog:42")
if delete_result is ClientResponseError:
push_error("Failed to delete entry: " + delete_result.to_string())
Complete Example
var BosBase = preload("res://gdscript-sdk/src/bosbase.gd")
var pb = BosBase.new("http://127.0.0.1:8090")
class_name CacheManager
var pb: BosBase
func setup_cache() -> void:
# Authenticate as superuser
var auth = await pb.admins().auth_with_password("admin@example.com", "password")
if auth is ClientResponseError:
push_error("Authentication failed: " + auth.to_string())
return
# Check if cache exists
var caches = await pb.caches.list()
if caches is ClientResponseError:
push_error("Failed to list caches: " + caches.to_string())
return
var cache_exists = false
for cache in caches:
if cache.name == "ai-session":
cache_exists = true
break
# Create cache if it doesn't exist
if not cache_exists:
var create_result = await pb.caches.create({
"name": "ai-session",
"sizeBytes": 64 * 1024 * 1024, # 64 MB
"defaultTTLSeconds": 300, # 5 minutes
"readTimeoutMs": 25,
})
if create_result is ClientResponseError:
push_error("Failed to create cache: " + create_result.to_string())
return
func cache_ai_response(key: String, prompt: String, response: String, ttl: int = 300) -> void:
var result = await pb.caches.set_entry("ai-session", key, {
"prompt": prompt,
"response": response,
"timestamp": Time.get_unix_time_from_system(),
}, ttl)
if result is ClientResponseError:
push_error("Failed to cache response: " + result.to_string())
return
print("Response cached with key: ", key)
func get_cached_response(key: String) -> Dictionary:
var entry = await pb.caches.get_entry("ai-session", key)
if entry is ClientResponseError:
if entry.status == 404:
print("Cache entry not found")
else:
push_error("Failed to get cache entry: " + entry.to_string())
return {}
print("Cache hit from: ", entry.source)
return entry.value
func clear_cache() -> void:
var result = await pb.caches.delete("ai-session")
if result is ClientResponseError:
push_error("Failed to delete cache: " + result.to_string())
return
print("Cache cleared")
Cluster-Aware Behavior
- Write-through persistence – every
set_entrywrites to and the_cache_entriestable so other nodes (or a restarted node) can immediately reload values. - Read path – is consulted first. If a lock cannot be acquired within
readTimeoutMsor if the entry is missing/expired, BosBase queries the database copy and repopulates in the background. - Automatic cleanup – expired entries are ignored and removed from the database when fetched, preventing stale data across nodes.
Use caches whenever you need fast, transient data that must still be recoverable or shareable across BosBase nodes.
Error Handling
func safe_cache_operation() -> void:
# Set entry
var set_result = await pb.caches.set_entry("ai-session", "key1", {"data": "value"}, 300)
if set_result is ClientResponseError:
match set_result.status:
400:
push_error("Invalid cache name or entry data")
404:
push_error("Cache not found")
_:
push_error("Failed to set entry: " + set_result.to_string())
return
# Get entry
var entry = await pb.caches.get_entry("ai-session", "key1")
if entry is ClientResponseError:
if entry.status == 404:
print("Entry not found")
else:
push_error("Failed to get entry: " + entry.to_string())
return
print("Entry found: ", entry.value)
Best Practices
- Cache Discovery: Always check existing caches before creating new ones
- TTL Management: Set appropriate TTLs based on your use case
- Error Handling: Always check for
ClientResponseErrorafter cache operations - Size Limits: Keep cache sizes reasonable (between 512KB and 512MB)
- Entry Cleanup: Use
delete_entryto remove unused entries - Cluster Safety: Cache operations are safe in multi-node setups
Related Documentation
- Management API - Other management operations
- Vector API - Vector database for semantic search