---
title: "External integrations"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{External integrations}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```

`zot` has two boundaries beyond ordinary in-memory planning: the local
Zotero SQLite database and Zotero file storage.
Their dependencies and effects are intentionally different.

```{r}
library(data.table)
library(zot)
```

| Boundary | Reads or effects | Operational note |
|---|---|---|
| Local SQLite | Read-only local query | May lag synchronized API state |
| File attachment | Creates an item and may upload bytes | Has immediate API and storage effects |

## Query SQLite read-only

`zotLocalQuery()` opens the supplied database with SQLite's read-only flag and
closes the connection when the query returns. This small fixture demonstrates
the boundary without depending on an installed Zotero client.

```{r}
Path <- tempfile(fileext = ".sqlite")
Connection <- DBI::dbConnect(RSQLite::SQLite(), Path)
DBI::dbWriteTable(
  Connection,
  "items",
  data.frame(key = c("A", "B"), title = c("One", "Two"))
)
DBI::dbDisconnect(Connection)

zotLocalQuery(
  "SELECT key, title FROM items WHERE key = ?",
  params = list("B"),
  path = Path
)
unlink(Path)
```

`zotLocalItems()` knows Zotero's client schema and returns non-trashed regular
items with library and date fields:

```{r, eval = FALSE}
Items <- zotLocalItems(library = "user")
Items[, .(key, itemType, title, dateModified)]
```

The desktop client may be in WAL mode or awaiting synchronization. These rows
are useful for a scan, not for proving a remote write succeeded.

## Exercise the attachment protocol offline

`ztAttach()` creates an attachment item, requests storage authorization,
uploads bytes when needed, registers the upload, and verifies the server-side
MD5. The example supplies a fake API performer and a transport that reports an
identical file already present, so no bytes leave the process.

```{r}
File <- tempfile(fileext = ".pdf")
writeBin(as.raw(1:8), File)

Performer <- function(config, method, path, query, body, version) {
  list(
    status = 200L,
    headers = list(),
    body = list(successful = list(`0` = list(key = "ATT1")))
  )
}

Config <- zotConfig(
  userID = "42",
  key = "example-key",
  performer = Performer
)

Transport <- list(
  authorize = function(...) list(exists = 1L),
  upload = function(...) stop("upload should not run"),
  register = function(...) stop("registration should not run")
)

ztAttach(
  Config,
  parentKey = NA_character_,
  filePath = File,
  fileTransport = Transport
)
unlink(File)
```

With the default transport, this call has immediate external effects and may
consume Zotero storage quota. A file-bearing record is refused by the
block-move planner because that planner does not transfer attachment bytes.

Without an explicit `contentType`, the type is chosen as Zotero's desktop
client chooses it: a table of known extensions first (`md` and `markdown` as
`text/markdown`, `bib` and `txt` as `text/plain`, `pdf`, `epub`, `html`, `csv`,
`json`, office formats and images by their registered types), then, for any
other extension, a sniff of the first 1024 bytes that yields `text/plain` when
every byte is a text character and `application/octet-stream` otherwise. Text
files are therefore never stored as `application/octet-stream`, which keeps
them readable and indexable in Zotero. A `text/*` file whose bytes are valid
UTF-8 is also given `charset = "utf-8"`, and an empty file is refused.

`ztAttach()` acts at once. To review the files before anything is created,
build an attach plan with `ztAttachPlan()` and run it through preview,
approval, execution and verification as in `vignette("guarded-workflow")`;
the command line exposes the same route as `zot plan attach`.

## Keep evidence in the correct plane

A curation workflow may discover candidates through SQLite,
establish current versions through the API, and upload an attachment. The same
functions can be invoked interactively, from scripts, or by agent applications.
API reads are required to observe remote outcomes; local data sources can lag
the synchronized library.
