@nestjs/config
Version:
Configuration for NestJS: load .env files and custom configuration, validate it at startup, and read it through an injectable ConfigService.
143 lines (142 loc) • 4.62 kB
JavaScript
import toPath from 'es-toolkit/compat/toPath';
/**
* Prototypes shared by every value of their type in the process. A property
* path never resolves to a member inherited from one of them (e.g.
* "flags.hasOwnProperty" or "constructor.prototype"), so that a path built
* from user input can neither read nor write a built-in
*/
const SHARED_PROTOTYPES = new Set([
...[
Object,
Function,
Array,
String,
Number,
Boolean,
Symbol,
BigInt,
Date,
RegExp,
Error,
Map,
Set,
WeakMap,
WeakSet,
Promise,
ArrayBuffer,
SharedArrayBuffer,
DataView,
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array,
BigInt64Array,
BigUint64Array,
Buffer,
].map(constructor => constructor.prototype),
// %TypedArray%.prototype and %IteratorPrototype%
Object.getPrototypeOf(Uint8Array.prototype),
Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())),
]);
const UNSAFE_KEYS = new Set([
'__proto__',
'constructor',
'prototype',
]);
// Bound once, so that it keeps working if "hasOwnProperty.call" is replaced
export const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
/**
* Returns whether "key" can be read from "target" as part of a property path:
* an own property, or one inherited from a prototype that is not a built-in
* (e.g. a getter of a class instance returned by a "load" factory).
*/
function isReachable(target, key) {
if (hasOwn(target, key)) {
return true;
}
if (UNSAFE_KEYS.has(key)) {
return false;
}
let owner = Object.getPrototypeOf(target);
while (owner && !hasOwn(owner, key)) {
owner = Object.getPrototypeOf(owner);
}
return !!owner && !SHARED_PROTOTYPES.has(owner);
}
/**
* Returns the first key of a property path (e.g. "database" for
* "database.host"), or the path itself if "object" has it as a key.
*/
export function getRootKey(object, path) {
return object === null || object === undefined
? path
: toSegments(Object(object), path)[0];
}
function toSegments(object, path) {
// Key that exists as is (e.g. "A.B" in process.env) is not split, same as
// in "es-toolkit"
if (typeof path !== 'string' || isReachable(object, path)) {
return [path];
}
const segments = toPath(path);
return segments.length > 1 ? segments : [path];
}
function isIndex(key) {
return typeof key === 'string' && /^(?:0|[1-9]\d*)$/.test(key);
}
/**
* Resolves a property path (e.g. "database.host") against "object".
* Members inherited from built-in prototypes resolve to `undefined`.
*/
export function getProperty(object, path) {
return resolveProperty(object, path).value;
}
/**
* Resolves a property path (e.g. "database.host") against "object". "found"
* tells a path whose value is `undefined` from one that doesn't exist.
*/
export function resolveProperty(object, path) {
if (object === null || object === undefined) {
return { found: false, value: undefined };
}
let current = object;
for (const key of toSegments(Object(object), path)) {
if (current === null || current === undefined) {
return { found: false, value: undefined };
}
const target = Object(current);
if (!isReachable(target, key)) {
return { found: false, value: undefined };
}
current = target[key];
}
return { found: true, value: current };
}
/**
* Sets the value at a property path (e.g. "database.host") of "object",
* creating the missing objects on the way. Only own properties are descended
* into, so the write can't land on a built-in shared by the whole process.
* Paths through "__proto__", "constructor" or "prototype" are ignored.
*/
export function setProperty(object, path, value) {
const segments = toSegments(object, path);
if (segments.some(key => UNSAFE_KEYS.has(key))) {
return;
}
let current = object;
for (let i = 0; i < segments.length - 1; i++) {
const key = segments[i];
let next = hasOwn(current, key) ? current[key] : undefined;
if (typeof next !== 'object' || next === null) {
next = isIndex(segments[i + 1]) ? [] : {};
current[key] = next;
}
current = next;
}
current[segments[segments.length - 1]] = value;
}