@@ -7,6 +7,12 @@ const publicMetadataKey = Symbol('public');
77const envMetadataKey = Symbol ( 'env' ) ;
88const arrayMetadataKey = Symbol ( 'arrayType' ) ;
99
10+ type DSpaceEnvVar = `DSPACE_${string } `;
11+
12+ interface DeepEnvSpec {
13+ [ k : string ] : DeepEnvSpec | DSpaceEnvVar | [ DSpaceEnvVar , ( val : string ) => any ] ;
14+ }
15+
1016export class Config {
1117 /**
1218 * Decorator that marks a config property as public
@@ -31,10 +37,28 @@ export class Config {
3137 * value of the environment variable and returns the appropriate
3238 * type to use for the config property.
3339 */
34- static env ( name : `DSPACE_${ string } ` , loader ?: ( val : string ) => any ) {
40+ static env ( name : DSpaceEnvVar , loader ?: ( val : string ) => any ) {
3541 return Reflect . metadata ( envMetadataKey , { name, loader } ) ;
3642 }
3743
44+
45+ /**
46+ * Decorator for changing a deeply nested config property via an environment variable
47+ *
48+ * The method 'Config#applyEnv' will load object properties with
49+ * this decoration from the environment object passed in.
50+ *
51+ * @param spec A plain object whose structure at least partially
52+ * matches that of the property being decorated, with values being
53+ * either a string environment variable name for that nested
54+ * property, or an array of a string environment variable name and
55+ * loader function which can transform the environment variable
56+ * value into the desired type.
57+ */
58+ static deepEnv ( spec : DeepEnvSpec ) {
59+ return Reflect . metadata ( envMetadataKey , { deep : true , spec } ) ;
60+ }
61+
3862 /**
3963 * Decorator to mark a config property as an array of another type of Config
4064 *
@@ -152,6 +176,27 @@ export class Config {
152176 // be a map of environment variable names to their values, such as
153177 // in `process.env`.
154178 protected applyEnvironment ( env : { [ k : string ] : string } ) {
179+ const deepEnv = (
180+ target : any , spec : any , envObj : { [ k : string ] : string } ,
181+ ) => {
182+ Object . keys ( spec ) . forEach ( k => {
183+ if ( typeof spec [ k ] === 'string' ) {
184+ const val = envObj [ spec [ k ] ] ;
185+ if ( isNotEmpty ( val ) ) {
186+ target [ k ] = val ;
187+ }
188+ } else if ( Array . isArray ( spec [ k ] )
189+ && ( typeof spec [ k ] [ 0 ] === 'string' ) ) {
190+ const val = envObj [ spec [ k ] [ 0 ] ] ;
191+ if ( isNotEmpty ( val ) ) {
192+ target [ k ] = spec [ k ] [ 1 ] ( val ) ;
193+ }
194+ } else {
195+ deepEnv ( target [ k ] , spec [ k ] , envObj ) ;
196+ }
197+ } ) ;
198+ } ;
199+
155200 Object . keys ( this ) . forEach ( k => {
156201 if ( this [ k ] instanceof Config ) {
157202 this [ k ] . applyEnvironment ( env ) ;
@@ -160,9 +205,13 @@ export class Config {
160205 } else {
161206 const envMeta = Reflect . getMetadata ( envMetadataKey , this , k ) ;
162207 if ( envMeta ) {
163- const val = env [ envMeta . name ] ;
164- if ( isNotEmpty ( val ) ) {
165- this [ k ] = envMeta . loader ? envMeta . loader ( val ) : val ;
208+ if ( envMeta . deep ) {
209+ deepEnv ( this [ k ] , envMeta . spec , env ) ;
210+ } else {
211+ const val = env [ envMeta . name ] ;
212+ if ( isNotEmpty ( val ) ) {
213+ this [ k ] = envMeta . loader ? envMeta . loader ( val ) : val ;
214+ }
166215 }
167216 }
168217 }
0 commit comments