An open API service indexing awesome lists of open source software.

https://github.com/jamesgober/iqdb

High-performance embedded vector database for Rust. HNSW, IVF, and flat indexes with first-class metadata filtering. In-process, persistent, zero-network similarity search.
https://github.com/jamesgober/iqdb

ai database embedded-database hnsw in-process ivf persistent reps rust vector-database

Last synced: 8 days ago
JSON representation

High-performance embedded vector database for Rust. HNSW, IVF, and flat indexes with first-class metadata filtering. In-process, persistent, zero-network similarity search.

Awesome Lists containing this project

README

          


Rust logo


iQDB


EMBEDDED VECTOR DATABASE FOR RUST


crates.io
Crates.io Downloads
docs.rs
MSRV
CI


FASTER - LIGHTER - SMARTER


Intelligence-grade vector storage for AI-native applications.


Sub-millisecond vector search. Zero network hops. - iQDB




iQDB is an embedded vector database for Rust, a single-process, in-application similarity-search engine designed for high-dimensional workloads where every microsecond on the query path matters.
It targets the same operational shape as sqlite or redb: no daemon, no network hop, no separate runtime. Open a handle, write vectors, query nearest neighbours — all from inside your binary.



The engine is built against a lock-free hot path, allocation-free steady state, and a cache-aware on-disk layout. Exact brute-force search, approximate-nearest-neighbour indices (HNSW and IVF), payload metadata, declarative filters, and durable write-ahead-logged storage are all in scope. Indices and storage are pluggable, so workloads can trade recall for latency without rewriting the surrounding application.



Built for cross-platform deployment from day one — Linux, macOS, and Windows are first-class targets, with the strongest power-loss sync each platform offers and atomic file replacement everywhere.







MSRV is 1.87+. The crate is dual-licensed under Apache-2.0 OR MIT at your option.



1.0.0 — stable. The public API and on-disk format are frozen. iQDB composes the published iqdb crate family for vocabulary (iqdb-types), index seam (iqdb-index), exact and approximate indices (iqdb-flat, iqdb-hnsw, iqdb-ivf), durable storage (iqdb-persist), and optional result caching (iqdb-cache). A database fixes its dimensionality and distance metric at open time; the index is selectable — exact Flat by default, or approximate Hnsw / Ivf through IqdbConfig. An opt-in async surface (AsyncIqdb) is available behind the async feature. Migrating from 0.4.x? See the migration guide. See CHANGELOG.md for the full release history and docs/API.md for the API reference.




## Design Goals

iQDB is engineered against the Rust Efficiency & Performance Standards (REPS). Every architectural decision is graded against the same hard constraints:

- **Embedded by default** — no daemon, no socket, no separate process. The library opens a path and gets out of the way.
- **Sub-millisecond queries** — exact and approximate search paths are budgeted in microseconds, not milliseconds, with the hot path measured under Criterion every release.
- **Enum-dispatched hot paths** — the index seam dispatches through a closed `match`, never `dyn`, so the query loop sees a concrete index with no virtual indirection.
- **Allocation-aware steady state** — vector payloads are shared as `Arc<[f32]>` between the authoritative store and the derived index, so a vector that lives in both costs one allocation, not two.
- **Pluggable indices** — flat, IVF, and HNSW share the `iqdb-index` trait surface so callers swap strategies through `IqdbConfig` without touching their query code. Flat is the exact recall ground truth the approximate indices are measured against.
- **Crash-safe writes** — the durable path uses write-ahead logging and atomic snapshot replacement. A pulled power cord must never corrupt the database.
- **Tier-1 cross-platform** — Linux (x86_64, aarch64), macOS (x86_64, Apple Silicon), Windows (x86_64) all compile and pass the full test suite on every commit.




## Status & Roadmap

iQDB ships milestone-by-milestone. Each tag below corresponds to a published release; everything above the current line is shipped, everything below is planned.

