logixlysia 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/lint-staged.config.cjs +6 -0
- package/package.json +14 -10
- package/src/index.ts +4 -11
- package/src/logger.ts +57 -24
- package/src/types/Logger.ts +12 -19
- package/src/utils/colorMapping.ts +40 -0
- package/src/utils/duration.ts +5 -14
- package/src/utils/log.ts +3 -10
- package/src/utils/method.ts +2 -13
- package/src/utils/path.ts +9 -5
- package/src/utils/start.ts +6 -7
- package/src/utils/status.ts +11 -13
- package/test/types/ColorMap.test.ts +24 -0
- package/test/types/Logger.test.ts +92 -0
- package/test/types/RequestInfo.test.ts +22 -0
- package/test/types/Server.test.ts +26 -0
- package/test/types/StoreData.test.ts +16 -0
- package/test/utils/duration.test.ts +20 -0
- package/test/utils/log.test.ts +25 -0
- package/test/utils/method.test.ts +20 -0
- package/test/utils/path.test.ts +35 -0
- package/test/utils/start.test.ts +35 -0
- package/test/utils/status.test.ts +30 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.0](https://github.com/PunGrumpy/logixlysia/compare/v2.0.2...v2.1.0) (2024-01-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **logger:** implement asynchronous logging functionality ([1017af0](https://github.com/PunGrumpy/logixlysia/commit/1017af0a28bd6121a85ec814a9177fd303c2572c))
|
|
9
|
+
|
|
10
|
+
## [2.0.2](https://github.com/PunGrumpy/logixlysia/compare/v2.0.1...v2.0.2) (2024-01-07)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **logger:** change time from `iso` to `local` ([33ff28a](https://github.com/PunGrumpy/logixlysia/commit/33ff28a4a41db1dda7ca224e4807bd2451b95985))
|
|
16
|
+
|
|
3
17
|
## [2.0.1](https://github.com/PunGrumpy/logixlysia/compare/v2.0.0...v2.0.1) (2024-01-03)
|
|
4
18
|
|
|
5
19
|
|
package/lint-staged.config.cjs
CHANGED
|
@@ -9,11 +9,17 @@ const isPnpm = existsSync(join(process.cwd(), 'pnpm-lock.yaml'))
|
|
|
9
9
|
const packageManager = isBun ? 'bun' : isYarn ? 'yarn' : isPnpm ? 'pnpm' : 'npm'
|
|
10
10
|
|
|
11
11
|
const options = {
|
|
12
|
+
// TypeScript & JavaScript files
|
|
12
13
|
'**/*.(ts|tsx)': () => `${packageManager} tsc --noEmit`,
|
|
13
14
|
'**/*.(ts|tsx|js)': filenames => [
|
|
14
15
|
`${packageManager} eslint --fix ${filenames.join(' ')}`,
|
|
15
16
|
`${packageManager} prettier --write ${filenames.join(' ')}`
|
|
16
17
|
],
|
|
18
|
+
'**/*.(css|less|scss)': filenames =>
|
|
19
|
+
`${packageManager} test --timeout 5000 --coverage --update-snapshots ${filenames.join(
|
|
20
|
+
' '
|
|
21
|
+
)}`,
|
|
22
|
+
// Markdown & JSON files
|
|
17
23
|
'**/*.(md|json)': filenames =>
|
|
18
24
|
`${packageManager} prettier --write ${filenames.join(' ')}`
|
|
19
25
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logixlysia",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "🦊 Logixlysia is a logger for Elysia",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -15,11 +15,15 @@
|
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
-
"dev": "bun run --watch example/basic.ts",
|
|
20
|
-
"prepare": "husky install",
|
|
18
|
+
"pretest": "bun run lint && bun run lint:fix && bun run prettier",
|
|
21
19
|
"lint": "eslint . --ext .ts",
|
|
22
20
|
"lint:fix": "eslint . --ext .ts --fix",
|
|
21
|
+
"prepublish": "bun run pretest && bun run test",
|
|
22
|
+
"test": "bun test --timeout 5000 --coverage --update-snapshots",
|
|
23
|
+
"test:ci": "bun test --timeout 5000 --coverage --update-snapshots",
|
|
24
|
+
"publish": "bun run prepublish && npm publish",
|
|
25
|
+
"dev": "bun run --watch example/basic.ts",
|
|
26
|
+
"prepare": "husky install",
|
|
23
27
|
"lint:staged": "lint-staged",
|
|
24
28
|
"prettier": "prettier --write ."
|
|
25
29
|
},
|
|
@@ -66,17 +70,17 @@
|
|
|
66
70
|
"middleware"
|
|
67
71
|
],
|
|
68
72
|
"dependencies": {
|
|
69
|
-
"@typescript-eslint/eslint-plugin": "^6.
|
|
70
|
-
"@typescript-eslint/parser": "^6.
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
|
74
|
+
"@typescript-eslint/parser": "^6.18.1",
|
|
71
75
|
"chalk": "^5.3.0",
|
|
72
|
-
"elysia": "^0.8.
|
|
73
|
-
"eslint": "^8.
|
|
76
|
+
"elysia": "^0.8.9",
|
|
77
|
+
"eslint": "^8.56.0"
|
|
74
78
|
},
|
|
75
79
|
"devDependencies": {
|
|
76
|
-
"bun-types": "^1.0.
|
|
80
|
+
"bun-types": "^1.0.22",
|
|
77
81
|
"husky": "^8.0.3",
|
|
78
82
|
"lint-staged": "^15.2.0",
|
|
79
|
-
"prettier": "^3.1.
|
|
83
|
+
"prettier": "^3.1.1"
|
|
80
84
|
},
|
|
81
85
|
"peerDependencies": {
|
|
82
86
|
"typescript": "^5.0.0"
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Server } from 'bun'
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a logger.
|
|
8
8
|
*
|
|
9
|
-
* @export
|
|
9
|
+
* @export
|
|
10
10
|
* @module logger
|
|
11
11
|
* @category Logger
|
|
12
12
|
* @subcategory Functions
|
|
@@ -28,20 +28,13 @@ export const logger = (): Elysia => {
|
|
|
28
28
|
startString(ctx.server as Server)
|
|
29
29
|
})
|
|
30
30
|
.onRequest(ctx => {
|
|
31
|
-
ctx.store = { beforeTime: process.hrtime.bigint() }
|
|
32
|
-
beforeTime: bigint
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
.onBeforeHandle(ctx => {
|
|
36
|
-
ctx.store = { beforeTime: process.hrtime.bigint() } as {
|
|
37
|
-
beforeTime: bigint
|
|
38
|
-
}
|
|
31
|
+
ctx.store = { beforeTime: process.hrtime.bigint() }
|
|
39
32
|
})
|
|
40
33
|
.onAfterHandle(({ request, store }) => {
|
|
41
|
-
log.
|
|
34
|
+
log.log('INFO', request, {}, store as { beforeTime: bigint })
|
|
42
35
|
})
|
|
43
36
|
.onError(({ request, error, store }) => {
|
|
44
|
-
log.
|
|
37
|
+
log.log('ERROR', request, error, store as { beforeTime: bigint })
|
|
45
38
|
})
|
|
46
39
|
|
|
47
40
|
return elysia
|
package/src/logger.ts
CHANGED
|
@@ -9,27 +9,47 @@ import { LogData, LogLevel, Logger } from './types/Logger'
|
|
|
9
9
|
import { StoreData } from './types/StoreData'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Asynchronously logs a message constructed from various log components.
|
|
13
13
|
*
|
|
14
|
-
* @
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {
|
|
14
|
+
* @async
|
|
15
|
+
* @param {LogLevel} level - The log level.
|
|
16
|
+
* @param {RequestInfo} request - The request information.
|
|
17
|
+
* @param {LogData} data - The log data.
|
|
18
|
+
* @param {StoreData} store - The store data.
|
|
18
19
|
*
|
|
19
|
-
* @returns {void}
|
|
20
|
+
* @returns {Promise<void>}
|
|
20
21
|
*/
|
|
21
|
-
function log(
|
|
22
|
+
async function log(
|
|
22
23
|
level: LogLevel,
|
|
23
24
|
request: RequestInfo,
|
|
24
25
|
data: LogData,
|
|
25
26
|
store: StoreData
|
|
26
|
-
): void {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
32
|
-
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
const logMessage = buildLogMessage(level, request, data, store)
|
|
29
|
+
try {
|
|
30
|
+
await writeToLogAsync(logMessage)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Error logging message:', error)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Builds the log message string from given parameters.
|
|
38
|
+
*
|
|
39
|
+
* @param {LogLevel} level - The log level.
|
|
40
|
+
* @param {RequestInfo} request - The request information.
|
|
41
|
+
* @param {LogData} data - The log data.
|
|
42
|
+
* @param {StoreData} store - The store data.
|
|
43
|
+
*
|
|
44
|
+
* @returns {string} - The constructed log message.
|
|
45
|
+
*/
|
|
46
|
+
function buildLogMessage(
|
|
47
|
+
level: LogLevel,
|
|
48
|
+
request: RequestInfo,
|
|
49
|
+
data: LogData,
|
|
50
|
+
store: StoreData
|
|
51
|
+
): string {
|
|
52
|
+
const nowStr = chalk.bgYellow(chalk.black(new Date().toLocaleString()))
|
|
33
53
|
const levelStr = logString(level)
|
|
34
54
|
const durationStr = durationString(store.beforeTime)
|
|
35
55
|
const methodStr = methodString(request.method)
|
|
@@ -37,21 +57,34 @@ function log(
|
|
|
37
57
|
const statusStr = statusString(data.status || 200)
|
|
38
58
|
const messageStr = data.message || ''
|
|
39
59
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
return `🦊 ${nowStr} ${levelStr} ${durationStr} ${methodStr} ${pathnameStr} ${statusStr} ${messageStr}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Writes a log message to the console asynchronously.
|
|
65
|
+
*
|
|
66
|
+
* @async
|
|
67
|
+
* @param {string} message - The message to log.
|
|
68
|
+
*
|
|
69
|
+
* @returns {Promise<void>}
|
|
70
|
+
* @throws {Error} - If the timeout is reached.
|
|
71
|
+
*/
|
|
72
|
+
function writeToLogAsync(message: string): Promise<void> {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
console.log(message)
|
|
75
|
+
resolve()
|
|
43
76
|
|
|
44
|
-
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
reject(new Error('Timed out while writing to log.'))
|
|
79
|
+
})
|
|
80
|
+
})
|
|
45
81
|
}
|
|
46
82
|
|
|
47
83
|
/**
|
|
48
|
-
* Creates a
|
|
84
|
+
* Creates a logger instance with an asynchronous log method.
|
|
49
85
|
*
|
|
50
|
-
* @returns {Logger} The
|
|
86
|
+
* @returns {Logger} - The logger instance.
|
|
51
87
|
*/
|
|
52
88
|
export const createLogger = (): Logger => ({
|
|
53
|
-
|
|
54
|
-
warning: (request, data, store) =>
|
|
55
|
-
log(LogLevel.WARNING, request, data, store),
|
|
56
|
-
error: (request, data, store) => log(LogLevel.ERROR, request, data, store)
|
|
89
|
+
log: (level, request, data, store) => log(level, request, data, store)
|
|
57
90
|
})
|
package/src/types/Logger.ts
CHANGED
|
@@ -2,27 +2,19 @@ import { RequestInfo } from './RequestInfo'
|
|
|
2
2
|
import { StoreData } from './StoreData'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The log level.
|
|
5
|
+
* The log level, including standard and custom levels.
|
|
6
6
|
*
|
|
7
|
-
* @
|
|
8
|
-
*
|
|
9
|
-
* @property {string} INFO - The info log level.
|
|
10
|
-
* @property {string} WARNING - The warning log level.
|
|
11
|
-
* @property {string} ERROR - The error log level.
|
|
7
|
+
* @type {string}
|
|
12
8
|
*/
|
|
13
|
-
|
|
14
|
-
INFO = 'INFO',
|
|
15
|
-
WARNING = 'WARNING',
|
|
16
|
-
ERROR = 'ERROR'
|
|
17
|
-
}
|
|
9
|
+
type LogLevel = 'INFO' | 'WARNING' | 'ERROR' | string
|
|
18
10
|
|
|
19
11
|
/**
|
|
20
12
|
* The log data interface.
|
|
21
13
|
*
|
|
22
14
|
* @interface LogData
|
|
23
15
|
*
|
|
24
|
-
* @property {number} status The status code.
|
|
25
|
-
* @property {string} message The message.
|
|
16
|
+
* @property {number} status - The status code.
|
|
17
|
+
* @property {string} message - The message.
|
|
26
18
|
*/
|
|
27
19
|
interface LogData {
|
|
28
20
|
status?: number
|
|
@@ -34,14 +26,15 @@ interface LogData {
|
|
|
34
26
|
*
|
|
35
27
|
* @interface Logger
|
|
36
28
|
*
|
|
37
|
-
* @property {Function}
|
|
38
|
-
* @property {Function} warning Logs a warning message.
|
|
39
|
-
* @property {Function} error Logs an error message.
|
|
29
|
+
* @property {Function} log - Logs a message with a given log level.
|
|
40
30
|
*/
|
|
41
31
|
interface Logger {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
log(
|
|
33
|
+
level: LogLevel,
|
|
34
|
+
request: RequestInfo,
|
|
35
|
+
data: LogData,
|
|
36
|
+
store: StoreData
|
|
37
|
+
): void
|
|
45
38
|
}
|
|
46
39
|
|
|
47
40
|
export { LogLevel, LogData, Logger }
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { ColorMap } from '~/types/ColorMap'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The color map for the log levels.
|
|
6
|
+
*
|
|
7
|
+
* @type {ColorMap}
|
|
8
|
+
* @property {chalk.Chalk} INFO The color for the INFO log level.
|
|
9
|
+
* @property {chalk.Chalk} WARNING The color for the WARNING log level.
|
|
10
|
+
* @property {chalk.Chalk} ERROR The color for the ERROR log level.
|
|
11
|
+
*/
|
|
12
|
+
const LogLevelColorMap: ColorMap = {
|
|
13
|
+
INFO: chalk.bgGreen.black,
|
|
14
|
+
WARNING: chalk.bgYellow.black,
|
|
15
|
+
ERROR: chalk.bgRed.black
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The color map for the HTTP methods.
|
|
20
|
+
*
|
|
21
|
+
* @type {ColorMap}
|
|
22
|
+
* @property {chalk.Chalk} GET The color for the GET HTTP method.
|
|
23
|
+
* @property {chalk.Chalk} POST The color for the POST HTTP method.
|
|
24
|
+
* @property {chalk.Chalk} PUT The color for the PUT HTTP method.
|
|
25
|
+
* @property {chalk.Chalk} DELETE The color for the DELETE HTTP method.
|
|
26
|
+
* @property {chalk.Chalk} PATCH The color for the PATCH HTTP method.
|
|
27
|
+
* @property {chalk.Chalk} OPTIONS The color for the OPTIONS HTTP method.
|
|
28
|
+
* @property {chalk.Chalk} HEAD The color for the HEAD HTTP method.
|
|
29
|
+
*/
|
|
30
|
+
const HttpMethodColorMap: ColorMap = {
|
|
31
|
+
GET: chalk.white,
|
|
32
|
+
POST: chalk.yellow,
|
|
33
|
+
PUT: chalk.blue,
|
|
34
|
+
DELETE: chalk.red,
|
|
35
|
+
PATCH: chalk.green,
|
|
36
|
+
OPTIONS: chalk.cyan,
|
|
37
|
+
HEAD: chalk.magenta
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { LogLevelColorMap, HttpMethodColorMap }
|
package/src/utils/duration.ts
CHANGED
|
@@ -9,30 +9,21 @@ import chalk from 'chalk'
|
|
|
9
9
|
*/
|
|
10
10
|
function durationString(beforeTime: bigint): string {
|
|
11
11
|
const now = process.hrtime.bigint()
|
|
12
|
-
const
|
|
13
|
-
const nanoseconds = Number(timeDifference)
|
|
14
|
-
|
|
15
|
-
const durationInMicroseconds = (nanoseconds / 1e3).toFixed(0)
|
|
16
|
-
const durationInMilliseconds = (nanoseconds / 1e6).toFixed(0)
|
|
12
|
+
const nanoseconds = Number(now - beforeTime)
|
|
17
13
|
|
|
18
14
|
let timeMessage: string = ''
|
|
19
15
|
|
|
20
16
|
if (nanoseconds >= 1e9) {
|
|
21
|
-
|
|
22
|
-
timeMessage = `${seconds}s`
|
|
17
|
+
timeMessage = `${(nanoseconds / 1e9).toFixed(2)}s`
|
|
23
18
|
} else if (nanoseconds >= 1e6) {
|
|
24
|
-
timeMessage = `${
|
|
19
|
+
timeMessage = `${(nanoseconds / 1e6).toFixed(0)}ms`
|
|
25
20
|
} else if (nanoseconds >= 1e3) {
|
|
26
|
-
timeMessage = `${
|
|
21
|
+
timeMessage = `${(nanoseconds / 1e3).toFixed(0)}µs`
|
|
27
22
|
} else {
|
|
28
23
|
timeMessage = `${nanoseconds}ns`
|
|
29
24
|
}
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
timeMessage = chalk.gray(timeMessage).padStart(8).padEnd(16)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return timeMessage
|
|
26
|
+
return timeMessage ? chalk.gray(timeMessage).padStart(8).padEnd(16) : ''
|
|
36
27
|
}
|
|
37
28
|
|
|
38
29
|
export default durationString
|
package/src/utils/log.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ColorMap } from '~/types/ColorMap'
|
|
1
|
+
import { LogLevelColorMap } from './colorMapping'
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Converts a log level to a colored string representation.
|
|
@@ -9,16 +8,10 @@ import { ColorMap } from '~/types/ColorMap'
|
|
|
9
8
|
* @returns {string} A colored string representing the log level.
|
|
10
9
|
*/
|
|
11
10
|
function logString(log: string): string {
|
|
12
|
-
const
|
|
13
|
-
INFO: chalk.bgGreen,
|
|
14
|
-
WARNING: chalk.bgYellow,
|
|
15
|
-
ERROR: chalk.bgRed
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const colorFunction = colorMap[log]
|
|
11
|
+
const colorFunction = LogLevelColorMap[log]
|
|
19
12
|
|
|
20
13
|
if (colorFunction) {
|
|
21
|
-
return colorFunction(
|
|
14
|
+
return colorFunction(log.padEnd(7))
|
|
22
15
|
}
|
|
23
16
|
|
|
24
17
|
return log
|
package/src/utils/method.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ColorMap } from '~/types/ColorMap'
|
|
1
|
+
import { HttpMethodColorMap } from './colorMapping'
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Converts an HTTP request method to a colored string representation.
|
|
@@ -9,17 +8,7 @@ import { ColorMap } from '~/types/ColorMap'
|
|
|
9
8
|
* @returns {string} A colored string representing the method.
|
|
10
9
|
*/
|
|
11
10
|
function methodString(method: string): string {
|
|
12
|
-
const
|
|
13
|
-
GET: chalk.white,
|
|
14
|
-
POST: chalk.yellow,
|
|
15
|
-
PUT: chalk.blue,
|
|
16
|
-
DELETE: chalk.red,
|
|
17
|
-
PATCH: chalk.green,
|
|
18
|
-
OPTIONS: chalk.cyan,
|
|
19
|
-
HEAD: chalk.magenta
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const colorFunction = colorMap[method]
|
|
11
|
+
const colorFunction = HttpMethodColorMap[method]
|
|
23
12
|
|
|
24
13
|
if (colorFunction) {
|
|
25
14
|
return colorFunction(method.padEnd(7))
|
package/src/utils/path.ts
CHANGED
|
@@ -3,13 +3,17 @@ import { RequestInfo } from '~/types/RequestInfo'
|
|
|
3
3
|
/**
|
|
4
4
|
* Returns the path string.
|
|
5
5
|
*
|
|
6
|
-
* @param {RequestInfo}
|
|
6
|
+
* @param {RequestInfo} requestInfo The request info.
|
|
7
7
|
*
|
|
8
|
-
* @returns {string} The path string.
|
|
8
|
+
* @returns {string | undefined} The path string.
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
|
|
11
|
+
function pathString(requestInfo: RequestInfo): string | undefined {
|
|
12
|
+
try {
|
|
13
|
+
return new URL(requestInfo.url).pathname
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return undefined
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export default pathString
|
package/src/utils/start.ts
CHANGED
|
@@ -9,8 +9,9 @@ import { Server } from '~/types/Server'
|
|
|
9
9
|
* @returns {string} The box text.
|
|
10
10
|
*/
|
|
11
11
|
function createBoxText(text: string, width: number): string {
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const paddingLength = Math.max(0, (width - text.length + 1) / 2)
|
|
13
|
+
const padding = ' '.repeat(paddingLength)
|
|
14
|
+
return `${padding}${text}${padding}`.slice(0, width)
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -29,15 +30,13 @@ function startString(config: Server): void {
|
|
|
29
30
|
const boxWidth = Math.max(title.length, messageWidth) + 4
|
|
30
31
|
const border = '─'.repeat(boxWidth)
|
|
31
32
|
|
|
32
|
-
process.stdout.write('\x1Bc')
|
|
33
|
-
|
|
34
33
|
console.log(`
|
|
35
34
|
┌${border}┐
|
|
36
|
-
│${createBoxText('', boxWidth)}
|
|
35
|
+
│${createBoxText('', boxWidth)}│
|
|
37
36
|
│${createBoxText(title, boxWidth)}│
|
|
38
|
-
│${createBoxText('', boxWidth)}
|
|
37
|
+
│${createBoxText('', boxWidth)}│
|
|
39
38
|
│${createBoxText(message, boxWidth)}│
|
|
40
|
-
│${createBoxText('', boxWidth)}
|
|
39
|
+
│${createBoxText('', boxWidth)}│
|
|
41
40
|
└${border}┘
|
|
42
41
|
`)
|
|
43
42
|
}
|
package/src/utils/status.ts
CHANGED
|
@@ -8,20 +8,18 @@ import chalk from 'chalk'
|
|
|
8
8
|
* @returns {string} The status string.
|
|
9
9
|
*/
|
|
10
10
|
function statusString(status: number): string {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
switch (true) {
|
|
12
|
+
case status >= 500:
|
|
13
|
+
return chalk.red(status.toString())
|
|
14
|
+
case status >= 400:
|
|
15
|
+
return chalk.yellow(status.toString())
|
|
16
|
+
case status >= 300:
|
|
17
|
+
return chalk.cyan(status.toString())
|
|
18
|
+
case status >= 200:
|
|
19
|
+
return chalk.green(status.toString())
|
|
20
|
+
default:
|
|
21
|
+
return status.toString()
|
|
13
22
|
}
|
|
14
|
-
if (status >= 400) {
|
|
15
|
-
return chalk.yellow(status.toString())
|
|
16
|
-
}
|
|
17
|
-
if (status >= 300) {
|
|
18
|
-
return chalk.cyan(status.toString())
|
|
19
|
-
}
|
|
20
|
-
if (status >= 200) {
|
|
21
|
-
return chalk.green(status.toString())
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return status.toString()
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
export default statusString
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { ColorMap } from '~/types/ColorMap'
|
|
3
|
+
|
|
4
|
+
describe('Color Mapping Interface', () => {
|
|
5
|
+
it('Defines an object with string keys mapping to functions', () => {
|
|
6
|
+
const colorMap: ColorMap = {
|
|
7
|
+
red: (str: string) => `Red: ${str}`,
|
|
8
|
+
green: (str: string) => `Green: ${str}`,
|
|
9
|
+
blue: (str: string) => `Blue: ${str}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
expect(colorMap).toEqual(
|
|
13
|
+
expect.objectContaining({
|
|
14
|
+
red: expect.any(Function),
|
|
15
|
+
green: expect.any(Function),
|
|
16
|
+
blue: expect.any(Function)
|
|
17
|
+
})
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
Object.keys(colorMap).forEach(key => {
|
|
21
|
+
expect(typeof colorMap[key]).toBe('function')
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, jest } from 'bun:test'
|
|
2
|
+
import { LogData } from '~/types/Logger'
|
|
3
|
+
import { RequestInfo } from '~/types/RequestInfo'
|
|
4
|
+
import { StoreData } from '~/types/StoreData'
|
|
5
|
+
|
|
6
|
+
interface Logger {
|
|
7
|
+
info(request: RequestInfo, data: LogData, store: StoreData): void
|
|
8
|
+
warning(request: RequestInfo, data: LogData, store: StoreData): void
|
|
9
|
+
error(request: RequestInfo, data: LogData, store: StoreData): void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('Logger interface', () => {
|
|
13
|
+
let logger: Logger
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
logger = {
|
|
17
|
+
info: jest.fn(),
|
|
18
|
+
warning: jest.fn(),
|
|
19
|
+
error: jest.fn()
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('Defines the Logger interface correctly', () => {
|
|
24
|
+
expect(logger).toEqual(
|
|
25
|
+
expect.objectContaining({
|
|
26
|
+
info: expect.any(Function),
|
|
27
|
+
warning: expect.any(Function),
|
|
28
|
+
error: expect.any(Function)
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('Calls the info log function with the correct arguments', () => {
|
|
34
|
+
const request: RequestInfo = {
|
|
35
|
+
url: '/info',
|
|
36
|
+
method: 'GET',
|
|
37
|
+
headers: {
|
|
38
|
+
get: function () {
|
|
39
|
+
throw new Error('Function not implemented.')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const data: LogData = { status: 200, message: 'Info log message' }
|
|
44
|
+
const store: StoreData = {
|
|
45
|
+
beforeTime: 0n
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
logger.info(request, data, store)
|
|
49
|
+
|
|
50
|
+
expect(logger.info).toHaveBeenCalledWith(request, data, store)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('Calls the warning log function with the correct arguments', () => {
|
|
54
|
+
const request: RequestInfo = {
|
|
55
|
+
url: '/warning',
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
get: function () {
|
|
59
|
+
throw new Error('Function not implemented.')
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const data: LogData = { status: 404, message: 'Warning log message' }
|
|
64
|
+
const store: StoreData = {
|
|
65
|
+
beforeTime: 0n
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
logger.warning(request, data, store)
|
|
69
|
+
|
|
70
|
+
expect(logger.warning).toHaveBeenCalledWith(request, data, store)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('Calls the error log function with the correct arguments', () => {
|
|
74
|
+
const request: RequestInfo = {
|
|
75
|
+
url: '/error',
|
|
76
|
+
method: 'DELETE',
|
|
77
|
+
headers: {
|
|
78
|
+
get: function () {
|
|
79
|
+
throw new Error('Function not implemented.')
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const data: LogData = { status: 500, message: 'Error log message' }
|
|
84
|
+
const store: StoreData = {
|
|
85
|
+
beforeTime: 0n
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
logger.error(request, data, store)
|
|
89
|
+
|
|
90
|
+
expect(logger.error).toHaveBeenCalledWith(request, data, store)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { RequestInfo } from '~/types/RequestInfo'
|
|
3
|
+
|
|
4
|
+
describe('Request Infomation interface', () => {
|
|
5
|
+
it('Defines the RequestInfo interface correctly', () => {
|
|
6
|
+
const headers = { get: () => 'value' }
|
|
7
|
+
const method = 'GET'
|
|
8
|
+
const url = 'https://example.com/api'
|
|
9
|
+
|
|
10
|
+
const request: RequestInfo = { headers, method, url }
|
|
11
|
+
|
|
12
|
+
expect(request).toEqual(
|
|
13
|
+
expect.objectContaining({
|
|
14
|
+
headers: expect.objectContaining({
|
|
15
|
+
get: expect.any(Function)
|
|
16
|
+
}),
|
|
17
|
+
method: expect.any(String),
|
|
18
|
+
url: expect.any(String)
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { Server } from '~/types/Server'
|
|
3
|
+
|
|
4
|
+
describe('Server interface', () => {
|
|
5
|
+
it('Defines the Server interface correctly', () => {
|
|
6
|
+
const server: Server = {
|
|
7
|
+
hostname: 'example.com',
|
|
8
|
+
port: 8080,
|
|
9
|
+
protocol: 'https'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
expect(server).toEqual(
|
|
13
|
+
expect.objectContaining({
|
|
14
|
+
hostname: expect.any(String),
|
|
15
|
+
port: expect.any(Number),
|
|
16
|
+
protocol: expect.any(String)
|
|
17
|
+
})
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('Allows optional properties in the Server interface', () => {
|
|
22
|
+
const serverWithoutOptionalProps: Server = {}
|
|
23
|
+
|
|
24
|
+
expect(serverWithoutOptionalProps).toEqual(expect.objectContaining({}))
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { StoreData } from '~/types/StoreData'
|
|
3
|
+
|
|
4
|
+
describe('Store Data interface', () => {
|
|
5
|
+
it('Defines the StoreData interface correctly', () => {
|
|
6
|
+
const beforeTime: bigint = BigInt(1633393533526) // Example bigint value
|
|
7
|
+
|
|
8
|
+
const storeData: StoreData = { beforeTime }
|
|
9
|
+
|
|
10
|
+
expect(storeData).toEqual(
|
|
11
|
+
expect.objectContaining({
|
|
12
|
+
beforeTime: expect.any(BigInt)
|
|
13
|
+
})
|
|
14
|
+
)
|
|
15
|
+
})
|
|
16
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import durationString from '~/utils/duration'
|
|
3
|
+
|
|
4
|
+
describe('Duration String', () => {
|
|
5
|
+
it('Generates a string representing the duration in appropriate units', () => {
|
|
6
|
+
const testCases = [
|
|
7
|
+
[1e9, 's'],
|
|
8
|
+
[1e6, 'ms'],
|
|
9
|
+
[1e3, 'µs'],
|
|
10
|
+
[1, 'ns']
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
for (const [nanoseconds, unit] of testCases) {
|
|
14
|
+
const beforeTime = process.hrtime.bigint() - BigInt(String(nanoseconds))
|
|
15
|
+
|
|
16
|
+
const result = durationString(beforeTime)
|
|
17
|
+
expect(result).toMatch(String(unit))
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import logString from '~/utils/log'
|
|
4
|
+
|
|
5
|
+
describe('Log String', () => {
|
|
6
|
+
it('Produces a green background string for INFO log level', () => {
|
|
7
|
+
const result = logString('INFO')
|
|
8
|
+
expect(result).toBe(chalk.bgGreen.black('INFO '))
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('Produces a yellow background string for WARNING log leve', () => {
|
|
12
|
+
const result = logString('WARNING')
|
|
13
|
+
expect(result).toBe(chalk.bgYellow.black('WARNING'))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('Produces a red background string for ERROR log level', () => {
|
|
17
|
+
const result = logString('ERROR')
|
|
18
|
+
expect(result).toBe(chalk.bgRed.black('ERROR '))
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('Returns the unmodified input string for unrecognized log levels', () => {
|
|
22
|
+
const result = logString('DEBUG') // Assuming 'DEBUG' is not in the colorMap
|
|
23
|
+
expect(result).toBe('DEBUG') // No coloring, returns the original string
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import methodString from '~/utils/method'
|
|
4
|
+
|
|
5
|
+
describe('Method String', () => {
|
|
6
|
+
it('Displays a colored string for the GET method', () => {
|
|
7
|
+
const result = methodString('GET')
|
|
8
|
+
expect(result).toBe(chalk.white('GET '))
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('Displays a colored string for the POST method', () => {
|
|
12
|
+
const result = methodString('POST')
|
|
13
|
+
expect(result).toBe(chalk.yellow('POST '))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('Outputs the original method string if it is not recognized', () => {
|
|
17
|
+
const result = methodString('INVALID_METHOD')
|
|
18
|
+
expect(result).toBe('INVALID_METHOD') // No coloring, returns the original string
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { RequestInfo } from '~/types/RequestInfo'
|
|
3
|
+
import pathString from '~/utils/path'
|
|
4
|
+
|
|
5
|
+
describe('Path String', () => {
|
|
6
|
+
it('Extracts the pathname from a valid URL', () => {
|
|
7
|
+
const testPath: RequestInfo = {
|
|
8
|
+
url: 'https://www.example.com/path/to/resource',
|
|
9
|
+
headers: new Map(),
|
|
10
|
+
method: 'GET'
|
|
11
|
+
}
|
|
12
|
+
const result = pathString(testPath)
|
|
13
|
+
expect(result).toBe('/path/to/resource')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('Handles malformed URL gracefully', () => {
|
|
17
|
+
const testPath: RequestInfo = {
|
|
18
|
+
url: 'invalid url',
|
|
19
|
+
headers: new Map(),
|
|
20
|
+
method: 'GET'
|
|
21
|
+
}
|
|
22
|
+
const result = pathString(testPath)
|
|
23
|
+
expect(result).toBeUndefined()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('Returns undefined if the URL is missing', () => {
|
|
27
|
+
const testPath: RequestInfo = {
|
|
28
|
+
url: '',
|
|
29
|
+
headers: new Map(),
|
|
30
|
+
method: 'GET'
|
|
31
|
+
}
|
|
32
|
+
const result = pathString(testPath)
|
|
33
|
+
expect(result).toBeUndefined()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, jest } from 'bun:test'
|
|
2
|
+
import { Server } from '~/types/Server'
|
|
3
|
+
import startString from '~/utils/start'
|
|
4
|
+
|
|
5
|
+
describe('Start String', () => {
|
|
6
|
+
let originalConsoleLog: any
|
|
7
|
+
let mockConsoleLog: jest.Mock
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
originalConsoleLog = console.log
|
|
11
|
+
mockConsoleLog = jest.fn()
|
|
12
|
+
console.log = mockConsoleLog
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
console.log = originalConsoleLog
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('Correctly logs the expected message upon server start', () => {
|
|
20
|
+
const config: Server = {
|
|
21
|
+
hostname: 'localhost',
|
|
22
|
+
port: 3000,
|
|
23
|
+
protocol: 'http'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
startString(config)
|
|
27
|
+
|
|
28
|
+
const expectedMessage = `🦊 Elysia is running at http://localhost:3000`
|
|
29
|
+
|
|
30
|
+
// Extract the arguments passed to console.log during the function call
|
|
31
|
+
const [[logMessage]] = mockConsoleLog.mock.calls
|
|
32
|
+
|
|
33
|
+
expect(logMessage).toMatch(expectedMessage)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { describe, expect, it } from 'bun:test'
|
|
3
|
+
import statusString from '~/utils/status'
|
|
4
|
+
|
|
5
|
+
describe('Status String', () => {
|
|
6
|
+
it('Presents the status string in green for a 200 status code', () => {
|
|
7
|
+
const result = statusString(200)
|
|
8
|
+
expect(result).toBe(chalk.green('200'))
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('Presents the status string in green for a 301 status code', () => {
|
|
12
|
+
const result = statusString(301)
|
|
13
|
+
expect(result).toBe(chalk.cyan('301'))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('Presents the status string in green for a 404 status code', () => {
|
|
17
|
+
const result = statusString(404)
|
|
18
|
+
expect(result).toBe(chalk.yellow('404'))
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('Presents the status string in green for a 500 status code', () => {
|
|
22
|
+
const result = statusString(500)
|
|
23
|
+
expect(result).toBe(chalk.red('500'))
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('Presents the status string in green for a 100 status code', () => {
|
|
27
|
+
const result = statusString(100)
|
|
28
|
+
expect(result).toBe('100')
|
|
29
|
+
})
|
|
30
|
+
})
|