Storage Backend API¶
SqliteStore
¶
SqliteStore(path: str)
Bases: BackendBase
Built-in SQLite storage backend.
This is the default, zero-configuration backend. Data is persisted
in a single *.db file using WAL mode.
| PARAMETER | DESCRIPTION |
|---|---|
path
|
Filesystem path to the SQLite database file. Created automatically if it doesn't exist.
TYPE:
|
Example::
from tryx.backend import SqliteStore
backend = SqliteStore("whatsapp.db")
Create a SQLite storage backend.
| PARAMETER | DESCRIPTION |
|---|---|
path
|
Filesystem path to the database file.
TYPE:
|
Example::
store = SqliteStore('session.db')
Methods:¶
FfiStoreProtocol
¶
Bases: Protocol
Structural typing protocol for native FFI-based storage backends.
Any object exposing a lib_path attribute and a config_json
attribute satisfies this protocol without inheriting from anything
in the Tryx package — keeping third-party store packages fully
decoupled.
The Tryx runtime loads the shared library (*.so / *.dylib /
*.dll) at lib_path and calls standardized C-ABI entry points
to perform storage operations with zero Python overhead.
Example (tryx-store-postgres)::
import json
class PostgresStore:
lib_path: str # path to compiled .so
config_json: str
backend = PostgresStore(
lib_path="./libtryx_pg.so",
config_json=json.dumps({"host": "localhost", "dbname": "tryx"}),
)
StoreBase
¶
Bases: ABC
Abstract base class for custom pure-Python storage backends.
Subclass this and implement all abstract methods to create a custom backend using any async-capable database (Redis, MongoDB, DynamoDB, etc.).
The Tryx Rust runtime detects StoreBase subclasses automatically
via duck-typing and bridges each async def method through PyO3's
async runtime.
Serialization convention:
- Simple types (
str,int,bool) are passed as-is. - Complex structs are passed as JSON-encoded bytes and should
be deserialized with
json.loads(data)/ serialized withjson.dumps(obj).encode().
Performance notes:
- GIL is held only for the brief moment of calling into and
extracting results from Python — the Rust side releases it
during
await. - The bridge uses
Python::attach(PyO3 0.28+) for minimal GIL acquisition overhead.
Example::
import json
import redis.asyncio as redis
from tryx.backend import StoreBase
class RedisStore(StoreBase):
def __init__(self, url: str = "redis://localhost"):
self.r = redis.from_url(url)
async def put_identity(self, address: str, key: bytes) -> None:
await self.r.set(f"identity:{address}", key)
async def load_identity(self, address: str) -> bytes | None:
return await self.r.get(f"identity:{address}")
# ... implement all other abstract methods ...
Methods:¶
put_identity
abstractmethod
async
¶
load_identity
abstractmethod
async
¶
Load an identity key for a remote address.
| RETURNS | DESCRIPTION |
|---|---|
bytes | None
|
32 raw bytes of the identity key, or |
delete_identity
abstractmethod
async
¶
delete_identity(address: str) -> None
Delete an identity key.
get_session
abstractmethod
async
¶
Get an encrypted Signal session record.
| RETURNS | DESCRIPTION |
|---|---|
bytes | None
|
Opaque session bytes, or |
put_session
abstractmethod
async
¶
Store an encrypted Signal session record.
store_prekey
abstractmethod
async
¶
load_prekey
abstractmethod
async
¶
Load a pre-key by ID.
| RETURNS | DESCRIPTION |
|---|---|
bytes | None
|
Serialized pre-key record bytes, or |
get_max_prekey_id
abstractmethod
async
¶
get_max_prekey_id() -> int
Get the maximum pre-key ID currently stored.
| RETURNS | DESCRIPTION |
|---|---|
int
|
The highest stored pre-key ID, or |
store_signed_prekey
abstractmethod
async
¶
Store a signed pre-key.
load_signed_prekey
abstractmethod
async
¶
Load a signed pre-key by ID.
load_all_signed_prekeys
abstractmethod
async
¶
remove_signed_prekey
abstractmethod
async
¶
remove_signed_prekey(id: int) -> None
Remove a signed pre-key.
put_sender_key
abstractmethod
async
¶
Store a sender key for group messaging.
get_sender_key
abstractmethod
async
¶
Get a sender key.
delete_sender_key
abstractmethod
async
¶
delete_sender_key(address: str) -> None
Delete a sender key.
get_sync_key
abstractmethod
async
¶
set_sync_key
abstractmethod
async
¶
get_version
abstractmethod
async
¶
set_version
abstractmethod
async
¶
Set the app state version for a collection.
| PARAMETER | DESCRIPTION |
|---|---|
state
|
JSON-encoded
TYPE:
|
put_mutation_macs
abstractmethod
async
¶
get_mutation_mac
abstractmethod
async
¶
Get a mutation MAC by index.
delete_mutation_macs
abstractmethod
async
¶
Delete mutation MACs by their index MACs.
| PARAMETER | DESCRIPTION |
|---|---|
index_macs
|
JSON-encoded
TYPE:
|
get_latest_sync_key_id
abstractmethod
async
¶
get_latest_sync_key_id() -> bytes | None
Get the most recently stored app state sync key ID.
save
abstractmethod
async
¶
save(device: bytes) -> None
Save device data.
| PARAMETER | DESCRIPTION |
|---|---|
device
|
JSON-encoded
TYPE:
|
load
abstractmethod
async
¶
load() -> bytes | None
Load device data.
| RETURNS | DESCRIPTION |
|---|---|
bytes | None
|
JSON-encoded |
create
abstractmethod
async
¶
create() -> int
Create a new device row and return its generated device_id.
get_sender_key_devices
abstractmethod
async
¶
set_sender_key_status
abstractmethod
async
¶
Set sender key status for devices.
| PARAMETER | DESCRIPTION |
|---|---|
entries
|
JSON-encoded
TYPE:
|
clear_sender_key_devices
abstractmethod
async
¶
clear_sender_key_devices(group_jid: str) -> None
Clear all sender key device tracking for a group.
delete_sender_key_device_rows
abstractmethod
async
¶
delete_sender_key_device_rows(device_jids: bytes) -> None
Delete specific sender_key_devices rows by device JID.
| PARAMETER | DESCRIPTION |
|---|---|
device_jids
|
JSON-encoded
TYPE:
|
clear_all_sender_key_devices
abstractmethod
async
¶
Clear all sender key device tracking across all groups.
get_lid_mapping
abstractmethod
async
¶
Get a LID-to-phone-number mapping.
| RETURNS | DESCRIPTION |
|---|---|
bytes | None
|
JSON-encoded |
get_pn_mapping
abstractmethod
async
¶
Get a phone-number-to-LID mapping.
put_lid_mapping
abstractmethod
async
¶
put_lid_mapping(entry: bytes) -> None
Store or update a LID-PN mapping.
| PARAMETER | DESCRIPTION |
|---|---|
entry
|
JSON-encoded
TYPE:
|
get_all_lid_mappings
abstractmethod
async
¶
save_base_key
abstractmethod
async
¶
Save a base key for retry collision detection.
has_same_base_key
abstractmethod
async
¶
Check if the current session has the same base key as the saved one.
delete_base_key
abstractmethod
async
¶
Delete a base key entry.
update_device_list
abstractmethod
async
¶
update_device_list(record: bytes) -> None
Update the device list for a user.
| PARAMETER | DESCRIPTION |
|---|---|
record
|
JSON-encoded
TYPE:
|
get_devices
abstractmethod
async
¶
Get all known devices for a user.
| RETURNS | DESCRIPTION |
|---|---|
bytes | None
|
JSON-encoded |
delete_devices
abstractmethod
async
¶
delete_devices(user: str) -> None
Delete a device list record.
get_tc_token
abstractmethod
async
¶
Get a trusted contact token.
| RETURNS | DESCRIPTION |
|---|---|
bytes | None
|
JSON-encoded |
put_tc_token
abstractmethod
async
¶
Store or update a trusted contact token.
| PARAMETER | DESCRIPTION |
|---|---|
entry
|
JSON-encoded
TYPE:
|
delete_tc_token
abstractmethod
async
¶
delete_tc_token(jid: str) -> None
Delete a trusted contact token.
get_all_tc_token_jids
abstractmethod
async
¶
Get all JIDs that have stored tc tokens.
delete_expired_tc_tokens
abstractmethod
async
¶
Delete tc tokens whose received token and sender bucket are both expired.
A row is removed only when its received token is expired-or-absent
(older than token_cutoff) AND its sender bucket is expired-or-absent
(older than sender_cutoff), so recent state on one axis keeps the row.
| PARAMETER | DESCRIPTION |
|---|---|
token_cutoff
|
Unix timestamp in seconds; received tokens older than this are considered expired.
TYPE:
|
sender_cutoff
|
Unix timestamp in seconds; sender buckets older than this are considered expired.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int
|
Number of rows deleted. |
store_sent_message
abstractmethod
async
¶
Store a sent message's serialized payload for retry handling.
take_sent_message
abstractmethod
async
¶
Retrieve and delete a sent message (atomic take).
delete_expired_sent_messages
abstractmethod
async
¶
Delete sent messages older than cutoff.
| RETURNS | DESCRIPTION |
|---|---|
int
|
Number of rows deleted. |
put_msg_secrets
abstractmethod
async
¶
This module defines the storage backends supported by Tryx for session persistence and device key management.
Backend Tiers¶
| Backend | Type | Use Case |
|---|---|---|
SqliteStore |
Built-in | Default, zero-config, single-file storage |
FfiStoreProtocol |
Native FFI | High-performance backends (PostgreSQL, MySQL) |
StoreBase |
Pure Python | Custom backends (Redis, MongoDB, etc.) |
SqliteStore¶
The default backend. Data is persisted in a single *.db file using WAL mode.
FfiStoreProtocol¶
Structural typing protocol for native FFI-based storage backends. Any object
exposing lib_path and config_json attributes satisfies this protocol.
import json
class PostgresStore:
lib_path: str # path to compiled .so
config_json: str
backend = PostgresStore(
lib_path="./libtryx_pg.so",
config_json=json.dumps({"host": "localhost", "dbname": "tryx"}),
)
StoreBase (Custom Python Backend)¶
Inherit from StoreBase to create a pure-Python async backend. Implement all
abstract methods for full control over storage operations.
from tryx.backend import StoreBase
class RedisStore(StoreBase):
async def get(self, key: str) -> bytes | None: ...
async def set(self, key: str, value: bytes) -> None: ...
async def delete(self, key: str) -> None: ...
When to Choose Each Backend¶
- Single-user bots
- Development and testing
- Embedded applications
- No external dependencies
- Production deployments
- Multi-user systems
- High-throughput requirements
- PostgreSQL/MySQL needed
- Redis/MongoDB/DynamoDB
- Custom caching layers
- Cloud-native storage
- Experimental backends
Migration path
Start with SqliteStore for development, then migrate to FfiStoreProtocol
or StoreBase for production. The API surface is identical.