Files
khadhroony-solana-project/crates/ksp-store-lib/USAGE.md

10 KiB

Utilisation de ksp-store-lib

1. Dépendance et backend compilé

Le consumer runtime dépend de la façade commune :

[dependencies]
ksp-store-lib = { path = "../ksp-store-lib" }

La feature par défaut compile le backend PostgreSQL :

postgres

Pour compiler la façade sans backend physique :

ksp-store-lib = { path = "../ksp-store-lib", default-features = false }

Dans ce mode, les settings PostgreSQL restent représentables, mais Store::open retourne ERROR_CODE_BACKEND_NOT_COMPILED avant toute I/O si PostgreSQL est sélectionné.

Un consumer applicatif ordinaire ne dépend pas directement de ksp-store-postgres-lib.

2. Obtenir les settings depuis Config

Le chemin applicatif recommandé passe par ksp-config-lib, propriétaire de std.store, de la résolution .env et des secrets.

fn resolve_store_settings(
    engine: &ksp_config_lib::ConfigDocumentEngine,
    environment: &ksp_config_lib::ConfigEnvironment,
    target: std::option::Option<&str>,
) -> ksp_core_lib::Result<ksp_store_lib::StoreSettings> {
    let resolved = engine.load_resolved_store_config(target, environment);
    let resolved = match resolved {
        std::result::Result::Ok(value) => value,
        std::result::Result::Err(error) => return std::result::Result::Err(error),
    };

    return std::result::Result::Ok(resolved.into_settings());
}

Chaque StoreSettings sélectionne exactement un réseau logique et un backend physique. La sélection d'un target nommé appartient à Config ; Store ne route pas automatiquement entre plusieurs targets.

3. Construire des settings programmatiquement

La construction directe est utile pour les tests et outils qui ne passent pas par Config.

fn programmatic_store_settings(connection_uri: std::string::String) -> ksp_store_lib::Result<ksp_store_lib::StoreSettings> {
    let network = ksp_store_lib::RawNetworkId::new("devnet");
    let network = match network {
        std::result::Result::Ok(value) => value,
        std::result::Result::Err(error) => return std::result::Result::Err(error),
    };

    let postgres = ksp_store_lib::PostgresStoreSettings::new(
        connection_uri,
        ksp_store_lib::PostgresPoolSettings::default(),
        ksp_store_lib::PostgresTlsMode::VerifyFull,
        ksp_store_lib::PostgresBootstrapSettings::default(),
    );

    let settings = ksp_store_lib::StoreSettings::with_default_shutdown(
        network,
        ksp_store_lib::StoreBackendSettings::Postgres(postgres),
    );

    if let std::result::Result::Err(error) = settings.validate() {
        return std::result::Result::Err(error);
    }

    return std::result::Result::Ok(settings);
}

PostgresStoreSettings ne fournit aucun getter public de l'URI. Son Debug remplace cette valeur par <redacted>.

4. Ouvrir, sonder et fermer un Store

Store::open est async et ne retourne un succès qu'après validation des settings, ouverture du backend compilé et bootstrap requis.

async fn use_store(settings: ksp_store_lib::StoreSettings) -> ksp_store_lib::Result<()> {
    let store = ksp_store_lib::Store::open(settings).await;
    let store = match store {
        std::result::Result::Ok(value) => value,
        std::result::Result::Err(error) => return std::result::Result::Err(error),
    };

    let runtime = store.runtime_snapshot();
    let _network = runtime.network();
    let _capacity = runtime.pool_capacity();
    let _size = runtime.pool_size();
    let _available = runtime.pool_available();
    let _waiting = runtime.pool_waiting();

    let health = store.health().await;
    match health.state() {
        ksp_store_lib::StoreHealthState::Ready => {}
        ksp_store_lib::StoreHealthState::NotReady => {
            let _safe_error_code = health.last_error_code();
        }
        _ => {}
    }

    return store.close().await;
}

Store::close(self) consomme l'instance. Une fermeture explicite ne peut donc pas être suivie d'une nouvelle opération via la même valeur.

5. Lire une transaction RAW

Importer le trait correspondant suffit pour utiliser la façade :

use ksp_store_lib::RawTransactionRead;

async fn read_transaction(
    store: &ksp_store_lib::Store,
    reference: &ksp_store_lib::RawTransactionReference,
) -> ksp_store_lib::Result<std::option::Option<ksp_store_lib::RawTransaction>> {
    return store.get_raw_transaction(reference).await;
}

Une transaction absente retourne None. Une transaction Purged retourne également None pour le payload canonique ; son tombstone reste accessible via la capability de rétention.

6. Paginer les références de transactions

La pagination est keyset et utilise un cursor opaque. Le consumer ne doit pas interpréter ses bytes.

use ksp_store_lib::RawTransactionRead;

