Data Providers
Overseer lets plugins publish structured data – monster stat blocks, spell lists, audio libraries, remote API endpoints – that any extension can consume. Plugins declare datasets alongside extensions, presets, themes, and locales. Extensions declare what datasets they depend on. Data flows through a main-process store over IPC, so every tile in the session sees a consistent view.
Static and dynamic datasets
Each dataset is one of two types, chosen in its manifest:
- Static datasets bundle a JSON array of records (e.g., 1,000 monsters). Extensions read, paginate, and optionally create, update, or delete records. Records are lazy-loaded on first read and cached in memory.
- Dynamic datasets expose named URL templates that resolve to remote API calls. Extensions invoke an action by name with parameters; Overseer fetches on their behalf (bypassing CORS) and returns the raw JSON response.
A single dataset is one or the other – never both. If a plugin needs both a local catalog and a live API for the same content, it declares two separate datasets.
Dataset IDs
Every dataset is addressed by a dataset ID in the form @pluginId:localId, for example @me/dnd5e-srd:monsters. The plugin loader constructs the ID from the plugin’s own ID and the dataset’s local ID, so a plugin cannot claim a dataset outside its own namespace.
The colon separator avoids ambiguity with the @scope/name format used for plugin IDs.
User overrides on static datasets
Static datasets are backed by two record sources that are merged at read time:
- Bundled records – the JSON file shipped with the plugin.
- User records – rows created or edited via the SDK, stored under
${dataDirectory}/Data/{pluginId}/{datasetLocalId}.json.
When the two sources share an ID, the user record wins. This lets a user edit a bundled monster’s hit points, add homebrew creatures alongside it, or delete overrides to revert to the shipped version. Because user records live outside the plugin directory, reinstalling or updating the plugin does not wipe user edits.
Writes are atomic: records are written to a .tmp file and renamed, so a crash mid-write cannot corrupt the stored data.
Reactive updates
Every CRUD write on a static dataset triggers a data:changed event broadcast to every open window. Extensions subscribed via onDataChanged(datasetId, callback) (from @overseer-studio/sdk) receive the event and can refetch or update their view.
Dynamic datasets don’t broadcast on their own – the store has no idea when a remote API’s state changes. If an extension performs a write through an action (for example, call('submit', ...)), it can follow up with invalidateData(datasetId) (from @overseer-studio/sdk) to signal other consumers to re-query. invalidateData fires a data:changed event with type: 'invalidated' but carries no record payload.
Missing dependencies
Extensions list the datasets they read in their manifest’s consumes array. At boot, Overseer checks each listed dataset against the loaded plugins. If a dataset is missing:
- The Plugin Manager shows a warning on the affected extension.
- A startup toast summarizes the count of extensions with missing data dependencies.
The warning is informational. The extension still loads; the SDK call that tries to read the missing dataset will simply reject at runtime. Harder blocking (refusing to load the extension at all) is deferred to a future version.
Security
Several constraints are enforced at manifest-validation and plugin-load time:
- Path traversal –
$refpaths in dataset manifests must resolve inside the plugin directory. References that escape the directory are rejected with aninvalid_dataseterror. - Safe local IDs – a dataset’s local
idmust match^[a-zA-Z0-9_-]+$. The id is used to derive the on-disk CRUD file path, so any path-segment character (/,\,.) is rejected at validation time. - HTTPS only for remote sources – dynamic dataset URL templates must use
https://. The scheme is verified with the template placeholders stripped. - Parameter encoding – values substituted into
{paramName}placeholders are passed throughencodeURIComponent, so parameters cannot escape a URL segment. - Namespaced IDs – dataset IDs are constructed by the loader, not read from the dataset manifest, so a plugin cannot publish a dataset under another plugin’s namespace.
- Size limits – record arrays above 50,000 entries are truncated; record files above 50 MB are skipped entirely (the file is statted before reading so a runaway dataset cannot OOM the main process). Both cases log a warning.
Next steps
- Datasets – how to package a dataset in a plugin.
- Extension Manifest – Consuming datasets – declare what your extension reads.
- SDK Reference – Data API – read data at runtime.