385 lines
14 KiB
Markdown
385 lines
14 KiB
Markdown
<!-- file: crates/ksp-store-lib/USAGE.md -->
|
|
<!-- version: 5 -->
|
|
|
|
# Utilisation de ksp-store-lib
|
|
|
|
## 1. Dépendance et backend compilé
|
|
|
|
Le consumer runtime dépend de la façade commune :
|
|
|
|
```toml
|
|
[dependencies]
|
|
ksp-store-lib = { path = "../ksp-store-lib" }
|
|
```
|
|
|
|
La feature par défaut compile le backend PostgreSQL :
|
|
|
|
```text
|
|
postgres
|
|
```
|
|
|
|
Pour compiler la façade sans backend physique :
|
|
|
|
```toml
|
|
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.
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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 :
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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. Lire et paginer les états account RAW
|
|
|
|
Importer `RawAccountStateRead` donne accès à la lecture par référence et à la navigation déterministe.
|
|
|
|
```rust
|
|
use ksp_store_lib::RawAccountStateRead;
|
|
|
|
async fn read_account_state(
|
|
store: &ksp_store_lib::Store,
|
|
reference: &ksp_store_lib::RawAccountStateReference,
|
|
) -> ksp_store_lib::Result<std::option::Option<ksp_store_lib::RawAccountState>> {
|
|
return store.get_raw_account_state(reference).await;
|
|
}
|
|
```
|
|
|
|
Pour une première page :
|
|
|
|
```rust
|
|
use ksp_store_lib::RawAccountStateRead;
|
|
|
|
async fn first_account_page(
|
|
store: &ksp_store_lib::Store,
|
|
network: ksp_store_lib::RawNetworkId,
|
|
pubkey: std::option::Option<ksp_store_lib::Pubkey>,
|
|
) -> ksp_store_lib::Result<ksp_store_lib::RawPage<ksp_store_lib::RawAccountStateReference>> {
|
|
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::RawAccountStateQuery::new(
|
|
network,
|
|
pubkey,
|
|
slots,
|
|
ksp_store_lib::RawSortDirection::Ascending,
|
|
ksp_store_lib::RawPageRequest::first(limit),
|
|
);
|
|
|
|
return store.list_raw_account_states(&query).await;
|
|
}
|
|
```
|
|
|
|
La continuation utilise uniquement `RawPage::next_cursor()` puis `RawPageRequest::after`. Le cursor account est opaque et lié au réseau, au filtre pubkey, à la direction et aux bornes de slots de la query qui l'a produit.
|
|
|
|
## 12. Persister une acquisition account
|
|
|
|
L'état canonique et son observation initiale sont une seule opération atomique.
|
|
|
|
```rust
|
|
use ksp_store_lib::RawAccountStateWrite;
|
|
|
|
async fn persist_account_acquisition(
|
|
store: &ksp_store_lib::Store,
|
|
state: ksp_store_lib::RawAccountState,
|
|
observation: ksp_store_lib::RawAccountObservation,
|
|
) -> ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome> {
|
|
return store.persist_raw_account_acquisition(state, observation).await;
|
|
}
|
|
```
|
|
|
|
`state.reference()` et `observation.account()` doivent désigner exactement le même état et le réseau du `Store`. Une répétition byte-identique est idempotente ; un contenu divergent sous la même identité retourne `ERROR_CODE_RAW_CONFLICT`.
|
|
|
|
## 13. Lire et ajouter une observation account
|
|
|
|
```rust
|
|
use ksp_store_lib::RawAccountObservationRead;
|
|
use ksp_store_lib::RawAccountObservationWrite;
|
|
|
|
async fn use_account_observation(
|
|
store: &ksp_store_lib::Store,
|
|
key: &ksp_store_lib::RawObservationKey,
|
|
observation: ksp_store_lib::RawAccountObservation,
|
|
) -> ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome> {
|
|
let existing = store.get_raw_account_observation(key).await;
|
|
if let std::result::Result::Err(error) = existing {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
|
|
return store.record_raw_account_observation(observation).await;
|
|
}
|
|
```
|
|
|
|
`record_raw_account_observation` ne crée jamais implicitement l'état canonique ; une référence absente retourne `ERROR_CODE_RAW_REFERENCE_NOT_FOUND`. `RawObservationKey` ne contient pas de réseau : la lecture par clé reste liée au backend mono-réseau déjà ouvert.
|
|
|
|
## 14. 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 :
|
|
|
|
```text
|
|
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`.
|
|
|
|
## 15. Limites de la façade
|
|
|
|
La façade ne fournit pas d'accès public au SQL, au pool, aux clients ou transactions PostgreSQL. Elle dispatch les dix capabilities RAW de l'API commune, mais ne fournit aucune capability de rétention, archivage, purge, delete ou compaction account.
|
|
|
|
La taille de page est une primitive de navigation. Les décisions de batch, priorité, backlog et scheduling appartiennent aux workers/jobs, pas à Store.
|