Repository: swimos/swim-rust (language: rust)

Issue:
Make persistence optional in the server
Persistence using `rocksdb` should be optional in the server and disabled by default. This will also allow for the `test_server` to depend on a lighter version of the server and improve compile times.


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 811ef552b..b82bf3b23 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ members = [
     "swim_server",
     "swim_warp",
     "stm",
+    "swim_store",
     "swim_utilities",
     "swim_utilities/swim_*",
 ]
diff --git a/swim_server/Cargo.toml b/swim_server/Cargo.toml
index ffe0dfa8b..bda9356d3 100644
--- a/swim_server/Cargo.toml
+++ b/swim_server/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2018"
 [features]
 default = []
 debug = ["log_verbose"]
+persistence = ["server_store/rocks"]
 log_verbose = []
 trust-dns = ["trust-dns-resolver"]
 
@@ -17,6 +18,14 @@ tracing = { version = "0.1.13", features = ["max_level_debug"] }
 tracing = "0.1.16"
 
 [dependencies]
+swim_store = { path = "../swim_store" }
+server_store = { path = "server_store"}
+swim_common = { path = "../swim_common", features = ["tls"] }
+swim_warp = { path = "../swim_warp" }
+swim_utilities = { path = "../swim_utilities", features = ["errors", "sync", "time", "text", "io"] }
+stringify_attr = { path = "../macro_utilities/stringify_attr" }
+swim_runtime = { path = "../swim_runtime" }
+stm = { path = "../stm" }
 either = "1.5.3"
 futures = "0.3.4"
 futures-util = "0.3.4"
@@ -28,12 +37,6 @@ pin-utils = "0.1.0"
 tokio = { version = "1.1.1", features = ["rt", "net"] }
 tokio-stream = { version = "0.1.2", features = ["sync"] }
 url = "2.1.1"
-swim_common = { path = "../swim_common", features = ["tls"] }
-swim_warp = { path = "../swim_warp" }
-swim_utilities = { path = "../swim_utilities", features = ["errors", "sync", "time", "text"] }
-stringify_attr = { path = "../macro_utilities/stringify_attr" }
-swim_runtime = { path = "../swim_runtime" }
-stm = { path = "../stm" }
 tracing-futures = "0.2.4"
 agent_derive = { path = "agent_derive" }
 trybuild = "1.0.48"
@@ -45,11 +48,7 @@ lazy_static = "1.4.0"
 percent-encoding = "2.1.0"
 thiserror = "1.0"
 crossbeam-queue = "0.3"
-store = { path = "store" }
 serde = { version = "1.0.124", features = ["derive", "rc"] }
-bincode = "1.3.2"
-rocksdb = "0.16.0"
-tempdir = "0.3.7"
 
 [dev-dependencies]
 tokio = { version = "1.1.1", features = ["rt-multi-thread", "macros", "test-util"] }
diff --git a/swim_server/agent_derive/src/agent.rs b/swim_server/agent_derive/src/agent.rs
index 44c9e3a10..cf2044e36 100644
--- a/swim_server/agent_derive/src/agent.rs
+++ b/swim_server/agent_derive/src/agent.rs
@@ -136,11 +136,11 @@ pub fn derive_swim_agent(input: DeriveInput) -> Result<TokenStream, TokenStream>
         use std::collections::HashMap;
         use std::boxed::Box;
 
-        use swim_server::agent::{LaneTasks, SwimAgent, AgentContext, IoPair, StoreIo, LaneIo};
+        use swim_server::store::{StoreIo, LaneNoStore};
+        use swim_server::agent::{LaneTasks, SwimAgent, AgentContext, IoPair, LaneIo};
         use swim_server::agent::lane::channels::AgentExecutionConfig;
         use swim_server::agent::context::AgentExecutionContext;
         use swim_server::agent::lane::lifecycle::LaneLifecycle;
-        use swim_server::agent::LaneNoStore;
 
         #[automatically_derived]
         impl SwimAgent<#config_type> for #agent_name {
@@ -155,7 +155,7 @@ pub fn derive_swim_agent(input: DeriveInput) -> Result<TokenStream, TokenStream>
             )
                 where
                     Context: AgentContext<Self> + AgentExecutionContext + Send + Sync + 'static,
