Skip to content

feat: add Database API — server-managed database for plugins#2409

Open
dirkwa wants to merge 4 commits intoSignalK:masterfrom
dirkwa:feat-database-api
Open

feat: add Database API — server-managed database for plugins#2409
dirkwa wants to merge 4 commits intoSignalK:masterfrom
dirkwa:feat-database-api

Conversation

@dirkwa
Copy link
Contributor

@dirkwa dirkwa commented Mar 4, 2026

Summary

Add a server-managed Database API that gives plugins access to an isolated relational database, eliminating the need for plugins to ship their own database binaries (better-sqlite3, DuckDB, etc.).

Follows the established provider pattern (History API, Resources API, Weather API) and the naming conventions from #2417.

  • Each plugin gets its own SQLite database file at {configPath}/plugin-db/{pluginId}.db
  • Two built-in providers: _builtin_nodesqlite (node:sqlite, preferred on Node ≥22.5.0) and _builtin (better-sqlite3, fallback for Node <22.5.0)
  • Both providers use the same on-disk SQLite format — no data migration needed when switching
  • Community plugins can register alternative backends (e.g. PostgreSQL) via registerDatabaseProvider()
  • Async interface (Promise&#x3C;>) supports both sync and async backends
  • HTTP endpoints at /signalk/v2/api/database/_providers for provider management
  • Native modules loaded lazily with try/catch guards — server never crashes if a backend is unavailable

Plugin API surface

const db = await app.getDatabaseApi().getPluginDb(plugin.id)
await db.migrate([{ version: 1, sql: 'CREATE TABLE ...' }])
const rows = await db.query('SELECT * FROM foo WHERE bar = ?', [42])
const result = await db.run('INSERT INTO foo (bar) VALUES (?)', [42])
await db.transaction(async (tx) => { /* atomic ops */ })

Node.js compatibility

Node Version node:sqlite better-sqlite3 Default Provider
20.x yes _builtin
22.x yes yes _builtin_nodesqlite
24.x yes yes _builtin_nodesqlite

Tested all three versions with nvm use, clean npm install, and verified provider registration via REST API.

Commits

  1. feat: add Database API with pluggable provider architecture — interfaces, both SQLite providers, registry, HTTP routes, server bootstrap, plugin wiring
  2. feat: prefer node:sqlite over better-sqlite3 when available — flip default preference, add lazy require guard for better-sqlite3
  3. refactor: rename DatabaseApiRegistry to DatabaseProviderRegistry — align with RFC: Consistent Naming Conventions for Provider and API Interfaces #2417 naming conventions
  4. docs: add Database API documentation — REST API docs, provider plugin guide, JSDoc

Files

  • packages/server-api/src/databaseapi.ts — public interfaces (DatabaseApiPluginDbDatabaseProviderRegistry) with JSDoc
  • src/api/database/sqliteprovider.ts — better-sqlite3 provider (lazy require)
  • src/api/database/nodesqliteprovider.ts — node:sqlite provider (lazy require)
  • src/api/database/index.ts — registry, HTTP routes, provider management
  • src/api/index.tssrc/interfaces/plugins.ts — server bootstrap and plugin wiring
  • docs/develop/rest-api/database_api.md — REST API documentation
  • docs/develop/plugins/database_provider_plugins.md — provider plugin guide

Tested with

  • signalk-postgsail plugin migrated as proof-of-concept consumer — removed better-sqlite3 dependency, verified migrations, query, run, and transaction on a live server with real GPS data
  • All three Node.js versions (20, 22, 24) verified with clean installs

Fixes: #2407

@dirkwa
Copy link
Contributor Author

dirkwa commented Mar 7, 2026

What do you think about using this database also for infernal storage, like: #2369 (review) ?

Having it all at a central place could be very beneficial for backup and restore.

@dirkwa
Copy link
Contributor Author

dirkwa commented Mar 7, 2026

NOTE:

Researching for the Plugin developer CI I found that

Victron Cerbo uses in latest 3.70.x node 20

I will look into a practical solution, not to loose this device.

PR back to draft.

@dirkwa dirkwa marked this pull request as draft March 7, 2026 17:20
dirkwa added 3 commits March 9, 2026 09:01
Introduce a server-managed relational database API that gives each
plugin its own isolated SQLite database. The API supports migrations,
parameterized queries, transactions, and pluggable provider backends.

Two built-in providers ship with the server:
- _builtin: SQLite via better-sqlite3 (default)
- _builtin_nodesqlite: SQLite via node:sqlite (Node >= 22.5.0)

Plugins can register alternative providers (e.g. PostgreSQL) via
app.registerDatabaseProvider() and consumers access the API via
app.getDatabaseApi().getPluginDb(plugin.id).
On Node >= 22.5.0, use the built-in node:sqlite module as the default
database provider. This eliminates the native addon dependency for
most users. On Node 20 (e.g. Victron Cerbo), better-sqlite3 remains
the default since node:sqlite is not available.

Both providers use lazy require() with try/catch guards so the server
boots cleanly regardless of what is available. Plugin providers now
correctly override either built-in default.
Align with the naming convention proposed in SignalK#2417:
Provider = what plugins implement, Api = what plugins consume.
The registry interface follows the <D>ProviderRegistry pattern
consistent with WeatherProviderRegistry, RadarProviderRegistry, etc.
@dirkwa dirkwa force-pushed the feat-database-api branch from 1213e65 to 1f59a77 Compare March 8, 2026 21:30
Add consumer-facing documentation (database_api.md) covering plugin
usage, migrations, queries, transactions, and provider management
REST endpoints. Add provider plugin documentation
(database_provider_plugins.md) with interface requirements, registration
example, and SQL portability notes.

Update JSDoc in databaseapi.ts to reflect node:sqlite preference order.
@dirkwa dirkwa force-pushed the feat-database-api branch from 1f59a77 to 7499b3c Compare March 8, 2026 21:38
@dirkwa dirkwa marked this pull request as ready for review March 8, 2026 21:52
@dirkwa
Copy link
Contributor Author

dirkwa commented Mar 8, 2026

The Cerbo node 20 topic is solved.

I decided to keep this PR clean and will bring a follow up PR "on top" of this one for a separate server db as addressed in #2369 (review) to implement further improvements of the signalk-server while separating the plugin db and the server db while reusing the infrastructure.

Ready for review.

@dirkwa
Copy link
Contributor Author

dirkwa commented Mar 9, 2026

Additional samples where this would be a great fit:

noforeignland/nfl-signalk — a track logger that:

  • Stores position data as JSONL (pending.jsonl) — one {lat, lon, t} per line via fs.appendFile()
  • Has to manage its own file lifecycle — pending.jsonl for unsent data, sent.jsonl for archives
  • Already had to write a migration (TrackMigration.ts) because the file naming scheme changed between versions — exactly the kind of schema evolution a database handles natively
  • Reads back data by streaming the whole file line-by-line via readline — no indexing, no querying by time range
  • Has to handle partial writes / corruption — invalid lines are skipped but not recoverable

signalk-logbook — daily YAML files, full-file rewrites on every entry edit

signalk-anchoralarm-plugin

  • Saves anchor state (position, radius, rode) to state.json via getDataDirPath()
  • Also calls savePluginOptions with a debounce timer to avoid hammering the filesystem
  • Classic "small structured state" that's a perfect fit for a DB record

@meri-imperiumi/signalk-logbook

  • Stores log entries as one YAML file per day
  • Every single-entry edit rewrites the entire day file
  • No cross-day search capability without reading every file
  • A database table with indexed datetime and category columns would be far better

signalk-tide-watch

  • Had to build a custom binary circular file format for depth readings because no storage API exists
  • Uses getDataDirPath() for the binary files

signalk-polar-recorder

  • Stores polar data files in getDataDirPath()
  • Lists files via fs.readdirSync(app.getDataDirPath())
  • Loads data via manual file reads

Any alarm/notification plugin — storing alarm history as individual files is fragile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC: Signal K Database API

1 participant