···1+//! Convenience type combining process and client.
2+3+use std::ops::Deref;
4+use std::path::Path;
5+6+use crate::client::TapClient;
7+use crate::config::TapConfig;
8+use crate::process::TapProcess;
9+use crate::Result;
10+11+/// A convenience type that owns both a tap process and its client.
12+///
13+/// `TapHandle` manages the lifecycle of a tap subprocess and provides
14+/// access to a [`TapClient`] for interacting with it. When dropped,
15+/// the process is gracefully shut down.
16+///
17+/// # Example
18+///
19+/// ```no_run
20+/// use tapped::{TapHandle, TapConfig};
21+///
22+/// #[tokio::main]
23+/// async fn main() -> tapped::Result<()> {
24+/// let config = TapConfig::builder()
25+/// .database_url("sqlite://tap.db")
26+/// .build();
27+///
28+/// // Spawn tap and get a handle
29+/// let handle = TapHandle::spawn_default(config).await?;
30+///
31+/// // Use the client methods directly on the handle
32+/// handle.health().await?;
33+///
34+/// let mut channel = handle.channel().await?;
35+/// while let Ok(event) = channel.recv().await {
36+/// println!("Event: {:?}", event.event);
37+/// }
38+///
39+/// Ok(())
40+/// }
41+/// ```
42+pub struct TapHandle {
43+ process: TapProcess,
44+ client: TapClient,
45+}
46+47+impl TapHandle {
48+ /// Spawn a tap process at the given path and create a client for it.
49+ ///
50+ /// This combines [`TapProcess::spawn`] and [`TapClient`] creation into
51+ /// a single convenient call.
52+ ///
53+ /// # Arguments
54+ ///
55+ /// * `path` - Path to the tap binary
56+ /// * `config` - Configuration for the tap instance
57+ ///
58+ /// # Errors
59+ ///
60+ /// Returns an error if the process fails to start or become healthy.
61+ pub async fn spawn(path: impl AsRef<Path>, config: TapConfig) -> Result<Self> {
62+ let process = TapProcess::spawn(path, config).await?;
63+ let client = process.client()?;
64+ Ok(Self { process, client })
65+ }
66+67+ /// Spawn a tap process using the default binary location.
68+ ///
69+ /// This looks for `tap` in the current directory first, then on the PATH.
70+ /// Combines [`TapProcess::spawn_default`] and [`TapClient`] creation.
71+ ///
72+ /// # Arguments
73+ ///
74+ /// * `config` - Configuration for the tap instance
75+ ///
76+ /// # Errors
77+ ///
78+ /// Returns an error if no tap binary is found or it fails to start.
79+ pub async fn spawn_default(config: TapConfig) -> Result<Self> {
80+ let process = TapProcess::spawn_default(config).await?;
81+ let client = process.client()?;
82+ Ok(Self { process, client })
83+ }
84+85+ /// Get a reference to the underlying process.
86+ pub fn process(&self) -> &TapProcess {
87+ &self.process
88+ }
89+90+ /// Get a mutable reference to the underlying process.
91+ pub fn process_mut(&mut self) -> &mut TapProcess {
92+ &mut self.process
93+ }
94+95+ /// Get a reference to the client.
96+ pub fn client(&self) -> &TapClient {
97+ &self.client
98+ }
99+100+ /// Check if the tap process is still running.
101+ pub fn is_running(&mut self) -> bool {
102+ self.process.is_running()
103+ }
104+105+ /// Gracefully shut down the tap process.
106+ ///
107+ /// This sends SIGTERM and waits for the process to exit, then
108+ /// sends SIGKILL if it doesn't exit within the shutdown timeout.
109+ pub async fn shutdown(&mut self) -> Result<()> {
110+ self.process.shutdown().await
111+ }
112+}
113+114+impl Deref for TapHandle {
115+ type Target = TapClient;
116+117+ fn deref(&self) -> &Self::Target {
118+ &self.client
119+ }
120+}
···1+//! # tapped
2+//!
3+//! A Rust wrapper for the `tap` ATProto sync utility.
4+//!
5+//! Tap simplifies ATProto sync by handling the firehose connection, verification,
6+//! backfill, and filtering. This crate provides an idiomatic async Rust interface
7+//! to tap's HTTP API and WebSocket event stream.
8+//!
9+//! ## Features
10+//!
11+//! - Connect to an existing tap instance or spawn one as a subprocess
12+//! - Strongly-typed configuration with builder pattern
13+//! - Async event streaming with automatic acknowledgment
14+//! - Full HTTP API coverage for repo management and statistics
15+//!
16+//! ## Example
17+//!
18+//! ```no_run
19+//! use tapped::{TapClient, TapConfig, Result};
20+//!
21+//! #[tokio::main]
22+//! async fn main() -> Result<()> {
23+//! // Connect to an existing tap instance
24+//! let client = TapClient::new("http://localhost:2480")?;
25+//!
26+//! // Check health
27+//! client.health().await?;
28+//!
29+//! // Add repos to track
30+//! client.add_repos(&["did:plc:example1234567890abc"]).await?;
31+//!
32+//! // Stream events
33+//! let mut receiver = client.channel().await?;
34+//! while let Ok(event) = receiver.recv().await {
35+//! println!("Received event: {:?}", event);
36+//! // Event is automatically acknowledged when dropped
37+//! }
38+//!
39+//! Ok(())
40+//! }
41+//! ```
42+43+mod channel;
44+mod client;
45+mod config;
46+mod error;
47+mod handle;
48+mod process;
49+mod types;
50+51+pub use channel::{EventReceiver, ReceivedEvent};
52+pub use client::TapClient;
53+pub use config::{LogLevel, TapConfig, TapConfigBuilder};
54+pub use error::Error;
55+pub use handle::TapHandle;
56+pub use process::TapProcess;
57+pub use types::{
58+ AccountStatus, Cursors, DidDocument, Event, IdentityEvent, Record, RecordAction, RecordEvent,
59+ RepoInfo, RepoState, Service, VerificationMethod,
60+};
61+62+/// A specialised Result type for tapped operations.
63+pub type Result<T> = std::result::Result<T, Error>;