| Milestone | Status | Surface delivered |
|-----------|--------|-------------------|
| `v0.1.0` — scaffolding | shipped | Crate scaffolding, lifecycle handle, `Error` type, CI matrix on all three Tier-1 platforms. |
| `v0.2.0` — vector primitives | shipped | Validated vectors, distance metrics, typed payloads, in-memory store with thread-safe CRUD. |
| `v0.3.0` — search | shipped | Flat top-`k` search, filters, batch variants, NaN-aware ranking, property-based tests. |
| `v0.4.0` — durable storage | shipped | Directory-backed store, snapshot + WAL, cross-platform sync, atomic compaction, corrupt-tail recovery. |
| `v0.5.0` — family composition + approximate indices | shipped | Re-platformed onto the iqdb crate family. Re-exported vocabulary (`Vector`, `VectorId`, `Metadata`, `Value`, `Hit`, `Filter`, `DistanceMetric`). Selectable index — exact `Flat`, plus `Hnsw` and `Ivf` through `IqdbConfig`. Durable storage via `iqdb-persist`; optional result cache via `iqdb-cache`. Recall validated against the flat oracle. |
| `v0.6.0` — async surface | shipped | `async`-feature-gated `AsyncIqdb`: a Tokio adapter that offloads each blocking call via `spawn_blocking`. Additive; the synchronous API and default build are unchanged. |
| `v0.7.0` — durability tuning (alpha) | shipped | `IqdbConfig::fsync` (WAL fsync cadence) and `IqdbConfig::compression` (snapshot `zstd` / `lz4`), wiring the compression features through. Additive; defaults unchanged. |
| `v0.8.0` — decoder hardening (beta) | shipped | Bounded every on-disk-decoder allocation against hostile length fields; fuzz-style robustness tests for the frame decoder; verified `cargo deny` / `cargo audit` pass. No API change. |
| `v0.9.0` — release candidate | shipped | Crash-recovery integration tests (corrupt WAL tail / corrupt snapshot); captured `criterion` benchmark baselines. No API change. |
| `v1.0.0` — stable | **current** | Public API and on-disk format frozen. SemVer guarantees. `IndexKind` marked `#[non_exhaustive]`. Migration guide from 0.4.x. |

The per-release detail — what was added, what changed, and what was verified — lives in the [`CHANGELOG`](./CHANGELOG.md) and the per-version notes under [`docs/release/`](./docs/release/).




## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
iqdb = "1"
```

Optional features (all additive):

```toml
[dependencies]
iqdb = { version = "1", features = ["serde", "parallel", "zstd"] }
```

iQDB compiles on stable Rust **1.87** and newer. It composes the published iqdb family crates (`iqdb-types`, `iqdb-index`, `iqdb-flat`, `iqdb-hnsw`, `iqdb-ivf`, `iqdb-build`, `iqdb-persist`, `iqdb-cache`, and their transitive dependencies).




## Quick Start

A database fixes its dimensionality and distance metric at open time, then exposes a small surface: `upsert` / `get` / `delete` for records, `search` / `search_with` for queries.

```rust
use iqdb::{DistanceMetric, Iqdb, Result, Vector, VectorId};

fn main() -> Result<()> {
// A 3-dimensional, in-memory database compared under cosine distance.
let db = Iqdb::open_in_memory(3, DistanceMetric::Cosine)?;

db.upsert(VectorId::from(1u64), Vector::new(vec![1.0, 0.0, 0.0])?, None)?;
db.upsert(VectorId::from(2u64), Vector::new(vec![0.99, 0.10, 0.0])?, None)?;

// Top-k similarity search. Results are sorted nearest-first under the
// smaller-is-closer rule; ties break on insertion order for determinism.
let hits = db.search(&Vector::new(vec![1.0, 0.0, 0.0])?, 5)?;
assert_eq!(hits[0].id, VectorId::from(1u64));

db.close()
}
```

### Filtered and batch search

Filters are declarative [`Filter`] expressions evaluated against each record's [`Metadata`]. On the exact flat index the filter is applied before scoring, so the result is exact.

```rust
use iqdb::{DistanceMetric, Filter, Iqdb, Metadata, Result, Value, Vector, VectorId};

