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, andfile-transformare enabled by default;sealedandsealed-encryptionare opt-in and independent of SQLite.
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
All stores expose the same read contract (ObjectSource), so restoration code is identical for every backend. They differ only in write lifecycle:
Pack: a mutable SQLite store in a single file — incremental writes, transactions, Merkle proofs.SqliteStore: the same store, embeddable in an existing SQLite database under a table namespace.- 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.
Storing files
Pipeline splits an input into chunks, compresses each one, and computes everything needed to rebuild the file. Persist the plan into Pack or SqliteStore and keep the returned recipe hash — it is the handle for later restoration:
use ;
async
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 ;
use TryStreamExt;
async
Restoring a file
Construct a TransformDecoderRegistry with every transform version you have ever written, then use one FileReader for any store:
use ;
async 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.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.