@objectstack/service-settings
v15.1.0
Published
Settings service for ObjectStack — manifest registry + K/V resolver (OS_* env > Tenant > User > Default) + REST routes. See ADR-0007.
Downloads
7,288
Maintainers
Readme
@objectstack/service-settings
Settings service for ObjectStack. Implements ADR-0007: a generic
namespace manifest mechanism + a single K/V table (sys_setting) +
a resolver that layers OS_* env > Tenant > User > Default.
What it gives you
SettingsServicePlugin— registers thesys_settingschema, exposes asettingsservice in the kernel, and mounts REST routes on the HTTP server (when one is available).SettingsService(kernel.getService('settings'))get(ns, key, ctx)/getNamespace(ns, ctx)— resolved values with{ value, source, locked }.set(ns, key, value, scope, ctx)/setMany(...)— writes that persist intosys_setting. Throws when the effective value is locked by env.registerManifest(manifest)/listManifests()/getManifest(ns).runAction(ns, actionId, input, ctx)— for "test connection", "rotate", etc. declared inaction_buttonspecifiers.
- REST routes (default base path
/api/settings):GET /api/settings→ manifests visible to callerGET /api/settings/:namespace→{ manifest, values }PUT /api/settings/:namespace→ batch upsertPOST /api/settings/:namespace/:actionId→ invoke declared action
Env override
process.env[OS_NAMESPACE_KEY] (uppercased, with dots / hyphens converted
to underscores) takes precedence over any stored value. For example,
mail.smtp_host resolves from OS_MAIL_SMTP_HOST, and
feature_flags.ai_enabled resolves from OS_FEATURE_FLAGS_AI_ENABLED.
Such fields are returned with source: 'env', locked: true and writes
(service or REST) fail with HTTP 409.
Encryption
Specifier.encrypted: true (implicit for password) routes the value
through a pluggable ICryptoProvider into sys_secret; only an opaque
handle id lands in sys_setting.value_enc. The same provider backs every
secret-at-rest in the platform: encrypted settings, ObjectQL secret
fields, and runtime datasource credentials.
Default provider: LocalCryptoProvider
The default is LocalCryptoProvider — AES-256-GCM keyed off a single
32-byte data key. It resolves its key in order:
OS_SECRET_KEY— the canonical production master key (32-byte hex or base64). Set this in any container / multi-node deployment. Generate one withopenssl rand -hex 32. It must be identical across every restart and every node, or previously-encrypted secrets become undecryptable.OS_DEV_CRYPTO_KEY— dev convenience key (same format).- A persisted file at
~/.objectstack/dev-crypto-key(mode 0600). In development this is auto-created so single-host dev loops survive restarts; in production it is only read, never minted.
Fail-loud in production. When NODE_ENV=production and no stable key
source (env var or pre-existing file) is available, the provider refuses
to start with an actionable error instead of silently generating an
ephemeral key. This turns the old silent-data-loss footgun — every
sys_secret value becoming undecryptable after a container restart or on
a second node — into a config error at boot.
Secrets surviving a restart is correctness, not a premium feature, so
LocalCryptoProvider and the env-key path are open-source. KMS / Vault
providers (managed custody, per-tenant keys, automatic rotation) plug in
behind the same ICryptoProvider seam via cryptoProvider plugin option.
InMemoryCryptoProvideris a deprecated alias forLocalCryptoProvider(the old name wrongly implied an ephemeral key).
The legacy CryptoAdapter / NoopCryptoAdapter (a base64 wrapper) remains
only as a pre-Phase-3 backward-compat path when no cryptoProvider is wired.
Audit
Every write emits a sys_audit_log row (when the audit service is
present). Encrypted values are masked with '<encrypted>' + checksum.
Always-on default
SettingsServicePlugin is part of the default capability slate —
it is auto-mounted by objectstack serve (any preset except
--preset minimal) and by mountDefaultProjectPlugins() on every
per-project kernel on hosted objectos. Apps no longer need to declare
requires: ['settings']. Apps with zero registered manifests pay
no runtime cost (the registry is empty, no routes fire).
The Settings hub in apps/console therefore appears in every app, and
the following built-in manifests are pre-registered out of the box:
| Namespace | Owner plugin | Highlights |
|----------------|-----------------------------|---------------------------------------------|
| mail | EmailServicePlugin | SMTP / Resend / Postmark + mail/test |
| storage | StorageServicePlugin | Local FS / S3 + encrypted secret + storage/test |
| branding | (built-in fallback) | Workspace name, logo, accent colour |
| feature_flags| (built-in fallback) | Opt-in experimental features |
License
Apache-2.0. See LICENSING.md.