fn main() -> Result<()> {
let db = Iqdb::open_in_memory(2, DistanceMetric::Cosine)?;

let doc: Metadata = [("kind".to_string(), Value::String("doc".into()))]
.into_iter().collect();
let img: Metadata = [("kind".to_string(), Value::String("image".into()))]
.into_iter().collect();
db.upsert(VectorId::from(1u64), Vector::new(vec![1.0, 0.0])?, Some(doc))?;
db.upsert(VectorId::from(2u64), Vector::new(vec![0.99, 0.10])?, Some(img))?;

// Only documents, ranked by cosine distance.
let filter = Filter::eq("kind", Value::String("doc".into()));
let hits = db.search_with(&Vector::new(vec![1.0, 0.0])?, 5, filter)?;
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].id, VectorId::from(1u64));

// Batch — one top-k result list per query, preserving input order.
let queries = vec![Vector::new(vec![1.0, 0.0])?, Vector::new(vec![0.0, 1.0])?];
let batches = db.search_batch(&queries, 1)?;
assert_eq!(batches.len(), 2);

Ok(())
}
```

### Choosing an index

Tier 1 (`open_in_memory` / `open`) defaults to the exact flat index. Tier 2 (`open_in_memory_with` / `open_with`) takes an [`IqdbConfig`] that selects an approximate index and tunes it, and can attach a result cache.

```rust
use iqdb::{CacheConfig, DistanceMetric, HnswConfig, IndexKind, Iqdb, IqdbConfig, Result};

fn main() -> Result<()> {
let cfg = IqdbConfig::new(128, DistanceMetric::Cosine)
.index(IndexKind::Hnsw(HnswConfig::default().with_ef_search(96)))
.cache(CacheConfig::new().capacity(10_000));
let db = Iqdb::open_in_memory_with(cfg)?;
assert!(db.is_empty());
Ok(())
}
```

On the approximate indices the metadata filter is applied after graph / cluster traversal, so a highly selective filter can return fewer than `k` hits — widen the search (HNSW `filter_widen`, IVF `n_probes`) when that matters. IVF is trained lazily from the stored vectors on the first search; after many writes, `Iqdb::optimize` retrains its centroids.

### Durable, file-backed storage

`Iqdb::open(path, dim, metric)` opens or creates a durable database. The path is the snapshot file; a write-ahead log lives beside it. Acknowledged writes survive a crash; reopening replays the log onto the snapshot.

```rust,no_run
use iqdb::{DistanceMetric, Iqdb, Result, Vector, VectorId};

fn main() -> Result<()> {
let db = Iqdb::open("./data/vectors.iqdb", 3, DistanceMetric::Cosine)?;
db.upsert(VectorId::from(1u64), Vector::new(vec![0.1, 0.2, 0.3])?, None)?;
db.flush()?; // compact: fold the WAL into a fresh snapshot
db.close()
}
```

A reopen whose requested `dim` / `metric` disagrees with the stored database fails with `Error::Config`. The stored index kind is part of the database identity and is restored from the snapshot regardless of the kind requested on reopen.

By default every acknowledged write is `fsync`ed and the snapshot is uncompressed. Trade durability for throughput, or shrink the snapshot, through `IqdbConfig`:

```rust,no_run
use iqdb::{Compression, DistanceMetric, FsyncPolicy, Iqdb, IqdbConfig};
use std::time::Duration;

# fn run() -> iqdb::Result<()> {
let cfg = IqdbConfig::new(128, DistanceMetric::Cosine)
.fsync(FsyncPolicy::Periodic(Duration::from_millis(50))) // bound the un-synced window
.compression(Compression::Zstd { level: 3 }); // requires the `zstd` feature
let db = Iqdb::open_with("./data/vectors.iqdb", cfg)?;
# let _ = db;
# Ok(())
# }
```

### Async (the `async` feature)

The family is synchronous by design, so the async surface is a thin Tokio adapter: `AsyncIqdb` holds an `Arc` and runs each blocking call on Tokio's blocking pool via `spawn_blocking`, so awaiting a search or a write never stalls the executor. It is `Clone` + `Send` + `Sync`. Enable the `async` feature and bring your own runtime.

```rust,ignore
use iqdb::{AsyncIqdb, DistanceMetric, Result, Vector, VectorId};

