@mpen/routekit 0.1.0 → 0.1.2

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.
Files changed (133) hide show
  1. package/dist/bin.d.mts +4 -0
  2. package/dist/client/react.d.mts +178 -0
  3. package/dist/client/react.mjs +142 -0
  4. package/dist/client.d.mts +433 -0
  5. package/dist/client.mjs +264 -0
  6. package/dist/content-BuDOmhH_.mjs +102 -0
  7. package/dist/core-CzUCxvGk.d.mts +140 -0
  8. package/dist/core-DbmQauwS.mjs +81 -0
  9. package/dist/handlers.d.mts +72 -0
  10. package/dist/handlers.mjs +153 -0
  11. package/dist/index.d.mts +3 -0
  12. package/dist/index.mjs +1152 -0
  13. package/dist/middleware.d.mts +388 -0
  14. package/dist/middleware.mjs +1222 -0
  15. package/dist/request-Dn0zc-xm.mjs +1025 -0
  16. package/dist/response/content.d.mts +79 -0
  17. package/dist/response/content.mjs +2 -0
  18. package/dist/response/json-rpc.d.mts +1 -0
  19. package/dist/response/json-rpc.mjs +1 -0
  20. package/dist/response/problem/valibot.d.mts +230 -0
  21. package/dist/response/problem/valibot.mjs +258 -0
  22. package/dist/response/problem.d.mts +415 -0
  23. package/dist/response/problem.mjs +183 -0
  24. package/dist/response/status.d.mts +45 -0
  25. package/dist/response/status.mjs +2 -0
  26. package/dist/responses-B379Ep9Y.d.mts +296 -0
  27. package/dist/responses-BpVrgeYi.mjs +101 -0
  28. package/dist/router-Cwb7ak0J.d.mts +1819 -0
  29. package/dist/routes.d.mts +282 -0
  30. package/dist/routes.mjs +311 -0
  31. package/dist/status-C-8mw-FB.mjs +59 -0
  32. package/dist/valibot-D7liFYyB.d.mts +290 -0
  33. package/dist/valibot-Du97X-TS.mjs +326 -0
  34. package/package.json +8 -2
  35. package/src/bin/gen-api-client.test.ts +0 -70
  36. package/src/bin/gen-api-client.ts +0 -986
  37. package/src/client/headers.ts +0 -31
  38. package/src/client/index.ts +0 -8
  39. package/src/client/promise.ts +0 -11
  40. package/src/client/react/index.test.tsx +0 -266
  41. package/src/client/react/index.ts +0 -431
  42. package/src/client/responses.test.ts +0 -151
  43. package/src/client/responses.ts +0 -278
  44. package/src/client/transport.ts +0 -74
  45. package/src/client/transports/body-codec.ts +0 -61
  46. package/src/client/transports/fetch.ts +0 -113
  47. package/src/client/tsconfig.json +0 -9
  48. package/src/client/types.ts +0 -15
  49. package/src/client/url.ts +0 -31
  50. package/src/index.ts +0 -63
  51. package/src/router/fetch-types.ts +0 -13
  52. package/src/router/handlers/index.ts +0 -2
  53. package/src/router/handlers/openapi/index.ts +0 -2
  54. package/src/router/handlers/openapi/openapi.ts +0 -293
  55. package/src/router/integration/zod-openapi.test.ts +0 -74
  56. package/src/router/lib/charset.test.ts +0 -22
  57. package/src/router/lib/charset.ts +0 -133
  58. package/src/router/lib/collections.ts +0 -3
  59. package/src/router/lib/format.test.ts +0 -67
  60. package/src/router/lib/format.ts +0 -35
  61. package/src/router/lib/host.ts +0 -4
  62. package/src/router/lib/json-schema.ts +0 -6
  63. package/src/router/lib/media-type.test.ts +0 -122
  64. package/src/router/lib/media-type.ts +0 -289
  65. package/src/router/lib/pathname.test.ts +0 -18
  66. package/src/router/lib/pathname.ts +0 -19
  67. package/src/router/lib/route-names.ts +0 -70
  68. package/src/router/lib/route-normalize.test.ts +0 -36
  69. package/src/router/lib/route-normalize.ts +0 -67
  70. package/src/router/lib/schema-merge.ts +0 -56
  71. package/src/router/middleware/accept-ctx.test.ts +0 -33
  72. package/src/router/middleware/accept-ctx.ts +0 -12
  73. package/src/router/middleware/body-limit.test.ts +0 -112
  74. package/src/router/middleware/body-limit.ts +0 -121
  75. package/src/router/middleware/content-type-context.ts +0 -0
  76. package/src/router/middleware/cors.test.ts +0 -269
  77. package/src/router/middleware/cors.ts +0 -490
  78. package/src/router/middleware/csrf.test.ts +0 -106
  79. package/src/router/middleware/csrf.ts +0 -192
  80. package/src/router/middleware/define.ts +0 -249
  81. package/src/router/middleware/index.ts +0 -34
  82. package/src/router/middleware/jsxhtml-response.ts +0 -0
  83. package/src/router/middleware/oas-swagger.ts +0 -0
  84. package/src/router/middleware/rate-limit.test.ts +0 -886
  85. package/src/router/middleware/rate-limit.ts +0 -920
  86. package/src/router/middleware/request-id-ctx.test.ts +0 -183
  87. package/src/router/middleware/request-id-ctx.ts +0 -135
  88. package/src/router/middleware/request-logger-format.test.ts +0 -16
  89. package/src/router/middleware/request-logger-format.ts +0 -269
  90. package/src/router/middleware/request-logger.test.ts +0 -267
  91. package/src/router/middleware/request-logger.ts +0 -131
  92. package/src/router/middleware/start-time-ctx.ts +0 -5
  93. package/src/router/request.ts +0 -611
  94. package/src/router/response/core.ts +0 -181
  95. package/src/router/response/directives.ts +0 -233
  96. package/src/router/response/formats/content/bodyless.ts +0 -54
  97. package/src/router/response/formats/content/content.ts +0 -79
  98. package/src/router/response/formats/content/index.ts +0 -2
  99. package/src/router/response/formats/json-rpc/index.ts +0 -2
  100. package/src/router/response/formats/problem/badRequest.ts +0 -90
  101. package/src/router/response/formats/problem/conflict.ts +0 -90
  102. package/src/router/response/formats/problem/created.ts +0 -40
  103. package/src/router/response/formats/problem/index.ts +0 -27
  104. package/src/router/response/formats/problem/notFound.ts +0 -90
  105. package/src/router/response/formats/problem/permissionDenied.ts +0 -90
  106. package/src/router/response/formats/problem/problem.test.ts +0 -888
  107. package/src/router/response/formats/problem/rateLimited.ts +0 -90
  108. package/src/router/response/formats/problem/responses.ts +0 -219
  109. package/src/router/response/formats/problem/root-errors.ts +0 -48
  110. package/src/router/response/formats/problem/sessionExpired.ts +0 -90
  111. package/src/router/response/formats/problem/types.ts +0 -170
  112. package/src/router/response/formats/problem/unauthenticated.ts +0 -90
  113. package/src/router/response/formats/problem/valibot.ts +0 -410
  114. package/src/router/response/formats/status/index.ts +0 -1
  115. package/src/router/response/formats/status/responses.ts +0 -59
  116. package/src/router/response/formats/status/status.test.ts +0 -21
  117. package/src/router/response/framers.ts +0 -85
  118. package/src/router/response/index.ts +0 -28
  119. package/src/router/response/openapi.test.ts +0 -96
  120. package/src/router/response/openapi.ts +0 -1
  121. package/src/router/response/serializers.ts +0 -66
  122. package/src/router/response/stream.ts +0 -35
  123. package/src/router/router.test.ts +0 -1571
  124. package/src/router/router.ts +0 -1965
  125. package/src/router/routes/index.ts +0 -46
  126. package/src/router/routes/valibot/index.ts +0 -18
  127. package/src/router/routes/valibot/valibot.ts +0 -1393
  128. package/src/router/routes/valibot.test.ts +0 -286
  129. package/src/router/routes/zod/index.ts +0 -18
  130. package/src/router/routes/zod/zod.ts +0 -1318
  131. package/src/router/routes/zod.test.ts +0 -280
  132. package/src/router/server-interface.ts +0 -31
  133. package/src/router/types.ts +0 -657