-                    Store: swim_server::agent::store::NodeStore,
+                    Store: swim_server::store::NodeStore,
             {
                 let mut io_map: HashMap<String, IoPair<Box<dyn LaneIo<Context>>, Box<dyn StoreIo>>> = HashMap::new();
 
diff --git a/swim_server/server_store/Cargo.toml b/swim_server/server_store/Cargo.toml
new file mode 100644
index 000000000..b6cdb0c40
--- /dev/null
+++ b/swim_server/server_store/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "server_store"
+version = "0.1.0"
+edition = "2018"
+
+[features]
+default = []
+rocks = ["rocksdb", "swim_store/rocks"]
+
+[dependencies]
+swim_store = { path = "../../swim_store" }
+swim_common = { path = "../../swim_common" }
+rocksdb = { version = "0.16.0", optional = true }
+serde = { version = "1.0.124", features = ["derive"] }
+swim_utilities = { path = "../../swim_utilities", features = ["io", "trigger"] }
+tokio = { version = "1.1.1", features = ["sync"] }
+futures = "0.3.4"
+futures-util = "0.3.4"
+
+[dev-dependencies]
+tokio = { version = "1.1.1", features = ["rt-multi-thread", "macros", "test-util"] }
\ No newline at end of file
diff --git a/swim_server/src/agent/lane/store/error.rs b/swim_server/server_store/src/agent/lane/error.rs
similarity index 99%
rename from swim_server/src/agent/lane/store/error.rs
rename to swim_server/server_store/src/agent/lane/error.rs
index 48728dd08..f0e80703a 100644
--- a/swim_server/src/agent/lane/store/error.rs
+++ b/swim_server/server_store/src/agent/lane/error.rs
@@ -13,8 +13,8 @@
 // limitations under the License.
 
 use std::fmt::{Display, Formatter};
-use store::StoreError;
 use swim_common::model::time::Timestamp;
+use swim_store::StoreError;
 
 #[derive(Debug)]
 pub struct StoreTaskError {
diff --git a/swim_server/src/agent/lane/store/mod.rs b/swim_server/server_store/src/agent/lane/mod.rs
similarity index 95%
rename from swim_server/src/agent/lane/store/mod.rs
rename to swim_server/server_store/src/agent/lane/mod.rs
index f278340c7..4219fed25 100644
--- a/swim_server/src/agent/lane/store/mod.rs
+++ b/swim_server/server_store/src/agent/lane/mod.rs
@@ -12,19 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::lane::store::error::{LaneStoreErrorReport, StoreErrorHandler};
-use futures::future::{ready, BoxFuture};
-
 #[cfg(test)]
-mod tests;
+pub use tests::store_err_partial_eq;
 
-use futures::FutureExt;
 #[cfg(test)]
-pub use tests::store_err_partial_eq;
+mod tests;
 
 pub mod error;
+mod model;
 pub mod task;
 
+use crate::agent::lane::error::{LaneStoreErrorReport, StoreErrorHandler};
+use futures::future::{ready, BoxFuture};
+use futures::FutureExt;
+
+pub use model::*;
+
 /// Deferred lane store IO attachment task.
 pub trait StoreIo: Send + 'static {
     /// Attempt to attach the lane event stream to the provided node store.
diff --git a/swim_server/src/agent/lane/model/map/map_store/io.rs b/swim_server/server_store/src/agent/lane/model/map/io.rs
similarity index 82%
rename from swim_server/src/agent/lane/model/map/map_store/io.rs
rename to swim_server/server_store/src/agent/lane/model/map/io.rs
index 7c9f15253..7698e39d1 100644
--- a/swim_server/src/agent/lane/model/map/map_store/io.rs
+++ b/swim_server/server_store/src/agent/lane/model/map/io.rs
@@ -12,19 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::lane::model::map::map_store::MapDataModel;
-use crate::agent::lane::model::map::MapLaneEvent;
-use crate::agent::lane::store::error::{LaneStoreErrorReport, StoreErrorHandler, StoreTaskError};
-use crate::agent::lane::store::StoreIo;
-use crate::agent::store::NodeStore;
+use crate::agent::lane::error::{LaneStoreErrorReport, StoreErrorHandler, StoreTaskError};
+use crate::agent::lane::model::map::{MapDataModel, MapStoreEvent};
+use crate::agent::lane::StoreIo;
+use crate::agent::NodeStore;
 use futures::future::BoxFuture;
 use futures::{Stream, StreamExt};
 use serde::de::DeserializeOwned;
 use serde::Serialize;
 use std::fmt::Debug;
-use store::StoreError;
 use swim_common::form::Form;
 use swim_common::model::time::Timestamp;
+use swim_store::StoreError;
 
 /// Map lane store IO task.
 ///
@@ -54,7 +53,7 @@ impl<K, V, Events, Store> MapLaneStoreIo<K, V, Events, Store> {
 impl<Store, Events, K, V> StoreIo for MapLaneStoreIo<K, V, Events, Store>
 where
     Store: NodeStore,
-    Events: Stream<Item = MapLaneEvent<K, V>> + Unpin + Send + Sync + 'static,
+    Events: Stream<Item = MapStoreEvent<K, V>> + Unpin + Send + Sync + 'static,
     K: Form + Debug + Send + Sync + Serialize + DeserializeOwned + 'static,
     V: Debug + Send + Sync + Serialize + DeserializeOwned + 'static,
 {
@@ -67,12 +66,11 @@ where
 
             while let Some(event) = events.next().await {
                 match event {
-                    MapLaneEvent::Checkpoint(_) => {}
-                    MapLaneEvent::Clear => on_event(&mut error_handler, || model.clear())?,
-                    MapLaneEvent::Update(key, value) => {
+                    MapStoreEvent::Clear => on_event(&mut error_handler, || model.clear())?,
+                    MapStoreEvent::Update(key, value) => {
                         on_event(&mut error_handler, || model.put(&key, &value))?
                     }
-                    MapLaneEvent::Remove(key) => {
+                    MapStoreEvent::Remove(key) => {
                         on_event(&mut error_handler, || model.delete(&key))?
                     }
                 }
diff --git a/swim_server/src/agent/lane/model/map/map_store/mod.rs b/swim_server/server_store/src/agent/lane/model/map/mod.rs
similarity index 89%
rename from swim_server/src/agent/lane/model/map/map_store/mod.rs
rename to swim_server/server_store/src/agent/lane/model/map/mod.rs
index 1813fdf08..f5f87ba9f 100644
--- a/swim_server/src/agent/lane/model/map/map_store/mod.rs
+++ b/swim_server/server_store/src/agent/lane/model/map/mod.rs
@@ -12,21 +12,33 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::store::NodeStore;
-use crate::store::StoreKey;
 use serde::de::DeserializeOwned;
 use serde::Serialize;
 use std::marker::PhantomData;
-use store::{deserialize, serialize, serialize_then, StoreError};
+use std::sync::Arc;
 
 #[cfg(test)]
 mod tests;
 
 mod io;
+use crate::agent::NodeStore;
+use crate::server::StoreKey;
 pub use io::MapLaneStoreIo;
+use swim_store::{deserialize, serialize, serialize_then, StoreError};
 
 const INCONSISTENT_DB: &str = "Missing key or value in store";
 
+/// A single event that occurred during a transaction.
+#[derive(Debug, PartialEq, Eq)]
+pub enum MapStoreEvent<K, V> {
+    /// The map as cleared.
+    Clear,
+    /// An entry was updated.
+    Update(K, Arc<V>),
+    /// An entry was removed.
+    Remove(K),
+}
+
 pub struct MapDataModel<D, K, V> {
     delegate: D,
     lane_id: u64,
diff --git a/swim_server/server_store/src/agent/lane/model/mod.rs b/swim_server/server_store/src/agent/lane/model/mod.rs
new file mode 100644
index 000000000..dfc404b6d
--- /dev/null
+++ b/swim_server/server_store/src/agent/lane/model/mod.rs
@@ -0,0 +1,16 @@
+// Copyright 2015-2021 SWIM.AI inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub mod map;
+pub mod value;
diff --git a/swim_server/src/agent/lane/model/value/value_store/io.rs b/swim_server/server_store/src/agent/lane/model/value/io.rs
similarity index 91%
rename from swim_server/src/agent/lane/model/value/value_store/io.rs
rename to swim_server/server_store/src/agent/lane/model/value/io.rs
index 119754846..f196b5bef 100644
--- a/swim_server/src/agent/lane/model/value/value_store/io.rs
+++ b/swim_server/server_store/src/agent/lane/model/value/io.rs
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::lane::store::error::{LaneStoreErrorReport, StoreErrorHandler, StoreTaskError};
-use crate::agent::lane::store::StoreIo;
-use crate::agent::model::value::value_store::ValueDataModel;
-use crate::agent::store::NodeStore;
+use crate::agent::lane::error::{LaneStoreErrorReport, StoreErrorHandler, StoreTaskError};
+use crate::agent::lane::model::value::ValueDataModel;
+use crate::agent::lane::StoreIo;
+use crate::agent::NodeStore;
 use futures::future::BoxFuture;
 use futures::{Stream, StreamExt};
 use serde::de::DeserializeOwned;
diff --git a/swim_server/src/agent/lane/model/value/value_store/mod.rs b/swim_server/server_store/src/agent/lane/model/value/mod.rs
similarity index 94%
rename from swim_server/src/agent/lane/model/value/value_store/mod.rs
rename to swim_server/server_store/src/agent/lane/model/value/mod.rs
index e2d53db2a..7bbe77413 100644
--- a/swim_server/src/agent/lane/model/value/value_store/mod.rs
+++ b/swim_server/server_store/src/agent/lane/model/value/mod.rs
@@ -12,17 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::store::NodeStore;
-use crate::store::StoreKey;
 use serde::de::DeserializeOwned;
 use serde::Serialize;
 use std::marker::PhantomData;
-use store::{deserialize, serialize_then, StoreError};
+use swim_store::{deserialize, serialize_then, StoreError};
 
 #[cfg(test)]
 mod tests;
 
 mod io;
+use crate::agent::NodeStore;
+use crate::server::StoreKey;
 pub use io::ValueLaneStoreIo;
 
 pub struct ValueDataModel<D, V> {
diff --git a/swim_server/src/agent/lane/store/task.rs b/swim_server/server_store/src/agent/lane/task.rs
similarity index 86%
rename from swim_server/src/agent/lane/store/task.rs
rename to swim_server/server_store/src/agent/lane/task.rs
index 68cf50871..622105fbd 100644
--- a/swim_server/src/agent/lane/store/task.rs
+++ b/swim_server/server_store/src/agent/lane/task.rs
@@ -1,11 +1,25 @@
-use crate::agent::lane::store::error::{LaneStoreErrorReport, StoreErrorHandler};
-use crate::agent::lane::store::StoreIo;
-use crate::agent::store::NodeStore;
+// Copyright 2015-2021 SWIM.AI inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::agent::lane::error::{LaneStoreErrorReport, StoreErrorHandler};
+use crate::agent::lane::StoreIo;
+use crate::agent::NodeStore;
 use futures::stream::FuturesUnordered;
 use futures::{FutureExt, StreamExt};
 use std::collections::HashMap;
 use std::fmt::{Display, Formatter};
-use store::EngineInfo;
+use swim_store::EngineInfo;
 use swim_utilities::trigger;
 
 /// Aggregated node store errors.
diff --git a/swim_server/src/agent/store/mock/mod.rs b/swim_server/server_store/src/agent/mock/mod.rs
similarity index 90%
rename from swim_server/src/agent/store/mock/mod.rs
rename to swim_server/server_store/src/agent/mock/mod.rs
index c56e69ef6..36196a73f 100644
--- a/swim_server/src/agent/store/mock/mod.rs
+++ b/swim_server/server_store/src/agent/mock/mod.rs
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::store::{NodeStore, SwimNodeStore};
-use crate::plane::store::mock::MockPlaneStore;
-use crate::store::{StoreEngine, StoreKey};
-use store::{EngineInfo, StoreError};
+use crate::agent::{NodeStore, SwimNodeStore};
+use crate::plane::mock::MockPlaneStore;
+use crate::server::{StoreEngine, StoreKey};
+use swim_store::{EngineInfo, StoreError};
 
 #[derive(Clone, Debug)]
 pub struct MockNodeStore {
diff --git a/swim_server/src/agent/store/mod.rs b/swim_server/server_store/src/agent/mod.rs
similarity index 97%
rename from swim_server/src/agent/store/mod.rs
rename to swim_server/server_store/src/agent/mod.rs
index 43fdf6215..c2f10a843 100644
--- a/swim_server/src/agent/store/mod.rs
+++ b/swim_server/server_store/src/agent/mod.rs
@@ -12,16 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+pub mod lane;
+pub mod mock;
+
 use std::fmt::{Debug, Formatter};
 use std::sync::Arc;
 
-use store::{EngineInfo, StoreError};
+use crate::plane::PlaneStore;
+use crate::server::{StoreEngine, StoreKey};
 use swim_common::model::text::Text;
-
-use crate::plane::store::PlaneStore;
-use crate::store::{StoreEngine, StoreKey};
-
-pub mod mock;
+use swim_store::{EngineInfo, StoreError};
 
 /// A trait for defining store engines which open stores for nodes.
 ///
diff --git a/swim_server/server_store/src/lib.rs b/swim_server/server_store/src/lib.rs
new file mode 100644
index 000000000..2041407ea
--- /dev/null
+++ b/swim_server/server_store/src/lib.rs
@@ -0,0 +1,23 @@
+// Copyright 2015-2021 SWIM.AI inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub mod agent;
+pub mod plane;
+mod server;
+pub use server::*;
+
+pub mod engine {
+    #[cfg(feature = "rocks")]
+    pub use swim_store::rocks;
+}
diff --git a/swim_server/src/plane/store/mock.rs b/swim_server/server_store/src/plane/mock.rs
similarity index 88%
rename from swim_server/src/plane/store/mock.rs
rename to swim_server/server_store/src/plane/mock.rs
index 58827b42c..0c873e3ab 100644
--- a/swim_server/src/plane/store/mock.rs
+++ b/swim_server/server_store/src/plane/mock.rs
@@ -12,13 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::store::mock::MockNodeStore;
-use crate::agent::store::SwimNodeStore;
-use crate::plane::store::PlaneStore;
-use crate::store::{StoreEngine, StoreKey};
-use store::keyspaces::{Keyspace, KeyspaceResolver};
-use store::{EngineInfo, StoreError};
+use crate::agent::mock::MockNodeStore;
+use crate::agent::SwimNodeStore;
+use crate::plane::PlaneStore;
+use crate::server::{StoreEngine, StoreKey};
 use swim_common::model::text::Text;
+use swim_store::{EngineInfo, Keyspace, KeyspaceResolver, StoreError};
 
 #[derive(Clone, Debug)]
 pub struct MockPlaneStore;
diff --git a/swim_server/src/plane/store/mod.rs b/swim_server/server_store/src/plane/mod.rs
similarity index 95%
rename from swim_server/src/plane/store/mod.rs
rename to swim_server/server_store/src/plane/mod.rs
index fb6447a5a..bef34560c 100644
--- a/swim_server/src/plane/store/mod.rs
+++ b/swim_server/server_store/src/plane/mod.rs
@@ -18,14 +18,13 @@ use std::io;
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
-use store::{serialize, EngineInfo, Store, StoreError};
 use swim_common::model::text::Text;
+use swim_store::{serialize, EngineInfo, Store, StoreError};
 
-use crate::agent::store::{NodeStore, SwimNodeStore};
-use crate::store::keystore::KeyStore;
-use crate::store::{KeyspaceName, StoreEngine, StoreKey};
-use store::engines::StoreBuilder;
-use store::keyspaces::Keyspaces;
+use crate::agent::{NodeStore, SwimNodeStore};
+use crate::server::keystore::KeyStore;
+use crate::server::{KeyspaceName, StoreEngine, StoreKey};
+use swim_store::{Keyspaces, StoreBuilder};
 
 pub mod mock;
 
@@ -212,7 +211,7 @@ impl<D> SwimPlaneStore<D>
 where
     D: Store,
 {
-    pub(crate) fn new<I: Into<Text>>(
+    pub fn new<I: Into<Text>>(
         plane_name: I,
         delegate: Arc<D>,
         keystore: KeyStore<D>,
diff --git a/swim_server/src/store/keystore.rs b/swim_server/server_store/src/server/keystore.rs
similarity index 78%
rename from swim_server/src/store/keystore.rs
rename to swim_server/server_store/src/server/keystore.rs
index 16330ff7c..d56ecff87 100644
--- a/swim_server/src/store/keystore.rs
+++ b/swim_server/server_store/src/server/keystore.rs
@@ -12,11 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::store::KeyspaceName;
+use crate::server::KeyspaceName;
 use std::sync::atomic::{AtomicU64, Ordering};
 use std::sync::Arc;
-use store::keyspaces::KeyspaceByteEngine;
-use store::{deserialize, serialize, MergeOperands, StoreError};
+use swim_store::{deserialize, deserialize_key, serialize, KeyspaceByteEngine, StoreError};
 use tokio::sync::oneshot;
 
 pub type KeyRequest = (String, oneshot::Sender<u64>);
@@ -24,9 +23,8 @@ pub type KeyRequest = (String, oneshot::Sender<u64>);
 /// The lane keyspace's counter key.
 pub const COUNTER_KEY: &str = "counter";
 pub const COUNTER_BYTES: &[u8] = COUNTER_KEY.as_bytes();
-const DESERIALIZATION_FAILURE: &str = "Failed to deserialize key";
-const SERIALIZATION_FAILURE: &str = "Failed to serialize key";
 pub const INCONSISTENT_KEYSPACE: &str = "Inconsistent keyspace";
+
 const INIT_FAILURE: &str = "Failed to initialise keystore";
 
 /// The prefix that all lane identifiers in the counter keyspace will be prefixed by.
@@ -89,42 +87,50 @@ impl<D: KeyspaceByteEngine> KeyStore<D> {
     }
 }
 
-fn deserialize_key<B: AsRef<[u8]>>(bytes: B) -> Result<u64, StoreError> {
-    bincode::deserialize::<u64>(bytes.as_ref()).map_err(|e| StoreError::Decoding(e.to_string()))
-}
-
 pub fn format_key<I: ToString>(uri: I) -> String {
     format!("{}/{}", LANE_PREFIX, uri.to_string())
 }
 
-#[allow(clippy::unnecessary_wraps)]
-pub(crate) fn incrementing_merge_operator(
-    _new_key: &[u8],
-    existing_value: Option<&[u8]>,
-    operands: &mut MergeOperands,
-) -> Option<Vec<u8>> {
-    let mut value = match existing_value {
-        Some(bytes) => deserialize_key(bytes).expect(DESERIALIZATION_FAILURE),
-        None => INITIAL,
-    };
+#[cfg(feature = "rocks")]
+pub mod rocks {
+    use crate::server::keystore::INITIAL;
+    use rocksdb::MergeOperands;
+    use swim_store::{deserialize_key, serialize};
+
+    #[cfg(feature = "rocks")]
+    const DESERIALIZATION_FAILURE: &str = "Failed to deserialize key";
+    #[cfg(feature = "rocks")]
+    const SERIALIZATION_FAILURE: &str = "Failed to serialize key";
+
+    #[cfg(feature = "rocks")]
+    #[allow(clippy::unnecessary_wraps)]
+    pub fn incrementing_merge_operator(
+        _new_key: &[u8],
+        existing_value: Option<&[u8]>,
+        operands: &mut MergeOperands,
+    ) -> Option<Vec<u8>> {
+        let mut value = match existing_value {
+            Some(bytes) => deserialize_key(bytes).expect(DESERIALIZATION_FAILURE),
+            None => INITIAL,
+        };
 
-    for op in operands {
-        let deserialized = deserialize_key(op).expect(DESERIALIZATION_FAILURE);
-        value += deserialized;
+        for op in operands {
+            let deserialized = deserialize_key(op).expect(DESERIALIZATION_FAILURE);
+            value += deserialized;
+        }
+        Some(serialize(&value).expect(SERIALIZATION_FAILURE))
     }
-    Some(serialize(&value).expect(SERIALIZATION_FAILURE))
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::store::keystore::{
-        deserialize_key, format_key, KeyStore, KeyspaceName, COUNTER_BYTES, COUNTER_KEY,
-        INCONSISTENT_KEYSPACE,
+    use crate::server::keystore::{
+        format_key, KeyStore, COUNTER_BYTES, COUNTER_KEY, INCONSISTENT_KEYSPACE,
     };
-    use crate::store::mock::MockStore;
+    use crate::server::mock::MockStore;
+    use crate::server::KeyspaceName;
     use std::sync::Arc;
-    use store::deserialize;
-    use store::keyspaces::{Keyspace, KeyspaceByteEngine};
+    use swim_store::{deserialize, deserialize_key, Keyspace, KeyspaceByteEngine};
 
     fn keyspaces() -> Vec<String> {
         vec![
diff --git a/swim_server/src/store/mock.rs b/swim_server/server_store/src/server/mock.rs
similarity index 95%
rename from swim_server/src/store/mock.rs
rename to swim_server/server_store/src/server/mock.rs
index 24f3a234c..6baf8066b 100644
--- a/swim_server/src/store/mock.rs
+++ b/swim_server/server_store/src/server/mock.rs
@@ -16,9 +16,8 @@ use std::collections::hash_map::Entry;
 use std::collections::HashMap;
 use std::sync::Mutex;
 
-use crate::store::keystore::STEP;
-use store::keyspaces::{Keyspace, KeyspaceByteEngine};
-use store::{deserialize, serialize, StoreError};
+use crate::server::keystore::STEP;
+use swim_store::{deserialize, serialize, Keyspace, KeyspaceByteEngine, StoreError};
 
 pub struct MockStore {
     values: Mutex<HashMap<String, HashMap<Vec<u8>, Vec<u8>>>>,
diff --git a/swim_server/src/store/mod.rs b/swim_server/server_store/src/server/mod.rs
similarity index 92%
rename from swim_server/src/store/mod.rs
rename to swim_server/server_store/src/server/mod.rs
index 622b04faf..0864645c0 100644
--- a/swim_server/src/store/mod.rs
+++ b/swim_server/server_store/src/server/mod.rs
@@ -12,27 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+pub mod keystore;
 #[cfg(test)]
 pub mod mock;
 
-pub(crate) mod fs;
-pub mod keystore;
-mod nostore;
-mod rocks;
+#[cfg(feature = "rocks")]
+pub mod rocks;
 
-pub use rocks::{default_db_opts, default_keyspaces, RocksDatabase, RocksOpts};
+use serde::{Deserialize, Serialize};
 use std::fmt::{Debug, Formatter};
 use std::io;
 use std::path::PathBuf;
-use store::engines::StoreBuilder;
-use store::StoreError;
-
-use serde::{Deserialize, Serialize};
-
-use crate::store::fs::Dir;
-use store::keyspaces::{Keyspace, Keyspaces};
+use swim_store::{Keyspace, Keyspaces, StoreBuilder, StoreError};
 
-use crate::plane::store::{open_plane, PlaneStore, SwimPlaneStore};
+use crate::plane::{open_plane, PlaneStore, SwimPlaneStore};
+pub use swim_store::nostore::NoStore;
+use swim_utilities::io::fs::Dir;
 
 /// Unique lane identifier keyspace. The name is `default` as either the Rust RocksDB crate or
 /// Rocks DB itself has an issue in using merge operators under a non-default column family.
@@ -142,10 +137,11 @@ where
     }
 }
 
-impl ServerStore<RocksOpts> {
-    pub fn transient_default(prefix: &str) -> io::Result<ServerStore<RocksOpts>> {
-        let db_opts = default_db_opts();
-        let keyspaces = default_keyspaces();
+#[cfg(feature = "rocks")]
+impl ServerStore<rocks::RocksOpts> {
+    pub fn transient_default(prefix: &str) -> io::Result<ServerStore<rocks::RocksOpts>> {
+        let db_opts = rocks::default_db_opts();
+        let keyspaces = rocks::default_keyspaces();
 
         Self::transient(db_opts, keyspaces, prefix)
     }
diff --git a/swim_server/src/store/rocks.rs b/swim_server/server_store/src/server/rocks.rs
similarity index 89%
rename from swim_server/src/store/rocks.rs
rename to swim_server/server_store/src/server/rocks.rs
index 3fbe781e0..fcca0a062 100644
--- a/swim_server/src/store/rocks.rs
+++ b/swim_server/server_store/src/server/rocks.rs
@@ -12,17 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::store::keystore::{incrementing_merge_operator, COUNTER_KEY};
-use crate::store::{LANE_KS, MAP_LANE_KS, VALUE_LANE_KS};
-use rocksdb::{ColumnFamilyDescriptor, DB};
+use crate::server::keystore::rocks::incrementing_merge_operator;
+use crate::server::keystore::COUNTER_KEY;
+use crate::server::{LANE_KS, MAP_LANE_KS, VALUE_LANE_KS};
+use rocksdb::{ColumnFamily, ColumnFamilyDescriptor, Options, SliceTransform, DB};
 use std::mem::size_of;
 use std::path::Path;
-use store::engines::{RocksEngine, RocksIterator, RocksPrefixIterator, StoreBuilder};
-use store::iterator::{EngineIterOpts, EngineRefIterator};
-use store::keyspaces::{Keyspace, KeyspaceByteEngine};
-use store::keyspaces::{KeyspaceDef, KeyspaceResolver, Keyspaces};
-use store::{ColumnFamily, Options, SliceTransform};
-use store::{EngineInfo, Store, StoreError};
+use swim_store::rocks::{RocksEngine, RocksIterator, RocksPrefixIterator};
+use swim_store::{
+    EngineInfo, EngineIterOpts, EngineRefIterator, Keyspace, KeyspaceByteEngine, KeyspaceDef,
+    KeyspaceResolver, Keyspaces, Store, StoreBuilder, StoreError,
+};
 
 const PREFIX_BLOOM_RATIO: f64 = 0.2;
 
@@ -147,7 +147,8 @@ impl StoreBuilder for RocksOpts {
                     vec
                 });
 
-        let db = DB::open_cf_descriptors(&self.0, path, descriptors)?;
+        let db = DB::open_cf_descriptors(&self.0, path, descriptors)
+            .map_err(|e| StoreError::Delegate(Box::new(e)))?;
         Ok(RocksDatabase {
             db: RocksEngine::new(db),
         })
diff --git a/swim_server/src/agent/context/mod.rs b/swim_server/src/agent/context/mod.rs
index 6935d5d71..d02d638d6 100644
--- a/swim_server/src/agent/context/mod.rs
+++ b/swim_server/src/agent/context/mod.rs
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::store::NodeStore;
 use crate::agent::{AgentContext, Eff};
 use crate::meta::log::NodeLogger;
 use crate::meta::metric::NodeMetricAggregator;
@@ -21,6 +20,7 @@ use crate::routing::ServerRouter;
 use futures::future::BoxFuture;
 use futures::sink::drain;
 use futures::{FutureExt, Stream, StreamExt};
+use server_store::agent::NodeStore;
 use std::collections::HashMap;
 use std::future::Future;
 use std::sync::atomic::{AtomicU64, Ordering};
diff --git a/swim_server/src/agent/lane/mod.rs b/swim_server/src/agent/lane/mod.rs
index 01dbcdbcd..f4db03e6e 100644
--- a/swim_server/src/agent/lane/mod.rs
+++ b/swim_server/src/agent/lane/mod.rs
@@ -19,7 +19,6 @@ use swim_common::form::structural::read::ReadError;
 pub mod channels;
 pub mod lifecycle;
 pub mod model;
-pub mod store;
 
 #[cfg(test)]
 pub mod tests;
diff --git a/swim_server/src/agent/lane/model/map/mod.rs b/swim_server/src/agent/lane/model/map/mod.rs
index 67859e4de..5598ce9a9 100644
--- a/swim_server/src/agent/lane/model/map/mod.rs
+++ b/swim_server/src/agent/lane/model/map/mod.rs
@@ -21,36 +21,36 @@ use std::sync::Arc;
 
 use im::OrdMap;
 
-use stm::local::TLocal;
-use stm::stm::{abort, left, right, Constant, Stm, VecStm, UNIT};
-use stm::transaction::{atomically, RetryManager, TransactionError, TransactionRunner};
-use stm::var::TVar;
-use summary::{clear_summary, remove_summary, update_summary};
-use swim_common::form::Form;
-use swim_common::model::Value;
-
-use crate::agent::lane::model::map::map_store::MapLaneStoreIo;
 use crate::agent::lane::model::map::summary::TransactionSummary;
 use crate::agent::lane::model::DeferredSubscription;
 use crate::agent::lane::{InvalidForm, LaneModel};
-use crate::agent::model::map::map_store::MapDataModel;
-use crate::agent::store::NodeStore;
 use crate::agent::StoreIo;
+use futures::future::ready;
 use futures::stream::{iter, Iter};
 use futures::Stream;
+use futures::StreamExt;
 use serde::de::DeserializeOwned;
 use serde::Serialize;
+use server_store::agent::lane::map::MapStoreEvent;
+use server_store::agent::lane::map::{MapDataModel, MapLaneStoreIo};
+use server_store::agent::NodeStore;
 use std::collections::HashMap;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::num::NonZeroUsize;
+use stm::local::TLocal;
+use stm::stm::{abort, left, right, Constant, Stm, VecStm, UNIT};
+use stm::transaction::{atomically, RetryManager, TransactionError, TransactionRunner};
 use stm::var::observer::{Observer, ObserverStream, ObserverSubscriber};
+use stm::var::TVar;
+use summary::{clear_summary, remove_summary, update_summary};
 use swim_common::form::structural::read::ReadError;
+use swim_common::form::Form;
+use swim_common::model::Value;
 use swim_utilities::future::{FlatmapStream, SwimStreamExt, Transform};
 use swim_warp::model::map::MapUpdate;
 use tracing::{event, Level};
 
-pub mod map_store;
 mod summary;
 
 pub type StreamedMapLane<K, V, S> = (
@@ -895,10 +895,19 @@ where
         let model = MapDataModel::<Store, K, V>::new(store, lane_id);
 
         Some(Box::new(MapLaneStoreIo::new(
-            summaries_to_events(observer),
+            summaries_to_events(observer).filter_map(|e| ready(to_map_store_event(e))),
             model,
         )))
     };
 
     (lane, subscriber, stream, store_io)
 }
+
+pub fn to_map_store_event<K, V>(event: MapLaneEvent<K, V>) -> Option<MapStoreEvent<K, V>> {
+    match event {
+        MapLaneEvent::Checkpoint(_) => None,
+        MapLaneEvent::Clear => Some(MapStoreEvent::Clear),
+        MapLaneEvent::Update(key, value) => Some(MapStoreEvent::Update(key, value)),
+        MapLaneEvent::Remove(key) => Some(MapStoreEvent::Remove(key)),
+    }
+}
diff --git a/swim_server/src/agent/lane/model/value/mod.rs b/swim_server/src/agent/lane/model/value/mod.rs
index d7dcb41b7..cb91c34dc 100644
--- a/swim_server/src/agent/lane/model/value/mod.rs
+++ b/swim_server/src/agent/lane/model/value/mod.rs
@@ -22,13 +22,12 @@ use stm::var::TVar;
 
 #[cfg(test)]
 mod tests;
-mod value_store;
 
-use crate::agent::store::NodeStore;
-use crate::agent::StoreIo;
 use serde::de::DeserializeOwned;
 use serde::Serialize;
-pub use value_store::{ValueDataModel, ValueLaneStoreIo};
+use server_store::agent::lane::value::{ValueDataModel, ValueLaneStoreIo};
+use server_store::agent::lane::StoreIo;
+use server_store::agent::NodeStore;
 
 /// A lane containing a single value.
 #[derive(Debug)]
diff --git a/swim_server/src/agent/mod.rs b/swim_server/src/agent/mod.rs
index 20f519666..0a8626845 100644
--- a/swim_server/src/agent/mod.rs
+++ b/swim_server/src/agent/mod.rs
@@ -16,7 +16,6 @@ pub mod context;
 pub(crate) mod dispatch;
 pub mod lane;
 pub mod lifecycle;
-pub mod store;
 
 #[cfg(test)]
 mod tests;
@@ -48,9 +47,7 @@ use crate::agent::lane::model::demand_map::{
 use crate::agent::lane::model::map::MapLane;
 use crate::agent::lane::model::map::{summaries_to_events, MapLaneEvent, MapSubscriber};
 use crate::agent::lane::model::supply::{make_lane_model, SupplyLane};
-use crate::agent::lane::model::value::{
-    ValueDataModel, ValueLane, ValueLaneEvent, ValueLaneStoreIo,
-};
+use crate::agent::lane::model::value::{ValueLane, ValueLaneEvent};
 use crate::agent::lane::model::DeferredSubscription;
 use crate::agent::lifecycle::AgentLifecycle;
 use crate::routing::{ServerRouter, TaggedClientEnvelope, TaggedEnvelope};
@@ -80,11 +77,7 @@ use tokio::sync::{mpsc, oneshot};
 use tracing::{event, span, Level};
 use tracing_futures::{Instrument, Instrumented};
 
-use crate::agent::lane::model::map::map_store::MapDataModel;
-use crate::agent::lane::store::task::{NodeStoreErrors, NodeStoreTask};
-pub use crate::agent::lane::store::{LaneNoStore, StoreIo};
-use crate::agent::model::map::map_store::MapLaneStoreIo;
-use crate::agent::store::NodeStore;
+use crate::agent::model::map::to_map_store_event;
 use crate::meta::info::{LaneInfo, LaneKind};
 use crate::meta::log::NodeLogger;
 use crate::meta::open_meta_lanes;
@@ -93,6 +86,11 @@ use crate::meta::open_meta_lanes;
 pub use agent_derive::*;
 use serde::de::DeserializeOwned;
 use serde::Serialize;
+use server_store::agent::lane::map::{MapDataModel, MapLaneStoreIo};
+use server_store::agent::lane::task::{NodeStoreErrors, NodeStoreTask};
+use server_store::agent::lane::value::{ValueDataModel, ValueLaneStoreIo};
+use server_store::agent::lane::StoreIo;
+use server_store::agent::NodeStore;
 use tokio_stream::wrappers::ReceiverStream;
 
 /// Trait that must be implemented for any agent. This is essentially just boilerplate and will
@@ -1093,7 +1091,7 @@ where
         None
     } else {
         Some(Box::new(MapLaneStoreIo::new(
-            summaries_to_events(observer),
+            summaries_to_events(observer).filter_map(|e| ready(to_map_store_event(e))),
             model,
         )))
     };
diff --git a/swim_server/src/interface/mod.rs b/swim_server/src/interface/mod.rs
index 7ec59932f..5f85b090c 100644
--- a/swim_server/src/interface/mod.rs
+++ b/swim_server/src/interface/mod.rs
@@ -15,26 +15,23 @@
 //! Interface for creating and running Swim server instances.
 //!
 //! The module provides methods and structures for creating and running Swim server instances.
+//!
 use crate::agent::lane::channels::AgentExecutionConfig;
 use crate::plane::router::PlaneRouter;
 use crate::plane::spec::{PlaneBuilder, PlaneSpec};
-use crate::plane::store::SwimPlaneStore;
 use crate::plane::{run_plane, EnvChannel};
 use crate::routing::remote::config::ConnectionConfig;
 use crate::routing::remote::net::dns::Resolver;
 use crate::routing::remote::net::plain::TokioPlainTextNetworking;
 use crate::routing::remote::{RemoteConnectionChannels, RemoteConnectionsTask};
 use crate::routing::{TopLevelRouter, TopLevelRouterFactory};
-use crate::store::RocksOpts;
-use crate::store::{default_keyspaces, RocksDatabase};
-use crate::store::{ServerStore, SwimStore};
 use either::Either;
 use futures::{io, join};
+use server_store::plane::PlaneStore;
 use std::error::Error;
 use std::fmt::{Display, Formatter};
 use std::net::SocketAddr;
 use std::sync::Arc;
-use store::StoreError;
 use swim_common::routing::ws::tungstenite::TungsteniteWsConnections;
 use swim_runtime::task::TaskError;
 use swim_runtime::time::clock::RuntimeClock;
@@ -44,42 +41,33 @@ use swim_utilities::trigger::promise;
 use tokio::sync::mpsc;
 use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
 
+#[cfg(feature = "persistence")]
+use server_store::rocks;
+use server_store::{ServerStore, SwimStore};
+use swim_store::nostore::NoStoreOpts;
+use swim_store::{Keyspaces, StoreError};
+
 /// Builder to create Swim server instance.
 ///
 /// The builder can be created with default or custom configuration.
-pub struct SwimServerBuilder {
+pub struct SwimServerBuilder<S>
+where
+    S: SwimStore,
+{
     address: Option<SocketAddr>,
     config: SwimServerConfig,
-    planes: Vec<
-        PlaneSpec<
-            RuntimeClock,
-            EnvChannel,
-            PlaneRouter<TopLevelRouter>,
-            SwimPlaneStore<RocksDatabase>,
-        >,
-    >,
-    store: ServerStore<RocksOpts>,
+    planes: Vec<PlaneSpec<RuntimeClock, EnvChannel, PlaneRouter<TopLevelRouter>, S::PlaneStore>>,
+    store: S,
 }
 
-type ServerPlaneBuilder = PlaneBuilder<
-    RuntimeClock,
-    EnvChannel,
-    PlaneRouter<TopLevelRouter>,
-    SwimPlaneStore<RocksDatabase>,
->;
+type ServerPlaneBuilder<S> = PlaneBuilder<RuntimeClock, EnvChannel, PlaneRouter<TopLevelRouter>, S>;
 
-impl SwimServerBuilder {
-    /// Create a new server builder with custom configuration.
-    ///
-    /// # Arguments
-    /// * `config` - The custom configuration for the server.
-    pub fn new(config: SwimServerConfig) -> io::Result<Self> {
-        Ok(SwimServerBuilder {
-            address: None,
-            config,
-            planes: Vec::new(),
-            store: ServerStore::new(RocksOpts::default(), default_keyspaces(), "target".into())?,
-        })
+impl<S> SwimServerBuilder<S>
+where
+    S: SwimStore,
+{
+    pub fn store(self, store: S) -> SwimServerBuilder<S> {
+        SwimServerBuilder { store, ..self }
     }
 
     /// Set the address of the server.
@@ -129,7 +117,7 @@ impl SwimServerBuilder {
     ///     }
     /// }
     ///
-    /// let mut swim_server_builder = SwimServerBuilder::temporary_store(SwimServerConfig::default(), "test").unwrap();
+    /// let mut swim_server_builder = SwimServerBuilder::no_store(SwimServerConfig::default()).unwrap();
     /// let mut plane_builder = swim_server_builder.plane_builder("test").unwrap();
     ///
     /// plane_builder
@@ -142,7 +130,7 @@ impl SwimServerBuilder {
     /// swim_server_builder.add_plane(plane_builder.build());
     /// # }
     /// ```
-    pub fn add_plane(&mut self, plane: PlaneDef) {
+    pub fn add_plane(&mut self, plane: PlaneDef<S::PlaneStore>) {
         if !self.planes.is_empty() {
             panic!("Multiple planes are not supported yet")
         }
@@ -152,7 +140,7 @@ impl SwimServerBuilder {
     pub fn plane_builder<I: Into<String>>(
         &mut self,
         name: I,
-    ) -> Result<ServerPlaneBuilder, SwimServerBuilderError> {
+    ) -> Result<ServerPlaneBuilder<S::PlaneStore>, SwimServerBuilderError> {
         let store = self
             .store
             .plane_store(name.into())
@@ -195,7 +183,7 @@ impl SwimServerBuilder {
     ///     }
     /// }
     ///
-    /// let mut swim_server_builder = SwimServerBuilder::temporary_store(SwimServerConfig::default(), "test").unwrap();
+    /// let mut swim_server_builder = SwimServerBuilder::no_store(SwimServerConfig::default()).unwrap();
     /// let mut plane_builder = swim_server_builder.plane_builder("test").unwrap();
     ///
     /// plane_builder
@@ -211,12 +199,14 @@ impl SwimServerBuilder {
     /// let (swim_server, server_handle) = swim_server_builder.bind_to(address).build().unwrap();
     /// # }
     /// ```
-    pub fn build(self) -> Result<(SwimServer, ServerHandle), SwimServerBuilderError> {
+    pub fn build(
+        self,
+    ) -> Result<(SwimServer<S::PlaneStore>, ServerHandle), SwimServerBuilderError> {
         let SwimServerBuilder {
             address,
             planes,
             config,
-            store,
+            store: _,
         } = self;
 
         let (stop_trigger_tx, stop_trigger_rx) = trigger::trigger();
@@ -229,7 +219,6 @@ impl SwimServerBuilder {
                 address: address.ok_or(SwimServerBuilderError::MissingAddress)?,
                 stop_trigger_rx,
                 address_tx,
-                _store: store,
             },
             ServerHandle {
                 either_address: Either::Left(address_rx),
@@ -237,6 +226,26 @@ impl SwimServerBuilder {
             },
         ))
     }
+}
+
+#[cfg(feature = "persistence")]
+impl SwimServerBuilder<ServerStore<rocks::RocksOpts>> {
+    /// Create a new server builder with custom configuration backed by RocksDB.
+    ///
+    /// # Arguments
+    /// * `config` - The custom configuration for the server.
+    pub fn new(config: SwimServerConfig) -> io::Result<Self> {
+        Ok(SwimServerBuilder {
+            address: None,
+            config,
+            planes: Vec::new(),
+            store: ServerStore::new(
+                rocks::RocksOpts::default(),
+                rocks::default_keyspaces(),
+                "target".into(),
+            )?,
+        })
+    }
 
     /// Constructs a new `SwimServerBuilder` with the default configuration and is backed by a
     /// temporary store.
@@ -249,9 +258,9 @@ impl SwimServerBuilder {
     pub fn temporary_store(config: SwimServerConfig, prefix: &str) -> io::Result<Self> {
         Ok(SwimServerBuilder {
             address: None,
-            store: ServerStore::<RocksOpts>::transient(
-                RocksOpts::default(),
-                default_keyspaces(),
+            store: ServerStore::<rocks::RocksOpts>::transient(
+                rocks::RocksOpts::default(),
+                rocks::default_keyspaces(),
                 prefix,
             )?,
             config,
@@ -260,22 +269,41 @@ impl SwimServerBuilder {
     }
 }
 
-type PlaneDef =
-    PlaneSpec<RuntimeClock, EnvChannel, PlaneRouter<TopLevelRouter>, SwimPlaneStore<RocksDatabase>>;
+impl SwimServerBuilder<ServerStore<NoStoreOpts>> {
+    /// Create a new server builder with custom configuration which will persist no data.
+    ///
+    /// # Arguments
+    /// * `config` - The custom configuration for the server.
+    pub fn no_store(config: SwimServerConfig) -> io::Result<Self> {
+        Ok(SwimServerBuilder {
+            address: None,
+            config,
+            planes: Vec::new(),
+            store: ServerStore::new(NoStoreOpts, Keyspaces::new(vec![]), "target".into())?,
+        })
+    }
+}
+
+type PlaneDef<S> = PlaneSpec<RuntimeClock, EnvChannel, PlaneRouter<TopLevelRouter>, S>;
 
 /// Swim server instance.
 ///
 /// The Swim server runs a plane and its agents and a task for remote connections asynchronously.
-pub struct SwimServer {
+pub struct SwimServer<S>
+where
+    S: PlaneStore,
+{
     address: SocketAddr,
     config: SwimServerConfig,
-    planes: Vec<PlaneDef>,
+    planes: Vec<PlaneDef<S>>,
     stop_trigger_rx: trigger::Receiver,
     address_tx: promise::Sender<SocketAddr>,
-    _store: ServerStore<RocksOpts>,
 }
 
-impl SwimServer {
+impl<S> SwimServer<S>
+where
+    S: PlaneStore,
+{
     /// Runs the Swim server instance.
     ///
     /// Runs the planes and remote connections tasks of the server asynchronously
@@ -290,7 +318,6 @@ impl SwimServer {
             mut planes,
             stop_trigger_rx,
             address_tx,
-            _store,
         } = self;
 
         let SwimServerConfig {
@@ -458,7 +485,7 @@ impl ServerHandle {
     /// use swim_server::interface::{SwimServer, SwimServerBuilder};
     ///
     /// let address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
-    /// let (mut swim_server, server_handle) = SwimServerBuilder::new(Default::default()).unwrap().bind_to(address).build().unwrap();
+    /// let (mut swim_server, server_handle) = SwimServerBuilder::no_store(Default::default()).unwrap().bind_to(address).build().unwrap();
     ///
     /// let success = server_handle.stop();
     ///
diff --git a/swim_server/src/lib.rs b/swim_server/src/lib.rs
index 0e9efc5bc..9b60bc897 100644
--- a/swim_server/src/lib.rs
+++ b/swim_server/src/lib.rs
@@ -21,7 +21,6 @@ pub mod macros;
 pub mod meta;
 pub mod plane;
 pub mod routing;
-pub mod store;
 
 #[allow(unused_imports)]
 pub use agent_derive::*;
@@ -32,4 +31,8 @@ pub use swim_utilities::routing::route_pattern::RoutePattern;
 pub use swim_utilities::routing::uri;
 
 #[doc(hidden)]
-pub use agent::model::value::ValueLaneStoreIo;
+pub mod store {
+    pub use server_store::agent::lane::value::ValueLaneStoreIo;
+    pub use server_store::agent::lane::{LaneNoStore, StoreIo};
+    pub use server_store::agent::NodeStore;
+}
diff --git a/swim_server/src/plane/mod.rs b/swim_server/src/plane/mod.rs
index e126ee612..01ccf3a69 100644
--- a/swim_server/src/plane/mod.rs
+++ b/swim_server/src/plane/mod.rs
@@ -18,7 +18,6 @@ pub mod lifecycle;
 pub(crate) mod provider;
 pub(crate) mod router;
 pub mod spec;
-pub mod store;
 #[cfg(test)]
 mod tests;
 
@@ -29,7 +28,6 @@ use crate::plane::context::PlaneContext;
 use crate::plane::error::NoAgentAtRoute;
 use crate::plane::router::{PlaneRouter, PlaneRouterFactory};
 use crate::plane::spec::{PlaneSpec, RouteSpec};
-use crate::plane::store::PlaneStore;
 use crate::routing::error::{RouterError, Unresolvable};
 use crate::routing::remote::RawRoute;
 use crate::routing::{ConnectionDropped, RoutingAddr, ServerRouterFactory, TaggedEnvelope};
@@ -38,6 +36,7 @@ use futures::future::{join, BoxFuture};
 use futures::{select_biased, FutureExt, StreamExt};
 use futures_util::stream::TakeUntil;
 use pin_utils::pin_mut;
+use server_store::plane::PlaneStore;
 use std::any::Any;
 use std::collections::{HashMap, HashSet};
 use std::fmt::Debug;
diff --git a/swim_server/src/plane/provider/mod.rs b/swim_server/src/plane/provider/mod.rs
index e3a63c475..008cc5f8f 100644
--- a/swim_server/src/plane/provider/mod.rs
+++ b/swim_server/src/plane/provider/mod.rs
@@ -14,12 +14,12 @@
 
 use crate::agent::lane::channels::AgentExecutionConfig;
 use crate::agent::lifecycle::AgentLifecycle;
-use crate::agent::store::NodeStore;
 use crate::agent::{AgentParameters, AgentResult, SwimAgent};
 use crate::plane::{AgentRoute, RouteAndParameters};
 use crate::routing::{ServerRouter, TaggedEnvelope};
 use futures::future::BoxFuture;
 use futures::{FutureExt, Stream};
+use server_store::agent::NodeStore;
 use std::any::Any;
 use std::fmt::{Debug, Formatter};
 use std::marker::PhantomData;
diff --git a/swim_server/src/plane/spec/mod.rs b/swim_server/src/plane/spec/mod.rs
index d54659ee4..1d1d74d22 100644
--- a/swim_server/src/plane/spec/mod.rs
+++ b/swim_server/src/plane/spec/mod.rs
@@ -17,10 +17,10 @@ use crate::agent::SwimAgent;
 use crate::plane::error::AmbiguousRoutes;
 use crate::plane::lifecycle::PlaneLifecycle;
 use crate::plane::provider::AgentProvider;
-use crate::plane::store::PlaneStore;
 use crate::plane::{AgentRoute, BoxAgentRoute};
 use crate::routing::{ServerRouter, TaggedEnvelope};
 use futures::Stream;
+use server_store::plane::PlaneStore;
 use std::fmt::Debug;
 use swim_runtime::time::clock::Clock;
 use swim_utilities::routing::route_pattern::RoutePattern;
diff --git a/swim_server/src/store/nostore.rs b/swim_server/src/store/nostore.rs
deleted file mode 100644
index c973b2e66..000000000
--- a/swim_server/src/store/nostore.rs
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2015-2021 SWIM.AI inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use std::path::{Path, PathBuf};
-use store::engines::StoreBuilder;
-use store::iterator::{
-    EngineIterOpts, EngineIterator, EnginePrefixIterator, EngineRefIterator, IteratorKey,
-};
-use store::keyspaces::{Keyspace, KeyspaceByteEngine, KeyspaceResolver, Keyspaces};
-use store::{EngineInfo, KvBytes, Store, StoreError};
-
-/// A store which will persist no data and exists purely to uphold the minimum contract required
-/// between a lane and its store.
-#[derive(Debug, Clone)]
-pub struct NoStore {
-    path: PathBuf,
-    ks: (),
-}
-
-impl Store for NoStore {
-    fn path(&self) -> &Path {
-        &self.path
-    }
-
-    fn engine_info(&self) -> EngineInfo {
-        EngineInfo {
-            path: "no store".to_string(),
-            kind: "no store".to_string(),
-        }
-    }
-}
-
-pub struct NoStoreIterator;
-impl EngineIterator for NoStoreIterator {
-    fn seek_to(&mut self, _key: IteratorKey) -> Result<bool, StoreError> {
-        Ok(false)
-    }
-
-    fn seek_next(&mut self) {}
-
-    fn key(&self) -> Option<&[u8]> {
-        None
-    }
-
-    fn value(&self) -> Option<&[u8]> {
-        None
-    }
-
-    fn valid(&self) -> Result<bool, StoreError> {
-        Ok(false)
-    }
-}
-
-impl EnginePrefixIterator for NoStoreIterator {
-    fn next(&mut self) -> Option<KvBytes> {
-        None
-    }
-
-    fn valid(&self) -> Result<bool, StoreError> {
-        Ok(false)
-    }
-}
-
-impl<'a: 'b, 'b> EngineRefIterator<'a, 'b> for NoStore {
-    type EngineIterator = NoStoreIterator;
-    type EnginePrefixIterator = NoStoreIterator;
-
-    fn iterator_opt(
-        &'a self,
-        _space: &'b Self::ResolvedKeyspace,
-        _opts: EngineIterOpts,
-    ) -> Result<Self::EngineIterator, StoreError> {
-        Ok(NoStoreIterator)
-    }
-
-    fn prefix_iterator_opt(
-        &'a self,
-        _space: &'b Self::ResolvedKeyspace,
-        _opts: EngineIterOpts,
-        _prefix: &'b [u8],
-    ) -> Result<Self::EnginePrefixIterator, StoreError> {
-        Ok(NoStoreIterator)
-    }
-}
-
-#[derive(Default, Clone)]
-pub struct NoStoreOpts;
-impl StoreBuilder for NoStoreOpts {
-    type Store = NoStore;
-
-    fn build<I>(self, _path: I, _keyspaces: &Keyspaces<Self>) -> Result<Self::Store, StoreError>
-    where
-        I: AsRef<Path>,
-    {
-        Ok(NoStore {
-            path: PathBuf::from("Transient".to_string()),
-            ks: (),
-        })
-    }
-}
-
-impl KeyspaceByteEngine for NoStore {
-    fn put_keyspace<K: Keyspace>(
-        &self,
-        _keyspace: K,
-        _key: &[u8],
-        _value: &[u8],
-    ) -> Result<(), StoreError> {
-        Ok(())
-    }
-
-    fn get_keyspace<K: Keyspace>(
-        &self,
-        _keyspace: K,
-        _key: &[u8],
-    ) -> Result<Option<Vec<u8>>, StoreError> {
-        Ok(None)
-    }
-
-    fn delete_keyspace<K: Keyspace>(&self, _keyspace: K, _key: &[u8]) -> Result<(), StoreError> {
-        Ok(())
-    }
-
-    fn merge_keyspace<K: Keyspace>(
-        &self,
-        _keyspace: K,
-        _key: &[u8],
-        _step: u64,
-    ) -> Result<(), StoreError> {
-        Ok(())
-    }
-
-    fn get_prefix_range<F, K, V, S>(
-        &self,
-        _keyspace: S,
-        _prefix: &[u8],
-        _map_fn: F,
-    ) -> Result<Option<Vec<(K, V)>>, StoreError>
-    where
-        F: for<'i> Fn(&'i [u8], &'i [u8]) -> Result<(K, V), StoreError>,
-        S: Keyspace,
-    {
-        Ok(None)
-    }
-}
-
-impl KeyspaceResolver for NoStore {
-    type ResolvedKeyspace = ();
-
-    fn resolve_keyspace<K: Keyspace>(&self, _space: &K) -> Option<&Self::ResolvedKeyspace> {
-        Some(&self.ks)
-    }
-}
diff --git a/swim_server/store/Cargo.toml b/swim_server/store/Cargo.toml
deleted file mode 100644
index eee69c89e..000000000
--- a/swim_server/store/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "store"
-version = "0.1.0"
-authors = ["Swim.AI developers info@swim.ai"]
-edition = "2018"
-
-[dependencies]
-rocksdb = "0.16.0"
-bincode = "1.3.2"
-serde = { version = "1.0.124", features = ["derive", "rc"] }
-tempdir = "0.3.7"
-thiserror = "1.0"
diff --git a/swim_server/store/src/engines/mod.rs b/swim_server/store/src/engines/mod.rs
deleted file mode 100644
index fd9bfaeb2..000000000
--- a/swim_server/store/src/engines/mod.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2015-2021 SWIM.AI inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use std::path::Path;
-
-pub use nostore::{NoStore, NoStoreOpts};
-pub use rocks::{RocksEngine, RocksIterator, RocksOpts, RocksPrefixIterator};
-
-use crate::keyspaces::Keyspaces;
-use crate::{Store, StoreError};
-
-mod nostore;
-mod rocks;
-
-/// A storage engine for server stores that handles byte arrays.
-pub trait ByteEngine: 'static {
-    /// Put a key-value pair into this store.
-    fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StoreError>;
-
-    /// Get an entry from this store by its key.
-    fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError>;
-
-    /// Delete a value from this store by its key.
-    fn delete(&self, key: &[u8]) -> Result<(), StoreError>;
-}
-
-pub trait StoreBuilder: Sized + Clone {
-    type Store: Store;
-
-    fn build<I>(self, path: I, keyspaces: &Keyspaces<Self>) -> Result<Self::Store, StoreError>
-    where
-        I: AsRef<Path>;
-}
diff --git a/swim_server/store/src/transient.rs b/swim_server/store/src/transient.rs
deleted file mode 100644
index 8b1378917..000000000
--- a/swim_server/store/src/transient.rs
+++ /dev/null
@@ -1,1 +0,0 @@
-
diff --git a/swim_store/Cargo.toml b/swim_store/Cargo.toml
new file mode 100644
index 000000000..5d0d72191
--- /dev/null
+++ b/swim_store/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "swim_store"
+version = "0.1.0"
+edition = "2018"
+
+[features]
+default = []
+rocks = ["rocks_store"]
+
+[dependencies]
+rocks_store = { path = "rocks_store", optional = true }
+store_common = { path = "store_common" }
diff --git a/swim_store/rocks_store/Cargo.toml b/swim_store/rocks_store/Cargo.toml
new file mode 100644
index 000000000..48784f31c
--- /dev/null
+++ b/swim_store/rocks_store/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "rocks_store"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+store_common = { path = "../store_common" }
+rocksdb = "0.16.0"
+
+[dev-dependencies]
+tempdir = "0.3.7"
diff --git a/swim_server/store/src/engines/rocks/iterator.rs b/swim_store/rocks_store/src/iterator.rs
similarity index 90%
rename from swim_server/store/src/engines/rocks/iterator.rs
rename to swim_store/rocks_store/src/iterator.rs
index 10c71b9fd..1f2e05307 100644
--- a/swim_server/store/src/engines/rocks/iterator.rs
+++ b/swim_store/rocks_store/src/iterator.rs
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::engines::RocksEngine;
-use crate::iterator::{
-    EngineIterOpts, EngineIterator, EnginePrefixIterator, EngineRefIterator, IteratorKey,
-};
-use crate::{KvBytes, StoreError};
+use crate::RocksEngine;
 use rocksdb::{DBIteratorWithThreadMode, DBRawIterator, DBWithThreadMode, SingleThreaded};
+use store_common::StoreError;
+use store_common::{
+    EngineIterOpts, EngineIterator, EnginePrefixIterator, EngineRefIterator, IteratorKey, KvBytes,
+};
 
 impl<'a: 'b, 'b> EngineRefIterator<'a, 'b> for RocksEngine {
     type EngineIterator = RocksIterator<'b>;
@@ -60,7 +60,7 @@ impl<'d> EnginePrefixIterator for RocksPrefixIterator<'d> {
         } else {
             match self.delegate.status() {
                 Ok(_) => Ok(false),
-                Err(e) => Err(StoreError::from(e)),
+                Err(e) => Err(StoreError::Delegate(Box::new(e))),
             }
         }
     }
@@ -94,7 +94,9 @@ impl<'d> EngineIterator for RocksIterator<'d> {
 
     fn valid(&self) -> Result<bool, StoreError> {
         if !self.iter.valid() {
-            self.iter.status().map_err(StoreError::from)?;
+            self.iter
+                .status()
+                .map_err(|e| StoreError::Delegate(Box::new(e)))?;
         }
         Ok(self.iter.valid())
     }
diff --git a/swim_server/store/src/engines/rocks/mod.rs b/swim_store/rocks_store/src/lib.rs
similarity index 90%
rename from swim_server/store/src/engines/rocks/mod.rs
rename to swim_store/rocks_store/src/lib.rs
index 3a0983103..5c959c16f 100644
--- a/swim_server/store/src/engines/rocks/mod.rs
+++ b/swim_store/rocks_store/src/lib.rs
@@ -17,21 +17,16 @@ mod tests;
 
 mod iterator;
 
-pub use crate::engines::rocks::iterator::{RocksIterator, RocksPrefixIterator};
-use crate::engines::StoreBuilder;
-use crate::iterator::{EnginePrefixIterator, EngineRefIterator};
-use crate::keyspaces::{Keyspace, KeyspaceByteEngine, KeyspaceResolver, Keyspaces};
-use crate::{serialize, EngineInfo, Store, StoreError};
+pub use crate::iterator::{RocksIterator, RocksPrefixIterator};
+
 use rocksdb::{ColumnFamily, ColumnFamilyDescriptor};
-use rocksdb::{Error, Options, DB};
+use rocksdb::{Options, DB};
 use std::path::Path;
 use std::sync::Arc;
-
-impl From<rocksdb::Error> for StoreError {
-    fn from(e: Error) -> Self {
-        StoreError::Delegate(Box::new(e))
-    }
-}
+use store_common::{
+    serialize, EngineInfo, EnginePrefixIterator, EngineRefIterator, Keyspace, KeyspaceByteEngine,
+    KeyspaceResolver, Keyspaces, Store, StoreBuilder, StoreError,
+};
 
 /// A Rocks database engine.
 ///
@@ -92,7 +87,8 @@ impl StoreBuilder for RocksOpts {
                     vec
                 });
 
-        let db = DB::open_cf_descriptors(&self.0, path, descriptors)?;
+        let db = DB::open_cf_descriptors(&self.0, path, descriptors)
+            .map_err(|e| StoreError::Delegate(Box::new(e)))?;
         Ok(RocksEngine::new(db))
     }
 }
@@ -113,7 +109,7 @@ where
     K: Keyspace,
 {
     match delegate.cf_handle(keyspace.name()) {
-        Some(cf) => f(delegate, cf).map_err(Into::into),
+        Some(cf) => f(delegate, cf).map_err(|e| StoreError::Delegate(Box::new(e))),
         None => Err(StoreError::KeyspaceNotFound),
     }
 }
diff --git a/swim_store/src/lib.rs b/swim_store/src/lib.rs
new file mode 100644
index 000000000..c4acda137
--- /dev/null
+++ b/swim_store/src/lib.rs
@@ -0,0 +1,21 @@
+// Copyright 2015-2021 SWIM.AI inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub mod nostore;
+pub use store_common::*;
+
+#[cfg(feature = "rocks")]
+pub mod rocks {
+    pub use rocks_store::*;
+}
diff --git a/swim_server/store/src/engines/nostore.rs b/swim_store/src/nostore.rs
similarity index 90%
rename from swim_server/store/src/engines/nostore.rs
rename to swim_store/src/nostore.rs
index 5b9161661..0bb704021 100644
--- a/swim_server/store/src/engines/nostore.rs
+++ b/swim_store/src/nostore.rs
@@ -12,17 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::engines::ByteEngine;
-use crate::engines::StoreBuilder;
-use crate::iterator::{
-    EngineIterOpts, EngineIterator, EnginePrefixIterator, EngineRefIterator, IteratorKey,
-};
-use crate::keyspaces::{Keyspace, KeyspaceByteEngine, KeyspaceResolver, Keyspaces};
-use crate::EngineInfo;
-use crate::{KvBytes, Store, StoreError};
 use std::borrow::Borrow;
 use std::path::{Path, PathBuf};
 
+use store_common::{
+    ByteEngine, EngineInfo, EngineIterOpts, EngineIterator, EnginePrefixIterator,
+    EngineRefIterator, IteratorKey, Keyspace, KeyspaceByteEngine, KeyspaceResolver, Keyspaces,
+    KvBytes, Store, StoreBuilder, StoreError,
+};
+
 /// A delegate store database that does nothing.
 #[derive(Debug, Clone)]
 pub struct NoStore {
@@ -36,7 +34,7 @@ impl Store for NoStore {
 
     fn engine_info(&self) -> EngineInfo {
         EngineInfo {
-            path: "Transient".to_string(),
+            path: "No store".to_string(),
             kind: "NoStore".to_string(),
         }
     }
@@ -105,7 +103,7 @@ impl StoreBuilder for NoStoreOpts {
         I: AsRef<Path>,
     {
         Ok(NoStore {
-            path: PathBuf::from("Transient".to_string()),
+            path: PathBuf::from("No store".to_string()),
         })
     }
 }
diff --git a/swim_store/store_common/Cargo.toml b/swim_store/store_common/Cargo.toml
new file mode 100644
index 000000000..4bcb58463
--- /dev/null
+++ b/swim_store/store_common/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "store_common"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+bincode = "1.3.2"
+serde = { version = "1.0.124", features = ["derive"] }
+tempdir = "0.3.7"
+thiserror = "1.0"
\ No newline at end of file
diff --git a/swim_server/store/src/iterator.rs b/swim_store/store_common/src/iterator.rs
similarity index 100%
rename from swim_server/store/src/iterator.rs
rename to swim_store/store_common/src/iterator.rs
diff --git a/swim_server/store/src/keyspaces.rs b/swim_store/store_common/src/keyspaces.rs
similarity index 100%
rename from swim_server/store/src/keyspaces.rs
rename to swim_store/store_common/src/keyspaces.rs
diff --git a/swim_server/store/src/lib.rs b/swim_store/store_common/src/lib.rs
similarity index 80%
rename from swim_server/store/src/lib.rs
rename to swim_store/store_common/src/lib.rs
index dd7690ada..594584eda 100644
--- a/swim_server/store/src/lib.rs
+++ b/swim_store/store_common/src/lib.rs
@@ -12,9 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-pub mod engines;
-pub mod iterator;
-pub mod keyspaces;
+mod iterator;
+mod keyspaces;
+mod utils;
+
+pub use iterator::*;
+pub use keyspaces::*;
+pub use utils::*;
 
 use std::error::Error;
 use std::fmt::Debug;
@@ -23,12 +27,6 @@ use std::{error::Error as StdError, io};
 
 use thiserror::Error;
 
-use crate::iterator::OwnedEngineRefIterator;
-use crate::keyspaces::{KeyspaceByteEngine, KeyspaceResolver};
-
-pub use rocksdb::{ColumnFamily, MergeOperands, Options, SliceTransform};
-use serde::{Deserialize, Serialize};
-
 pub type KvBytes = (Box<[u8]>, Box<[u8]>);
 
 /// Store errors.
@@ -125,22 +123,22 @@ pub struct EngineInfo {
     pub kind: String,
 }
 
-pub fn serialize_then<S, F, O, E>(engine: &E, obj: &S, f: F) -> Result<O, StoreError>
-where
-    S: Serialize,
-    F: Fn(&E, Vec<u8>) -> Result<O, StoreError>,
-{
-    f(engine, serialize(obj)?).map_err(Into::into)
-}
+/// A storage engine for server stores that handles byte arrays.
+pub trait ByteEngine: 'static {
+    /// Put a key-value pair into this store.
+    fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StoreError>;
 
-pub fn serialize<S: Serialize>(obj: &S) -> Result<Vec<u8>, StoreError> {
-    bincode::serialize(obj).map_err(|e| StoreError::Encoding(e.to_string()))
-}
+    /// Get an entry from this store by its key.
+    fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError>;
 
-pub fn deserialize<'de, D: Deserialize<'de>>(obj: &'de [u8]) -> Result<D, StoreError> {
-    bincode::deserialize(obj).map_err(|e| StoreError::Decoding(e.to_string()))
+    /// Delete a value from this store by its key.
+    fn delete(&self, key: &[u8]) -> Result<(), StoreError>;
 }
 
-pub fn deserialize_key<B: AsRef<[u8]>>(bytes: B) -> Result<u64, StoreError> {
-    bincode::deserialize::<u64>(bytes.as_ref()).map_err(|e| StoreError::Decoding(e.to_string()))
+pub trait StoreBuilder: Sized + Clone {
+    type Store: Store;
+
+    fn build<I>(self, path: I, keyspaces: &Keyspaces<Self>) -> Result<Self::Store, StoreError>
+    where
+        I: AsRef<Path>;
 }
diff --git a/swim_store/store_common/src/utils.rs b/swim_store/store_common/src/utils.rs
new file mode 100644
index 000000000..c2fbee7d1
--- /dev/null
+++ b/swim_store/store_common/src/utils.rs
@@ -0,0 +1,36 @@
+// Copyright 2015-2021 SWIM.AI inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::StoreError;
+use serde::{Deserialize, Serialize};
+
+pub fn serialize_then<S, F, O, E>(engine: &E, obj: &S, f: F) -> Result<O, StoreError>
+where
+    S: Serialize,
+    F: Fn(&E, Vec<u8>) -> Result<O, StoreError>,
+{
+    f(engine, serialize(obj)?).map_err(Into::into)
+}
+
+pub fn serialize<S: Serialize>(obj: &S) -> Result<Vec<u8>, StoreError> {
+    bincode::serialize(obj).map_err(|e| StoreError::Encoding(e.to_string()))
+}
+
+pub fn deserialize<'de, D: Deserialize<'de>>(obj: &'de [u8]) -> Result<D, StoreError> {
+    bincode::deserialize(obj).map_err(|e| StoreError::Decoding(e.to_string()))
+}
+
+pub fn deserialize_key<B: AsRef<[u8]>>(bytes: B) -> Result<u64, StoreError> {
+    bincode::deserialize::<u64>(bytes.as_ref()).map_err(|e| StoreError::Decoding(e.to_string()))
+}
diff --git a/swim_utilities/Cargo.toml b/swim_utilities/Cargo.toml
index 41a1852c8..23d286471 100644
--- a/swim_utilities/Cargo.toml
+++ b/swim_utilities/Cargo.toml
@@ -22,14 +22,15 @@ test-util = ["swim_test_util"]
 time = ["swim_time"]
 topic = ["swim_sync", "swim_sync/topic"]
 trigger = ["swim_trigger"]
+io = ["swim_io"]
 
 [dependencies]
-
 swim_bilock = { path = "./swim_bilock", optional = true }
 swim_errors = { path = "./swim_errors", optional = true }
 swim_format = { path = "./swim_format", optional = true }
 swim_trigger = { path = "./swim_trigger", optional = true }
 swim_future = { path = "./swim_future", optional = true }
+swim_io = { path = "./swim_io", optional = true }
 swim_iteratee = { path = "./swim_iteratee", optional = true }
 swim_lrucache = { path = "./swim_lrucache", optional = true }
 swim_route = { path = "./swim_route", optional = true }
diff --git a/swim_utilities/src/lib.rs b/swim_utilities/src/lib.rs
index 88707b0cd..229aade4c 100644
--- a/swim_utilities/src/lib.rs
+++ b/swim_utilities/src/lib.rs
@@ -37,6 +37,9 @@ pub mod collections {
 #[cfg(feature = "time")]
 pub use swim_time as time;
 
+#[cfg(feature = "io")]
+pub use swim_io as io;
+
 #[cfg(feature = "text")]
 pub use swim_route as routing;
 
diff --git a/swim_utilities/swim_io/Cargo.toml b/swim_utilities/swim_io/Cargo.toml
new file mode 100644
index 000000000..003c561eb
--- /dev/null
+++ b/swim_utilities/swim_io/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "swim_io"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+tempdir = "0.3.7"
diff --git a/swim_server/src/store/fs.rs b/swim_utilities/swim_io/src/fs.rs
similarity index 100%
rename from swim_server/src/store/fs.rs
rename to swim_utilities/swim_io/src/fs.rs
diff --git a/swim_utilities/swim_io/src/lib.rs b/swim_utilities/swim_io/src/lib.rs
new file mode 100644
index 000000000..9bc060874
--- /dev/null
+++ b/swim_utilities/swim_io/src/lib.rs
@@ -0,0 +1,15 @@
+// Copyright 2015-2021 SWIM.AI inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub mod fs;


Test patch:
diff --git a/swim_client/test_server/Cargo.toml b/swim_client/test_server/Cargo.toml
index c0cff97cd..ad667de25 100644
--- a/swim_client/test_server/Cargo.toml
+++ b/swim_client/test_server/Cargo.toml
@@ -7,5 +7,6 @@ edition = "2018"
 [dependencies]
 futures = "0.3.4"
 swim_server = { path = "../../swim_server" }
+server_store = { path = "../../swim_server/server_store" }
 tracing = "0.1.16"
 tracing-futures = "0.2.4"
\ No newline at end of file
diff --git a/swim_client/test_server/src/lib.rs b/swim_client/test_server/src/lib.rs
index 3de9295cb..5ece47c32 100644
--- a/swim_client/test_server/src/lib.rs
+++ b/swim_client/test_server/src/lib.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use server_store::plane::SwimPlaneStore;
+use server_store::NoStore;
 use std::fmt::Debug;
 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
 use swim_server::agent::command_lifecycle;
@@ -25,11 +27,10 @@ use swim_server::agent::SwimAgent;
 use swim_server::interface::{ServerHandle, SwimServer, SwimServerBuilder, SwimServerConfig};
 use swim_server::RoutePattern;
 
-pub async fn build_server() -> (SwimServer, ServerHandle) {
+pub async fn build_server() -> (SwimServer<SwimPlaneStore<NoStore>>, ServerHandle) {
     let address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
     let mut swim_server_builder =
-        SwimServerBuilder::temporary_store(SwimServerConfig::default(), "test")
-            .expect("Failed to build transient store");
+        SwimServerBuilder::no_store(SwimServerConfig::default()).expect("Failed to build store");
     let mut plane_builder = swim_server_builder.plane_builder("test").unwrap();
 
     plane_builder
diff --git a/swim_server/src/agent/lane/model/map/map_store/tests.rs b/swim_server/server_store/src/agent/lane/model/map/tests.rs
similarity index 62%
rename from swim_server/src/agent/lane/model/map/map_store/tests.rs
rename to swim_server/server_store/src/agent/lane/model/map/tests.rs
index fe35ee870..88118092e 100644
--- a/swim_server/src/agent/lane/model/map/map_store/tests.rs
+++ b/swim_server/server_store/src/agent/lane/model/map/tests.rs
@@ -12,37 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::lane::model::map::summaries_to_events;
-use crate::agent::lane::store::error::StoreErrorHandler;
-use crate::agent::model::map::map_store::{MapDataModel, MapLaneStoreIo};
-use crate::agent::model::map::MapLane;
-use crate::agent::store::NodeStore;
-use crate::agent::StoreIo;
-use crate::plane::store::mock::MockPlaneStore;
-use crate::store::{StoreEngine, StoreKey};
-use futures::future::{ready, Ready};
-use futures::stream::{empty, Empty};
+use crate::agent::lane::model::map::MapDataModel;
+use crate::agent::NodeStore;
+use crate::plane::mock::MockPlaneStore;
+use crate::server::{StoreEngine, StoreKey};
 use std::collections::HashMap;
-use std::num::NonZeroUsize;
 use std::ops::Deref;
 use std::sync::{Arc, Mutex};
-use stm::transaction::{atomically, RetryManager};
-use store::{serialize, EngineInfo, StoreError};
-
-struct ExactlyOnce;
-
-impl RetryManager for ExactlyOnce {
-    type ContentionManager = Empty<()>;
-    type RetryFut = Ready<bool>;
-
-    fn contention_manager(&self) -> Self::ContentionManager {
-        empty()
-    }
-
-    fn retry(&mut self) -> Self::RetryFut {
-        ready(false)
-    }
-}
+use swim_store::{serialize, EngineInfo, StoreError};
 
 #[derive(Debug, Clone)]
 struct TrackingMapStore {
@@ -275,106 +252,106 @@ fn model_crud() {
     }
 }
 
-#[tokio::test]
-async fn io_load_some() {
-    let mut expected_id_0 = HashMap::new();
-    expected_id_0.insert("a".to_string(), Arc::new(1));
-    expected_id_0.insert("b".to_string(), Arc::new(2));
-    expected_id_0.insert("c".to_string(), Arc::new(3));
-    expected_id_0.insert("d".to_string(), Arc::new(4));
-    expected_id_0.insert("e".to_string(), Arc::new(5));
-
-    let mut expected_id_1 = HashMap::new();
-    expected_id_1.insert("f".to_string(), Arc::new(6));
-    expected_id_1.insert("g".to_string(), Arc::new(7));
-    expected_id_1.insert("h".to_string(), Arc::new(8));
-    expected_id_1.insert("i".to_string(), Arc::new(9));
-    expected_id_1.insert("j".to_string(), Arc::new(10));
-
-    let mut initial = expected_id_0
-        .clone()
-        .into_iter()
-        .fold(HashMap::new(), |mut map, (k, v)| {
-            let store_key = make_store_key(0, k);
-            map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
-            map
-        });
-
-    let serialized_id_1 = expected_id_1
-        .into_iter()
-        .fold(HashMap::new(), |mut map, (k, v)| {
-            let store_key = make_store_key(1, k);
-            map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
-            map
-        });
-
-    initial.extend(serialized_id_1);
-
-    let values = Arc::new(Mutex::new(initial));
-    let store = TrackingMapStore {
-        values: values.clone(),
-    };
-
-    let model = MapDataModel::new(store, 0);
-    let (lane, observer) =
-        MapLane::<String, i32>::store_observable(&model, NonZeroUsize::new(8).unwrap());
-    let events = summaries_to_events::<String, i32>(observer.clone());
-    let store_io = MapLaneStoreIo::new(events, model);
-
-    let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
-
-    let lane_snapshot: HashMap<String, Arc<i32>> =
-        atomically(&lane.snapshot(), ExactlyOnce).await.unwrap();
-    assert_eq!(lane_snapshot, expected_id_0);
-}
-
-#[tokio::test]
-async fn io_crud() {
-    let mut initial = HashMap::new();
-    initial.insert("a".to_string(), Arc::new(1));
-    initial.insert("b".to_string(), Arc::new(2));
-    initial.insert("c".to_string(), Arc::new(3));
-    initial.insert("d".to_string(), Arc::new(4));
-    initial.insert("e".to_string(), Arc::new(5));
-
-    let initial = initial
-        .clone()
-        .into_iter()
-        .fold(HashMap::new(), |mut map, (k, v)| {
-            let store_key = make_store_key(0, k);
-            map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
-            map
-        });
-
-    let values = Arc::new(Mutex::new(initial));
-    let store = TrackingMapStore {
-        values: values.clone(),
-    };
-
-    let model = MapDataModel::new(store, 0);
-
-    let (lane, observer) =
-        MapLane::<String, i32>::store_observable(&model, NonZeroUsize::new(8).unwrap());
-    let events = summaries_to_events::<String, i32>(observer.clone());
-
-    let store_io = MapLaneStoreIo::new(events, model);
-
-    let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
-
-    let update_stm = lane.update("b".to_string(), Arc::new(13));
-    assert!(atomically(&update_stm, ExactlyOnce).await.is_ok());
-
-    let delete_stm = lane.remove("c".to_string());
-    assert!(atomically(&delete_stm, ExactlyOnce).await.is_ok());
-
-    let lane_snapshot: HashMap<String, Arc<i32>> =
-        atomically(&lane.snapshot(), ExactlyOnce).await.unwrap();
-
-    let mut expected = HashMap::new();
-    expected.insert("a".to_string(), Arc::new(1));
-    expected.insert("b".to_string(), Arc::new(13));
-    expected.insert("d".to_string(), Arc::new(4));
-    expected.insert("e".to_string(), Arc::new(5));
-
-    assert_eq!(lane_snapshot, expected);
-}
+// #[tokio::test]
+// async fn io_load_some() {
+//     let mut expected_id_0 = HashMap::new();
+//     expected_id_0.insert("a".to_string(), Arc::new(1));
+//     expected_id_0.insert("b".to_string(), Arc::new(2));
+//     expected_id_0.insert("c".to_string(), Arc::new(3));
+//     expected_id_0.insert("d".to_string(), Arc::new(4));
+//     expected_id_0.insert("e".to_string(), Arc::new(5));
+//
+//     let mut expected_id_1 = HashMap::new();
+//     expected_id_1.insert("f".to_string(), Arc::new(6));
+//     expected_id_1.insert("g".to_string(), Arc::new(7));
+//     expected_id_1.insert("h".to_string(), Arc::new(8));
+//     expected_id_1.insert("i".to_string(), Arc::new(9));
+//     expected_id_1.insert("j".to_string(), Arc::new(10));
+//
+//     let mut initial = expected_id_0
+//         .clone()
+//         .into_iter()
+//         .fold(HashMap::new(), |mut map, (k, v)| {
+//             let store_key = make_store_key(0, k);
+//             map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
+//             map
+//         });
+//
+//     let serialized_id_1 = expected_id_1
+//         .into_iter()
+//         .fold(HashMap::new(), |mut map, (k, v)| {
+//             let store_key = make_store_key(1, k);
+//             map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
+//             map
+//         });
+//
+//     initial.extend(serialized_id_1);
+//
+//     let values = Arc::new(Mutex::new(initial));
+//     let store = TrackingMapStore {
+//         values: values.clone(),
+//     };
+//
+//     let model = MapDataModel::new(store, 0);
+//     let (lane, observer) =
+//         MapLane::<String, i32>::store_observable(&model, NonZeroUsize::new(8).unwrap());
+//     let events = summaries_to_events::<String, i32>(observer.clone());
+//     let store_io = MapLaneStoreIo::new(events, model);
+//
+//     let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
+//
+//     let lane_snapshot: HashMap<String, Arc<i32>> =
+//         atomically(&lane.snapshot(), ExactlyOnce).await.unwrap();
+//     assert_eq!(lane_snapshot, expected_id_0);
+// }
+
+// #[tokio::test]
+// async fn io_crud() {
+//     let mut initial = HashMap::new();
+//     initial.insert("a".to_string(), Arc::new(1));
+//     initial.insert("b".to_string(), Arc::new(2));
+//     initial.insert("c".to_string(), Arc::new(3));
+//     initial.insert("d".to_string(), Arc::new(4));
+//     initial.insert("e".to_string(), Arc::new(5));
+//
+//     let initial = initial
+//         .clone()
+//         .into_iter()
+//         .fold(HashMap::new(), |mut map, (k, v)| {
+//             let store_key = make_store_key(0, k);
+//             map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
+//             map
+//         });
+//
+//     let values = Arc::new(Mutex::new(initial));
+//     let store = TrackingMapStore {
+//         values: values.clone(),
+//     };
+//
+//     let model = MapDataModel::new(store, 0);
+//
+//     let (lane, observer) =
+//         MapLane::<String, i32>::store_observable(&model, NonZeroUsize::new(8).unwrap());
+//     let events = summaries_to_events::<String, i32>(observer.clone());
+//
+//     let store_io = MapLaneStoreIo::new(events, model);
+//
+//     let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
+//
+//     let update_stm = lane.update("b".to_string(), Arc::new(13));
+//     assert!(atomically(&update_stm, ExactlyOnce).await.is_ok());
+//
+//     let delete_stm = lane.remove("c".to_string());
+//     assert!(atomically(&delete_stm, ExactlyOnce).await.is_ok());
+//
+//     let lane_snapshot: HashMap<String, Arc<i32>> =
+//         atomically(&lane.snapshot(), ExactlyOnce).await.unwrap();
+//
+//     let mut expected = HashMap::new();
+//     expected.insert("a".to_string(), Arc::new(1));
+//     expected.insert("b".to_string(), Arc::new(13));
+//     expected.insert("d".to_string(), Arc::new(4));
+//     expected.insert("e".to_string(), Arc::new(5));
+//
+//     assert_eq!(lane_snapshot, expected);
+// }
diff --git a/swim_server/src/agent/lane/model/value/value_store/tests.rs b/swim_server/server_store/src/agent/lane/model/value/tests.rs
similarity index 71%
rename from swim_server/src/agent/lane/model/value/value_store/tests.rs
rename to swim_server/server_store/src/agent/lane/model/value/tests.rs
index 5cde2b8c1..3dc1ff7c7 100644
--- a/swim_server/src/agent/lane/model/value/value_store/tests.rs
+++ b/swim_server/server_store/src/agent/lane/model/value/tests.rs
@@ -12,18 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::lane::store::error::StoreErrorHandler;
-use crate::agent::lane::store::StoreIo;
-use crate::agent::model::value::value_store::io::ValueLaneStoreIo;
-use crate::agent::model::value::value_store::ValueDataModel;
-use crate::agent::model::value::ValueLane;
-use crate::agent::store::NodeStore;
-use crate::plane::store::mock::MockPlaneStore;
-use crate::store::{StoreEngine, StoreKey};
+use crate::agent::lane::model::value::ValueDataModel;
+use crate::agent::NodeStore;
+use crate::plane::mock::MockPlaneStore;
+use crate::server::{StoreEngine, StoreKey};
 use std::fmt::Debug;
-use std::num::NonZeroUsize;
 use std::sync::{Arc, Mutex};
-use store::{serialize, EngineInfo, StoreError};
+use swim_store::{serialize, EngineInfo, StoreError};
 
 #[derive(Clone, Debug)]
 struct TrackingValueStore {
@@ -119,24 +114,24 @@ fn store_load() {
     }
 }
 
-#[tokio::test]
-async fn io() {
-    let store_initial = "loaded".to_string();
-    let store = TrackingValueStore {
-        value: Arc::new(Mutex::new(Some(serialize(&store_initial).unwrap()))),
-    };
-
-    let model = ValueDataModel::<TrackingValueStore, String>::new(store, 0);
-    let (lane, observer) = ValueLane::<String>::store_observable(
-        &model,
-        NonZeroUsize::new(8).unwrap(),
-        Default::default(),
-    );
-    let observer_stream = observer.into_stream();
-
-    let store_io = ValueLaneStoreIo::new(observer_stream, model);
-    let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
-
-    let lane_value = lane.load().await;
-    assert_eq!(*lane_value, store_initial);
-}
+// #[tokio::test]
+// async fn io() {
+//     let store_initial = "loaded".to_string();
+//     let store = TrackingValueStore {
+//         value: Arc::new(Mutex::new(Some(serialize(&store_initial).unwrap()))),
+//     };
+//
+//     let model = ValueDataModel::<TrackingValueStore, String>::new(store, 0);
+//     let (lane, observer) = ValueLane::<String>::store_observable(
+//         &model,
+//         NonZeroUsize::new(8).unwrap(),
+//         Default::default(),
+//     );
+//     let observer_stream = observer.into_stream();
+//
+//     let store_io = ValueLaneStoreIo::new(observer_stream, model);
+//     let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
+//
+//     let lane_value = lane.load().await;
+//     assert_eq!(*lane_value, store_initial);
+// }
diff --git a/swim_server/src/agent/lane/store/tests.rs b/swim_server/server_store/src/agent/lane/tests.rs
similarity index 95%
rename from swim_server/src/agent/lane/store/tests.rs
rename to swim_server/server_store/src/agent/lane/tests.rs
index c8794147c..44c2fe767 100644
--- a/swim_server/src/agent/lane/store/tests.rs
+++ b/swim_server/server_store/src/agent/lane/tests.rs
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::lane::store::error::{LaneStoreErrorReport, StoreErrorHandler, StoreTaskError};
-use crate::agent::lane::store::task::NodeStoreTask;
-use crate::agent::lane::store::StoreIo;
-use crate::agent::store::mock::MockNodeStore;
+use crate::agent::lane::error::{LaneStoreErrorReport, StoreErrorHandler, StoreTaskError};
+use crate::agent::lane::task::NodeStoreTask;
+use crate::agent::lane::StoreIo;
+use crate::agent::mock::MockNodeStore;
 use futures::future::pending;
 use futures::future::BoxFuture;
 use futures::Future;
 use std::collections::HashMap;
-use store::StoreError;
 use swim_common::model::time::Timestamp;
-use swim_runtime::time::timeout::timeout;
+use swim_store::StoreError;
 use swim_utilities::trigger;
+use tokio::time::timeout;
 use tokio::time::{sleep, Duration};
 
 pub fn store_err_partial_eq(expected: Vec<StoreError>, actual: Vec<StoreTaskError>) {
diff --git a/swim_server/src/agent/context/tests.rs b/swim_server/src/agent/context/tests.rs
index ff57e20aa..1bd8f0c52 100644
--- a/swim_server/src/agent/context/tests.rs
+++ b/swim_server/src/agent/context/tests.rs
@@ -13,12 +13,12 @@
 // limitations under the License.
 
 use crate::agent::context::{ContextImpl, RoutingContext, SchedulerContext};
-use crate::agent::store::mock::MockNodeStore;
-use crate::agent::store::SwimNodeStore;
 use crate::agent::tests::test_clock::TestClock;
 use crate::agent::AgentContext;
 use crate::meta::meta_context_sink;
-use crate::plane::store::mock::MockPlaneStore;
+use server_store::agent::mock::MockNodeStore;
+use server_store::agent::SwimNodeStore;
+use server_store::plane::mock::MockPlaneStore;
 use std::collections::HashMap;
 use std::sync::Arc;
 use swim_runtime::task;
diff --git a/swim_server/src/agent/dispatch/tests/mock.rs b/swim_server/src/agent/dispatch/tests/mock.rs
index 583134806..5ec394372 100644
--- a/swim_server/src/agent/dispatch/tests/mock.rs
+++ b/swim_server/src/agent/dispatch/tests/mock.rs
@@ -18,11 +18,8 @@ use crate::agent::lane::channels::update::UpdateError;
 use crate::agent::lane::channels::uplink::spawn::UplinkErrorReport;
 use crate::agent::lane::channels::uplink::UplinkError;
 use crate::agent::lane::channels::AgentExecutionConfig;
-use crate::agent::store::mock::MockNodeStore;
-use crate::agent::store::SwimNodeStore;
 use crate::agent::{AttachError, Eff, LaneIo};
 use crate::meta::metric::{aggregator_sink, NodeMetricAggregator};
-use crate::plane::store::mock::MockPlaneStore;
 use crate::routing::error::RouterError;
 use crate::routing::{
     ConnectionDropped, Route, RoutingAddr, ServerRouter, TaggedClientEnvelope, TaggedEnvelope,
@@ -31,6 +28,9 @@ use crate::routing::{
 use futures::future::BoxFuture;
 use futures::FutureExt;
 use parking_lot::Mutex;
+use server_store::agent::mock::MockNodeStore;
+use server_store::agent::SwimNodeStore;
+use server_store::plane::mock::MockPlaneStore;
 use std::collections::hash_map::Entry;
 use std::collections::HashMap;
 use std::sync::Arc;
diff --git a/swim_server/src/agent/lane/channels/task/tests.rs b/swim_server/src/agent/lane/channels/task/tests.rs
index 41720a173..1f802aace 100644
--- a/swim_server/src/agent/lane/channels/task/tests.rs
+++ b/swim_server/src/agent/lane/channels/task/tests.rs
@@ -27,8 +27,6 @@ use crate::agent::lane::model::action::{Action, ActionLane};
 use crate::agent::lane::model::command::{Command, CommandLane};
 use crate::agent::lane::model::DeferredSubscription;
 use crate::agent::model::supply::SupplyLane;
-use crate::agent::store::mock::MockNodeStore;
-use crate::agent::store::SwimNodeStore;
 use crate::agent::Eff;
 use crate::meta::accumulate_metrics;
 use crate::meta::log::make_node_logger;
@@ -38,7 +36,6 @@ use crate::meta::metric::node::NodePulse;
 use crate::meta::metric::uplink::{UplinkObserver, WarpUplinkPulse};
 use crate::meta::metric::{aggregator_sink, AggregatorError, NodeMetricAggregator};
 use crate::meta::pulse::PulseLanes;
-use crate::plane::store::mock::MockPlaneStore;
 use crate::routing::error::RouterError;
 use crate::routing::{
     ConnectionDropped, Route, RoutingAddr, ServerRouter, TaggedClientEnvelope, TaggedEnvelope,
@@ -48,6 +45,9 @@ use futures::future::{join, join3, ready, BoxFuture};
 use futures::stream::{once, BoxStream, FusedStream};
 use futures::{Future, FutureExt, Stream, StreamExt};
 use pin_utils::pin_mut;
+use server_store::agent::mock::MockNodeStore;
+use server_store::agent::SwimNodeStore;
+use server_store::plane::mock::MockPlaneStore;
 use std::collections::{HashMap, HashSet};
 use std::convert::{identity, TryFrom};
 use std::fmt::Debug;
diff --git a/swim_server/src/agent/lane/channels/uplink/spawn/tests.rs b/swim_server/src/agent/lane/channels/uplink/spawn/tests.rs
index 3ad7d9a68..ab0ab0637 100644
--- a/swim_server/src/agent/lane/channels/uplink/spawn/tests.rs
+++ b/swim_server/src/agent/lane/channels/uplink/spawn/tests.rs
@@ -20,11 +20,8 @@ use crate::agent::lane::channels::uplink::{
     PeelResult, UplinkAction, UplinkError, UplinkStateMachine,
 };
 use crate::agent::lane::channels::{AgentExecutionConfig, LaneMessageHandler, TaggedAction};
-use crate::agent::store::mock::MockNodeStore;
-use crate::agent::store::SwimNodeStore;
 use crate::agent::Eff;
 use crate::meta::metric::{aggregator_sink, NodeMetricAggregator};
-use crate::plane::store::mock::MockPlaneStore;
 use crate::routing::error::RouterError;
 use crate::routing::{
     ConnectionDropped, Route, RoutingAddr, ServerRouter, TaggedEnvelope, TaggedSender,
@@ -34,6 +31,9 @@ use futures::stream::iter;
 use futures::stream::{BoxStream, FusedStream};
 use futures::{FutureExt, Stream, StreamExt};
 use pin_utils::pin_mut;
+use server_store::agent::mock::MockNodeStore;
+use server_store::agent::SwimNodeStore;
+use server_store::plane::mock::MockPlaneStore;
 use std::collections::{HashMap, HashSet};
 use std::num::NonZeroUsize;
 use std::sync::Arc;
diff --git a/swim_server/src/agent/lane/channels/uplink/stateless/tests.rs b/swim_server/src/agent/lane/channels/uplink/stateless/tests.rs
index 9e2dd39a6..d0fe4c31d 100644
--- a/swim_server/src/agent/lane/channels/uplink/stateless/tests.rs
+++ b/swim_server/src/agent/lane/channels/uplink/stateless/tests.rs
@@ -28,15 +28,15 @@ use swim_common::warp::path::RelativePath;
 use crate::agent::lane::channels::uplink::stateless::StatelessUplinks;
 use crate::agent::lane::channels::uplink::{AddressedUplinkMessage, UplinkAction, UplinkKind};
 use crate::agent::lane::channels::TaggedAction;
-use crate::agent::store::mock::MockNodeStore;
-use crate::agent::store::SwimNodeStore;
 use crate::meta::metric::uplink::{
     uplink_observer, TaggedWarpUplinkProfile, UplinkObserver, UplinkProfileSender,
     WarpUplinkProfile,
 };
 use crate::meta::metric::{aggregator_sink, NodeMetricAggregator};
-use crate::plane::store::mock::MockPlaneStore;
 use crate::routing::error::RouterError;
+use server_store::agent::mock::MockNodeStore;
+use server_store::agent::SwimNodeStore;
+use server_store::plane::mock::MockPlaneStore;
 use std::ops::Add;
 use swim_common::routing::ResolutionError;
 use swim_utilities::routing::uri::RelativeUri;
diff --git a/swim_server/src/agent/lane/model/map/tests.rs b/swim_server/src/agent/lane/model/map/tests.rs
index 5e7a27e65..52fc0dd2b 100644
--- a/swim_server/src/agent/lane/model/map/tests.rs
+++ b/swim_server/src/agent/lane/model/map/tests.rs
@@ -12,17 +12,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::agent::lane::model::map::{MapLane, MapLaneEvent, MapSubscriber, MapUpdate};
+use crate::agent::lane::model::map::{
+    summaries_to_events, to_map_store_event, MapLane, MapLaneEvent, MapSubscriber, MapUpdate,
+};
 use crate::agent::lane::model::DeferredSubscription;
 use crate::agent::lane::tests::ExactlyOnce;
+use futures::future::ready;
 use futures::{FutureExt, Stream, StreamExt};
+use server_store::agent::lane::error::StoreErrorHandler;
+use server_store::agent::lane::map::{MapDataModel, MapLaneStoreIo};
+use server_store::agent::lane::StoreIo;
+use server_store::agent::NodeStore;
+use server_store::plane::mock::MockPlaneStore;
+use server_store::{StoreEngine, StoreKey};
 use std::collections::HashMap;
 use std::num::NonZeroUsize;
-use std::sync::Arc;
+use std::ops::Deref;
+use std::sync::{Arc, Mutex};
 use stm::stm::Stm;
 use stm::transaction::{atomically, TransactionRunner};
 use swim_common::form::Form;
 use swim_common::model::{Attr, Item, Value};
+use swim_store::{serialize, EngineInfo, StoreError};
 
 fn buffer_size() -> NonZeroUsize {
     NonZeroUsize::new(16).unwrap()
@@ -507,3 +518,208 @@ async fn update_direct_multiple_test() {
             .is_ok());
     }
 }
+
+fn make_store_key(lane_id: u64, key: String) -> StoreKey {
+    StoreKey::Map {
+        lane_id,
+        key: Some(serialize(&key).expect("Failed to serialize store key")),
+    }
+}
+
+#[derive(Debug, Clone)]
+struct TrackingMapStore {
+    values: Arc<Mutex<HashMap<Vec<u8>, Vec<u8>>>>,
+}
+
+impl NodeStore for TrackingMapStore {
+    type Delegate = MockPlaneStore;
+
+    fn engine_info(&self) -> EngineInfo {
+        EngineInfo {
+            path: "tracking".to_string(),
+            kind: "tracking".to_string(),
+        }
+    }
+
+    fn lane_id_of(&self, _lane: &str) -> Result<u64, StoreError> {
+        Ok(0)
+    }
+
+    fn load_ranged_snapshot<F, K, V>(
+        &self,
+        prefix: StoreKey,
+        map_fn: F,
+    ) -> Result<Option<Vec<(K, V)>>, StoreError>
+    where
+        F: for<'i> Fn(&'i [u8], &'i [u8]) -> Result<(K, V), StoreError>,
+    {
+        let prefix = serialize(&prefix)?;
+        let guard = self.values.lock().unwrap();
+        let mut entries = guard.deref().clone().into_iter();
+        let mapped = entries.try_fold(Vec::new(), |mut entries, (k, v)| {
+            if k.starts_with(&prefix) {
+                match map_fn(k.as_ref(), v.as_ref()) {
+                    Ok((k, v)) => {
+                        entries.push((k, v));
+                        Ok(entries)
+                    }
+                    Err(e) => Err(e),
+                }
+            } else {
+                Ok(entries)
+            }
+        })?;
+
+        if mapped.is_empty() {
+            Ok(None)
+        } else {
+            Ok(Some(mapped))
+        }
+    }
+}
+
+impl StoreEngine for TrackingMapStore {
+    fn put(&self, key: StoreKey, value: &[u8]) -> Result<(), StoreError> {
+        match key {
+            k @ StoreKey::Map { .. } => {
+                let mut guard = self.values.lock().unwrap();
+                guard.insert(serialize(&k)?, value.to_vec());
+                Ok(())
+            }
+            StoreKey::Value { .. } => {
+                panic!("Expected a map key")
+            }
+        }
+    }
+
+    fn get(&self, key: StoreKey) -> Result<Option<Vec<u8>>, StoreError> {
+        match key {
+            k @ StoreKey::Map { .. } => {
+                let guard = self.values.lock().unwrap();
+                Ok(guard.get(serialize(&k)?.as_slice()).map(|o| o.clone()))
+            }
+            StoreKey::Value { .. } => {
+                panic!("Expected a map key")
+            }
+        }
+    }
+
+    fn delete(&self, key: StoreKey) -> Result<(), StoreError> {
+        match key {
+            k @ StoreKey::Map { .. } => {
+                let mut guard = self.values.lock().unwrap();
+                guard.remove(serialize(&k)?.as_slice());
+                Ok(())
+            }
+            StoreKey::Value { .. } => {
+                panic!("Expected a map key")
+            }
+        }
+    }
+}
+
+#[tokio::test]
+async fn io_load_some() {
+    let mut expected_id_0 = HashMap::new();
+    expected_id_0.insert("a".to_string(), Arc::new(1));
+    expected_id_0.insert("b".to_string(), Arc::new(2));
+    expected_id_0.insert("c".to_string(), Arc::new(3));
+    expected_id_0.insert("d".to_string(), Arc::new(4));
+    expected_id_0.insert("e".to_string(), Arc::new(5));
+
+    let mut expected_id_1 = HashMap::new();
+    expected_id_1.insert("f".to_string(), Arc::new(6));
+    expected_id_1.insert("g".to_string(), Arc::new(7));
+    expected_id_1.insert("h".to_string(), Arc::new(8));
+    expected_id_1.insert("i".to_string(), Arc::new(9));
+    expected_id_1.insert("j".to_string(), Arc::new(10));
+
+    let mut initial = expected_id_0
+        .clone()
+        .into_iter()
+        .fold(HashMap::new(), |mut map, (k, v)| {
+            let store_key = make_store_key(0, k);
+            map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
+            map
+        });
+
+    let serialized_id_1 = expected_id_1
+        .into_iter()
+        .fold(HashMap::new(), |mut map, (k, v)| {
+            let store_key = make_store_key(1, k);
+            map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
+            map
+        });
+
+    initial.extend(serialized_id_1);
+
+    let values = Arc::new(Mutex::new(initial));
+    let store = TrackingMapStore {
+        values: values.clone(),
+    };
+
+    let model = MapDataModel::new(store, 0);
+    let (lane, observer) =
+        MapLane::<String, i32>::store_observable(&model, NonZeroUsize::new(8).unwrap());
+    let events = summaries_to_events::<String, i32>(observer.clone())
+        .filter_map(|e| ready(to_map_store_event(e)));
+    let store_io = MapLaneStoreIo::new(events, model);
+
+    let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
+
+    let lane_snapshot: HashMap<String, Arc<i32>> =
+        atomically(&lane.snapshot(), ExactlyOnce).await.unwrap();
+    assert_eq!(lane_snapshot, expected_id_0);
+}
+
+#[tokio::test]
+async fn io_crud() {
+    let mut initial = HashMap::new();
+    initial.insert("a".to_string(), Arc::new(1));
+    initial.insert("b".to_string(), Arc::new(2));
+    initial.insert("c".to_string(), Arc::new(3));
+    initial.insert("d".to_string(), Arc::new(4));
+    initial.insert("e".to_string(), Arc::new(5));
+
+    let initial = initial
+        .clone()
+        .into_iter()
+        .fold(HashMap::new(), |mut map, (k, v)| {
+            let store_key = make_store_key(0, k);
+            map.insert(serialize(&store_key).unwrap(), serialize(&v).unwrap());
+            map
+        });
+
+    let values = Arc::new(Mutex::new(initial));
+    let store = TrackingMapStore {
+        values: values.clone(),
+    };
+
+    let model = MapDataModel::new(store, 0);
+
+    let (lane, observer) =
+        MapLane::<String, i32>::store_observable(&model, NonZeroUsize::new(8).unwrap());
+    let events = summaries_to_events::<String, i32>(observer.clone())
+        .filter_map(|e| ready(to_map_store_event(e)));
+
+    let store_io = MapLaneStoreIo::new(events, model);
+
+    let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
+
+    let update_stm = lane.update("b".to_string(), Arc::new(13));
+    assert!(atomically(&update_stm, ExactlyOnce).await.is_ok());
+
+    let delete_stm = lane.remove("c".to_string());
+    assert!(atomically(&delete_stm, ExactlyOnce).await.is_ok());
+
+    let lane_snapshot: HashMap<String, Arc<i32>> =
+        atomically(&lane.snapshot(), ExactlyOnce).await.unwrap();
+
+    let mut expected = HashMap::new();
+    expected.insert("a".to_string(), Arc::new(1));
+    expected.insert("b".to_string(), Arc::new(13));
+    expected.insert("d".to_string(), Arc::new(4));
+    expected.insert("e".to_string(), Arc::new(5));
+
+    assert_eq!(lane_snapshot, expected);
+}
diff --git a/swim_server/src/agent/lane/model/value/tests.rs b/swim_server/src/agent/lane/model/value/tests.rs
index 6eb6aaa79..41776e1b0 100644
--- a/swim_server/src/agent/lane/model/value/tests.rs
+++ b/swim_server/src/agent/lane/model/value/tests.rs
@@ -15,9 +15,14 @@
 use super::*;
 use crate::agent::lane::tests::ExactlyOnce;
 use futures::future::join;
+use server_store::agent::lane::error::StoreErrorHandler;
+use server_store::plane::mock::MockPlaneStore;
+use server_store::{StoreEngine, StoreKey};
+use std::sync::Mutex;
 use std::time::Duration;
 use stm::transaction::atomically;
 use stm::var::observer::ObserverSubscriber;
+use swim_store::{serialize, EngineInfo, StoreError};
 use swim_utilities::sync::topic::TryRecvError;
 use swim_utilities::trigger;
 use tokio::sync::mpsc;
@@ -118,3 +123,72 @@ async fn value_lane_subscribe() {
     let result = tokio::time::timeout(Duration::from_secs(5), join(t1, t2)).await;
     assert!(matches!(result, Ok((Ok(_), Ok(_)))));
 }
+
+#[derive(Clone, Debug)]
+struct TrackingValueStore {
+    value: Arc<Mutex<Option<Vec<u8>>>>,
+}
+
+impl NodeStore for TrackingValueStore {
+    type Delegate = MockPlaneStore;
+
+    fn engine_info(&self) -> EngineInfo {
+        EngineInfo {
+            path: "tracking".to_string(),
+            kind: "tracking".to_string(),
+        }
+    }
+
+    fn lane_id_of(&self, _lane: &str) -> Result<u64, StoreError> {
+        Ok(0)
+    }
+
+    fn load_ranged_snapshot<F, K, V>(
+        &self,
+        _prefix: StoreKey,
+        _map_fn: F,
+    ) -> Result<Option<Vec<(K, V)>>, StoreError>
+    where
+        F: for<'i> Fn(&'i [u8], &'i [u8]) -> Result<(K, V), StoreError>,
+    {
+        panic!("Unexpected snapshot request")
+    }
+}
+
+impl StoreEngine for TrackingValueStore {
+    fn put(&self, _key: StoreKey, value: &[u8]) -> Result<(), StoreError> {
+        self.value.lock().unwrap().replace(value.to_vec());
+        Ok(())
+    }
+
+    fn get(&self, _key: StoreKey) -> Result<Option<Vec<u8>>, StoreError> {
+        Ok(self.value.lock().unwrap().clone())
+    }
+
+    fn delete(&self, _key: StoreKey) -> Result<(), StoreError> {
+        self.value.lock().unwrap().take();
+        Ok(())
+    }
+}
+
+#[tokio::test]
+async fn io() {
+    let store_initial = "loaded".to_string();
+    let store = TrackingValueStore {
+        value: Arc::new(Mutex::new(Some(serialize(&store_initial).unwrap()))),
+    };
+
+    let model = ValueDataModel::<TrackingValueStore, String>::new(store, 0);
+    let (lane, observer) = ValueLane::<String>::store_observable(
+        &model,
+        NonZeroUsize::new(8).unwrap(),
+        Default::default(),
+    );
+    let observer_stream = observer.into_stream();
+
+    let store_io = ValueLaneStoreIo::new(observer_stream, model);
+    let _task_handle = tokio::spawn(store_io.attach(StoreErrorHandler::new(0)));
+
+    let lane_value = lane.load().await;
+    assert_eq!(*lane_value, store_initial);
+}
diff --git a/swim_server/src/agent/tests/data_macro_agent.rs b/swim_server/src/agent/tests/data_macro_agent.rs
index 4994b7190..aa0d9e6e2 100644
--- a/swim_server/src/agent/tests/data_macro_agent.rs
+++ b/swim_server/src/agent/tests/data_macro_agent.rs
@@ -21,7 +21,6 @@ use crate::agent::lane::model::value::{ValueLane, ValueLaneEvent};
 use crate::agent::lane::model::{action, command};
 use crate::agent::lane::tests::ExactlyOnce;
 use crate::agent::lifecycle::AgentLifecycle;
-use crate::agent::store::mock::MockNodeStore;
 use crate::agent::tests::stub_router::SingleChannelRouter;
 use crate::agent::tests::test_clock::TestClock;
 use crate::agent::AgentContext;
@@ -31,6 +30,7 @@ use crate::routing::RoutingAddr;
 use crate::{
     action_lifecycle, agent_lifecycle, command_lifecycle, map_lifecycle, value_lifecycle, SwimAgent,
 };
+use server_store::agent::mock::MockNodeStore;
 use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::fmt::Debug;
diff --git a/swim_server/src/agent/tests/mod.rs b/swim_server/src/agent/tests/mod.rs
index 790f047ab..5dee773a8 100644
--- a/swim_server/src/agent/tests/mod.rs
+++ b/swim_server/src/agent/tests/mod.rs
@@ -12,12 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#[cfg(feature = "persistence")]
+mod store_agent;
+
 mod data_macro_agent;
 mod declarive_macro_agent;
 mod derive;
 mod reporting_agent;
 mod reporting_macro_agent;
-mod store_agent;
 pub(crate) mod test_clock;
 
 use crate::agent::lane::channels::AgentExecutionConfig;
@@ -30,7 +32,6 @@ use crate::agent::lane::model::map::{MapLane, MapLaneEvent};
 use crate::agent::lane::model::value::{ValueLane, ValueLaneEvent};
 use crate::agent::lane::LaneModel;
 use crate::agent::lifecycle::AgentLifecycle;
-use crate::agent::store::mock::MockNodeStore;
 use crate::agent::tests::reporting_agent::TestAgentConfig;
 use crate::agent::tests::reporting_macro_agent::ReportingAgentEvent;
 use crate::agent::tests::stub_router::SingleChannelRouter;
@@ -46,6 +47,7 @@ use crate::plane::RouteAndParameters;
 use crate::routing::RoutingAddr;
 use futures::future::{join, BoxFuture};
 use futures::Stream;
+use server_store::agent::mock::MockNodeStore;
 use std::collections::HashMap;
 use std::fmt::Debug;
 use std::future::Future;
diff --git a/swim_server/src/agent/tests/reporting_agent.rs b/swim_server/src/agent/tests/reporting_agent.rs
index b7ed118eb..badafde30 100644
--- a/swim_server/src/agent/tests/reporting_agent.rs
+++ b/swim_server/src/agent/tests/reporting_agent.rs
@@ -25,13 +25,13 @@ use crate::agent::lane::model::map::{MapLane, MapLaneEvent};
 use crate::agent::lane::model::value::{ValueLane, ValueLaneEvent};
 use crate::agent::lane::tests::ExactlyOnce;
 use crate::agent::lifecycle::AgentLifecycle;
-use crate::agent::store::NodeStore;
 use crate::agent::tests::reporting_macro_agent::ReportingAgentEvent;
 use crate::agent::{
     AgentContext, DynamicAgentIo, DynamicLaneTasks, LaneConfig, LaneParts, LaneTasks, SwimAgent,
 };
 use futures::future::{ready, BoxFuture, Ready};
 use futures::FutureExt;
+use server_store::agent::NodeStore;
 use std::collections::HashMap;
 use std::fmt::Debug;
 use std::sync::Arc;
diff --git a/swim_server/src/agent/tests/store_agent.rs b/swim_server/src/agent/tests/store_agent.rs
index 0c9c862be..ad1dd3d30 100644
--- a/swim_server/src/agent/tests/store_agent.rs
+++ b/swim_server/src/agent/tests/store_agent.rs
@@ -21,37 +21,33 @@ use crate::agent::lane::tests::ExactlyOnce;
 use crate::agent::lifecycle::AgentLifecycle;
 use crate::agent::model::map::MapLane;
 use crate::agent::model::value::{ValueLane, ValueLaneEvent};
-use crate::agent::store::NodeStore;
 use crate::agent::tests::stub_router::SingleChannelRouter;
 use crate::agent::{
     AgentContext, DynamicAgentIo, DynamicLaneTasks, LaneConfig, LaneParts, SwimAgent, TestClock,
 };
 use crate::agent::{IoPair, LaneTasks};
 use crate::plane::provider::AgentProvider;
-use crate::plane::store::{PlaneStore, SwimPlaneStore};
 use crate::plane::RouteAndParameters;
 use crate::routing::RoutingAddr;
-use crate::store::fs::Dir;
-use crate::store::keystore::{KeyStore, COUNTER_BYTES};
-use crate::store::{
-    default_keyspaces, KeyspaceName, RocksDatabase, RocksOpts, StoreEngine, StoreKey,
-};
 use futures::future::ready;
 use futures::future::{BoxFuture, Ready};
+use server_store::agent::NodeStore;
+use server_store::keystore::{KeyStore, COUNTER_BYTES};
+use server_store::plane::{PlaneStore, SwimPlaneStore};
+use server_store::rocks::RocksOpts;
+use server_store::rocks::{default_keyspaces, RocksDatabase};
+use server_store::{KeyspaceName, StoreEngine, StoreKey};
 use std::collections::HashMap;
 use std::num::NonZeroUsize;
 use std::sync::Arc;
 use stm::stm::Stm;
 use stm::transaction::atomically;
-use store::engines::StoreBuilder;
-use store::keyspaces::KeyspaceByteEngine;
-use store::StoreError;
-use store::{deserialize, serialize};
+use swim_store::{deserialize, serialize, KeyspaceByteEngine, StoreBuilder, StoreError};
+use swim_utilities::io::fs::Dir;
 use swim_utilities::routing::uri::RelativeUri;
 use tokio::sync::mpsc;
 use tokio::time::Duration;
 use tokio_stream::wrappers::ReceiverStream;
-
 const INTERVAL: Duration = Duration::from_millis(1);
 const MAX_PERIODS: i32 = 10;
 
diff --git a/swim_server/src/meta/info/tests.rs b/swim_server/src/meta/info/tests.rs
index 2b999eeba..f15f68e96 100644
--- a/swim_server/src/meta/info/tests.rs
+++ b/swim_server/src/meta/info/tests.rs
@@ -15,7 +15,6 @@
 use crate::agent::lane::channels::AgentExecutionConfig;
 use crate::agent::lane::model::map::MapLane;
 use crate::agent::lane::model::value::ValueLane;
-use crate::agent::store::mock::MockNodeStore;
 use crate::agent::{agent_lifecycle, map_lifecycle, value_lifecycle, SwimAgent, TestClock};
 use crate::meta::info::{LaneInfo, LaneKind};
 use crate::plane::provider::AgentProvider;
@@ -26,6 +25,7 @@ use crate::routing::{
 };
 use futures::future::BoxFuture;
 use futures::FutureExt;
+use server_store::agent::mock::MockNodeStore;
 use std::collections::{HashMap, HashSet};
 use std::convert::TryFrom;
 use std::num::NonZeroUsize;
diff --git a/swim_server/src/meta/log/tests.rs b/swim_server/src/meta/log/tests.rs
index 880f41ab0..395c5722f 100644
--- a/swim_server/src/meta/log/tests.rs
+++ b/swim_server/src/meta/log/tests.rs
@@ -16,7 +16,6 @@ use crate::agent::lane::channels::AgentExecutionConfig;
 use crate::agent::lane::model::map::{MapLane, MapLaneEvent};
 use crate::agent::lane::model::supply::SupplyLane;
 use crate::agent::lane::model::value::ValueLane;
-use crate::agent::store::mock::MockNodeStore;
 use crate::agent::{
     agent_lifecycle, map_lifecycle, value_lifecycle, AgentContext, SwimAgent, TestClock,
 };
@@ -30,6 +29,7 @@ use crate::routing::{
 };
 use futures::future::BoxFuture;
 use futures::FutureExt;
+use server_store::agent::mock::MockNodeStore;
 use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::num::NonZeroUsize;
diff --git a/swim_server/src/plane/spec/tests.rs b/swim_server/src/plane/spec/tests.rs
index 15c976831..f29545f5c 100644
--- a/swim_server/src/plane/spec/tests.rs
+++ b/swim_server/src/plane/spec/tests.rs
@@ -15,18 +15,18 @@
 use crate::agent::context::AgentExecutionContext;
 use crate::agent::lane::channels::AgentExecutionConfig;
 use crate::agent::lifecycle::AgentLifecycle;
-use crate::agent::store::NodeStore;
 use crate::agent::{AgentContext, DynamicAgentIo, DynamicLaneTasks, SwimAgent};
 use crate::plane::context::PlaneContext;
 use crate::plane::error::AmbiguousRoutes;
 use crate::plane::lifecycle::PlaneLifecycle;
 use crate::plane::router::PlaneRouter;
 use crate::plane::spec::{PlaneBuilder, PlaneSpec, RouteSpec};
-use crate::plane::store::mock::MockPlaneStore;
 use crate::routing::error::RouterError;
 use crate::routing::{Route, RoutingAddr, ServerRouter, TaggedEnvelope};
 use futures::future::{ready, BoxFuture, Ready};
 use futures::FutureExt;
+use server_store::agent::NodeStore;
+use server_store::plane::mock::MockPlaneStore;
 use std::time::Duration;
 use swim_common::routing::ResolutionError;
 use swim_runtime::time::clock::Clock;
diff --git a/swim_server/src/plane/tests/fixture.rs b/swim_server/src/plane/tests/fixture.rs
index 51edd63a8..10f39d9ec 100644
--- a/swim_server/src/plane/tests/fixture.rs
+++ b/swim_server/src/plane/tests/fixture.rs
@@ -13,12 +13,10 @@
 // limitations under the License.
 
 use crate::agent::lane::channels::AgentExecutionConfig;
-use crate::agent::store::SwimNodeStore;
 use crate::agent::{AgentResult, AgentTaskResult};
 use crate::plane::context::PlaneContext;
 use crate::plane::lifecycle::PlaneLifecycle;
 use crate::plane::router::PlaneRouter;
-use crate::plane::store::mock::MockPlaneStore;
 use crate::plane::{AgentRoute, EnvChannel, RouteAndParameters};
 use crate::routing::{ServerRouter, TaggedEnvelope};
 use futures::future::BoxFuture;
@@ -26,6 +24,8 @@ use futures::{FutureExt, StreamExt};
 use http::Uri;
 use parking_lot::Mutex;
 use pin_utils::pin_mut;
+use server_store::agent::SwimNodeStore;
+use server_store::plane::mock::MockPlaneStore;
 use std::any::Any;
 use std::num::NonZeroUsize;
 use std::sync::Arc;
diff --git a/swim_server/src/plane/tests/mod.rs b/swim_server/src/plane/tests/mod.rs
index 7c13a904f..06010dcef 100644
--- a/swim_server/src/plane/tests/mod.rs
+++ b/swim_server/src/plane/tests/mod.rs
@@ -15,11 +15,11 @@
 use crate::plane::lifecycle::PlaneLifecycle;
 use crate::plane::router::PlaneRouter;
 use crate::plane::spec::{PlaneSpec, RouteSpec};
-use crate::plane::store::mock::MockPlaneStore;
 use crate::plane::tests::fixture::{ReceiveAgentRoute, SendAgentRoute, TestLifecycle};
 use crate::plane::{AgentRoute, EnvChannel};
 use crate::routing::{ServerRouter, TopLevelRouterFactory};
 use futures::future::join;
+use server_store::plane::mock::MockPlaneStore;
 use std::time::Duration;
 use swim_runtime::time::clock::Clock;
 use swim_runtime::time::timeout;
diff --git a/swim_server/store/src/engines/rocks/tests.rs b/swim_store/rocks_store/src/tests.rs
similarity index 96%
rename from swim_server/store/src/engines/rocks/tests.rs
rename to swim_store/rocks_store/src/tests.rs
index b249a9819..c56925502 100644
--- a/swim_server/store/src/engines/rocks/tests.rs
+++ b/swim_store/rocks_store/src/tests.rs
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crate::engines::{RocksEngine, RocksOpts, StoreBuilder};
-use crate::iterator::{EngineIterator, EngineRefIterator};
-use crate::keyspaces::{Keyspace, KeyspaceByteEngine, KeyspaceDef, KeyspaceResolver, Keyspaces};
-
-use crate::{deserialize, deserialize_key, serialize, StoreError};
+use crate::{RocksEngine, RocksOpts};
 use rocksdb::{MergeOperands, Options, SliceTransform};
 use std::collections::HashMap;
 use std::mem::size_of;
 use std::ops::{Deref, Range};
+use store_common::{
+    deserialize, deserialize_key, serialize, EngineIterator, EngineRefIterator, Keyspace,
+    KeyspaceByteEngine, KeyspaceDef, KeyspaceResolver, Keyspaces, StoreBuilder, StoreError,
+};
 use tempdir::TempDir;
 
 impl Deref for TransientDatabase {
diff --git a/swim_utilities/swim_test_util/src/lib.rs b/swim_utilities/swim_test_util/src/lib.rs
index 12055623f..36ff01361 100644
--- a/swim_utilities/swim_test_util/src/lib.rs
+++ b/swim_utilities/swim_test_util/src/lib.rs
@@ -1,3 +1,17 @@
+// Copyright 2015-2021 SWIM.AI inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 use futures::task;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;