Skip to content

Commit 8438efc

Browse files
authored
Merge pull request #47 from SecJS/feat/len-add-config-class
feat: add config class to project
2 parents 122b555 + bea4328 commit 8438efc

File tree

12 files changed

+291
-3
lines changed

12 files changed

+291
-3
lines changed

README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,71 @@ Path.switchBuild().assets() // '/home/your/computer/path/your-project-name/publi
289289

290290
---
291291

292+
### Config
293+
294+
> Handle configurations files values inside your application ecosystem
295+
296+
```ts
297+
// First you need to create your configuration file using the file template
298+
299+
// app.ts
300+
export default {
301+
name: 'secjs'
302+
}
303+
304+
// database.ts
305+
export default {
306+
host: '127.0.0.1',
307+
port: Env('PORT', 5432),
308+
// You can use Config.get inside this config files and Config class will handle it for you
309+
database: Config.get('app.name')
310+
}
311+
```
312+
313+
> Loading configuration files and get then
314+
315+
```ts
316+
// To load configuration files you need to create a instance of Config
317+
const config = new Config()
318+
319+
// Loading database.ts will automatic load app.ts, because database.ts depends on app.ts
320+
config.load('database.ts')
321+
322+
// So now we can get information of both
323+
324+
console.log(Config.get('app.name')) // 'secjs'
325+
console.log(Config.get('database.port')) // 5432
326+
console.log(Config.get('database.database')) // 'secjs'
327+
328+
// You can call load again and you will never lose the previous states
329+
config.load('example.ts')
330+
331+
console.log(Config.get('app.name')) // 'secjs'
332+
```
333+
334+
> Be careful with Config.get() in configuration files ⚠️🛑
335+
336+
```ts
337+
// Lets create this two configuration files as example
338+
339+
// recursive-errorA.ts
340+
export default {
341+
// Here we are using a property from recursive-errorB
342+
recursive: Config.get('recursive-errorB.recursive')
343+
}
344+
345+
// recursive-errorB.ts
346+
export default {
347+
// And here we are using a property from recursive-errorA
348+
recursive: Config.get('recursive-errorA.recursive')
349+
}
350+
351+
// If you try to load any of this two files you will get an error from Config class
352+
// Config class will start going file to file and she cant resolve any of then because one depends on the other
353+
```
354+
355+
---
356+
292357
### Json
293358

294359
> Use Json to parse json without errors, deep copy, observeChanges inside objects and more.

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export * from './src/Classes/Folder'
1414
export * from './src/Classes/Parser'
1515
export * from './src/Classes/String'
1616
export * from './src/Classes/Number'
17+
export * from './src/Classes/Config'
1718
export * from './src/Classes/Blacklist'
1819

1920
export * from './src/Functions/sleep'

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@secjs/utils",
3-
"version": "1.7.3",
3+
"version": "1.7.4",
44
"description": "",
55
"license": "MIT",
66
"author": "João Lenon",
@@ -9,7 +9,7 @@
99
"homepage": "https://github.com/SecJS/Utils#readme",
1010
"scripts": {
1111
"build": "tsc",
12-
"test": "jest --verbose",
12+
"test": "NODE_TS=true jest --verbose",
1313
"test:debug": "DEBUG=* jest --verbose",
1414
"lint:fix": "eslint \"{src,tests}/**/*.ts\" --fix"
1515
},

