DocShell.Web.Cache (DocShell v0.1.0)

Copy Markdown View Source

Holds validated artifacts in ETS so serving them never touches disk.

Documentation JSON is read constantly and written rarely. Reading and decoding a file per request wastes work on every one of them, so the cache loads the whole artifact directory once at startup and answers from memory afterwards.

Add it to the host's supervision tree:

children = [
  {DocShell.Web.Cache, dir: "priv/doc_shell/public"}
]

fetch/2 reads an immutable generation in ETS directly from the calling process, so lookups never queue behind the GenServer. The process exists to own the table and serialize reloads, not to serve reads.

Disposable by design

The cache is derived state. The JSON files are the source of truth, so crashing, restarting, or reloading cannot lose anything durable — which is why init/1 can afford to fail hard. A directory of artifacts that will not load is a deployment error, and starting anyway with a half-populated table would turn it into a scattering of 404s that look like missing documentation.

reload/1 re-reads the directory after a rebuild. It is all-or-nothing: the manifest and every listed artifact must carry the same generation_id. The replacement generation is filled before one ETS pointer is switched, so readers see the complete old or complete new snapshot, never an empty or mixed table. DocShell.Artifact writes each file atomically for the same reason.

Multiple caches

The registered :name doubles as the ETS table name, so several caches can coexist over different directories:

{DocShell.Web.Cache, name: :docs_internal, dir: "priv/doc_shell/private"}

Pass that name to fetch/2, or to DocShell.Web.Plug as :cache.

This module does not require Plug and is useful on its own to any host that wants artifacts in memory.

Summary

Functions

Returns a specification to start this module under a supervisor.

Fetches a cached artifact by filename from the given cache table.

Fetches a cached artifact as its complete stored envelope.

Re-reads the artifact directory, replacing the cache contents.

Starts the cache and loads a directory of JSON artifacts.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

fetch(artifact, table \\ DocShell.Web.Cache)

@spec fetch(String.t(), atom()) :: {:ok, term()} | :error

Fetches a cached artifact by filename from the given cache table.

Returns :error when the artifact is absent or the cache has not been started (missing ETS table), so callers never crash on an unstarted cache.

fetch_envelope(artifact, table \\ DocShell.Web.Cache)

@spec fetch_envelope(String.t(), atom()) :: {:ok, map()} | :error

Fetches a cached artifact as its complete stored envelope.

Serving needs this rather than fetch/2: the response has to carry the generated_at of the build that produced the artifact, and rebuilding an envelope around a bare payload would stamp it with the time of the request.

reload(server \\ DocShell.Web.Cache)

@spec reload(GenServer.server()) :: :ok | {:error, term()}

Re-reads the artifact directory, replacing the cache contents.

Returns {:error, {path, reason}} and leaves the previous contents in place if any file fails to read or validate.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Starts the cache and loads a directory of JSON artifacts.

The registered :name (default DocShell.Web.Cache) is also used as the ETS table name, so multiple independently-named caches can coexist.