---
title: "Quick Start"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Quick Start}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(getaca)
# The session temp directory carries the account name of whoever knits, so
# printing a cache path would put it in the built article. Redact it.
local({
roots <- unique(c(tempdir(),
normalizePath(tempdir(), winslash = "\\", mustWork = FALSE),
normalizePath(tempdir(), winslash = "/", mustWork = FALSE)))
roots <- unique(c(gsub("\\", "\\\\", roots, fixed = TRUE), roots))
roots <- roots[order(nchar(roots), decreasing = TRUE)]
render <- knitr::knit_hooks$get("output")
knitr::knit_hooks$set(output = function(x, options) {
for (root in roots) x <- gsub(root, "", x, fixed = TRUE)
render(x, options)
})
})
# Everything below runs against a throwaway cache with the network switched
# off, so this vignette builds identically on a machine that has never seen
# these resources and on one that has them all.
.old_options <- options(getaca.cache = file.path(tempdir(), "getaca-quickstart"))
.old_envvars <- Sys.getenv(c("GETACA_OFFLINE", "NOT_CRAN"), unset = NA)
Sys.setenv(GETACA_OFFLINE = "true", NOT_CRAN = "true")
```
A package that needs a four-gigabyte reference dataset cannot ship it, cannot
download it during `R CMD check`, and cannot afford to fetch a different
version on Tuesday than it fetched on Monday. `getaca` is the layer that makes
those three constraints compatible.
There is one engine and many declarations. Packages say what they need;
`getaca` gets it. The declaration is a small R object that ships inside the
installed package, so retrieving a resource requires no registration call, no
load hook, and no service to be up.
This vignette walks the whole cycle: declaring a resource, retrieving it,
behaving during checks, reading its provenance, and knowing where it went.
Each section links the companion article that goes deeper.
## Declaring what you need
A resource record names exact bytes: one checksum, one version label, and the
places those bytes live.
```{r}
backbone <- resource(
name = "backbone",
version = "2026-06",
urls = c("https://primary.invalid/backbone-2026-06.zip",
"https://mirror.invalid/backbone-2026-06.zip"),
sha256 = strrep("9f", 32),
size = 4.1e9,
license = "CC-BY-4.0"
)
backbone
```
Each field earns its place. The `sha256` is what makes the record a record
rather than a bookmark: a truncated transfer, a proxy that served an HTML
error page, and a publisher who quietly recut the file all produce a different
digest, and all three are caught before the bytes reach your code. The `size`
is a cheap early check, so a transfer that ends at 40% fails as a truncation
rather than running a four-gigabyte hash to discover the same thing. Extra
`urls` are tried in order, which turns one host's outage into a slower first
call. The `license` travels into provenance, so a result can say what terms
the data came under.
Records live in a registry, which is scoped to the declaring package:
```{r}
reg <- registry(package = "yourpkg", resources = list(backbone))
reg
```
Scoping matters more than it looks. Identity here is the triple
`package / name / version`, so two packages may both declare something called
`"backbone"` and never collide, in the cache or anywhere else.
```{r}
format(resource_id("yourpkg", "backbone", "2026-06"))
```
Ship the registry where `getaca` looks for it, and there is nothing else to
wire up:
```{r, eval = FALSE}
registry_write(reg, "inst/getaca/registry.rds")
```
Discovery is a file test across the library paths. `getaca` asks
`system.file()` for `getaca/registry.rds` inside each installed package, which
is why a declaring package needs no `.onLoad()` and no dependency on `getaca`
being attached.
See `vignette("declaring")` for the full authoring guide: mirrors, derived
artefacts, YAML authoring and the pre-ship checklist.
## Getting it
```{r, eval = FALSE}
path <- getaca("backbone", package = "yourpkg")
```
That path points to a complete file, verified against the declared checksum,
at the resolved version, in a cache slot `getaca` owns. What happens on the
way there depends on what is already true:
- cached and intact, the path comes back after a size check
- cached but past the re-verification interval, the bytes are re-hashed first
- absent, the mirrors are tried in order into a temporary file, which is sized
and hashed before it is moved into place
An interrupted transfer resumes rather than restarting, and can never be
mistaken for a finished resource. The temporary file is named after the
declared checksum and the mirror that produced it, so a resumed transfer only
ever continues bytes from the same host. Sharing one partial file across
mirrors would let a failed attempt at the first be resumed onto by the second,
and the resulting corruption is indistinguishable from the publisher having
changed the file.
Two sessions asking for the same resource at the same time take a per-resource
directory lock. The second waits, then observes the first session's success and
returns the same path, so a four-gigabyte download happens once. `vignette("cache")`
covers the locking protocol and what happens to a lock whose holder died.
## Which version a bare name resolves to
`getaca("backbone")` asks for a name, and the registry says which record that
name means. When a package offers several versions, it states the answer:
```{r}
multi <- registry(
package = "yourpkg",
current = c(backbone = "2026-09"),
resources = list(
resource("backbone", "2026-06",
urls = "https://primary.invalid/backbone-2026-06.zip",
sha256 = strrep("9f", 32), license = "CC-BY-4.0"),
resource("backbone", "2026-09",
urls = "https://primary.invalid/backbone-2026-09.zip",
sha256 = strrep("ab", 32), license = "CC-BY-4.0")
)
)
resolve_resource("backbone", registry = multi)$id
```
The older record stays resolvable by asking for it:
```{r}
resolve_resource("backbone", registry = multi, version = "2026-06")$id
```
Version strings here are labels rather than semantic versions.
`source-2026-06_build-3` is a perfectly good label and has no defensible
ordering, so `getaca` never tries to rank them. A registry that declares two
versions of a name and states no head is refused:
```{r, error = TRUE}
registry(
package = "yourpkg",
resources = list(
resource("backbone", "2026-09",
urls = "https://primary.invalid/a",
sha256 = strrep("ab", 32)),
resource("backbone", "2026-03",
urls = "https://primary.invalid/b",
sha256 = strrep("cd", 32))
)
)
```
The error lands on the author's machine, at the moment the registry is built,
rather than on every user of the package.
## Surviving R CMD check
Resolution collapses to `offline` under check, whatever policy is set, so a
check run never reaches the network. Three helpers cover the three places that
matters, and they answer three different questions.
In tests, skip cleanly and say what is missing:
```{r, eval = FALSE}
test_that("the backbone parses", {
getaca_skip_if_unavailable("backbone", package = "yourpkg")
expect_s3_class(read_backbone(getaca("backbone", package = "yourpkg")), "backbone")
})
```
In examples and vignettes, degrade to a message rather than an error. This
vignette is running with the network switched off, so the call below takes
exactly the path a CRAN check machine would take:
```{r}
path <- getaca_optional("backbone", registry = reg)
is.null(path)
```
And where a plain logical reads better:
```{r}
getaca_available("backbone", registry = reg)
```
`getaca_available()` never touches the network. It asks whether a cached copy
exists and passes its cheap integrity check, which is what makes it safe to
call in a condition that gates expensive work.
To prepare a machine that will later be offline, or a CI job that should find
everything already present:
```{r, eval = FALSE}
getaca_prefetch("backbone", package = "yourpkg")
getaca_prefetch(package = "yourpkg") # everything the package declares
```
Setting `GETACA_CACHE` points any session at a cache that has already been
seeded, which is how a CI job restores a cached directory and finds the
resources waiting. `vignette("checks")` has the workflow files.
## When it does not work
Every failure is classed, and carries an `actor` field naming who can act on
it. Here is the one a check run produces, in full:
```{r}
err <- tryCatch(getaca("backbone", registry = reg), getaca_error = function(e) e)
class(err)
```
```{r}
cat(conditionMessage(err))
```
```{r}
err$actor
```
`getaca_error_offline` is a subclass of `getaca_error_unavailable`, so a
handler for the general case catches it and a narrower handler can separate
the two. A declaring package usually catches the ones its users will meet and
answers in its own vocabulary:
```{r, eval = FALSE}
install_backbone <- function(name = "backbone") {
path <- tryCatch(
getaca(name, package = "yourpkg"),
getaca_error_unavailable = function(e) {
stop("The backbone is not installed and no network is available.\n",
"Connect, then run: yourpkg::install_backbone(\"backbone\")", call. = FALSE)
}
)
open_backbone(path)
}
```
Six conditions cover the failure surface, and they distinguish causes a plain
downloader reports identically. `vignette("failures")` works through each one,
including the case where several independent mirrors agree with each other and
disagree with the registry.
## Knowing where a file came from
```{r, eval = FALSE}
getaca_info("backbone", package = "yourpkg")
#> yourpkg/backbone@2026-06
#> path ~/.cache/R/getaca/yourpkg/backbone/2026-06/raw/backbone-2026-06.zip
#> sha256 9f9f9f...
#> size 4,100,000,000 bytes
#> license CC-BY-4.0
#> resolved by bundled registry sha256:1c4d7a90f2be (published 2026-07-20)
#> source url https://primary.invalid/backbone-2026-06.zip
#> getaca 0.0.0.9000
#> fetched 2026-07-26 11:02:13
#> verified 2026-07-26 11:09:44 (full re-hash)
#> checked 2026-07-26 15:31:02 (size and mtime)
```
The registry digest names the exact declaration that chose these bytes, so
"which version of yourpkg's declaration was this" has an answer years later,
without anyone having kept a revision number in step by hand.
Four timestamps, kept apart on purpose. "Verified" means the bytes were
re-hashed then. "Checked" means size and modification time were compared
against the entry, which is the cheap test run on ordinary access.
"Accessed" is the clock the retention sweeps read. Collapsing them into one
"last checked" field would make a resource verified in January look verified
today because someone opened it this morning.
An uncached resource gives `NULL` from `getaca_info()`, which keeps the call
usable in a report covering a machine that holds some of the set:
```{r}
is.null(getaca_info("backbone", registry = reg))
```
`getaca_catalogue()` widens that to a data frame covering both halves: every
resource the installed packages declare, and every copy the cache holds.
```{r}
getaca_catalogue(registry = multi)[, c("name", "version", "current",
"declared", "cached")]
```
The three logical columns answer three different questions. `current` marks
the version a bare request resolves to. `declared` says whether the registry
in force names that version at all. `cached` says whether a local copy is
recorded. A row with `declared = TRUE, cached = FALSE` is work still to do on
this machine. A row with `declared = FALSE` is a copy of a version nothing
asks for any more, which is what the retention sweeps reclaim first.
## Where things are stored
```{r}
getaca_cache_dir()
```
That is the temporary directory this vignette is sandboxed in. The default is
`tools::R_user_dir("getaca", "cache")`, which is what CRAN policy permits, on
the condition that contents are actively managed. `getaca` treats that as a
retention policy rather than a function users might find, and sweeps after
every successful retrieval. The `getaca.cache` option and the `GETACA_CACHE`
environment variable override it, in that order.
The layout is scoped the same way identity is:
```
/
.locks/ per-resource locks
.tmp/ in-flight downloads, never visible as cache
/
index.rds provenance for this package only
//
raw/ verified bytes as served
proc-/ processed result, own provenance
```
Nothing about it is private. It is an ordinary directory tree, which is what
makes `GETACA_CACHE` and a CI cache key sufficient for seeding.
To see what a sweep would remove without removing it:
```{r}
getaca_clean(dry_run = TRUE)
```
An empty result on a fresh cache. On a working one, each row names the
resource, the reason, and the bytes it would reclaim. `vignette("cache")`
covers the four sweeps, the clocks they read, and what is never touched.
## Choosing a policy
The registry declares a default, and a session or a single call can override
it:
```{r}
getaca_policy()
```
```{r, eval = FALSE}
getaca_policy("current") # for this session
getaca("backbone", package = "yourpkg", policy = "bundled") # for this call
```
`bundled` is the default because a dependency that resolves differently on
different days is not a dependency. `current` consults an author-controlled
remote registry, which lets a dead mirror be repaired without a CRAN release.
`pinned` resolves through a frozen local snapshot, so an analysis keeps
resolving what it was written against. `offline` never reaches for the
network at all.
```{r, eval = FALSE}
getaca_pin(c("yourpkg", "otherpkg")) # writes getaca.pins.rds in the project
```
`vignette("policies")` covers what a remote channel is allowed to change, why
redefining a published version is refused, and how pinning interacts with
`renv`.
## Where to go next
- `vignette("declaring")` for package authors: what to declare, where to put
it, and the checklist before shipping
- `vignette("policies")` for channels, remote registries and pinning
- `vignette("checks")` for `R CMD check`, CI workflows and seeding a cache
- `vignette("cache")` for the layout, verification schedule, locking and
retention
- `vignette("failures")` for the eleven conditions and how to handle each
- `vignette("alternatives")` for choosing between `getaca`, a companion data
package, and the neighbouring tools
```{r, include = FALSE}
options(.old_options)
Sys.unsetenv(names(.old_envvars)[is.na(.old_envvars)])
.restore <- .old_envvars[!is.na(.old_envvars)]
if (length(.restore)) do.call(Sys.setenv, as.list(.restore))
```