(KV_DBTrakeva.KEY_VALUE_STORE): Trakeva.KEY_VALUE_STORE = struct

  let default_collection_name = "trakeva-greedy-cache-default-collection"

  type t = {
    original: KV_DB.t;
    cache: Greedy_cache.t;
    mutex: Lwt_mutex.t;
  }
  let load s =
    KV_DB.load s
    >>= fun original ->
    return {original; cache = Greedy_cache.create (); mutex = Lwt_mutex.create ()}

  let close {original; _} =
    KV_DB.close original

  open Trakeva.Action
  open Trakeva.Key_in_collection

  let cache_collection t ~collection =
    if !debug then dbg "cache_collection %s" collection;
    match Greedy_cache.find_collection t.cache collection with
    | Some s -> return s
    | None ->
      if !debug then dbg "cache_collection %s: get_all" collection;
      KV_DB.get_all t.original collection
      >>= fun all_keys ->
      let col = Greedy_cache.new_collection t.cache collection in
      Deferred_list.while_sequential all_keys ~f:(fun key ->
          KV_DB.get t.original ~collection ~key
          >>= function
          | Some value ->
            if !debug then dbg "cache_collection %s: add %s → %s" collection key value;
            Greedy_cache.add_to_collection t.cache col ~key ~value;
            return ()
          | None -> return ()
        )
      >>= fun (_ : unit list) ->
      return col

  let get ?(collection = default_collection_name) t ~key =
    Lwt_mutex.with_lock t.mutex (fun () ->
        cache_collection t ~collection
        >>= fun col ->
        return (Greedy_cache.from_collection col key)
      )
    >>< function
    | `Ok o -> return o
    | `Error (`Database (`Get_all _, s_)) ->
      fail (`Database (`Get (create ~collection key), s_))
    | `Error (`Database (`Get _,_) as e) -> fail e

  let get_all t ~collection =
    Lwt_mutex.with_lock t.mutex (fun () ->
        cache_collection t ~collection
        >>= fun col ->
        if !debug then dbg "get_all %s" collection;
        return (Greedy_cache.fold_collection col ~init:[]
                  ~f:(fun prev ~key ~value -> key :: prev))
      )
    >>< function
    | `Ok o -> return o
    | `Error (`Database (`Get_all _,_) as e) -> fail e
    | `Error (`Database (`Get { key; _ }, s_)) ->
      fail (`Database (`Get_all collection, sprintf "getting %s: %s" key s_))

  let iterator t ~collection =
    let all = ref None in
    begin fun () ->
      begin
        begin match !all with
        | None ->
          get_all t ~collection
          >>= fun all_keys ->
          all := Some all_keys;
          return all_keys
        | Some l -> return l
        end
        >>= function
        | key :: more_keys ->
          all := Some more_keys;
          return (Some key)
        | [] -> return None
      end
      >>< function
      | `Ok o -> return o
      | `Error (`Database (`Get_all _, s_)) ->
        fail (`Database (`Iter collection, sprintf "get-all %s: %s" collection s_))
      | `Error (`Database (`Get { key; _ }, s_)) ->
        fail (`Database (`Iter collection, sprintf "getting %s: %s" key s_))
    end

  let act_in_cache t ~action =
    let open Trakeva.Action in
    let open Trakeva.Key_in_collection in
    let actual_collection = Option.value ~default:default_collection_name in
    let rec go = function
    | Set ({key; collection}, v) ->
      cache_collection t (actual_collection collection)
      >>= fun col ->
      Hashtbl.replace col key v;
      return ()
    | Unset {key; collection} ->
      cache_collection t (actual_collection collection)
      >>= fun col ->
      Hashtbl.remove col key;
      return ()
    | Check _ -> return ()
    | Sequence l ->
      Deferred_list.while_sequential l ~f:(fun act -> go act)
      >>= fun _ ->
      return ()
    in
    go action

  let act t ~action =
    begin
      KV_DB.act t.original ~action
      >>= function
      | `Done ->
        act_in_cache t ~action
        >>= fun () ->
        return `Done
      | `Not_done -> return `Not_done
    end >>< function
    | `Ok o -> return o
    | `Error (`Database (`Get_all collection, s_)) ->
      fail (`Database (`Act action, sprintf "get-all %s: %s" collection s_))
    | `Error (`Database (`Get { key; _ }, s_)) ->
      fail (`Database (`Act action, sprintf "getting %s: %s" key s_))
    | `Error (`Database (`Act _,_) as e) -> fail e

end