Skip to content

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: str

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: str

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 with json.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

put_identity(address: str, key: bytes) -> None

Store a 32-byte identity key for a remote address.

PARAMETER DESCRIPTION
address

Signal address string (e.g. "5599800001@s.whatsapp.net").

TYPE: str

key

32-byte identity public key.

TYPE: bytes

load_identity abstractmethod async

load_identity(address: str) -> bytes | None

Load an identity key for a remote address.

RETURNS DESCRIPTION
bytes | None

32 raw bytes of the identity key, or None if not found.

delete_identity abstractmethod async

delete_identity(address: str) -> None

Delete an identity key.

get_session abstractmethod async

get_session(address: str) -> bytes | None

Get an encrypted Signal session record.

RETURNS DESCRIPTION
bytes | None

Opaque session bytes, or None if no session exists.

put_session abstractmethod async

put_session(address: str, session: bytes) -> None

Store an encrypted Signal session record.

delete_session abstractmethod async

delete_session(address: str) -> None

Delete a Signal session.

store_prekey abstractmethod async

store_prekey(
    id: int, record: bytes, uploaded: bool
) -> None

Store a pre-key.

PARAMETER DESCRIPTION
id

Pre-key ID (u32).

TYPE: int

record

Serialized pre-key record.

TYPE: bytes

uploaded

Whether this key has been uploaded to the server.

TYPE: bool

load_prekey abstractmethod async

load_prekey(id: int) -> bytes | None

Load a pre-key by ID.

RETURNS DESCRIPTION
bytes | None

Serialized pre-key record bytes, or None.

remove_prekey abstractmethod async

remove_prekey(id: int) -> None

Remove a pre-key.

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 0 if none exist.

store_signed_prekey abstractmethod async

store_signed_prekey(id: int, record: bytes) -> None

Store a signed pre-key.

load_signed_prekey abstractmethod async

load_signed_prekey(id: int) -> bytes | None

Load a signed pre-key by ID.

load_all_signed_prekeys abstractmethod async

load_all_signed_prekeys() -> list[tuple[int, bytes]]

Load all signed pre-keys.

RETURNS DESCRIPTION
list[tuple[int, bytes]]

List of (id, record_bytes) tuples.

remove_signed_prekey abstractmethod async

remove_signed_prekey(id: int) -> None

Remove a signed pre-key.

put_sender_key abstractmethod async

put_sender_key(address: str, record: bytes) -> None

Store a sender key for group messaging.

get_sender_key abstractmethod async

get_sender_key(address: str) -> bytes | None

Get a sender key.

delete_sender_key abstractmethod async

delete_sender_key(address: str) -> None

Delete a sender key.

get_sync_key abstractmethod async

get_sync_key(key_id: bytes) -> bytes | None

Get an app state sync key by ID.

PARAMETER DESCRIPTION
key_id

Raw key ID bytes.

TYPE: bytes

RETURNS DESCRIPTION
bytes | None

JSON-encoded AppStateSyncKey bytes, or None.

set_sync_key abstractmethod async

set_sync_key(key_id: bytes, key: bytes) -> None

Set an app state sync key.

PARAMETER DESCRIPTION
key_id

Raw key ID bytes.

TYPE: bytes

key

JSON-encoded AppStateSyncKey bytes.

TYPE: bytes

get_version abstractmethod async

get_version(name: str) -> bytes

Get the app state version for a collection.

PARAMETER DESCRIPTION
name

Collection name (e.g. "critical_block").

TYPE: str

RETURNS DESCRIPTION
bytes

JSON-encoded HashState bytes.

set_version abstractmethod async

set_version(name: str, state: bytes) -> None

Set the app state version for a collection.

PARAMETER DESCRIPTION
state

JSON-encoded HashState bytes.

TYPE: bytes

put_mutation_macs abstractmethod async

put_mutation_macs(
    name: str, version: int, mutations: bytes
) -> None

Store mutation MACs for a version.

PARAMETER DESCRIPTION
version

App state version number (u64).

TYPE: int

mutations

JSON-encoded [AppStateMutationMAC] bytes.

TYPE: bytes

get_mutation_mac abstractmethod async

get_mutation_mac(
    name: str, index_mac: bytes
) -> bytes | None

Get a mutation MAC by index.

delete_mutation_macs abstractmethod async

delete_mutation_macs(name: str, index_macs: bytes) -> None

Delete mutation MACs by their index MACs.

PARAMETER DESCRIPTION
index_macs

JSON-encoded [Vec<u8>] bytes.

TYPE: bytes

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 Device struct bytes.

TYPE: bytes

load abstractmethod async

load() -> bytes | None

Load device data.

RETURNS DESCRIPTION
bytes | None

