Validate Opts
Refuse an option nothing reads. Every primitive in the framework declares the keys its opts accepts, and a key outside that list throws, naming the one it did not recognize and listing the ones it does.
An ignored option looks exactly like an option that worked. { maxBytes: 1024 } passed to a primitive that reads maxSize is a cap the operator believes is set and is not, and nothing about the running system says otherwise. Refusing at the call is the point at which the typo is still cheap.
The helpers are the per-value checks those primitives share. Each takes the caller's own error class and code, so a refusal arrives as that primitive's error rather than as a bare TypeError from somewhere the caller has never heard of.
Every helper whose name starts with optional accepts undefined and null as "not supplied", so a caller may pass an absent option straight through without testing it first.
b.validateOpts(opts, allowedKeys, primitive) #
Throw when opts carries a key not in allowedKeys. The message names the unrecognized key and lists the accepted ones sorted, so the reader sees the name they meant next to the one they typed. primitive is the name the message opens with, which is how an operator tells which call refused.
A null or undefined opts passes, since a primitive called with no options takes its defaults. An opts that is not an object throws, and so does an empty allowedKeys or a missing primitive, which are mistakes in the primitive rather than in the call.
It checks the key names and nothing else. The values are the helpers' job, and it throws a plain Error; b.validateOpts.checkOrThrow is the same check raising the caller's own error class instead.
var b = require("@blamejs/core");
b.validateOpts({ maxSize: 1024 }, ["maxSize", "timeoutMs"], "myThing.create");
// returns: every key is allowed
try {
b.validateOpts({ maxBytes: 1 }, ["maxSize", "timeoutMs"], "myThing.create");
} catch (e) {
e.message;
// → "myThing.create: unknown option 'maxBytes'. Allowed keys: maxSize, timeoutMs."
}
b.validateOpts.check(opts, allowedKeys, primitive) #
The same function the namespace itself is, under its own name. b.validateOpts(...) and b.validateOpts.check(...) are one function, so a caller that destructures { check } and one that calls the module get the same behavior.
var b = require("@blamejs/core");
b.validateOpts.check === b.validateOpts; // → true
b.validateOpts.auditShape(audit, callerLabel, errorClass, code) #
Refuse an audit option that is not an audit sink. A sink is an object carrying a safeEmit function, which is the shape b.audit has.
undefined and null pass, meaning no sink was supplied. A boolean does not: audit: true is the flag a primitive uses to turn its own auditing on, and passing it where a sink belongs would emit nothing while looking like it had been configured.
var b = require("@blamejs/core");
b.validateOpts.auditShape(b.audit, "myThing.create", MyError, "my/bad-opt");
// returns the sink
b.validateOpts.auditShape(true, "myThing.create", MyError, "my/bad-opt");
// throws: audit must be a b.audit-shaped object (safeEmit fn)
b.validateOpts.optionalBoolean(value, label, errorClass, code) #
Refuse a value that is neither a boolean nor absent, and answer it otherwise. undefined and null pass as "not supplied".
A string is refused, which is the point: "false" is truthy, so a flag read from an environment variable without conversion turns a feature on while the operator reads their config as turning it off.
var b = require("@blamejs/core");
b.validateOpts.optionalBoolean(false, "myThing: opts.strict", MyError, "my/bad-opt");
// → false
b.validateOpts.optionalBoolean("false", "myThing: opts.strict", MyError, "my/bad-opt");
// throws: must be a boolean, got string
b.validateOpts.optionalPositiveInt(value, label, errorClass, code) #
Refuse anything that is not a whole number of at least 1, and answer it otherwise. undefined and null pass as "not supplied".
This is the check for a count: retries, workers, batch size. Zero is refused because a count of zero is almost always a mistake rather than a request to do nothing, and a fraction is refused because there is no half a worker. Infinity is refused, so an unbounded count cannot be asked for by accident.
var b = require("@blamejs/core");
b.validateOpts.optionalPositiveInt(4, "myThing: opts.workers", MyError, "my/bad-opt");
// → 4
b.validateOpts.optionalPositiveInt(0, "myThing: opts.workers", MyError, "my/bad-opt");
// throws: must be a positive integer (>= 1, finite), got 0
b.validateOpts.optionalFiniteNonNegative(value, label, errorClass, code) #
Refuse anything that is not a finite number of at least 0, and answer it otherwise. undefined and null pass as "not supplied". A fraction is allowed.
This is the check for a quantity where zero means something: a delay of none, a threshold of none. Infinity and NaN are refused, so a value that arrived from arithmetic on a missing input does not become a timer that never fires.
var b = require("@blamejs/core");
b.validateOpts.optionalFiniteNonNegative(0, "myThing: opts.delayMs", MyError, "my/bad-opt");
// → 0
b.validateOpts.optionalFiniteNonNegative(Infinity, "myThing: opts.delayMs", MyError, "my/bad-opt");
// throws: must be a non-negative finite number, got Infinity
b.validateOpts.optionalDate(value, label, errorClass, code) #
Refuse anything that is not a Date carrying a real time, and answer it otherwise. undefined and null pass as "not supplied".
new Date("nope") is a Date whose time is NaN, and every comparison against it is false, so it would read as a deadline that is neither past nor future. It is refused here rather than at whatever compares it.
A number is refused: epoch milliseconds are not a Date, and the two are easy to pass to the wrong parameter.
The check reads the value's internal slot, so a Date built in another realm, by a node:vm context for instance, is a Date here too.
var b = require("@blamejs/core");
b.validateOpts.optionalDate(new Date(), "myThing: opts.notAfter", MyError, "my/bad-opt");
// → the Date
b.validateOpts.optionalDate(new Date("nope"), "myThing: opts.notAfter", MyError, "my/bad-opt");
// throws: must be a valid Date
b.validateOpts.optionalPositiveFinite(value, label, errorClass, code) #
Refuse anything that is not a finite number above 0, and answer it otherwise. undefined and null pass as "not supplied". A fraction is allowed.
This is the check for a duration or a size that has to be something: a timeout of zero is not a short timeout, it is no timeout, and a cap of zero admits nothing. Both are refused so the mistake surfaces at the call rather than as behavior nobody asked for.
var b = require("@blamejs/core");
b.validateOpts.optionalPositiveFinite(1500, "myThing: opts.timeoutMs", MyError, "my/bad-opt");
// → 1500
b.validateOpts.optionalPositiveFinite(0, "myThing: opts.timeoutMs", MyError, "my/bad-opt");
// throws: must be a positive finite number (> 0), got 0
b.validateOpts.optionalFunction(value, label, errorClass, code) #
Refuse a value that is neither a function nor absent, and answer it otherwise. undefined and null pass as "not supplied".
A hook that is not callable is never called, and a primitive that guards every call site with typeof would skip it silently. Refusing here means the operator hears about it at the call that configured it.
var b = require("@blamejs/core");
b.validateOpts.optionalFunction(onError, "myThing: opts.onError", MyError, "my/bad-opt");
// → the function
b.validateOpts.optionalFunction("onError", "myThing: opts.onError", MyError, "my/bad-opt");
// throws: must be a function, got string
b.validateOpts.definedFunctionMessage(value, label) #
Answer the complaint about value, or null when there is none. It is the message b.validateOpts.definedFunction throws, without throwing, for a caller that gathers several complaints before reporting them together.
Null means no complaint: a function has none, and neither does undefined, which stands for "not supplied". Anything else answers a sentence naming what arrived.
var b = require("@blamejs/core");
b.validateOpts.definedFunctionMessage(function () {}, "onError"); // → null
b.validateOpts.definedFunctionMessage(undefined, "onError"); // → null
b.validateOpts.definedFunctionMessage(42, "onError");
// → a sentence beginning "onError must be a function, got number"
b.validateOpts.definedFunction(value, label, errorClass, code) #
Refuse a value that was supplied and is not a function. undefined passes, meaning nothing was supplied.
null does not pass, which is the difference from b.validateOpts.optionalFunction. Writing null is a decision to supply something, and what was supplied cannot be called, so it is the shape of a hook that was meant to be there and is not.
var b = require("@blamejs/core");
b.validateOpts.definedFunction(undefined, "onError", MyError, "my/bad-opt");
// → undefined: nothing was supplied
b.validateOpts.definedFunction(null, "onError", MyError, "my/bad-opt");
// throws: onError must be a function, got null
b.validateOpts.optionalPort(value, label, errorClass, code, opts?) #
{
allowZero: boolean, // accept 0, meaning any free port
}
Refuse anything that is not a TCP or UDP port number, and answer it otherwise. A port is a whole number in [1, 65535]. undefined and null pass as "not supplied".
Zero is refused. To the operating system it means "any free port", which is useful in a test and almost never what an operator meant to write in a configuration file, where it reads as a port that was never filled in.
var b = require("@blamejs/core");
b.validateOpts.optionalPort(8443, "myThing: opts.port", MyError, "my/bad-opt");
// → 8443
b.validateOpts.optionalPort(70000, "myThing: opts.port", MyError, "my/bad-opt");
// throws: must be an integer in [1,65535], got number 70000
b.validateOpts.applyDefaults(opts, defaults) #
Answer a new object holding defaults with opts over the top. Neither argument is modified, and a null or undefined opts answers the defaults.
A key whose value is undefined takes the default rather than overriding it with nothing, so building an options object from variables that may be unset does not blank out the defaults for the ones that are.
var b = require("@blamejs/core");
b.validateOpts.applyDefaults({ retries: 5 }, { retries: 3, timeoutMs: 1000 });
// → { retries: 5, timeoutMs: 1000 }
b.validateOpts.applyDefaults({ retries: undefined }, { retries: 3 });
// → { retries: 3 }
b.validateOpts.requireObject(opts, callerLabel, errorClass, code) #
Refuse anything that is not a non-null object, and answer it otherwise. This is for a primitive whose options are required rather than optional, so undefined and null are both refused.
It asks only whether there is an object there. An array satisfies it; b.validateOpts.optionalPlainObject is the check that does not.
var b = require("@blamejs/core");
b.validateOpts.requireObject({ url: "x" }, "myThing.create", MyError, "my/bad-opt");
// → the object
b.validateOpts.requireObject(null, "myThing.create", MyError, "my/bad-opt");
// throws
b.validateOpts.requireMethods(obj, methods, callerLabel, errorClass, code, permanent?) #
Refuse an object that does not carry every named method as a function, and answer it otherwise. This is how a primitive states what it needs from a dependency an operator supplies: a cache, a store, a transport.
The message names the first method that is missing and lists all the ones required, so the reader sees the whole contract rather than discovering it one call at a time.
var b = require("@blamejs/core");
b.validateOpts.requireMethods(store, ["get", "set"], "myThing.create", MyError, "my/bad-opt");
// → store
b.validateOpts.requireMethods({}, ["get", "set"], "myThing.create", MyError, "my/bad-opt");
// throws: myThing.create must expose a get() method (requires { get, set })
b.validateOpts.optionalNonEmptyString(value, label, errorClass, code) #
Refuse anything that is not a string with something in it, and answer it otherwise. undefined and null pass as "not supplied".
The empty string is refused rather than treated as absent, because the two mean different things and only one of them is a mistake: a name, a namespace or a prefix that came out empty is a value that was built and lost, not a value nobody supplied.
var b = require("@blamejs/core");
b.validateOpts.optionalNonEmptyString("orders", "myThing: opts.namespace", MyError, "my/bad-opt");
// → "orders"
b.validateOpts.optionalNonEmptyString("", "myThing: opts.namespace", MyError, "my/bad-opt");
// throws
b.validateOpts.requireNonEmptyString(value, label, errorClass, code) #
Refuse anything that is not a string with something in it, and answer it otherwise. Unlike the optional form, undefined and null are refused too: this is for a value the primitive cannot run without.
var b = require("@blamejs/core");
b.validateOpts.requireNonEmptyString("orders", "myThing.create: name", MyError, "my/bad-opt");
// → "orders"
b.validateOpts.requireNonEmptyString(undefined, "myThing.create: name", MyError, "my/bad-opt");
// throws: must be a non-empty string, got undefined
b.validateOpts.optionalNonEmptyStringArray(value, label, errorClass, code) #
Refuse anything that is not an array whose every entry is a non-empty string, and answer it otherwise. undefined and null pass as "not supplied".
An empty array passes: a list of no allowed hosts, or no scopes, is a list the caller wrote and meant. What is refused is an entry that is not a string, since a list of names holding a number or an object is a list assembled from the wrong thing.
var b = require("@blamejs/core");
b.validateOpts.optionalNonEmptyStringArray(["read", "write"], "myThing: opts.scopes", MyError, "my/bad-opt");
// → ["read", "write"]
b.validateOpts.optionalNonEmptyStringArray([1], "myThing: opts.scopes", MyError, "my/bad-opt");
// throws
b.validateOpts.optionalObjectWithMethod(value, method, label, errorClass, code, description?) #
Refuse a supplied value that is not an object carrying method as a function, and answer it otherwise. undefined and null pass as "not supplied".
This is b.validateOpts.requireMethods for the single-method case, on an option that may be omitted. description names the shape in the message, so the reader is told what kind of thing was wanted rather than only which method was missing.
var b = require("@blamejs/core");
b.validateOpts.optionalObjectWithMethod(signer, "sign", "myThing: opts.signWith",
MyError, "my/bad-opt", "a b.auditSign-shaped object");
// → signer
b.validateOpts.optionalObjectWithMethod({}, "sign", "myThing: opts.signWith",
MyError, "my/bad-opt", "a b.auditSign-shaped object");
// throws
b.validateOpts.optionalPlainObject(value, label, errorClass, code, description?) #
Refuse a supplied value that is not a plain object, and answer it otherwise. undefined and null pass as "not supplied".
An array is refused, which is the difference from b.validateOpts.requireObject. This is the check for an option that is a bag of named values, headers or labels or metadata, where an array would be read for its numeric keys and quietly contribute nothing.
Plain means the prototype is Object.prototype or null, so a Map, a Set, a Date, a Buffer, a typed array and a RegExp are refused as well. Each of those is an object the caller means as a value rather than a bag, and reading one for named keys finds none of them, which is the same silent nothing an array contributes. A class instance carrying the options as own properties is refused too; pass its fields.
var b = require("@blamejs/core");
b.validateOpts.optionalPlainObject({ region: "eu" }, "myThing: opts.labels", MyError, "my/bad-opt");
// → { region: "eu" }
b.validateOpts.optionalPlainObject(["eu"], "myThing: opts.labels", MyError, "my/bad-opt");
// throws
b.validateOpts.shape(opts, schema, callerLabel, errorClass, code, options?) #
Check a whole options object against a schema in one call, and answer it. The schema maps each key to a rule name or to a function that checks the value itself.
The rule names are required-string, optional-string, optional-string-array, optional-boolean, optional-positive-int, optional-positive-finite, optional-non-negative, optional-date, optional-function, optional-plain-object, optional-port, optional-positive-finite-int, optional-non-negative-finite-int, required-positive-finite-int and required-object. Each runs the helper of the same shape.
A name outside that list is refused, naming the rule and the field, because a misspelled rule would otherwise check nothing while reading as a check.
opts must be an object; there is no optional form, since a schema describes options the primitive is going to read.
var b = require("@blamejs/core");
b.validateOpts.shape({ name: "orders", retries: 3 }, {
name: "required-string",
retries: "optional-positive-int",
}, "myThing.create", MyError, "my/bad-opt");
// → the opts object
b.validateOpts.makeAuditEmitter(audit) #
Answer a function that emits to audit, or one that does nothing when no sink was supplied. The caller emits unconditionally and never tests whether auditing is configured.
The emitter never throws. An audit sink is not the caller's reason for running, so a sink that fails must not take the request with it.
var b = require("@blamejs/core");
var emit = b.validateOpts.makeAuditEmitter(b.audit);
emit({ action: "thing.done", outcome: "success" });
b.validateOpts.makeNamespacedEmitters(prefix, deps) #
Answer { audit, metric }, two emitters that put prefix in front of every name they send. A primitive builds them once and emits without repeating its own name at each call site, so the names it reports cannot drift apart from one another.
Both are drop-safe: neither an audit sink nor a metrics sink is the caller's reason for running.
var b = require("@blamejs/core");
var emit = b.validateOpts.makeNamespacedEmitters("myThing", { audit: b.audit });
emit.audit("started", "success", { region: "eu" });
emit.metric("queue.depth", 12);
b.validateOpts.assignOwnEnumerable(target, source, reservedKeys?) #
Copy source's own enumerable keys onto target and answer target, skipping anything named in reservedKeys.
It is Object.assign with two differences that matter when the source came from outside: nothing inherited is copied, and the keys the caller reserves for its own use cannot be overwritten by the source. A caller merging operator input onto a record uses this so a field the record owns, an id or a tenant, stays the record's.
var b = require("@blamejs/core");
b.validateOpts.assignOwnEnumerable({}, { name: "ada", tenantId: "evil" }, ["tenantId"]);
// → { name: "ada" }
b.validateOpts.outboundHttpOpts(value, callerLabel, errorClass, codePrefix) #
Check the options a primitive takes for making outbound HTTP requests, and answer them resolved as { client, allowedHosts }, both null when nothing was supplied.
Every primitive that calls out to a URL takes the same two: a replacement HTTP client, which is what a test supplies instead of a socket, and the hosts it may reach. Checking them in one place means a primitive that adds an outbound call gets the host restriction rather than reinventing it or leaving it out.
var b = require("@blamejs/core");
b.validateOpts.outboundHttpOpts({ allowedHosts: ["api.example"] },
"myThing.create", MyError, "my");
// → { client: null, allowedHosts: ["api.example"] }
b.validateOpts.observabilityShape(observability, callerLabel, errorClass, code) #
Refuse an observability option that is not a metrics sink, and answer it otherwise. A sink is an object carrying an event function, which is the shape b.observability has. undefined and null pass as "not supplied".
An object carrying only safeEvent is refused: that is the drop-silent wrapper rather than the namespace, and a primitive handed it would find the call it expected missing.
var b = require("@blamejs/core");
b.validateOpts.observabilityShape(b.observability, "myThing.create", MyError, "my/bad-opt");
// → the sink
b.validateOpts.observabilityShape({}, "myThing.create", MyError, "my/bad-opt");
// throws: observability must be a b.observability-shaped object (event fn)
b.validateOpts.checkOrThrow(opts, allowedKeys, primitive, ErrorClass, code) #
The unknown-key check of b.validateOpts, raising the caller's own error class and code instead of a plain Error.
The message is the same. What changes is what a caller catches: a primitive whose every other refusal is its own error type reports a misspelled option the same way, so an operator matches on one class rather than on that class and Error.
var b = require("@blamejs/core");
try {
b.validateOpts.checkOrThrow({ maxBytes: 1 }, ["maxSize"], "myThing.create",
MyError, "my/bad-opt");
} catch (e) {
e.code; // → "my/bad-opt"
}