@@ -9,6 +9,7 @@ const NumberCtor = Number
99// `exports.SomeName = void 0;` which causes runtime errors.
1010// See: https://github.com/SocketDev/socket-packageurl-js/issues/3
1111const NumberIsFinite = Number . isFinite
12+ const NumberIsNaN = Number . isNaN
1213const NumberParseInt = Number . parseInt
1314const StringCtor = String
1415
@@ -173,31 +174,59 @@ export function createEnvProxy(
173174 } ) as NodeJS . ProcessEnv
174175}
175176
177+ /**
178+ * Options for `envAsBoolean`.
179+ */
180+ export interface EnvAsBooleanOptions {
181+ /** Default when value is null/undefined. @default false */
182+ defaultValue ?: boolean | undefined
183+ /**
184+ * Whether to trim whitespace from string values before matching. When
185+ * `false`, `' true '` is NOT recognised as truthy — only exact matches.
186+ * @default true
187+ */
188+ trim ?: boolean | undefined
189+ }
190+
176191/**
177192 * Convert an environment variable value to a boolean.
178193 *
194+ * Back-compat overload: passing a bare boolean as the second argument is
195+ * equivalent to `{ defaultValue: B }`.
196+ *
179197 * @param value - The value to convert
180- * @param defaultValue - Default when value is null/undefined (default: `false`)
181- * @returns `true` if value is '1' or 'true ' (case-insensitive), `false` otherwise
198+ * @param defaultValueOrOptions - Default (boolean) or options object
199+ * @returns `true` if value is '1', 'true', or 'yes ' (case-insensitive), `false` otherwise
182200 *
183201 * @example
184202 * ```typescript
185203 * import { envAsBoolean } from '@socketsecurity/lib/env'
186204 *
187- * envAsBoolean('true') // true
188- * envAsBoolean('1') // true
189- * envAsBoolean('false') // false
190- * envAsBoolean(undefined) // false
205+ * envAsBoolean('true') // true
206+ * envAsBoolean('1') // true
207+ * envAsBoolean('yes') // true
208+ * envAsBoolean(' true ') // true (trimmed)
209+ * envAsBoolean(' true ', { trim: false }) // false (strict)
210+ * envAsBoolean(undefined) // false
211+ * envAsBoolean(undefined, true) // true (legacy positional default)
191212 * ```
192213 */
193214/*@__NO_SIDE_EFFECTS__ */
194- export function envAsBoolean ( value : unknown , defaultValue = false ) : boolean {
215+ export function envAsBoolean (
216+ value : unknown ,
217+ defaultValueOrOptions : boolean | EnvAsBooleanOptions | undefined = false ,
218+ ) : boolean {
219+ const opts : EnvAsBooleanOptions =
220+ typeof defaultValueOrOptions === 'boolean'
221+ ? { defaultValue : defaultValueOrOptions }
222+ : ( defaultValueOrOptions ?? { } )
223+ const { defaultValue = false , trim = true } = opts
195224 if ( typeof value === 'string' ) {
196- const trimmed = value . trim ( )
197- if ( ! trimmed ) {
225+ const candidate = trim ? value . trim ( ) : value
226+ if ( ! candidate ) {
198227 return ! ! defaultValue
199228 }
200- const lower = trimmed . toLowerCase ( )
229+ const lower = candidate . toLowerCase ( )
201230 return lower === '1' || lower === 'true' || lower === 'yes'
202231 }
203232 if ( value === null || value === undefined ) {
@@ -206,25 +235,80 @@ export function envAsBoolean(value: unknown, defaultValue = false): boolean {
206235 return ! ! value
207236}
208237
238+ /**
239+ * Options for `envAsNumber`.
240+ */
241+ export interface EnvAsNumberOptions {
242+ /**
243+ * Whether to return `±Infinity` when input parses to infinity. When
244+ * `false` (default), infinities and NaN are coerced to `defaultValue`.
245+ * @default false
246+ */
247+ allowInfinity ?: boolean | undefined
248+ /** Default when value is not a finite number. @default 0 */
249+ defaultValue ?: number | undefined
250+ /**
251+ * Parse mode. `'int'` (default) uses `parseInt(_, 10)` — integer only.
252+ * `'float'` uses `Number()` — decimals preserved.
253+ * @default 'int'
254+ */
255+ mode ?: 'int' | 'float' | undefined
256+ }
257+
209258/**
210259 * Convert an environment variable value to a number.
211260 *
261+ * Back-compat overload: passing a bare number as the second argument is
262+ * equivalent to `{ defaultValue: N }`.
263+ *
212264 * @param value - The value to convert
213- * @param defaultValue - Default when value is not a finite number (default: `0`)
214- * @returns The parsed integer , or the default value if parsing fails
265+ * @param defaultValueOrOptions - Default ( number) or options object
266+ * @returns The parsed number , or the default value if parsing fails
215267 *
216268 * @example
217269 * ```typescript
218270 * import { envAsNumber } from '@socketsecurity/lib/env'
219271 *
220- * envAsNumber('3000') // 3000
221- * envAsNumber('abc') // 0
222- * envAsNumber(undefined) // 0
272+ * envAsNumber('3000') // 3000 (int mode)
273+ * envAsNumber('3.14', { mode: 'float' }) // 3.14
274+ * envAsNumber('abc') // 0
275+ * envAsNumber(undefined, 42) // 42 (legacy positional default)
223276 * ```
224277 */
225278/*@__NO_SIDE_EFFECTS__ */
226- export function envAsNumber ( value : unknown , defaultValue = 0 ) : number {
227- const numOrNaN = NumberParseInt ( String ( value ) , 10 )
279+ export function envAsNumber (
280+ value : unknown ,
281+ defaultValueOrOptions : number | EnvAsNumberOptions | undefined = 0 ,
282+ ) : number {
283+ const opts : EnvAsNumberOptions =
284+ typeof defaultValueOrOptions === 'number'
285+ ? { defaultValue : defaultValueOrOptions }
286+ : ( defaultValueOrOptions ?? { } )
287+ const { allowInfinity = false , defaultValue = 0 , mode = 'int' } = opts
288+
289+ // Fast-paths for the strict `string | undefined` shape (helpers semantics).
290+ if ( value === undefined || value === null ) {
291+ return defaultValue
292+ }
293+ if ( typeof value === 'string' ) {
294+ if ( ! value ) {
295+ return defaultValue
296+ }
297+ const num = mode === 'float' ? NumberCtor ( value ) : NumberParseInt ( value , 10 )
298+ if ( NumberIsNaN ( num ) ) {
299+ return defaultValue
300+ }
301+ if ( ! NumberIsFinite ( num ) ) {
302+ return allowInfinity ? num : defaultValue
303+ }
304+ return num || 0
305+ }
306+
307+ // Broad (unknown) path — coerce via String() then parse.
308+ const numOrNaN =
309+ mode === 'float'
310+ ? NumberCtor ( String ( value ) )
311+ : NumberParseInt ( String ( value ) , 10 )
228312 const numMayBeNegZero = NumberIsFinite ( numOrNaN )
229313 ? numOrNaN
230314 : NumberCtor ( defaultValue )
@@ -233,30 +317,74 @@ export function envAsNumber(value: unknown, defaultValue = 0): number {
233317}
234318
235319/**
236- * Convert an environment variable value to a trimmed string.
320+ * Options for `envAsString`.
321+ */
322+ export interface EnvAsStringOptions {
323+ /** Default when value is null/undefined. @default '' */
324+ defaultValue ?: string | undefined
325+ /**
326+ * Whether to trim whitespace from string values. `true` (default) trims.
327+ * Set `false` to preserve whitespace (helpers.envAsString semantics).
328+ * @default true
329+ */
330+ trim ?: boolean | undefined
331+ }
332+
333+ /**
334+ * Convert an environment variable value to a string.
335+ *
336+ * Back-compat overload: passing a bare string as the second argument is
337+ * equivalent to `{ defaultValue: S }`.
237338 *
238339 * @param value - The value to convert
239- * @param defaultValue - Default when value is null/undefined (default: `''`)
240- * @returns The trimmed string value, or the default value
340+ * @param defaultValueOrOptions - Default (string) or options object
341+ * @returns The string value, or the default value
241342 *
242343 * @example
243344 * ```typescript
244345 * import { envAsString } from '@socketsecurity/lib/env'
245346 *
246- * envAsString(' hello ') // 'hello'
247- * envAsString(undefined) // ''
248- * envAsString(null, 'n/a') // 'n/a'
347+ * envAsString(' hello ') // 'hello' (trimmed)
348+ * envAsString(' hello ', { trim: false }) // ' hello '
349+ * envAsString(undefined) // ''
350+ * envAsString(null, 'n/a') // 'n/a' (legacy positional)
249351 * ```
250352 */
251353/*@__NO_SIDE_EFFECTS__ */
252- export function envAsString ( value : unknown , defaultValue = '' ) : string {
253- if ( typeof value === 'string' ) {
254- return value . trim ( )
354+ export function envAsString (
355+ value : unknown ,
356+ defaultValueOrOptions : string | EnvAsStringOptions | undefined = '' ,
357+ ) : string {
358+ // Accept bare string OR any non-options value as positional default for
359+ // legacy compat (`envAsString(null, 123)` coerces to '123'). Options form
360+ // is detected by plain-object shape with known keys.
361+ const isOptionsObject =
362+ typeof defaultValueOrOptions === 'object' &&
363+ defaultValueOrOptions !== null &&
364+ ! Array . isArray ( defaultValueOrOptions ) &&
365+ ( 'defaultValue' in defaultValueOrOptions || 'trim' in defaultValueOrOptions )
366+ const opts : EnvAsStringOptions = isOptionsObject
367+ ? ( defaultValueOrOptions as EnvAsStringOptions )
368+ : {
369+ defaultValue :
370+ defaultValueOrOptions === undefined
371+ ? ''
372+ : typeof defaultValueOrOptions === 'string'
373+ ? defaultValueOrOptions
374+ : StringCtor ( defaultValueOrOptions ) ,
375+ }
376+ const { defaultValue = '' , trim = true } = opts
377+
378+ if ( value === undefined || value === null ) {
379+ return defaultValue === '' || ! trim
380+ ? defaultValue
381+ : StringCtor ( defaultValue ) . trim ( )
255382 }
256- if ( value === null || value === undefined ) {
257- return defaultValue === '' ? defaultValue : StringCtor ( defaultValue ) . trim ( )
383+ if ( typeof value === 'string' ) {
384+ return trim ? value . trim ( ) : value
258385 }
259- return StringCtor ( value ) . trim ( )
386+ const str = StringCtor ( value )
387+ return trim ? str . trim ( ) : str
260388}
261389
262390/**
0 commit comments