Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Limitations

What this binding does not do, and why each one is a decision rather than a gap.

No initSync

Validation happens inside the load, so the load runs on a worker thread and calls back into the event loop. A synchronous init() would be the loop waiting for itself.

Use top-level await in an ESM module, or await config.init() in your main. A configuration that must exist before anything else — a Nest provider, a Next.js module — has an async door already: useFactory is async, and a server component is.

No configuration engine in the browser

No filesystem, no watcher, no store, and a bundler cannot polyfill any of them. What ships to a client is a snapshot the server chose to send. Web Frameworks draws the line and examples/12-react/README.md writes it out.

The eight Rust stores are a second package

etcd, Consul, Vault, NATS, Redis, S3, Firestore and git each carry a client — gRPC, an AWS SDK, three HTTP stacks — and putting them in every npm install dynamic-config-node is not a default anybody asked for. Same reasoning as the second wheel in Python.

A store this package does not ship is still a function away: Remote Stores.

A remote fetch is synchronous

setRemote takes a function that answers { text, format }, not a promise. It is called from a worker thread through the loop, and awaiting from there is not possible. An async source keeps its own last answer; the pattern is three lines and it is in the chapter.

Encrypted files are not exposed

Decryption needs a Decryptor, which is a Rust trait. Decrypt with the CLI and point this at the result. The Python binding draws the same line for the same reason.

save and JSON Schema export are not exposed

The Rust crate can write a configuration back and export a JSON Schema from a type. Neither has an obvious Node shape — a schema here is a function, so there is nothing to export from — and both are one CLI invocation away.

What a validator may not be

Asynchronous. A validator is called inside the load, on a worker thread, and a promise cannot be awaited there. Every schema library's synchronous door — Zod's parse, Ajv's compiled validator — is what this takes. If a check genuinely needs I/O, it is not validation: do it after init() and refuse to start.

A class instance, or anything else JSON cannot carry. What a validator returns crosses back into Rust to be stored, so it is serialised: the document current() hands back is a plain object with the same data and none of the identity. A class loses its prototype — instanceof is false, methods and getters are gone. A Date is worse than a string: it serialises to {}, because it has no fields.

class Database { constructor(host) { this.host = host } get shouty() { … } }

validate: (document) => new Database(document.host)

config.current() instanceof Database   // false
config.current().shouty                // undefined

Return plain data — objects, arrays, strings, numbers, booleans, null — and keep the behaviour outside the configuration, where a reload does not have to rebuild it. Zod is fine as long as the schema is: z.date() and z.map() produce values with the same problem, and z.coerce.string() or an ISO string in the document is the shape that survives. A wrapper the program wants is one line at the read: new Database(config.current()).