From 2e2190d7834364191c0afd40243dca01eedf4581 Mon Sep 17 00:00:00 2001
From: librelois <c@elo.tf>
Date: Sun, 11 Oct 2020 14:28:59 +0200
Subject: [PATCH] [feat] create rust lib duniter-dbs

---
 Cargo.lock                                    | 1003 +++++++++++++++--
 Cargo.toml                                    |    6 +-
 rust-libs/duniter-dbs/Cargo.toml              |   40 +
 rust-libs/duniter-dbs/src/bc_v1.rs            |  158 +++
 rust-libs/duniter-dbs/src/errors.rs           |   49 +
 rust-libs/duniter-dbs/src/keys.rs             |   25 +
 rust-libs/duniter-dbs/src/keys/all.rs         |   56 +
 .../duniter-dbs/src/keys/block_number.rs      |   97 ++
 rust-libs/duniter-dbs/src/keys/blockstamp.rs  |   61 +
 rust-libs/duniter-dbs/src/keys/hash.rs        |   52 +
 rust-libs/duniter-dbs/src/keys/pubkey.rs      |   80 ++
 .../duniter-dbs/src/keys/pubkey_and_sig.rs    |   69 ++
 rust-libs/duniter-dbs/src/keys/source_key.rs  |   91 ++
 rust-libs/duniter-dbs/src/keys/timestamp.rs   |   58 +
 rust-libs/duniter-dbs/src/keys/uid.rs         |   65 ++
 .../duniter-dbs/src/keys/wallet_conditions.rs |   65 ++
 rust-libs/duniter-dbs/src/lib.rs              |   95 ++
 rust-libs/duniter-dbs/src/values.rs           |   26 +
 rust-libs/duniter-dbs/src/values/block_db.rs  |  117 ++
 .../duniter-dbs/src/values/block_head_db.rs   |   86 ++
 .../src/values/block_number_array_db.rs       |   54 +
 rust-libs/duniter-dbs/src/values/cindex_db.rs |   99 ++
 rust-libs/duniter-dbs/src/values/iindex_db.rs |   82 ++
 rust-libs/duniter-dbs/src/values/kick_db.rs   |   56 +
 rust-libs/duniter-dbs/src/values/mindex_db.rs |  106 ++
 rust-libs/duniter-dbs/src/values/pubkey_db.rs |  109 ++
 rust-libs/duniter-dbs/src/values/sindex_db.rs |  128 +++
 .../duniter-dbs/src/values/ud_entry_db.rs     |   80 ++
 rust-libs/duniter-dbs/src/values/wallet_db.rs |   56 +
 rust-libs/duniter-dbs/tests/test_explorer.rs  |  254 +++++
 .../duniter-dbs/tests/test_read_write.rs      |  350 ++++++
 rust-libs/duniter-dbs/tests/test_tmp_real.rs  |  736 ++++++++++++
 rust-libs/tools/kv_typed/Cargo.toml           |   57 +
 .../kv_typed/benches/compare_backends.rs      |  101 ++
 rust-libs/tools/kv_typed/src/as_bytes.rs      |   36 +
 rust-libs/tools/kv_typed/src/backend.rs       |   74 ++
 .../tools/kv_typed/src/backend/leveldb.rs     |  208 ++++
 .../tools/kv_typed/src/backend/memory.rs      |  200 ++++
 rust-libs/tools/kv_typed/src/backend/mock.rs  |   70 ++
 rust-libs/tools/kv_typed/src/backend/sled.rs  |  145 +++
 .../src/backend/sled/transactional.rs         |   83 ++
 rust-libs/tools/kv_typed/src/batch.rs         |   74 ++
 rust-libs/tools/kv_typed/src/collection_ro.rs |   85 ++
 rust-libs/tools/kv_typed/src/collection_rw.rs |  121 ++
 rust-libs/tools/kv_typed/src/error.rs         |   65 ++
 rust-libs/tools/kv_typed/src/event.rs         |   30 +
 rust-libs/tools/kv_typed/src/explorer.rs      |  371 ++++++
 rust-libs/tools/kv_typed/src/from_bytes.rs    |   22 +
 rust-libs/tools/kv_typed/src/iter.rs          |  116 ++
 rust-libs/tools/kv_typed/src/iter/keys.rs     |   58 +
 rust-libs/tools/kv_typed/src/iter/range.rs    |  110 ++
 rust-libs/tools/kv_typed/src/iter/values.rs   |   61 +
 rust-libs/tools/kv_typed/src/key.rs           |   62 +
 rust-libs/tools/kv_typed/src/lib.rs           |  111 ++
 rust-libs/tools/kv_typed/src/subscription.rs  |  108 ++
 rust-libs/tools/kv_typed/src/utils.rs         |   24 +
 rust-libs/tools/kv_typed/src/utils/arc.rs     |  267 +++++
 rust-libs/tools/kv_typed/src/utils/ivec.rs    |  314 ++++++
 rust-libs/tools/kv_typed/src/value.rs         |   59 +
 rust-libs/tools/kv_typed/tests/db_schema.rs   |   73 ++
 rust-libs/tools/kv_typed/tests/test_mock.rs   |   54 +
 rust-libs/tools/kv_typed_code_gen/Cargo.toml  |   19 +
 .../tools/kv_typed_code_gen/src/col_schema.rs |   81 ++
 .../kv_typed_code_gen/src/db_readable.rs      |   49 +
 .../tools/kv_typed_code_gen/src/db_schema.rs  |   48 +
 .../kv_typed_code_gen/src/db_writable.rs      |   85 ++
 rust-libs/tools/kv_typed_code_gen/src/lib.rs  |  236 ++++
 67 files changed, 7997 insertions(+), 89 deletions(-)
 create mode 100644 rust-libs/duniter-dbs/Cargo.toml
 create mode 100644 rust-libs/duniter-dbs/src/bc_v1.rs
 create mode 100644 rust-libs/duniter-dbs/src/errors.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/all.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/block_number.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/blockstamp.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/hash.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/pubkey.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/source_key.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/timestamp.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/uid.rs
 create mode 100644 rust-libs/duniter-dbs/src/keys/wallet_conditions.rs
 create mode 100644 rust-libs/duniter-dbs/src/lib.rs
 create mode 100644 rust-libs/duniter-dbs/src/values.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/block_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/block_head_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/block_number_array_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/cindex_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/iindex_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/kick_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/mindex_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/pubkey_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/sindex_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/ud_entry_db.rs
 create mode 100644 rust-libs/duniter-dbs/src/values/wallet_db.rs
 create mode 100644 rust-libs/duniter-dbs/tests/test_explorer.rs
 create mode 100644 rust-libs/duniter-dbs/tests/test_read_write.rs
 create mode 100644 rust-libs/duniter-dbs/tests/test_tmp_real.rs
 create mode 100644 rust-libs/tools/kv_typed/Cargo.toml
 create mode 100644 rust-libs/tools/kv_typed/benches/compare_backends.rs
 create mode 100644 rust-libs/tools/kv_typed/src/as_bytes.rs
 create mode 100644 rust-libs/tools/kv_typed/src/backend.rs
 create mode 100644 rust-libs/tools/kv_typed/src/backend/leveldb.rs
 create mode 100644 rust-libs/tools/kv_typed/src/backend/memory.rs
 create mode 100644 rust-libs/tools/kv_typed/src/backend/mock.rs
 create mode 100644 rust-libs/tools/kv_typed/src/backend/sled.rs
 create mode 100644 rust-libs/tools/kv_typed/src/backend/sled/transactional.rs
 create mode 100644 rust-libs/tools/kv_typed/src/batch.rs
 create mode 100644 rust-libs/tools/kv_typed/src/collection_ro.rs
 create mode 100644 rust-libs/tools/kv_typed/src/collection_rw.rs
 create mode 100644 rust-libs/tools/kv_typed/src/error.rs
 create mode 100644 rust-libs/tools/kv_typed/src/event.rs
 create mode 100644 rust-libs/tools/kv_typed/src/explorer.rs
 create mode 100644 rust-libs/tools/kv_typed/src/from_bytes.rs
 create mode 100644 rust-libs/tools/kv_typed/src/iter.rs
 create mode 100644 rust-libs/tools/kv_typed/src/iter/keys.rs
 create mode 100644 rust-libs/tools/kv_typed/src/iter/range.rs
 create mode 100644 rust-libs/tools/kv_typed/src/iter/values.rs
 create mode 100644 rust-libs/tools/kv_typed/src/key.rs
 create mode 100644 rust-libs/tools/kv_typed/src/lib.rs
 create mode 100644 rust-libs/tools/kv_typed/src/subscription.rs
 create mode 100644 rust-libs/tools/kv_typed/src/utils.rs
 create mode 100644 rust-libs/tools/kv_typed/src/utils/arc.rs
 create mode 100644 rust-libs/tools/kv_typed/src/utils/ivec.rs
 create mode 100644 rust-libs/tools/kv_typed/src/value.rs
 create mode 100644 rust-libs/tools/kv_typed/tests/db_schema.rs
 create mode 100644 rust-libs/tools/kv_typed/tests/test_mock.rs
 create mode 100644 rust-libs/tools/kv_typed_code_gen/Cargo.toml
 create mode 100644 rust-libs/tools/kv_typed_code_gen/src/col_schema.rs
 create mode 100644 rust-libs/tools/kv_typed_code_gen/src/db_readable.rs
 create mode 100644 rust-libs/tools/kv_typed_code_gen/src/db_schema.rs
 create mode 100644 rust-libs/tools/kv_typed_code_gen/src/db_writable.rs
 create mode 100644 rust-libs/tools/kv_typed_code_gen/src/lib.rs

