diff --git a/Docs/img/recombine-diagram.svg b/Docs/img/recombine-diagram.svg
new file mode 100644
index 0000000..1deae18
--- /dev/null
+++ b/Docs/img/recombine-diagram.svg
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/Docs/img/recombine_concept.graffle b/Docs/img/recombine_concept.graffle
deleted file mode 100644
index 2e273c9..0000000
Binary files a/Docs/img/recombine_concept.graffle and /dev/null differ
diff --git a/Docs/img/recombine_concept.png b/Docs/img/recombine_concept.png
deleted file mode 100644
index 8e74af3..0000000
Binary files a/Docs/img/recombine_concept.png and /dev/null differ
diff --git a/README.md b/README.md
index 2a6d581..dd02f97 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,13 @@ A non-comprehensive list of benefits:
# About Recombine
-Recombine relies on three principles:
+Recombine relies on four principles:
- **The Store** stores your entire app state in the form of a single data structure. This state can only be modified by dispatching Actions to the store. Whenever the state in the store changes, the store will notify all observers.
- **Actions** are a declarative way of describing a state change. Actions don't contain any code, they are consumed by the store and forwarded to reducers. Reducers will handle the actions by implementing a different state change for each action.
- **Reducers** provide pure functions that create a new app state from actions and the current app state. These are your business and navigation logic routers.
- **Middleware** is a transformative type that lets you go from unrefined actions to refined ones, allowing for asynchronous calls and shortcut expansion of one action into many. Middleware is perfect for extracting records from databases or servers.
-![](Docs/img/recombine_concept.png)
+![Recombine flow diagram](Docs/img/recombine-diagram.svg)
For a very simple app, one that maintains a counter, you can define the app state as following:
diff --git a/Sources/RecombinePackage/Middleware.swift b/Sources/RecombinePackage/Middleware.swift
index 81b1f56..760e18b 100644
--- a/Sources/RecombinePackage/Middleware.swift
+++ b/Sources/RecombinePackage/Middleware.swift
@@ -1,28 +1,127 @@
import Combine
-/// Middleware is a dependency injection structure that allows you to transform raw actions into refined ones,
-/// Refined actions produced by Middleware are then forwarded to the main reducer.
+/// A dependency injection structure where you transform raw actions, into refined actions which are sent to the store's `Reducer`.
///
+/// The middleware is where you handle side effects, asynchronous calls, and generally code which interacts with the outside world (ie: making a network call, loading app data from disk, getting the user's current location), and also aggregate operations like resetting the state. Much like the rest of Recombine, `Middleware` harnesses Combine and its publishers to represent these interactions.
+///
+///`Middleware` is generic over 3 types:
+/// * `State`: The data structure which represents the current app state.
+/// * `Input`: Most commonly raw actions, this is the value that will be transformed into the `Output`.
+/// * `Output`: Most commonly refined actions, this is the result of the `Input`'s transformation, which is then sent to the store's `Reducer`
+///
+/// When creating the middleware, you pass in the `State`, `Input`, and `Output` in the angle brackets, and then a closure which takes two arguments – a publisher of `State`, the `Input`, and which returns an `AnyPublisher` of the `Output`.
+///
+/// Critically, you don't have access to the current state itself – only a "stream" where you can send refined actions.
+///
+/// Because you need to return an `AnyPublisher`, you usually make your asynchronous calls using Combine publishers, which you can `flatMap(_:)` into the `statePublisher` to return a refined action. It is recommended to make publisher extensions on common types which don't already have one, like `FileManager` or `CLLocationManager`.
+///
+/// For example, a middleware which handles making a network call and resetting the app's state:
+///
+/// static let middleware = Middleware { statePublisher, action -> AnyPublisher in
+/// switch action {
+/// case let networkCall(url):
+/// URLSession.shared.dataTaskPublisher(for: url)
+/// .map(\.data)
+/// .decode(type: MyModel.self, decoder: JSONDecoder())
+/// .replaceError(with: MyModel())
+/// .flatMap { myModel in
+/// statePublisher.map { _ in
+/// return .setModel(myModel)
+/// }
+/// }
+/// .eraseToAnyPublisher()
+/// }
+/// case resetAppState:
+/// return [
+/// .setModel(MyModel.empty),
+/// .usernameModification(.delete))
+/// ]
+/// .publisher
+/// .eraseToAnyPublisher()
+/// }
+/// }
+/// In the code above, the network call is made in the form of `URLSession`'s `dataTaskPublisher(for:)`. We decode the data and change the publisher's error type using `replaceError(with:)` (since the returned `AnyPublisher`'s error type must be `Never` – this can be done with other operators like `catch(:)` and `mapError(_:)`).
+///
+/// Then, we replace the `URLSession` publisher with the `statePublisher` using `flatMap(_:)`, which itself returns a refined action: `.setModel(MyModel)`.
+///
+/// This middleware also handles an aggregate operation, resetting the app state. It simply returns an array of refined actions, which is turned into a publisher using the `publisher` property on the `Sequence` protocol.
public struct Middleware {
public typealias StatePublisher = Publishers.First.Publisher>
- public typealias Function = (StatePublisher, Input) -> AnyPublisher