Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add config class to project #47

Merged
merged 3 commits into from
Mar 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,71 @@ Path.switchBuild().assets() // '/home/your/computer/path/your-project-name/publi

---

### Config

> Handle configurations files values inside your application ecosystem

```ts
// First you need to create your configuration file using the file template

// app.ts
export default {
name: 'secjs'
}

// database.ts
export default {
host: '127.0.0.1',
port: Env('PORT', 5432),
// You can use Config.get inside this config files and Config class will handle it for you
database: Config.get('app.name')
}
```

> Loading configuration files and get then

```ts
// To load configuration files you need to create a instance of Config
const config = new Config()

// Loading database.ts will automatic load app.ts, because database.ts depends on app.ts
config.load('database.ts')

// So now we can get information of both

console.log(Config.get('app.name')) // 'secjs'
console.log(Config.get('database.port')) // 5432
console.log(Config.get('database.database')) // 'secjs'

// You can call load again and you will never lose the previous states
config.load('example.ts')

console.log(Config.get('app.name')) // 'secjs'
```

> Be careful with Config.get() in configuration files ⚠️🛑

```ts
// Lets create this two configuration files as example

// recursive-errorA.ts
export default {
// Here we are using a property from recursive-errorB
recursive: Config.get('recursive-errorB.recursive')
}

// recursive-errorB.ts
export default {
// And here we are using a property from recursive-errorA
recursive: Config.get('recursive-errorA.recursive')
}

// If you try to load any of this two files you will get an error from Config class
// Config class will start going file to file and she cant resolve any of then because one depends on the other
```

---

### Json

> Use Json to parse json without errors, deep copy, observeChanges inside objects and more.
Expand Down
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from './src/Classes/Folder'
export * from './src/Classes/Parser'
export * from './src/Classes/String'
export * from './src/Classes/Number'
export * from './src/Classes/Config'
export * from './src/Classes/Blacklist'

export * from './src/Functions/sleep'
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@secjs/utils",
"version": "1.7.3",
"version": "1.7.4",
"description": "",
"license": "MIT",
"author": "João Lenon",
Expand All @@ -9,7 +9,7 @@
"homepage": "https://github.com/SecJS/Utils#readme",
"scripts": {
"build": "tsc",
"test": "jest --verbose",
"test": "NODE_TS=true jest --verbose",
"test:debug": "DEBUG=* jest --verbose",
"lint:fix": "eslint \"{src,tests}/**/*.ts\" --fix"
},
Expand Down
83 changes: 83 additions & 0 deletions src/Classes/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @secjs/utils
*
* (c) João Lenon <lenon@secjs.com.br>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { parse } from 'path'
import { File } from './File'
import { Debug } from './Debug'
import { InternalServerException } from '@secjs/exceptions'