#[tokio::main]
async fn main() -> Result<()> {
let db = AsyncIqdb::open_in_memory(3, DistanceMetric::Cosine).await?;
db.upsert(VectorId::from(1u64), Vector::new(vec![1.0, 0.0, 0.0])?, None).await?;

let hits = db.search(Vector::new(vec![1.0, 0.0, 0.0])?, 1).await?;
assert_eq!(hits[0].id, VectorId::from(1u64));
db.close().await
}
```




## API Overview

The full API reference lives at [`docs/API.md`](./docs/API.md); the rustdoc at [docs.rs/iqdb](https://docs.rs/iqdb) carries the same information in browseable form. The public surface:

- [`Iqdb`](./src/handle.rs) — the database handle.
- `Iqdb::open_in_memory(dim, metric)` — an ephemeral, exact-flat database.
- `Iqdb::open_in_memory_with(config)` — an in-memory database from a full [`IqdbConfig`].
- `Iqdb::open(path, dim, metric)` / `Iqdb::open_with(path, config)` — a durable, file-backed database.
- `Iqdb::upsert(id, vector, metadata)` — insert or replace. Rejects a wrong-dimension vector at the boundary.
- `Iqdb::get(id)` — look up the stored vector and metadata. `Ok(None)` when absent.
- `Iqdb::delete(id)` — remove by id; returns whether it was present.
- `Iqdb::len()` / `Iqdb::is_empty()` — cardinality.
- `Iqdb::search(query, k)` — top-`k` similarity search under the database metric.
- `Iqdb::search_with(query, k, filter)` — top-`k` restricted by a metadata [`Filter`].
- `Iqdb::search_batch(...)` / `search_batch_with(...)` — order-preserving batch variants.
- `Iqdb::optimize()` — rebuild / retrain the approximate index over the current vectors.
- `Iqdb::cache_stats()` — cache hit/miss statistics, when a cache is configured.
- `Iqdb::flush()` — compact a file-backed store; no-op in memory.
- `Iqdb::close(self)` — final compaction, then release.
- [`AsyncIqdb`](./src/async_db.rs) — *(`async` feature)* a Tokio adapter mirroring the `Iqdb` surface; offloads each blocking call via `spawn_blocking`. `Clone` + `Send` + `Sync`.
- [`IqdbConfig`](./src/config.rs) — fluent construction config: `dim`, `metric`, an [`IndexKind`], and an optional [`CacheConfig`].
- [`IndexKind`](./src/config.rs) — `Flat` (exact), `Hnsw(HnswConfig)`, `Ivf(IvfConfig)`.
- [`HnswConfig`] / [`IvfConfig`] / [`CacheConfig`] — re-exported tuning structs for the approximate indices and the cache.
- [`Vector`] / [`VectorId`] / [`Metadata`] / [`Value`] / [`Hit`] / [`Filter`] / [`DistanceMetric`] — the shared vocabulary, re-exported from `iqdb-types`.
- [`Error`](./src/error.rs) / [`Result`](./src/error.rs) — the unified error type (`#[non_exhaustive]`) and its `Result` alias.

[`Filter`]: https://docs.rs/iqdb-types
[`Metadata`]: https://docs.rs/iqdb-types
[`Value`]: https://docs.rs/iqdb-types
[`Vector`]: https://docs.rs/iqdb-types
[`VectorId`]: https://docs.rs/iqdb-types
[`Hit`]: https://docs.rs/iqdb-types
[`DistanceMetric`]: https://docs.rs/iqdb-types
[`HnswConfig`]: https://docs.rs/iqdb-hnsw
[`IvfConfig`]: https://docs.rs/iqdb-ivf
[`CacheConfig`]: https://docs.rs/iqdb-cache
[`IqdbConfig`]: ./src/config.rs
[`IndexKind`]: ./src/config.rs

### Error variants

