# `TerminusDB.Document`
[🔗](https://github.com/thanos/terminusdb-client-elixir/blob/v0.3.3/lib/terminus_db/document.ex#L1)

Document CRUD and query API for TerminusDB.

Wraps the `/api/document/{path}` endpoints. A "document" is a JSON object
conforming to a schema class, stored as linked triples. Documents can be
inserted, retrieved, queried by template, replaced, and deleted.

All functions require a `TerminusDB.Config` scoped to a database (via
`TerminusDB.Config.with_database/2`). The organization defaults to
`config.organization` but can be overridden per call via the `:organization`
option.

## Graph types

Operations target the `:instance` graph (data) by default. Pass
`graph_type: :schema` to operate on the schema graph (schema documents are
documents too).

## Quick start

    config =
      TerminusDB.Config.new(endpoint: "http://localhost:6363")
      |> TerminusDB.Config.with_database("mydb")

    # Insert a schema (Class document)
    {:ok, _} =
      TerminusDB.Document.insert(config,
        %{"@type" => "Class", "@id" => "Person", "name" => "xsd:string"},
        author: "admin", message: "add schema", graph_type: :schema
      )

    # Insert a document
    {:ok, _} =
      TerminusDB.Document.insert(config,
        %{"@type" => "Person", "name" => "Alice"},
        author: "admin", message: "add Alice"
      )

    # Retrieve documents by type
    {:ok, docs} = TerminusDB.Document.get(config, type: "Person", as_list: true)

    # Query by template
    {:ok, matches} =
      TerminusDB.Document.query(config, %{"@type" => "Person", "name" => "Alice"})

    # Stream large result sets
    TerminusDB.Document.stream(config, type: "Person")
    |> Stream.each(&IO.inspect/1)
    |> Stream.run()

# `delete_opt`

```elixir
@type delete_opt() ::
  {:author, String.t()}
  | {:message, String.t()}
  | {:graph_type, graph_type()}
  | {:id, String.t()}
  | {:nuke, boolean()}
  | {:organization, String.t()}
```

# `get_opt`

```elixir
@type get_opt() ::
  {:graph_type, graph_type()}
  | {:id, String.t()}
  | {:type, String.t()}
  | {:skip, non_neg_integer()}
  | {:count, pos_integer()}
  | {:as_list, boolean()}
  | {:unfold, boolean()}
  | {:minimized, boolean()}
  | {:compress_ids, boolean()}
  | {:organization, String.t()}
```

# `graph_type`

```elixir
@type graph_type() :: :instance | :schema
```

# `insert_opt`

```elixir
@type insert_opt() ::
  {:author, String.t()}
  | {:message, String.t()}
  | {:graph_type, graph_type()}
  | {:full_replace, boolean()}
  | {:raw_json, boolean()}
  | {:organization, String.t()}
```

# `query_opt`

```elixir
@type query_opt() ::
  {:graph_type, graph_type()}
  | {:skip, non_neg_integer()}
  | {:count, pos_integer()}
  | {:as_list, boolean()}
  | {:organization, String.t()}
```

# `update_opt`

```elixir
@type update_opt() ::
  {:author, String.t()}
  | {:message, String.t()}
  | {:graph_type, graph_type()}
  | {:create, boolean()}
  | {:raw_json, boolean()}
  | {:organization, String.t()}
```

# `delete`

```elixir
@spec delete(TerminusDB.Config.t(), [delete_opt()]) ::
  {:ok, term()} | {:error, TerminusDB.Error.t()}
```

Deletes documents from the database.

With `:id`, deletes a single document. With `:nuke`, deletes all documents at
the resource location. Without either, a body containing a list of IDs must
be posted (not yet supported by this function; use `:id` or `:nuke`).

## Options

- `:author`, `:message` — commit metadata (required by the API).
- `:graph_type` — `:instance` (default) or `:schema`.
- `:id` — delete a specific document by ID.
- `:nuke` — delete all documents (dangerous).
- `:organization` — overrides `config.organization`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> {:ok, resp} = TerminusDB.Document.delete(config,
    ...>   id: "Person/Alice", author: "admin", message: "remove Alice"
    ...> )
    iex> resp["api:status"]
    "api:success"

# `delete!`

```elixir
@spec delete!(TerminusDB.Config.t(), [delete_opt()]) :: term()
```

Deletes documents, or raises `TerminusDB.Error`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> TerminusDB.Document.delete!(config, id: "Person/Alice", author: "a", message: "m")
    %{"api:status" => "api:success"}

# `get`

```elixir
@spec get(TerminusDB.Config.t(), [get_opt()]) ::
  {:ok, term()} | {:error, TerminusDB.Error.t()}
```

Retrieves documents from the database.

With `:id`, returns a single document. Without `:id`, returns all documents
(or those of a `:type`). By default TerminusDB returns concatenated JSON; pass
`as_list: true` to get a JSON array decoded into a list of maps.

## Options

- `:id` — retrieve a specific document by ID.
- `:type` — retrieve documents of a specific type.
- `:graph_type` — `:instance` (default) or `:schema`.
- `:skip` — number of documents to skip (default `0`).
- `:count` — max number of documents to return.
- `:as_list` — request a JSON array instead of concatenated JSON.
- `:unfold` — join referenced documents (default `true`).
- `:minimized` — minify output (default `true`).
- `:compress_ids` — compress IDs using prefixes (default `true`).
- `:organization` — overrides `config.organization`.

## Examples

Get a single document by ID:

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"@id" => "Person/Alice", "name" => "Alice"})} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> {:ok, person} = TerminusDB.Document.get(config, id: "Person/Alice", as_list: false)
    iex> person["name"]
    "Alice"