export class Config {
private static configs: Map<string, any> = new Map()
private static debug = new Debug(Config.name, 'api:configurations')

static get<T = any>(key: string, defaultValue = undefined): T | undefined {
const [mainKey, ...keys] = key.split('.')

let config = this.configs.get(mainKey)

if (config && keys.length) {
keys.forEach(key => (config = config[key]))
}

if (!config) config = defaultValue

return config
}

// Load the configuration file on demand
load(path: string, callNumber = 0) {
const { dir, name, base } = parse(path)

if (callNumber > 500) {
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.`

throw new InternalServerException(content)
}

if (Config.configs.get(name)) return
if (base.includes('.map') || base.includes('.d.ts')) return

const file = new File(`${dir}/${base}`).loadSync()
const fileContent = file.getContentSync().toString()

if (
!fileContent.includes('export default') &&
!fileContent.includes('module.exports')
) {
throw new InternalServerException(
`Config file ${base} is not normalized because it is not exporting a default value`,
)
}

if (fileContent.includes('Config.get')) {
const matches = fileContent.match(/Config.get\(([^)]+)\)/g)

for (let match of matches) {
match = match.replace('Config.get', '').replace(/[(^)']/g, '')

const fileExtension = process.env.NODE_TS === 'true' ? 'ts' : 'js'
const fileName = `${match.split('.')[0]}`
const fileBase = `${fileName}.${fileExtension}`
const filePath = `${dir}/${fileBase}`

// If configuration already exists continue the loop
if (Config.configs.has(fileName)) continue

this.load(filePath, callNumber + 1)
}
}

Config.debug.log(`Loading ${name} configuration file`)
Config.configs.set(name, require(`${dir}/${name}`).default)
}

clear() {
Config.configs.clear()
}
}
1 change: 0 additions & 1 deletion src/Classes/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { String } from './String'
import { InternalServerException } from '@secjs/exceptions'
import { getReasonPhrase, getStatusCode } from 'http-status-codes'
import { DBUrlParserContract } from '../Contracts/DBUrlParserContract'
import { homedir } from 'os'

export class Parser {
/**
Expand Down
61 changes: 61 additions & 0 deletions tests/Classes/config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @secjs/utils
*
* (c) João Lenon <lenon@secjs.com.br>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Path } from '../../src/Classes/Path'
import { Config } from '../../src/Classes/Config'

describe('\n Config Class', () => {
beforeEach(async () => {
const config = new Config()

config.clear()
config.load(Path.tests('stubs/test.ts'))
})

it('should be able to get configurations values from Config class', async () => {
const string = Config.get('test.hello')

expect(string).toBe('world')
})

it('should be able to create a load chain when a configuration uses other configuration', async () => {
const config = new Config()

config.load(Path.tests('stubs/test-ondemand.ts'))

expect(Config.get('sub-test.sub')).toBe(true)
expect(Config.get('test-ondemand.hello')).toBe(true)
})

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 () => {
try {
new Config().load(Path.tests('stubs/infinite-callA.ts'))
} catch (err) {
expect(err.name).toBe('InternalServerException')
expect(err.status).toBe(500)
expect(err.isSecJsException).toBe(true)
expect(err.content).toBe(
"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.",
)
}
})

it('should throw an error when trying to load a file that is not normalized', async () => {
try {
new Config().load(Path.tests('stubs/test-error.ts'))
} catch (err) {
expect(err.name).toBe('InternalServerException')
expect(err.status).toBe(500)
expect(err.isSecJsException).toBe(true)
expect(err.content).toBe(
'Config file test-error.ts is not normalized because it is not exporting a default value',
)
}
})
})
14 changes: 14 additions & 0 deletions tests/stubs/infinite-callA.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @secjs/utils
*
* (c) João Lenon <lenon@secjs.com.br>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Config } from '../../src/Classes/Config'

export default {
infinite: Config.get('infinite-callB.infinite'),
}
14 changes: 14 additions & 0 deletions tests/stubs/infinite-callB.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @secjs/utils
*
* (c) João Lenon <lenon@secjs.com.br>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Config } from '../../src/Classes/Config'

export default {
infinite: Config.get('infinite-callA.infinite'),
}
12 changes: 12 additions & 0 deletions tests/stubs/sub-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @secjs/utils
*
* (c) João Lenon <lenon@secjs.com.br>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export default {
sub: true,
}
12 changes: 12 additions & 0 deletions tests/stubs/test-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @secjs/utils
*
* (c) João Lenon <lenon@secjs.com.br>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export const config = {
error: true,
}
14 changes: 14 additions & 0 deletions tests/stubs/test-ondemand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @secjs/utils
*
* (c) João Lenon <lenon@secjs.com.br>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Config } from '../../src/Classes/Config'

export default {
hello: Config.get('sub-test.sub'),
}
13 changes: 13 additions & 0 deletions tests/stubs/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @secjs/utils
*
* (c) João Lenon <lenon@secjs.com.br>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export default {
hello: 'world',
nice: 'package',
}