logixlysia 2.0.2 → 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 CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## [2.0.2](https://github.com/PunGrumpy/logixlysia/compare/v2.0.1...v2.0.2) (2024-01-07)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logixlysia",
3
- "version": "2.0.2",
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,14 +15,15 @@
15
15
  },
16
16
  "license": "MIT",
17
17
  "scripts": {
18
- "pretest": "bun run lint && bun run prettier && bun run lint:fix",
18
+ "pretest": "bun run lint && bun run lint:fix && bun run prettier",
19
+ "lint": "eslint . --ext .ts",
20
+ "lint:fix": "eslint . --ext .ts --fix",
21
+ "prepublish": "bun run pretest && bun run test",
19
22
  "test": "bun test --timeout 5000 --coverage --update-snapshots",
20
- "prepublish": "bun run test",
23
+ "test:ci": "bun test --timeout 5000 --coverage --update-snapshots",
21
24
  "publish": "bun run prepublish && npm publish",
22
25
  "dev": "bun run --watch example/basic.ts",
23
26
  "prepare": "husky install",
24
- "lint": "eslint . --ext .ts",
25
- "lint:fix": "eslint . --ext .ts --fix",
26
27
  "lint:staged": "lint-staged",
27
28
  "prettier": "prettier --write ."
28
29
  },
@@ -69,14 +70,14 @@
69
70
  "middleware"
70
71
  ],
71
72
  "dependencies": {
72
- "@typescript-eslint/eslint-plugin": "^6.17.0",
73
- "@typescript-eslint/parser": "^6.17.0",
73
+ "@typescript-eslint/eslint-plugin": "^6.18.1",
74
+ "@typescript-eslint/parser": "^6.18.1",
74
75
  "chalk": "^5.3.0",
75
- "elysia": "^0.8.8",
76
+ "elysia": "^0.8.9",
76
77
  "eslint": "^8.56.0"
77
78
  },
78
79
  "devDependencies": {
79
- "bun-types": "^1.0.21",
80
+ "bun-types": "^1.0.22",
80
81
  "husky": "^8.0.3",
81
82
  "lint-staged": "^15.2.0",
82
83
  "prettier": "^3.1.1"
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@ import { Server } from 'bun'
6
6
  /**
7
7
  * Creates a logger.
8
8
  *
9
- * @export {Function} The logger.
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() } as {
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.info(request, {}, store as { beforeTime: bigint })
34
+ log.log('INFO', request, {}, store as { beforeTime: bigint })
42
35
  })
43
36
  .onError(({ request, error, store }) => {
44
- log.error(request, error, store as { beforeTime: bigint })
37
+ log.log('ERROR', request, error, store as { beforeTime: bigint })
45
38
  })
46
39
 
47
40
  return elysia
package/src/logger.ts CHANGED
@@ -9,22 +9,46 @@ import { LogData, LogLevel, Logger } from './types/Logger'
9
9
  import { StoreData } from './types/StoreData'
10
10
 
11
11
  /**
12
- * Logs a message to the console.
12
+ * Asynchronously logs a message constructed from various log components.
13
13
  *
14
- * @param {LogLevel} level The log level.
15
- * @param {RequestInfo} request The request information.
16
- * @param {LogData} data The log data.
17
- * @param {StoreData} store The store data.
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} The log message.
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 logStr: string[] = []
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 {
28
52
  const nowStr = chalk.bgYellow(chalk.black(new Date().toLocaleString()))
29
53
  const levelStr = logString(level)
30
54
  const durationStr = durationString(store.beforeTime)
@@ -33,21 +57,34 @@ function log(
33
57
  const statusStr = statusString(data.status || 200)
34
58
  const messageStr = data.message || ''
35
59
 
36
- logStr.push(
37
- `🦊 ${nowStr} ${levelStr} ${durationStr} ${methodStr} ${pathnameStr} ${statusStr} ${messageStr}`
38
- )
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()
39
76
 
40
- console.log(logStr.join(' '))
77
+ setTimeout(() => {
78
+ reject(new Error('Timed out while writing to log.'))
79
+ })
80
+ })
41
81
  }
42
82
 
43
83
  /**
44
- * Creates a formatted logger.
84
+ * Creates a logger instance with an asynchronous log method.
45
85
  *
46
- * @returns {Logger} The formatted logger.
86
+ * @returns {Logger} - The logger instance.
47
87
  */
48
88
  export const createLogger = (): Logger => ({
49
- info: (request, data, store) => log(LogLevel.INFO, request, data, store),
50
- warning: (request, data, store) =>
51
- log(LogLevel.WARNING, request, data, store),
52
- error: (request, data, store) => log(LogLevel.ERROR, request, data, store)
89
+ log: (level, request, data, store) => log(level, request, data, store)
53
90
  })
@@ -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
- * @enum {string}
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
- enum LogLevel {
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} info Logs an info message.
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
- info(request: RequestInfo, data: LogData, store: StoreData): void
43
- warning(request: RequestInfo, data: LogData, store: StoreData): void
44
- error(request: RequestInfo, data: LogData, store: StoreData): void
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 }
@@ -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 timeDifference = now - beforeTime
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
- const seconds = (nanoseconds / 1e9).toFixed(2)
22
- timeMessage = `${seconds}s`
17
+ timeMessage = `${(nanoseconds / 1e9).toFixed(2)}s`
23
18
  } else if (nanoseconds >= 1e6) {
24
- timeMessage = `${durationInMilliseconds}ms`
19
+ timeMessage = `${(nanoseconds / 1e6).toFixed(0)}ms`
25
20
  } else if (nanoseconds >= 1e3) {
26
- timeMessage = `${durationInMicroseconds}µs`
21
+ timeMessage = `${(nanoseconds / 1e3).toFixed(0)}µs`
27
22
  } else {
28
23
  timeMessage = `${nanoseconds}ns`
29
24
  }