JSON-encoded Device bytes, or None if no device exists.

exists abstractmethod async

exists() -> bool

Check if a device exists in the store.

create abstractmethod async

create() -> int

Create a new device row and return its generated device_id.

get_sender_key_devices abstractmethod async

get_sender_key_devices(
    group_jid: str,
) -> list[tuple[str, bool]]

Get sender key distribution status for all devices in a group.

RETURNS DESCRIPTION
list[tuple[str, bool]]

List of (device_jid, has_key) tuples.

set_sender_key_status abstractmethod async

set_sender_key_status(
    group_jid: str, entries: bytes
) -> None

Set sender key status for devices.

PARAMETER DESCRIPTION
entries

JSON-encoded [(&str, bool)] array bytes.

TYPE: bytes

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 [str] array bytes.

TYPE: bytes

clear_all_sender_key_devices abstractmethod async

clear_all_sender_key_devices() -> None

Clear all sender key device tracking across all groups.

get_lid_mapping abstractmethod async

get_lid_mapping(lid: str) -> bytes | None

Get a LID-to-phone-number mapping.

RETURNS DESCRIPTION
bytes | None

JSON-encoded LidPnMappingEntry bytes, or None.

get_pn_mapping abstractmethod async

get_pn_mapping(phone: str) -> bytes | None

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 LidPnMappingEntry bytes.

TYPE: bytes

get_all_lid_mappings abstractmethod async

get_all_lid_mappings() -> list[bytes]

Get all LID-PN mappings.

RETURNS DESCRIPTION
list[bytes]

List of JSON-encoded LidPnMappingEntry bytes.

save_base_key abstractmethod async

save_base_key(
    address: str, message_id: str, base_key: bytes
) -> None

Save a base key for retry collision detection.

has_same_base_key abstractmethod async

has_same_base_key(
    address: str, message_id: str, current_base_key: bytes
) -> bool

Check if the current session has the same base key as the saved one.

delete_base_key abstractmethod async

delete_base_key(address: str, message_id: str) -> None

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 DeviceListRecord bytes.

TYPE: bytes

get_devices abstractmethod async

get_devices(user: str) -> bytes | None

Get all known devices for a user.

RETURNS DESCRIPTION
bytes | None

JSON-encoded DeviceListRecord bytes, or None.

delete_devices abstractmethod async

delete_devices(user: str) -> None

Delete a device list record.

get_tc_token abstractmethod async

get_tc_token(jid: str) -> bytes | None

Get a trusted contact token.

RETURNS DESCRIPTION
bytes | None

JSON-encoded TcTokenEntry bytes, or None.

put_tc_token abstractmethod async

put_tc_token(jid: str, entry: bytes) -> None

Store or update a trusted contact token.

PARAMETER DESCRIPTION
entry

JSON-encoded TcTokenEntry bytes.

TYPE: bytes

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_tc_token_jids() -> list[str]

Get all JIDs that have stored tc tokens.

delete_expired_tc_tokens abstractmethod async

delete_expired_tc_tokens(
    token_cutoff: int, sender_cutoff: int
) -> int

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: int

sender_cutoff

Unix timestamp in seconds; sender buckets older than this are considered expired.

TYPE: int

RETURNS DESCRIPTION
int

Number of rows deleted.

store_sent_message abstractmethod async

store_sent_message(
    chat_jid: str, message_id: str, payload: bytes
) -> None

Store a sent message's serialized payload for retry handling.

take_sent_message abstractmethod async

take_sent_message(
    chat_jid: str, message_id: str
) -> bytes | None

Retrieve and delete a sent message (atomic take).

delete_expired_sent_messages abstractmethod async

delete_expired_sent_messages(cutoff: int) -> int

Delete sent messages older than cutoff.

RETURNS DESCRIPTION
int

Number of rows deleted.

put_msg_secrets abstractmethod async

put_msg_secrets(entries: bytes) -> int

Batch-upsert message secrets.

PARAMETER DESCRIPTION
entries

JSON-encoded [MsgSecretEntry] bytes.

TYPE: bytes

RETURNS DESCRIPTION
int

Number of rows affected.

get_msg_secret abstractmethod async

get_msg_secret(
    chat: str, sender: str, msg_id: str
) -> bytes | None

Fetch the persisted message secret.

RETURNS DESCRIPTION
bytes | None

Raw secret bytes, or None if absent.

delete_expired_msg_secrets abstractmethod async

delete_expired_msg_secrets(cutoff: int) -> int

Delete expired message secrets.

RETURNS DESCRIPTION
int

Number of rows deleted.


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.

from tryx.backend import SqliteStore

backend = SqliteStore("whatsapp.db")
app = Tryx(backend)

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.