项目作者: cabal7

项目描述 :
Yet Another Elixir CouchDB Client. So alpha, your kittens will cry. Git tags should be usable, and are taken off the /develop/ branch. Use that until we have an actual official release.
高级语言: Elixir
项目地址: git://github.com/cabal7/sofa.git
创建时间: 2021-04-09T13:09:52Z
项目社区:https://github.com/cabal7/sofa

开源协议:BSD 2-Clause "Simplified" License

下载


Sofa

A straightforwards, idiomatic CouchDB client.

The intention is to provide an idiomatic Elixir client, that can play
nicely with Ecto, Maps, and in particular, Structs and Protocols. You
should be able to store a Struct in CouchDB, and have it come back to
you as a Struct again, assuming you’re not doing anything too messy,
such as nested structs, or trying to store pids, refs, and other
distinctly non-JSON things.

Installation

It is recommended to use a Tesla.Adapter. While in principle these are
all equivalent, in practice, their patterns for handling query
parameters, headers, empty HTTP bodies, IPv6, and generally dealing with
nil, true, false and so forth mean that they are not created
equal. This library should work, in most cases transparently, and if
not, we welcome tests and converters to address any shortcomings.

Sofa makes no guarantees about specific HTTP modules, but should run
with:

  • default Erlang httpc “no dependencies!”
  • Mint and Finch

The package can be installed by adding sofa to your list of
dependencies in mix.exs:

  1. def deps do
  2. [
  3. {:sofa, "~> 0.1.0"}
  4. ]
  5. end
  1. # config/config.exs
  2. import Config
  3. if config_env() == :test do
  4. config :tesla, adapter: Tesla.Mock
  5. else
  6. config :tesla, adapter: Tesla.Adapter.Mint
  7. end

Docs

Sofa really only has 2 important abstractions that live above the
CouchDB API:

  • %Sofa{} aka Sofa.t() which is a struct that wraps your HTTP API
    connection, along with any custom headers & settings you may
    require, and the returned data from the CouchDB server you connect
    to, including feature flags and vendor settings. As a convenience,
    it also doubles as your “database” struct, as that’s really only a
    single additional field to be inserted into the CouchDB URL
  • %Sofa.Doc{} aka Sofa.Doc.t() which is the main struct you’ll work
    with. We’ve tried to keep it as close to the CouchDB API as possible,
    so aside from id, rev, and the attachments stubs, all the
    JSON is contained in a body and Sofa keeps out of your way.

Road Map

While not yet implemented, Sofa wants to support “native” Elixir struct
usage, where you implement the Protocol to convert your custom Struct
to/from Sofa, and Sofa will use the type key that is commonly used in
CouchDB to detect & marshall your Struct directly to/from CouchDB’s JSON
API transparently.

  • server: Sofa.*
  • raw HTTP: Sofa.Raw.*
  • database: Sofa.DB.*
  • document: Sofa.Doc.*
  • user: Sofa.User.*
  • attachments
  • transparent Struct API
  • view: Sofa.View.*
  • changes: Sofa.Changes.*
  • timeouts for requests and inactivity
  • bearer token authorisation
  • runtime tracing filterable by method & URL
  • embeddable within CouchDB BEAM runtime
  • native CouchDB erlang term support

Usage

Connecting to CouchDB

Sofa.init/1 and Sofa.client/1 are effectively static structures, so
you can build them at compile time, or store them efficiently in ETS
tables, or persistent_term for faster access.

Sofa.connect!/1 needs access to the CouchDB server, to verify that
your credentials are sufficient, and to retrieve feature flags and
vendor settings.

Exactly how you use this, is dependent on your Tesla.Adapter and
supervision trees. Make sure that you’re not opening a new TCP
connection for every call to the database, and then leave them
dangling until your app or the server runs of of connections!