30
25
 
31
- if (timeMessage) {
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 chalk from 'chalk'
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 colorMap: ColorMap = {
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(chalk.black(log.padEnd(7)))
14
+ return colorFunction(log.padEnd(7))
22
15
  }
23
16
 
24
17
  return log
@@ -1,5 +1,4 @@
1
- import chalk from 'chalk'
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 colorMap: ColorMap = {
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} path The request information.
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
- function pathString(path: RequestInfo): string {
11
- const url = new URL(path?.url).pathname
12
- return url
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
@@ -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 padding = ' '.repeat((width - text.length) / 2)
13
- return `${padding}${text}${padding}`
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
  /**
@@ -31,11 +32,11 @@ function startString(config: Server): void {
31
32
 
32
33
  console.log(`
33
34
  ┌${border}┐
34
- │${createBoxText('', boxWidth)}
35
+ │${createBoxText('', boxWidth)}│
35
36
  │${createBoxText(title, boxWidth)}│
36
- │${createBoxText('', boxWidth)}
37
+ │${createBoxText('', boxWidth)}│
37
38
  │${createBoxText(message, boxWidth)}│
38
- │${createBoxText('', boxWidth)}
39
+ │${createBoxText('', boxWidth)}│
39
40
  └${border}┘
40
41
  `)
41
42
  }
@@ -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
- if (status >= 500) {
12
- return chalk.red(status.toString())
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
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'bun:test'
2
2
  import durationString from '~/utils/duration'
3
3
 
4
4
  describe('Duration String', () => {
5
- it('Returns a duration string with different units', () => {
5
+ it('Generates a string representing the duration in appropriate units', () => {
6
6
  const testCases = [
7
7
  [1e9, 's'],
8
8
  [1e6, 'ms'],
@@ -3,22 +3,22 @@ import chalk from 'chalk'
3
3
  import logString from '~/utils/log'
4
4
 
5
5
  describe('Log String', () => {
6
- it('Returns a colored string for INFO log level', () => {
6
+ it('Produces a green background string for INFO log level', () => {
7
7
  const result = logString('INFO')
8
8
  expect(result).toBe(chalk.bgGreen.black('INFO '))
9
9
  })
10
10
 
11
- it('Returns a colored string for WARNING log level', () => {
11
+ it('Produces a yellow background string for WARNING log leve', () => {
12
12
  const result = logString('WARNING')
13
13
  expect(result).toBe(chalk.bgYellow.black('WARNING'))
14
14
  })
15
15
 
16
- it('Returns a colored string for ERROR log level', () => {
16
+ it('Produces a red background string for ERROR log level', () => {
17
17
  const result = logString('ERROR')
18
18
  expect(result).toBe(chalk.bgRed.black('ERROR '))
19
19
  })
20
20
 
21
- it('Returns the input string if log level is not recognized', () => {
21
+ it('Returns the unmodified input string for unrecognized log levels', () => {
22
22
  const result = logString('DEBUG') // Assuming 'DEBUG' is not in the colorMap
23
23
  expect(result).toBe('DEBUG') // No coloring, returns the original string
24
24
  })
@@ -3,17 +3,17 @@ import chalk from 'chalk'
3
3
  import methodString from '~/utils/method'
4
4
 
5
5
  describe('Method String', () => {
6
- it('Returns a colored string for GET method', () => {
6
+ it('Displays a colored string for the GET method', () => {
7
7
  const result = methodString('GET')
8
8
  expect(result).toBe(chalk.white('GET '))
9
9
  })
10
10
 
11
- it('Returns a colored string for POST method', () => {
11
+ it('Displays a colored string for the POST method', () => {
12
12
  const result = methodString('POST')
13
13
  expect(result).toBe(chalk.yellow('POST '))
14
14
  })
15
15
 
16
- it('Returns the input string if method is not recognized', () => {
16
+ it('Outputs the original method string if it is not recognized', () => {
17
17
  const result = methodString('INVALID_METHOD')
18
18
  expect(result).toBe('INVALID_METHOD') // No coloring, returns the original string
19
19
  })
@@ -1,10 +1,35 @@
1
1
  import { describe, expect, it } from 'bun:test'
2
+ import { RequestInfo } from '~/types/RequestInfo'
2
3
  import pathString from '~/utils/path'
3
4
 
4
5
  describe('Path String', () => {
5
- it('Returns the path string from a URL', () => {
6
- const testPath: any = { url: 'https://www.example.com/path/to/resource' }
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
+ }
7
12
  const result = pathString(testPath)
8
- expect(result).toMatch('/path/to/resource')
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()
9
34
  })
10
35
  })
@@ -16,7 +16,7 @@ describe('Start String', () => {
16
16
  console.log = originalConsoleLog
17
17
  })
18
18
 
19
- it('Logs the expected server start message', () => {
19
+ it('Correctly logs the expected message upon server start', () => {
20
20
  const config: Server = {
21
21
  hostname: 'localhost',
22
22
  port: 3000,
@@ -3,27 +3,27 @@ import { describe, expect, it } from 'bun:test'
3
3
  import statusString from '~/utils/status'
4
4
 
5
5
  describe('Status String', () => {
6
- it('Returns the status string for 200 status code', () => {
6
+ it('Presents the status string in green for a 200 status code', () => {
7
7
  const result = statusString(200)
8
8
  expect(result).toBe(chalk.green('200'))
9
9
  })
10
10
 
11
- it('Returns the status string for 301 status code', () => {
11
+ it('Presents the status string in green for a 301 status code', () => {
12
12
  const result = statusString(301)
13
13
  expect(result).toBe(chalk.cyan('301'))
14
14
  })
15
15
 
16
- it('Returns the status string for 404 status code', () => {
16
+ it('Presents the status string in green for a 404 status code', () => {
17
17
  const result = statusString(404)
18
18
  expect(result).toBe(chalk.yellow('404'))
19
19
  })
20
20
 
21
- it('Returns the status string for 500 status code', () => {
21
+ it('Presents the status string in green for a 500 status code', () => {
22
22
  const result = statusString(500)
23
23
  expect(result).toBe(chalk.red('500'))
24
24
  })
25
25
 
26
- it('Returns the status string for 100 status code', () => {
26
+ it('Presents the status string in green for a 100 status code', () => {
27
27
  const result = statusString(100)
28
28
  expect(result).toBe('100')
29
29
  })