| Variant | Meaning | Recovery |
|---------|---------|----------|
| `Error::Index(IqdbError)` | A failure from the index / vocabulary layer — dimension mismatch, absent id, invalid metric for the chosen index, malformed filter. | Inspect the wrapped [`iqdb_types::IqdbError`] kind and fix the construction or query site. |
| `Error::Persist(PersistError)` | A failure from the durable-storage layer — snapshot / WAL I/O, a corrupt or truncated file, a checksum mismatch, or an unsupported compression feature. | Inspect the wrapped [`iqdb_persist::PersistError`]. A corrupt WAL tail is truncated automatically; a corrupt snapshot fails the open. |
| `Error::Config(&'static str)` | A handle-level consistency check failed — most often a reopen whose `dim` / `metric` does not match the stored database. | Open with the values the database was created with. |

The enum is `#[non_exhaustive]`; always include a `_` arm in a `match`.




## Examples

Self-contained examples live in [`examples/`](./examples). Run them with `cargo run --example `.

- **`basic`** — open, upsert, get, search, delete. [`examples/basic.rs`](./examples/basic.rs)
- **`in_memory_store`** — metadata, replace-on-upsert, and a metadata-filtered search. [`examples/in_memory_store.rs`](./examples/in_memory_store.rs)
- **`search`** — top-`k`, batch, and the effect of the distance metric. [`examples/search.rs`](./examples/search.rs)
- **`persistence`** — three sessions against one durable file, showing data survives reopen. [`examples/persistence.rs`](./examples/persistence.rs)
- **`index_selection`** — flat vs HNSW vs IVF through `IqdbConfig`, plus a cache and `optimize`. [`examples/index_selection.rs`](./examples/index_selection.rs)
- **`async_search`** *(`async` feature)* — concurrent searches fanned out across Tokio tasks. [`examples/async_search.rs`](./examples/async_search.rs)

```sh
cargo run --example index_selection
cargo run --example async_search --features async
```




## Benchmarks

A Criterion harness lives in [`benches/search.rs`](./benches/search.rs):

- **`flat/search_dim64_n1000_k10`** / **`hnsw/search_dim64_n1000_k10`** — top-`k` query throughput on the exact and approximate paths over 1 000 vectors at dim 64.
- **`flat/upsert_dim64`** — write throughput building a fresh database.

```sh
cargo bench --bench search
```

Indicative baselines on a developer machine (dim 64, 1 000 vectors): flat `search` ≈ 7.9 µs, HNSW `search` ≈ 35.8 µs, flat `upsert` of 1 000 vectors ≈ 185 µs. At this corpus size the exact flat scan beats HNSW's graph traversal — the approximate index earns its overhead at much larger scale.

Criterion writes reports to `target/criterion/`. A regression beyond the REPS threshold (5% on a tracked metric) blocks a release.




## Testing

Every public path has happy / error / edge-case coverage:

- Unit tests live in `#[cfg(test)] mod tests` blocks inside each source file.
- Integration tests live in [`tests/`](./tests):
- [`tests/persistence.rs`](./tests/persistence.rs) — durable lifecycle: open / upsert / close / reopen, delete and metadata persistence, WAL replay without close, dim/metric-mismatch rejection, IVF round-trip, multi-session accumulation.
- [`tests/properties.rs`](./tests/properties.rs) — `proptest`-driven invariants: flat ranking (sorted, bounded, unique) and the durable round-trip preserving arbitrary record sets.
- [`tests/recall.rs`](./tests/recall.rs) — recall@k of HNSW and IVF measured against the exact flat oracle on deterministic synthetic data.
- [`tests/recovery.rs`](./tests/recovery.rs) — crash recovery: a torn WAL tail is truncated (prior records survive), a corrupt snapshot fails the open, a non-database file is rejected.
- Doc tests run as part of `cargo test` and validate every `# Examples` block.

```sh
cargo test
cargo test --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
```




## Cross-Platform Support

**Tier 1 targets** — every commit is built and tested on:

- Linux (`x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`)
- macOS (`x86_64-apple-darwin`, `aarch64-apple-darwin`)
- Windows (`x86_64-pc-windows-msvc`)

Durable storage is provided by `iqdb-persist`, which takes the strongest power-loss sync each platform offers and replaces snapshots atomically. The on-disk format is little-endian on every platform, so a database written on one architecture reads back identically on another.




