seniority
Every export of seniority, with its signature and doc comment: explain, ConfigError, envBoolean, envName, ORDER, RANK and 34 more, plus 31 types.
seniority — which source outranks the others.
One resolution for flags, environment, config files, a package.json field and declared
defaults, in a fixed order, with provenance: every value can say where it came from.
That last part is the whole reason this is a package rather than a function — --explain
is only trustworthy if the thing that picked the value is the thing that reports it.
flag > env > config file > package.json field > defaultThe order is not configurable. A precedence a program can rearrange is a precedence nobody can reason about from the outside.
resolve is pure — layers in, values and provenance out — so it can be tested without a
filesystem, an environment, or a process. discover is the half that does touch the
disk, and it is a separate import for exactly that reason.
Zero dependencies; Node builtins only.
import { explain, ConfigError, envBoolean, … } from 'seniority';Functions
check
The throwing form: the resolved values, or one CONFIG-class error listing every
violation. validate is the record; this is the rendering a program that just wants to
exit reaches for (E1, E3 — a message and a hint, never a stack).
function check(shape: Record<string, Shape>, resolution: Resolution): Record<string, unknown>;| Parameter | Type |
|---|---|
shape | Record<string, Shape> |
resolution | Resolution |
Returns Record<string, unknown>
decodeFileContent
A config file saved as UTF-16 — PowerShell's Out-File default on Windows — is not valid
UTF-8, and reading it as UTF-8 produces text no parser can use. A UTF-8 BOM is deliberately
left where it is: that is what 10.0.1 does, and one of its four cases asserts it.
function decodeFileContent(buffer: Buffer): string;| Parameter | Type |
|---|---|
buffer | Buffer |
Returns string
explanationEvent
The agent rendering: the family's { event, data } envelope over the same projection.
function explanationEvent(e: Explanation): ExplanationEvent;| Parameter | Type |
|---|---|
e | Explanation |
Returns ExplanationEvent
explanationJson
The --json rendering: the same record, with set spelled out rather than inferred from a missing key.
function explanationJson(e: Explanation): ExplanationJson;| Parameter | Type |
|---|---|
e | Explanation |
Returns ExplanationJson
getDefaultSearchPlaces
.foorc, .foorc.json, foo.config.js, … — the async explorer's twenty-one places, in order.
function getDefaultSearchPlaces(moduleName: string): string[];| Parameter | Type |
|---|---|
moduleName | string |
Returns string[]
getDefaultSearchPlacesSync
The same list minus every .mjs: a synchronous load cannot import an ES module.
function getDefaultSearchPlacesSync(moduleName: string): string[];| Parameter | Type |
|---|---|
moduleName | string |
Returns string[]
getPropertyByPath
A property name, or a period-delimited path, or an array of names.
The literal key wins: getPropertyByPath(source, 'ant.beetle.cootie') returns
source['ant.beetle.cootie'] when that key exists, and only otherwise splits on periods.
A name with a period inside a path can therefore only be expressed as an array, which is
what the array form is for.
The walk guards undefined and nothing else, as 10.0.1's does. A path through a string
reads the string's own properties (packageProp: 'name.length' is a number), and a path
through a null — "foo": null under packageProp: 'foo.bar' — throws the TypeError
upstream throws, which the loader then annotates with the file. lilconfig.ts reproduces
the same throw on purpose; a guard here would make the two façades disagree about one file.
function getPropertyByPath(source: unknown, path: string | readonly string[]): unknown;| Parameter | Type |
|---|---|
source | unknown |
path | string | readonly string[] |
Returns unknown
loaderFor
The loader for a path, or the USAGE refusal that names what is missing.
function loaderFor(filepath: string, loaders?: Readonly<Record<string, Loader>>): Loader;| Parameter | Type |
|---|---|
filepath | string |
loaders (optional) | Readonly<Record<string, Loader>> |
Returns Loader
loadPath
Read one file and parse it. The object check is here rather than in each loader so a caller's loader cannot be the reason a non-object reaches the resolver.
function loadPath(filepath: string, options?: LoadOptions): Promise<Record<string, unknown>>;| Parameter | Type |
|---|---|
filepath | string |
options (optional) | LoadOptions |
Returns Promise<Record<string, unknown>>
search
The nearest match, or undefined.
Proximity outranks the name. Every name is checked in one directory before the walk
steps up, so a .apprc beside you wins over an app.config.json two directories above
even when the caller listed the latter first. That is the question an upward walk is
being asked; ordering by name instead would answer a different one silently.
function search(names: string | readonly string[], options: SearchOptions): Found | undefined;| Parameter | Type |
|---|---|
names | string | readonly string[] |
options | SearchOptions |
Returns Found \| undefined
searchAll
Every match on the way up, nearest directory first and, within a directory, in the order given.
function searchAll(names: string | readonly string[], options: SearchOptions): Found[];| Parameter | Type |
|---|---|
names | string | readonly string[] |
options | SearchOptions |
Returns Found[]
validate
Every violation, in the order the shape declares its options — never the first one alone. A config file with three mistakes should be fixable in one pass, not three runs.
function validate(shape: Record<string, Shape>, resolution: Resolution): Violation[];| Parameter | Type |
|---|---|
shape | Record<string, Shape> |
resolution | Resolution |
Returns Violation[]
Classes
LoaderError
A format nobody declared a loader for.
USAGE and not CONFIG on purpose. CONFIG (3) tells the user their configuration file
is wrong, and sends them to read a file that may be perfectly good; the mistake is the
program's, one line up, where it did not declare the loader it needs. Carrying the code on
the error rather than choosing it at the exit is what lets a façade report it correctly
without this package owning a process.
class LoaderError extends Error {
readonly extension: string;
readonly hint: string;
readonly exitCode = 2;
constructor(message: string, extension: string, hint: string);
}Constants
builtinLoaders
The four Node can read unaided. Frozen: one caller's loaders['.json'] = … must not reach another's.
const defaultLoaders: Readonly<Record<string, Loader>>;defaultLoaders
The async loader table. Frozen: index.test.ts asserts that deleting a key throws.
const defaultLoaders: Readonly<Record<string, Loader>>;defaultLoadersSync
The sync table: no .mjs, no .mts, and loadJsSync in place of loadJs.
const defaultLoadersSync: Readonly<Record<string, Loader>>;globalConfigSearchPlaces
Tried in the user's global config directory, which is always the last directory a global search visits.
const globalConfigSearchPlaces: string[];globalConfigSearchPlacesSync
The same, minus .mjs.
const globalConfigSearchPlacesSync: string[];metaSearchPlaces
Where cosmiconfig looks for its own configuration — the cosmiconfig key of a
.config/config.* or a package.json. Internal to 10.0.1 and exported here because the
meta explorer is the one part of the option pipeline a caller can observe.
const metaSearchPlaces: string[];NOT_BUNDLED
The formats a caller may supply and this package will not bundle, listed so the refusal can name them and so a reader can see the 747 M/wk that is being declined rather than missed.
const NOT_BUNDLED: readonly string[];WALK_LIMIT
How many directories one walk may visit.
A number rather than "until the root" alone, because the root is not the only way a walk ends badly: a bind mount, a container overlay or a deliberately deep fixture tree all present as an ancestor chain that is long rather than infinite. Sixty-four is far past any real repository — a monorepo package sits six or seven directories down — and small enough that the worst case is imperceptible.
const WALK_LIMIT = 64;Interfaces
ExplanationEvent
interface ExplanationEvent {
event: 'config.explain';
data: ExplanationJson;
}ExplanationJson
interface ExplanationJson {
option: string;
value?: unknown;
source?: string;
location?: string;
line?: number;
candidates: ExplanationCandidateJson[];
}Found
interface Found {
/** The file, absolute. */
path: string;
/** The directory it was found in, absolute. */
dir: string;
/** How many steps up from `cwd` — `0` is `cwd` itself. What `--explain` prints as "found N levels up". */
depth: number;
}LoadOptions
interface LoadOptions {
/** Merged over `defaultLoaders`; a caller may add a format or replace a builtin. */
loaders?: Readonly<Record<string, Loader>>;
}SearchOptions
interface SearchOptions {
/** Where the walk starts. The first directory checked, not the first one above it. */
cwd: string;
/** The highest directory the walk may look in, inclusive. Defaults to the filesystem root. */
stopAt?: string;
/** Directories to visit at most. Defaults to `WALK_LIMIT`; a smaller number wins. */
limit?: number;
/** Injected for tests and for a caller with its own filesystem. Defaults to `existsSync`. */
exists?: (path: string) => boolean;
/** Injected the same way. Defaults to `realpathSync`, and a path that cannot be resolved is used as written. */
realpath?: (path: string) => string;
}Shape
What validation reads from a manifest, and no more.
type is checked when it is one of the four JavaScript kinds and ignored otherwise: a
shape that says nothing asserts nothing, which is what lets a richer spec pass through a
field this package has never heard of.
interface Shape {
type?: string;
choices?: readonly unknown[];
required?: boolean;
default?: unknown;
}Violation
interface Violation {
key: string;
/** The whole sentence, ready to print: the rule, then where the value came from. */
message: string;
/** What the value should have been — `'a string'`, `'one of fast, safe'`. */
expected: string;
value: unknown;
/** Absent only for a required option that no source set: there is no origin to name. */
provenance?: Provenance;
}Types
Loader
What a loader is handed and what it gives back. The same shape cosmiconfig uses, so a caller's loader ports unchanged.
type Loader = (filepath: string, content: string) => unknown;Re-exported
Documented on the page of the entry point that declares them.
Incremental migration from cosmiconfig
Swap cosmiconfig for seniority in one import, then move to discover() and resolve() when you want precedence and provenance — and what changes at each step.
seniority/precedence
Every export of seniority/precedence, with its signature and doc comment: screaming, envName, envBoolean, resolve, ORDER, RANK and 1 more, plus 9 types.