async fn first_transaction_page(
    store: &ksp_store_lib::Store,
    network: ksp_store_lib::RawNetworkId,
) -> ksp_store_lib::Result<ksp_store_lib::RawPage<ksp_store_lib::RawTransactionReference>> {
    let limit = ksp_store_lib::RawPageLimit::new(100);
    let limit = match limit {
        std::result::Result::Ok(value) => value,
        std::result::Result::Err(error) => return std::result::Result::Err(error),
    };

    let slots = ksp_store_lib::RawSlotRange::new(std::option::Option::None, std::option::Option::None);
    let slots = match slots {
        std::result::Result::Ok(value) => value,
        std::result::Result::Err(error) => return std::result::Result::Err(error),
    };

    let query = ksp_store_lib::RawTransactionQuery::new(
        network,
        slots,
        ksp_store_lib::RawSortDirection::Ascending,
        ksp_store_lib::RawPageRequest::first(limit),
    );

    return store.list_raw_transactions(&query).await;
}

Pour continuer, recopier le cursor retourné par RawPage::next_cursor() dans RawPageRequest::after. Le réseau, la direction et les bornes de slots doivent rester identiques à ceux de la query ayant produit le cursor.

7. Persister une acquisition canonique

La transaction canonique et son observation initiale forment une seule opération atomique.

use ksp_store_lib::RawTransactionWrite;

async fn persist_acquisition(
    store: &ksp_store_lib::Store,
    transaction: ksp_store_lib::RawTransaction,
    observation: ksp_store_lib::RawTransactionObservation,
) -> ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome> {
    return store
        .persist_raw_transaction_acquisition(
            transaction,
            observation,
            ksp_store_lib::RawTransactionAcquisitionMode::Normal,
        )
        .await;
}

Le mode Normal respecte un tombstone Purged. ForceRehydrate doit être choisi explicitement lorsqu'un caller veut restaurer un payload purgé et que l'identité retenue est compatible.

Un contenu divergent sous la même identité produit ERROR_CODE_RAW_CONFLICT; Store ne remplace jamais silencieusement le contenu gagnant.

8. Lire et ajouter une observation

Une observation supplémentaire référence une transaction canonique déjà durable.

use ksp_store_lib::RawTransactionObservationRead;
use ksp_store_lib::RawTransactionObservationWrite;

async fn use_observation(
    store: &ksp_store_lib::Store,
    key: &ksp_store_lib::RawObservationKey,
    observation: ksp_store_lib::RawTransactionObservation,
) -> ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome> {
    let existing = store.get_raw_transaction_observation(key).await;
    if let std::result::Result::Err(error) = existing {
        return std::result::Result::Err(error);
    }

    return store.record_raw_transaction_observation(observation).await;
}

record_raw_transaction_observation ne crée pas implicitement le canonique. Une référence absente est signalée par ERROR_CODE_RAW_REFERENCE_NOT_FOUND.

9. Lire la rétention et le tombstone

use ksp_store_lib::RawTransactionRetentionRead;

async fn read_retention(
    store: &ksp_store_lib::Store,
    reference: &ksp_store_lib::RawTransactionReference,
) -> ksp_store_lib::Result<std::option::Option<ksp_store_lib::RawRetentionState>> {
    let tombstone = store.get_raw_transaction_tombstone(reference).await;
    if let std::result::Result::Err(error) = tombstone {
        return std::result::Result::Err(error);
    }

    return store.get_raw_transaction_retention_state(reference).await;
}

Le tombstone minimal est utile uniquement lorsque le payload a été purgé ; il ne remplace pas le modèle canonique lorsqu'un payload est encore disponible.

10. Appliquer une transition de rétention

La policy qui décide qu'une transition est autorisée appartient au caller. Store applique uniquement la transition demandée de manière atomique.

use ksp_store_lib::RawTransactionRetentionWrite;

async fn archive_transaction(
    store: &ksp_store_lib::Store,
    reference: ksp_store_lib::RawTransactionReference,
) -> ksp_store_lib::Result<ksp_store_lib::RawRetentionWriteOutcome> {
    let transition = ksp_store_lib::RawTransactionRetentionTransition::try_new(
        reference,
        ksp_store_lib::RawRetentionState::Full,
        ksp_store_lib::RawRetentionState::Archived,
    );
    let transition = match transition {
        std::result::Result::Ok(value) => value,
        std::result::Result::Err(error) => return std::result::Result::Err(error),
    };

    return store.transition_raw_transaction_retention(transition).await;
}

Le backend PostgreSQL supporte physiquement Full -> Archived -> Purged. Une transition impliquant Compacted est rejetée par ce backend tant qu'aucune représentation compactée réelle n'est implémentée.

11. Diagnostics et erreurs

Les snapshots et erreurs de façade n'exposent ni URI, host, user, database, SQL, handle backend, valeur de bind ni texte d'erreur PostgreSQL.

Les codes Store utiles incluent notamment :

store.wrong_network
store.raw_reference_not_found
store.postgres_read_failed
store.postgres_write_failed
store.postgres_data_invalid
store.postgres_page_limit_unsupported
store.postgres_retention_compaction_unsupported

Les conflits et queries invalides utilisent les codes backend-neutral store_api.raw_conflict et store_api.raw_query_invalid.

12. Limites de la façade

La façade ne fournit pas d'accès public au SQL, au pool, aux clients ou transactions PostgreSQL. Les capabilities RawAccount* réexportées par l'API commune ne sont pas encore dispatchées par Store.

La taille de page est une primitive de navigation. Les décisions de batch, priorité, backlog et scheduling appartiennent aux workers/jobs, pas à Store.