The Sofa.DB.open!/2 call also does similar checks, ensuring you have
at least permissions to access the database, in some form. There is
nothing that changes over time within this struct, so feel free to cache
it “for a while” in your processes if that helps.

  1. # connect to CouchDB and ensure our credentials are valid
  2. iex> sofa = Sofa.init("http://admin:passwd@localhost:5984/")
  3. |> Sofa.client()
  4. |> Sofa.connect!()
  5. #Sofa<
  6. client: %Tesla.Client{
  7. adapter: nil,
  8. fun: nil,
  9. post: [],
  10. pre: [{Tesla.Middleware.BaseUrl, ...}, {...}, ...]
  11. },
  12. features: ["access-ready", "partitioned", "pluggable-storage-engines",
  13. "reshard", "scheduler"],
  14. timeout: nil,
  15. uri: %URI{
  16. authority: "admin:passwd@localhost:5984",
  17. fragment: nil,
  18. host: "localhost",
  19. ...
  20. },
  21. uuid: "092b8cafefcaeef659beef7b60a5a9",
  22. vendor: %{"name" => "FreeBSD", ...},
  23. version: "3.2.0",
  24. ...
  25. # re-use the same struct, and confirm we can access a specific database
  26. iex> db = Sofa.DB.open!("mydb")
  27. #Sofa<
  28. client: %Tesla.Client{ ... },
  29. database: "mydb",
  30. ...
  31. version: "3.2.0"
  32. >

Doc Usage

There shouldn’t be any surprises here - an Elixir Map %{} becomes the
body of the %Sofa.Doc{} struct, and the usual CouchDB internal
fields are available as additional atom fields off the struct:

  1. iex> doc = %{"_id" => "smol", "cute" => true} |> Sofa.Doc.from_map()
  2. %Sofa.Doc{
  3. attachments: nil,
  4. body: %{
  5. "cute" => true
  6. },
  7. id: "smol",
  8. rev: nil,
  9. type: nil
  10. }
  11. iex> doc |> Sofa.Doc.to_map()
  12. %{
  13. "_id" => "smol",
  14. "cute" => true
  15. }
  16. # fetch and retrieve documents works like you'd expect
  17. iex> Sofa.Doc.exists?(db,"missing")
  18. false

Raw Mode

Sometimes you just want to re-upholster the Couch yourself. That’s fine,
raw mode is here to help you:

  1. # raw mode gives you direct access to CouchDB API, with JSONification
  2. iex> db = Sofa.init("http://admin:passwd@localhost:5984/")
  3. |> Sofa.client()
  4. |> Sofa.connect!()
  5. |> Sofa.raw("/_membership")
  6. {:ok,
  7. #Sofa<
  8. client: %Tesla.Client{...},
  9. database: nil,
  10. features: ["access-ready",... "reshard", "scheduler"],
  11. timeout: nil,
  12. uri: %URI{...},
  13. uuid: "092b8cafefcaeef659beef7b60a5a9",
  14. vendor: %{"name" => "FreeBSD", ...},
  15. version: "3.2.0",
  16. ...
  17. >,
  18. %Sofa.Response{
  19. body: %{
  20. "all_nodes" => ["couchdb@127.0.0.1"],
  21. "cluster_nodes" => ["couchdb@127.0.0.1"]
  22. },
  23. headers: %{
  24. cache_control: "must-revalidate",
  25. content_length: 74,
  26. content_type: "application/json",
  27. date: "Wed, 28 Apr 2021 14:11:10 GMT",
  28. server: "CouchDB/3.2.0 (Erlang OTP/22)"
  29. },
  30. method: :get,
  31. query: [],
  32. status: 200,
  33. url: "http://localhost:5984/_membership"
  34. }}

Contributing

If raw mode can’t do it, send a PR, and we’ll make it so. If you find
yourself reaching for raw mode often, consider a PR that extends Sofa
itself?

Sofa should pass credo, and also respect dialyzer, via make lint.

Thanks

To the CouchDB team, a part of my life for more than a decade. Relax.