@btravstack/config
@btravstack/config
Classes
ConfigFieldInvalid
Defined in: packages/config/src/config.ts:194
Why one variable's value is not the value a field wanted — the message ConfigInvalid reports against it.
Extends
TaggedErrorInstance<"ConfigFieldInvalid", {reason:string; }>
Constructors
Constructor
new ConfigFieldInvalid(args): ConfigFieldInvalid;Defined in: node_modules/.pnpm/unthrown@5.12.1/node_modules/unthrown/dist/index.d.mts:2386
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
TaggedError("ConfigFieldInvalid")<{ readonly reason: string; }>.constructor
Properties
| Property | Modifier | Type | Overrides | Inherited from | Defined in |
|---|---|---|---|---|---|
_tag | readonly | "ConfigFieldInvalid" | - | TaggedError("ConfigFieldInvalid")._tag | node_modules/.pnpm/unthrown@5.12.1/node_modules/unthrown/dist/index.d.mts:2363 |
cause? | public | unknown | - | TaggedError("ConfigFieldInvalid").cause | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24 |
message | public | string | TaggedError("ConfigFieldInvalid").message | - | packages/config/src/config.ts:197 |
name | public | string | - | TaggedError("ConfigFieldInvalid").name | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074 |
reason | readonly | string | - | TaggedError("ConfigFieldInvalid").reason | packages/config/src/config.ts:195 |
stack? | public | string | - | TaggedError("ConfigFieldInvalid").stack | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1076 |
ConfigInvalid
Defined in: packages/config/src/config.ts:153
A configuration port could not be bound: the environment is wrong, not the code. runMain maps it to sysexits(3)'s EX_CONFIG (78) rather than the generic startup 1, and its message names every offending variable.
Extends
TaggedErrorInstance<"ConfigInvalid", {issues: readonlyConfigIssue[];port:string; }>
Constructors
Constructor
new ConfigInvalid(args): ConfigInvalid;Defined in: node_modules/.pnpm/unthrown@5.12.1/node_modules/unthrown/dist/index.d.mts:2386
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
TaggedError("ConfigInvalid")<{ readonly port: string; readonly issues: readonly ConfigIssue[]; }>.constructor
Properties
| Property | Modifier | Type | Overrides | Inherited from | Defined in |
|---|---|---|---|---|---|
_tag | readonly | "ConfigInvalid" | - | TaggedError("ConfigInvalid")._tag | node_modules/.pnpm/unthrown@5.12.1/node_modules/unthrown/dist/index.d.mts:2363 |
cause? | public | unknown | - | TaggedError("ConfigInvalid").cause | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24 |
issues | readonly | readonly ConfigIssue[] | - | TaggedError("ConfigInvalid").issues | packages/config/src/config.ts:155 |
message | public | string | TaggedError("ConfigInvalid").message | - | packages/config/src/config.ts:157 |
name | public | string | - | TaggedError("ConfigInvalid").name | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074 |
port | readonly | string | - | TaggedError("ConfigInvalid").port | packages/config/src/config.ts:154 |
stack? | public | string | - | TaggedError("ConfigInvalid").stack | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1076 |
Env
Defined in: packages/config/src/config.ts:29
The environment, as a service. The kernel provides this port to every graph it boots — process.env by default, StartOptions.env for a test — so nothing else in an application touches process.env.
Extends
PortInstance<"Env",Readonly<Record<string,string|undefined>>>
Constructors
Constructor
new Env(): Env;Defined in: packages/di/dist/index.d.mts:15
Returns
Inherited from
Port("Env")<Environment>.constructor
Properties
Type Aliases
ConfigField
type ConfigField<T, V, Need> = object;Defined in: packages/config/src/config.ts:210
One environment variable, read into a value: parse receives the raw string (or undefined when unset) and answers the value or the reason it is not one. Compose them with Config.object.
check is the same field's rule applied to a value that is ALREADY a T — a pin, or a default — so the two routes into a configuration cannot disagree about what is valid. It is optional: a hand-written field keeps compiling and simply accepts whatever it is handed.
Type Parameters
| Type Parameter | Default type |
|---|---|
T | - |
V extends string | string |
Need extends "required" | "optional" | "required" | "optional" |
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
~need? | readonly | Need | Type-only: whether the variable must be set — "optional" for a field with a default or a pin. Never present at runtime. | packages/config/src/config.ts:222 |
check? | readonly | (value) => Result<T, ConfigFieldInvalid> | - | packages/config/src/config.ts:217 |
parse | readonly | (raw) => Result<T, ConfigFieldInvalid> | - | packages/config/src/config.ts:216 |
variable | readonly | V | - | packages/config/src/config.ts:215 |
ConfigIssue
type ConfigIssue = object;Defined in: packages/config/src/config.ts:143
The one issue shape a configuration schema reports: Standard Schema's Issue, restated structurally so this package depends on nothing.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
message | readonly | string | packages/config/src/config.ts:144 |
path? | readonly | ReadonlyArray< | PropertyKey | { key: PropertyKey; }> | packages/config/src/config.ts:145 |
ConfigSchema
type ConfigSchema<Input, Output> = object;Defined in: packages/config/src/config.ts:176
The slice of Standard Schema (v1) this package speaks — structurally, so a zod, valibot or arktype schema is accepted with no adapter, and so is anything Config.object builds.
Type Parameters
| Type Parameter |
|---|
Input |
Output |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
~standard | readonly | object | packages/config/src/config.ts:177 |
~standard.types? | readonly | object | packages/config/src/config.ts:189 |
~standard.types.input | readonly | Input | packages/config/src/config.ts:189 |
~standard.types.output | readonly | Output | packages/config/src/config.ts:189 |
~standard.validate | readonly | (value) => | { issues?: undefined; value: Output; } | { issues: readonly ConfigIssue[]; } | Promise< | { issues?: undefined; value: Output; } | { issues: readonly ConfigIssue[]; }> | packages/config/src/config.ts:180 |
~standard.vendor | readonly | string | packages/config/src/config.ts:179 |
~standard.version | readonly | 1 | packages/config/src/config.ts:178 |
Environment
type Environment = Readonly<Record<string, string | undefined>>;Defined in: packages/config/src/config.ts:22
The process environment as it actually arrives: flat, and every value a string or absent.
EnvironmentFor
type EnvironmentFor<N> = [Unnamed<N>] extends [never] ? { readonly [K in RequiredOf<N> & string]: string } & { readonly [K in Exclude<OptionalOf<N>, RequiredOf<N>> & string]?: string } : Environment;Defined in: packages/config/src/config.ts:133
The environment a graph with needs N is configured from: every variable a reader requires, every one it can do without, and nothing else — or any record at all when one of its readers named nothing.
A variable one reader requires and another defaults is required.
Type Parameters
| Type Parameter |
|---|
N |
EnvNeed
type EnvNeed<I> = string extends keyof I ? Env : [I extends unknown ? keyof I : never] extends [keyof I] ? EnvReading<{ [K in keyof I]-?: Record<never, never> extends Pick<I, K> ? never : K }[keyof I] & string, { [K in keyof I]-?: Record<never, never> extends Pick<I, K> ? K : never }[keyof I] & string> : Env;Defined in: packages/config/src/config.ts:50
What reading I costs a provider: the variables I names, or plain Env when I is an open record and nothing can be said about them.
The key computations are INLINE rather than named helpers: declaration emit prints a named alias unreduced, and a consumer exporting a module would then name an alias this package does not export.
Type Parameters
| Type Parameter |
|---|
I |
EnvPortFor
type EnvPortFor<I> = object & (abstract new () => EnvNeed<I>);Defined in: packages/config/src/config.ts:118
The Env port class, as a need for EnvNeed<I> — what Config.env answers.
Type Parameters
| Type Parameter |
|---|
I |
Type Declaration
| Name | Type | Defined in |
|---|---|---|
portId | "Env" | packages/config/src/config.ts:118 |
EnvReading
type EnvReading<Required, Optional> = Env & object;Defined in: packages/config/src/config.ts:38
Env, as a need that names what its reader reads: the variables it requires and the ones it can do without. A module's needs union them, and EnvironmentFor turns that union into the env its boot accepts.
A string key rather than a symbol, so declaration emit can always name it.
Type Parameters
| Type Parameter |
|---|
Required extends string |
Optional extends string |
Type Declaration
| Name | Type | Defined in |
|---|---|---|
~env | object | packages/config/src/config.ts:39 |
~env.optional | Optional | packages/config/src/config.ts:39 |
~env.required | Required | packages/config/src/config.ts:39 |
MaybePinned
type MaybePinned<O, K, V> = [PinState<O, K>] extends ["absent"] ? never : [PinState<O, K>] extends ["set"] ? never : V;Defined in: packages/config/src/config.ts:96
V, when O may or may not set K: a variable that may be set.
Type Parameters
| Type Parameter |
|---|
O |
K extends PropertyKey |
V extends string |
OptionsAs
type OptionsAs<O, T> = { readonly [K in keyof O]: K extends keyof T ? T[K] : never };Defined in: packages/config/src/config.ts:110
O, checked key by key against the options type T: a key T does not know is never, so a misspelt option is an error at its own key. A starter takes its options as O & OptionsAs<O, T> with O extends T — O naked, so it is inferred as written, a union or an annotated type included.
Type Parameters
| Type Parameter |
|---|
O |
T |
Unpinned
type Unpinned<O, K, V> = [PinState<O, K>] extends ["absent"] ? V : never;Defined in: packages/config/src/config.ts:89
V, when the options O certainly leave K — the option that could pin it — unset: a variable that must be set. For a starter spelling its needs from its options' type, which over generic options its schema cannot resolve.
Type Parameters
| Type Parameter |
|---|
O |
K extends PropertyKey |
V extends string |
Variables
Config
const Config: object;Defined in: packages/config/src/config.ts:378
Configuration, the twelve-factor way: typed values bound from the environment, validated once as the graph is built, and injected like any other service.
Config.object({...}) describes a slice of the environment as a schema — any Standard Schema does as well — and Config.provider(Port)(schema) turns it into a provider that reads Env and answers ConfigInvalid.
Type Declaration
| Name | Type | Default value | Description | Defined in |
|---|---|---|---|---|
boolean() | <V, O>(variable, options?) => ConfigField<boolean, V, NeedOf<O>> | - | A flag: true/false, 1/0, yes/no or on/off, case-insensitive. Anything else is an error rather than a falsy reading — a deployment that wrote HTTP_COMPRESSION=enabled meant to turn it on. | packages/config/src/config.ts:423 |
env() | <I>(_schema) => EnvPortFor<I> | - | Env, typed as reading exactly what schema reads — the same port at run time. Inject it where a provider parses the environment itself rather than through Config.provider, so its needs name the variables and a boot's env is typed by them. | packages/config/src/config.ts:615 |
integer() | <V, O>(variable, options?) => ConfigField<number, V, NeedOf<O>> | - | A whole number, optionally bounded (both bounds inclusive). | packages/config/src/config.ts:400 |
list() | <V, O>(variable, given?) => ConfigField<readonly string[], V, NeedOf<O>> | - | A comma-separated list, each entry trimmed and empty entries dropped, so "a, b," is ["a", "b"]. min is the floor a shorter list is named against — a variable that lists nothing is a deployment mistake, not an empty list. Remarks min defaults to 1, unless a default is given — then 0. A default is the composition root saying what it wants when nobody sets the variable, and { default: [] } is a legitimate thing to want; under the flat 1 it was always refused, with a message blaming a variable nobody had set. An explicit min still applies to the default too, so the "a bound the environment route would refuse is not one a default gets to smuggle past" rule survives wherever a caller states one. A pin is checked against min whatever it is, which is why Config.pinned([], Config.list("HTTP_SESSION_KEYS")) is still refused: that field has no default, so its floor is 1, and a session codec with no key to seal with is broken rather than empty. | packages/config/src/config.ts:464 |
object() | <F>(fields) => ConfigSchema<EnvironmentOf<F>, { readonly [K in string | number | symbol]: F[K] extends ConfigField<T, string, "required" | "optional"> ? T : never }> | - | A record of fields, as a Standard Schema over the environment. Every field is read, so one validation names every offending variable at once — an operator fixes the deployment in one round trip. | packages/config/src/config.ts:534 |
parse() | <Output>(port, schema) => (env) => AsyncResult<Output, ConfigInvalid> | - | Validates env against schema; the issues become one ConfigInvalid naming port. This is the step Config.provider performs, on its own — for a piece that is already its own provider and has no second port to hang a Config.provider on, such as a starter binding its options inside a make arm. | packages/config/src/config.ts:588 |
pinned() | <T, V, N, X>(value, field) => ConfigField<T, undefined extends X ? V : never, [X] extends [undefined] ? N : "optional"> | - | field, unless value is given — then a field answering value and reading nothing. Explicit beats environment beats default, per field: http({ port: 0 }) still reads HOST. The pin is CHECKED against the field's own rule, so a value the deployment route would refuse is refused here too, with the same message. Without that, a pinned NaN body limit turned a trust boundary off in silence — size > NaN is false — and the composition root was the one input to the configuration system nothing validated. | packages/config/src/config.ts:513 |
port() | <V, O>(variable, options?) => ConfigField<number, V, NeedOf<O>> | - | A TCP port: a whole number the OS will accept, 0 (an ephemeral bind) included. | packages/config/src/config.ts:490 |
provider() | { <P>(port): <I>(schema) => Provider<InstanceType<P>, ConfigInvalid, EnvNeed<I>> & object; <Name>(name): <I, Output>(schema) => Provider<PortInstance<Name, Output>, ConfigInvalid, EnvNeed<I>> & object; } | configProvider | A provider binding a port from the environment through schema. Reads Env, so the port is built with the rest of the graph and a bad environment is a modeled startup Err rather than a silently wrong value. Two forms of the first call. Config.provider(Port)(schema) binds a port you declared — for a port that is public API another package names. Config.provider("RelayConfig")(schema) mints the port and hands back the provider carrying it, for a slice that is one application's own. | packages/config/src/config.ts:607 |
string() | <V, O>(variable, options?) => ConfigField<string, V, NeedOf<O>> | - | A non-empty string — a rule about the RAW value, deliberately not a check: "set but empty" is a deployment mistake, where a pinned "" is a decision. http({ cors: false }) pins exactly that, and an off switch spelled as the empty string must not be refused as a blank variable. Remarks The bound value is trimmed. X=" abc " binds "abc", because every field parses the trimmed string — it is what makes a whitespace-only variable "set but empty" rather than a value, and what stops a stray space from turning a port into a parse error. A secret whose whitespace is significant is therefore the one value this field cannot carry: pin it through the composition root instead, where Config.pinned hands the value over untouched. | packages/config/src/config.ts:394 |
url() | <V, O>(variable, options?) => ConfigField<string, V, NeedOf<O>> | - | A URL, kept as the string it was written as. The value is what a consumer hands to new URL, and this is what stops that construction from throwing: a malformed one is a ConfigInvalid naming the variable, at graph build, rather than a Defect from wherever the URL is finally needed. | packages/config/src/config.ts:438 |
Functions
cleartext()
function cleartext(url, allowed): boolean | "refused";Defined in: packages/config/src/cleartext.ts:29
Whether this URL may be talked to in cleartext, decided once at boot.
Config.url says a value PARSES, not that it is safe — measured, it accepts javascript:, mailto:, file: and a bare host:port, and it is named for a property it does not check. So the scheme question is asked here, by whoever knows what the URL is for.
https: is always fine, http: on a loopback host is the dev loop, and any other http: sends whatever the URL carries across the wire in the open. That case is "refused" — a ConfigInvalid at boot unless the caller pinned the opt-in — and so is every other scheme, opt-in or not: a file: issuer is a deployment mistake to report at boot, not on the first call, which is where a security posture belongs and is why it is an option rather than a variable: rule 6's own test, that a value whose silent change is a security regression stays in the composition root.
One rule for every starter that is handed a URL carrying a credential — an issuer a client secret is sent to, a JWKS whose keys decide which tokens are accepted. A public key set fetched in cleartext lets anything on the path substitute its own key and mint tokens the process then accepts (RFC 8725 §3), so even a URL that carries no secret is worth attacking.
Parameters
| Parameter | Type |
|---|---|
url | string |
allowed | boolean |
Returns
boolean | "refused"
cleartextRefused()
function cleartextRefused(args): ConfigInvalid;Defined in: packages/config/src/cleartext.ts:49
The refusal, as the ConfigInvalid a boot reports — one message, so the callers cannot drift into explaining the same rule differently.
port is the configuration port's name and variable the one an operator sets; option is the option they would pin to say they meant it and on the call it is pinned at. The two are separate fields because this builder puts the backticks in — folding the call into option produced `allowInsecureIssuer` on `oidc(): true`, with the : true attached to the wrong half and the backticks unbalanced.
Parameters
| Parameter | Type |
|---|---|
args | { on: string; option: string; port: string; variable: string; what: string; } |
args.on | string |
args.option | string |
args.port | string |
args.variable | string |
args.what | string |