| Title: | Shared 'SQLite' Cache Backend for the 'nordstat' Package Family |
|---|---|
| Description: | Provides a SQLite-backed cell-level cache that can be used as a drop-in backend by the 'nordstat' family of packages ('rKolada', 'rTrafa', and 'pixieweb'). Designed for multi-user web applications where minimal fetch latency and asynchronous writes are required. Individual statistical values ("cells") are stored in a gatekeeper schema with a sidecar table for arbitrary metadata dimensions, enabling deduplication across overlapping queries. |
| Authors: | Love Hansson [aut, cre] |
| Maintainer: | Love Hansson <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0.9004 |
| Built: | 2026-05-19 08:18:04 UTC |
| Source: | https://github.com/lchansson/nordstatextras |
Produces a closure matching the cache_handler() contract used by
rKolada, rTrafa, and pixieweb: a function of (method, df) where method
is one of "discover", "load", or "store". Unlike the per-package
.rds handlers, this one stores values in a shared SQLite file. Two kinds
of cached content are supported:
nxt_cache_handler( source, entity, cache, cache_location, key_params = list(), kind = c("data", "metadata"), ttl_days = NXT_DEFAULT_TTL_DAYS, reconstruct_extra = list(), normalize_extra = list() )nxt_cache_handler( source, entity, cache, cache_location, key_params = list(), kind = c("data", "metadata"), ttl_days = NXT_DEFAULT_TTL_DAYS, reconstruct_extra = list(), normalize_extra = list() )
source |
One of |
entity |
Entity keyword (e.g. |
cache |
Logical — whether caching is enabled. |
cache_location |
Either an |
key_params |
Named list of parameters that together with |
kind |
Either |
ttl_days |
Time-to-live in days. Defaults to 30. |
reconstruct_extra |
Named list of extra arguments forwarded to the
reconstruct function — used when trafa/pixieweb need product/alias/etc.
at load time. Ignored when |
normalize_extra |
Same for the normalize function, used at store time.
Ignored when |
kind = "data" (default) — statistical data stored at cell granularity
with cross-query freshness. Matches the existing rKolada get_values()
/ rTrafa get_data() / pixieweb get_data() integration.
kind = "metadata" — whole serialized objects (tibbles or lists) stored
as BLOBs on the queries row. Matches the metadata-caching integration
for get_kpi(), get_products(), get_tables(), etc. TTL is query-level.
A function with signature function(method, df = NULL).
path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) # Data caching (cell-level) ch <- nxt_cache_handler( source = "kolada", entity = "values", cache = TRUE, cache_location = handle, key_params = list(kpi = "N03700", period = "2024") ) ch("discover") # FALSE - nothing cached yet # Metadata caching (whole-BLOB) ch_meta <- nxt_cache_handler( source = "kolada", entity = "kpi", cache = TRUE, cache_location = handle, kind = "metadata", key_params = list(id = NULL) ) ch_meta("discover") # FALSE nxt_close(handle) unlink(path)path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) # Data caching (cell-level) ch <- nxt_cache_handler( source = "kolada", entity = "values", cache = TRUE, cache_location = handle, key_params = list(kpi = "N03700", period = "2024") ) ch("discover") # FALSE - nothing cached yet # Metadata caching (whole-BLOB) ch_meta <- nxt_cache_handler( source = "kolada", entity = "kpi", cache = TRUE, cache_location = handle, kind = "metadata", key_params = list(id = NULL) ) ch_meta("discover") # FALSE nxt_close(handle) unlink(path)
Removes queries (and any cells / metadata rows no longer referenced) from the cache. Filters narrow the scope to a single source, entity, or kind.
nxt_clear(handle, source = NULL, entity = NULL, kind = NULL)nxt_clear(handle, source = NULL, entity = NULL, kind = NULL)
handle |
An |
source |
Optional source filter ( |
entity |
Optional entity filter. |
kind |
Optional kind filter ( |
invisible(NULL). Emits a message summarising what was removed.
path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) # Clear all cached data for a specific source nxt_clear(handle, source = "kolada") # Clear everything nxt_clear(handle) nxt_close(handle) unlink(path)path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) # Clear all cached data for a specific source nxt_clear(handle, source = "kolada") # Clear everything nxt_clear(handle) nxt_close(handle) unlink(path)
Flushes any pending async writes and disconnects the underlying DBI connection.
nxt_close(handle)nxt_close(handle)
handle |
A handle returned by |
invisible(NULL)
path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) nxt_close(handle) unlink(path)path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) nxt_close(handle) unlink(path)
Returns the (query, source) pairs whose last_run_at is older than
max_age days (or their own ttl_days entry, whichever is smaller).
A cron job can iterate the result and re-run each search against the
live API, passing the fresh entity list back to nxt_record_search().
nxt_expired_searches(handle, max_age = 30L)nxt_expired_searches(handle, max_age = 30L)
handle |
|
max_age |
Default maximum age in days when a row's own
|
A tibble with columns query, source, last_run_at
(POSIXct), ttl_days, last_hit_count. Ordered oldest-first.
Blocks until all buffered writes for a handle have been committed.
nxt_flush(handle)nxt_flush(handle)
handle |
An |
invisible(NULL)
path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) nxt_flush(handle) nxt_close(handle) unlink(path)path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) nxt_flush(handle) nxt_close(handle) unlink(path)
Branches on queries.kind:
nxt_gc(handle, max_age_days = NXT_DEFAULT_TTL_DAYS)nxt_gc(handle, max_age_days = NXT_DEFAULT_TTL_DAYS)
handle |
An |
max_age_days |
Maximum age in days. Default 30. |
kind='data': deletes individual cells older than max_age_days, drops
junction rows pointing at them, then removes queries that lost every
cell. Queries that still have at least one fresh cell are kept.
kind='metadata': deletes queries whose fetched_at is older than
max_age_days (query-level TTL — metadata blobs are opaque and can't
benefit from cross-query freshness).
Finally prunes orphan rows from cell_dims and meta_search.
invisible(NULL).
path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) # Remove cells and metadata older than 30 days (default) nxt_gc(handle) # Custom TTL: remove anything older than 7 days nxt_gc(handle, max_age_days = 7) nxt_close(handle) unlink(path)path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) # Remove cells and metadata older than 30 days (default) nxt_gc(handle) # Custom TTL: remove anything older than 7 days nxt_gc(handle, max_age_days = 7) nxt_close(handle) unlink(path)
Returns TRUE when cache_location points at a .sqlite/.db file or
an existing nxt_handle. Used by rKolada/rTrafa/pixieweb inside their
cache_handler() functions to decide whether to delegate.
nxt_is_backend(cache_location)nxt_is_backend(cache_location)
cache_location |
A path, function returning a path, or |
Logical scalar.
nxt_is_backend("my_cache.sqlite") # TRUE nxt_is_backend("my_cache.db") # TRUE nxt_is_backend("/tmp/cache_dir") # FALSE nxt_is_backend(tempdir()) # FALSEnxt_is_backend("my_cache.sqlite") # TRUE nxt_is_backend("my_cache.db") # TRUE nxt_is_backend("/tmp/cache_dir") # FALSE nxt_is_backend(tempdir()) # FALSE
Opens a SQLite database at path, creating the file and applying the
schema if it does not yet exist. Sets WAL mode and pragmas suitable for
multi-process read/write access.
nxt_open(path, create = TRUE)nxt_open(path, create = TRUE)
path |
Path to the |
create |
Logical. If |
An object of class nxt_handle — a thin wrapper around the DBI
connection that also carries the path and async-queue identity.
path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) print(handle) nxt_close(handle) unlink(path)path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) print(handle) nxt_close(handle) unlink(path)
Appends query to the search_keywords column of every entity in
entity_ids (deduplicated, whitespace-separated), refreshes the FTS5
index, and UPSERTs the query into recorded_searches with a last_run_at
timestamp. The entities must already exist in meta_search — call the
source-specific get_* functions with the nordstatExtras cache first.
nxt_record_search(handle, query, source, entity_ids, ttl_days = 30L)nxt_record_search(handle, query, source, entity_ids, ttl_days = 30L)
handle |
|
query |
Character scalar — the user's search term (kept verbatim
in |
source |
Character scalar — one of |
entity_ids |
Character vector of entity ids to associate with the
query (from the respective |
ttl_days |
Integer — max age before |
Invisibly, the number of meta_search rows updated.
Runs a typeahead-suitable search against the meta_search FTS5 index.
Every call to a metadata-caching function (e.g. get_kpi(),
get_products(), get_tables()) populates this index as a side effect,
so nxt_search() returns matches across all three source packages in a
single query.
nxt_search(handle, query, sources = NULL, entity_types = NULL, limit = 50L)nxt_search(handle, query, sources = NULL, entity_types = NULL, limit = 50L)
handle |
An |
query |
FTS5 query string. Supports prefix match via |
sources |
Optional character vector restricting results to
specific sources (e.g. |
entity_types |
Optional character vector restricting results to
specific entity types (e.g. |
limit |
Maximum number of results to return. Default 50. |
A tibble with columns source, entity_type, entity_id,
title, description, and rank (lower = better match). Empty
tibble if nothing matches.
path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) # Populate the search index by storing some metadata ch <- nxt_cache_handler( source = "kolada", entity = "kpi", cache = TRUE, cache_location = handle, kind = "metadata", key_params = list() ) ch("store", data.frame( id = c("N03700", "N01951"), title = c("Befolkning totalt", "Bruttoregionprodukt"), description = c("Antal invanare", "BRP per capita") )) # Typeahead search nxt_search(handle, "bef*") # Filter by source nxt_search(handle, "bef*", sources = "kolada") nxt_close(handle) unlink(path)path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) # Populate the search index by storing some metadata ch <- nxt_cache_handler( source = "kolada", entity = "kpi", cache = TRUE, cache_location = handle, kind = "metadata", key_params = list() ) ch("store", data.frame( id = c("N03700", "N01951"), title = c("Befolkning totalt", "Bruttoregionprodukt"), description = c("Antal invanare", "BRP per capita") )) # Typeahead search nxt_search(handle, "bef*") # Filter by source nxt_search(handle, "bef*", sources = "kolada") nxt_close(handle) unlink(path)
Buffers a write for a specific (source, entity, key_params) query and
returns immediately. The actual UPSERT into SQLite happens either in a
background mirai task or synchronously when the package isn't available.
nxt_write_async( handle, source, entity, key_params, df, normalize_extra = list() )nxt_write_async( handle, source, entity, key_params, df, normalize_extra = list() )
handle |
An |
source, entity, key_params
|
Identity of the query (same as
|
df |
The tibble to store. |
normalize_extra |
Named list of extra args for the normalizer. |
invisible(handle), for pipe-friendliness.
path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) df <- data.frame( kpi = "N03700", municipality_id = "0180", year = 2024L, value = 103.2 ) suppressWarnings( nxt_write_async(handle, "kolada", "values", key_params = list(kpi = "N03700"), df = df) ) nxt_flush(handle) nxt_close(handle) unlink(path)path <- tempfile(fileext = ".sqlite") handle <- nxt_open(path) df <- data.frame( kpi = "N03700", municipality_id = "0180", year = 2024L, value = 103.2 ) suppressWarnings( nxt_write_async(handle, "kolada", "values", key_params = list(kpi = "N03700"), df = df) ) nxt_flush(handle) nxt_close(handle) unlink(path)