@@ -1,183 +0,0 @@
1
- #!/usr/bin/env -S bun test
2
- import { describe, expect, it } from 'bun:test'
3
- import { HttpMethod } from '@mpen/http'
4
- import { Router } from '../router'
5
- import { requestIdCtx } from './request-id-ctx'
6
- import type { RouterHeadersInit } from '../fetch-types'
7
-
8
- function makeRequest(headers?: RouterHeadersInit): Request {
9
- const init: RequestInit = { method: HttpMethod.GET }
10
- if (headers) init.headers = headers
11
- return new Request('https://example.com/', init)
12
- }
13
-
14
- describe(requestIdCtx.name, () => {
15
- it('uses the request id header when present', async () => {
16
- const router = new Router()
17
- router.useRequest(requestIdCtx())
18
- router.add({
19
- method: HttpMethod.GET,
20
- path: '/',
21
- handler: ({ requestId }) => {
22
- expect(requestId).toBe('req-123')
23
- return new Response(requestId)
24
- },
25
- })
26
-
27
- const response = await router.fetch(makeRequest({ 'x-request-id': 'req-123' }))
28
-
29
- expect(await response.text()).toBe('req-123')
30
- })
31
-
32
- it('uses the generate callback when missing a header', async () => {
33
- const router = new Router()
34
- const seenExtra: { prefix: string; hotReloadCounter: number; requestCounter: number }[] = []
35
- const generated: string[] = []
36
- const generate = (
37
- _ctx: unknown,
38
- extra: { prefix: string; hotReloadCounter: number; requestCounter: number },
39
- ) => {
40
- seenExtra.push(extra)
41
- const value = `${extra.prefix}.${extra.hotReloadCounter}.${extra.requestCounter}`
42
- generated.push(value)
43
- return value
44
- }
45
- router.useRequest(requestIdCtx({ generate, prefix: 'custom' }))
46
- router.add({
47
- method: HttpMethod.GET,
48
- path: '/',
49
- handler: ({ requestId }) => {
50
- expect(requestId).toBe(generated[generated.length - 1])
51
- return new Response(requestId)
52
- },
53
- })
54
-
55
- const first = await router.fetch(makeRequest())
56
- const second = await router.fetch(makeRequest())
57
-
58
- expect(await first.text()).toMatch(/^custom\.\d+\.\d+$/)
59
- expect(await second.text()).toMatch(/^custom\.\d+\.\d+$/)
60
- expect(seenExtra).toHaveLength(2)
61
- expect(seenExtra[0]?.prefix).toBe('custom')
62
- expect(seenExtra[1]?.prefix).toBe('custom')
63
- expect(seenExtra[0]?.hotReloadCounter).toBe(seenExtra[1]?.hotReloadCounter)
64
- expect(seenExtra[0]?.requestCounter).toBe(1)
65
- expect(seenExtra[1]?.requestCounter).toBe(2)
66
- })
67
-
68
- it('respects header order when multiple names are provided', async () => {
69
- const router = new Router()
70
- router.useRequest(requestIdCtx({ readHeaderName: ['x-request-id', 'x-trace-id'] }))
71
- router.add({
72
- method: HttpMethod.GET,
73
- path: '/',
74
- handler: ({ requestId, request }) => {
75
- const headerId =
76
- request.headers.get('x-request-id') ?? request.headers.get('x-trace-id')
77
- expect(requestId).toBe(headerId)
78
- return new Response(requestId)
79
- },
80
- })
81
-
82
- const fallback = await router.fetch(makeRequest({ 'x-trace-id': 'trace-456' }))
83
- const preferred = await router.fetch(
84
- makeRequest({
85
- 'x-request-id': 'primary-789',
86
- 'x-trace-id': 'secondary-000',
87
- }),
88
- )
89
-
90
- expect(await fallback.text()).toBe('trace-456')
91
- expect(await preferred.text()).toBe('primary-789')
92
- })
93
-
94
- it('writes the request id into the response header when configured', async () => {
95
- const router = new Router()
96
- router.useRequest(
97
- requestIdCtx({
98
- generate: () => 'req-42',
99
- writeHeaderName: 'x-request-id',
100
- }),
101
- )
102
- router.add({
103
- method: HttpMethod.GET,
104
- path: '/',
105
- handler: ({ requestId }) => {
106
- expect(requestId).toBe('req-42')
107
- return 'ok'
108
- },
109
- })
110
-
111
- const response = await router.fetch(makeRequest())
112
-
113
- expect(response.headers.get('x-request-id')).toBe('req-42')
114
- expect(await response.text()).toBe('ok')
115
- })
116
-
117
- it('writes the request id into logical response headers', async () => {
118
- const router = new Router()
119
- router.useRequest(
120
- requestIdCtx({
121
- generate: () => 'req-logical',
122
- writeHeaderName: 'x-request-id',
123
- }),
124
- )
125
- router.add({
126
- method: HttpMethod.GET,
127
- path: '/',
128
- handler: () => ({ ok: true }),
129
- })
130
-
131
- const response = await router.fetch(makeRequest())
132
-
133
- expect(response.headers.get('x-request-id')).toBe('req-logical')
134
- expect(await response.json()).toEqual({ ok: true })
135
- })
136
-
137
- it('uses the default generator format when no header is provided', async () => {
138
- const router = new Router()
139
- router.useRequest(requestIdCtx({ prefix: 'req' }))
140
- router.add({
141
- method: HttpMethod.GET,
142
- path: '/',
143
- handler: ({ requestId }) => {
144
- return new Response(requestId)
145
- },
146
- })
147
-
148
- const first = await router.fetch(makeRequest())
149
- const second = await router.fetch(makeRequest())
150
-
151
- const firstId = await first.text()
152
- const secondId = await second.text()
153
-
154
- expect(firstId).toBe('req.1')
155
- expect(secondId).toBe('req.2')
156
- })
157
-
158
- it('does not declare a terminal response while decorating downstream output', () => {
159
- const router = new Router().useRequest(requestIdCtx({ writeHeaderName: 'x-request-id' }))
160
- router.add({ method: HttpMethod.GET, path: '/', handler: () => new Response() })
161
-
162
- expect(router.getRoutes()[0]?.schema).toBeUndefined()
163
- })
164
-
165
- it('writes correlated ids on generated responses', async () => {
166
- const router = new Router()
167
- .useRequest(
168
- requestIdCtx({
169
- generate: () => 'req-generated',
170
- writeHeaderName: 'x-request-id',
171
- }),
172
- )
173
- .get('/exists', () => new Response('ok'))
174
-
175
- const missing = await router.fetch(new Request('https://example.com/missing'))
176
- const options = await router.fetch(
177
- new Request('https://example.com/exists', { method: HttpMethod.OPTIONS }),
178
- )
179
-
180
- expect(missing.headers.get('x-request-id')).toBe('req-generated')
181
- expect(options.headers.get('x-request-id')).toBe('req-generated')
182
- })
183
- })
@@ -1,135 +0,0 @@
1
- import type { AnyContext, HandlerContext, OneOrMany, RequestMiddleware } from '../types'
2
-
3
- declare global {
4
- var _reloadCounter: number
5
- var _globalRequestCounter: number
6
- }
7
-
8
- globalThis._reloadCounter = globalThis._reloadCounter == null ? 0 : globalThis._reloadCounter + 1
9
- globalThis._globalRequestCounter ??= 0
10
-
11
- interface ExtraContext {
12
- prefix: string
13
- /**
14
- * Number of times this server has been hot-reloaded.
15
- */
16
- hotReloadCounter: number
17
- /**
18
- * Sequential request number for this middleware instance.
19
- */
20
- requestCounter: number
21
- /**
22
- * Sequential request number across request id middleware instances.
23
- */
24
- globalRequestCounter: number
25
- }
26
-
27
- /**
28
- * Options for [`requestIdCtx`]{@link requestIdCtx}.
29
- *
30
- * @example
31
- * ```ts
32
- * const options: RequestIdCtxOptions = {
33
- * writeHeaderName: 'x-request-id',
34
- * generate: () => crypto.randomUUID(),
35
- * }
36
- * ```
37
- *
38
- * @typeParam Ctx - Context available before request id generation.
39
- */
40
- export interface RequestIdCtxOptions<Ctx extends object = AnyContext> {
41
- /**
42
- * Prefix used by the default generator. Defaults to an empty string.
43
- */
44
- prefix?: string
45
- /**
46
- * Header(s) to check for a request id. Defaults to common request/trace headers.
47
- * Set to `null` or an empty array to disable header reads.
48
- */
49
- readHeaderName?: OneOrMany<string> | null
50
- /**
51
- * Response header name to write the request id into.
52
- */
53
- writeHeaderName?: string
54
- /**
55
- * Custom request id generator used when no read header is present.
56
- *
57
- * @param ctx - Current request context.
58
- * @param extra - Prefix and counter values maintained by this middleware.
59
- * @returns Request identifier.
60
- */
61
- generate?: (ctx: HandlerContext<Ctx>, extra: ExtraContext) => string
62
- }
63
-
64
- export const isHMR = import.meta.hot !== undefined || process.execArgv.includes('--hot')
65
-
66
- function defaultRequestIdGenerator(_ctx: HandlerContext<AnyContext>, extra: ExtraContext): string {
67
- let requestId = extra.requestCounter.toString(36)
68
- if (isHMR) {
69
- requestId = extra.hotReloadCounter.toString(36) + '.' + requestId
70
- }
71
- if (extra.prefix) requestId = `${extra.prefix}.${requestId}`
72
- return requestId
73
- }
74
-
75
- /**
76
- * Attach a correlated request id to every final response and contextual logger record.
77
- *
78
- * @example
79
- * ```ts
80
- * router.useRequest(requestIdCtx({
81
- * readHeaderName: ['x-request-id', 'x-trace-id'],
82
- * writeHeaderName: 'x-request-id',
83
- * generate: () => crypto.randomUUID(),
84
- * }))
85
- * ```
86
- *
87
- * @param options - Configuration for reading, generating, and writing request ids.
88
- * @returns Request-boundary middleware that populates `requestId` and logger context.
89
- * @typeParam Ctx - Context available before request id generation.
90
- */
91
- export function requestIdCtx<Ctx extends object = AnyContext>(
92
- options: RequestIdCtxOptions<Ctx> = {},
93
- ): RequestMiddleware<{ requestId: string }, Ctx> {
94
- const prefix = options.prefix ?? ''
95
- const headers =
96
- options.readHeaderName === undefined
97
- ? ['x-request-id', 'x-trace-id', 'traceparent']
98
- : options.readHeaderName == null
99
- ? []
100
- : Array.isArray(options.readHeaderName)
101
- ? options.readHeaderName
102
- : [options.readHeaderName]
103
- const writeHeaderName = options.writeHeaderName
104
- const hotReloadCounter = globalThis._reloadCounter
105
- let requestCounter = 0
106
-
107
- return async (ctx, next) => {
108
- let headerId: string | null = null
109
-
110
- for (const name of headers) {
111
- headerId = ctx.request.headers.get(name)
112
- if (headerId !== null) break
113
- }
114
-
115
- const extra: ExtraContext = {
116
- prefix,
117
- hotReloadCounter,
118
- requestCounter: ++requestCounter,
119
- globalRequestCounter: ++globalThis._globalRequestCounter,
120
- }
121
- const requestId =
122
- headerId ??
123
- options.generate?.(ctx, extra) ??
124
- defaultRequestIdGenerator(ctx, extra)
125
-
126
- ctx.requestId = requestId
127
- ctx.logger = ctx.logger.withContext({ 'request.id': requestId })
128
-
129
- const response = await next()
130
- if (writeHeaderName != null) {
131
- response.headers.set(writeHeaderName, requestId)
132
- }
133
- return response
134
- }
135
- }
@@ -1,16 +0,0 @@
1
- import { describe, test, expect } from 'bun:test'
2
- import { formatDurationMilliseconds } from './request-logger-format.ts'
3
-
4
- describe(formatDurationMilliseconds.name, () => {
5
- test('formats durations correctly', () => {
6
- expect(formatDurationMilliseconds(0.005)).toBe('0.01')
7
- expect(formatDurationMilliseconds(0.05)).toBe('0.05')
8
- expect(formatDurationMilliseconds(0.5)).toBe('0.50')
9
- expect(formatDurationMilliseconds(5)).toBe('5.0')
10
- expect(formatDurationMilliseconds(50)).toBe('50.0')
11
- expect(formatDurationMilliseconds(500)).toBe('500')
12
-
13
- expect(formatDurationMilliseconds(0.99)).toBe('0.99')
14
- expect(formatDurationMilliseconds(0.996)).toBe('1.0')
15
- })
16
- })
@@ -1,269 +0,0 @@
1
- import type { LogRecord, LogRecordTransform, TerminalLogRecordFormatter } from '@mpen/logger'
2
-
3
- export const ROUTEKIT_HTTP_SERVER_REQUEST_EVENT_NAME = 'http.server.request'
4
-
5
- const ROUTEKIT_REQUEST_CONTEXT_KEYS = ['http.request.method', 'url.path', 'request.id'] as const
6
-
7
- const REQUEST_ID_COLOR_KEYS = [
8
- 'cyanBright',
9
- 'magentaBright',
10
- 'greenBright',
11
- 'yellowBright',
12
- 'blueBright',
13
- 'redBright',
14
- 'cyan',
15
- 'magenta',
16
- ] as const
17
-
18
- /**
19
- * Transform Routekit request activity records into production JSON access logs.
20
- *
21
- * Removes the fallback activity message from `http.server.request` records while preserving
22
- * normal application log messages written through the same contextual logger.
23
- *
24
- * @example
25
- * ```ts
26
- * import { JsonLogger } from '@mpen/logger'
27
- * import { transformRoutekitJsonLogRecord } from '@mpen/routekit/middleware'
28
- *
29
- * const logger = new JsonLogger({
30
- * transformRecord: transformRoutekitJsonLogRecord,
31
- * })
32
- * ```
33
- *
34
- * @param record - Record emitted by a logger.
35
- * @returns The transformed record, or `undefined` to use the original record.
36
- */
37
- export const transformRoutekitJsonLogRecord: LogRecordTransform = (record) => {
38
- if (!isRoutekitRequestEvent(record)) {
39
- return undefined
40
- }
41
-
42
- return {
43
- ...record,
44
- data: stripActivityLifecycleMessage(record),
45
- }
46
- }
47
-
48
- /**
49
- * Format Routekit request context for terminal logs.
50
- *
51
- * Renders compact request start/completion lines, color-correlated request identifiers, status
52
- * codes, elapsed time, and response sizes while leaving unrelated records to
53
- * [`TerminalLogger`]{@link import('@mpen/logger').TerminalLogger}'s default renderer.
54
- *
55
- * @example
56
- * ```ts
57
- * import { TerminalLogger } from '@mpen/logger'
58
- * import { formatRoutekitTerminalLogRecord } from '@mpen/routekit/middleware'
59
- *
60
- * const logger = new TerminalLogger({
61
- * formatRecord: formatRoutekitTerminalLogRecord,
62
- * })
63
- * ```
64
- *
65
- * @param record - Record emitted by a logger.
66
- * @param terminal - Terminal formatting helpers.
67
- * @returns Formatted terminal output, or `undefined` to use the default renderer.
68
- */
69
- export const formatRoutekitTerminalLogRecord: TerminalLogRecordFormatter = (record, terminal) => {
70
- if (!hasRoutekitRequestContext(record)) {
71
- return undefined
72
- }
73
-
74
- const colors = terminal.colors
75
- const context = record.context
76
- const requestId = getStringContext(record, 'request.id')
77
- const method = getStringContext(record, 'http.request.method')!
78
- const path = getStringContext(record, 'url.path')!
79
- const route = getStringContext(record, 'http.route')
80
- const direction = getActivityDirection(record)
81
- const parts = [
82
- formatServiceName(getStringContext(record, 'service.name'), colors),
83
- requestId == null ? undefined : formatRequestId(requestId, colors),
84
- requestId == null ? undefined : formatDirection(direction, colors),
85
- requestId == null ? formatDirection(direction, colors) : undefined,
86
- colors.bold(colors.whiteBright(method)),
87
- colors.white(path),
88
- route == null || route === path ? undefined : colors.blackBright(`route=${route}`),
89
- formatStatusCode(getNumberContext(context['http.response.status_code']), colors),
90
- formatDuration(getNumberContext(context.duration_ms), colors),
91
- formatBodySize(getNumberContext(context['http.response.body.size']), colors),
92
- ].filter((part): part is string => part != null && part !== '')
93
- const prefix = `${terminal.icon} ${colors.blackBright(terminal.time)} ${parts.join(' ')}`
94
- const data = direction == null ? record.data : stripActivityLifecycleMessage(record)
95
-
96
- if (data.length === 0) {
97
- return prefix.trimEnd()
98
- }
99
-
100
- const message = terminal.formatData(data)
101
-
102
- if (message === '') {
103
- return prefix.trimEnd()
104
- }
105
-
106
- const [firstLine = '', ...remainingLines] = message.split('\n')
107
-
108
- return [prefix + ' ' + firstLine, ...remainingLines.map((line) => ' ' + line)].join('\n')
109
- }
110
-
111
- function hasRoutekitRequestContext(record: LogRecord): boolean {
112
- return ROUTEKIT_REQUEST_CONTEXT_KEYS.every((key) => record.context[key] != null)
113
- }
114
-
115
- function isRoutekitRequestEvent(record: LogRecord): boolean {
116
- return record.context['event.name'] === ROUTEKIT_HTTP_SERVER_REQUEST_EVENT_NAME
117
- }
118
-
119
- function stripActivityLifecycleMessage(record: LogRecord): readonly unknown[] {
120
- return record.metadata.activity == null ? record.data : record.data.slice(1)
121
- }
122
-
123
- function getActivityDirection(record: LogRecord): '\u2192' | '\u2190' | undefined {
124
- switch (record.metadata.activity?.phase) {
125
- case 'start':
126
- return '\u2192'
127
- case 'end':
128
- return '\u2190'
129
- }
130
- }
131
-
132
- function getStringContext(record: LogRecord, key: string): string | undefined {
133
- const value = record.context[key]
134
-
135
- return value == null ? undefined : String(value)
136
- }
137
-
138
- function getNumberContext(value: unknown): number | undefined {
139
- if (typeof value !== 'number') {
140
- return undefined
141
- }
142
-
143
- return Number.isFinite(value) ? value : undefined
144
- }
145
-
146
- function formatServiceName(
147
- value: string | undefined,
148
- colors: Parameters<TerminalLogRecordFormatter>[1]['colors'],
149
- ): string | undefined {
150
- if (value == null) {
151
- return undefined
152
- }
153
-
154
- const text = `[${value}]`
155
-
156
- return colors.bold(colors.blueBright(text))
157
- }
158
-
159
- function formatRequestId(
160
- value: string,
161
- colors: Parameters<TerminalLogRecordFormatter>[1]['colors'],
162
- ): string {
163
- const text = `[${value}]`
164
- let hash = 0
165
-
166
- for (const char of value) {
167
- hash = (hash * 31 + char.codePointAt(0)!) >>> 0
168
- }
169
-
170
- const color = REQUEST_ID_COLOR_KEYS[hash % REQUEST_ID_COLOR_KEYS.length]!
171
-
172
- return colors[color](text)
173
- }
174
-
175
- function formatDirection(
176
- value: '\u2192' | '\u2190' | undefined,
177
- colors: Parameters<TerminalLogRecordFormatter>[1]['colors'],
178
- ): string | undefined {
179
- return value == null ? undefined : colors.bold(colors.whiteBright(value))
180
- }
181
-
182
- function formatStatusCode(
183
- value: number | undefined,
184
- colors: Parameters<TerminalLogRecordFormatter>[1]['colors'],
185
- ): string | undefined {
186
- if (value == null) {
187
- return undefined
188
- }
189
-
190
- const text = String(value)
191
- const formatted =
192
- value >= 500
193
- ? colors.redBright(text)
194
- : value >= 400
195
- ? colors.yellowBright(text)
196
- : value >= 300
197
- ? colors.cyanBright(text)
198
- : colors.greenBright(text)
199
-
200
- return colors.bold(formatted)
201
- }
202
-
203
- function formatDuration(
204
- value: number | undefined,
205
- colors: Parameters<TerminalLogRecordFormatter>[1]['colors'],
206
- ): string | undefined {
207
- if (value == null) {
208
- return undefined
209
- }
210
-
211
- const text = `${formatDurationMilliseconds(value)}ms`
212
-
213
- if (value >= 1000) {
214
- return colors.redBright(text)
215
- }
216
-
217
- return value >= 250 ? colors.yellowBright(text) : colors.greenBright(text)
218
- }
219
-
220
- /**
221
- * @internal
222
- */
223
- export function formatDurationMilliseconds(value: number): string {
224
- const p2 = value.toFixed(2)
225
- if (Number(p2) < 1) {
226
- return p2
227
- }
228
-
229
- const p1 = value.toFixed(1)
230
- if (Number(p1) < 100) {
231
- return p1
232
- }
233
-
234
- return value.toFixed(0)
235
- }
236
-
237
- function formatBodySize(
238
- value: number | undefined,
239
- colors: Parameters<TerminalLogRecordFormatter>[1]['colors'],
240
- ): string | undefined {
241
- return value == null ? undefined : colors.cyan(formatByteSize(value))
242
- }
243
-
244
- function formatByteSize(value: number): string {
245
- if (!Number.isFinite(value) || value < 0) {
246
- return String(value)
247
- }
248
-
249
- if (value < 1024) {
250
- return `${value} B`
251
- }
252
-
253
- const units = ['KiB', 'MiB', 'GiB'] as const
254
- let amount = value
255
- let unit = 'B'
256
-
257
- for (const nextUnit of units) {
258
- amount /= 1024
259
- unit = nextUnit
260
-
261
- if (amount < 1024 || nextUnit === units.at(-1)) {
262
- break
263
- }
264
- }
265
-
266
- const precision = amount >= 100 ? 0 : amount >= 10 ? 1 : 2
267
-
268
- return `${amount.toFixed(precision)} ${unit}`
269
- }