Get all documents of a type:

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: [%{"@id" => "Person/Alice"}, %{"@id" => "Person/Bob"}])} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> {:ok, docs} = TerminusDB.Document.get(config, type: "Person", as_list: true)
    iex> length(docs)
    2

# `get!`

```elixir
@spec get!(TerminusDB.Config.t(), [get_opt()]) :: term()
```

Retrieves documents, or raises `TerminusDB.Error`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"@id" => "Person/Alice"})} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> TerminusDB.Document.get!(config, id: "Person/Alice")
    %{"@id" => "Person/Alice"}

# `insert`

```elixir
@spec insert(TerminusDB.Config.t(), map() | [map()], [insert_opt()]) ::
  {:ok, term()} | {:error, TerminusDB.Error.t()}
```

Inserts one or more documents into the database.

`document` can be a single map or a list of maps. The response body from
TerminusDB (the inserted document IDs) is returned.

## Options

- `:author` — commit author (required by the API).
- `:message` — commit message (required by the API).
- `:graph_type` — `:instance` (default) or `:schema`.
- `:full_replace` — if `true`, delete all existing documents before inserting.
- `:raw_json` — if `true`, insert as untyped `sys:JSONDocument` (no schema check).
- `:organization` — overrides `config.organization`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req ->
    ...>     {req, Req.Response.new(status: 200, body: [%{"@id" => "Person/Alice"}])}
    ...>   end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> {:ok, ids} = TerminusDB.Document.insert(config,
    ...>   %{"@type" => "Person", "name" => "Alice"},
    ...>   author: "admin", message: "add Alice"
    ...> )
    iex> ids
    [%{"@id" => "Person/Alice"}]

Inserting a list of documents:

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: [%{"@id" => "Person/Alice"}, %{"@id" => "Person/Bob"}])} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> {:ok, ids} = TerminusDB.Document.insert(config, [
    ...>   %{"@type" => "Person", "name" => "Alice"},
    ...>   %{"@type" => "Person", "name" => "Bob"}
    ...> ], author: "admin", message: "add people")
    iex> length(ids)
    2

# `insert!`

