weifuwu 0.25.2 → 0.27.1
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/README.md +291 -2489
- package/ai/provider.ts +129 -0
- package/ai/stream.ts +63 -0
- package/{dist/cli.d.ts → cli.js} +1 -1
- package/cli.ts +55 -257
- package/core/cookie.ts +114 -0
- package/core/env.ts +142 -0
- package/core/logger.ts +72 -0
- package/core/router.ts +795 -0
- package/core/serve.ts +294 -0
- package/core/sse.ts +85 -0
- package/core/trace.ts +146 -0
- package/graphql.ts +267 -0
- package/hub.ts +133 -0
- package/index.ts +71 -0
- package/mailer.ts +81 -0
- package/middleware/compress.ts +103 -0
- package/middleware/cors.ts +81 -0
- package/middleware/csrf.ts +112 -0
- package/middleware/flash.ts +144 -0
- package/middleware/health.ts +44 -0
- package/middleware/helmet.ts +98 -0
- package/middleware/i18n.ts +175 -0
- package/middleware/rate-limit.ts +167 -0
- package/middleware/request-id.ts +60 -0
- package/middleware/static.ts +149 -0
- package/middleware/theme.ts +84 -0
- package/middleware/upload.ts +168 -0
- package/middleware/validate.ts +186 -0
- package/package.json +15 -36
- package/postgres/client.ts +132 -0
- package/postgres/index.ts +4 -0
- package/postgres/module.ts +37 -0
- package/postgres/schema/columns.ts +186 -0
- package/postgres/schema/index.ts +36 -0
- package/postgres/schema/sql.ts +39 -0
- package/postgres/schema/table.ts +548 -0
- package/postgres/schema/where.ts +99 -0
- package/postgres/types.ts +48 -0
- package/queue/cron.ts +90 -0
- package/queue/index.ts +654 -0
- package/queue/types.ts +60 -0
- package/redis/client.ts +24 -0
- package/{dist/redis/index.d.ts → redis/index.ts} +2 -2
- package/redis/types.ts +28 -0
- package/types.ts +78 -0
- package/cli/template/app.ts +0 -22
- package/cli/template/index.ts +0 -10
- package/cli/template/locales/en.json +0 -13
- package/cli/template/locales/zh-CN.json +0 -13
- package/cli/template/locales/zh-TW.json +0 -13
- package/cli/template/locales/zh.json +0 -13
- package/cli/template/ui/app/globals.css +0 -2
- package/cli/template/ui/app/layout.tsx +0 -15
- package/cli/template/ui/app/page.tsx +0 -124
- package/cli/template/ui/components/Greeting.tsx +0 -3
- package/dist/agent/client.d.ts +0 -2
- package/dist/agent/index.d.ts +0 -2
- package/dist/agent/rest.d.ts +0 -14
- package/dist/agent/run.d.ts +0 -19
- package/dist/agent/types.d.ts +0 -55
- package/dist/ai/provider.d.ts +0 -45
- package/dist/ai/utils.d.ts +0 -5
- package/dist/ai/workflow.d.ts +0 -17
- package/dist/ai-sdk.d.ts +0 -2
- package/dist/ai.d.ts +0 -13
- package/dist/analytics.d.ts +0 -45
- package/dist/auth.d.ts +0 -22
- package/dist/cache.d.ts +0 -74
- package/dist/cli.js +0 -302
- package/dist/client-locale.d.ts +0 -25
- package/dist/client-pref.d.ts +0 -3
- package/dist/client-router.d.ts +0 -300
- package/dist/client-state.d.ts +0 -22
- package/dist/client-theme.d.ts +0 -36
- package/dist/compile.d.ts +0 -15
- package/dist/compress.d.ts +0 -20
- package/dist/cookie.d.ts +0 -36
- package/dist/cors.d.ts +0 -25
- package/dist/cron-utils.d.ts +0 -73
- package/dist/csrf.d.ts +0 -47
- package/dist/deploy/config.d.ts +0 -2
- package/dist/deploy/gateway.d.ts +0 -2
- package/dist/deploy/index.d.ts +0 -4
- package/dist/deploy/manager.d.ts +0 -16
- package/dist/deploy/process.d.ts +0 -14
- package/dist/deploy/types.d.ts +0 -53
- package/dist/env.d.ts +0 -69
- package/dist/error-boundary.d.ts +0 -2
- package/dist/flash.d.ts +0 -90
- package/dist/fts.d.ts +0 -36
- package/dist/graphql.d.ts +0 -16
- package/dist/head.d.ts +0 -6
- package/dist/health.d.ts +0 -24
- package/dist/helmet.d.ts +0 -33
- package/dist/html-shell.d.ts +0 -1
- package/dist/hub.d.ts +0 -37
- package/dist/i18n.d.ts +0 -39
- package/dist/iii/client.d.ts +0 -2
- package/dist/iii/index.d.ts +0 -4
- package/dist/iii/register-worker.d.ts +0 -9
- package/dist/iii/rest.d.ts +0 -3
- package/dist/iii/stream.d.ts +0 -82
- package/dist/iii/types.d.ts +0 -121
- package/dist/iii/worker.d.ts +0 -2
- package/dist/iii/ws.d.ts +0 -22
- package/dist/index.d.ts +0 -101
- package/dist/index.js +0 -12752
- package/dist/kb/index.d.ts +0 -3
- package/dist/kb/types.d.ts +0 -72
- package/dist/layout.d.ts +0 -2
- package/dist/live.d.ts +0 -7
- package/dist/logdb/client.d.ts +0 -2
- package/dist/logdb/index.d.ts +0 -2
- package/dist/logdb/rest.d.ts +0 -5
- package/dist/logdb/types.d.ts +0 -27
- package/dist/logger.d.ts +0 -16
- package/dist/mailer.d.ts +0 -51
- package/dist/mcp.d.ts +0 -34
- package/dist/messager/agent.d.ts +0 -11
- package/dist/messager/client.d.ts +0 -2
- package/dist/messager/index.d.ts +0 -2
- package/dist/messager/rest.d.ts +0 -15
- package/dist/messager/types.d.ts +0 -57
- package/dist/messager/ws.d.ts +0 -14
- package/dist/module-server.d.ts +0 -9
- package/dist/not-found.d.ts +0 -2
- package/dist/notifier/client.d.ts +0 -2
- package/dist/notifier/index.d.ts +0 -2
- package/dist/notifier/types.d.ts +0 -105
- package/dist/opencode/client.d.ts +0 -2
- package/dist/opencode/index.d.ts +0 -2
- package/dist/opencode/permissions.d.ts +0 -5
- package/dist/opencode/prompt.d.ts +0 -8
- package/dist/opencode/rest.d.ts +0 -16
- package/dist/opencode/run.d.ts +0 -13
- package/dist/opencode/session.d.ts +0 -26
- package/dist/opencode/skills.d.ts +0 -4
- package/dist/opencode/tools/bash.d.ts +0 -6
- package/dist/opencode/tools/edit.d.ts +0 -19
- package/dist/opencode/tools/glob.d.ts +0 -9
- package/dist/opencode/tools/grep.d.ts +0 -17
- package/dist/opencode/tools/index.d.ts +0 -12
- package/dist/opencode/tools/question.d.ts +0 -5
- package/dist/opencode/tools/read.d.ts +0 -16
- package/dist/opencode/tools/skill.d.ts +0 -18
- package/dist/opencode/tools/web.d.ts +0 -18
- package/dist/opencode/tools/write.d.ts +0 -13
- package/dist/opencode/types.d.ts +0 -90
- package/dist/opencode/ws.d.ts +0 -21
- package/dist/permissions.d.ts +0 -51
- package/dist/postgres/client.d.ts +0 -4
- package/dist/postgres/index.d.ts +0 -4
- package/dist/postgres/module.d.ts +0 -17
- package/dist/postgres/schema/columns.d.ts +0 -99
- package/dist/postgres/schema/index.d.ts +0 -6
- package/dist/postgres/schema/sql.d.ts +0 -22
- package/dist/postgres/schema/table.d.ts +0 -141
- package/dist/postgres/schema/where.d.ts +0 -29
- package/dist/postgres/types.d.ts +0 -50
- package/dist/queue/index.d.ts +0 -2
- package/dist/queue/types.d.ts +0 -62
- package/dist/rate-limit.d.ts +0 -45
- package/dist/react.d.ts +0 -14
- package/dist/react.js +0 -751
- package/dist/redis/client.d.ts +0 -2
- package/dist/redis/types.d.ts +0 -18
- package/dist/request-id.d.ts +0 -40
- package/dist/router.d.ts +0 -73
- package/dist/s3.d.ts +0 -68
- package/dist/seo.d.ts +0 -104
- package/dist/serve.d.ts +0 -38
- package/dist/server-registry.d.ts +0 -10
- package/dist/session.d.ts +0 -117
- package/dist/sse.d.ts +0 -47
- package/dist/ssr-entries.d.ts +0 -4
- package/dist/ssr.d.ts +0 -11
- package/dist/static.d.ts +0 -23
- package/dist/stream.d.ts +0 -24
- package/dist/tailwind.d.ts +0 -15
- package/dist/tenant/client.d.ts +0 -2
- package/dist/tenant/graphql.d.ts +0 -3
- package/dist/tenant/index.d.ts +0 -2
- package/dist/tenant/rest.d.ts +0 -3
- package/dist/tenant/schema.d.ts +0 -5
- package/dist/tenant/types.d.ts +0 -48
- package/dist/tenant/utils.d.ts +0 -9
- package/dist/test-utils.d.ts +0 -194
- package/dist/theme.d.ts +0 -31
- package/dist/trace.d.ts +0 -95
- package/dist/tsx-context.d.ts +0 -32
- package/dist/types.d.ts +0 -47
- package/dist/upload.d.ts +0 -55
- package/dist/use-action.d.ts +0 -42
- package/dist/use-agent-stream.d.ts +0 -49
- package/dist/use-flash-message.d.ts +0 -17
- package/dist/use-websocket.d.ts +0 -42
- package/dist/user/client.d.ts +0 -30
- package/dist/user/index.d.ts +0 -2
- package/dist/user/oauth-login.d.ts +0 -21
- package/dist/user/oauth2.d.ts +0 -31
- package/dist/user/types.d.ts +0 -178
- package/dist/validate.d.ts +0 -32
- package/dist/vendor.d.ts +0 -7
- package/dist/webhook.d.ts +0 -79
- package/opencode/ui/app/globals.css +0 -1
- package/opencode/ui/app/layout.tsx +0 -13
- package/opencode/ui/app/page.tsx +0 -523
package/core/cookie.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** Options for setting a cookie. All fields map to standard Set-Cookie attributes. */
|
|
2
|
+
export interface CookieOptions {
|
|
3
|
+
domain?: string
|
|
4
|
+
path?: string
|
|
5
|
+
maxAge?: number
|
|
6
|
+
expires?: Date
|
|
7
|
+
httpOnly?: boolean
|
|
8
|
+
secure?: boolean
|
|
9
|
+
sameSite?: 'strict' | 'lax' | 'none'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Parse cookies from a Request's `Cookie` header.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const cookies = getCookies(req)
|
|
17
|
+
* console.log(cookies.session_id) // value or undefined
|
|
18
|
+
* ``` */
|
|
19
|
+
export function getCookies(req: Request): Record<string, string> {
|
|
20
|
+
const header = req.headers.get('cookie')
|
|
21
|
+
if (!header) return {}
|
|
22
|
+
|
|
23
|
+
const cookies: Record<string, string> = {}
|
|
24
|
+
for (const pair of header.split(';')) {
|
|
25
|
+
const idx = pair.indexOf('=')
|
|
26
|
+
if (idx === -1) continue
|
|
27
|
+
let name = pair.slice(0, idx).trim()
|
|
28
|
+
let value = pair.slice(idx + 1).trim()
|
|
29
|
+
if (!name) continue
|
|
30
|
+
// Decode cookie name (consistent with value)
|
|
31
|
+
try {
|
|
32
|
+
name = decodeURIComponent(name)
|
|
33
|
+
} catch {}
|
|
34
|
+
// Strip surrounding quotes from value (RFC 6265)
|
|
35
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
36
|
+
value = value.slice(1, -1)
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
cookies[name] = decodeURIComponent(value)
|
|
40
|
+
} catch {
|
|
41
|
+
cookies[name] = value
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return cookies
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function serializeCookie(name: string, value: string, options?: CookieOptions): string {
|
|
48
|
+
// Reject control characters and special chars per RFC 6265
|
|
49
|
+
// eslint-disable-next-line no-control-regex
|
|
50
|
+
if (/[\x00-\x1F\x7F-\x9F;,]/.test(name) || /[\x00-\x1F\x7F-\x9F;,]/.test(value)) {
|
|
51
|
+
throw new Error(`Invalid cookie name or value: contains control characters or special chars`)
|
|
52
|
+
}
|
|
53
|
+
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`]
|
|
54
|
+
if (options?.maxAge != null) parts.push(`Max-Age=${options.maxAge}`)
|
|
55
|
+
if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`)
|
|
56
|
+
if (options?.domain) parts.push(`Domain=${options.domain}`)
|
|
57
|
+
if (options?.path) parts.push(`Path=${options.path}`)
|
|
58
|
+
if (options?.httpOnly) parts.push('HttpOnly')
|
|
59
|
+
if (options?.secure) parts.push('Secure')
|
|
60
|
+
if (options?.sameSite) parts.push(`SameSite=${options.sameSite}`)
|
|
61
|
+
return parts.join('; ')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Set a cookie on a Response.
|
|
65
|
+
*
|
|
66
|
+
* Appends a `Set-Cookie` header to the existing response headers.
|
|
67
|
+
* Returns a new Response with the added header.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const res = new Response('ok')
|
|
72
|
+
* return setCookie(res, 'session', token, { httpOnly: true, path: '/' })
|
|
73
|
+
* ``` */
|
|
74
|
+
export function setCookie(
|
|
75
|
+
res: Response,
|
|
76
|
+
name: string,
|
|
77
|
+
value: string,
|
|
78
|
+
options?: CookieOptions,
|
|
79
|
+
): Response {
|
|
80
|
+
const headers = new Headers(res.headers)
|
|
81
|
+
headers.append('Set-Cookie', serializeCookie(name, value, options))
|
|
82
|
+
return new Response(res.body, {
|
|
83
|
+
status: res.status,
|
|
84
|
+
statusText: res.statusText,
|
|
85
|
+
headers,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Delete a cookie by setting `Max-Age=0`.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* return deleteCookie(res, 'session')
|
|
94
|
+
* ``` */
|
|
95
|
+
export function deleteCookie(
|
|
96
|
+
res: Response,
|
|
97
|
+
name: string,
|
|
98
|
+
options?: Omit<CookieOptions, 'maxAge'>,
|
|
99
|
+
): Response {
|
|
100
|
+
const headers = new Headers(res.headers)
|
|
101
|
+
headers.append(
|
|
102
|
+
'Set-Cookie',
|
|
103
|
+
serializeCookie(name, '', {
|
|
104
|
+
...options,
|
|
105
|
+
maxAge: 0,
|
|
106
|
+
expires: new Date(0),
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
return new Response(res.body, {
|
|
110
|
+
status: res.status,
|
|
111
|
+
statusText: res.statusText,
|
|
112
|
+
headers,
|
|
113
|
+
})
|
|
114
|
+
}
|
package/core/env.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import type { Context, Middleware } from '../types.ts'
|
|
4
|
+
|
|
5
|
+
/** Build-time injection from esbuild --define. `true` in dist/index.js, undefined in TS source. */
|
|
6
|
+
declare let __WFW_BUNDLED__: boolean | undefined
|
|
7
|
+
|
|
8
|
+
const PUBLIC_PREFIX = 'WEIFUWU_PUBLIC_'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get all public environment variables (those prefixed with `WEIFUWU_PUBLIC_`),
|
|
12
|
+
* with the prefix stripped.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* const pub = getPublicEnv()
|
|
16
|
+
* // WEIFUWU_PUBLIC_API_URL=http://api.example.com → { API_URL: 'http://api.example.com' }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function getPublicEnv(): Record<string, string> {
|
|
20
|
+
const result: Record<string, string> = {}
|
|
21
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
22
|
+
if (key.startsWith(PUBLIC_PREFIX) && value !== undefined) {
|
|
23
|
+
result[key.slice(PUBLIC_PREFIX.length)] = value
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Augment Context with env property
|
|
30
|
+
declare module '../types.ts' {
|
|
31
|
+
interface Context {
|
|
32
|
+
env?: Record<string, string>
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether this code is running from the compiled `dist/index.js` bundle.
|
|
38
|
+
* `false` when running TypeScript source directly (dev workflow in weifuwu repo).
|
|
39
|
+
*
|
|
40
|
+
* Used by modules that need to resolve package-internal files differently
|
|
41
|
+
* depending on whether they are compiled (published npm package) or raw TS.
|
|
42
|
+
*/
|
|
43
|
+
export function isBundled(): boolean {
|
|
44
|
+
return typeof __WFW_BUNDLED__ !== 'undefined' ? __WFW_BUNDLED__ : false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether `NODE_ENV` is explicitly set to `'development'`.
|
|
49
|
+
*
|
|
50
|
+
* Used for dev-only features: HMR, livereload, React `createRoot` (not hydrate).
|
|
51
|
+
* **Not** the opposite of {@link isProd} — when `NODE_ENV` is unset, both return `false`.
|
|
52
|
+
*/
|
|
53
|
+
export function isDev(): boolean {
|
|
54
|
+
const env = process.env.NODE_ENV
|
|
55
|
+
return env !== 'production' && env !== 'test'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether `NODE_ENV` is explicitly set to `'production'`.
|
|
60
|
+
*
|
|
61
|
+
* Used for production-only behavior: plain-text 404, suppressed warnings, minified output.
|
|
62
|
+
*/
|
|
63
|
+
export function isProd(): boolean {
|
|
64
|
+
return process.env.NODE_ENV === 'production'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Load environment variables from a `.env` file into `process.env`.
|
|
69
|
+
*
|
|
70
|
+
* Does **not** override existing `process.env` values.
|
|
71
|
+
* Supports quoted values and inline comments.
|
|
72
|
+
*
|
|
73
|
+
* @param path - Path to `.env` file (default: `'.env'` relative to cwd).
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* import { loadEnv } from 'weifuwu'
|
|
77
|
+
* loadEnv()
|
|
78
|
+
* console.log(process.env.PORT)
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function loadEnv(path?: string): void {
|
|
82
|
+
const filePath = resolve(process.cwd(), path ?? '.env')
|
|
83
|
+
|
|
84
|
+
let content: string
|
|
85
|
+
try {
|
|
86
|
+
content = readFileSync(filePath, 'utf-8')
|
|
87
|
+
} catch {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const line of content.split('\n')) {
|
|
92
|
+
const trimmed = line.trim()
|
|
93
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
94
|
+
|
|
95
|
+
const eqIdx = trimmed.indexOf('=')
|
|
96
|
+
if (eqIdx === -1) continue
|
|
97
|
+
|
|
98
|
+
const key = trimmed.slice(0, eqIdx).trim()
|
|
99
|
+
if (!key) continue
|
|
100
|
+
|
|
101
|
+
if (process.env[key] !== undefined) continue
|
|
102
|
+
|
|
103
|
+
let value = trimmed.slice(eqIdx + 1).trim()
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
107
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
108
|
+
) {
|
|
109
|
+
value = value.slice(1, -1)
|
|
110
|
+
} else {
|
|
111
|
+
// Strip inline comments: space before #, or # at start of value
|
|
112
|
+
const commentIdx = value.search(/\s#/)
|
|
113
|
+
if (commentIdx !== -1) {
|
|
114
|
+
value = value.slice(0, commentIdx).trimEnd()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
process.env[key] = value
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Public env middleware.
|
|
124
|
+
*
|
|
125
|
+
* Injects `ctx.env` with all environment variables prefixed with `WEIFUWU_PUBLIC_`,
|
|
126
|
+
* with the prefix stripped. Safe to expose to the client.
|
|
127
|
+
*
|
|
128
|
+
* ```ts
|
|
129
|
+
* import { env } from 'weifuwu'
|
|
130
|
+
* app.use(env())
|
|
131
|
+
*
|
|
132
|
+
* // .env: WEIFUWU_PUBLIC_API_URL=https://api.example.com
|
|
133
|
+
* // ctx: ctx.env.API_URL === 'https://api.example.com'
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function env(): Middleware<Context, Context & { env: Record<string, string> }> {
|
|
137
|
+
const entries = getPublicEnv()
|
|
138
|
+
return async (req, ctx, next) => {
|
|
139
|
+
;(ctx as Context & { env: Record<string, string> }).env = entries
|
|
140
|
+
return next(req, ctx as Context & { env: Record<string, string> })
|
|
141
|
+
}
|
|
142
|
+
}
|
package/core/logger.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import type { Middleware, Context } from '../types.ts'
|
|
3
|
+
import { currentTraceId } from './trace.ts'
|
|
4
|
+
|
|
5
|
+
export interface LoggerOptions {
|
|
6
|
+
/** 'short' = method + path + status + ms, 'combined' = short + query string, 'json' = structured stderr JSON */
|
|
7
|
+
format?: 'short' | 'combined' | 'json'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface LogEvent {
|
|
11
|
+
level: 'info' | 'warn' | 'error'
|
|
12
|
+
message: string
|
|
13
|
+
method?: string
|
|
14
|
+
path?: string
|
|
15
|
+
status?: number
|
|
16
|
+
elapsed_ms?: number
|
|
17
|
+
traceId?: string
|
|
18
|
+
timestamp?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function emit(event: LogEvent): void {
|
|
22
|
+
event.traceId = event.traceId ?? currentTraceId()
|
|
23
|
+
event.timestamp = new Date().toISOString()
|
|
24
|
+
process.stderr.write(JSON.stringify(event) + '\n')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function logger(options?: LoggerOptions): Middleware<Context, Context> {
|
|
28
|
+
const format = options?.format ?? 'short'
|
|
29
|
+
|
|
30
|
+
return async (req, ctx, next) => {
|
|
31
|
+
const start = Date.now()
|
|
32
|
+
const url = new URL(req.url)
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const res = await next(req, ctx)
|
|
36
|
+
const ms = Date.now() - start
|
|
37
|
+
const pathAndQuery = format === 'combined' ? url.pathname + url.search : url.pathname
|
|
38
|
+
|
|
39
|
+
if (format === 'json') {
|
|
40
|
+
emit({
|
|
41
|
+
level: 'info',
|
|
42
|
+
message: 'request',
|
|
43
|
+
method: req.method,
|
|
44
|
+
path: pathAndQuery,
|
|
45
|
+
status: res.status,
|
|
46
|
+
elapsed_ms: ms,
|
|
47
|
+
})
|
|
48
|
+
} else {
|
|
49
|
+
console.log(`${req.method} ${pathAndQuery} ${res.status} ${ms}ms`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return res
|
|
53
|
+
} catch (err) {
|
|
54
|
+
const ms = Date.now() - start
|
|
55
|
+
const pathAndQuery = format === 'combined' ? url.pathname + url.search : url.pathname
|
|
56
|
+
|
|
57
|
+
if (format === 'json') {
|
|
58
|
+
emit({
|
|
59
|
+
level: 'error',
|
|
60
|
+
message: err instanceof Error ? err.message : String(err),
|
|
61
|
+
method: req.method,
|
|
62
|
+
path: pathAndQuery,
|
|
63
|
+
status: 500,
|
|
64
|
+
elapsed_ms: ms,
|
|
65
|
+
})
|
|
66
|
+
} else {
|
|
67
|
+
console.log(`${req.method} ${pathAndQuery} 500 ${ms}ms`)
|
|
68
|
+
}
|
|
69
|
+
throw err
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|