Skip to content

Commit

Permalink
feat: improve nodeEnv typings
Browse files Browse the repository at this point in the history
  • Loading branch information
allohamora committed Apr 10, 2023
1 parent 2d1c8e4 commit 3673a1d
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 23 deletions.
24 changes: 12 additions & 12 deletions src/env/env.manager.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Manager } from 'src/utils/manager.utils.js';
import { EnvPicker } from './env.picker.js';
import { BaseConfig, EnvPicker } from './env.picker.js';

interface Options<C> {
interface Options<E, C> {
load?: () => C;
nodeEnv?: () => string;
nodeEnv?: () => E;
}

export class EnvManager<C extends Record<string, string | undefined>> extends Manager<C> {
export class EnvManager<E extends string, C extends BaseConfig = BaseConfig> extends Manager<C> {
private nodeEnv: string;
private cache = new Map<keyof C, EnvPicker<string | undefined>>();
private cache = new Map<keyof C, EnvPicker<E, string | undefined>>();

constructor({
load = () => process.env as C,
nodeEnv = () => process.env.NODE_ENV ?? 'development',
}: Options<C> = {}) {
nodeEnv = () => (process.env.NODE_ENV ?? 'development') as E,
}: Options<E, C> = {}) {
super({ load });

this.nodeEnv = nodeEnv();
Expand All @@ -29,21 +29,21 @@ export class EnvManager<C extends Record<string, string | undefined>> extends Ma
return value;
}

public pick<K extends keyof C>(key: K): EnvPicker<C[K] | undefined> {
public pick<K extends keyof C>(key: K): EnvPicker<E, C[K] | undefined> {
if (!this.cache.has(key)) {
this.cache.set(key, new EnvPicker(this.getEnvValue(key), this.nodeEnv));
this.cache.set(key, new EnvPicker(this.getEnvValue(key), this.nodeEnv) as EnvPicker<E, C[K] | undefined>);
}

return this.cache.get(key) as EnvPicker<C[K] | undefined>;
return this.cache.get(key) as EnvPicker<E, C[K] | undefined>;
}

public pickOrThrow<K extends keyof C>(key: K): EnvPicker<C[K]> {
public pickOrThrow<K extends keyof C>(key: K): EnvPicker<E, C[K]> {
const value = this.source[key];

if (value === null || value === undefined || value.trim() === '') {
throw new Error(`key: ${key.toString()} is empty`);
}

return this.pick(key) as EnvPicker<C[K]>;
return this.pick(key) as EnvPicker<E, C[K]>;
}
}
24 changes: 13 additions & 11 deletions src/env/env.picker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type WrappedInEnvPickers<T> = {
[K in keyof T]: EnvPicker<T[K]>;
export type WrappedInEnvPickers<E extends string, T> = {
[K in keyof T]: EnvPicker<E, T[K]>;
};

type MoveOptional<T, R> = T extends null
Expand All @@ -10,22 +10,24 @@ type MoveOptional<T, R> = T extends null
? R | undefined
: R;

export const wrapInEnvPickers = <C extends Record<string, string | undefined>>(config: C, nodeEnv: string) => {
export type BaseConfig = Record<string, string | undefined>;

export const wrapInEnvPickers = <E extends string, C extends BaseConfig>(config: C, nodeEnv: E) => {
return Object.keys(config).reduce((result, key) => {
return { ...result, [key]: new EnvPicker(config[key], nodeEnv) };
}, {} as WrappedInEnvPickers<C>);
}, {} as WrappedInEnvPickers<E, C>);
};

export class EnvPicker<S> {
constructor(private state: S, private nodeEnv: string) {}
export class EnvPicker<E extends string, S> {
constructor(private state: S, private nodeEnv: E) {}

public default<NS extends S>(newState: NS): EnvPicker<NS | NonNullable<S>> {
public default<NS extends S>(newState: NS): EnvPicker<E, NS | NonNullable<S>> {
this.state ??= newState;

return this as unknown as EnvPicker<NS | NonNullable<S>>;
return this as unknown as EnvPicker<E, NS | NonNullable<S>>;
}

public defaultFor(envRecord: Record<string, S>) {
public defaultFor(envRecord: Partial<Record<E, S>>) {
this.state ??= envRecord[this.nodeEnv] as S;

return this;
Expand All @@ -34,15 +36,15 @@ export class EnvPicker<S> {
public map<R>(mapper: (state: S) => R) {
this.state = mapper(this.state) as unknown as S;

return this as unknown as EnvPicker<R>;
return this as unknown as EnvPicker<E, R>;
}

public mapIfExists<R>(mapper: (state: NonNullable<S>) => R) {
if (this.state !== null && this.state !== undefined) {
this.state = mapper(this.state as NonNullable<S>) as unknown as S;
}

return this as unknown as EnvPicker<MoveOptional<S, R>>;
return this as unknown as EnvPicker<E, MoveOptional<S, R>>;
}

public value() {
Expand Down

0 comments on commit 3673a1d

Please sign in to comment.