@pori15/logixlysia 6.0.1 → 6.0.3

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.
@@ -1,26 +1,26 @@
1
- import elysiaPkg from 'elysia/package.json'
2
-
3
- const centerText = (text: string, width: number): string => {
4
- if (text.length >= width) {
5
- return text.slice(0, width)
6
- }
7
-
8
- const left = Math.floor((width - text.length) / 2)
9
- const right = width - text.length - left
10
- return `${' '.repeat(left)}${text}${' '.repeat(right)}`
11
- }
12
-
13
- export const renderBanner = (message: string): string => {
14
- const versionLine = `Elysia v${elysiaPkg.version}`
15
- const contentWidth = Math.max(message.length, versionLine.length)
16
- const innerWidth = contentWidth + 4 // 2 spaces padding on both sides
17
-
18
- const top = `┌${'─'.repeat(innerWidth)}┐`
19
- const bot = `└${'─'.repeat(innerWidth)}┘`
20
- const empty = `│${' '.repeat(innerWidth)}│`
21
-
22
- const versionRow = `│${centerText(versionLine, innerWidth)}│`
23
- const messageRow = `│ ${message}${' '.repeat(Math.max(0, innerWidth - message.length - 4))} │`
24
-
25
- return [top, empty, versionRow, empty, messageRow, empty, bot].join('\n')
26
- }
1
+ import elysiaPkg from 'elysia/package.json'
2
+
3
+ const centerText = (text: string, width: number): string => {
4
+ if (text.length >= width) {
5
+ return text.slice(0, width)
6
+ }
7
+
8
+ const left = Math.floor((width - text.length) / 2)
9
+ const right = width - text.length - left
10
+ return `${' '.repeat(left)}${text}${' '.repeat(right)}`
11
+ }
12
+
13
+ export const renderBanner = (message: string): string => {
14
+ const versionLine = `Elysia v${elysiaPkg.version}`
15
+ const contentWidth = Math.max(message.length, versionLine.length)
16
+ const innerWidth = contentWidth + 4 // 2 spaces padding on both sides
17
+
18
+ const top = `┌${'─'.repeat(innerWidth)}┐`
19
+ const bot = `└${'─'.repeat(innerWidth)}┘`
20
+ const empty = `│${' '.repeat(innerWidth)}│`
21
+
22
+ const versionRow = `│${centerText(versionLine, innerWidth)}│`
23
+ const messageRow = `│ ${message}${' '.repeat(Math.max(0, innerWidth - message.length - 4))} │`
24
+
25
+ return [top, empty, versionRow, empty, messageRow, empty, bot].join('\n')
26
+ }
@@ -1,28 +1,28 @@
1
- import type { Options } from '../interfaces'
2
- import { renderBanner } from './banner'
3
-
4
- export const startServer = (
5
- server: { port?: number; hostname?: string; protocol?: string | null },
6
- options: Options
7
- ): void => {
8
- const showStartupMessage = options.config?.showStartupMessage ?? true
9
- if (!showStartupMessage) {
10
- return
11
- }
12
-
13
- const { port, hostname, protocol } = server
14
- if (port === undefined || !hostname || !protocol) {
15
- return
16
- }
17
-
18
- const url = `${protocol}://${hostname}:${port}`
19
- const message = `🦊 Elysia is running at ${url}`
20
-
21
- const format = options.config?.startupMessageFormat ?? 'banner'
22
- if (format === 'simple') {
23
- console.log(message)
24
- return
25
- }
26
-
27
- console.log(renderBanner(message))
28
- }
1
+ import type { Options } from '../interfaces'
2
+ import { renderBanner } from './banner'
3
+
4
+ export const startServer = (
5
+ server: { port?: number; hostname?: string; protocol?: string | null },
6
+ options: Options
7
+ ): void => {
8
+ const showStartupMessage = options.config?.showStartupMessage ?? true
9
+ if (!showStartupMessage) {
10
+ return
11
+ }
12
+
13
+ const { port, hostname, protocol } = server
14
+ if (port === undefined || !hostname || !protocol) {
15
+ return
16
+ }
17
+
18
+ const url = `${protocol}://${hostname}:${port}`
19
+ const message = `🦊 Elysia is running at ${url}`
20
+
21
+ const format = options.config?.startupMessageFormat ?? 'banner'
22
+ if (format === 'simple') {
23
+ console.log(message)
24
+ return
25
+ }
26
+
27
+ console.log(renderBanner(message))
28
+ }
@@ -1,58 +1,58 @@
1
- import { StatusMap } from 'elysia'
2
-
3
- const DIGITS_ONLY = /^\d+$/
4
- const DELIMITERS = /[_-]+/g
5
- const CAMEL_BOUNDARY_1 = /([a-z0-9])([A-Z])/g
6
- const CAMEL_BOUNDARY_2 = /([A-Z])([A-Z][a-z])/g
7
- const APOSTROPHES = /['’]/g
8
- const NON_ALPHANUMERIC = /[^a-z0-9\s]+/g
9
- const WHITESPACE = /\s+/g
10
-
11
- const normalizeStatusName = (value: string): string => {
12
- // Handles common variants:
13
- // - case differences: "not found" vs "Not Found"
14
- // - spacing/punctuation: "Not-Found", "not_found"
15
- // - camelCase/PascalCase: "InternalServerError"
16
- const trimmed = value.trim()
17
- if (!trimmed) {
18
- return ''
19
- }
20
-
21
- return trimmed
22
- .replace(DELIMITERS, ' ')
23
- .replace(CAMEL_BOUNDARY_1, '$1 $2')
24
- .replace(CAMEL_BOUNDARY_2, '$1 $2')
25
- .replace(APOSTROPHES, '')
26
- .toLowerCase()
27
- .replace(NON_ALPHANUMERIC, ' ')
28
- .replace(WHITESPACE, ' ')
29
- .trim()
30
- }
31
-
32
- const STATUS_BY_NORMALIZED_NAME = (() => {
33
- const map = new Map<string, number>()
34
-
35
- for (const [name, code] of Object.entries(StatusMap)) {
36
- map.set(normalizeStatusName(name), code)
37
- }
38
-
39
- return map
40
- })()
41
-
42
- export const getStatusCode = (value: unknown): number => {
43
- if (typeof value === 'number' && Number.isFinite(value)) {
44
- return value
45
- }
46
-
47
- if (typeof value === 'string') {
48
- const trimmed = value.trim()
49
- if (DIGITS_ONLY.test(trimmed)) {
50
- return Number(trimmed)
51
- }
52
-
53
- const known = STATUS_BY_NORMALIZED_NAME.get(normalizeStatusName(trimmed))
54
- return known ?? 500
55
- }
56
-
57
- return 500
58
- }
1
+ import { StatusMap } from 'elysia'
2
+
3
+ const DIGITS_ONLY = /^\d+$/
4
+ const DELIMITERS = /[_-]+/g
5
+ const CAMEL_BOUNDARY_1 = /([a-z0-9])([A-Z])/g
6
+ const CAMEL_BOUNDARY_2 = /([A-Z])([A-Z][a-z])/g
7
+ const APOSTROPHES = /['’]/g
8
+ const NON_ALPHANUMERIC = /[^a-z0-9\s]+/g
9
+ const WHITESPACE = /\s+/g
10
+
11
+ const normalizeStatusName = (value: string): string => {
12
+ // Handles common variants:
13
+ // - case differences: "not found" vs "Not Found"
14
+ // - spacing/punctuation: "Not-Found", "not_found"
15
+ // - camelCase/PascalCase: "InternalServerError"
16
+ const trimmed = value.trim()
17
+ if (!trimmed) {
18
+ return ''
19
+ }
20
+
21
+ return trimmed
22
+ .replace(DELIMITERS, ' ')
23
+ .replace(CAMEL_BOUNDARY_1, '$1 $2')
24
+ .replace(CAMEL_BOUNDARY_2, '$1 $2')
25
+ .replace(APOSTROPHES, '')
26
+ .toLowerCase()
27
+ .replace(NON_ALPHANUMERIC, ' ')
28
+ .replace(WHITESPACE, ' ')
29
+ .trim()
30
+ }
31
+
32
+ const STATUS_BY_NORMALIZED_NAME = (() => {
33
+ const map = new Map<string, number>()
34
+
35
+ for (const [name, code] of Object.entries(StatusMap)) {
36
+ map.set(normalizeStatusName(name), code)
37
+ }
38
+
39
+ return map
40
+ })()
41
+
42
+ export const getStatusCode = (value: unknown): number => {
43
+ if (typeof value === 'number' && Number.isFinite(value)) {
44
+ return value
45
+ }
46
+
47
+ if (typeof value === 'string') {
48
+ const trimmed = value.trim()
49
+ if (DIGITS_ONLY.test(trimmed)) {
50
+ return Number(trimmed)
51
+ }
52
+
53
+ const known = STATUS_BY_NORMALIZED_NAME.get(normalizeStatusName(trimmed))
54
+ return known ?? 500
55
+ }
56
+
57
+ return 500
58
+ }
package/src/index.ts CHANGED
@@ -1,141 +1,162 @@
1
- import { Elysia, type SingletonBase } from 'elysia'
2
- import { startServer } from './extensions'
3
- import type { LogixlysiaStore, Options } from './interfaces'
4
- import { createLogger } from './logger'
5
- import { normalizeToProblem } from './utils/handle-error'
6
-
7
- export type Logixlysia = Elysia<
8
- 'Logixlysia',
9
- SingletonBase & { store: LogixlysiaStore }
10
- >
11
-
12
- export const logixlysia = (options: Options = {}): Logixlysia => {
13
- const didCustomLog = new WeakSet<Request>()
14
- const baseLogger = createLogger(options)
15
- const logger = {
16
- ...baseLogger,
17
- debug: (
18
- request: Request,
19
- message: string,
20
- context?: Record<string, unknown>
21
- ) => {
22
- didCustomLog.add(request)
23
- baseLogger.debug(request, message, context)
24
- },
25
- info: (
26
- request: Request,
27
- message: string,
28
- context?: Record<string, unknown>
29
- ) => {
30
- didCustomLog.add(request)
31
- baseLogger.info(request, message, context)
32
- },
33
- warn: (
34
- request: Request,
35
- message: string,
36
- context?: Record<string, unknown>
37
- ) => {
38
- didCustomLog.add(request)
39
- baseLogger.warn(request, message, context)
40
- },
41
- error: (
42
- request: Request,
43
- message: string,
44
- context?: Record<string, unknown>
45
- ) => {
46
- didCustomLog.add(request)
47
- baseLogger.error(request, message, context)
48
- }
49
- }
50
-
51
- const app = new Elysia({
52
- name: 'Logixlysia',
53
- detail: {
54
- description:
55
- 'Logixlysia is a plugin for Elysia that provides a logger and pino logger.',
56
- tags: ['logging', 'pino']
57
- }
58
- })
59
-
60
- return (
61
- app
62
- .state('logger', logger)
63
- .state('pino', logger.pino)
64
- .state('beforeTime', BigInt(0))
65
- .onStart(({ server }) => {
66
- if (server) {
67
- startServer(server, options)
68
- }
69
- })
70
- .onRequest(({ store }) => {
71
- store.beforeTime = process.hrtime.bigint()
72
- })
73
- .onAfterHandle(({ request, set, store }) => {
74
- if (didCustomLog.has(request)) {
75
- return
76
- }
77
-
78
- const status = typeof set.status === 'number' ? set.status : 200
79
- let level: 'INFO' | 'WARNING' | 'ERROR' = 'INFO'
80
- if (status >= 500) {
81
- level = 'ERROR'
82
- } else if (status >= 400) {
83
- level = 'WARNING'
84
- }
85
-
86
- logger.log(level, request, { status }, store)
87
- })
88
- .onError(({ request, error,code, path,store,set }) => {
89
- // logger.handleHttpError(request, error, store)
90
-
91
- // ==========================================
92
- // Phase 1: Transform (转换)
93
- // ==========================================
94
- let result = options.transform ? options.transform(error, { request, code }) : error
95
-
96
- // ==========================================
97
- // Phase 2: Normalization (规范化)
98
- // ==========================================
99
- // 统一转为 ProblemError 实例
100
- const problem = normalizeToProblem(result, code, path, options.config?.error?.problemJson?.typeBaseUrl)
101
-
102
- // ==========================================
103
- // Phase 3: Logging (日志)
104
- // ==========================================
105
- // 调用上面改造后的函数,它现在只负责记录,不负责逻辑判断
106
- logger.handleHttpError(request, problem, store, options)
107
-
108
- // ==========================================
109
- // Phase 4: Response (响应)
110
- // ==========================================
111
- // 统一设置 Header 和 Status
112
- set.status = problem.status
113
- set.headers['content-type'] = 'application/problem+json'
114
-
115
- // 返回符合 RFC 标准的 JSON
116
- return problem.toJSON()
117
-
118
-
119
- })
120
- // Ensure plugin lifecycle hooks (onRequest/onAfterHandle/onError) apply to the parent app.
121
- .as('scoped') as unknown as Logixlysia
122
- )
123
- }
124
-
125
- export type {
126
- Logger,
127
- LogixlysiaContext,
128
- LogixlysiaStore,
129
- LogLevel,
130
- Options,
131
- Pino,
132
- StoreData,
133
- Transport,
134
- } from './interfaces'
135
-
136
- export { HttpError } from './Error/errors'
137
- export { toProblemJson, formatProblemJsonLog } from './utils/handle-error'
138
- export type { ProblemJson } from './utils/handle-error'
139
-
140
- export default logixlysia
141
-
1
+ import { Elysia, type SingletonBase } from 'elysia'
2
+ import { startServer } from './extensions'
3
+ import type { LogixlysiaStore, Options } from './interfaces'
4
+ import { createLogger } from './logger'
5
+ import { normalizeToProblem } from './utils/handle-error'
6
+
7
+ export type Logixlysia = Elysia<
8
+ 'Logixlysia',
9
+ SingletonBase & { store: LogixlysiaStore }
10
+ >
11
+
12
+ export const logixlysia = (options: Options = {}): Logixlysia => {
13
+ const didCustomLog = new WeakSet<Request>()
14
+ const baseLogger = createLogger(options)
15
+ const logger = {
16
+ ...baseLogger,
17
+ debug: (
18
+ request: Request,
19
+ message: string,
20
+ context?: Record<string, unknown>
21
+ ) => {
22
+ didCustomLog.add(request)
23
+ baseLogger.debug(request, message, context)
24
+ },
25
+ info: (
26
+ request: Request,
27
+ message: string,
28
+ context?: Record<string, unknown>
29
+ ) => {
30
+ didCustomLog.add(request)
31
+ baseLogger.info(request, message, context)
32
+ },
33
+ warn: (
34
+ request: Request,
35
+ message: string,
36
+ context?: Record<string, unknown>
37
+ ) => {
38
+ didCustomLog.add(request)
39
+ baseLogger.warn(request, message, context)
40
+ },
41
+ error: (
42
+ request: Request,
43
+ message: string,
44
+ context?: Record<string, unknown>
45
+ ) => {
46
+ didCustomLog.add(request)
47
+ baseLogger.error(request, message, context)
48
+ }
49
+ }
50
+
51
+ const app = new Elysia({
52
+ name: 'Logixlysia',
53
+ detail: {
54
+ description:
55
+ 'Logixlysia is a plugin for Elysia that provides a logger and pino logger.',
56
+ tags: ['logging', 'pino']
57
+ }
58
+ })
59
+
60
+ return (
61
+ app
62
+ .state('logger', logger)
63
+ .state('pino', logger.pino)
64
+ .state('beforeTime', BigInt(0))
65
+ .onStart(({ server }) => {
66
+ if (server) {
67
+ startServer(server, options)
68
+ }
69
+ })
70
+ .onRequest(({ store }) => {
71
+ store.beforeTime = process.hrtime.bigint()
72
+ })
73
+ .onAfterHandle(({ request, set, store }) => {
74
+ if (didCustomLog.has(request)) {
75
+ return
76
+ }
77
+
78
+ const status = typeof set.status === 'number' ? set.status : 200
79
+ let level: 'INFO' | 'WARNING' | 'ERROR' = 'INFO'
80
+ if (status >= 500) {
81
+ level = 'ERROR'
82
+ } else if (status >= 400) {
83
+ level = 'WARNING'
84
+ }
85
+
86
+ logger.log(level, request, { status }, store)
87
+ })
88
+ .onError(({ request, error, code, path, store, set }) => {
89
+ // logger.handleHttpError(request, error, store)
90
+
91
+ // ==========================================
92
+ // Phase 1: Transform (转换)
93
+ // ==========================================
94
+ const result = options.transform
95
+ ? options.transform(error, { request, code, path })
96
+ : error
97
+
98
+ // ==========================================
99
+ // Phase 2: Normalization (规范化)
100
+ // ==========================================
101
+ // 统一转为 ProblemError 实例
102
+ const problem = normalizeToProblem(
103
+ result,
104
+ code,
105
+ path,
106
+ options.config?.error?.problemJson?.typeBaseUrl
107
+ )
108
+
109
+ // ==========================================
110
+ // Phase 3: Logging (日志)
111
+ // ==========================================
112
+ // 调用上面改造后的函数,它现在只负责记录,不负责逻辑判断
113
+ logger.handleHttpError(request, problem, store, options)
114
+
115
+ // ==========================================
116
+ // Phase 4: Response (响应)
117
+ // ==========================================
118
+ // 统一设置 Header 和 Status
119
+ set.status = problem.status
120
+ set.headers['content-type'] = 'application/problem+json'
121
+
122
+ // 返回符合 RFC 标准的 JSON
123
+ return problem.toJSON()
124
+ })
125
+ // Ensure plugin lifecycle hooks (onRequest/onAfterHandle/onError) apply to the parent app.
126
+ .as('scoped') as unknown as Logixlysia
127
+ )
128
+ }
129
+
130
+ // ==========================================
131
+ // Error Exports
132
+ // ==========================================
133
+
134
+ export * from "./Error/errors";
135
+ export type { ProblemDocument } from './Error/errors'
136
+ export type {
137
+ Code,
138
+ ErrorContext,
139
+ HttpProblemJsonOptions
140
+ } from './Error/type'
141
+ export { normalizeToProblem } from './utils/handle-error'
142
+ export type {
143
+ ProblemJson,
144
+ ProblemJsonOptions
145
+ } from './utils/handle-error'
146
+ export { formatProblemJsonLog, toProblemJson } from './utils/handle-error'
147
+
148
+ // ==========================================
149
+ // Core Exports
150
+ // ==========================================
151
+ export type {
152
+ Logger,
153
+ LogixlysiaContext,
154
+ LogixlysiaStore,
155
+ LogLevel,
156
+ Options,
157
+ Pino,
158
+ StoreData,
159
+ Transport
160
+ } from './interfaces'
161
+
162
+ export default logixlysia