assetpack
assetpack provides Rust crates for content-addressed asset packing. It focuses on splitting files into stable chunks, deduplicating payloads by SHA3-256, writing SQLite-backed/Sealed packs with content-addressed objects, and recording recipes that can rebuild the original bytes.
Crates
assetpack-core: core hashing, chunking, codec, recipe,FileReader, pipeline, and SQLite/Sealed pack primitives.assetpack-transform-precomp2: optional file transforms built onprecomp2, with zstd and lzma wrapping variants.- Core features
serde,sqlite-pack,rusqlite-store, andfile-transformare enabled by default.sqlx-store,sealed, andsealed-encryptionare opt-in.
Features
- SHA3-256 object identity and recipe integrity checks.
- FastCDC content-defined chunking with stable default bounds.
- Per-chunk raw, zstd, and Brotli storage with automatic fallback when compression is not worthwhile.
- SQLite object storage for chunks and recipes, plus optional namespaced stores for embedding.
- File transform selection with decode verification before accepting transformed output.
- Merkle index rebuilds, integrity checks, membership proofs, and batched proof lookup.
- Optional
precomp2,precomp2-zstd, andprecomp2-lzmatransforms.
Usage
Choosing a store
The synchronous backends implement ObjectSource; SQLx implements AsyncObjectSource:
- SealedPack: an immutable single-file snapshot for distribution or archival. It is built once from a complete object set, can be encrypted, and is never updated in place — changing content means building a new pack.
SqlitePack: owns a rusqlite connection and standalone database-file lifecycle.RusqliteStore<'_>: borrows an existing rusqlite connection or transaction and supports a table prefix.SqlxStore: owns an existing SQLxSqlitePool, supports a table prefix, and accepts caller-owned SQLx transactions.
sqlite-pack and rusqlite-store do not enable SQLx, Tokio, or futures. Async applications choose their own scheduling boundary when using a synchronous backend.
Storing files
Pipeline splits an input into chunks, compresses each one, and computes everything needed to rebuild the file. Persist the plan and keep the returned recipe hash:
use ;
To also try precomp2-family transforms during selection, build a TransformSelector from assetpack_transform_precomp2::default_specs() and pass it as the last argument of run.
Building a SealedPack
use ;
Restoring a file
Construct a TransformDecoderRegistry with every transform version you have ever written. Use FileReader with Sealed/rusqlite sources and AsyncFileReader with SqlxStore:
use ;
Sized>
Decoders are selected by the exact (transform_id, transform_version) recorded in the recipe; unknown pairs fail with UnsupportedTransform instead of falling back silently. For precomp2 support, register assetpack_transform_precomp2::default_decoders(&config). Restoration returns the complete file; seek or range decoding of transformed files is not supported.
What a successful call proves
ObjectSource::read_object: the returned bytes match the requested object hash.AsyncObjectSource::read_object: the same contract, with explicit asynchronous source scheduling.SealedPackReader::verify_all_objects(): every object in the container passes that same check.FileReader::read_file/restore_file: the restored bytes equal the original file, in size and SHA3-256.
Compatibility
These crates are pre-1.0 and the public API may still change. The SealedPack v1 on-disk format is pinned by a byte-exact golden fixture; any format change requires a new revision. Parsers and the shared read path are fuzzed continuously in CI: curated seeds live in fuzz/corpus, dynamic corpus stays in CI caches, and every crash becomes a deterministic regression test.
License
Licensed under the AGPL-3.0-or-later license.