# Store
_Path: en/lua/storage/store_
## Table of Contents
- Key-Value Store
## Content
# Key-Value Store
Fast key-value storage with TTL support. Ideal for caching, sessions, and temporary state.
For store configuration, see [Store](system/store.md).
## Loading
```lua
local store = require("store")
```
## Acquiring a Store
Get a store resource by registry ID:
```lua
local cache, err = store.get("app:cache")
if err then
return nil, err
end
cache:set("user:123", {name = "Alice"}, 3600)
local user = cache:get("user:123")
cache:release()
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Store resource ID |
**Returns:** `Store, error`
## Storing Values
Store a value with optional TTL:
```lua
local cache = store.get("app:cache")
-- Simple set
cache:set("user:123:name", "Alice")
-- Set with TTL (expires in 300 seconds)
cache:set("session:abc", {user_id = 123, role = "admin"}, 300)
```
| 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
Get a value by key:
```lua
local user = cache:get("user:123")
if not user then
-- Key not found or expired
end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Key to retrieve |
**Returns:** `any, error`
Returns `nil` and an `errors.NOT_FOUND` error if the key doesn't exist or has expired.
## Checking Existence
Check if a key exists without retrieving:
```lua
if cache:has("lock:" .. resource_id) then
return nil, errors.new("CONFLICT", "Resource is locked")
end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Key to check |
**Returns:** `boolean, error`
## Deleting Keys
Remove a key from the store:
```lua
cache:delete("session:" .. session_id)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Key to delete |
**Returns:** `boolean, error`
Returns `true` if deleted, `false` if key didn't 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 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 paging:
```lua
local page, err = cache:list({ prefix = "session:", limit = 100 })
for _, e in ipairs(page.items) do
print(e.key, e.value)
end
-- next page
if page.has_more then
page = cache:list({ prefix = "session:", after = page.cursor })
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
-- 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
end
-- compare-and-set: write only if the version still matches
local cur = cache:entry("config")
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
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 = cache:info()
-- 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
if cache: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
Store operations are subject to security policy evaluation.
| 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 |
## Errors
`store.get()` and all methods on the store handle (`get`, `entry`, `set`, `put`, `list`, `has`, `delete`, `info`) return structured errors (use `err:kind()`).
| Condition | Kind | Retryable |
|-----------|------|-----------|
| Empty resource ID | `errors.INVALID` | no |
| Resource not found | `errors.NOT_FOUND` | no |
| Store released | `errors.INVALID` | no |
| Permission denied | `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 (lua/storage/sql)
Next: Filesystem (lua/storage/filesystem)