Skip to content

Getting Started

Configuard turns a flat list of configuration rows — typically the contents of a config database table — into a nested, typed, immutable object your runtime can read safely.

Terminal window
npm i configuard

notation is a runtime dependency and is installed with it.

Each row is an IConfigItem — a key in dot/bracket notation, a raw string value, and the metadata that says how to parse it and who may see it. Here is a small device config used throughout these docs:

config rows
[
{
"accessor": "system",
"appAccess": null,
"key": "@UIColors",
"type": "string",
"listType": "csl",
"value": "Blue,Red,Green,Amber",
"options": null,
"defaultValue": null,
"editable": false,
"requiresReboot": false,
"encrypt": false,
"description": "Reusable list of allowed UI colors."
},
{
"accessor": "system",
"appAccess": null,
"key": "company.name",
"type": "string",
"listType": "none",
"value": "Acme",
"options": null,
"defaultValue": null,
"editable": true,
"requiresReboot": false,
"encrypt": false,
"description": "Display name of the operator."
},
{
"accessor": "system",
"appAccess": null,
"key": "device.port",
"type": "integer",
"listType": "none",
"value": "8080",
"options": null,
"defaultValue": "8080",
"editable": false,
"requiresReboot": true,
"encrypt": false,
"description": "Port the device listens on. Reboot to apply."
},
{
"accessor": "system",
"appAccess": null,
"key": "device.config.updateOnBoot",
"type": "boolean",
"listType": "none",
"value": "true",
"options": null,
"defaultValue": "true",
"editable": true,
"requiresReboot": false,
"encrypt": false,
"description": "Whether the device refreshes its config on boot."
},
{
"accessor": "system",
"appAccess": null,
"key": "device.config.lifeSpan",
"type": "integer",
"listType": "none",
"value": "1440",
"options": null,
"defaultValue": "1440",
"editable": true,
"requiresReboot": false,
"encrypt": false,
"description": "Config validity window, in minutes."
},
{
"accessor": "system",
"appAccess": null,
"key": "device.ui.accent",
"type": "string",
"listType": "none",
"value": "Amber",
"options": "${@UIColors}",
"defaultValue": "Blue",
"editable": true,
"requiresReboot": false,
"encrypt": false,
"description": "Accent color, constrained to the @UIColors list."
},
{
"accessor": "system",
"appAccess": null,
"key": "device.protocol.transports",
"type": "string",
"listType": "array",
"value": "http,mqtt",
"options": null,
"defaultValue": null,
"editable": true,
"requiresReboot": true,
"encrypt": false,
"description": "Enabled transports, parsed into a string array."
},
{
"accessor": "system",
"appAccess": null,
"key": "environment.host",
"type": "string",
"listType": "none",
"value": "device.local",
"options": null,
"defaultValue": null,
"editable": true,
"requiresReboot": false,
"encrypt": false,
"description": "Base hostname for device endpoints."
},
{
"accessor": "system",
"appAccess": null,
"key": "device.diagnostics.uploadUrl",
"type": "string",
"listType": "none",
"value": "https://${environment.host}/upload",
"options": null,
"defaultValue": null,
"editable": true,
"requiresReboot": false,
"encrypt": false,
"description": "Diagnostics endpoint, templated off environment.host."
},
{
"accessor": "system",
"appAccess": null,
"key": "db.password",
"type": "string",
"listType": "none",
"value": "enc:5f3a9c2e7b104d8f",
"options": null,
"defaultValue": null,
"editable": true,
"requiresReboot": false,
"encrypt": true,
"description": "Database password, stored encrypted at rest."
}
]

Pass the rows to the constructor with the accessor doing the reading. The result is built once, deep-frozen, and read by notation path:

import { Configuard, AccessorType } from 'configuard';
const cfg = new Configuard(rows, { accessor: AccessorType.SYSTEM });
cfg.data;
// {
// company: { name: 'Acme' },
// device: {
// port: 8080,
// config: { updateOnBoot: true, lifeSpan: 1440 },
// ui: { accent: 'Amber' },
// protocol: { transports: ['http', 'mqtt'] },
// diagnostics: { uploadUrl: 'https://device.local/upload' }
// },
// environment: { host: 'device.local' },
// db: { password: 'enc:5f3a9c2e7b104d8f' } // encrypted — no decrypt hook here
// }
cfg.get<number>('device.port'); // 8080 — a number, not "8080"
cfg.get('device.protocol.transports'); // ['http', 'mqtt']
cfg.has('device.ui.accent'); // true

Note what the build did for you: strings were cast to their declared type ("8080" → 8080), the array row was split into a list, the ${environment.host} template was resolved, and the whole object was frozen so nothing downstream can mutate live config.

Configuard sits across the whole life of a setting, not just the read:

  1. Store — keep settings as rows in one config table.
  2. Build & use — new Configuard(rows) produces the nested, type-cast, ABAC-filtered object for your runtime. Read it via .data / .get() / .has().
  3. Edit in a UI — Configuard.parseFlat() returns the same flat list with templates resolved and option lists expanded, ready to render as form fields.
  4. Save back — Configuard.serializeFlat() validates the edits, serializes them to DB strings, and returns the diff to persist.

Configuard also fails loud: a corrupt row throws at construction rather than silently producing a partial object.