Thunder
Thunder is a key-value store for unstructured data. It is optimised for fast reads and infrequent writes, which makes it a good fit for data that does not change often — configuration, feature flags, user preferences, or any data that does not belong in a typed component schema.
Each record belongs to a named collection (the first argument to every method). Collections are created implicitly on first write.
SDK Usage
Read Methods
find(name, filters, options?)
Returns all records in the collection that match the filters.
With field projection and cache:
const flags = await thunder.find(
'feature_flags',
{ enabled: true },
{ cacheTTL: 60, only: ['key', 'enabled'] }
)
findOne(name, filters, options?)
Returns the first record that matches the filters, or null.
paginate(name, filters, options?)
Returns a paginated result set. Pass page and perPage inside filters.
const page = await thunder.paginate(
'audit_log',
{ user_id: 42, page: 0, perPage: 20 },
{ cacheTTL: 30 }
)
Read Options
All read methods accept an optional options object as the last argument.
| Option | Type | Description |
|---|---|---|
cacheTTL |
number |
Maximum age (seconds) of a cached response to accept. See below. |
only |
string[] |
Limit which fields are returned. |
cacheTTL has three distinct states, and omitting it is not the same as passing 0:
cacheTTL |
Behaviour |
|---|---|
| omitted | Serve whatever is cached, regardless of age. The cheapest read. |
0 |
Skip the cache, read from the database, then repopulate the cache |
> 0 |
Serve a cached response only if it is younger than cacheTTL seconds, otherwise refresh from the database |
Values are not clamped — cacheTTL: 1 and cacheTTL: 86400 are both honoured exactly.
// Cheapest: any cached value will do.
const flags = await thunder.find('feature_flags', {})
// Tolerate up to 10s of staleness.
const recent = await thunder.find('feature_flags', {}, { cacheTTL: 10 })
// Must reflect the latest write, wherever it came from.
const live = await thunder.findOne('feature_flags', { key: 'dark_mode' }, { cacheTTL: 0 })
Omission is the default because Thunder is optimised for fast reads, and most callers are reading configuration that a stale-by-seconds answer serves perfectly well.
When you actually need cacheTTL
Writes through Thunder already invalidate every cached read key for the collection, so a
read following an insertOne/updateOne/upsert/operation/delete sees current data
without any option at all.
Reach for cacheTTL when the data can change without a Thunder write — a direct
database write, another service touching the same collection, or an invalidation that
failed — since those changes are invisible to the cache.
Dashboard reads always bypass the cache
Requests authenticated with a platform token — that is, calls made from the Protokol
Dashboard — read straight from the database regardless of cacheTTL, so operators always
see live data.
Component reads use an option of the same name with the same unit, but omitting it there
behaves like 0, and the value is clamped to 5–300s — see
Components → Cache.
Write Methods
insertOne(name, data)
Inserts a new record.
updateOne(name, filters, data)
Updates the first record that matches the filters.
upsert(name, filters, data)
Updates a matching record or creates one if none exists.
operation(name, filters, data)
Runs a custom server-side operation on matching records.
delete(name, filters)
Deletes all records matching the filters.