diff --git a/lib/src/cf/mod.rs b/lib/src/cf/mod.rs
new file mode 100644
index 00000000..e35d8500
--- /dev/null
+++ b/lib/src/cf/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod mutations;
diff --git a/lib/src/cf/mutations.rs b/lib/src/cf/mutations.rs
new file mode 100644
index 00000000..16f70adf
--- /dev/null
+++ b/lib/src/cf/mutations.rs
@@ -0,0 +1,172 @@
+use crate::sql::array::Array;
+use crate::sql::object::Object;
+use crate::sql::thing::Thing;
+use crate::sql::value::Value;
+use crate::vs::to_u128_be;
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeMap;
+use std::fmt::{self, Display, Formatter};
+
+use derive::Store;
+
+// Mutation is a single mutation to a table.
+#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
+pub enum TableMutation {
+	// Although the Value is supposed to contain a field "id" of Thing,
+	// we do include it in the first field for convenience.
+	Set(Thing, Value),
+	Del(Thing),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
+pub struct TableMutations(pub String, pub Vec<TableMutation>);
+
+impl TableMutations {
+	pub fn new(tb: String) -> Self {
+		Self(tb, Vec::new())
+	}
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
+pub struct DatabaseMutation(pub Vec<TableMutations>);
+
+impl DatabaseMutation {
+	pub fn new() -> Self {
+		Self(Vec::new())
+	}
+}
+
+impl Default for DatabaseMutation {
+	fn default() -> Self {
+		Self::new()
+	}
+}
+// Change is a set of mutations made to a table at the specific timestamp.
+#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
+pub struct ChangeSet(pub [u8; 10], pub DatabaseMutation);
+
+impl TableMutation {
+	pub fn into_value(self) -> Value {
+		let (k, v) = match self {
+			TableMutation::Set(_t, v) => ("update".to_string(), v),
+			TableMutation::Del(t) => {
+				let mut h = BTreeMap::<String, Value>::new();
+				h.insert("id".to_string(), Value::Thing(t));
+				let o = Object::from(h);
+				("delete".to_string(), Value::Object(o))
+			}
+		};
+
+		let mut h = BTreeMap::<String, Value>::new();
+		h.insert(k, v);
+		let o = crate::sql::object::Object::from(h);
+		Value::Object(o)
+	}
+}
+
+impl DatabaseMutation {
+	pub fn into_value(self) -> Value {
+		let mut changes = Vec::<Value>::new();
+		for tbs in self.0 {
+			for tb in tbs.1 {
+				changes.push(tb.into_value());
+			}
+		}
+		Value::Array(Array::from(changes))
+	}
+}
+
+impl ChangeSet {
+	pub fn into_value(self) -> Value {
+		let mut m = BTreeMap::<String, Value>::new();
+		let vs = to_u128_be(self.0);
+		m.insert("versionstamp".to_string(), Value::from(vs));
+		m.insert("changes".to_string(), self.1.into_value());
+		let so: Object = m.into();
+		Value::Object(so)
+	}
+}
+
+impl Display for TableMutation {
+	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+		match self {
+			TableMutation::Set(id, v) => write!(f, "SET {} {}", id, v),
+			TableMutation::Del(id) => write!(f, "DEL {}", id),
+		}
+	}
+}
+
+impl Display for TableMutations {
+	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+		let tb = &self.0;
+		let muts = &self.1;
+		write!(f, "{}", tb)?;
+		muts.iter().try_for_each(|v| write!(f, "{}", v))
+	}
+}
+
+impl Display for DatabaseMutation {
+	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+		let x = &self.0;
+
+		x.iter().try_for_each(|v| write!(f, "{}", v))
+	}
+}
+
+impl Display for ChangeSet {
+	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+		let x = &self.1;
+
+		write!(f, "{}", x)
+	}
+}
+
+// WriteMutationSet is a set of mutations to be to a table at the specific timestamp.
+#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
+pub struct WriteMutationSet(pub Vec<TableMutations>);
+
+impl WriteMutationSet {
+	pub fn new() -> Self {
+		Self(Vec::new())
+	}
+}
+
+impl Default for WriteMutationSet {
+	fn default() -> Self {
+		Self::new()
+	}
+}
+
+#[cfg(test)]
+mod tests {
+	#[test]
+	fn serialization() {
+		use super::*;
+		use std::collections::HashMap;
+		let cs = ChangeSet(
+			[0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
+			DatabaseMutation(vec![TableMutations(
+				"mytb".to_string(),
+				vec![
+					TableMutation::Set(
+						Thing::from(("mytb".to_string(), "tobie".to_string())),
+						Value::from(Value::Object(Object::from(HashMap::from([
+							(
+								"id",
+								Value::from(Thing::from(("mytb".to_string(), "tobie".to_string()))),
+							),
+							("note", Value::from("surreal")),
+						])))),
+					),
+					TableMutation::Del(Thing::from(("mytb".to_string(), "tobie".to_string()))),
+				],
+			)]),
+		);
+		let v = cs.into_value().into_json();
+		let s = serde_json::to_string(&v).unwrap();
+		assert_eq!(
+			s,
+			r#"{"changes":[{"update":{"id":"mytb:tobie","note":"surreal"}},{"delete":{"id":"mytb:tobie"}}],"versionstamp":1}"#
+		);
+	}
+}
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index e1a98964..aa9c2c8c 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -107,6 +107,7 @@ extern crate tracing;
 mod mac;
 
 mod api;
+mod cf;
 mod ctx;
 mod doc;
 mod exe;