diff --git a/Cargo.lock b/Cargo.lock
index dfbe77eda..93ae65161 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,5 +1,15 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+[[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
 [[package]]
 name = "addr2line"
 version = "0.13.0"
@@ -17,9 +27,9 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.10"
+version = "0.7.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
+checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
 dependencies = [
  "memchr",
 ]
@@ -51,6 +61,123 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
 
+[[package]]
+name = "async-attributes"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-channel"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21279cfaa4f47df10b1816007e738ca3747ef2ee53ffc51cdbf57a8bb266fee3"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+ "vec-arena",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fefeb39da249f4c33af940b779a56723ce45809ef5c54dad84bb538d4ffb6d9e"
+dependencies = [
+ "async-executor",
+ "async-io",
+ "futures-lite",
+ "num_cpus",
+ "once_cell",
+]
+
+[[package]]
+name = "async-io"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a027f4b662e59d7070791e33baafd36affe67388965701b50039db5ebb240d2"
+dependencies = [
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "libc",
+ "log",
+ "nb-connect",
+ "once_cell",
+ "parking",
+ "polling",
+ "vec-arena",
+ "waker-fn",
+ "winapi",
+]
+
+[[package]]
+name = "async-mutex"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-std"
+version = "1.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9fa76751505e8df1c7a77762f60486f60c71bbd9b8557f4da6ad47d083732ed"
+dependencies = [
+ "async-attributes",
+ "async-global-executor",
+ "async-io",
+ "async-mutex",
+ "blocking",
+ "crossbeam-utils",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-lite",
+ "gloo-timers",
+ "kv-log-macro",
+ "log",
+ "memchr",
+ "num_cpus",
+ "once_cell",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "async-task"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ab27c1aa62945039e44edaeee1dc23c74cc0c303dd5fe0fb462a184f1c3a518"
+
+[[package]]
+name = "atomic-waker"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
+
 [[package]]
 name = "atty"
 version = "0.2.14"
@@ -64,15 +191,15 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 
 [[package]]
 name = "backtrace"
-version = "0.3.50"
+version = "0.3.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293"
+checksum = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1"
 dependencies = [
  "addr2line",
  "cfg-if",
@@ -102,9 +229,9 @@ checksum = "474a626a67200bd107d44179bb3d4fc61891172d11696609264589be6a0e6a43"
 
 [[package]]
 name = "bincode"
-version = "1.2.1"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf"
+checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
 dependencies = [
  "byteorder",
  "serde",
@@ -148,17 +275,43 @@ dependencies = [
  "byte-tools",
 ]
 
+[[package]]
+name = "blocking"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2640778f8053e72c11f621b0a5175a0560a269282aa98ed85107773ab8e2a556"
+dependencies = [
+ "async-channel",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+ "waker-fn",
+]
+
 [[package]]
 name = "bs58"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb"
 
+[[package]]
+name = "bstr"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
 [[package]]
 name = "bumpalo"
-version = "3.2.1"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187"
+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
 
 [[package]]
 name = "byte-tools"
@@ -172,11 +325,26 @@ version = "1.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
 
+[[package]]
+name = "cache-padded"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
+
+[[package]]
+name = "cast"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
+dependencies = [
+ "rustc_version",
+]
+
 [[package]]
 name = "cc"
-version = "1.0.52"
+version = "1.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d"
+checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c"
 
 [[package]]
 name = "cfg-if"
@@ -221,6 +389,33 @@ dependencies = [
  "vec_map",
 ]
 
+[[package]]
+name = "cloudabi"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
+dependencies = [
+ "cache-padded",
+]
+
 [[package]]
 name = "constant_time_eq"
 version = "0.1.5"
@@ -236,6 +431,52 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "criterion"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8"
+dependencies = [
+ "atty",
+ "cast",
+ "clap",
+ "criterion-plot",
+ "csv",
+ "itertools",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_cbor",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
+dependencies = [
+ "crossbeam-utils",
+ "maybe-uninit",
+]
+
 [[package]]
 name = "crossbeam-deque"
 version = "0.7.3"
@@ -262,16 +503,6 @@ dependencies = [
  "scopeguard",
 ]
 
-[[package]]
-name = "crossbeam-queue"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
-dependencies = [
- "cfg-if",
- "crossbeam-utils",
-]
-
 [[package]]
 name = "crossbeam-utils"
 version = "0.7.2"
@@ -295,6 +526,28 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40"
 
+[[package]]
+name = "csv"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279"
+dependencies = [
+ "bstr",
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "ctrlc"
 version = "3.1.6"
@@ -316,6 +569,12 @@ dependencies = [
  "snafu",
 ]
 
+[[package]]
+name = "difference"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
+
 [[package]]
 name = "digest"
 version = "0.8.1"
@@ -351,6 +610,12 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 
+[[package]]
+name = "downcast"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d"
+
 [[package]]
 name = "dubp-common"
 version = "0.25.2"
@@ -414,6 +679,25 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "duniter-dbs"
+version = "0.1.0"
+dependencies = [
+ "arrayvec",
+ "chrono",
+ "dubp-common",
+ "kv_typed",
+ "log",
+ "mockall",
+ "once_cell",
+ "serde",
+ "serde_json",
+ "smallvec",
+ "tempdir",
+ "thiserror",
+ "unwrap",
+]
+
 [[package]]
 name = "duniter-launcher"
 version = "1.8.1"
@@ -468,9 +752,9 @@ dependencies = [
 
 [[package]]
 name = "either"
-version = "1.5.3"
+version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
 
 [[package]]
 name = "envmnt"
@@ -492,17 +776,32 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "event-listener"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+
 [[package]]
 name = "fake-simd"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
 
+[[package]]
+name = "fastrand"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3"
+dependencies = [
+ "instant",
+]
+
 [[package]]
 name = "flate2"
-version = "1.0.17"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "766d0e77a2c1502169d4a93ff3b8c15a71fd946cd0126309752104e5f3c46d94"
+checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee"
 dependencies = [
  "cfg-if",
  "crc32fast",
@@ -523,12 +822,88 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "float-cmp"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "fragile"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2"
+
+[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "fsio"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3"
 
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
+[[package]]
+name = "futures-channel"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
+
+[[package]]
+name = "futures-io"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
+
+[[package]]
+name = "futures-lite"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b2ec5fce115ef4d4cc77f46025232fba6a2f84381ff42337794147050aae971"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
 [[package]]
 name = "generic-array"
 version = "0.12.3"
@@ -571,6 +946,25 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
 
+[[package]]
+name = "gloo-timers"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "half"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177"
+
 [[package]]
 name = "hashbrown"
 version = "0.9.1"
@@ -588,9 +982,9 @@ dependencies = [
 
 [[package]]
 name = "hermit-abi"
-version = "0.1.12"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4"
+checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
 dependencies = [
  "libc",
 ]
@@ -605,6 +999,24 @@ dependencies = [
  "hashbrown",
 ]
 
+[[package]]
+name = "instant"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itertools"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.6"
@@ -613,9 +1025,9 @@ checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
 
 [[package]]
 name = "js-sys"
-version = "0.3.37"
+version = "0.3.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055"
+checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -624,25 +1036,96 @@ dependencies = [
 name = "json-pest-parser"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bc5c84a2bceeda1ce3bd58497bde2d8cba61ca0b45873ef502401f0ff2ae8ed"
+checksum = "3bc5c84a2bceeda1ce3bd58497bde2d8cba61ca0b45873ef502401f0ff2ae8ed"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "thiserror",
+ "unwrap",
+]
+
+[[package]]
+name = "kv-log-macro"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "kv_typed"
+version = "0.1.0"
+dependencies = [
+ "async-channel",
+ "async-std",
+ "criterion",
+ "crossbeam-channel",
+ "kv_typed_code_gen",
+ "leveldb_minimal",
+ "maybe-async",
+ "mockall",
+ "parking_lot",
+ "rayon",
+ "regex",
+ "serde_json",
+ "sled",
+ "smallvec",
+ "thiserror",
+ "unwrap",
+]
+
+[[package]]
+name = "kv_typed_code_gen"
+version = "0.1.0"
+dependencies = [
+ "Inflector",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "leveldb-sys"
+version = "2.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76c44b9b785ca705d58190ebd432a4e7edb900eadf236ff966d7d1307e482e87"
+dependencies = [
+ "cmake",
+ "libc",
+ "num_cpus",
+]
+
+[[package]]
+name = "leveldb_minimal"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b4cb22d7d3cce486fc6e2ef7cd25d38e118f525f79c4a946ac48d89c5d16b1"
 dependencies = [
- "pest",
- "pest_derive",
- "thiserror",
- "unwrap",
+ "leveldb-sys",
+ "libc",
 ]
 
 [[package]]
-name = "lazy_static"
-version = "1.4.0"
+name = "libc"
+version = "0.2.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98"
 
 [[package]]
-name = "libc"
-version = "0.2.69"
+name = "lock_api"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
+checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
+dependencies = [
+ "scopeguard",
+]
 
 [[package]]
 name = "log"
@@ -665,6 +1148,17 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
 
+[[package]]
+name = "maybe-async"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51772c1b1c6c129eeeaf8ffe7581906aa14c8cca116b40d41ce1285f5a4fdfa0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "maybe-uninit"
 version = "2.0.0"
@@ -679,20 +1173,58 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
 
 [[package]]
 name = "memoffset"
-version = "0.5.4"
+version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8"
+checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
 dependencies = [
  "autocfg",
 ]
 
 [[package]]
 name = "miniz_oxide"
-version = "0.4.0"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
+checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9"
 dependencies = [
  "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "mockall"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ecfc6340c5b98a9a270b56e5f43353d87ebb18d9458a9301344bc79317c563"
+dependencies = [
+ "cfg-if",
+ "downcast",
+ "fragile",
+ "lazy_static",
+ "mockall_derive",
+ "predicates",
+ "predicates-tree",
+]
+
+[[package]]
+name = "mockall_derive"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b873f753808fe0c3827ce76edb3ace27804966dfde3043adfac1c24d0a2559df"
+dependencies = [
+ "cfg-if",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "nb-connect"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998"
+dependencies = [
+ "libc",
+ "winapi",
 ]
 
 [[package]]
@@ -769,6 +1301,12 @@ dependencies = [
  "void",
 ]
 
+[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
 [[package]]
 name = "num"
 version = "0.2.1"
@@ -855,12 +1393,50 @@ version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
 
+[[package]]
+name = "oorandom"
+version = "11.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c"
+
 [[package]]
 name = "opaque-debug"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
 
+[[package]]
+name = "parking"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
+dependencies = [
+ "cfg-if",
+ "cloudabi",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
 [[package]]
 name = "pest"
 version = "2.1.3"
@@ -904,6 +1480,72 @@ dependencies = [
  "sha-1",
 ]
 
+[[package]]
+name = "pin-project-lite"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "plotters"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb"
+dependencies = [
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "polling"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7215a098a80ab8ebd6349db593dc5faf741781bad0c4b7c5701fea6af548d52c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "log",
+ "wepoll-sys",
+ "winapi",
+]
+
+[[package]]
+name = "predicates"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a"
+dependencies = [
+ "difference",
+ "float-cmp",
+ "normalize-line-endings",
+ "predicates-core",
+ "regex",
+]
+
+[[package]]
+name = "predicates-core"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178"
+
+[[package]]
+name = "predicates-tree"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124"
+dependencies = [
+ "predicates-core",
+ "treeline",
+]
+
 [[package]]
 name = "proc-macro-error"
 version = "1.0.4"
@@ -930,28 +1572,57 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.19"
+version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
 dependencies = [
  "unicode-xid",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.3"
+version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
 dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
+dependencies = [
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.3.1",
+ "rdrand",
+ "winapi",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+dependencies = [
+ "rand_core 0.4.2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+
 [[package]]
 name = "rayon"
-version = "1.3.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
+checksum = "dcf6960dc9a5b4ee8d3e4c5787b4a112a8818e0290a42ff664ad60692fdf2032"
 dependencies = [
+ "autocfg",
  "crossbeam-deque",
  "either",
  "rayon-core",
@@ -959,17 +1630,26 @@ dependencies = [
 
 [[package]]
 name = "rayon-core"
-version = "1.7.0"
+version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
+checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf"
 dependencies = [
+ "crossbeam-channel",
  "crossbeam-deque",
- "crossbeam-queue",
  "crossbeam-utils",
  "lazy_static",
  "num_cpus",
 ]
 
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.1.57"
@@ -989,9 +1669,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.3.7"
+version = "1.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
+checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -999,11 +1679,29 @@ dependencies = [
  "thread_local",
 ]
 
+[[package]]
+name = "regex-automata"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
+dependencies = [
+ "byteorder",
+]
+
 [[package]]
 name = "regex-syntax"
-version = "0.6.17"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
 
 [[package]]
 name = "ring"
@@ -1038,6 +1736,15 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
 
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
 [[package]]
 name = "rusty-hook"
 version = "0.11.2"
@@ -1056,6 +1763,15 @@ version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
 
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -1079,18 +1795,28 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 
 [[package]]
 name = "serde"
-version = "1.0.115"
+version = "1.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5"
+checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
 dependencies = [
  "serde_derive",
 ]
 
+[[package]]
+name = "serde_cbor"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
+dependencies = [
+ "half",
+ "serde",
+]
+
 [[package]]
 name = "serde_derive"
-version = "1.0.115"
+version = "1.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48"
+checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1099,9 +1825,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.57"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
+checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4"
 dependencies = [
  "itoa",
  "ryu",
@@ -1120,6 +1846,28 @@ dependencies = [
  "opaque-debug",
 ]
 
+[[package]]
+name = "slab"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+
+[[package]]
+name = "sled"
+version = "0.34.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f72c064e63fbca3138ad07f3588c58093f1684f3a99f60dcfa6d46b87e60fde7"
+dependencies = [
+ "crc32fast",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "fs2",
+ "fxhash",
+ "libc",
+ "log",
+ "parking_lot",
+]
+
 [[package]]
 name = "smallvec"
 version = "1.4.2"
@@ -1188,9 +1936,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.39"
+version = "1.0.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9"
+checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1199,9 +1947,9 @@ dependencies = [
 
 [[package]]
 name = "synstructure"
-version = "0.12.3"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
+checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1209,6 +1957,16 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "tempdir"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
+dependencies = [
+ "rand",
+ "remove_dir_all",
+]
+
 [[package]]
 name = "textwrap"
 version = "0.11.0"
@@ -1258,6 +2016,16 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "tinytemplate"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "toml"
 version = "0.5.6"
@@ -1267,6 +2035,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "treeline"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
+
 [[package]]
 name = "typenum"
 version = "1.12.0"
@@ -1293,9 +2067,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
 
 [[package]]
 name = "unicode-xid"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
 
 [[package]]
 name = "untrusted"
@@ -1309,6 +2083,12 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f"
 
+[[package]]
+name = "vec-arena"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d"
+
 [[package]]
 name = "vec_map"
 version = "0.8.2"
@@ -1327,6 +2107,23 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 
+[[package]]
+name = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
+[[package]]
+name = "walkdir"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
 [[package]]
 name = "wasi"
 version = "0.9.0+wasi-snapshot-preview1"
@@ -1341,9 +2138,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.60"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f"
+checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -1351,9 +2148,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.60"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd"
+checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
 dependencies = [
  "bumpalo",
  "lazy_static",
@@ -1364,11 +2161,23 @@ dependencies = [
  "wasm-bindgen-shared",
 ]
 
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.60"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4"
+checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -1376,9 +2185,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.60"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931"
+checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1389,25 +2198,34 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.60"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639"
+checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
 
 [[package]]
 name = "web-sys"
-version = "0.3.37"
+version = "0.3.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb"
+checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "wepoll-sys"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142bc2cba3fe88be1a8fcb55c727fa4cd5b0cf2d7438722792e22f26f04bc1e0"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "winapi"
-version = "0.3.8"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
 dependencies = [
  "winapi-i686-pc-windows-gnu",
  "winapi-x86_64-pc-windows-gnu",
@@ -1419,6 +2237,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
@@ -1435,18 +2262,18 @@ dependencies = [
 
 [[package]]
 name = "zeroize"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8"
+checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a"
 dependencies = [
  "zeroize_derive",
 ]
 
 [[package]]
 name = "zeroize_derive"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
+checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/Cargo.toml b/Cargo.toml
index e146e0790..0e0a33e25 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,7 +30,10 @@ rusty-hook = "0.11.2"
 members = [
     "neon/native",
     "rust-bins/xtask",
-    "rust-libs/dubp-wot"
+    "rust-libs/dubp-wot",
+    "rust-libs/duniter-dbs",
+    "rust-libs/tools/kv_typed",
+    "rust-libs/tools/kv_typed_code_gen"
 ]
 
 [patch.crates-io]
@@ -38,3 +41,4 @@ members = [
 #dubp-documents  = { path = "../dubp-rs-libs/documents" }
 #dubp-documents-parser = { path = "../dubp-rs-libs/documents-parser" }
 #dubp-wallet  = { path = "../dubp-rs-libs/wallet" }
+#leveldb_minimal = { path = "../../../../rust/leveldb_minimal" }
diff --git a/rust-libs/duniter-dbs/Cargo.toml b/rust-libs/duniter-dbs/Cargo.toml
new file mode 100644
index 000000000..5d2d1c4e3
--- /dev/null
+++ b/rust-libs/duniter-dbs/Cargo.toml
@@ -0,0 +1,40 @@
+[package]
+name = "duniter-dbs"
+version = "0.1.0"
+authors = ["elois <elois@duniter.org>"]
+description = "Duniter blockchain DB"
+repository = "https://git.duniter.org/nodes/typescript/duniter"
+keywords = ["dubp", "duniter", "blockchain", "database"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies]
+arrayvec = "0.5.1"
+chrono = { version = "0.4.15", optional = true }
+dubp-common = { version = "0.25.2", features = ["crypto_scrypt"] }
+kv_typed = { path = "../tools/kv_typed", default-features = false }
+log = "0.4.8"
+mockall = { version = "0.8.0", optional = true }
+serde = { version = "1.0.105", features = ["derive"] }
+serde_json = "1.0.53"
+smallvec = { version = "1.4.0", features = ["serde"] }
+thiserror = "1.0.20"
+
+[dev-dependencies]
+once_cell = "1.4.0"
+tempdir = "0.3.7"
+unwrap = "1.2.1"
+
+[features]
+default = ["leveldb_backend", "memory_backend", "sled_backend", "subscription", "sync"]
+
+explorer = ["chrono", "kv_typed/explorer"]
+leveldb_backend = ["kv_typed/leveldb_backend"]
+memory_backend = ["kv_typed/memory_backend"]
+mock = ["kv_typed/mock", "mockall"]
+sled_backend = ["kv_typed/sled_backend"]
+subscription = ["kv_typed/subscription"]
+sync = ["kv_typed/sync"]
diff --git a/rust-libs/duniter-dbs/src/bc_v1.rs b/rust-libs/duniter-dbs/src/bc_v1.rs
new file mode 100644
index 000000000..870bea9ad
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/bc_v1.rs
@@ -0,0 +1,158 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+db_schema!(
+    BcV1,
+    [
+        ["level_blockchain", main_blocks, BlockNumberKeyV1, BlockDbV1,],
+        [
+            "level_blockchain/idty",
+            mb_idty,
+            PubKeyKeyV1,
+            BlockNumberArrayV1,
+        ],
+        [
+            "level_blockchain/certs",
+            mb_certs,
+            PubKeyKeyV1,
+            BlockNumberArrayV1
+        ],
+        [
+            "level_blockchain/joiners",
+            mb_joiners,
+            PubKeyKeyV1,
+            BlockNumberArrayV1
+        ],
+        [
+            "level_blockchain/actives",
+            mb_actives,
+            PubKeyKeyV1,
+            BlockNumberArrayV1
+        ],
+        [
+            "level_blockchain/leavers",
+            mb_leavers,
+            PubKeyKeyV1,
+            BlockNumberArrayV1
+        ],
+        [
+            "level_blockchain/excluded",
+            mb_excluded,
+            PubKeyKeyV1,
+            BlockNumberArrayV1
+        ],
+        [
+            "level_blockchain/revoked",
+            mb_revoked,
+            PubKeyAndSigV1,
+            BlockNumberArrayV1
+        ],
+        [
+            "level_blockchain/dividends",
+            mb_dividends,
+            AllKeyV1,
+            BlockNumberArrayV1
+        ],
+        [
+            "level_blockchain/transactions",
+            mb_transactions,
+            AllKeyV1,
+            BlockNumberArrayV1
+        ],
+        [
+            "level_blockchain/forks",
+            fork_blocks,
+            BlockstampKeyV1,
+            BlockDbV1
+        ],
+        ["level_bindex", bindex, BlockNumberKeyV1, BlockHeadDbV1],
+        ["level_iindex", iindex, PubKeyKeyV1, IIndexDbV1],
+        [
+            "level_iindex/hash",
+            iindex_hash,
+            HashKeyV1,
+            PublicKeySingletonDbV1
+        ],
+        ["level_iindex/kick", iindex_kick, PubKeyKeyV1, KickDbV1],
+        [
+            "level_iindex/writtenOn",
+            iindex_written_on,
+            BlockNumberKeyV1,
+            PublicKeyArrayDbV1
+        ],
+        ["level_iindex/uid", uids, UidKeyV1, PublicKeySingletonDbV1],
+        ["level_mindex", mindex, PubKeyKeyV1, MIndexDbV1],
+        [
+            "level_mindex/expiresOn",
+            mindex_expires_on,
+            TimestampKeyV1,
+            PublicKeyArrayDbV1
+        ],
+        [
+            "level_mindex/revokesOn",
+            mindex_revokes_on,
+            TimestampKeyV1,
+            PublicKeyArrayDbV1
+        ],
+        [
+            "level_mindex/writtenOn",
+            mindex_written_on,
+            BlockNumberKeyV1,
+            PublicKeyArrayDbV1
+        ],
+        ["level_cindex", cindex, PubKeyKeyV1, CIndexDbV1],
+        [
+            "level_cindex/expiresOn",
+            cindex_expires_on,
+            BlockNumberKeyV1,
+            PublicKeyArrayDbV1
+        ],
+        [
+            "level_cindex/writtenOn",
+            cindex_written_on,
+            BlockNumberKeyV1,
+            PublicKeyArrayDbV1
+        ],
+        ["level_wallet", Wallet, WalletConditionsV1, WalletDbV1],
+        ["level_dividend", uds, PubKeyKeyV1, UdEntryDbV1],
+        [
+            "level_dividend/level_dividend_trim_index",
+            uds_trim,
+            BlockNumberKeyV1,
+            PublicKeyArrayDbV1
+        ],
+        ["level_sindex", sindex, SourceKeyV1, SIndexDBV1],
+        [
+            "level_sindex/written_on",
+            sindex_written_on,
+            BlockNumberKeyV1,
+            SourceKeyArrayDbV1
+        ],
+        [
+            "level_sindex/consumed_on",
+            sindex_consumed_on,
+            BlockNumberKeyV1,
+            SourceKeyArrayDbV1
+        ],
+        [
+            "level_sindex/conditions",
+            sindex_conditions,
+            WalletConditionsV1,
+            SourceKeyArrayDbV1
+        ],
+    ]
+);
diff --git a/rust-libs/duniter-dbs/src/errors.rs b/rust-libs/duniter-dbs/src/errors.rs
new file mode 100644
index 000000000..d945b9235
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/errors.rs
@@ -0,0 +1,49 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub type Result<T> = std::result::Result<T, ErrorDb>;
+
+#[derive(Debug, Error)]
+pub enum ErrorDb {
+    #[error("batch already consumed")]
+    BatchConsumed,
+    #[error("Collection '{collection_typename}' not exist on db '{db_version}'")]
+    NonexistentCollection {
+        collection_typename: &'static str,
+        db_version: &'static str,
+    },
+    #[cfg(feature = "explorer")]
+    #[error("Collection '{collection_name}' not exist on db '{db_version}'")]
+    NonexistentCollectionName {
+        collection_name: String,
+        db_version: &'static str,
+    },
+    #[error("DbError: {0}")]
+    DbError(String),
+    #[error("DeserError: {0}")]
+    DeserError(String),
+    #[error("SerError: {0}")]
+    Ser(String),
+    #[error("Fail to create DB folder: {0}")]
+    FailToCreateDbFolder(std::io::Error),
+}
+
+impl From<KvError> for ErrorDb {
+    fn from(e: KvError) -> Self {
+        ErrorDb::DbError(format!("{}", e))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys.rs b/rust-libs/duniter-dbs/src/keys.rs
new file mode 100644
index 000000000..d05a345d4
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys.rs
@@ -0,0 +1,25 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+pub mod all;
+pub mod block_number;
+pub mod blockstamp;
+pub mod hash;
+pub mod pubkey;
+pub mod pubkey_and_sig;
+pub mod source_key;
+pub mod timestamp;
+pub mod uid;
+pub mod wallet_conditions;
diff --git a/rust-libs/duniter-dbs/src/keys/all.rs b/rust-libs/duniter-dbs/src/keys/all.rs
new file mode 100644
index 000000000..067e44616
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/all.rs
@@ -0,0 +1,56 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct AllKeyV1;
+
+impl KeyAsBytes for AllKeyV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(b"ALL")
+    }
+}
+
+impl kv_typed::prelude::FromBytes for AllKeyV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        if bytes == b"ALL" {
+            Ok(Self)
+        } else {
+            Err(StringErr(format!(
+                "Invalid key: expected '{:?}', found '{:?}'",
+                b"ALL", bytes
+            )))
+        }
+    }
+}
+
+impl ToDumpString for AllKeyV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for AllKeyV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        <Self as kv_typed::prelude::FromBytes>::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned()))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/block_number.rs b/rust-libs/duniter-dbs/src/keys/block_number.rs
new file mode 100644
index 000000000..b2cd7755b
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/block_number.rs
@@ -0,0 +1,97 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct BlockNumberKeyV1(pub BlockNumber);
+
+impl KeyAsBytes for BlockNumberKeyV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        if self.0 == BlockNumber(u32::MAX) {
+            f(b"0000000NaN")
+        } else {
+            f(format!("{:010}", (self.0).0).as_bytes())
+        }
+    }
+}
+
+impl FromBytes for BlockNumberKeyV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let key_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?;
+        if key_str == "0000000NaN" {
+            Ok(BlockNumberKeyV1(BlockNumber(u32::MAX)))
+        } else {
+            Ok(BlockNumberKeyV1(BlockNumber(
+                key_str
+                    .parse()
+                    .map_err(|e| StringErr(format!("{}: {}", e, key_str)))?,
+            )))
+        }
+    }
+}
+
+impl ToDumpString for BlockNumberKeyV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for BlockNumberKeyV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        Ok(format!("{}", (self.0).0))
+    }
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct BlockNumberKeyV2(pub BlockNumber);
+
+impl KeyAsBytes for BlockNumberKeyV2 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        if cfg!(target_endian = "big") {
+            println!("TMP: big");
+            f(unsafe { std::mem::transmute::<u32, [u8; 4]>((self.0).0) }.as_ref())
+        } else {
+            println!("TMP: little");
+            f(unsafe { std::mem::transmute::<u32, [u8; 4]>((self.0).0.to_be()) }.as_ref())
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn test_block_number_str_10_ser() {
+        BlockNumberKeyV1(BlockNumber(35))
+            .as_bytes(|bytes| assert_eq!(bytes, &[48, 48, 48, 48, 48, 48, 48, 48, 51, 53]))
+    }
+
+    #[test]
+    fn block_number_key_v2() {
+        let k = BlockNumberKeyV2(BlockNumber(3));
+        k.as_bytes(|bytes| {
+            assert_eq!(bytes, &[0, 0, 0, 3]);
+        });
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/blockstamp.rs b/rust-libs/duniter-dbs/src/keys/blockstamp.rs
new file mode 100644
index 000000000..36cf85b1c
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/blockstamp.rs
@@ -0,0 +1,61 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct BlockstampKeyV1(Blockstamp);
+
+impl KeyAsBytes for BlockstampKeyV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(format!("{:010}-{}", self.0.number.0, self.0.hash).as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for BlockstampKeyV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let blockstamp_strs: ArrayVec<[&str; 2]> = std::str::from_utf8(bytes)
+            .map_err(|e| StringErr(format!("{}", e)))?
+            .split('-')
+            .collect();
+        let block_number = blockstamp_strs[0]
+            .parse()
+            .map_err(|e| StringErr(format!("{}", e)))?;
+        let block_hash =
+            Hash::from_hex(blockstamp_strs[1]).map_err(|e| StringErr(format!("{}", e)))?;
+        Ok(BlockstampKeyV1(Blockstamp {
+            number: BlockNumber(block_number),
+            hash: BlockHash(block_hash),
+        }))
+    }
+}
+
+impl ToDumpString for BlockstampKeyV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for BlockstampKeyV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        Ok(format!("{}", self.0))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/hash.rs b/rust-libs/duniter-dbs/src/keys/hash.rs
new file mode 100644
index 000000000..a3a11f8b0
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/hash.rs
@@ -0,0 +1,52 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct HashKeyV1(pub Hash);
+
+impl KeyAsBytes for HashKeyV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(self.0.to_hex().as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for HashKeyV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let hash_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?;
+        Ok(HashKeyV1(
+            Hash::from_hex(&hash_str).map_err(|e| StringErr(format!("{}", e)))?,
+        ))
+    }
+}
+
+impl ToDumpString for HashKeyV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for HashKeyV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned()))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/pubkey.rs b/rust-libs/duniter-dbs/src/keys/pubkey.rs
new file mode 100644
index 000000000..9476cca71
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/pubkey.rs
@@ -0,0 +1,80 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct PubKeyKeyV1(pub PublicKey);
+
+impl PubKeyKeyV1 {
+    const ALL: &'static str = "ALL";
+    const ALL_WITH_LEADING_1: &'static str = "11111111111111111111111111111ALL";
+
+    pub fn all() -> Self {
+        Self(PublicKey::from_base58(Self::ALL).expect("invalid PubKeyKeyV1::all()"))
+    }
+}
+
+impl KeyAsBytes for PubKeyKeyV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        let b58_string = self.0.to_base58();
+        if b58_string == Self::ALL_WITH_LEADING_1 {
+            f(Self::ALL.as_bytes())
+        } else {
+            f(self.0.to_base58().as_bytes())
+        }
+    }
+}
+
+impl kv_typed::prelude::FromBytes for PubKeyKeyV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let pubkey_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?;
+        Ok(PubKeyKeyV1(PublicKey::from_base58(&pubkey_str).map_err(
+            |e| StringErr(format!("{}: {}", e, pubkey_str)),
+        )?))
+    }
+}
+
+impl ToDumpString for PubKeyKeyV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    #[test]
+    fn pubkey_all() {
+        let all = PubKeyKeyV1::all();
+        assert_eq!(
+            all.as_bytes(|bytes| bytes.to_vec()),
+            PubKeyKeyV1::ALL.as_bytes()
+        )
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for PubKeyKeyV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned()))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs b/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs
new file mode 100644
index 000000000..65934750d
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs
@@ -0,0 +1,69 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub struct PubKeyAndSigV1(PublicKey, Signature);
+
+impl PubKeyAndSigV1 {
+    pub fn all() -> Self {
+        Self(PublicKey::default(), Signature([0u8; 64]))
+    }
+}
+
+impl KeyAsBytes for PubKeyAndSigV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        if self == &Self::all() {
+            f(b"ALL")
+        } else {
+            f(format!("{}:{}", self.0.to_base58(), self.1.to_base64()).as_bytes())
+        }
+    }
+}
+
+impl kv_typed::prelude::FromBytes for PubKeyAndSigV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let raw_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?;
+        if raw_str == "ALL" {
+            Ok(PubKeyAndSigV1::all())
+        } else {
+            let array_str: ArrayVec<[&str; 2]> = raw_str.split(':').collect();
+            let pubkey =
+                PublicKey::from_base58(array_str[0]).map_err(|e| StringErr(format!("{}", e)))?;
+            let sig =
+                Signature::from_base64(array_str[1]).map_err(|e| StringErr(format!("{}", e)))?;
+            Ok(PubKeyAndSigV1(pubkey, sig))
+        }
+    }
+}
+
+impl ToDumpString for PubKeyAndSigV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for PubKeyAndSigV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned()))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/source_key.rs b/rust-libs/duniter-dbs/src/keys/source_key.rs
new file mode 100644
index 000000000..1c1e781c2
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/source_key.rs
@@ -0,0 +1,91 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
+pub struct SourceKeyV1 {
+    pub tx_hash: Hash,
+    pub pos: u32,
+    pub consumed: Option<bool>,
+}
+
+impl ToString for SourceKeyV1 {
+    fn to_string(&self) -> String {
+        format!(
+            "{}-{:010}{}",
+            self.tx_hash,
+            self.pos,
+            match self.consumed {
+                Some(true) => "-1",
+                Some(false) => "-0",
+                None => "",
+            }
+        )
+    }
+}
+
+impl KeyAsBytes for SourceKeyV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(self.to_string().as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for SourceKeyV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let strs: ArrayVec<[&str; 3]> = std::str::from_utf8(bytes)
+            .map_err(|e| StringErr(format!("{}", e)))?
+            .split('-')
+            .collect();
+        let tx_hash = Hash::from_hex(strs[0]).map_err(|e| StringErr(format!("{}", e)))?;
+        let pos = strs[1].parse().map_err(|e| StringErr(format!("{}", e)))?;
+        let consumed = if strs.len() <= 2 {
+            None
+        } else {
+            match strs[2] {
+                "1" => Some(true),
+                "0" => Some(false),
+                _ => {
+                    return Err(StringErr(
+                        "invalid format: field consumed must be encoded with '0' or '1'".to_owned(),
+                    ))
+                }
+            }
+        };
+        Ok(SourceKeyV1 {
+            tx_hash,
+            pos,
+            consumed,
+        })
+    }
+}
+
+impl ToDumpString for SourceKeyV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for SourceKeyV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned()))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/timestamp.rs b/rust-libs/duniter-dbs/src/keys/timestamp.rs
new file mode 100644
index 000000000..691e04784
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/timestamp.rs
@@ -0,0 +1,58 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct TimestampKeyV1(pub u64);
+
+impl KeyAsBytes for TimestampKeyV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(format!("{}", self.0).as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for TimestampKeyV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let key_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?;
+        Ok(TimestampKeyV1(
+            key_str
+                .parse()
+                .map_err(|e| StringErr(format!("{}: {}", e, key_str)))?,
+        ))
+    }
+}
+
+impl ToDumpString for TimestampKeyV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for TimestampKeyV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        NaiveDateTime::parse_from_str(source, "%Y-%m-%d %H:%M:%S")
+            .map(|dt| TimestampKeyV1(dt.timestamp() as u64))
+            .map_err(|e| StringErr(format!("{}: {}", e, source)))
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        Ok(NaiveDateTime::from_timestamp(self.0 as i64, 0)
+            .format("%Y-%m-%d %H:%M:%S")
+            .to_string())
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/uid.rs b/rust-libs/duniter-dbs/src/keys/uid.rs
new file mode 100644
index 000000000..7a481de4b
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/uid.rs
@@ -0,0 +1,65 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+const USERNAME_MAX_LEN: usize = 100;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct UidKeyV1(pub ArrayString<[u8; USERNAME_MAX_LEN]>);
+
+impl KeyAsBytes for UidKeyV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(self.0.as_str().as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for UidKeyV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let uid_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?;
+        Ok(Self(
+            ArrayString::<[u8; USERNAME_MAX_LEN]>::from_str(uid_str)
+                .map_err(|e| StringErr(format!("{}", e)))?,
+        ))
+    }
+}
+
+impl FromStr for UidKeyV1 {
+    type Err = arrayvec::CapacityError;
+
+    fn from_str(source: &str) -> std::result::Result<Self, Self::Err> {
+        Ok(UidKeyV1(ArrayString::<[u8; USERNAME_MAX_LEN]>::from_str(
+            source,
+        )?))
+    }
+}
+
+impl ToDumpString for UidKeyV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for UidKeyV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned()))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/keys/wallet_conditions.rs b/rust-libs/duniter-dbs/src/keys/wallet_conditions.rs
new file mode 100644
index 000000000..9432eab0c
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/wallet_conditions.rs
@@ -0,0 +1,65 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+const CONDITIONS_MAX_LEN: usize = 256;
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
+pub struct WalletConditionsV1(pub ArrayString<[u8; CONDITIONS_MAX_LEN]>);
+
+impl KeyAsBytes for WalletConditionsV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(self.0.as_str().as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for WalletConditionsV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let uid_str = std::str::from_utf8(bytes).map_err(|e| StringErr(format!("{}", e)))?;
+        Ok(Self(
+            ArrayString::<[u8; CONDITIONS_MAX_LEN]>::from_str(uid_str)
+                .map_err(|e| StringErr(format!("{}", e)))?,
+        ))
+    }
+}
+
+impl FromStr for WalletConditionsV1 {
+    type Err = arrayvec::CapacityError;
+
+    fn from_str(source: &str) -> std::result::Result<Self, Self::Err> {
+        Ok(WalletConditionsV1(
+            ArrayString::<[u8; CONDITIONS_MAX_LEN]>::from_str(source)?,
+        ))
+    }
+}
+
+impl ToDumpString for WalletConditionsV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for WalletConditionsV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        self.as_bytes(|bytes| Ok(unsafe { std::str::from_utf8_unchecked(bytes) }.to_owned()))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/lib.rs b/rust-libs/duniter-dbs/src/lib.rs
new file mode 100644
index 000000000..0a30b4ae2
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/lib.rs
@@ -0,0 +1,95 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces
+)]
+
+mod bc_v1;
+mod errors;
+mod keys;
+mod values;
+
+// Re-export dependencies
+pub use arrayvec;
+pub use dubp_common;
+#[cfg(feature = "explorer")]
+pub use kv_typed::regex;
+pub use serde;
+pub use serde_json;
+pub use smallvec;
+
+// Re-export crates
+pub use kv_typed;
+
+// Prelude
+pub mod prelude {
+    pub use crate::errors::ErrorDb;
+    #[cfg(feature = "explorer")]
+    pub use kv_typed::explorer::{
+        DbExplorable, EntryFound, ExplorerAction, ExplorerActionResponse, ValueCaptures,
+    };
+}
+
+// Export technical types
+pub use crate::errors::Result;
+
+// Export profession types
+pub use bc_v1::{BcV1Db, BcV1DbReadable, BcV1DbRo, BcV1DbWritable, MainBlockEvent, UidEvent};
+pub use keys::all::AllKeyV1;
+pub use keys::block_number::BlockNumberKeyV1;
+pub use keys::blockstamp::BlockstampKeyV1;
+pub use keys::hash::HashKeyV1;
+pub use keys::pubkey::PubKeyKeyV1;
+pub use keys::pubkey_and_sig::PubKeyAndSigV1;
+pub use keys::source_key::SourceKeyV1;
+pub use keys::timestamp::TimestampKeyV1;
+pub use keys::uid::UidKeyV1;
+pub use keys::wallet_conditions::WalletConditionsV1;
+pub use values::block_db::{BlockDbEnum, BlockDbV1, TransactionInBlockDbV1};
+pub use values::block_head_db::BlockHeadDbV1;
+pub use values::block_number_array_db::BlockNumberArrayV1;
+pub use values::cindex_db::CIndexDbV1;
+pub use values::iindex_db::IIndexDbV1;
+pub use values::kick_db::KickDbV1;
+pub use values::mindex_db::MIndexDbV1;
+pub use values::pubkey_db::{PublicKeyArrayDbV1, PublicKeySingletonDbV1};
+pub use values::sindex_db::{SIndexDBV1, SourceKeyArrayDbV1};
+pub use values::ud_entry_db::{ConsumedUdDbV1, UdAmountDbV1, UdEntryDbV1};
+pub use values::wallet_db::WalletDbV1;
+
+// Crate imports
+pub(crate) use arrayvec::{ArrayString, ArrayVec};
+#[cfg(feature = "explorer")]
+use chrono::NaiveDateTime;
+pub(crate) use dubp_common::crypto::bases::b58::ToBase58 as _;
+pub(crate) use dubp_common::crypto::bases::BaseConversionError;
+pub(crate) use dubp_common::crypto::hashs::Hash;
+pub(crate) use dubp_common::crypto::keys::ed25519::{PublicKey, Signature};
+pub(crate) use dubp_common::crypto::keys::{PublicKey as _, Signature as _};
+pub(crate) use dubp_common::prelude::*;
+pub(crate) use kv_typed::prelude::*;
+pub(crate) use serde::{Deserialize, Serialize};
+pub(crate) use smallvec::SmallVec;
+pub(crate) use std::{fmt::Debug, iter::Iterator, str::FromStr};
+
+pub trait ToDumpString {
+    fn to_dump_string(&self) -> String;
+}
diff --git a/rust-libs/duniter-dbs/src/values.rs b/rust-libs/duniter-dbs/src/values.rs
new file mode 100644
index 000000000..a542fe0bc
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values.rs
@@ -0,0 +1,26 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+pub mod block_db;
+pub mod block_head_db;
+pub mod block_number_array_db;
+pub mod cindex_db;
+pub mod iindex_db;
+pub mod kick_db;
+pub mod mindex_db;
+pub mod pubkey_db;
+pub mod sindex_db;
+pub mod ud_entry_db;
+pub mod wallet_db;
diff --git a/rust-libs/duniter-dbs/src/values/block_db.rs b/rust-libs/duniter-dbs/src/values/block_db.rs
new file mode 100644
index 000000000..eae51fae1
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/block_db.rs
@@ -0,0 +1,117 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Debug)]
+pub enum BlockDbEnum {
+    BlockDbV1(BlockDbV1),
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BlockDbV1 {
+    pub version: u64,
+    pub number: u64,
+    pub currency: String,
+    pub hash: String,
+    pub signature: String,
+    #[serde(rename = "inner_hash")]
+    pub inner_hash: String,
+    pub previous_hash: Option<String>,
+    pub issuer: String,
+    pub previous_issuer: Option<String>,
+    pub time: u64,
+    pub pow_min: u64,
+    #[serde(rename = "unitbase")]
+    pub unit_base: u64,
+    pub members_count: u64,
+    pub issuers_count: u64,
+    pub issuers_frame: u64,
+    pub issuers_frame_var: i64,
+    pub identities: Vec<String>,
+    pub joiners: Vec<String>,
+    pub actives: Vec<String>,
+    pub leavers: Vec<String>,
+    pub revoked: Vec<String>,
+    pub excluded: Vec<String>,
+    pub certifications: Vec<String>,
+    pub transactions: Vec<TransactionInBlockDbV1>,
+    pub median_time: u64,
+    pub nonce: u64,
+    pub fork: bool,
+    pub parameters: String,
+    pub monetary_mass: u64,
+    pub dividend: Option<u64>,
+    #[serde(rename = "UDTime")]
+    pub ud_time: Option<u64>,
+    #[serde(rename = "writtenOn")]
+    pub written_on: Option<u64>,
+    #[serde(rename = "written_on")]
+    pub written_on_str: String,
+    pub wrong: bool,
+}
+
+impl ValueAsBytes for BlockDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(json.as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for BlockDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for BlockDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for BlockDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct TransactionInBlockDbV1 {
+    version: u64,
+    currency: String,
+    #[serde(rename = "locktime")]
+    lock_time: u64,
+    hash: Option<String>,
+    blockstamp: String,
+    blockstamp_time: u64,
+    issuers: SmallVec<[String; 1]>,
+    inputs: SmallVec<[String; 4]>,
+    outputs: SmallVec<[String; 2]>,
+    unlocks: SmallVec<[String; 4]>,
+    signatures: SmallVec<[String; 1]>,
+    comment: String,
+}
diff --git a/rust-libs/duniter-dbs/src/values/block_head_db.rs b/rust-libs/duniter-dbs/src/values/block_head_db.rs
new file mode 100644
index 000000000..99d101406
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/block_head_db.rs
@@ -0,0 +1,86 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BlockHeadDbV1 {
+    pub version: u64,
+    pub currency: Option<String>,
+    #[serde(rename = "bsize")]
+    pub block_size: u64,
+    pub avg_block_size: u64,
+    pub ud_time: u64,
+    pub ud_reeval_time: u64,
+    pub mass_reeval: u64,
+    pub mass: u64,
+    pub hash: String,
+    pub previous_hash: Option<String>,
+    pub previous_issuer: Option<String>,
+    pub issuer: String,
+    pub time: u64,
+    pub median_time: u64,
+    pub number: u64,
+    pub pow_min: u64,
+    pub diff_number: u64,
+    pub issuers_count: u64,
+    pub issuers_frame: u64,
+    pub issuers_frame_var: i64,
+    pub issuer_diff: u64,
+    pub pow_zeros: u64,
+    pub pow_remainder: u64,
+    pub speed: f64,
+    pub unit_base: u64,
+    pub members_count: u64,
+    pub dividend: u64,
+    #[serde(rename = "new_dividend")]
+    pub new_dividend: Option<u64>,
+    pub issuer_is_member: bool,
+}
+
+impl ValueAsBytes for BlockHeadDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(json.as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for BlockHeadDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for BlockHeadDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for BlockHeadDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/values/block_number_array_db.rs b/rust-libs/duniter-dbs/src/values/block_number_array_db.rs
new file mode 100644
index 000000000..dd4a97937
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/block_number_array_db.rs
@@ -0,0 +1,54 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct BlockNumberArrayV1(pub SmallVec<[BlockNumber; 1]>);
+
+impl ValueAsBytes for BlockNumberArrayV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json_string =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(format!("[{}]", json_string).as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for BlockNumberArrayV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        //println!("json_str='{}'", &json_str);
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for BlockNumberArrayV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for BlockNumberArrayV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/values/cindex_db.rs b/rust-libs/duniter-dbs/src/values/cindex_db.rs
new file mode 100644
index 000000000..7afc8169f
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/cindex_db.rs
@@ -0,0 +1,99 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct CIndexDbV1 {
+    pub received: SmallVec<[String; 10]>,
+    pub issued: Vec<CIndexLineDbV1>,
+}
+
+impl ValueAsBytes for CIndexDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json_string =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(format!("[{}]", json_string).as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for CIndexDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        //println!("json_str='{}'", &json_str);
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for CIndexDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for CIndexDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CIndexLineDbV1 {
+    pub op: String,
+    #[serde(rename = "writtenOn")]
+    pub written_on: Option<u64>,
+    #[serde(rename = "written_on")]
+    pub written_on_str: String,
+    pub issuer: String,
+    pub receiver: String,
+    #[serde(rename = "created_on")]
+    pub created_on: u64,
+    pub sig: Option<String>,
+    #[serde(rename = "chainable_on")]
+    pub chainable_on: Option<u64>,
+    #[serde(rename = "replayable_on")]
+    pub replayable_on: Option<u64>,
+    #[serde(rename = "expires_on")]
+    pub expires_on: Option<u64>,
+    #[serde(rename = "expired_on")]
+    pub expired_on: u64,
+    pub unchainables: Option<u64>,
+    pub age: Option<u64>,
+    pub stock: Option<u64>,
+    pub from_member: Option<bool>,
+    pub to_member: Option<bool>,
+    pub to_newcomer: Option<bool>,
+    pub to_leaver: Option<bool>,
+    pub is_replay: Option<bool>,
+    pub is_replayable: Option<bool>,
+    #[serde(rename = "sigOK")]
+    pub sig_ok: Option<bool>,
+    #[serde(rename = "created_on_ref")]
+    pub created_on_ref: Option<CreatedOnRef>,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CreatedOnRef {
+    pub median_time: u64,
+}
diff --git a/rust-libs/duniter-dbs/src/values/iindex_db.rs b/rust-libs/duniter-dbs/src/values/iindex_db.rs
new file mode 100644
index 000000000..70a4ebc32
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/iindex_db.rs
@@ -0,0 +1,82 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct IIndexDbV1(pub SmallVec<[IIndexLineDbV1; 1]>);
+
+impl ValueAsBytes for IIndexDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json_string =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(format!("[{}]", json_string).as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for IIndexDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        //println!("json_str='{}'", &json_str);
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for IIndexDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for IIndexDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct IIndexLineDbV1 {
+    pub op: String,
+    #[serde(rename = "writtenOn")]
+    pub written_on: Option<u64>,
+    #[serde(rename = "written_on")]
+    pub written_on_str: String,
+    pub uid: Option<String>,
+    #[serde(rename = "pub")]
+    pub pubkey: String,
+    pub hash: Option<String>,
+    pub sig: Option<String>,
+    #[serde(rename = "created_on")]
+    pub created_on: Option<String>,
+    pub member: Option<bool>,
+    pub was_member: Option<bool>,
+    pub kick: Option<bool>,
+    #[serde(rename = "wotb_id")]
+    pub wotb_id: Option<usize>,
+    pub age: Option<u64>,
+    pub pub_unique: Option<bool>,
+    pub excluded_is_member: Option<bool>,
+    pub is_being_kicked: Option<bool>,
+    pub uid_unique: Option<bool>,
+    pub has_to_be_excluded: Option<bool>,
+}
diff --git a/rust-libs/duniter-dbs/src/values/kick_db.rs b/rust-libs/duniter-dbs/src/values/kick_db.rs
new file mode 100644
index 000000000..8d7c93f50
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/kick_db.rs
@@ -0,0 +1,56 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+pub struct KickDbV1 {
+    on: Option<u64>,          // The next time that the identity must be kicked
+    done: SmallVec<[u64; 4]>, // The reversion history
+}
+
+impl ValueAsBytes for KickDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(json.as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for KickDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for KickDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for KickDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/values/mindex_db.rs b/rust-libs/duniter-dbs/src/values/mindex_db.rs
new file mode 100644
index 000000000..f65f856c4
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/mindex_db.rs
@@ -0,0 +1,106 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct MIndexDbV1(pub SmallVec<[MIndexLineDbV1; 1]>);
+
+impl ValueAsBytes for MIndexDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json_string =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(format!("[{}]", json_string).as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for MIndexDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        //println!("json_str='{}'", &json_str);
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for MIndexDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for MIndexDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MIndexLineDbV1 {
+    pub op: String,
+    #[serde(rename = "writtenOn")]
+    pub written_on: Option<u64>,
+    #[serde(rename = "written_on")]
+    pub written_on_str: String,
+    #[serde(rename = "pub")]
+    pub pubkey: String,
+    pub created_on: Option<String>,
+    #[serde(rename = "type")]
+    pub r#type: Option<String>,
+    #[serde(rename = "expires_on")]
+    pub expires_on: Option<u64>,
+    #[serde(rename = "expired_on")]
+    pub expired_on: Option<u64>,
+    pub revocation: Option<String>,
+    #[serde(rename = "revokes_on")]
+    pub revokes_on: Option<u64>,
+    #[serde(rename = "chainable_on")]
+    pub chainable_on: Option<u64>,
+    #[serde(rename = "revoked_on")]
+    pub revoked_on: Option<String>,
+    pub leaving: Option<bool>,
+    pub age: Option<u64>,
+    pub is_being_revoked: Option<bool>,
+    pub unchainables: Option<u64>,
+    pub number_following: Option<bool>,
+    #[serde(rename = "distanceOK")]
+    pub distance_ok: Option<bool>,
+    pub on_revoked: Option<bool>,
+    pub joins_twice: Option<bool>,
+    pub enough_certs: Option<bool>,
+    pub leaver_is_member: Option<bool>,
+    pub active_is_member: Option<bool>,
+    pub revoked_is_member: Option<bool>,
+    pub already_revoked: Option<bool>,
+    #[serde(rename = "revocationSigOK")]
+    pub revocation_sig_ok: Option<bool>,
+    #[serde(rename = "created_on_ref")]
+    pub created_on_ref: Option<BlockstampTimed>,
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BlockstampTimed {
+    pub median_time: u64,
+    pub number: u32,
+    pub hash: String,
+}
diff --git a/rust-libs/duniter-dbs/src/values/pubkey_db.rs b/rust-libs/duniter-dbs/src/values/pubkey_db.rs
new file mode 100644
index 000000000..78e750ef6
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/pubkey_db.rs
@@ -0,0 +1,109 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct PublicKeySingletonDbV1(pub PublicKey);
+
+impl ValueAsBytes for PublicKeySingletonDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        f(format!("[\"{}\"]", self.0).as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for PublicKeySingletonDbV1 {
+    type Err = BaseConversionError;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let mut pubkey_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+
+        pubkey_str = &pubkey_str[2..pubkey_str.len() - 2];
+        Ok(Self(PublicKey::from_base58(pubkey_str)?))
+    }
+}
+
+impl ToDumpString for PublicKeySingletonDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for PublicKeySingletonDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Ok(Self(
+            PublicKey::from_base58(source).map_err(|e| StringErr(format!("{}", e)))?,
+        ))
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        Ok(serde_json::Value::String(self.0.to_base58()))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct PublicKeyArrayDbV1(pub SmallVec<[PublicKey; 8]>);
+
+impl ValueAsBytes for PublicKeyArrayDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let vec_pub_str = self
+            .0
+            .iter()
+            .map(|pubkey| pubkey.to_base58())
+            .collect::<SmallVec<[String; 8]>>();
+        let json = serde_json::to_string(&vec_pub_str)
+            .map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(json.as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for PublicKeyArrayDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        let vec_pub_str: SmallVec<[String; 8]> = serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?;
+        Ok(Self(
+            vec_pub_str
+                .into_iter()
+                .map(|pub_str| {
+                    PublicKey::from_base58(&pub_str).map_err(|e| StringErr(format!("{}", e)))
+                })
+                .collect::<std::result::Result<SmallVec<[PublicKey; 8]>, Self::Err>>()?,
+        ))
+    }
+}
+
+impl ToDumpString for PublicKeyArrayDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for PublicKeyArrayDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        Ok(serde_json::Value::Array(
+            self.0
+                .iter()
+                .map(|pubkey| serde_json::Value::String(pubkey.to_base58()))
+                .collect(),
+        ))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/values/sindex_db.rs b/rust-libs/duniter-dbs/src/values/sindex_db.rs
new file mode 100644
index 000000000..27f7ed0db
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/sindex_db.rs
@@ -0,0 +1,128 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SIndexDBV1 {
+    pub src_type: String,
+    pub tx: Option<String>,
+    pub identifier: String,
+    pub pos: u32,
+    #[serde(rename = "created_on")]
+    pub created_on: Option<String>,
+    #[serde(rename = "written_time")]
+    pub written_time: u64,
+    #[serde(rename = "locktime")]
+    pub lock_time: u64,
+    pub unlock: Option<String>,
+    pub amount: u32,
+    pub base: u32,
+    pub conditions: String,
+    pub consumed: bool,
+    pub tx_obj: TransactionInBlockDbV1,
+    pub age: u64,
+    #[serde(rename = "type")]
+    pub type_: Option<String>,
+    pub available: Option<bool>,
+    pub is_locked: Option<bool>,
+    pub is_time_locked: Option<bool>,
+}
+
+impl ValueAsBytes for SIndexDBV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(json.as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for SIndexDBV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for SIndexDBV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for SIndexDBV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct SourceKeyArrayDbV1(pub SmallVec<[SourceKeyV1; 8]>);
+
+impl ValueAsBytes for SourceKeyArrayDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let vec_pub_str = self
+            .0
+            .iter()
+            .map(|source_key| source_key.to_string())
+            .collect::<SmallVec<[String; 8]>>();
+        let json = serde_json::to_string(&vec_pub_str)
+            .map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(json.as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for SourceKeyArrayDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        let vec_source_key_str: SmallVec<[String; 8]> = serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?;
+        Ok(Self(
+            vec_source_key_str
+                .into_iter()
+                .map(|source_key_str| {
+                    SourceKeyV1::from_bytes(source_key_str.as_bytes())
+                        .map_err(|e| StringErr(format!("{}", e)))
+                })
+                .collect::<std::result::Result<SmallVec<[SourceKeyV1; 8]>, Self::Err>>()?,
+        ))
+    }
+}
+
+impl ToDumpString for SourceKeyArrayDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for SourceKeyArrayDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/values/ud_entry_db.rs b/rust-libs/duniter-dbs/src/values/ud_entry_db.rs
new file mode 100644
index 000000000..13bf96baa
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/ud_entry_db.rs
@@ -0,0 +1,80 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UdEntryDbV1 {
+    #[serde(rename = "pub")]
+    pub pubkey: String,
+    pub member: bool,
+    pub availables: Vec<u32>,
+    pub consumed: Vec<u32>,
+    #[serde(rename = "consumedUDs")]
+    pub consumed_uds: Vec<ConsumedUdDbV1>,
+    pub dividends: Vec<UdAmountDbV1>,
+}
+
+impl ValueAsBytes for UdEntryDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(json.as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for UdEntryDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for UdEntryDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for UdEntryDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ConsumedUdDbV1 {
+    pub dividend_number: u32,
+    pub tx_hash: String,
+    pub tx_created_on: String,
+    #[serde(rename = "txLocktime")]
+    pub tx_lock_time: u32,
+    pub dividend: UdAmountDbV1,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
+pub struct UdAmountDbV1 {
+    pub amount: u32,
+    pub base: u32,
+}
diff --git a/rust-libs/duniter-dbs/src/values/wallet_db.rs b/rust-libs/duniter-dbs/src/values/wallet_db.rs
new file mode 100644
index 000000000..12b7a3fa9
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/values/wallet_db.rs
@@ -0,0 +1,56 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct WalletDbV1 {
+    pub conditions: String,
+    pub balance: u64,
+}
+
+impl ValueAsBytes for WalletDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
+        let json =
+            serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
+        f(json.as_bytes())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for WalletDbV1 {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let json_str = std::str::from_utf8(bytes).expect("corrupted db : invalid utf8 bytes");
+        Ok(serde_json::from_str(&json_str)
+            .map_err(|e| StringErr(format!("{}: '{}'", e, json_str)))?)
+    }
+}
+
+impl ToDumpString for WalletDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for WalletDbV1 {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        Self::from_bytes(source.as_bytes())
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(format!("{}", e)))
+    }
+}
diff --git a/rust-libs/duniter-dbs/tests/test_explorer.rs b/rust-libs/duniter-dbs/tests/test_explorer.rs
new file mode 100644
index 000000000..96db3271e
--- /dev/null
+++ b/rust-libs/duniter-dbs/tests/test_explorer.rs
@@ -0,0 +1,254 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#[cfg(feature = "explorer")]
+mod explorer {
+    use dubp_common::crypto::keys::ed25519::PublicKey;
+    use dubp_common::crypto::keys::PublicKey as _;
+    //use dubp_common::prelude::*;
+    use duniter_dbs::kv_typed::prelude::*;
+    use duniter_dbs::kv_typed::regex;
+    use duniter_dbs::prelude::*;
+    use duniter_dbs::smallvec::smallvec;
+    use duniter_dbs::{BcV1Db, BcV1DbWritable, PublicKeySingletonDbV1, Result, UidKeyV1};
+    use std::{num::NonZeroUsize, str::FromStr};
+    use tempdir::TempDir;
+    use unwrap::unwrap;
+
+    const COLLECTION_NAME: &str = "uids";
+
+    fn stringify_json_value_test(v: serde_json::Value) -> serde_json::Value {
+        v
+    }
+
+    #[test]
+    fn explorer_test_leveldb() -> Result<()> {
+        let tmp_dir = unwrap!(TempDir::new("explorer_test_leveldb"));
+
+        let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(tmp_dir.path().to_owned()))?;
+
+        explorer_test(&db)
+    }
+
+    #[test]
+    fn explorer_test_sled() -> Result<()> {
+        let db = BcV1Db::<Sled>::open(SledConf::new().temporary(true))?;
+
+        explorer_test(&db)
+    }
+
+    fn explorer_test<B: Backend>(db: &BcV1Db<B>) -> Result<()> {
+        // Defines test data
+        let k1 = unwrap!(UidKeyV1::from_str("toto"));
+        let k2 = unwrap!(UidKeyV1::from_str("titi"));
+        let v1 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58(
+            "ByE9TU6qhktHYYVAqeTcWcaULBx151siQLyL3TrKvY85"
+        )));
+        let v2 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58(
+            "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp"
+        )));
+
+        // Insert test data
+        db.uids_write().upsert(k1, v1)?;
+        db.uids_write().upsert(k2, v2)?;
+
+        // Test action count
+        let res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Count,
+            stringify_json_value_test,
+        )?;
+        assert_eq!(Ok(ExplorerActionResponse::Count(2)), res);
+
+        // Test action get
+        let res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Get { key: "unexist" },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(Ok(ExplorerActionResponse::Get(None)), res);
+        let res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Get { key: "toto" },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(
+            Ok(ExplorerActionResponse::Get(Some(
+                serde_json::Value::String(
+                    "ByE9TU6qhktHYYVAqeTcWcaULBx151siQLyL3TrKvY85".to_owned()
+                )
+            ))),
+            res
+        );
+
+        // Test action put
+        let res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Put {
+                key: "titu",
+                value: "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr",
+            },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(Ok(ExplorerActionResponse::PutOk), res);
+        let res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Get { key: "titu" },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(
+            Ok(ExplorerActionResponse::Get(Some(
+                serde_json::Value::String(
+                    "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr".to_owned()
+                )
+            ))),
+            res
+        );
+        let res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Count,
+            stringify_json_value_test,
+        )?;
+        assert_eq!(Ok(ExplorerActionResponse::Count(3)), res);
+
+        // Test action find
+        let range_res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Find {
+                key_min: Some("ti00".to_owned()),
+                key_max: Some("tizz".to_owned()),
+                key_regex: None,
+                value_regex: None,
+                limit: Some(10),
+                reverse: false,
+                step: unsafe { NonZeroUsize::new_unchecked(1) },
+            },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(
+            Ok(ExplorerActionResponse::Find(vec![
+                EntryFound {
+                    key: "titi".to_owned(),
+                    value: serde_json::Value::String(
+                        "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp".to_owned()
+                    ),
+                    captures: None,
+                },
+                EntryFound {
+                    key: "titu".to_owned(),
+                    value: serde_json::Value::String(
+                        "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr".to_owned()
+                    ),
+                    captures: None,
+                },
+            ])),
+            range_res
+        );
+
+        // Test action find with limit
+        let range_res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Find {
+                key_min: Some("ti00".to_owned()),
+                key_max: Some("tizz".to_owned()),
+                key_regex: None,
+                value_regex: None,
+                limit: Some(1),
+                reverse: false,
+                step: unsafe { NonZeroUsize::new_unchecked(1) },
+            },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(
+            Ok(ExplorerActionResponse::Find(vec![EntryFound {
+                key: "titi".to_owned(),
+                value: serde_json::Value::String(
+                    "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp".to_owned()
+                ),
+                captures: None,
+            }])),
+            range_res
+        );
+
+        // Test action find with limit and reverse
+        let range_res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Find {
+                key_min: Some("ti00".to_owned()),
+                key_max: Some("tizz".to_owned()),
+                key_regex: None,
+                value_regex: None,
+                limit: Some(1),
+                reverse: true,
+                step: unsafe { NonZeroUsize::new_unchecked(1) },
+            },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(
+            Ok(ExplorerActionResponse::Find(vec![EntryFound {
+                key: "titu".to_owned(),
+                value: serde_json::Value::String(
+                    "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr".to_owned()
+                ),
+                captures: None,
+            }])),
+            range_res
+        );
+
+        // Test action find with regex capture
+        let range_res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Find {
+                key_min: Some("ti00".to_owned()),
+                key_max: Some("tizz".to_owned()),
+                key_regex: None,
+                value_regex: Some(regex::Regex::new("(E[Cv])[A-Z]").expect("wrong regex")),
+                limit: Some(10),
+                reverse: false,
+                step: unsafe { NonZeroUsize::new_unchecked(1) },
+            },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(
+            Ok(ExplorerActionResponse::Find(vec![EntryFound {
+                key: "titu".to_owned(),
+                value: serde_json::Value::String(
+                    "Bi6ECSc352gdfEvVzGiQuuDQyaTptHkcxooMGTJk14Tr".to_owned()
+                ),
+                captures: Some(ValueCaptures(smallvec![
+                    smallvec![Some("EC".to_owned())],
+                    smallvec![Some("Ev".to_owned())]
+                ])),
+            }])),
+            range_res
+        );
+
+        // Test action delete
+        let res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Delete { key: "toto" },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(Ok(ExplorerActionResponse::DeleteOk), res);
+        let res = db.explore(
+            COLLECTION_NAME,
+            ExplorerAction::Get { key: "toto" },
+            stringify_json_value_test,
+        )?;
+        assert_eq!(Ok(ExplorerActionResponse::Get(None)), res);
+
+        Ok(())
+    }
+}
diff --git a/rust-libs/duniter-dbs/tests/test_read_write.rs b/rust-libs/duniter-dbs/tests/test_read_write.rs
new file mode 100644
index 000000000..480c4b6b8
--- /dev/null
+++ b/rust-libs/duniter-dbs/tests/test_read_write.rs
@@ -0,0 +1,350 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use dubp_common::crypto::keys::ed25519::PublicKey;
+use dubp_common::crypto::keys::PublicKey as _;
+use dubp_common::prelude::*;
+use duniter_dbs::kv_typed::prelude::*;
+use duniter_dbs::{
+    BcV1Db, BcV1DbReadable, BcV1DbWritable, BlockDbV1, BlockNumberKeyV1, MainBlockEvent,
+    PublicKeySingletonDbV1, Result, UidKeyV1,
+};
+use kv_typed::channel::TryRecvError;
+use std::str::FromStr;
+use tempdir::TempDir;
+use unwrap::unwrap;
+
+#[test]
+fn write_read_delete_b0_leveldb() -> Result<()> {
+    let tmp_dir = unwrap!(TempDir::new("write_read_delete_b0_leveldb"));
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(tmp_dir.path().to_owned()))?;
+
+    write_read_delete_b0_test(&db)
+}
+
+#[test]
+fn write_read_delete_b0_sled() -> Result<()> {
+    let db = BcV1Db::<Sled>::open(SledConf::new().temporary(true))?;
+
+    write_read_delete_b0_test(&db)
+}
+
+#[test]
+fn iter_test_leveldb() -> Result<()> {
+    let tmp_dir = unwrap!(TempDir::new("batch_test_leveldb"));
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(tmp_dir.path().to_owned()))?;
+
+    write_some_entries_and_iter(&db)
+}
+
+#[test]
+fn iter_test_mem() -> Result<()> {
+    let db = BcV1Db::<Mem>::open(MemConf::default())?;
+
+    write_some_entries_and_iter(&db)
+}
+
+#[test]
+fn iter_test_sled() -> Result<()> {
+    let db = BcV1Db::<Sled>::open(SledConf::new().temporary(true))?;
+
+    write_some_entries_and_iter(&db)
+}
+
+#[test]
+fn batch_test_leveldb() -> Result<()> {
+    let tmp_dir = unwrap!(TempDir::new("batch_test_leveldb"));
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(tmp_dir.path().to_owned()))?;
+
+    batch_test(&db)
+}
+
+#[test]
+fn batch_test_mem() -> Result<()> {
+    let db = BcV1Db::<Mem>::open(MemConf::default())?;
+
+    batch_test(&db)
+}
+
+#[test]
+fn batch_test_sled() -> Result<()> {
+    let db = BcV1Db::<Sled>::open(SledConf::new().temporary(true))?;
+
+    batch_test(&db)
+}
+
+fn write_read_delete_b0_test<B: Backend>(db: &BcV1Db<B>) -> Result<()> {
+    let main_blocks_reader = db.main_blocks();
+
+    let (subscriber, events_recv) = kv_typed::channel::unbounded();
+
+    main_blocks_reader.subscribe(subscriber)?;
+
+    // Empty db
+    assert_eq!(
+        main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(0)))?,
+        None
+    );
+    assert_eq!(
+        main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(1)))?,
+        None
+    );
+    assert_eq!(main_blocks_reader.iter(..).keys().next_res()?, None);
+    assert_eq!(main_blocks_reader.iter(..).values().next_res()?, None);
+    if let Err(TryRecvError::Empty) = events_recv.try_recv() {
+    } else {
+        panic!("should not receive event");
+    }
+
+    // Insert b0
+    let b0 = BlockDbV1::default();
+    let main_blocks_writer = db.main_blocks_write();
+    main_blocks_writer.upsert(BlockNumberKeyV1(BlockNumber(0)), b0.clone())?;
+    assert_eq!(
+        main_blocks_reader
+            .get(&BlockNumberKeyV1(BlockNumber(0)))?
+            .as_ref(),
+        Some(&b0)
+    );
+    assert_eq!(
+        main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(1)))?,
+        None
+    );
+    let mut keys_iter = main_blocks_reader.iter(..).keys();
+    assert_eq!(
+        keys_iter.next_res()?,
+        Some(BlockNumberKeyV1(BlockNumber(0)))
+    );
+    assert_eq!(keys_iter.next_res()?, None);
+    let mut values_iter = main_blocks_reader.iter(..).values();
+    assert_eq!(values_iter.next_res()?, Some(b0.clone()));
+    assert_eq!(values_iter.next_res()?, None);
+    if let Ok(events) = events_recv.try_recv() {
+        assert_eq!(events.len(), 1);
+        let event = &events[0];
+        assert_eq!(
+            event,
+            &MainBlockEvent::Upsert {
+                key: BlockNumberKeyV1(BlockNumber(0)),
+                value: b0,
+            },
+        );
+    } else {
+        panic!("should receive event");
+    }
+
+    // Delete b0
+    main_blocks_writer.remove(BlockNumberKeyV1(BlockNumber(0)))?;
+    assert_eq!(
+        main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(0)))?,
+        None
+    );
+    assert_eq!(
+        main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(1)))?,
+        None
+    );
+    assert_eq!(main_blocks_reader.iter(..).keys().next_res()?, None);
+    assert_eq!(main_blocks_reader.iter(..).values().next_res()?, None);
+    if let Ok(events) = events_recv.try_recv() {
+        assert_eq!(events.len(), 1);
+        let event = &events[0];
+        assert_eq!(
+            event,
+            &MainBlockEvent::Remove {
+                key: BlockNumberKeyV1(BlockNumber(0)),
+            },
+        );
+    } else {
+        panic!("should receive event");
+    }
+
+    Ok(())
+}
+
+fn write_some_entries_and_iter<B: Backend>(db: &BcV1Db<B>) -> Result<()> {
+    let k1 = unwrap!(UidKeyV1::from_str("titi"));
+    let p1 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58(
+        "42jMJtb8chXrpHMAMcreVdyPJK7LtWjEeRqkPw4eSEVp"
+    )));
+    let k2 = unwrap!(UidKeyV1::from_str("titu"));
+    let p2 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58(
+        "D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C"
+    )));
+    let k3 = unwrap!(UidKeyV1::from_str("toto"));
+    let p3 = PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58(
+        "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp"
+    )));
+    let uids_writer = db.uids_write();
+    uids_writer.upsert(k1, p1)?;
+    uids_writer.upsert(k2, p2)?;
+    uids_writer.upsert(k3, p3)?;
+
+    let uids_reader = db.uids();
+    {
+        let mut values_iter_step_2 = uids_reader.iter(..).values().step_by(2);
+        assert_eq!(Some(p1), values_iter_step_2.next_res()?);
+        assert_eq!(Some(p3), values_iter_step_2.next_res()?);
+        assert_eq!(None, values_iter_step_2.next_res()?);
+
+        let mut entries_iter_step_2 = uids_reader.iter(..).step_by(2);
+        assert_eq!(Some((k1, p1)), entries_iter_step_2.next_res()?);
+        assert_eq!(Some((k3, p3)), entries_iter_step_2.next_res()?);
+        assert_eq!(None, entries_iter_step_2.next_res()?);
+
+        let mut entries_iter = uids_reader.iter(k2..);
+        assert_eq!(Some((k2, p2)), entries_iter.next_res()?);
+        assert_eq!(Some((k3, p3)), entries_iter.next_res()?);
+        assert_eq!(None, entries_iter.next_res()?);
+
+        let mut entries_iter = uids_reader.iter(..=k2);
+        assert_eq!(Some((k1, p1)), entries_iter.next_res()?);
+        assert_eq!(Some((k2, p2)), entries_iter.next_res()?);
+        assert_eq!(None, entries_iter.next_res()?);
+
+        let mut entries_iter_rev = uids_reader.iter(k2..).reverse();
+        assert_eq!(Some((k3, p3)), entries_iter_rev.next_res()?);
+        assert_eq!(Some((k2, p2)), entries_iter_rev.next_res()?);
+        assert_eq!(None, entries_iter_rev.next_res()?);
+
+        let mut entries_iter_rev = uids_reader.iter(..=k2).reverse();
+        assert_eq!(Some((k2, p2)), entries_iter_rev.next_res()?);
+        assert_eq!(Some((k1, p1)), entries_iter_rev.next_res()?);
+        assert_eq!(None, entries_iter.next_res()?);
+
+        let mut keys_iter_rev = uids_reader.iter(..=k2).keys().reverse();
+        assert_eq!(Some(k2), keys_iter_rev.next_res()?);
+        assert_eq!(Some(k1), keys_iter_rev.next_res()?);
+        assert_eq!(None, keys_iter_rev.next_res()?);
+    }
+
+    uids_writer.remove(k3)?;
+
+    let mut keys_iter_rev = uids_reader.iter(..).keys();
+    assert_eq!(Some(k1), keys_iter_rev.next_res()?);
+    assert_eq!(Some(k2), keys_iter_rev.next_res()?);
+    assert_eq!(None, keys_iter_rev.next_res()?);
+
+    Ok(())
+}
+
+fn batch_test<B: Backend>(db: &BcV1Db<B>) -> Result<()> {
+    let main_blocks_reader = db.main_blocks();
+
+    let mut batch = db.new_batch();
+
+    let (subscriber, events_recv) = kv_typed::channel::unbounded();
+
+    main_blocks_reader.subscribe(subscriber)?;
+
+    // Empty db
+    assert_eq!(
+        main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(0)))?,
+        None
+    );
+    assert_eq!(
+        main_blocks_reader.get(&BlockNumberKeyV1(BlockNumber(1)))?,
+        None
+    );
+    assert_eq!(main_blocks_reader.iter(..).keys().next_res()?, None);
+    assert_eq!(main_blocks_reader.iter(..).values().next_res()?, None);
+    if let Err(TryRecvError::Empty) = events_recv.try_recv() {
+    } else {
+        panic!("should not receive event");
+    }
+
+    // Insert b0 in batch
+    let b0 = BlockDbV1::default();
+    batch
+        .main_blocks()
+        .upsert(BlockNumberKeyV1(BlockNumber(0)), b0.clone());
+
+    // bo should written in batch
+    assert_eq!(
+        batch.main_blocks().get(&BlockNumberKeyV1(BlockNumber(0))),
+        Some(&b0)
+    );
+
+    // bo should not written in db
+    assert_eq!(
+        db.main_blocks().get(&BlockNumberKeyV1(BlockNumber(0)))?,
+        None
+    );
+
+    if let Err(TryRecvError::Empty) = events_recv.try_recv() {
+    } else {
+        panic!("should not receive event");
+    }
+
+    // Insert b1 in batch
+    let b1 = BlockDbV1 {
+        number: 1,
+        ..Default::default()
+    };
+    batch
+        .main_blocks()
+        .upsert(BlockNumberKeyV1(BlockNumber(1)), b1.clone());
+
+    // Write batch in db
+    db.write_batch(batch)?;
+
+    // bo should written in db
+    assert_eq!(
+        db.main_blocks()
+            .get(&BlockNumberKeyV1(BlockNumber(0)))?
+            .as_ref(),
+        Some(&b0)
+    );
+    let mut keys_iter = db.main_blocks().iter(..).keys();
+    assert_eq!(
+        keys_iter.next_res()?,
+        Some(BlockNumberKeyV1(BlockNumber(0)))
+    );
+    assert_eq!(
+        keys_iter.next_res()?,
+        Some(BlockNumberKeyV1(BlockNumber(1)))
+    );
+    assert_eq!(keys_iter.next_res()?, None);
+    let mut values_iter = db.main_blocks().iter(..).values();
+    assert_eq!(values_iter.next_res()?.as_ref(), Some(&b0));
+    assert_eq!(values_iter.next_res()?.as_ref(), Some(&b1));
+    assert_eq!(values_iter.next_res()?, None);
+    if let Ok(events) = events_recv.try_recv() {
+        assert_eq!(events.len(), 2);
+        assert!(assert_eq_pairs(
+            [&events[0], &events[1]],
+            [
+                &MainBlockEvent::Upsert {
+                    key: BlockNumberKeyV1(BlockNumber(0)),
+                    value: b0,
+                },
+                &MainBlockEvent::Upsert {
+                    key: BlockNumberKeyV1(BlockNumber(1)),
+                    value: b1,
+                }
+            ]
+        ));
+    } else {
+        panic!("should receive event");
+    }
+
+    Ok(())
+}
+
+fn assert_eq_pairs<T: PartialEq>(a: [T; 2], b: [T; 2]) -> bool {
+    (a[0] == b[0] && a[1] == b[1]) || (a[1] == b[0] && a[0] == b[1])
+}
diff --git a/rust-libs/duniter-dbs/tests/test_tmp_real.rs b/rust-libs/duniter-dbs/tests/test_tmp_real.rs
new file mode 100644
index 000000000..00b2c81cd
--- /dev/null
+++ b/rust-libs/duniter-dbs/tests/test_tmp_real.rs
@@ -0,0 +1,736 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use dubp_common::crypto::bases::b58::ToBase58 as _;
+use dubp_common::crypto::hashs::Hash;
+use dubp_common::crypto::keys::PublicKey;
+use dubp_common::prelude::*;
+use duniter_dbs::kv_typed::prelude::*;
+use duniter_dbs::*;
+use duniter_dbs::{
+    BcV1Db, BcV1DbReadable, BcV1DbWritable, BlockDbV1, BlockNumberKeyV1, PublicKeySingletonDbV1,
+    Result, UidKeyV1,
+};
+use once_cell::sync::Lazy;
+use std::{path::PathBuf, str::FromStr, sync::Mutex};
+use unwrap::unwrap;
+
+// Empty mutex used to ensure that only one test runs at a time
+static MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
+
+//const DB_PATH: &str = "/home/elois/.config/duniter/duniter_default/data";
+const DB_PATH: &str = "/home/elois/Documents/ml/leveldb-archives/g1-317499/leveldb";
+
+#[test]
+fn db_v1_main_blocks__() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    //let block52 = db.get::<MainBlocksColV1>(&MainBlockKeyV1(52))?;
+    //println!("{:#?}", block52);
+
+    let current_block_number_opt = db
+        .main_blocks()
+        .iter(..)
+        .keys()
+        .reverse()
+        .next()
+        .transpose()?;
+    if let Some(current_block_number) = current_block_number_opt {
+        println!("current_block_number={:#?}", current_block_number);
+        let current_block = db.main_blocks().get(&current_block_number)?;
+        println!("current_block={:#?}", current_block);
+    }
+
+    /*// Collect all main blocks
+    let entries = db
+        .main_blocks()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, BlockDbV1)>>>()?;
+    println!("entries_len={}", entries.len());*/
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_idty() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let all = unwrap!(db.mb_idty().get(&PubKeyKeyV1::all())?);
+    assert!(all.0.len() > 2);
+
+    // Collect all main blocks idty
+    let entries = db
+        .mb_idty()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?;
+    println!("identities_count={}", entries.len() - 1);
+    for (k, v) in &entries {
+        if v.0.len() == 2 {
+            println!("{:?}", k.0.as_ref());
+        }
+    }
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_certs() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all main blocks idty
+    let entries = db
+        .mb_certs()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?;
+    println!("certifications_count={}", entries.len() - 1);
+    for (k, v) in &entries[..10] {
+        if v.0.len() > 1 {
+            println!("{}={:?}", k.0, v.0);
+        }
+    }
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_joiners() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let all = unwrap!(db.mb_joiners().get(&PubKeyKeyV1::all())?);
+    assert!(all.0.len() > 100);
+
+    // Collect all main blocks joiners
+    let entries = db
+        .mb_joiners()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?;
+    println!("joiners_count={}", entries.len() - 1);
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_actives() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let all = unwrap!(db.mb_actives().get(&PubKeyKeyV1::all())?);
+    assert!(all.0.len() > 100);
+
+    // Collect all main blocks actives
+    let entries = db
+        .mb_actives()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?;
+    println!("actives_count={}", entries.len() - 1);
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_leavers() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let all = unwrap!(db.mb_leavers().get(&PubKeyKeyV1::all())?);
+    assert!(all.0.len() >= 3);
+
+    // Collect all main blocks with leavers
+    let entries = db
+        .mb_leavers()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?;
+    println!("leavers_count={}", entries.len() - 1);
+    for (k, v) in entries {
+        println!("{}={:?}", k.0, v.0);
+    }
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_excluded() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let all = unwrap!(db.mb_excluded().get(&PubKeyKeyV1::all())?);
+    assert!(all.0.len() >= 50);
+
+    // Collect all main blocks with excluded
+    let entries = db
+        .mb_excluded()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, BlockNumberArrayV1)>>>()?;
+    println!("excluded_count={}", entries.len() - 1);
+    /*for (k, v) in entries {
+        println!("{}={:?}", k.0, v.0);
+    }*/
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_revoked() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let all = unwrap!(db.mb_revoked().get(&PubKeyAndSigV1::all())?);
+    assert!(all.0.len() >= 20);
+
+    // Collect all main blocks with revoked
+    let entries = db
+        .mb_revoked()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyAndSigV1, BlockNumberArrayV1)>>>()?;
+    println!("revoked_count={}", entries.len() - 1);
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_dividend() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let all = unwrap!(db.mb_dividends().get(&AllKeyV1)?);
+    assert!(all.0.len() >= 900);
+    println!("blocks with dividend={}", all.0.len());
+    println!("last block with dividend={:?}", all.0.last());
+
+    // Collect all main blocks with dividends
+    let entries = db
+        .mb_dividends()
+        .iter(..)
+        .collect::<KvResult<Vec<(AllKeyV1, BlockNumberArrayV1)>>>()?;
+    println!("dividends_keys={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_main_blocks_transactions() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let all = unwrap!(db.mb_transactions().get(&AllKeyV1)?);
+    assert!(all.0.len() >= 900);
+    println!("blocks with tx={}", all.0.len());
+    println!("last block with tx={:?}", all.0.last());
+
+    // Collect all main blocks with transactions
+    let entries = db
+        .mb_transactions()
+        .iter(..)
+        .collect::<KvResult<Vec<(AllKeyV1, BlockNumberArrayV1)>>>()?;
+    println!("transactions_keys={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_fork_blocks() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    /*let fork_blocks_keys = db
+        .keys_iter::<ForkBlocksColV1, _>(..)
+        .take(1)
+        .collect::<KvResult<Vec<BlockstampKeyV1>>>()?;
+    let one_fork_block = unwrap!(db.get::<ForkBlocksColV1>(&fork_blocks_keys[0])?);
+
+    println!("{:#?}", one_fork_block);*/
+
+    // Collect all fork blocks
+    let entries = db
+        .fork_blocks()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockstampKeyV1, BlockDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_bindex() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all bindex entries
+    let entries = db
+        .bindex()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, BlockHeadDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    //println!("last_bindex={:?}", entries.last());
+    //for (_k, v) in entries {}
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_iindex__() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let iindex_keys = db
+        .iindex()
+        .iter(..)
+        .keys()
+        .take(1)
+        .collect::<KvResult<Vec<PubKeyKeyV1>>>()?;
+    let one_iindex_db = unwrap!(db.iindex().get(&iindex_keys[0])?);
+    assert_eq!(one_iindex_db.0[0].pubkey, iindex_keys[0].0.to_base58());
+
+    //println!("{:#?}", one_iindex_db);
+
+    if let Some(ref hash) = one_iindex_db.0[0].hash {
+        let pubkey = unwrap!(db
+            .iindex_hash()
+            .get(&HashKeyV1(unwrap!(Hash::from_hex(hash))))?);
+        assert_eq!(pubkey.0, iindex_keys[0].0);
+    }
+
+    // Count iindex entries
+    let count = db.iindex().count()?;
+    println!("iindex size={}", count);
+
+    // Count members
+    let count_members = db
+        .iindex()
+        .iter(..)
+        .filter_map(KvResult::ok)
+        .filter(|(_k, v)| v.0[0].member.is_some() && unwrap!(v.0[0].member))
+        .count();
+    println!("count_members={}", count_members);
+
+    // Collect all iindex entries
+    let entries = db
+        .iindex()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, IIndexDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_iindex_hash() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let iindex_entries = db
+        .iindex_hash()
+        .iter(..)
+        .take(3)
+        .collect::<KvResult<Vec<(HashKeyV1, PublicKeySingletonDbV1)>>>()?;
+
+    println!(
+        "(hash, pub)=({:#?},{:#?})",
+        iindex_entries[0].0, iindex_entries[0].1
+    );
+
+    // Collect all iindex/hash entries
+    let entries = db
+        .iindex_hash()
+        .iter(..)
+        .collect::<KvResult<Vec<(HashKeyV1, PublicKeySingletonDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_iindex_kick() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let entries = db
+        .iindex_kick()
+        .iter(..)
+        .take(3)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, KickDbV1)>>>()?;
+
+    println!("(pub, kick)=({:#?},{:#?})", entries[0].0, entries[0].1);
+
+    // Collect all iindex/kick entries
+    let entries = db
+        .iindex_kick()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, KickDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_iindex_written_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all iindex/written_on entries
+    let entries = db
+        .iindex_written_on()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    println!("entries={:?}", entries);
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_uid_col() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let uid_keys = db
+        .uids()
+        .iter(..)
+        .keys()
+        .take(1)
+        .collect::<KvResult<Vec<UidKeyV1>>>()?;
+    let one_pubkey_db = db.uids().get(&uid_keys[0])?;
+
+    println!(
+        "(uid, pubkey) = ({}, {:#?})",
+        uid_keys[0].0.as_str(),
+        one_pubkey_db
+    );
+
+    let start_key = unwrap!(UidKeyV1::from_str("1b"));
+    let end_key = unwrap!(UidKeyV1::from_str("404_not_found"));
+    let uid_index = db
+        .uids()
+        .iter(start_key..end_key)
+        .collect::<KvResult<Vec<(UidKeyV1, PublicKeySingletonDbV1)>>>()?;
+    assert_eq!(
+        uid_index,
+        vec![(
+            unwrap!(UidKeyV1::from_str("1claude1")),
+            PublicKeySingletonDbV1(unwrap!(PublicKey::from_base58(
+                "8B5XCAHknsckCkMWeGF9FoGibSNZXF9HtAvzxzg3bSyp"
+            )))
+        )],
+    );
+
+    // Collect all iindex/uid entries
+    let entries = db
+        .uids()
+        .iter(..)
+        .collect::<KvResult<Vec<(UidKeyV1, PublicKeySingletonDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_mindex__() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    let mindex_keys = db
+        .mindex()
+        .iter(..)
+        .keys()
+        .take(1)
+        .collect::<KvResult<Vec<PubKeyKeyV1>>>()?;
+    let one_mindex_db = unwrap!(db.mindex().get(&mindex_keys[0])?);
+    assert_eq!(one_mindex_db.0[0].pubkey, mindex_keys[0].0.to_base58());
+
+    //println!("{:#?}", one_mindex_db);
+
+    // Count mindex entries
+    let count = db.mindex().count()?;
+    println!("mindex size={}", count);
+
+    // Collect all mindex entries
+    let entries = db
+        .mindex()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, MIndexDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_mindex_expires_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all mindex/expires_on entries
+    let entries = db
+        .mindex_expires_on()
+        .iter(..)
+        .collect::<KvResult<Vec<(TimestampKeyV1, PublicKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    /*for (k, v) in entries {
+        if k.0 == BlockNumber(u32::MAX) {
+            println!("{:?}", v.0)
+        }
+    }*/
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_mindex_revokes_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all mindex/revokes_on entries
+    let entries = db
+        .mindex_revokes_on()
+        .iter(..)
+        .collect::<KvResult<Vec<(TimestampKeyV1, PublicKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_mindex_written_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all mindex/written_on entries
+    let entries = db
+        .mindex_written_on()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    for (k, v) in entries {
+        if k.0 == BlockNumber(u32::MAX) {
+            println!("{:?}", v.0)
+        }
+    }
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_cindex__() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all bindex entries
+    let entries = db
+        .cindex()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, CIndexDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    //println!("last_bindex={:?}", entries.last());
+    for (_k, v) in entries {
+        for cindex_line in v.issued {
+            if cindex_line.created_on_ref.is_some() {
+                println!("cindex_line={:?}", cindex_line)
+            }
+        }
+    }
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_cindex_expires_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all cindex/expires_on entries
+    let entries = db
+        .cindex_expires_on()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_cindex_written_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all cindex/written_on entries
+    let entries = db
+        .cindex_written_on()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_wallet() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all wallet entries
+    let entries = db
+        .wallet()
+        .iter(..)
+        .collect::<KvResult<Vec<(WalletConditionsV1, WalletDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    let mut max_cond_len = 0;
+    for (k, _v) in entries {
+        if k.0.len() > max_cond_len {
+            max_cond_len = k.0.len();
+            println!("k={}", k.0.as_str());
+        }
+    }
+    println!("max_cond_len={}", max_cond_len);
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_dividend() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all level_dividend entries
+    let entries = db
+        .uds()
+        .iter(..)
+        .collect::<KvResult<Vec<(PubKeyKeyV1, UdEntryDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    println!("entries[0]=({:?}, {:?})", entries[0].0, entries[0].1);
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_dividend_written_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all level_dividend/level_dividend_trim_index entries
+    let entries = db
+        .uds_trim()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, PublicKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_sindex() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all level_sindex entries
+    let entries = db
+        .sindex()
+        .iter(..)
+        .collect::<KvResult<Vec<(SourceKeyV1, SIndexDBV1)>>>()?;
+    println!("entries_len={}", entries.len());
+
+    println!("entries[0]=({:?}, {:?})", entries[0].0, entries[0].1);
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_sindex_written_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all mindex/written_on entries
+    let entries = db
+        .sindex_written_on()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, SourceKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    for (k, v) in entries {
+        if k.0 == BlockNumber(u32::MAX) {
+            println!("{:?}", v.0)
+        }
+    }
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_sindex_consumed_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+    // Collect all mindex/written_on entries
+    let entries = db
+        .sindex_consumed_on()
+        .iter(..)
+        .collect::<KvResult<Vec<(BlockNumberKeyV1, SourceKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    for (k, v) in entries {
+        println!("{:?} => {:?}", k.0, v.0)
+    }
+
+    Ok(())
+}
+
+#[test]
+fn db_v1_sindex_conditions_on() -> Result<()> {
+    let _lock = MUTEX.lock().expect("MUTEX poisoned");
+
+    let db = BcV1Db::<LevelDb>::open(LevelDbConf::path(PathBuf::from(DB_PATH)))?;
+
+    // Collect all mindex/written_on entries
+    let entries = db
+        .sindex_conditions()
+        .iter(..)
+        .collect::<KvResult<Vec<(WalletConditionsV1, SourceKeyArrayDbV1)>>>()?;
+    println!("entries_len={}", entries.len());
+    /*for (k, v) in entries {
+        println!("{:?} => {:?}", k.0, v.0)
+    }*/
+
+    Ok(())
+}
diff --git a/rust-libs/tools/kv_typed/Cargo.toml b/rust-libs/tools/kv_typed/Cargo.toml
new file mode 100644
index 000000000..e87cbd58b
--- /dev/null
+++ b/rust-libs/tools/kv_typed/Cargo.toml
@@ -0,0 +1,57 @@
+[package]
+name = "kv_typed"
+version = "0.1.0"
+authors = ["elois <c@elo.tf>"]
+description = "Strongly typed key-value storage"
+repository = "https://git.duniter.org/nodes/typescript/duniter"
+keywords = ["database", "key", "sled"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies]
+async-channel = { version = "1.4.2", optional = true }
+crossbeam-channel = { version = "0.4.4", optional = true }
+kv_typed_code_gen = { path = "../kv_typed_code_gen" }
+leveldb_minimal = { version = "0.1.0", optional = true }
+mockall = { version = "0.8.0", optional = true }
+parking_lot = { version = "0.11.0", optional = true }
+rayon = { version = "1.3.1", optional = true }
+regex = { version = "1.3.9", optional = true }
+serde_json = { version = "1.0.53", optional = true }
+sled = { version = "0.34.4", optional = true }
+smallvec = { version = "1.4.0", features = ["serde"] }
+thiserror = "1.0.20"
+
+[[bench]]
+name = "compare_backends"
+harness = false
+required-features = ["leveldb_backend", "memory_backend", "sled_backend"]
+
+[dev-dependencies]
+async-std = { version = "1.6.3", features = ["attributes"] }
+maybe-async = "0.2.0"
+smallvec = { version = "1.4.0", features = ["serde"] }
+unwrap = "1.2.1"
+
+# Benches dependencies
+criterion = { version = "0.3.1" }
+
+[features]
+#default = ["memory_backend"]
+
+async = ["async-channel"]
+explorer = ["rayon", "regex", "serde_json"]
+leveldb_backend = ["leveldb_minimal"]
+memory_backend = ["parking_lot"]
+sled_backend = ["sled"]
+subscription = ["parking_lot"]
+sync = ["crossbeam-channel"]
+
+mock = ["mockall"]
+
+default = ["memory_backend", "subscription", "sync"]
+#default = ["memory_backend", "subscription", "sync", "explorer"]
+#default = ["memory_backend", "subscription", "sync", "mock"]
diff --git a/rust-libs/tools/kv_typed/benches/compare_backends.rs b/rust-libs/tools/kv_typed/benches/compare_backends.rs
new file mode 100644
index 000000000..b89d4be1a
--- /dev/null
+++ b/rust-libs/tools/kv_typed/benches/compare_backends.rs
@@ -0,0 +1,101 @@
+use criterion::{criterion_group, criterion_main, AxisScale, Criterion, PlotConfiguration};
+use kv_typed::prelude::*;
+use std::{fmt::Debug, path::PathBuf};
+
+db_schema!(Test, [["c1", col_1, u32, String],]);
+const LEVELDB_DIR_PATH: &str = "/dev/shm/kv_typed/benches/compare_backends/leveldb";
+static SMALL_VAL: &str = "abcdefghijklmnopqrst";
+static LARGE_VAL: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+fn read_n_entries<B: Backend>(db: &TestDb<B>, n: u32, val: String) {
+    let mut iter = db.col_1().iter(..).values();
+    for _ in 0..n {
+        assert_eq!(iter.next_res().expect(""), Some(val.clone()));
+        //assert_eq!(db.col_1().get(&i).expect(""), Some(val.clone()));
+    }
+    assert_eq!(iter.next_res().expect(""), None);
+}
+fn write_n_entries<B: Backend>(db: &TestDb<B>, n: u32, val: String) {
+    for i in 0..n {
+        db.col_1_write().remove(i).expect("fail to write");
+        db.col_1_write()
+            .upsert(i, val.clone())
+            .expect("fail to write");
+    }
+}
+
+pub fn benchmark(c: &mut Criterion) {
+    // Read chart config
+    let read_chart_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
+
+    // Create DBs
+    std::fs::create_dir_all(LEVELDB_DIR_PATH).expect("fail to create leveldb dir");
+    let leveldb_db = TestDb::<LevelDb>::open(LevelDbConf {
+        db_path: PathBuf::from(LEVELDB_DIR_PATH),
+        ..Default::default()
+    })
+    .expect("fail to open db");
+    let mem_db = TestDb::<Mem>::open(MemConf::default()).expect("fail to open db");
+    let sled_db =
+        TestDb::<Sled>::open(SledConf::default().temporary(true)).expect("fail to open db");
+
+    // Test write small values
+    let mut group = c.benchmark_group("write small values");
+    group.bench_function("leveldb", |b| {
+        b.iter(|| write_n_entries::<LevelDb>(&leveldb_db, 100, String::from(SMALL_VAL)))
+    });
+    group.bench_function("mem", |b| {
+        b.iter(|| write_n_entries::<Mem>(&mem_db, 100, String::from(SMALL_VAL)))
+    });
+    group.bench_function("sled", |b| {
+        b.iter(|| write_n_entries::<Sled>(&sled_db, 100, String::from(SMALL_VAL)))
+    });
+    group.finish();
+
+    // Test read small values
+    let mut group = c.benchmark_group("read small values");
+    group.plot_config(read_chart_config.clone());
+    group.bench_function("leveldb", |b| {
+        b.iter(|| read_n_entries(&leveldb_db, 100, String::from(SMALL_VAL)))
+    });
+    group.bench_function("mem", |b| {
+        b.iter(|| read_n_entries(&mem_db, 100, String::from(SMALL_VAL)))
+    });
+    group.bench_function("sled", |b| {
+        b.iter(|| read_n_entries(&sled_db, 100, String::from(SMALL_VAL)))
+    });
+    group.finish();
+
+    // Test write large values
+    let mut group = c.benchmark_group("write large values");
+    group.bench_function("leveldb", |b| {
+        b.iter(|| write_n_entries::<LevelDb>(&leveldb_db, 100, String::from(LARGE_VAL)))
+    });
+    group.bench_function("mem", |b| {
+        b.iter(|| write_n_entries::<Mem>(&mem_db, 100, String::from(LARGE_VAL)))
+    });
+    group.bench_function("sled", |b| {
+        b.iter(|| write_n_entries::<Sled>(&sled_db, 100, String::from(LARGE_VAL)))
+    });
+    group.finish();
+
+    // Test read large values
+    let mut group = c.benchmark_group("read large values");
+    group.plot_config(read_chart_config);
+    group.bench_function("leveldb", |b| {
+        b.iter(|| read_n_entries(&leveldb_db, 100, String::from(LARGE_VAL)))
+    });
+    group.bench_function("mem", |b| {
+        b.iter(|| read_n_entries(&mem_db, 100, String::from(LARGE_VAL)))
+    });
+    group.bench_function("sled", |b| {
+        b.iter(|| read_n_entries(&sled_db, 100, String::from(LARGE_VAL)))
+    });
+    group.finish();
+
+    // Close DBs
+    std::fs::remove_dir_all(LEVELDB_DIR_PATH).expect("fail to create leveldb dir");
+}
+
+criterion_group!(benches, benchmark);
+criterion_main!(benches);
diff --git a/rust-libs/tools/kv_typed/src/as_bytes.rs b/rust-libs/tools/kv_typed/src/as_bytes.rs
new file mode 100644
index 000000000..33cb6012d
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/as_bytes.rs
@@ -0,0 +1,36 @@
+use crate::*;
+
+pub trait KeyAsBytes {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, f: F) -> T;
+}
+pub trait ValueAsBytes {
+    fn as_bytes<T, F: FnMut(&[u8]) -> Result<T, KvError>>(&self, f: F) -> Result<T, KvError>;
+}
+
+impl KeyAsBytes for String {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(self.as_bytes())
+    }
+}
+impl ValueAsBytes for String {
+    fn as_bytes<T, F: FnMut(&[u8]) -> Result<T, KvError>>(&self, mut f: F) -> Result<T, KvError> {
+        f(self.as_bytes())
+    }
+}
+
+macro_rules! impl_as_bytes_for_numbers {
+    ($($T:ty),*) => {$(
+        impl KeyAsBytes for $T {
+            fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+                f(&self.to_be_bytes()[..])
+            }
+        }
+        impl ValueAsBytes for $T {
+            fn as_bytes<T, F: FnMut(&[u8]) -> Result<T, KvError>>(&self, mut f: F) -> Result<T, KvError> {
+                f(&self.to_be_bytes()[..])
+            }
+        }
+    )*};
+}
+
+impl_as_bytes_for_numbers!(usize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64);
diff --git a/rust-libs/tools/kv_typed/src/backend.rs b/rust-libs/tools/kv_typed/src/backend.rs
new file mode 100644
index 000000000..6992e41f8
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/backend.rs
@@ -0,0 +1,74 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed Backend Trait
+
+#[cfg(feature = "leveldb_backend")]
+pub mod leveldb;
+#[cfg(feature = "memory_backend")]
+pub mod memory;
+#[cfg(feature = "mock")]
+pub mod mock;
+#[cfg(feature = "sled_backend")]
+pub mod sled;
+
+use crate::*;
+
+pub trait TransactionalBackend<DbReader: From<Vec<Self::TxCol>>, DbWriter: From<Vec<Self::TxCol>>>:
+    Backend
+{
+    type Err: Error + Send + Sync + 'static;
+    type TxCol: BackendCol;
+
+    fn read<A: Debug, D, F: Fn(&DbReader) -> TransactionResult<D, A, Self::Err>>(
+        &self,
+        f: F,
+    ) -> TransactionResult<D, A, Self::Err>;
+    fn write<A: Debug, F: Fn(&DbWriter) -> TransactionResult<(), A, Self::Err>>(
+        &self,
+        f: F,
+    ) -> TransactionResult<(), A, Self::Err>;
+}
+
+pub trait Backend: 'static + Clone + Sized {
+    const NAME: &'static str;
+    type Col: BackendCol;
+    type Conf: Default;
+
+    fn open(conf: &Self::Conf) -> KvResult<Self>;
+    fn open_col(&mut self, conf: &Self::Conf, col_name: &str) -> KvResult<Self::Col>;
+}
+
+pub trait BackendCol: 'static + Clone {
+    type Batch: BackendBatch;
+    type KeyBytes: AsRef<[u8]>;
+    type ValueBytes: AsRef<[u8]>;
+    type Iter: Iterator<Item = Result<(Self::KeyBytes, Self::ValueBytes), DynErr>>
+        + ReversableIterator;
+
+    fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>>;
+    fn count(&self) -> KvResult<usize>;
+    fn iter<K: Key, V: Value>(&self, range: RangeBytes) -> Self::Iter;
+    fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()>;
+    fn delete<K: Key>(&self, k: &K) -> KvResult<()>;
+    fn new_batch() -> Self::Batch;
+    fn write_batch(&self, inner_batch: Self::Batch) -> KvResult<()>;
+}
+
+#[cfg_attr(feature = "mock", mockall::automock)]
+pub trait BackendBatch: Default {
+    fn upsert(&mut self, k: &[u8], v: &[u8]);
+    fn remove(&mut self, k: &[u8]);
+}
diff --git a/rust-libs/tools/kv_typed/src/backend/leveldb.rs b/rust-libs/tools/kv_typed/src/backend/leveldb.rs
new file mode 100644
index 000000000..83892d4cf
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/backend/leveldb.rs
@@ -0,0 +1,208 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! LevelDb backend for KV Typed
+
+use crate::*;
+pub use leveldb_minimal::database::batch::{Batch as _, Writebatch as WriteBatch};
+use leveldb_minimal::database::cache::Cache as LevelDbCache;
+pub use leveldb_minimal::database::error::Error as LevelDbError;
+use leveldb_minimal::database::iterator::Iterator as LevelDbIterator;
+pub use leveldb_minimal::database::Database as LevelDbDb;
+use leveldb_minimal::iterator::{Iterable, LevelDBIterator as _};
+use leveldb_minimal::kv::KV as _;
+pub use leveldb_minimal::options::{Options as LevelDbOptions, ReadOptions, WriteOptions};
+use leveldb_minimal::Compression;
+use std::path::PathBuf;
+
+#[derive(Clone, Copy, Debug)]
+pub struct LevelDb;
+
+impl Backend for LevelDb {
+    const NAME: &'static str = "leveldb";
+    type Col = LevelDbCol;
+    type Conf = LevelDbConf;
+
+    fn open(_conf: &Self::Conf) -> KvResult<Self> {
+        Ok(LevelDb)
+    }
+    fn open_col(&mut self, conf: &Self::Conf, col_name: &str) -> KvResult<Self::Col> {
+        Ok(LevelDbCol(Arc::new(LevelDbDb::open(
+            &conf.db_path.join(col_name),
+            conf.clone().into(),
+        )?)))
+    }
+}
+
+#[derive(Clone)]
+pub struct LevelDbCol(Arc<LevelDbDb>);
+
+impl Debug for LevelDbCol {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("LevelDbCol")
+            .field("0", &"Arc<LevelDbDb>")
+            .finish()
+    }
+}
+
+impl BackendBatch for WriteBatch {
+    fn upsert(&mut self, k: &[u8], v: &[u8]) {
+        self.put(k, v)
+    }
+
+    fn remove(&mut self, k: &[u8]) {
+        self.delete(k)
+    }
+}
+
+impl BackendCol for LevelDbCol {
+    type Batch = WriteBatch;
+    type KeyBytes = Vec<u8>;
+    type ValueBytes = Vec<u8>;
+    type Iter = LevelDbIter;
+
+    #[inline(always)]
+    fn new_batch() -> Self::Batch {
+        WriteBatch::default()
+    }
+    #[inline(always)]
+    fn count(&self) -> KvResult<usize> {
+        Ok(self
+            .0
+            .iter(ReadOptions {
+                verify_checksums: false,
+                fill_cache: false,
+                snapshot: None,
+            })
+            .count())
+    }
+    #[inline(always)]
+    fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>> {
+        k.as_bytes(|k_bytes| {
+            self.0
+                .get(ReadOptions::new(), k_bytes)?
+                .map(|bytes| {
+                    V::from_bytes(&bytes).map_err(|e| KvError::DeserError(format!("{}", e)))
+                })
+                .transpose()
+        })
+    }
+    #[inline(always)]
+    fn delete<K: Key>(&self, k: &K) -> KvResult<()> {
+        k.as_bytes(|k_bytes| self.0.delete(WriteOptions::new(), k_bytes))?;
+        Ok(())
+    }
+    #[inline(always)]
+    fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()> {
+        value.as_bytes(|value_bytes| {
+            k.as_bytes(|k_bytes| self.0.put(WriteOptions::new(), k_bytes, value_bytes))?;
+            Ok(())
+        })
+    }
+    #[inline(always)]
+    fn write_batch(&self, inner_batch: Self::Batch) -> KvResult<()> {
+        self.0.write(WriteOptions::new(), &inner_batch)?;
+        Ok(())
+    }
+    #[inline(always)]
+    fn iter<K: Key, V: Value>(&self, _range: RangeBytes) -> Self::Iter {
+        LevelDbIter(self.0.iter(ReadOptions::new()))
+    }
+}
+
+pub struct LevelDbIter(LevelDbIterator);
+impl Debug for LevelDbIter {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("LevelDbIter")
+            .field("0", &"LevelDbIterator<'db>")
+            .finish()
+    }
+}
+
+impl Iterator for LevelDbIter {
+    type Item = Result<(Vec<u8>, Vec<u8>), DynErr>;
+
+    #[inline(always)]
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0.next().map(Ok)
+    }
+}
+impl ReversableIterator for LevelDbIter {
+    #[inline(always)]
+    fn reverse(self) -> Self {
+        Self(self.0.reverse())
+    }
+}
+
+#[derive(Clone, Debug)]
+/// leveldb configuration
+pub struct LevelDbConf {
+    pub create_if_missing: bool,
+    pub db_path: PathBuf,
+    pub error_if_exists: bool,
+    pub paranoid_checks: bool,
+    pub write_buffer_size: Option<usize>,
+    pub max_open_files: Option<i32>,
+    pub block_size: Option<usize>,
+    pub block_restart_interval: Option<i32>,
+    pub compression: bool,
+    pub cache: Option<usize>,
+}
+
+impl LevelDbConf {
+    pub fn path(db_path: PathBuf) -> Self {
+        Self {
+            db_path,
+            ..Default::default()
+        }
+    }
+}
+
+impl Default for LevelDbConf {
+    fn default() -> Self {
+        LevelDbConf {
+            create_if_missing: true,
+            db_path: PathBuf::default(),
+            error_if_exists: false,
+            paranoid_checks: false,
+            write_buffer_size: None,
+            max_open_files: None,
+            block_size: None,
+            block_restart_interval: None,
+            compression: true,
+            cache: None,
+        }
+    }
+}
+
+impl Into<LevelDbOptions> for LevelDbConf {
+    fn into(self) -> LevelDbOptions {
+        LevelDbOptions {
+            create_if_missing: self.create_if_missing,
+            error_if_exists: self.error_if_exists,
+            paranoid_checks: self.paranoid_checks,
+            write_buffer_size: self.write_buffer_size,
+            max_open_files: self.max_open_files,
+            block_size: self.block_size,
+            block_restart_interval: self.block_restart_interval,
+            compression: if self.compression {
+                Compression::Snappy
+            } else {
+                Compression::No
+            },
+            cache: self.cache.map(LevelDbCache::new),
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/backend/memory.rs b/rust-libs/tools/kv_typed/src/backend/memory.rs
new file mode 100644
index 000000000..68aa9275b
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/backend/memory.rs
@@ -0,0 +1,200 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Memory backend for KV Typed,
+
+use crate::*;
+use parking_lot::{RwLock, RwLockReadGuard};
+use std::collections::BTreeMap;
+
+#[derive(Clone, Copy, Debug)]
+pub struct Mem;
+
+#[derive(Clone, Copy, Debug, Default)]
+pub struct MemConf {
+    // Allows to prevent `MemConf` from being instantiated without using the `Default` trait.
+    // Thus the eventual addition of a field in the future will not be a breaking change.
+    phantom: PhantomData<()>,
+}
+
+type KeyBytes = IVec;
+type ValueBytes = IVec;
+type Map = BTreeMap<KeyBytes, ValueBytes>;
+type ArcSharedMap = Arc<RwLock<Map>>;
+
+impl Backend for Mem {
+    const NAME: &'static str = "mem";
+    type Col = MemCol;
+    type Conf = MemConf;
+
+    fn open(_conf: &Self::Conf) -> KvResult<Self> {
+        Ok(Mem)
+    }
+    fn open_col(&mut self, _conf: &Self::Conf, _col_name: &str) -> KvResult<Self::Col> {
+        Ok(MemCol(Arc::new(RwLock::new(BTreeMap::new()))))
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct MemBatch {
+    upsert_ops: Vec<(KeyBytes, ValueBytes)>,
+    remove_ops: Vec<KeyBytes>,
+}
+
+impl BackendBatch for MemBatch {
+    fn upsert(&mut self, k: &[u8], v: &[u8]) {
+        self.upsert_ops.push((k.into(), v.into()));
+    }
+
+    fn remove(&mut self, k: &[u8]) {
+        self.remove_ops.push(k.into());
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct MemCol(ArcSharedMap);
+
+impl BackendCol for MemCol {
+    type Batch = MemBatch;
+    type KeyBytes = KeyBytes;
+    type ValueBytes = ValueBytes;
+    type Iter = MemIter;
+
+    #[inline(always)]
+    fn new_batch() -> Self::Batch {
+        MemBatch::default()
+    }
+    #[inline(always)]
+    fn count(&self) -> KvResult<usize> {
+        let reader = self.0.read();
+        Ok(reader.len())
+    }
+    #[inline(always)]
+    fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>> {
+        k.as_bytes(|k_bytes| {
+            let reader = self.0.read();
+            reader
+                .get(k_bytes)
+                .map(|bytes| {
+                    V::from_bytes(&bytes).map_err(|e| KvError::DeserError(format!("{}", e)))
+                })
+                .transpose()
+        })
+    }
+    #[inline(always)]
+    fn delete<K: Key>(&self, k: &K) -> KvResult<()> {
+        k.as_bytes(|k_bytes| {
+            let mut writer = self.0.write();
+            writer.remove(k_bytes)
+        });
+        Ok(())
+    }
+    #[inline(always)]
+    fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()> {
+        value.as_bytes(|value_bytes| {
+            k.as_bytes(|k_bytes| {
+                let mut writer = self.0.write();
+                writer.insert(k_bytes.into(), value_bytes.into());
+            });
+            Ok(())
+        })
+    }
+    #[inline(always)]
+    fn write_batch(&self, inner_batch: Self::Batch) -> KvResult<()> {
+        let mut writer = self.0.write();
+        for (k, v) in inner_batch.upsert_ops {
+            writer.insert(k, v);
+        }
+        for k in inner_batch.remove_ops {
+            writer.remove(&k);
+        }
+        Ok(())
+    }
+    #[inline(always)]
+    fn iter<K: Key, V: Value>(&self, range: RangeBytes) -> Self::Iter {
+        let map_shared_arc = self.0.clone();
+        let map_shared_ref = map_shared_arc.as_ref();
+
+        let reader = map_shared_ref.read();
+        self.iter_inner(range, reader)
+    }
+}
+
+impl MemCol {
+    fn iter_inner(
+        &self,
+        range: RangeBytes,
+        reader: RwLockReadGuard<BTreeMap<KeyBytes, ValueBytes>>,
+    ) -> MemIter {
+        let reader = unsafe {
+            std::mem::transmute::<
+                RwLockReadGuard<BTreeMap<KeyBytes, ValueBytes>>,
+                RwLockReadGuard<'static, BTreeMap<KeyBytes, ValueBytes>>,
+            >(reader)
+        };
+        let reader_ref = unsafe {
+            std::mem::transmute::<
+                &RwLockReadGuard<BTreeMap<KeyBytes, ValueBytes>>,
+                &'static RwLockReadGuard<'static, BTreeMap<KeyBytes, ValueBytes>>,
+            >(&reader)
+        };
+        let iter = reader_ref.range(range);
+
+        MemIter {
+            col: self.clone(),
+            reader: Some(reader),
+            iter,
+            reversed: false,
+        }
+    }
+}
+
+pub struct MemIter {
+    #[allow(dead_code)]
+    // Needed for safety
+    col: MemCol,
+    #[allow(dead_code)]
+    // Needed for safety
+    reader: Option<RwLockReadGuard<'static, BTreeMap<KeyBytes, ValueBytes>>>,
+    iter: std::collections::btree_map::Range<'static, KeyBytes, ValueBytes>,
+    reversed: bool,
+}
+
+impl Debug for MemIter {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("MemIter").field("0", &"???").finish()
+    }
+}
+impl Iterator for MemIter {
+    type Item = Result<(KeyBytes, ValueBytes), DynErr>;
+
+    #[inline(always)]
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.reversed {
+            self.iter.next_back()
+        } else {
+            self.iter.next()
+        }
+        .map(|(k, v)| Ok((k.to_owned(), v.to_owned())))
+    }
+}
+
+impl ReversableIterator for MemIter {
+    #[inline(always)]
+    fn reverse(mut self) -> Self {
+        self.reversed = !self.reversed;
+        self
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/backend/mock.rs b/rust-libs/tools/kv_typed/src/backend/mock.rs
new file mode 100644
index 000000000..5e8b99879
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/backend/mock.rs
@@ -0,0 +1,70 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed mock backend
+
+use super::MockBackendBatch;
+use crate::*;
+
+#[cfg(feature = "mock")]
+mockall::mock! {
+    pub BackendIter {}
+    trait Iterator {
+        type Item = Result<(Vec<u8>, Vec<u8>), DynErr>;
+
+        fn next(&mut self) -> Option<<Self as Iterator>::Item>;
+    }
+    trait ReversableIterator {
+        fn reverse(self) -> Self;
+    }
+}
+
+#[cfg(feature = "mock")]
+mockall::mock! {
+    pub BackendCol {}
+    trait Clone {
+        fn clone(&self) -> Self;
+    }
+    trait BackendCol {
+        type Batch = MockBackendBatch;
+        type KeyBytes = Vec<u8>;
+        type ValueBytes = Vec<u8>;
+        type Iter = MockBackendIter;
+
+        fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>>;
+        fn count(&self) -> KvResult<usize>;
+        fn iter<K: Key, V: Value>(&self, range: RangeBytes) -> MockBackendIter;
+        fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()>;
+        fn delete<K: Key>(&self, k: &K) -> KvResult<()>;
+        fn new_batch() -> MockBackendBatch;
+        fn write_batch(&self, inner_batch: MockBackendBatch) -> KvResult<()>;
+    }
+}
+
+#[cfg(feature = "mock")]
+mockall::mock! {
+    pub Backend {}
+    trait Clone {
+        fn clone(&self) -> Self;
+    }
+    trait Backend: 'static + Clone + Sized {
+        const NAME: &'static str = "mock";
+        type Col = MockBackendCol;
+        type Conf = ();
+
+        fn open(conf: &()) -> KvResult<Self>;
+        fn open_col(&mut self, conf: &(), col_name: &str) -> KvResult<MockBackendCol>;
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/backend/sled.rs b/rust-libs/tools/kv_typed/src/backend/sled.rs
new file mode 100644
index 000000000..348ca2bdd
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/backend/sled.rs
@@ -0,0 +1,145 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Sled backend for KV Typed,
+
+mod transactional;
+
+pub use sled::Config;
+
+use crate::*;
+
+#[derive(Clone, Debug)]
+pub struct Sled {
+    db: sled::Db,
+    trees: Vec<sled::Tree>,
+}
+
+impl Backend for Sled {
+    const NAME: &'static str = "sled";
+    type Col = SledCol;
+    type Conf = Config;
+
+    fn open(conf: &Self::Conf) -> KvResult<Self> {
+        Ok(Sled {
+            db: conf.open()?,
+            trees: Vec::new(),
+        })
+    }
+    fn open_col(&mut self, _conf: &Self::Conf, col_name: &str) -> KvResult<Self::Col> {
+        let tree = self.db.open_tree(col_name)?;
+        self.trees.push(tree.clone());
+        Ok(SledCol(tree))
+    }
+}
+
+impl BackendBatch for sled::Batch {
+    fn upsert(&mut self, k: &[u8], v: &[u8]) {
+        self.insert(k, v)
+    }
+
+    fn remove(&mut self, k: &[u8]) {
+        self.remove(k)
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct SledCol(sled::Tree);
+
+impl BackendCol for SledCol {
+    type Batch = sled::Batch;
+    type KeyBytes = IVec;
+    type ValueBytes = IVec;
+    type Iter = SledIter;
+
+    #[inline(always)]
+    fn new_batch() -> Self::Batch {
+        sled::Batch::default()
+    }
+    #[inline(always)]
+    fn count(&self) -> KvResult<usize> {
+        Ok(self.0.len())
+    }
+    #[inline(always)]
+    fn get<K: Key, V: Value>(&self, k: &K) -> KvResult<Option<V>> {
+        k.as_bytes(|k_bytes| {
+            self.0
+                .get(k_bytes)?
+                .map(|bytes| {
+                    V::from_bytes(&bytes).map_err(|e| KvError::DeserError(format!("{}", e)))
+                })
+                .transpose()
+        })
+    }
+    #[inline(always)]
+    fn delete<K: Key>(&self, k: &K) -> KvResult<()> {
+        k.as_bytes(|k_bytes| self.0.remove(k_bytes))?;
+        Ok(())
+    }
+    #[inline(always)]
+    fn put<K: Key, V: Value>(&self, k: &K, value: &V) -> KvResult<()> {
+        value.as_bytes(|value_bytes| {
+            k.as_bytes(|k_bytes| self.0.insert(k_bytes, value_bytes))?;
+            Ok(())
+        })
+    }
+    #[inline(always)]
+    fn write_batch(&self, inner_batch: Self::Batch) -> KvResult<()> {
+        self.0.apply_batch(inner_batch)?;
+        Ok(())
+    }
+    #[inline(always)]
+    fn iter<K: Key, V: Value>(&self, range: RangeBytes) -> Self::Iter {
+        SledIter {
+            iter: self.0.range(range),
+            reversed: false,
+        }
+    }
+}
+
+pub struct SledIter {
+    iter: sled::Iter,
+    reversed: bool,
+}
+
+impl Debug for SledIter {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("SledIter")
+            .field("0", &"sled::Iter")
+            .finish()
+    }
+}
+impl Iterator for SledIter {
+    type Item = Result<(IVec, IVec), DynErr>;
+
+    #[inline(always)]
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.reversed {
+            self.iter.next_back()
+        } else {
+            self.iter.next()
+        }
+        .map(|res| res.map_err(Box::new).map_err(Into::into))
+    }
+}
+impl ReversableIterator for SledIter {
+    #[inline(always)]
+    fn reverse(self) -> Self {
+        SledIter {
+            iter: self.iter,
+            reversed: !self.reversed,
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/backend/sled/transactional.rs b/rust-libs/tools/kv_typed/src/backend/sled/transactional.rs
new file mode 100644
index 000000000..5ec9b43bb
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/backend/sled/transactional.rs
@@ -0,0 +1,83 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Sled backend for KV Typed,
+
+/*use crate::*;
+use sled::transaction::{ConflictableTransactionError, Transactional, TransactionalTree};
+
+enum AbortType<A> {
+    User(A),
+    Kv(KvError),
+}
+
+#[allow(missing_copy_implementations)]
+pub struct SledTxCol(&'static TransactionalTree);
+
+impl Debug for SledTxCol {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("SledTxCol")
+            .field("0", &"TransactionalTree")
+            .finish()
+    }
+}
+
+impl<DbReader: From<Vec<SledTxCol>>, DbWriter> TransactionalBackend<DbReader, DbWriter> for Sled {
+    type Err = sled::Error;
+    type TxCol = SledTxCol;
+
+    fn read<A: Debug, D, F: Fn(&DbReader) -> TransactionResult<D, A, Self::Err>>(
+        &self,
+        f: F,
+    ) -> TransactionResult<D, A, Self::Err> {
+        match self
+            .trees
+            .transaction::<_, D>(|tx_trees: &Vec<TransactionalTree>| {
+                let reader = DbReader::from(
+                    tx_trees
+                        .iter()
+                        .map(|tx_tree| SledTxCol(unsafe { to_static_ref(tx_tree) }))
+                        .collect(),
+                );
+                f(&reader).map_err(|e| match e {
+                    TransactionError::Abort(a) => {
+                        ConflictableTransactionError::Abort(AbortType::User(a))
+                    }
+                    TransactionError::BackendErr(e) => ConflictableTransactionError::Storage(e),
+                    TransactionError::KvError(e) => {
+                        ConflictableTransactionError::Abort(AbortType::Kv(e))
+                    }
+                })
+            }) {
+            Ok(t) => Ok(t),
+            Err(e) => match e {
+                sled::transaction::TransactionError::Abort(a) => match a {
+                    AbortType::User(a) => Err(TransactionError::Abort(a)),
+                    AbortType::Kv(e) => Err(TransactionError::KvError(e)),
+                },
+                sled::transaction::TransactionError::Storage(e) => {
+                    Err(TransactionError::BackendErr(e))
+                }
+            },
+        }
+    }
+
+    fn write<A: Debug, F: Fn(&DbWriter) -> TransactionResult<(), A, Self::Err>>(
+        &self,
+        _f: F,
+    ) -> TransactionResult<(), A, Self::Err> {
+        todo!()
+    }
+}*/
diff --git a/rust-libs/tools/kv_typed/src/batch.rs b/rust-libs/tools/kv_typed/src/batch.rs
new file mode 100644
index 000000000..c7d3d8c64
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/batch.rs
@@ -0,0 +1,74 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use std::collections::{BTreeSet, HashMap, HashSet};
+
+#[derive(Debug)]
+pub struct Batch<BC: BackendCol, C: DbCollectionRw> {
+    backend_batch: BC::Batch,
+    upsert_keys_bytes: BTreeSet<IVec>,
+    upsert_ops: HashMap<C::K, C::V>,
+    delete_ops: HashSet<C::K>,
+}
+
+impl<BC: BackendCol, C: DbCollectionRw> Default for Batch<BC, C> {
+    fn default() -> Self {
+        Batch {
+            backend_batch: BC::Batch::default(),
+            upsert_keys_bytes: BTreeSet::default(),
+            upsert_ops: HashMap::default(),
+            delete_ops: HashSet::default(),
+        }
+    }
+}
+
+impl<BC: BackendCol, C: DbCollectionRw> Batch<BC, C> {
+    pub fn get(&self, k: &C::K) -> Option<&C::V> {
+        if self.delete_ops.contains(k) {
+            None
+        } else {
+            self.upsert_ops.get(k)
+        }
+    }
+    pub fn upsert(&mut self, k: C::K, v: C::V) {
+        let _ = k.as_bytes(|k_bytes| {
+            self.upsert_keys_bytes.insert(k_bytes.into());
+            v.as_bytes(|v_bytes| {
+                self.backend_batch.upsert(k_bytes, v_bytes);
+                Ok(())
+            })
+        });
+        self.upsert_ops.insert(k, v);
+    }
+    pub fn remove(&mut self, k: C::K) {
+        let _ = k.as_bytes(|k_bytes| self.backend_batch.remove(k_bytes));
+        self.delete_ops.insert(k);
+    }
+    #[cfg(not(feature = "subscription"))]
+    pub fn into_backend_batch(self) -> BC::Batch {
+        self.backend_batch
+    }
+    #[cfg(feature = "subscription")]
+    pub fn into_backend_batch_and_events(self) -> (BC::Batch, SmallVec<[C::Event; 4]>) {
+        let mut events: SmallVec<[C::Event; 4]> = self
+            .upsert_ops
+            .into_iter()
+            .map(|(k, v)| C::Event::upsert(k, v))
+            .collect();
+        events.extend(self.delete_ops.into_iter().map(C::Event::remove));
+        (self.backend_batch, events)
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/collection_ro.rs b/rust-libs/tools/kv_typed/src/collection_ro.rs
new file mode 100644
index 000000000..9161e8df1
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/collection_ro.rs
@@ -0,0 +1,85 @@
+use crate::*;
+
+pub trait DbCollectionRo: Sized {
+    type BackendCol: BackendCol;
+    type K: Key;
+    type V: Value;
+    type Event: EventTrait<K = Self::K, V = Self::V>;
+
+    fn count(&self) -> KvResult<usize>;
+    fn get(&self, k: &Self::K) -> KvResult<Option<Self::V>>;
+    fn iter<R: 'static + RangeBounds<Self::K>>(
+        &self,
+        range: R,
+    ) -> KvIter<Self::BackendCol, Self::K, Self::V>;
+    #[cfg(feature = "subscription")]
+    fn subscribe(&self, subscriber_sender: Subscriber<Self::Event>) -> KvResult<()>;
+}
+
+#[cfg(feature = "mock")]
+mockall::mock! {
+    pub ColRo<E: EventTrait> {}
+    trait DbCollectionRo {
+        type BackendCol = MockBackendCol;
+        type K = E::K;
+        type V = E::V;
+        type Event = E;
+
+        fn count(&self) -> KvResult<usize>;
+        fn get(&self, k: &E::K) -> KvResult<Option<E::V>>;
+        fn iter<R: 'static + RangeBounds<E::K>>(&self, range: R)
+        -> KvIter<MockBackendCol, E::K, E::V>;
+        #[cfg(feature = "subscription")]
+        fn subscribe(&self, subscriber_sender: Subscriber<E>) -> KvResult<()>;
+    }
+}
+
+#[derive(Debug)]
+pub struct ColRo<BC: BackendCol, E: EventTrait> {
+    pub(crate) inner: BC,
+    #[cfg(not(feature = "subscription"))]
+    pub(crate) phantom: PhantomData<E>,
+    #[cfg(feature = "subscription")]
+    pub(crate) subscription_sender: crate::subscription::SubscriptionsSender<E>,
+}
+impl<BC: BackendCol, E: EventTrait> Clone for ColRo<BC, E> {
+    fn clone(&self) -> Self {
+        Self {
+            inner: self.inner.clone(),
+            #[cfg(not(feature = "subscription"))]
+            phantom: PhantomData,
+            #[cfg(feature = "subscription")]
+            subscription_sender: self.subscription_sender.clone(),
+        }
+    }
+}
+impl<BC: BackendCol, E: EventTrait> DbCollectionRo for ColRo<BC, E> {
+    type BackendCol = BC;
+    type K = E::K;
+    type V = E::V;
+    type Event = E;
+
+    #[inline(always)]
+    fn count(&self) -> KvResult<usize> {
+        self.inner.count()
+    }
+    #[inline(always)]
+    fn get(&self, k: &Self::K) -> KvResult<Option<Self::V>> {
+        self.inner.get(k)
+    }
+    #[inline(always)]
+    fn iter<R: 'static + RangeBounds<Self::K>>(
+        &self,
+        range: R,
+    ) -> KvIter<Self::BackendCol, Self::K, Self::V> {
+        let range: RangeBytes = KvIter::<BC, Self::K, Self::V>::convert_range::<R>(range);
+        KvIter::new(self.inner.iter::<Self::K, Self::V>(range.clone()), range)
+    }
+    #[cfg(feature = "subscription")]
+    #[inline(always)]
+    fn subscribe(&self, subscriber_sender: Subscriber<Self::Event>) -> KvResult<()> {
+        self.subscription_sender
+            .try_send(subscriber_sender)
+            .map_err(|_| KvError::FailToSubscribe)
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/collection_rw.rs b/rust-libs/tools/kv_typed/src/collection_rw.rs
new file mode 100644
index 000000000..962e94289
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/collection_rw.rs
@@ -0,0 +1,121 @@
+use crate::*;
+#[cfg(feature = "subscription")]
+use parking_lot::Mutex;
+
+pub trait DbCollectionRw {
+    type K: Key;
+    type V: Value;
+    type Event: EventTrait<K = Self::K, V = Self::V>;
+
+    fn remove(&self, k: Self::K) -> KvResult<()>;
+    fn upsert(&self, k: Self::K, v: Self::V) -> KvResult<()>;
+}
+
+#[derive(Debug)]
+pub struct ColRw<BC: BackendCol, E: EventTrait> {
+    inner: ColRo<BC, E>,
+    #[cfg(feature = "subscription")]
+    pending_events_receiver: Receiver<Events<E>>,
+    #[cfg(feature = "subscription")]
+    pending_events_sender: Sender<Events<E>>,
+    #[cfg(feature = "subscription")]
+    subscribers: Arc<Mutex<ColSubscribers<E>>>,
+}
+
+impl<BC: BackendCol, E: EventTrait> Clone for ColRw<BC, E> {
+    fn clone(&self) -> Self {
+        Self {
+            inner: self.inner.clone(),
+            #[cfg(feature = "subscription")]
+            pending_events_receiver: self.pending_events_receiver.clone(),
+            #[cfg(feature = "subscription")]
+            pending_events_sender: self.pending_events_sender.clone(),
+            #[cfg(feature = "subscription")]
+            subscribers: self.subscribers.clone(),
+        }
+    }
+}
+
+impl<BC: BackendCol, E: EventTrait> DbCollectionRw for ColRw<BC, E> {
+    type K = E::K;
+    type V = E::V;
+    type Event = E;
+
+    fn remove(&self, k: Self::K) -> KvResult<()> {
+        self.inner.inner.delete(&k)?;
+        #[cfg(feature = "subscription")]
+        {
+            let events = smallvec::smallvec![E::remove(k)];
+            self.notify_subscribers(events);
+        }
+        Ok(())
+    }
+    fn upsert(&self, k: Self::K, v: Self::V) -> KvResult<()> {
+        self.inner.inner.put(&k, &v)?;
+        #[cfg(feature = "subscription")]
+        {
+            let events = smallvec::smallvec![E::upsert(k, v)];
+            self.notify_subscribers(events);
+        }
+        Ok(())
+    }
+}
+
+impl<BC: BackendCol, E: EventTrait> ColRw<BC, E> {
+    #[cfg(not(feature = "subscription"))]
+    pub fn new(col_backend: BC) -> Self {
+        Self {
+            inner: ColRo {
+                inner: col_backend,
+                phantom: PhantomData,
+            },
+        }
+    }
+    #[cfg(feature = "subscription")]
+    pub fn new(col_backend: BC) -> Self {
+        let subscribers = ColSubscribers::<E>::default();
+        let subscription_sender = subscribers.get_subscription_sender();
+        let inner = ColRo {
+            inner: col_backend,
+            subscription_sender,
+        };
+        let (pending_events_sender, pending_events_receiver) = unbounded();
+        Self {
+            inner,
+            pending_events_sender,
+            pending_events_receiver,
+            subscribers: Arc::new(Mutex::new(subscribers)),
+        }
+    }
+    pub fn to_ro(&self) -> &ColRo<BC, E> {
+        &self.inner
+    }
+    #[cfg(feature = "subscription")]
+    fn notify_subscribers(&self, mut events: Events<E>) {
+        if let Some(mut subscribers_guard) = self.subscribers.try_lock() {
+            // Take pending events
+            while let Ok(pending_events) = self.pending_events_receiver.try_recv() {
+                events.extend(pending_events);
+            }
+            // Add new subscribers, notify all subscribers them prune died subscribers
+            subscribers_guard.add_new_subscribers();
+            let died_subscribers = subscribers_guard.notify_subscribers(Arc::new(events));
+            subscribers_guard.prune_subscribers(died_subscribers);
+        } else if !events.is_empty() {
+            // Push pending events into the queue
+            let _ = self.pending_events_sender.try_send(events);
+        }
+    }
+    #[cfg(not(feature = "subscription"))]
+    pub fn write_batch(&self, batch: Batch<BC, Self>) -> KvResult<()> {
+        self.inner.inner.write_batch(batch.into_backend_batch())?;
+        Ok(())
+    }
+    #[cfg(feature = "subscription")]
+    pub fn write_batch(&self, batch: Batch<BC, Self>) -> KvResult<()> {
+        let (backend_batch, events) = batch.into_backend_batch_and_events();
+        self.inner.inner.write_batch(backend_batch)?;
+        self.notify_subscribers(events);
+        Ok(())
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/error.rs b/rust-libs/tools/kv_typed/src/error.rs
new file mode 100644
index 000000000..dd9a362f5
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/error.rs
@@ -0,0 +1,65 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed error type
+
+use crate::*;
+
+#[derive(Clone, Debug, Error, PartialEq)]
+#[error("{0}")]
+pub struct StringErr(pub String);
+
+pub type DynErr = Box<dyn Error + Send + Sync + 'static>;
+
+/// KV Typed error
+pub type KvResult<T> = Result<T, KvError>;
+
+/// KV Typed error
+#[derive(Debug, Error)]
+pub enum KvError {
+    /// Backend error
+    #[error("Backend error: {0}")]
+    BackendError(DynErr),
+    // Error at serialisation or deserialisation
+    #[error("DeserError: {0}")]
+    DeserError(String),
+    /// FailToCreateDbFolder
+    #[error("FailToCreateDbFolder: {0}")]
+    FailToCreateDbFolder(std::io::Error),
+    /// FailToSubscribe
+    #[error("FailToSubscribe")]
+    FailToSubscribe,
+}
+
+#[cfg(feature = "leveldb_backend")]
+impl From<crate::backend::leveldb::LevelDbError> for KvError {
+    fn from(e: crate::backend::leveldb::LevelDbError) -> Self {
+        KvError::BackendError(Box::new(e).into())
+    }
+}
+#[cfg(feature = "sled_backend")]
+impl From<sled::Error> for KvError {
+    fn from(e: sled::Error) -> Self {
+        KvError::BackendError(Box::new(e).into())
+    }
+}
+
+pub type TransactionResult<D, A, BE> = Result<D, TransactionError<A, BE>>;
+#[derive(Debug)]
+pub enum TransactionError<A: Debug, BE: Error + Send + Sync + 'static> {
+    Abort(A),
+    BackendErr(BE),
+    KvError(KvError),
+}
diff --git a/rust-libs/tools/kv_typed/src/event.rs b/rust-libs/tools/kv_typed/src/event.rs
new file mode 100644
index 000000000..b2faa4395
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/event.rs
@@ -0,0 +1,30 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed event
+
+use crate::*;
+
+/// Database events
+pub type Events<E> = SmallVec<[E; 4]>;
+
+/// Event trait
+pub trait EventTrait: 'static + Debug + PartialEq + Send + Sync {
+    type K: Key;
+    type V: Value;
+
+    fn upsert(k: Self::K, v: Self::V) -> Self;
+    fn remove(k: Self::K) -> Self;
+}
diff --git a/rust-libs/tools/kv_typed/src/explorer.rs b/rust-libs/tools/kv_typed/src/explorer.rs
new file mode 100644
index 000000000..2d912b836
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/explorer.rs
@@ -0,0 +1,371 @@
+use crate::*;
+#[cfg(not(feature = "async"))]
+use rayon::{iter::ParallelBridge, prelude::*};
+use std::num::NonZeroUsize;
+
+pub trait DbExplorable {
+    fn explore<'a>(
+        &self,
+        collection_name: &str,
+        action: ExplorerAction<'a>,
+        stringify_json_value: fn(serde_json::Value) -> serde_json::Value,
+    ) -> KvResult<Result<ExplorerActionResponse, StringErr>>;
+    fn list_collections(&self) -> Vec<(&'static str, &'static str, &'static str)>;
+}
+
+pub trait ExplorableKey: Sized {
+    fn from_explorer_str(source: &str) -> Result<Self, StringErr>;
+    fn to_explorer_string(&self) -> KvResult<String>;
+}
+
+impl ExplorableKey for String {
+    fn from_explorer_str(source: &str) -> Result<Self, StringErr> {
+        Ok(source.to_owned())
+    }
+
+    fn to_explorer_string(&self) -> KvResult<String> {
+        Ok(self.clone())
+    }
+}
+
+macro_rules! impl_explorable_key_for_numbers {
+    ($($T:ty),*) => {$(
+        impl ExplorableKey for $T {
+            fn from_explorer_str(source: &str) -> Result<Self, StringErr> {
+                source.parse().map_err(|e| StringErr(format!("{}", e)))
+            }
+
+            fn to_explorer_string(&self) -> KvResult<String> {
+                Ok(format!("{}", self))
+            }
+        }
+    )*};
+}
+
+impl_explorable_key_for_numbers!(usize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64);
+
+pub trait ExplorableValue: Sized {
+    fn from_explorer_str(source: &str) -> Result<Self, StringErr>;
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value>;
+}
+
+impl ExplorableValue for String {
+    fn from_explorer_str(source: &str) -> Result<Self, StringErr> {
+        Ok(source.to_owned())
+    }
+
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        Ok(serde_json::Value::String(self.clone()))
+    }
+}
+
+macro_rules! impl_explorable_value_for_numbers {
+    ($($T:ty),*) => {$(
+        impl ExplorableValue for $T {
+            fn from_explorer_str(source: &str) -> Result<Self, StringErr> {
+                source.parse().map_err(|e| StringErr(format!("{}", e)))
+            }
+
+            #[allow(trivial_numeric_casts)]
+            fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+                Ok(serde_json::Value::Number(serde_json::Number::from_f64(*self as f64).expect("too large number")))
+            }
+        }
+    )*};
+}
+
+impl_explorable_value_for_numbers!(
+    usize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64
+);
+
+#[derive(Debug)]
+pub enum ExplorerAction<'a> {
+    Count,
+    Get {
+        key: &'a str,
+    },
+    Find {
+        key_min: Option<String>,
+        key_max: Option<String>,
+        key_regex: Option<regex::Regex>,
+        value_regex: Option<regex::Regex>,
+        limit: Option<usize>,
+        reverse: bool,
+        step: NonZeroUsize,
+    },
+    Put {
+        key: &'a str,
+        value: &'a str,
+    },
+    Delete {
+        key: &'a str,
+    },
+}
+
+#[derive(Debug, PartialEq)]
+pub struct EntryFound {
+    pub key: String,
+    pub value: serde_json::Value,
+    pub captures: Option<ValueCaptures>,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct ValueCaptures(pub SmallVec<[SmallVec<[Option<String>; 8]>; 8]>);
+
+#[derive(Debug, PartialEq)]
+pub enum ExplorerActionResponse {
+    Count(usize),
+    Get(Option<serde_json::Value>),
+    Find(Vec<EntryFound>),
+    PutOk,
+    DeleteOk,
+}
+
+impl<'a> ExplorerAction<'a> {
+    pub fn exec<BC: BackendCol, E: EventTrait>(
+        self,
+        col: &ColRw<BC, E>,
+        stringify_json_value: fn(serde_json::Value) -> serde_json::Value,
+    ) -> KvResult<Result<ExplorerActionResponse, StringErr>> {
+        Ok(match self {
+            Self::Count => Ok(ExplorerActionResponse::Count(col.to_ro().count()?)),
+            Self::Get { key } => match E::K::from_explorer_str(key) {
+                Ok(k) => Ok(ExplorerActionResponse::Get(
+                    col.to_ro()
+                        .get(&k)?
+                        .map(|v| v.to_explorer_json())
+                        .transpose()?,
+                )),
+                Err(e) => Err(e),
+            },
+            Self::Find {
+                key_min,
+                key_max,
+                key_regex,
+                value_regex,
+                limit,
+                reverse,
+                step,
+            } => match define_range::<E::K>(key_min, key_max) {
+                Ok(range) => Ok(ExplorerActionResponse::Find(match range {
+                    Range::Full => Self::get_range_inner(
+                        col.to_ro(),
+                        ..,
+                        key_regex,
+                        value_regex,
+                        limit,
+                        reverse,
+                        step,
+                        stringify_json_value,
+                    )?,
+                    Range::From(range) => Self::get_range_inner(
+                        col.to_ro(),
+                        range,
+                        key_regex,
+                        value_regex,
+                        limit,
+                        reverse,
+                        step,
+                        stringify_json_value,
+                    )?,
+                    Range::FromTo(range) => Self::get_range_inner(
+                        col.to_ro(),
+                        range,
+                        key_regex,
+                        value_regex,
+                        limit,
+                        reverse,
+                        step,
+                        stringify_json_value,
+                    )?,
+                    Range::To(range) => Self::get_range_inner(
+                        col.to_ro(),
+                        range,
+                        key_regex,
+                        value_regex,
+                        limit,
+                        reverse,
+                        step,
+                        stringify_json_value,
+                    )?,
+                })),
+                Err(e) => Err(e),
+            },
+            Self::Put { key, value } => match E::K::from_explorer_str(key) {
+                Ok(k) => match E::V::from_explorer_str(value) {
+                    Ok(v) => {
+                        col.upsert(k, v)?;
+                        Ok(ExplorerActionResponse::PutOk)
+                    }
+                    Err(e) => Err(e),
+                },
+                Err(e) => Err(e),
+            },
+            Self::Delete { key } => match E::K::from_explorer_str(key) {
+                Ok(k) => {
+                    col.remove(k)?;
+                    Ok(ExplorerActionResponse::DeleteOk)
+                }
+                Err(e) => Err(e),
+            },
+        })
+    }
+    #[allow(clippy::too_many_arguments)]
+    fn get_range_inner<BC: BackendCol, E: EventTrait, R: 'static + RangeBounds<E::K>>(
+        col: &ColRo<BC, E>,
+        range: R,
+        key_regex: Option<regex::Regex>,
+        value_regex: Option<regex::Regex>,
+        limit: Option<usize>,
+        reverse: bool,
+        step: NonZeroUsize,
+        stringify_json_value: fn(serde_json::Value) -> serde_json::Value,
+    ) -> KvResult<Vec<EntryFound>> {
+        let filter_map_closure = move |entry_res| {
+            stringify_and_filter_entry_res::<E::K, E::V>(
+                entry_res,
+                key_regex.as_ref(),
+                value_regex.as_ref(),
+                stringify_json_value,
+            )
+        };
+
+        if let Some(limit) = limit {
+            let iter = col.iter(range);
+
+            if reverse {
+                iter.reverse()
+                    .step_by(step.get())
+                    .filter_map(filter_map_closure)
+                    .take(limit)
+                    .collect()
+            } else {
+                iter.step_by(step.get())
+                    .filter_map(filter_map_closure)
+                    .take(limit)
+                    .collect()
+            }
+        } else {
+            #[cfg(feature = "async")]
+            {
+                if reverse {
+                    col.iter(range)
+                        .reverse()
+                        .step_by(step.get())
+                        .filter_map(filter_map_closure)
+                        .collect()
+                } else {
+                    col.iter(range)
+                        .step_by(step.get())
+                        .filter_map(filter_map_closure)
+                        .collect()
+                }
+            }
+            #[cfg(not(feature = "async"))]
+            {
+                let (send, recv) = unbounded();
+
+                let handler = std::thread::spawn(move || {
+                    let iter = recv.into_iter().step_by(step.get()).par_bridge();
+
+                    iter.filter_map(filter_map_closure).collect()
+                });
+
+                if reverse {
+                    for entry_res in col.iter(range).reverse() {
+                        if send.try_send(entry_res).is_err() {
+                            return handler.join().expect("child thread panic");
+                        }
+                    }
+                } else {
+                    for entry_res in col.iter(range) {
+                        if send.try_send(entry_res).is_err() {
+                            return handler.join().expect("child thread panic");
+                        }
+                    }
+                }
+                drop(send);
+
+                handler.join().expect("child thread panic")
+            }
+        }
+    }
+}
+
+enum Range<K> {
+    Full,
+    From(core::ops::RangeFrom<K>),
+    To(core::ops::RangeToInclusive<K>),
+    FromTo(core::ops::RangeInclusive<K>),
+}
+
+fn define_range<K: Key>(
+    key_min_opt: Option<String>,
+    key_max_opt: Option<String>,
+) -> Result<Range<K>, StringErr> {
+    if let Some(key_min) = key_min_opt {
+        let k_min = K::from_explorer_str(&key_min)?;
+        if let Some(key_max) = key_max_opt {
+            let k_max = K::from_explorer_str(&key_max)?;
+            Ok(Range::FromTo(core::ops::RangeInclusive::new(k_min, k_max)))
+        } else {
+            Ok(Range::From(core::ops::RangeFrom { start: k_min }))
+        }
+    } else if let Some(key_max) = key_max_opt {
+        let k_max = K::from_explorer_str(&key_max)?;
+        Ok(Range::To(core::ops::RangeToInclusive { end: k_max }))
+    } else {
+        Ok(Range::Full)
+    }
+}
+
+fn stringify_and_filter_entry_res<K: Key, V: Value>(
+    entry_res: KvResult<(K, V)>,
+    key_regex_opt: Option<&regex::Regex>,
+    value_regex_opt: Option<&regex::Regex>,
+    stringify_json_value: fn(serde_json::Value) -> serde_json::Value,
+) -> Option<KvResult<EntryFound>> {
+    match entry_res {
+        Ok((k, v)) => match k.to_explorer_string() {
+            Ok(key_string) => {
+                if let Some(key_regex) = key_regex_opt {
+                    if !key_regex.is_match(&key_string) {
+                        return None;
+                    }
+                }
+                match v.to_explorer_json() {
+                    Ok(mut value_json) => {
+                        value_json = stringify_json_value(value_json);
+                        let captures = if let Some(value_regex) = value_regex_opt {
+                            let value_string = value_json.to_string();
+                            if !value_regex.is_match(&value_string) {
+                                return None;
+                            }
+                            Some(ValueCaptures(
+                                value_regex
+                                    .captures_iter(&value_string)
+                                    .map(|caps| {
+                                        caps.iter()
+                                            .skip(1)
+                                            .map(|m_opt| m_opt.map(|m| m.as_str().to_owned()))
+                                            .collect::<SmallVec<[Option<String>; 8]>>()
+                                    })
+                                    .collect(),
+                            ))
+                        } else {
+                            None
+                        };
+                        Some(Ok(EntryFound {
+                            key: key_string,
+                            value: value_json,
+                            captures,
+                        }))
+                    }
+                    Err(e) => Some(Err(e)),
+                }
+            }
+            Err(e) => Some(Err(e)),
+        },
+        Err(e) => Some(Err(e)),
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/from_bytes.rs b/rust-libs/tools/kv_typed/src/from_bytes.rs
new file mode 100644
index 000000000..41329b14a
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/from_bytes.rs
@@ -0,0 +1,22 @@
+use crate::*;
+
+pub trait FromBytes: Sized {
+    type Err: Error;
+
+    /// Create Self from bytes.
+    fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err>;
+}
+
+macro_rules! impl_from_bytes_for_numbers {
+    ($($T:ty),*) => {$(
+        impl FromBytes for $T {
+            type Err = std::array::TryFromSliceError;
+
+            fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err> {
+                Ok(<$T>::from_be_bytes(bytes.try_into()?))
+            }
+        }
+    )*};
+}
+
+impl_from_bytes_for_numbers!(usize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64);
diff --git a/rust-libs/tools/kv_typed/src/iter.rs b/rust-libs/tools/kv_typed/src/iter.rs
new file mode 100644
index 000000000..b177bb65d
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/iter.rs
@@ -0,0 +1,116 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed iterators
+
+pub mod keys;
+mod range;
+pub mod values;
+
+use crate::*;
+
+pub trait ReversableIterator: Iterator + Sized {
+    fn reverse(self) -> Self;
+
+    #[inline(always)]
+    fn last(self) -> Option<Self::Item> {
+        self.reverse().next()
+    }
+}
+
+pub trait ResultIter<T, E>: Iterator<Item = Result<T, E>> + Sized {
+    #[inline(always)]
+    fn next_res(&mut self) -> Result<Option<T>, E> {
+        self.next().transpose()
+    }
+}
+impl<I, T, E> ResultIter<T, E> for I where I: Iterator<Item = Result<T, E>> + Sized {}
+
+pub type RangeBytes = (Bound<IVec>, Bound<IVec>);
+
+#[derive(Debug)]
+pub struct KvIter<C: BackendCol, K: Key, V: Value> {
+    range_iter: range::RangeIter<C>,
+    phantom_key: PhantomData<K>,
+    phantom_value: PhantomData<V>,
+}
+
+impl<C: BackendCol, K: Key, V: Value> Iterator for KvIter<C, K, V> {
+    type Item = KvResult<(K, V)>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self.range_iter.next() {
+            Some(Ok((key_bytes, value_bytes))) => match K::from_bytes(key_bytes.as_ref()) {
+                Ok(key) => match V::from_bytes(value_bytes.as_ref()) {
+                    Ok(value) => Some(Ok((key, value))),
+                    Err(e) => Some(Err(KvError::DeserError(format!("{}", e)))),
+                },
+                Err(e) => Some(Err(KvError::DeserError(format!("{}", e)))),
+            },
+            Some(Err(e)) => Some(Err(KvError::BackendError(e))),
+            None => None,
+        }
+    }
+}
+
+impl<C: BackendCol, K: Key, V: Value> ReversableIterator for KvIter<C, K, V> {
+    #[inline(always)]
+    fn reverse(self) -> Self {
+        Self {
+            range_iter: self.range_iter.reverse(),
+            phantom_key: PhantomData,
+            phantom_value: PhantomData,
+        }
+    }
+}
+
+impl<C: BackendCol, K: Key, V: Value> KvIter<C, K, V> {
+    pub fn keys(self) -> KvIterKeys<C, K> {
+        KvIterKeys::new(self.range_iter)
+    }
+    pub fn values(self) -> KvIterValues<C, K, V> {
+        KvIterValues::new(self.range_iter)
+    }
+    pub(crate) fn convert_range<RK: RangeBounds<K>>(range: RK) -> RangeBytes {
+        let range_start = convert_bound(range.start_bound());
+        let range_end = convert_bound(range.end_bound());
+        (range_start, range_end)
+    }
+    #[cfg(feature = "mock")]
+    pub fn new(backend_iter: C::Iter, range: RangeBytes) -> Self {
+        Self {
+            range_iter: range::RangeIter::new(backend_iter, range.0, range.1),
+            phantom_key: PhantomData,
+            phantom_value: PhantomData,
+        }
+    }
+    #[cfg(not(feature = "mock"))]
+    pub(crate) fn new(backend_iter: C::Iter, range: RangeBytes) -> Self {
+        Self {
+            range_iter: range::RangeIter::new(backend_iter, range.0, range.1),
+            phantom_key: PhantomData,
+            phantom_value: PhantomData,
+        }
+    }
+}
+
+#[inline(always)]
+fn convert_bound<K: Key>(bound_key: Bound<&K>) -> Bound<IVec> {
+    match bound_key {
+        Bound::Included(key) => Bound::Included(key.as_bytes(|key_bytes| key_bytes.into())),
+        Bound::Excluded(key) => Bound::Excluded(key.as_bytes(|key_bytes| key_bytes.into())),
+        Bound::Unbounded => Bound::Unbounded,
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/iter/keys.rs b/rust-libs/tools/kv_typed/src/iter/keys.rs
new file mode 100644
index 000000000..bbe9a0d74
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/iter/keys.rs
@@ -0,0 +1,58 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed iterators
+
+use crate::*;
+
+#[derive(Debug)]
+pub struct KvIterKeys<C: BackendCol, K: Key> {
+    range_iter: super::range::RangeIter<C>,
+    phantom_key: PhantomData<K>,
+}
+
+impl<C: BackendCol, K: Key> Iterator for KvIterKeys<C, K> {
+    type Item = KvResult<K>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self.range_iter.next() {
+            Some(Ok((key_bytes, _value_bytes))) => match K::from_bytes(key_bytes.as_ref()) {
+                Ok(key) => Some(Ok(key)),
+                Err(e) => Some(Err(KvError::DeserError(format!("{}", e)))),
+            },
+            Some(Err(e)) => Some(Err(KvError::BackendError(e))),
+            None => None,
+        }
+    }
+}
+
+impl<C: BackendCol, K: Key> ReversableIterator for KvIterKeys<C, K> {
+    #[inline(always)]
+    fn reverse(self) -> Self {
+        Self {
+            range_iter: self.range_iter.reverse(),
+            phantom_key: PhantomData,
+        }
+    }
+}
+
+impl<C: BackendCol, K: Key> KvIterKeys<C, K> {
+    pub(super) fn new(range_iter: super::range::RangeIter<C>) -> Self {
+        Self {
+            range_iter,
+            phantom_key: PhantomData,
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/iter/range.rs b/rust-libs/tools/kv_typed/src/iter/range.rs
new file mode 100644
index 000000000..58afce651
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/iter/range.rs
@@ -0,0 +1,110 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed iterators
+
+use crate::*;
+
+// V2
+pub(super) struct RangeIter<C: BackendCol> {
+    backend_iter: C::Iter,
+    reversed: bool,
+    range_start: Bound<IVec>,
+    range_end: Bound<IVec>,
+}
+
+impl<C: BackendCol> Debug for RangeIter<C> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("LevelDbCol")
+            .field("backend_iter", &"BackendIter")
+            .field("reversed", &format!("{:?}", self.reversed))
+            .field("range_start", &format!("{:?}", self.range_start))
+            .field("range_end", &format!("{:?}", self.range_end))
+            .finish()
+    }
+}
+
+impl<C: BackendCol> RangeIter<C> {
+    #[inline(always)]
+    pub(crate) fn new(
+        backend_iter: C::Iter,
+        range_start: Bound<IVec>,
+        range_end: Bound<IVec>,
+    ) -> Self {
+        RangeIter {
+            backend_iter,
+            reversed: false,
+            range_start,
+            range_end,
+        }
+    }
+}
+
+impl<C: BackendCol> Iterator for RangeIter<C> {
+    type Item = <C::Iter as Iterator>::Item;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            match self.backend_iter.next() {
+                Some(Ok((key_bytes, value_bytes))) => {
+                    let start_bound_ok = match &self.range_start {
+                        Bound::Included(start_bytes) => key_bytes.as_ref() >= start_bytes.as_ref(),
+                        Bound::Excluded(start_bytes) => key_bytes.as_ref() > start_bytes.as_ref(),
+                        Bound::Unbounded => true,
+                    };
+                    let end_bound_ok = match &self.range_end {
+                        Bound::Included(end_bytes) => key_bytes.as_ref() <= end_bytes.as_ref(),
+                        Bound::Excluded(end_bytes) => key_bytes.as_ref() < end_bytes.as_ref(),
+                        Bound::Unbounded => true,
+                    };
+                    if start_bound_ok {
+                        if end_bound_ok {
+                            break Some(Ok((key_bytes, value_bytes)));
+                        } else if self.reversed {
+                            // The interval has not yet begun.
+                            continue;
+                        } else {
+                            // The range has been fully traversed, the iterator is finished.
+                            break None;
+                        }
+                    } else if end_bound_ok {
+                        if self.reversed {
+                            // The range has been fully traversed, the iterator is finished.
+                            break None;
+                        } else {
+                            // The interval has not yet begun.
+                            continue;
+                        }
+                    } else {
+                        // Empty range, the iterator is finished.
+                        break None;
+                    }
+                }
+                other => break other,
+            }
+        }
+    }
+}
+impl<C: BackendCol> ReversableIterator for RangeIter<C> {
+    #[inline(always)]
+    fn reverse(self) -> Self {
+        RangeIter {
+            backend_iter: self.backend_iter.reverse(),
+            reversed: !self.reversed,
+            range_start: self.range_start,
+            range_end: self.range_end,
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/iter/values.rs b/rust-libs/tools/kv_typed/src/iter/values.rs
new file mode 100644
index 000000000..d75986e46
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/iter/values.rs
@@ -0,0 +1,61 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed iterators
+
+use crate::*;
+
+#[derive(Debug)]
+pub struct KvIterValues<C: BackendCol, K: Key, V: Value> {
+    range_iter: super::range::RangeIter<C>,
+    phantom_key: PhantomData<K>,
+    phantom_value: PhantomData<V>,
+}
+
+impl<C: BackendCol, K: Key, V: Value> Iterator for KvIterValues<C, K, V> {
+    type Item = KvResult<V>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self.range_iter.next() {
+            Some(Ok((_key_bytes, value_bytes))) => match V::from_bytes(value_bytes.as_ref()) {
+                Ok(value) => Some(Ok(value)),
+                Err(e) => Some(Err(KvError::DeserError(format!("{}", e)))),
+            },
+            Some(Err(e)) => Some(Err(KvError::BackendError(e))),
+            None => None,
+        }
+    }
+}
+
+impl<C: BackendCol, K: Key, V: Value> ReversableIterator for KvIterValues<C, K, V> {
+    #[inline(always)]
+    fn reverse(self) -> Self {
+        Self {
+            range_iter: self.range_iter.reverse(),
+            phantom_key: PhantomData,
+            phantom_value: PhantomData,
+        }
+    }
+}
+
+impl<C: BackendCol, K: Key, V: Value> KvIterValues<C, K, V> {
+    pub(super) fn new(range_iter: super::range::RangeIter<C>) -> Self {
+        Self {
+            range_iter,
+            phantom_key: PhantomData,
+            phantom_value: PhantomData,
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/key.rs b/rust-libs/tools/kv_typed/src/key.rs
new file mode 100644
index 000000000..7d820dfa7
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/key.rs
@@ -0,0 +1,62 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed Key trait
+
+use crate::*;
+
+/// Trait to be implemented by the collection key
+
+#[cfg(not(feature = "explorer"))]
+pub trait Key:
+    'static + KeyAsBytes + Debug + Eq + FromBytes + std::hash::Hash + Send + Sync + Sized
+{
+}
+
+#[cfg(feature = "explorer")]
+pub trait Key:
+    'static
+    + KeyAsBytes
+    + Debug
+    + Eq
+    + ExplorableKey
+    + FromBytes
+    + std::hash::Hash
+    + Send
+    + Sync
+    + Sized
+{
+}
+
+#[cfg(not(feature = "explorer"))]
+impl<T> Key for T where
+    T: 'static + KeyAsBytes + Debug + Eq + FromBytes + std::hash::Hash + Send + Sync + Sized
+{
+}
+
+#[cfg(feature = "explorer")]
+impl<T> Key for T where
+    T: 'static
+        + KeyAsBytes
+        + Debug
+        + Eq
+        + ExplorableKey
+        + FromBytes
+        + std::hash::Hash
+        + Send
+        + Sync
+        + Sized
+{
+}
diff --git a/rust-libs/tools/kv_typed/src/lib.rs b/rust-libs/tools/kv_typed/src/lib.rs
new file mode 100644
index 000000000..2920e4c00
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/lib.rs
@@ -0,0 +1,111 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Strongly typed key-value storage
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    missing_debug_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces,
+    unused_qualifications
+)]
+
+mod as_bytes;
+pub mod backend;
+mod batch;
+mod collection_ro;
+mod collection_rw;
+mod error;
+mod event;
+#[cfg(feature = "explorer")]
+pub mod explorer;
+mod from_bytes;
+mod iter;
+mod key;
+#[cfg(feature = "subscription")]
+mod subscription;
+mod utils;
+mod value;
+
+// Re-export dependencies
+#[cfg(feature = "async")]
+pub use async_channel as channel;
+#[cfg(all(not(feature = "async"), feature = "sync"))]
+pub use crossbeam_channel as channel;
+#[cfg(feature = "explorer")]
+pub use regex;
+
+/// Kv Typed prelude
+pub mod prelude {
+    pub use crate::as_bytes::{KeyAsBytes, ValueAsBytes};
+    #[cfg(feature = "leveldb_backend")]
+    pub use crate::backend::leveldb::{LevelDb, LevelDbConf};
+    #[cfg(feature = "memory_backend")]
+    pub use crate::backend::memory::{Mem, MemConf};
+    #[cfg(feature = "mock")]
+    pub use crate::backend::mock::{MockBackend, MockBackendCol, MockBackendIter};
+    #[cfg(feature = "sled_backend")]
+    pub use crate::backend::sled::{Config as SledConf, Sled};
+    pub use crate::backend::{Backend, BackendCol, TransactionalBackend};
+    pub use crate::batch::Batch;
+    #[cfg(feature = "mock")]
+    pub use crate::collection_ro::MockColRo;
+    pub use crate::collection_ro::{ColRo, DbCollectionRo};
+    pub use crate::collection_rw::{ColRw, DbCollectionRw};
+    pub use crate::error::{
+        DynErr, KvError, KvResult, StringErr, TransactionError, TransactionResult,
+    };
+    pub use crate::event::{EventTrait, Events};
+    #[cfg(feature = "explorer")]
+    pub use crate::explorer::{ExplorableKey, ExplorableValue};
+    pub use crate::from_bytes::FromBytes;
+    pub use crate::iter::{
+        keys::KvIterKeys, values::KvIterValues, KvIter, ResultIter, ReversableIterator,
+    };
+    pub use crate::key::Key;
+    #[cfg(feature = "subscription")]
+    pub use crate::subscription::{NewSubscribers, Subscriber, Subscribers};
+    pub use crate::value::Value;
+    pub use kv_typed_code_gen::db_schema;
+}
+
+// Internal crate imports
+pub(crate) use crate::backend::BackendBatch;
+#[cfg(feature = "explorer")]
+pub(crate) use crate::explorer::{ExplorableKey, ExplorableValue};
+pub(crate) use crate::iter::RangeBytes;
+pub(crate) use crate::prelude::*;
+#[cfg(feature = "subscription")]
+pub(crate) use crate::subscription::ColSubscribers;
+pub(crate) use crate::utils::arc::Arc;
+pub(crate) use crate::utils::ivec::IVec;
+#[cfg(feature = "async")]
+use async_channel::{unbounded, Receiver, Sender, TrySendError};
+#[cfg(all(not(feature = "async"), feature = "sync"))]
+#[allow(unused_imports)]
+use crossbeam_channel::{unbounded, Receiver, Sender, TrySendError};
+pub(crate) use smallvec::SmallVec;
+pub(crate) use std::{
+    convert::TryInto,
+    error::Error,
+    fmt::Debug,
+    marker::PhantomData,
+    ops::{Bound, RangeBounds},
+};
+pub(crate) use thiserror::Error;
diff --git a/rust-libs/tools/kv_typed/src/subscription.rs b/rust-libs/tools/kv_typed/src/subscription.rs
new file mode 100644
index 000000000..305332890
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/subscription.rs
@@ -0,0 +1,108 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed subscription
+
+use crate::*;
+
+/// Subscriber
+pub type Subscriber<E> = Sender<Arc<Events<E>>>;
+/// Subscriptions sender
+pub(crate) type SubscriptionsSender<E> = Sender<Subscriber<E>>;
+/// Subscribers
+pub type Subscribers<E> = std::collections::BTreeMap<usize, Subscriber<E>>;
+/// New subscribers
+pub type NewSubscribers<E> = SmallVec<[Subscriber<E>; 4]>;
+
+#[derive(Debug)]
+#[doc(hidden)]
+pub struct ColSubscribers<E: EventTrait> {
+    subscription_sender: SubscriptionsSender<E>,
+    subscription_receiver: Receiver<Subscriber<E>>,
+    subscribers: Subscribers<E>,
+    subscriber_index: usize,
+}
+
+impl<E: EventTrait> Default for ColSubscribers<E> {
+    fn default() -> Self {
+        let (subscription_sender, subscription_receiver) = unbounded();
+        ColSubscribers {
+            subscription_sender,
+            subscription_receiver,
+            subscribers: std::collections::BTreeMap::new(),
+            subscriber_index: 0,
+        }
+    }
+}
+
+impl<E: EventTrait> ColSubscribers<E> {
+    pub(crate) fn get_subscription_sender(&self) -> Sender<Subscriber<E>> {
+        self.subscription_sender.clone()
+    }
+    #[inline(always)]
+    pub(crate) fn get_new_subscribers(&self) -> NewSubscribers<E> {
+        if !self.subscription_receiver.is_empty() {
+            let mut new_subscribers = SmallVec::new();
+            while let Ok(subscriber) = self.subscription_receiver.try_recv() {
+                new_subscribers.push(subscriber)
+            }
+            new_subscribers
+        } else {
+            SmallVec::new()
+        }
+    }
+    pub(crate) fn notify_subscribers(&self, events: Arc<Events<E>>) -> Vec<usize> {
+        let mut died_subscribers = Vec::with_capacity(self.subscribers.len());
+        let mut unsend_events_opt = None;
+        for (id, subscriber) in &self.subscribers {
+            if let Err(e) = subscriber.try_send(
+                unsend_events_opt
+                    .take()
+                    .unwrap_or_else(|| Arc::clone(&events)),
+            ) {
+                match e {
+                    #[cfg(feature = "async")]
+                    TrySendError::Closed(events_) => {
+                        unsend_events_opt = Some(events_);
+                        died_subscribers.push(*id);
+                    }
+                    #[cfg(not(feature = "async"))]
+                    TrySendError::Disconnected(events_) => {
+                        unsend_events_opt = Some(events_);
+                        died_subscribers.push(*id);
+                    }
+                    TrySendError::Full(events_) => {
+                        unsend_events_opt = Some(events_);
+                    }
+                }
+            }
+        }
+        died_subscribers
+    }
+    #[inline(always)]
+    pub(crate) fn add_new_subscribers(&mut self) {
+        for new_subscriber in self.get_new_subscribers() {
+            self.subscribers
+                .insert(self.subscriber_index, new_subscriber);
+            self.subscriber_index += 1;
+        }
+    }
+    #[inline(always)]
+    pub(crate) fn prune_subscribers(&mut self, died_subscribers: Vec<usize>) {
+        for died_subscriber in died_subscribers {
+            self.subscribers.remove(&died_subscriber);
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/utils.rs b/rust-libs/tools/kv_typed/src/utils.rs
new file mode 100644
index 000000000..68bd74c49
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/utils.rs
@@ -0,0 +1,24 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed utils
+
+pub mod arc;
+#[cfg(not(feature = "sled_backend"))]
+pub mod ivec;
+#[cfg(feature = "sled_backend")]
+pub mod ivec {
+    pub use sled::IVec;
+}
diff --git a/rust-libs/tools/kv_typed/src/utils/arc.rs b/rust-libs/tools/kv_typed/src/utils/arc.rs
new file mode 100644
index 000000000..6eef0caad
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/utils/arc.rs
@@ -0,0 +1,267 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV typed Arc
+
+#![allow(clippy::unwrap_used, dead_code, unsafe_code)]
+/// We use this because we never use the weak count on the std
+/// `Arc`, but we use a LOT of `Arc`'s, so the extra 8 bytes
+/// turn into a huge overhead.
+use std::{
+    alloc::{alloc, dealloc, Layout},
+    convert::TryFrom,
+    fmt::{self, Debug},
+    mem,
+    ops::Deref,
+    ptr,
+    sync::atomic::{AtomicUsize, Ordering},
+};
+
+// we make this repr(C) because we do a raw
+// write to the beginning where we expect
+// the rc to be.
+#[repr(C)]
+struct ArcInner<T: ?Sized> {
+    rc: AtomicUsize,
+    inner: T,
+}
+
+pub struct Arc<T: ?Sized> {
+    ptr: *mut ArcInner<T>,
+}
+
+unsafe impl<T: Send + Sync + ?Sized> Send for Arc<T> {}
+unsafe impl<T: Send + Sync + ?Sized> Sync for Arc<T> {}
+
+impl<T: Debug + ?Sized> Debug for Arc<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        Debug::fmt(&**self, f)
+    }
+}
+
+impl<T> Arc<T> {
+    pub fn new(inner: T) -> Arc<T> {
+        let bx = Box::new(ArcInner {
+            inner,
+            rc: AtomicUsize::new(1),
+        });
+        let ptr = Box::into_raw(bx);
+        Arc { ptr }
+    }
+
+    // See std::sync::arc::Arc::copy_from_slice,
+    // "Unsafe because the caller must either take ownership or bind `T: Copy`"
+    unsafe fn copy_from_slice(s: &[T]) -> Arc<[T]> {
+        let align = std::cmp::max(mem::align_of::<T>(), mem::align_of::<AtomicUsize>());
+
+        let rc_width = std::cmp::max(align, mem::size_of::<AtomicUsize>());
+        let data_width = mem::size_of::<T>().checked_mul(s.len()).unwrap();
+
+        let size_unpadded = rc_width.checked_add(data_width).unwrap();
+        // Pad size out to alignment
+        let size_padded = (size_unpadded + align - 1) & !(align - 1);
+
+        let layout = Layout::from_size_align(size_padded, align).unwrap();
+
+        let ptr = alloc(layout);
+
+        assert!(!ptr.is_null(), "failed to allocate Arc");
+        #[allow(clippy::cast_ptr_alignment)]
+        ptr::write(ptr as _, AtomicUsize::new(1));
+
+        let data_ptr = ptr.add(rc_width);
+        ptr::copy_nonoverlapping(s.as_ptr(), data_ptr as _, s.len());
+
+        let fat_ptr: *const ArcInner<[T]> = Arc::fatten(ptr, s.len());
+
+        Arc {
+            ptr: fat_ptr as *mut _,
+        }
+    }
+
+    /// <https://users.rust-lang.org/t/construct-fat-pointer-to-struct/29198/9>
+    #[allow(trivial_casts)]
+    fn fatten(data: *const u8, len: usize) -> *const ArcInner<[T]> {
+        // Requirements of slice::from_raw_parts.
+        assert!(!data.is_null());
+        assert!(isize::try_from(len).is_ok());
+
+        let slice = unsafe { core::slice::from_raw_parts(data as *const (), len) };
+        slice as *const [()] as *const _
+    }
+
+    pub fn into_raw(arc: Arc<T>) -> *const T {
+        let ptr = unsafe { &(*arc.ptr).inner };
+        #[allow(clippy::mem_forget)]
+        mem::forget(arc);
+        ptr
+    }
+
+    pub unsafe fn from_raw(ptr: *const T) -> Arc<T> {
+        let align = std::cmp::max(mem::align_of::<T>(), mem::align_of::<AtomicUsize>());
+
+        let rc_width = std::cmp::max(align, mem::size_of::<AtomicUsize>());
+
+        let sub_ptr = (ptr as *const u8).sub(rc_width) as *mut ArcInner<T>;
+
+        Arc { ptr: sub_ptr }
+    }
+}
+
+impl<T: ?Sized> Arc<T> {
+    pub fn strong_count(arc: &Arc<T>) -> usize {
+        unsafe { (*arc.ptr).rc.load(Ordering::Acquire) }
+    }
+
+    pub fn get_mut(arc: &mut Arc<T>) -> Option<&mut T> {
+        if Arc::strong_count(arc) == 1 {
+            Some(unsafe { &mut arc.ptr.as_mut().unwrap().inner })
+        } else {
+            None
+        }
+    }
+}
+
+impl<T: ?Sized + Clone> Arc<T> {
+    pub fn make_mut(arc: &mut Arc<T>) -> &mut T {
+        if Arc::strong_count(arc) != 1 {
+            *arc = Arc::new((**arc).clone());
+            assert_eq!(Arc::strong_count(arc), 1);
+        }
+        Arc::get_mut(arc).unwrap()
+    }
+}
+
+impl<T: Default> Default for Arc<T> {
+    fn default() -> Arc<T> {
+        Arc::new(T::default())
+    }
+}
+
+impl<T: ?Sized> Clone for Arc<T> {
+    fn clone(&self) -> Arc<T> {
+        // safe to use Relaxed ordering below because
+        // of the required synchronization for passing
+        // any objects to another thread.
+        let last_count = unsafe { (*self.ptr).rc.fetch_add(1, Ordering::Relaxed) };
+
+        if last_count == usize::max_value() {
+            #[cold]
+            std::process::abort();
+        }
+
+        Arc { ptr: self.ptr }
+    }
+}
+
+impl<T: ?Sized> Drop for Arc<T> {
+    fn drop(&mut self) {
+        unsafe {
+            let rc = (*self.ptr).rc.fetch_sub(1, Ordering::Release) - 1;
+            if rc == 0 {
+                std::sync::atomic::fence(Ordering::Acquire);
+                Box::from_raw(self.ptr);
+            }
+        }
+    }
+}
+
+impl<T: Copy> From<&[T]> for Arc<[T]> {
+    #[inline]
+    fn from(s: &[T]) -> Arc<[T]> {
+        unsafe { Arc::copy_from_slice(s) }
+    }
+}
+
+#[allow(clippy::fallible_impl_from)]
+impl<T> From<Box<[T]>> for Arc<[T]> {
+    #[inline]
+    fn from(b: Box<[T]>) -> Arc<[T]> {
+        let len = b.len();
+        unsafe {
+            let src = Box::into_raw(b);
+            let value_layout = Layout::for_value(&*src);
+            let align = std::cmp::max(value_layout.align(), mem::align_of::<AtomicUsize>());
+            let rc_width = std::cmp::max(align, mem::size_of::<AtomicUsize>());
+            let unpadded_size = rc_width.checked_add(value_layout.size()).unwrap();
+            // pad the total `Arc` allocation size to the alignment of
+            // `max(value, AtomicUsize)`
+            let size = (unpadded_size + align - 1) & !(align - 1);
+            let dst_layout = Layout::from_size_align(size, align).unwrap();
+            let dst = alloc(dst_layout);
+            assert!(!dst.is_null(), "failed to allocate Arc");
+
+            #[allow(clippy::cast_ptr_alignment)]
+            ptr::write(dst as _, AtomicUsize::new(1));
+            let data_ptr = dst.add(rc_width);
+            ptr::copy_nonoverlapping(src as *const u8, data_ptr, value_layout.size());
+
+            // free the old box memory without running Drop
+            if value_layout.size() != 0 {
+                dealloc(src as *mut u8, value_layout);
+            }
+
+            let fat_ptr: *const ArcInner<[T]> = Arc::fatten(dst, len);
+
+            Arc {
+                ptr: fat_ptr as *mut _,
+            }
+        }
+    }
+}
+
+#[test]
+fn boxed_slice_to_arc_slice() {
+    let box1: Box<[u8]> = Box::new([1, 2, 3]);
+    let arc1: Arc<[u8]> = box1.into();
+    assert_eq!(&*arc1, &*vec![1, 2, 3]);
+    let box2: Box<[u64]> = Box::new([1, 2, 3]);
+    let arc2: Arc<[u64]> = box2.into();
+    assert_eq!(&*arc2, &*vec![1, 2, 3]);
+}
+
+impl<T> From<Vec<T>> for Arc<[T]> {
+    #[inline]
+    fn from(mut v: Vec<T>) -> Arc<[T]> {
+        unsafe {
+            let arc = Arc::copy_from_slice(&v);
+
+            // Allow the Vec to free its memory, but not destroy its contents
+            v.set_len(0);
+
+            arc
+        }
+    }
+}
+
+impl<T: ?Sized> Deref for Arc<T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        unsafe { &(*self.ptr).inner }
+    }
+}
+
+impl<T: ?Sized> std::borrow::Borrow<T> for Arc<T> {
+    fn borrow(&self) -> &T {
+        &**self
+    }
+}
+
+impl<T: ?Sized> AsRef<T> for Arc<T> {
+    fn as_ref(&self) -> &T {
+        &**self
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/utils/ivec.rs b/rust-libs/tools/kv_typed/src/utils/ivec.rs
new file mode 100644
index 000000000..50a8d7528
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/utils/ivec.rs
@@ -0,0 +1,314 @@
+use std::{
+    convert::TryFrom,
+    fmt,
+    hash::{Hash, Hasher},
+    ops::{Deref, DerefMut},
+    sync::Arc,
+};
+
+const CUTOFF: usize = 22;
+
+type Inner = [u8; CUTOFF];
+
+/// A buffer that may either be inline or remote and protected
+/// by an Arc
+#[derive(Clone)]
+pub struct IVec(IVecInner);
+
+impl Default for IVec {
+    fn default() -> Self {
+        Self::from(&[])
+    }
+}
+
+impl Hash for IVec {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.deref().hash(state);
+    }
+}
+
+#[derive(Clone)]
+enum IVecInner {
+    Inline(u8, Inner),
+    Remote(Arc<[u8]>),
+    Subslice {
+        base: Arc<[u8]>,
+        offset: usize,
+        len: usize,
+    },
+}
+
+const fn is_inline_candidate(length: usize) -> bool {
+    length <= CUTOFF
+}
+
+impl IVec {
+    pub fn subslice(&self, slice_offset: usize, len: usize) -> Self {
+        assert!(self.len().checked_sub(slice_offset).expect("") >= len);
+
+        let inner = match self.0 {
+            IVecInner::Remote(ref base) => IVecInner::Subslice {
+                base: base.clone(),
+                offset: slice_offset,
+                len,
+            },
+            IVecInner::Inline(_, old_inner) => {
+                // old length already checked above in assertion
+                let mut new_inner = Inner::default();
+                new_inner[..len].copy_from_slice(&old_inner[slice_offset..slice_offset + len]);
+
+                IVecInner::Inline(u8::try_from(len).expect(""), new_inner)
+            }
+            IVecInner::Subslice {
+                ref base,
+                ref offset,
+                ..
+            } => IVecInner::Subslice {
+                base: base.clone(),
+                offset: offset + slice_offset,
+                len,
+            },
+        };
+
+        IVec(inner)
+    }
+
+    fn inline(slice: &[u8]) -> Self {
+        assert!(is_inline_candidate(slice.len()));
+
+        let mut data = Inner::default();
+
+        #[allow(unsafe_code)]
+        unsafe {
+            std::ptr::copy_nonoverlapping(slice.as_ptr(), data.as_mut_ptr(), slice.len());
+        }
+
+        Self(IVecInner::Inline(
+            u8::try_from(slice.len()).expect(""),
+            data,
+        ))
+    }
+
+    fn remote(arc: Arc<[u8]>) -> Self {
+        Self(IVecInner::Remote(arc))
+    }
+
+    fn make_mut(&mut self) {
+        match self.0 {
+            IVecInner::Remote(ref mut buf) if Arc::strong_count(buf) != 1 => {
+                self.0 = IVecInner::Remote(buf.to_vec().into());
+            }
+            IVecInner::Subslice {
+                ref mut base,
+                offset,
+                len,
+            } if Arc::strong_count(base) != 1 => {
+                self.0 = IVecInner::Remote(base[offset..offset + len].to_vec().into());
+            }
+            _ => {}
+        }
+    }
+}
+
+impl From<Box<[u8]>> for IVec {
+    fn from(b: Box<[u8]>) -> Self {
+        if is_inline_candidate(b.len()) {
+            Self::inline(&b)
+        } else {
+            Self::remote(Arc::from(b))
+        }
+    }
+}
+
+impl From<&[u8]> for IVec {
+    fn from(slice: &[u8]) -> Self {
+        if is_inline_candidate(slice.len()) {
+            Self::inline(slice)
+        } else {
+            Self::remote(Arc::from(slice))
+        }
+    }
+}
+
+impl From<Arc<[u8]>> for IVec {
+    fn from(arc: Arc<[u8]>) -> Self {
+        if is_inline_candidate(arc.len()) {
+            Self::inline(&arc)
+        } else {
+            Self::remote(arc)
+        }
+    }
+}
+
+impl From<&IVec> for IVec {
+    fn from(v: &Self) -> Self {
+        v.clone()
+    }
+}
+
+impl std::borrow::Borrow<[u8]> for IVec {
+    fn borrow(&self) -> &[u8] {
+        self.as_ref()
+    }
+}
+
+impl std::borrow::Borrow<[u8]> for &IVec {
+    fn borrow(&self) -> &[u8] {
+        self.as_ref()
+    }
+}
+
+macro_rules! from_array {
+    ($($s:expr),*) => {
+        $(
+            impl From<&[u8; $s]> for IVec {
+                fn from(v: &[u8; $s]) -> Self {
+                    Self::from(&v[..])
+                }
+            }
+        )*
+    }
+}
+
+from_array!(
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+    26, 27, 28, 29, 30, 31, 32
+);
+
+impl Into<Arc<[u8]>> for IVec {
+    fn into(self) -> Arc<[u8]> {
+        match self.0 {
+            IVecInner::Inline(..) => Arc::from(self.as_ref()),
+            IVecInner::Remote(arc) => arc,
+            IVecInner::Subslice { .. } => self.deref().into(),
+        }
+    }
+}
+
+impl Deref for IVec {
+    type Target = [u8];
+
+    #[inline]
+    fn deref(&self) -> &[u8] {
+        self.as_ref()
+    }
+}
+
+impl AsRef<[u8]> for IVec {
+    #[inline]
+    #[allow(unsafe_code)]
+    fn as_ref(&self) -> &[u8] {
+        match &self.0 {
+            IVecInner::Inline(sz, buf) => unsafe { buf.get_unchecked(..*sz as usize) },
+            IVecInner::Remote(buf) => buf,
+            IVecInner::Subslice {
+                ref base,
+                offset,
+                len,
+            } => &base[*offset..*offset + *len],
+        }
+    }
+}
+
+impl DerefMut for IVec {
+    #[inline]
+    fn deref_mut(&mut self) -> &mut [u8] {
+        self.as_mut()
+    }
+}
+
+impl AsMut<[u8]> for IVec {
+    #[inline]
+    #[allow(unsafe_code)]
+    fn as_mut(&mut self) -> &mut [u8] {
+        self.make_mut();
+
+        match &mut self.0 {
+            IVecInner::Inline(ref sz, ref mut buf) => unsafe {
+                std::slice::from_raw_parts_mut(buf.as_mut_ptr(), *sz as usize)
+            },
+            IVecInner::Remote(ref mut buf) => Arc::get_mut(buf).expect(""),
+            IVecInner::Subslice {
+                ref mut base,
+                offset,
+                len,
+            } => &mut Arc::get_mut(base).expect("")[*offset..*offset + *len],
+        }
+    }
+}
+
+impl Ord for IVec {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.as_ref().cmp(other.as_ref())
+    }
+}
+
+impl PartialOrd for IVec {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl<T: AsRef<[u8]>> PartialEq<T> for IVec {
+    fn eq(&self, other: &T) -> bool {
+        self.as_ref() == other.as_ref()
+    }
+}
+
+impl PartialEq<[u8]> for IVec {
+    fn eq(&self, other: &[u8]) -> bool {
+        self.as_ref() == other
+    }
+}
+
+impl Eq for IVec {}
+
+impl fmt::Debug for IVec {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.as_ref().fmt(f)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn ivec_usage() {
+        let iv2 = IVec::from(&[4; 128][..]);
+        assert_eq!(iv2, vec![4; 128]);
+    }
+
+    #[test]
+    fn boxed_slice_conversion() {
+        let boite1: Box<[u8]> = Box::new([1, 2, 3]);
+        let iv1: IVec = boite1.into();
+        assert_eq!(iv1, vec![1, 2, 3]);
+        let boite2: Box<[u8]> = Box::new([4; 128]);
+        let iv2: IVec = boite2.into();
+        assert_eq!(iv2, vec![4; 128]);
+    }
+
+    #[test]
+    #[should_panic]
+    fn subslice_usage_00() {
+        let iv1 = IVec::from(&[1, 2, 3][..]);
+        let _subslice = iv1.subslice(0, 4);
+    }
+
+    #[test]
+    #[should_panic]
+    fn subslice_usage_01() {
+        let iv1 = IVec::from(&[1, 2, 3][..]);
+        let _subslice = iv1.subslice(3, 1);
+    }
+
+    #[test]
+    fn ivec_as_mut_identity() {
+        let initial = &[1];
+        let mut iv = IVec::from(initial);
+        assert_eq!(&*initial, &*iv);
+        assert_eq!(&*initial, &mut *iv);
+        assert_eq!(&*initial, iv.as_mut());
+    }
+}
diff --git a/rust-libs/tools/kv_typed/src/value.rs b/rust-libs/tools/kv_typed/src/value.rs
new file mode 100644
index 000000000..2681c1457
--- /dev/null
+++ b/rust-libs/tools/kv_typed/src/value.rs
@@ -0,0 +1,59 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! KV Typed Value trait
+
+use crate::*;
+
+/// Trait to be implemented by the collection value
+#[cfg(not(feature = "explorer"))]
+pub trait Value:
+    'static + ValueAsBytes + Debug + FromBytes + PartialEq + Send + Sync + Sized
+{
+}
+
+#[cfg(feature = "explorer")]
+pub trait Value:
+    'static + ValueAsBytes + Debug + ExplorableValue + FromBytes + PartialEq + Send + Sync + Sized
+{
+}
+
+#[cfg(not(feature = "explorer"))]
+impl<T> Value for T where
+    T: 'static + ValueAsBytes + Debug + FromBytes + PartialEq + Send + Sync + Sized
+{
+}
+
+#[cfg(feature = "explorer")]
+impl<T> Value for T where
+    T: 'static
+        + ValueAsBytes
+        + Debug
+        + ExplorableValue
+        + FromBytes
+        + PartialEq
+        + Send
+        + Sync
+        + Sized
+{
+}
+
+impl FromBytes for String {
+    type Err = std::str::Utf8Error;
+
+    fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err> {
+        Ok(std::str::from_utf8(bytes)?.to_owned())
+    }
+}
diff --git a/rust-libs/tools/kv_typed/tests/db_schema.rs b/rust-libs/tools/kv_typed/tests/db_schema.rs
new file mode 100644
index 000000000..c35941384
--- /dev/null
+++ b/rust-libs/tools/kv_typed/tests/db_schema.rs
@@ -0,0 +1,73 @@
+#[cfg(feature = "memory_backend")]
+mod tests {
+    use kv_typed::prelude::*;
+    use std::fmt::Debug;
+
+    db_schema!(
+        TestV1,
+        [
+            ["c1", col_1, i32, String],
+            ["c2", col_2, usize, i128],
+            ["c2", col_3, u64, u128],
+        ]
+    );
+
+    #[maybe_async::test(not(feature = "async"), async(feature = "async", async_std::test))]
+    async fn test_db_schema() -> KvResult<()> {
+        let db = TestV1Db::<Mem>::open(MemConf::default())?;
+
+        #[cfg(feature = "subscription")]
+        let (sender, recv) = kv_typed::channel::unbounded();
+        #[cfg(feature = "subscription")]
+        db.col_1().subscribe(sender)?;
+
+        let db2 = db.clone();
+
+        let handler = std::thread::spawn(move || db2.col_1_write().upsert(3, "toto".to_owned()));
+        handler.join().expect("thread panic")?;
+
+        #[cfg(feature = "subscription")]
+        {
+            let expected_events: Events<Col1Event> = smallvec::smallvec![Col1Event::Upsert {
+                key: 3,
+                value: "toto".to_owned(),
+            }];
+            #[allow(unused_parens)]
+            if let Ok(msg) = recv.recv().await {
+                assert_eq!(msg.as_ref(), &expected_events,)
+            } else {
+                panic!("must be receive event")
+            }
+        }
+
+        assert_eq!(db.col_1().get(&3)?, Some("toto".to_owned()),);
+
+        assert_eq!(db.col_2().get(&3)?, None,);
+
+        db.col_1_write().upsert(5, "tutu".to_owned())?;
+
+        {
+            let mut iter = db.col_1().iter(..);
+
+            assert_eq!(iter.next_res()?, Some((3, "toto".to_owned())));
+            assert_eq!(iter.next_res()?, Some((5, "tutu".to_owned())));
+            assert_eq!(iter.next_res()?, None);
+
+            let mut iter = db.col_1().iter(..).values().reverse();
+
+            assert_eq!(iter.next_res()?, Some("tutu".to_owned()));
+            assert_eq!(iter.next_res()?, Some("toto".to_owned()));
+            assert_eq!(iter.next_res()?, None);
+        }
+
+        db.col_1_write().upsert(7, "titi".to_owned())?;
+
+        let mut iter = db.col_1().iter(..).values().reverse().step_by(2);
+
+        assert_eq!(iter.next_res()?, Some("titi".to_owned()));
+        assert_eq!(iter.next_res()?, Some("toto".to_owned()));
+        assert_eq!(iter.next_res()?, None);
+
+        Ok(())
+    }
+}
diff --git a/rust-libs/tools/kv_typed/tests/test_mock.rs b/rust-libs/tools/kv_typed/tests/test_mock.rs
new file mode 100644
index 000000000..8f68164a2
--- /dev/null
+++ b/rust-libs/tools/kv_typed/tests/test_mock.rs
@@ -0,0 +1,54 @@
+#[cfg(feature = "mock")]
+mod tests {
+
+    use kv_typed::prelude::*;
+    use mockall::predicate::*;
+    use std::fmt::Debug;
+    use std::ops::{Bound, RangeFull};
+
+    db_schema!(
+        Test,
+        [["c1", col_1, u32, String], ["c2", col_2, String, u64],]
+    );
+
+    fn use_readable_db<DB: TestDbReadable>(db: &DB) -> KvResult<()> {
+        let col1_reader = db.col_1();
+        assert_eq!(col1_reader.count()?, 899);
+        assert_eq!(col1_reader.get(&42)?, Some("toto".to_owned()));
+        let mut iter = col1_reader.iter(..);
+        assert_eq!(iter.next_res()?, Some((42, "toto".to_owned())));
+        assert_eq!(iter.next_res()?, None);
+        Ok(())
+    }
+
+    #[test]
+    fn test_mock_db() -> KvResult<()> {
+        let mut db = MockTestDbReadable::new();
+        db.expect_col_1().times(1).returning(|| {
+            let mut col_1 = MockColRo::<Col1Event>::new();
+            col_1.expect_count().times(1).returning(|| Ok(899));
+            col_1
+                .expect_get()
+                .times(1)
+                .returning(|_| Ok(Some("toto".to_owned())));
+            col_1.expect_iter::<RangeFull>().times(1).returning(|_| {
+                let mut b_iter = MockBackendIter::new();
+                #[allow(clippy::string_lit_as_bytes)]
+                let mut items = vec![
+                    None,
+                    Some(Ok((vec![0u8, 0, 0, 42], "toto".as_bytes().to_vec()))),
+                ];
+                b_iter
+                    .expect_next()
+                    .times(2)
+                    .returning(move || items.pop().unwrap_or(None));
+                KvIter::new(b_iter, (Bound::Unbounded, Bound::Unbounded))
+            });
+            col_1
+        });
+
+        use_readable_db(&db)?;
+
+        Ok(())
+    }
+}
diff --git a/rust-libs/tools/kv_typed_code_gen/Cargo.toml b/rust-libs/tools/kv_typed_code_gen/Cargo.toml
new file mode 100644
index 000000000..6f09757d6
--- /dev/null
+++ b/rust-libs/tools/kv_typed_code_gen/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "kv_typed_code_gen"
+version = "0.1.0"
+authors = ["elois <c@elo.tf>"]
+description = "Macros to generate code for kv_typed users"
+repository = "https://git.duniter.org/nodes/typescript/duniter"
+keywords = ["database", "macro", "key", "sled"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+proc-macro = true
+path = "src/lib.rs"
+
+[dependencies]
+Inflector = "0.11.4"
+proc-macro2 = "1.0.20"
+quote = "1.0"
+syn = { version = "1.0.39", features = ["full"] }
diff --git a/rust-libs/tools/kv_typed_code_gen/src/col_schema.rs b/rust-libs/tools/kv_typed_code_gen/src/col_schema.rs
new file mode 100644
index 000000000..085d0839a
--- /dev/null
+++ b/rust-libs/tools/kv_typed_code_gen/src/col_schema.rs
@@ -0,0 +1,81 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(crate) struct ColSchema {
+    pub(crate) name_class: Ident,
+    pub(crate) name_snake: Ident,
+    pub(crate) path: LitStr,
+    pub(crate) key_ty: Ident,
+    pub(crate) value_ty: Ident,
+}
+
+impl From<ExprArray> for ColSchema {
+    fn from(expr_array: ExprArray) -> Self {
+        let mut expr_iter = expr_array.elems.into_iter();
+
+        let col_path = if let Some(Expr::Lit(ExprLit { lit, .. })) = expr_iter.next() {
+            if let Lit::Str(lit_str) = lit {
+                lit_str
+            } else {
+                panic!("Collection must be defined by array of one literal followed by three identifiers");
+            }
+        } else {
+            panic!(
+                "Collection must be defined by array of one literal followed by three identifiers"
+            );
+        };
+        let col_name = if let Some(Expr::Path(ExprPath { path, .. })) = expr_iter.next() {
+            path.get_ident()
+                .expect("Collection name must be a plain identifier")
+                .to_owned()
+        } else {
+            panic!(
+                "Collection must be defined by array of one literal followed by three identifiers"
+            );
+        };
+        let key_ty = if let Some(Expr::Path(ExprPath { path, .. })) = expr_iter.next() {
+            path.get_ident()
+                .expect("Collection key type must be a plain identifier")
+                .to_owned()
+        } else {
+            panic!(
+                "Collection must be defined by array of one literal followed by three identifiers"
+            );
+        };
+        let value_ty = if let Some(Expr::Path(ExprPath { path, .. })) = expr_iter.next() {
+            path.get_ident()
+                .expect("Collection value type must be a plain identifier")
+                .to_owned()
+        } else {
+            panic!(
+                "Collection must be defined by array of one literal followed by three identifiers"
+            );
+        };
+        if expr_iter.next().is_some() {
+            panic!(
+                "Collection must be defined by array of one literal followed by three identifiers"
+            );
+        }
+        ColSchema {
+            path: col_path,
+            name_class: format_ident!("{}", ident_to_class_case_string(&col_name)),
+            name_snake: format_ident!("{}", ident_to_snake_case_string(&col_name)),
+            key_ty,
+            value_ty,
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed_code_gen/src/db_readable.rs b/rust-libs/tools/kv_typed_code_gen/src/db_readable.rs
new file mode 100644
index 000000000..9773269a9
--- /dev/null
+++ b/rust-libs/tools/kv_typed_code_gen/src/db_readable.rs
@@ -0,0 +1,49 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[allow(clippy::too_many_arguments)]
+pub(crate) fn impl_db_readable(
+    db: &Ident,
+    db_ro: &Ident,
+    db_readable: &Ident,
+    col_name_class: &[Ident],
+    col_field: &[Ident],
+    col_event_type: &[Ident],
+    col_key_type: &[Ident],
+    col_value_type: &[Ident],
+) -> proc_macro2::TokenStream {
+    quote! {
+        pub trait #db_readable: Sized {
+            type Backend: Backend;
+            #(type #col_name_class: DbCollectionRo<Event=#col_event_type, K=#col_key_type, V=#col_value_type>;)*
+
+            #(fn #col_field(&self) -> Self::#col_name_class;)*
+        }
+        impl<B: Backend> #db_readable for #db<B> {
+            type Backend = B;
+            #(type #col_name_class = ColRo<B::Col, #col_event_type>;)*
+
+            #(fn #col_field(&self) -> Self::#col_name_class { self.collections.#col_field.to_ro().clone() })*
+        }
+        impl<B: Backend> #db_readable for #db_ro<B>{
+            type Backend = B;
+            #(type #col_name_class = ColRo<B::Col, #col_event_type>;)*
+
+            #(fn #col_field(&self) -> Self::#col_name_class { self.collections.#col_field.clone() })*
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed_code_gen/src/db_schema.rs b/rust-libs/tools/kv_typed_code_gen/src/db_schema.rs
new file mode 100644
index 000000000..c60ee621f
--- /dev/null
+++ b/rust-libs/tools/kv_typed_code_gen/src/db_schema.rs
@@ -0,0 +1,48 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+/// Parses the following syntax
+///
+/// DbVersion, [collection_name, ColKeyType, ColValueType]
+///
+pub(crate) struct DbSchema {
+    pub(crate) version: Ident,
+    pub(crate) collections: VecExprArray,
+}
+
+pub(crate) struct VecExprArray(pub(crate) Vec<ExprArray>);
+
+impl Parse for VecExprArray {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let tmp = Punctuated::<ExprArray, Token![,]>::parse_terminated(input)?;
+        Ok(VecExprArray(tmp.into_iter().collect()))
+    }
+}
+
+impl Parse for DbSchema {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let version: Ident = input.parse()?;
+        input.parse::<Token![,]>()?;
+        let collections_group: Group = input.parse()?;
+        let collections: VecExprArray = syn::parse(collections_group.stream().into())?;
+
+        Ok(DbSchema {
+            version,
+            collections,
+        })
+    }
+}
diff --git a/rust-libs/tools/kv_typed_code_gen/src/db_writable.rs b/rust-libs/tools/kv_typed_code_gen/src/db_writable.rs
new file mode 100644
index 000000000..01c7e157b
--- /dev/null
+++ b/rust-libs/tools/kv_typed_code_gen/src/db_writable.rs
@@ -0,0 +1,85 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[allow(clippy::too_many_arguments)]
+pub(crate) fn impl_db_writable(
+    db: &Ident,
+    db_ro: &Ident,
+    db_readable: &Ident,
+    db_writable: &Ident,
+    col_name_class: &[Ident],
+    col_field: &[Ident],
+    col_path: &[LitStr],
+    col_event_type: &[Ident],
+) -> proc_macro2::TokenStream {
+    let db_batch = format_ident!("{}Batch", db);
+    let db_cols_rw = format_ident!("{}ColsRw", db);
+    let col_method_rw: Vec<Ident> = col_field
+        .iter()
+        .map(|field_name| format_ident!("{}_write", field_name))
+        .collect();
+    quote! {
+        pub trait #db_writable: #db_readable {
+            type Backend: Backend;
+            type Batch;
+            #(type #col_name_class: DbCollectionRw;)*
+            type DbRo: Sized;
+
+            fn get_ro_handler(&self) -> Self::DbRo;
+            fn open(
+                backend_conf: <<Self as #db_writable>::Backend as kv_typed::backend::Backend>::Conf,
+            ) -> KvResult <Self>;
+            fn new_batch(&self) -> Self::Batch;
+            fn write_batch(&self, batch: Self::Batch) -> KvResult<()>;
+            #(fn #col_method_rw(&self) -> &Self::#col_name_class;)*
+        }
+        impl<B: Backend> #db_writable for #db<B> {
+            type Backend = B;
+            type Batch = #db_batch<B>;
+            #(type #col_name_class = ColRw<B::Col, #col_event_type>;)*
+            type DbRo = #db_ro<B>;
+
+            #[inline(always)]
+            fn get_ro_handler(&self) -> Self::DbRo {
+                #db_ro {
+                    collections: self.collections.to_ro(),
+                }
+            }
+            #[inline(always)]
+            fn new_batch(&self) -> Self::Batch {
+                <#db_batch::<B>>::default()
+            }
+            fn write_batch(&self, batch: Self::Batch) -> KvResult<()> {
+                #(self.collections.#col_field.write_batch(batch.#col_field)?;)*
+                Ok(())
+            }
+            fn open(
+                backend_conf: <<Self as #db_writable>::Backend as kv_typed::backend::Backend>::Conf,
+            ) -> KvResult <Self> {
+                let mut db = B::open(&backend_conf)?;
+                Ok(#db {
+                    collections: #db_cols_rw {
+                        #(#col_field: <ColRw<B::Col, #col_event_type>>::new(
+                            db.open_col(&backend_conf, #col_path)?
+                        ),)*
+                    },
+                })
+            }
+            #(fn #col_method_rw(&self) -> &ColRw<B::Col, #col_event_type> { &self.collections.#col_field })*
+        }
+    }
+}
diff --git a/rust-libs/tools/kv_typed_code_gen/src/lib.rs b/rust-libs/tools/kv_typed_code_gen/src/lib.rs
new file mode 100644
index 000000000..b93df173f
--- /dev/null
+++ b/rust-libs/tools/kv_typed_code_gen/src/lib.rs
@@ -0,0 +1,236 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Strongly typed key-value storage
+//!
+//! Macro to generate database code from their schema
+
+mod col_schema;
+mod db_readable;
+mod db_schema;
+mod db_writable;
+
+use inflector::Inflector;
+use proc_macro::TokenStream;
+use proc_macro2::Group;
+use quote::{format_ident, quote};
+use syn::{
+    parse::{Parse, ParseStream, Result},
+    parse_macro_input,
+    punctuated::Punctuated,
+    Expr, ExprArray, ExprLit, ExprPath, Ident, Lit, LitStr, Token,
+};
+
+use crate::col_schema::ColSchema;
+use crate::db_readable::impl_db_readable;
+use crate::db_schema::DbSchema;
+use crate::db_writable::impl_db_writable;
+
+fn ident_to_class_case_string(ident: &Ident) -> String {
+    Inflector::to_class_case(&format!("{}", ident))
+}
+fn ident_to_snake_case_string(ident: &Ident) -> String {
+    Inflector::to_snake_case(&format!("{}", ident))
+}
+
+#[allow(clippy::let_and_return)]
+#[proc_macro]
+pub fn db_schema(input: TokenStream) -> TokenStream {
+    let db_schema = parse_macro_input!(input as DbSchema);
+
+    let version = db_schema.version;
+    let name = ident_to_snake_case_string(&version);
+    let collections: Vec<ColSchema> = db_schema
+        .collections
+        .0
+        .into_iter()
+        .map(Into::into)
+        .collect();
+
+    let db = format_ident!("{}Db", version);
+    let db_batch = format_ident!("{}Batch", db);
+    let db_ro = format_ident!("{}Ro", db);
+    let db_cols_ro = format_ident!("{}ColsRo", db);
+    let db_cols_rw = format_ident!("{}ColsRw", db);
+
+    let col_path: Vec<LitStr> = collections.iter().map(|col| col.path.clone()).collect();
+    let col_field: Vec<Ident> = collections
+        .iter()
+        .map(|col| col.name_snake.clone())
+        .collect();
+    let col_event_type: Vec<Ident> = collections
+        .iter()
+        .map(|col| format_ident!("{}Event", &col.name_class))
+        .collect();
+    let col_key_type: Vec<Ident> = collections.iter().map(|col| col.key_ty.clone()).collect();
+    let col_name_class: Vec<Ident> = collections
+        .iter()
+        .map(|col| col.name_class.clone())
+        .collect();
+    let col_name_class_rw: Vec<Ident> = collections
+        .iter()
+        .map(|col| format_ident!("{}Rw", &col.name_class))
+        .collect();
+    let col_value_type: Vec<Ident> = collections.iter().map(|col| col.value_ty.clone()).collect();
+
+    /*let define_each_db_collection: Vec<proc_macro2::TokenStream> =
+    collections.iter().map(define_db_collection).collect();*/
+    let db_readable = format_ident!("{}Readable", db);
+    let impl_db_readable = impl_db_readable(
+        &db,
+        &db_ro,
+        &db_readable,
+        &col_name_class,
+        &col_field,
+        &col_event_type,
+        &col_key_type,
+        &col_value_type,
+    );
+    let db_writable = format_ident!("{}Writable", db);
+    let impl_db_writable = impl_db_writable(
+        &db,
+        &db_ro,
+        &db_readable,
+        &db_writable,
+        &col_name_class_rw,
+        &col_field,
+        &col_path,
+        &col_event_type,
+    );
+
+    let expanded = quote! {
+        pub use __inner::{#db, #db_batch, #db_ro, #db_readable, #db_writable};
+        #(
+            // Define each collection event type
+            #[derive(Debug, PartialEq)]
+            pub enum #col_event_type {
+                Upsert { key: #col_key_type, value: #col_value_type },
+                Remove { key: #col_key_type },
+            }
+            impl EventTrait for #col_event_type {
+                type K = #col_key_type;
+                type V = #col_value_type;
+
+                fn upsert(k: Self::K, v: Self::V) -> Self { Self::Upsert { key: k, value: v, } }
+                fn remove(k: Self::K) -> Self { Self::Remove { key: k } }
+            }
+        )*
+        // Mocks
+        #[cfg(feature = "mock")]
+        mockall::mock! {
+            pub #db_readable {}
+
+            trait #db_readable {
+                type Backend = kv_typed::backend::mock::MockBackend;
+                #(type #col_name_class = kv_typed::prelude::MockColRo<#col_event_type>;)*
+
+                #(fn #col_field(&self) -> kv_typed::prelude::MockColRo<#col_event_type>;)*
+            }
+        }
+        // Inner module used to hide internals types that must not be exposed on public api
+        mod __inner {
+            use super::*;
+            // DbCollections
+            #[derive(Clone, Debug)]
+            pub struct #db_cols_ro<BC: BackendCol> {
+                #(#col_field: ColRo<BC, #col_event_type>,)*
+            }
+            #[derive(Clone, Debug)]
+            pub struct #db_cols_rw<BC: BackendCol> {
+                #(#col_field: ColRw<BC, #col_event_type>,)*
+            }
+            impl<BC: BackendCol> #db_cols_rw<BC> {
+                fn to_ro(&self) -> #db_cols_ro<BC> {
+                    #db_cols_ro {
+                        #(#col_field: self.#col_field.to_ro().clone(),)*
+                    }
+                }
+            }
+            // Db
+            #[derive(Debug)]
+            pub struct #db<B: Backend> {
+                collections: #db_cols_rw<B::Col>,
+            }
+            impl<B: Backend> #db<B> {
+                pub const NAME: &'static str = #name;
+            }
+            impl<B: Backend> Clone for #db<B> {
+                fn clone(&self) -> Self {
+                    #db {
+                        collections: self.collections.clone(),
+                    }
+                }
+            }
+            #[cfg(feature = "explorer")]
+            use kv_typed::prelude::StringErr;
+            #[cfg(feature = "explorer")]
+            impl<B: Backend> kv_typed::explorer::DbExplorable for #db<B> {
+                fn explore<'a>(
+                    &self,
+                    collection_name: &str,
+                    action: kv_typed::explorer::ExplorerAction<'a>,
+                    stringify_json_value: fn(serde_json::Value) -> serde_json::Value,
+                ) -> KvResult<std::result::Result<kv_typed::explorer::ExplorerActionResponse, StringErr>> {
+                    #( if stringify!(#col_field) == collection_name {
+                        return action.exec(&self.collections.#col_field, stringify_json_value);
+                    } )*
+                    Ok(Err(StringErr(format!("collection '{}' not exist in database '{}'.", collection_name, stringify!(#db)))))
+                }
+                fn list_collections(&self) -> Vec<(&'static str, &'static str, &'static str)> {
+                    vec![
+                        #((stringify!(#col_field), stringify!(#col_key_type), stringify!(#col_value_type)),)*
+                    ]
+                }
+            }
+            pub struct #db_batch<B: Backend> {
+                #(#col_field: Batch<B::Col, ColRw<B::Col, #col_event_type>>,)*
+            }
+            impl<B: Backend> Default for #db_batch<B> {
+                fn default() -> Self {
+                    #db_batch {
+                        #(#col_field: Batch::default(),)*
+                    }
+                }
+            }
+            impl<B: Backend> #db_batch<B> {
+                #(pub fn #col_field(&mut self) -> &mut Batch<B::Col, ColRw<B::Col, #col_event_type>> { &mut self.#col_field })*
+            }
+            // DbRo
+            #[derive(Debug)]
+            pub struct #db_ro<B: Backend> {
+                collections: #db_cols_ro<B::Col>,
+            }
+            impl<B: Backend> #db_ro<B> {
+                pub const NAME: &'static str = #name;
+            }
+            impl<B: Backend> Clone for #db_ro<B> {
+                fn clone(&self) -> Self {
+                    #db_ro {
+                        collections: self.collections.clone(),
+                    }
+                }
+            }
+            // Read operations
+            #impl_db_readable
+            // Write operations
+            #impl_db_writable
+        }
+    };
+
+    //let tokens = TokenStream::from(expanded);
+    //eprintln!("TOKENS: {:#}", tokens);
+
+    TokenStream::from(expanded)
+}
-- 
GitLab