```elixir
@spec insert!(TerminusDB.Config.t(), map() | [map()], [insert_opt()]) :: term()
```

Inserts documents, returning the response body or raising `TerminusDB.Error`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: [%{"@id" => "Person/Alice"}])} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> TerminusDB.Document.insert!(config, %{"@type" => "Person", "name" => "Alice"},
    ...>   author: "admin", message: "add"
    ...> )
    [%{"@id" => "Person/Alice"}]

# `query`

```elixir
@spec query(TerminusDB.Config.t(), map(), [query_opt()]) ::
  {:ok, term()} | {:error, TerminusDB.Error.t()}
```

Queries documents by a template.

`template` is a map describing the shape of documents to match, e.g.
`%{"@type" => "Person", "age" => 30}`. TerminusDB returns all documents that
match the template.

## Options

- `:graph_type` — `:instance` (default) or `:schema`.
- `:skip`, `:count` — pagination.
- `:as_list` — return a JSON array.
- `:organization` — overrides `config.organization`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: [%{"@type" => "Person", "name" => "Alice", "age" => 30}])} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> {:ok, matches} = TerminusDB.Document.query(config, %{"@type" => "Person", "age" => 30})
    iex> hd(matches)["name"]
    "Alice"

# `query!`

```elixir
@spec query!(TerminusDB.Config.t(), map(), [query_opt()]) :: term()
```

Queries documents by template, or raises `TerminusDB.Error`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: [%{"name" => "Alice"}])} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> TerminusDB.Document.query!(config, %{"name" => "Alice"})
    [%{"name" => "Alice"}]

# `replace`

```elixir
@spec replace(TerminusDB.Config.t(), map() | [map()], [update_opt()]) ::
  {:ok, term()} | {:error, TerminusDB.Error.t()}
```

Replaces one or more existing documents.

If a document does not exist, an error is returned unless `create: true` is
set, in which case it is inserted.

## Options

- `:author`, `:message` — commit metadata (required by the API).
- `:graph_type` — `:instance` (default) or `:schema`.
- `:create` — insert if the document does not exist (default `false`).
- `:raw_json` — treat as untyped JSON (default `false`).
- `:organization` — overrides `config.organization`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"@id" => "Person/Alice", "name" => "Alicia"})} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> {:ok, updated} = TerminusDB.Document.replace(config,
    ...>   %{"@id" => "Person/Alice", "name" => "Alicia"},
    ...>   author: "admin", message: "rename Alice"
    ...> )
    iex> updated["name"]
    "Alicia"

# `replace!`

```elixir
@spec replace!(TerminusDB.Config.t(), map() | [map()], [update_opt()]) :: term()
```

Replaces documents, or raises `TerminusDB.Error`.

## Examples

    iex> config = TerminusDB.Config.new(
    ...>   endpoint: "http://localhost:6363",
    ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"@id" => "Person/Alice"})} end
    ...> ) |> TerminusDB.Config.with_database("mydb")
    iex> TerminusDB.Document.replace!(config, %{"@id" => "Person/Alice", "name" => "Alicia"},
    ...>   author: "admin", message: "rename"
    ...> )
    %{"@id" => "Person/Alice"}

# `stream`

```elixir
@spec stream(TerminusDB.Config.t(), [get_opt()]) :: Enumerable.t()
```

Returns a `Stream` of documents, decoded incrementally from the response.

Uses Req's response streaming (`into: :self`) and the concatenated-JSON
splitter in `TerminusDB.Streaming` to yield documents one at a time with
constant memory. Useful for large result sets.

## Options

Accepts the same options as `get/2` (`:type`, `:graph_type`, `:skip`,
`:count`, etc.).

## Examples

    # Stream all Person documents and process one at a time:
    TerminusDB.Document.stream(config, type: "Person")
    |> Stream.each(&IO.inspect/1)
    |> Stream.run()

---

*Consult [api-reference.md](api-reference.md) for complete listing*