src/Classes/Config.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* @secjs/utils
3+
*
4+
* (c) João Lenon <lenon@secjs.com.br>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { parse } from 'path'
11+
import { File } from './File'
12+
import { Debug } from './Debug'
13+
import { InternalServerException } from '@secjs/exceptions'
14+
15+
export class Config {
16+
private static configs: Map<string, any> = new Map()
17+
private static debug = new Debug(Config.name, 'api:configurations')
18+
19+
static get<T = any>(key: string, defaultValue = undefined): T | undefined {
20+
const [mainKey, ...keys] = key.split('.')
21+
22+
let config = this.configs.get(mainKey)
23+
24+
if (config && keys.length) {
25+
keys.forEach(key => (config = config[key]))
26+
}
27+
28+
if (!config) config = defaultValue
29+
30+
return config
31+
}
32+
33+
// Load the configuration file on demand
34+
load(path: string, callNumber = 0) {
35+
const { dir, name, base } = parse(path)
36+
37+
if (callNumber > 500) {
38+
const content = `Your config file ${base} is using Config.get() to an other config file that is using a Config.get('${name}*'), creating a infinite recursive call.`
39+
40+
throw new InternalServerException(content)
41+
}
42+
43+
if (Config.configs.get(name)) return
44+
if (base.includes('.map') || base.includes('.d.ts')) return
45+
46+
const file = new File(`${dir}/${base}`).loadSync()
47+
const fileContent = file.getContentSync().toString()
48+
49+
if (
50+
!fileContent.includes('export default') &&
51+
!fileContent.includes('module.exports')
52+
) {
53+
throw new InternalServerException(
54+
`Config file ${base} is not normalized because it is not exporting a default value`,
55+
)
56+
}
57+
58+
if (fileContent.includes('Config.get')) {
59+
const matches = fileContent.match(/Config.get\(([^)]+)\)/g)
60+
61+
for (let match of matches) {
62+
match = match.replace('Config.get', '').replace(/[(^)']/g, '')
63+
64+
const fileExtension = process.env.NODE_TS === 'true' ? 'ts' : 'js'
65+
const fileName = `${match.split('.')[0]}`
66+
const fileBase = `${fileName}.${fileExtension}`
67+
const filePath = `${dir}/${fileBase}`
68+
69+
// If configuration already exists continue the loop
70+
if (Config.configs.has(fileName)) continue
71+
72+
this.load(filePath, callNumber + 1)
73+
}
74+
}
75+
76+
Config.debug.log(`Loading ${name} configuration file`)
77+
Config.configs.set(name, require(`${dir}/${name}`).default)
78+
}
79+
80+
clear() {
81+
Config.configs.clear()
82+
}
83+
}

src/Classes/Parser.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { String } from './String'
1515
import { InternalServerException } from '@secjs/exceptions'
1616
import { getReasonPhrase, getStatusCode } from 'http-status-codes'
1717
import { DBUrlParserContract } from '../Contracts/DBUrlParserContract'
18-
import { homedir } from 'os'
1918

2019
export class Parser {
2120
/**

tests/Classes/config.spec.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @secjs/utils
3+
*
4+
* (c) João Lenon <lenon@secjs.com.br>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { Path } from '../../src/Classes/Path'
11+
import { Config } from '../../src/Classes/Config'
12+
13+
describe('\n Config Class', () => {
14+
beforeEach(async () => {
15+
const config = new Config()
16+
17+
config.clear()
18+
config.load(Path.tests('stubs/test.ts'))
19+
})
20+
21+
it('should be able to get configurations values from Config class', async () => {
22+
const string = Config.get('test.hello')
23+
24+
expect(string).toBe('world')
25+
})
26+
27+
it('should be able to create a load chain when a configuration uses other configuration', async () => {
28+
const config = new Config()
29+
30+
config.load(Path.tests('stubs/test-ondemand.ts'))
31+
32+
expect(Config.get('sub-test.sub')).toBe(true)
33+
expect(Config.get('test-ondemand.hello')).toBe(true)
34+
})
35+
36+
it('should throw an error when file is trying to use Config.get() to get information from other config file but this config file is trying to use Config.get() to this same file', async () => {
37+
try {
38+
new Config().load(Path.tests('stubs/infinite-callA.ts'))
39+
} catch (err) {
40+
expect(err.name).toBe('InternalServerException')
41+
expect(err.status).toBe(500)
42+
expect(err.isSecJsException).toBe(true)
43+
expect(err.content).toBe(
44+
"Your config file infinite-callB.ts is using Config.get() to an other config file that is using a Config.get('infinite-callB*'), creating a infinite recursive call.",
45+
)
46+
}
47+
})
48+
49+
it('should throw an error when trying to load a file that is not normalized', async () => {
50+
try {
51+
new Config().load(Path.tests('stubs/test-error.ts'))
52+
} catch (err) {
53+
expect(err.name).toBe('InternalServerException')
54+
expect(err.status).toBe(500)
55+
expect(err.isSecJsException).toBe(true)
56+
expect(err.content).toBe(
57+
'Config file test-error.ts is not normalized because it is not exporting a default value',
58+
)
59+
}
60+
})
61+
})

tests/stubs/infinite-callA.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @secjs/utils
3+
*
4+
* (c) João Lenon <lenon@secjs.com.br>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { Config } from '../../src/Classes/Config'
11+
12+
export default {
13+
infinite: Config.get('infinite-callB.infinite'),
14+
}

tests/stubs/infinite-callB.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @secjs/utils
3+
*
4+
* (c) João Lenon <lenon@secjs.com.br>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { Config } from '../../src/Classes/Config'
11+
12+
export default {
13+
infinite: Config.get('infinite-callA.infinite'),
14+
}

tests/stubs/sub-test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @secjs/utils
3+
*
4+
* (c) João Lenon <lenon@secjs.com.br>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
export default {
11+
sub: true,
12+
}

tests/stubs/test-error.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @secjs/utils
3+
*
4+
* (c) João Lenon <lenon@secjs.com.br>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
export const config = {
11+
error: true,
12+
}

0 commit comments

Comments
 (0)