# "Key-Value Store"
_Path: en/lua/storage/store_
> "Fast key-value storage with TTL support. Ideal for caching, sessions, and temporary state."
## Table of Contents
- Key-Value Store
## Content
# Key-Value Store
The `store` module provides key-value storage with optional TTLs. It can hold cached data, sessions, and other temporary state.
This page is an API reference. Its snippets assume a configured store, the permissions listed below, and application-provided values such as `owner` or `new_value`. Snippets after acquisition use an existing live `cache` handle and are not standalone functions.
For store configuration, see [Store](system/store.md).
## Loading
```lua
local store = require("store")
```
## Acquiring a Store
Acquire a store resource by registry ID:
```lua
local cache, err = store.get("app:cache")
if err then
return nil, err
end
local _, set_err = cache:set("user:123", {name = "Alice"}, 3600)
if set_err then
cache:release()
return nil, set_err
end
local user, get_err = cache:get("user:123")
cache:release()
if get_err then return nil, get_err end
return user
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Store resource ID |
**Returns:** `Store, error`
## Storing Values
Store a value with an optional TTL:
```lua
-- Simple set
local _, err = cache:set("user:123:name", "Alice")
if err then return nil, err end
-- Set with TTL (expires in 300 seconds)
local ok, ttl_err = cache:set("session:abc", {user_id = 123, role = "admin"}, 300)
if ttl_err then return nil, ttl_err end
return ok
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Key |
| `value` | any | Value (tables, strings, numbers, booleans) |
| `ttl` | number | TTL in seconds (optional, 0 = no expiry) |
**Returns:** `boolean, error`
## Retrieving Values
Retrieve a value by key:
```lua
local errors = require("errors")
local user, err = cache:get("user:123")
if err then
if err:kind() == errors.NOT_FOUND then
return nil -- key missing or expired
end
return nil, err
end
return user
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Key to retrieve |
**Returns:** `any, error`
The method returns `nil` and an `errors.NOT_FOUND` error when the key does not exist or has expired.
## Checking Existence
Check whether a key exists without retrieving its value:
```lua
if cache:has("lock:" .. resource_id) then
return nil, errors.new({ kind = errors.CONFLICT, message = "Resource is locked" })
end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Key to check |
**Returns:** `boolean, error`
## Deleting Keys
Remove a key from the store:
```lua
local deleted, err = cache:delete("session:" .. session_id)
if err then return nil, err end
return deleted
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Key to delete |
**Returns:** `boolean, error`
The method returns `true` when it deletes the key and `false` when the key does not exist.
## Reading Entry Metadata
`entry` returns the value together with its `version` — an opaque string used for optimistic concurrency:
```lua
local e, err = cache:entry("user:123")
if err then return nil, err end
if e then
print(e.key, e.value, e.version)
end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Key to read |
**Returns:** `Entry, error` — `{key: string, value: any, version: string}`
## Listing Keys
List entries in deterministic key order with pagination:
```lua
local page, err = cache:list({ prefix = "session:", limit = 100 })
if err then return nil, err end
for _, e in ipairs(page.items) do
print(e.key, e.value)
end
-- next page
if page.has_more then
local next_page, next_err = cache:list({ prefix = "session:", after = page.cursor })
if next_err then return nil, next_err end
page = next_page
end
```
| Option | Type | Description |
|--------|------|-------------|
| `prefix` | string | Only keys with this prefix |
| `after` | string | Continue after this cursor (from a previous page) |
| `limit` | integer | Max items per page |
**Returns:** `Page, error` — `{items: Entry[], cursor: string, has_more: boolean}`
## Conditional Writes
`put` writes a value and returns its new `Entry`. Options enable optimistic concurrency:
```lua
local errors = require("errors")
-- create only if the key does not exist
local e, err = cache:put("lock:job-1", owner, { only_if_absent = true })
if err and err:kind() == errors.ALREADY_EXISTS then
-- someone else holds it
elseif err then
return nil, err
end
-- compare-and-set: write only if the version still matches
local cur, read_err = cache:entry("config")
if read_err then return nil, read_err end
local e2, err2 = cache:put("config", new_value, { if_version = cur.version })
if err2 and err2:kind() == errors.CONFLICT then
-- a concurrent writer changed it; re-read and retry
elseif err2 then
return nil, err2
end
```
| Option | Type | Description |
|--------|------|-------------|
| `ttl` | number | TTL in seconds |
| `only_if_absent` | boolean | Write only if the key does not exist |
| `if_version` | string | Write only if the current version matches |
`only_if_absent` and `if_version` are mutually exclusive.
**Returns:** `Entry, error`
Conditional writes require a store whose info().conditional_put is true (the memory and store.kv.raft stores). On store.kv.crdt and store.sql they return an errors.INVALID error — use store.kv.raft when you need conditional writes.
## Store Capabilities
`info` reports the backend and what it supports, so code can adapt to whichever store is bound:
```lua
local info, err = cache:info()
if err then return nil, err end
-- info.backend -> one of store.backend.* (e.g. "kv.raft")
-- info.consistency -> one of store.consistency.* (e.g. "linearizable")
-- info.durable / info.list / info.versioned / info.conditional_put / info.ttl (booleans)
```
**Returns:** `Info, error` — `{id, backend, consistency, durable, list, versioned, conditional_put, ttl}`
### Constants
| Constant | Values |
|----------|--------|
| `store.backend` | `MEMORY`, `SQL`, `KV_RAFT`, `KV_CRDT`, `UNKNOWN` |
| `store.consistency` | `LINEARIZABLE`, `EVENTUAL`, `LOCAL`, `UNKNOWN` |
```lua
local info, err = cache:info()
if err then return nil, err end
if info.consistency == store.consistency.LINEARIZABLE then
-- safe to use compare-and-set
end
```
## Store Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `get(key)` | `any, error` | Retrieve value by key |
| `entry(key)` | `Entry, error` | Retrieve value with version metadata |
| `set(key, value, ttl?)` | `boolean, error` | Store value with optional TTL |
| `put(key, value, opts?)` | `Entry, error` | Conditional/versioned write, returns the new entry |
| `list(opts?)` | `Page, error` | Paged listing in key order |
| `has(key)` | `boolean, error` | Check if key exists |
| `delete(key)` | `boolean, error` | Remove key |
| `info()` | `Info, error` | Backend, consistency, and capability flags |
| `release()` | `boolean` | Release store back to pool |
## Permissions
Security policy evaluation applies to store operations.
| Action | Resource | Attributes | Description |
|--------|----------|------------|-------------|
| `store.get` | Store ID | - | Acquire a store resource |
| `store.info` | Store ID | - | Inspect store capabilities |
| `store.key.get` | Store ID | `key` | Read a key value (also `entry`) |
| `store.key.set` | Store ID | `key` | Write a key value (also `put`) |
| `store.key.delete` | Store ID | `key` | Delete a key |
| `store.key.has` | Store ID | `key` | Check key existence |
| `store.key.list` | Store ID | `prefix` | List entries |
Permission denials from `store.get`, `get`, `set`, `delete`, and `has` raise a Lua error. The `info`, `entry`, `list`, and `put` methods instead return an `errors.PERMISSION_DENIED` error. Grant the required actions before calling code that cannot tolerate a raised denial.
## Errors
`store.get()` and all methods on the store handle (`get`, `entry`, `set`, `put`, `list`, `has`, `delete`, `info`) return structured errors (use `err:kind()`), except that a permission denial in `store.get`, `get`, `set`, `has` and `delete` raises a Lua error instead.
| Condition | Kind | Retryable |
|-----------|------|-----------|
| Empty resource ID | `errors.INVALID` | no |
| Resource not found | `errors.INTERNAL` | no |
| Store released | `errors.INVALID` | no |
| Permission denied (`entry`, `put`, `list`, `info`) | `errors.PERMISSION_DENIED` | no |
| `only_if_absent` and key exists | `errors.ALREADY_EXISTS` | no |
| `if_version` mismatch | `errors.CONFLICT` | yes |
| Conditional write on a store without support | `errors.INVALID` | no |
See [Error Handling](lua/core/errors.md) for working with errors.
## Navigation
Previous: "SQL Database" (lua/storage/sql)
Next: "Filesystem" (lua/storage/filesystem)