## Configuration

### Feature flags

Feature flags are strictly additive (per REPS) — enabling any combination never removes or weakens existing functionality.

| Feature | Default | Description |
|------------|---------|-------------|
| `serde` | off | Derive `Serialize` / `Deserialize` on the public data types (forwards to the family `serde` features). |
| `parallel` | off | Rayon-backed parallel distance scan on the flat index (forwards to `iqdb-flat`). |
| `zstd` | off | Zstandard snapshot compression (forwards to `iqdb-persist`). |
| `lz4` | off | LZ4 snapshot compression (forwards to `iqdb-persist`). |
| `async` | off | Tokio-driven `AsyncIqdb` mirror of the public API. Pulls `tokio` (only the `rt` feature). |

```toml
iqdb = { version = "1", features = ["serde"] }
```

### Runtime configuration

`Iqdb::open_in_memory(dim, metric)` and `Iqdb::open(path, dim, metric)` cover the common case. Index selection, tuning, and caching are configured through the fluent [`IqdbConfig`](./src/config.rs) passed to the `_with` constructors.




## Architecture

The crate is the integration layer over the iqdb family; each module owns one concern:

- `src/lib.rs` — crate root, lint profile, vocabulary re-exports.
- `src/handle.rs` — the [`Iqdb`](./src/handle.rs) handle and its `RwLock`-guarded in-memory / file-backed storage seam.
- `src/config.rs` — the fluent [`IqdbConfig`](./src/config.rs) and the [`IndexKind`](./src/config.rs) union.
- `src/error.rs` — the unified [`Error`](./src/error.rs) wrapping the family error vocabularies.
- `src/engine/mod.rs` — `IqdbCore`, the owned engine that implements the `iqdb-index` and `iqdb-persist` traits over an authoritative row store plus a derived index.
- `src/engine/store.rs` — the authoritative, insertion-ordered row store (the single source of truth for `len` and rebuilds).
- `src/engine/index.rs` — `AnyIndex`, the closed enum over `FlatIndex` / `HnswIndex` / `IvfIndex` with the IVF training hooks.
- `src/engine/codec.rs` — the little-endian on-disk payload codec inside the `iqdb-persist` frame.

### Compile-time guarantees

The crate root enables the strict REPS lint profile in [`src/lib.rs`](./src/lib.rs):

```text
#![deny(warnings)]
#![deny(missing_docs)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(unused_must_use)]
#![deny(unused_results)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::todo)]
#![deny(clippy::unimplemented)]
#![deny(clippy::print_stdout)]
#![deny(clippy::print_stderr)]
#![deny(clippy::dbg_macro)]
#![deny(clippy::unreachable)]
#![deny(clippy::undocumented_unsafe_blocks)]
```

Test modules locally relax the `unwrap_used` / `expect_used` lints — the strict profile is for production library code, not assertion scaffolding inside `#[cfg(test)]` blocks. The crate contains no `unsafe` code.




## Contributing

Pull requests are welcome. Before opening one, please make sure the full CI gate passes locally:

```sh
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo test --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo deny check
cargo audit
```

Every contribution is expected to honour the standards in [`REPS.md`](./REPS.md) — performance, security, error handling, testing, documentation, and dependency hygiene are all enforced as merge gates, not afterthoughts. Commit messages are imperative, lowercase, and scoped to a single logical change.






HOME
 │ 
CHANGELOG
 │ 
STANDARDS
 │ 
DOCS



## Links

- [Documentation (docs.rs)](https://docs.rs/iqdb)
- [Crates.io](https://crates.io/crates/iqdb)
- [Repository](https://github.com/jamesgober/iqdb)
- [Issues](https://github.com/jamesgober/iqdb/issues)
- [Changelog](./CHANGELOG.md)
- [Standards (REPS)](./REPS.md)






LICENSE


Licensed under either of


Apache License, Version 2.0: LICENSE-APACHE
http://www.apache.org/licenses/LICENSE-2.0



MIT License: LICENSE-MIT
http://opensource.org/licenses/MIT



At your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.




Copyright © 2026 James Gober.