duto 0.0.0 → 0.260906.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/LICENSE +21 -0
- package/README.md +21 -12
- package/package.json +91 -2
- package/src/cli.ts +42 -0
- package/src/enum.ts +2 -0
- package/src/index.d.ts +61 -0
- package/src/index.ts +19 -0
- package/src/middleware.ts +2 -0
- package/src/pages/client/app.tsx +123 -0
- package/src/pages/client/hmr.ts +13 -0
- package/src/pages/client/island.ts +136 -0
- package/src/pages/client/link.ts +70 -0
- package/src/pages/client/router.ts +329 -0
- package/src/pages/client/storage.ts +95 -0
- package/src/pages/commands/build.ts +74 -0
- package/src/pages/commands/dev-old.ts +213 -0
- package/src/pages/commands/dev.ts +87 -0
- package/src/pages/commands/preview.ts +33 -0
- package/src/pages/compiler/aliases.ts +36 -0
- package/src/pages/compiler/assets.ts +43 -0
- package/src/pages/compiler/ast.ts +764 -0
- package/src/pages/compiler/constants.ts +24 -0
- package/src/pages/compiler/file.ts +60 -0
- package/src/pages/compiler/index.ts +307 -0
- package/src/pages/compiler/island.ts +108 -0
- package/src/pages/compiler/meta.ts +601 -0
- package/src/pages/compiler/routes.ts +29 -0
- package/src/pages/compiler/types.ts +209 -0
- package/src/pages/compiler/utils.ts +5 -0
- package/src/pages/compiler/vite-plugin.ts +324 -0
- package/src/pages/components/link.tsx +38 -0
- package/src/pages/i18n/client.ts +403 -0
- package/src/pages/i18n/element.ts +44 -0
- package/src/pages/i18n/server.ts +56 -0
- package/src/pages/index.ts +28 -0
- package/src/pages/preact/client-dev.ts +5 -0
- package/src/pages/preact/client.ts +87 -0
- package/src/pages/preact/context.ts +41 -0
- package/src/pages/preact/server.ts +186 -0
- package/src/pages/preact/signals.ts +89 -0
- package/src/pages/preact/static-html.ts +45 -0
- package/src/pages/preact/types.ts +26 -0
- package/src/server/adapter/aws.ts +4 -0
- package/src/server/adapter/workerd.ts +4 -0
- package/src/server/app.ts +96 -0
- package/src/server/auth/ability.ts +72 -0
- package/src/server/auth/authnz.ts +86 -0
- package/src/server/auth/index.ts +5 -0
- package/src/server/auth/token.ts +147 -0
- package/src/server/auth/types.ts +2 -0
- package/src/server/cli/commands/build.ts +41 -0
- package/src/server/cli/commands/deploy.ts +82 -0
- package/src/server/cli/commands/dev.ts +277 -0
- package/src/server/cli/commands/make.ts +98 -0
- package/src/server/cli/commands/migrate.ts +93 -0
- package/src/server/cli/commands/routes.ts +79 -0
- package/src/server/cli/index.ts +27 -0
- package/src/server/cli/stubs.ts +39 -0
- package/src/server/cli/types.ts +3 -0
- package/src/server/cli/utils.ts +443 -0
- package/src/server/config.ts +4 -0
- package/src/server/db/d1.d.ts +1 -0
- package/src/server/db/d1.ts +1 -0
- package/src/server/db/dynamodb.d.ts +1 -0
- package/src/server/db/dynamodb.ts +1 -0
- package/src/server/db/index.ts +1 -0
- package/src/server/entry.ts +19 -0
- package/src/server/handle.ts +30 -0
- package/src/server/http.ts +23 -0
- package/src/server/middlewares/auth.ts +40 -0
- package/src/server/middlewares/logger.ts +30 -0
- package/src/server/open-api/register.ts +29 -0
- package/src/server/open-api/spec.ts +107 -0
- package/src/server/request.ts +233 -0
- package/src/server/response.ts +142 -0
- package/src/server/routes.ts +474 -0
- package/src/server/types.ts +142 -0
- package/src/server/utils/environment.ts +17 -0
- package/src/server/utils.ts +46 -0
- package/src/server/validator.ts +70 -0
- package/src/utils.ts +5 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Config from '$/config'
|
|
2
|
+
import { registerOpenAPI } from '$/open-api/register'
|
|
3
|
+
import { Ability } from '$/auth'
|
|
4
|
+
import $ from '$/app'
|
|
5
|
+
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import '.duto/imports.mjs'
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { routes, middlewares } from '.duto/imports.mjs'
|
|
10
|
+
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
Ability.fromRoutes(routes)
|
|
13
|
+
Ability.roles = Config.get('roles', {})
|
|
14
|
+
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
const app = $({ routes, middlewares })
|
|
17
|
+
registerOpenAPI(app, Config.get('duto.docs', {}))
|
|
18
|
+
|
|
19
|
+
export { app }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import res from './response'
|
|
2
|
+
import $v from './validator'
|
|
3
|
+
import { GET_REQUEST } from './request'
|
|
4
|
+
import type { Context, Next, RuleFactory } from './types'
|
|
5
|
+
|
|
6
|
+
export const _mw: Record<string, Function> = {}
|
|
7
|
+
export const getMiddlewares = () => _mw
|
|
8
|
+
export function getMiddleware(id: string | Function): Function {
|
|
9
|
+
if (typeof id === 'function') return id
|
|
10
|
+
const mw = _mw[id] || null
|
|
11
|
+
if (!mw) throw new Error(`Handler ${id} not registered`)
|
|
12
|
+
return mw
|
|
13
|
+
}
|
|
14
|
+
export function registerMiddleware(id: string, handler: Function) {
|
|
15
|
+
_mw[id] = handler
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const resolveMw = (mw: Function) => {
|
|
19
|
+
return mw.length < 3 ? mw : async (c: Context, next: Next) => await getMiddleware(mw)(c.get(GET_REQUEST as unknown as string), res, next, c)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const resolveHandle = (mw: (string | Function)[], handle: Function, ruleFn?: RuleFactory) => {
|
|
23
|
+
const h = async (c: Context) => await handle(c.get(GET_REQUEST as unknown as string), res)
|
|
24
|
+
mw = mw?.length ? mw.map(getMiddleware) : []
|
|
25
|
+
|
|
26
|
+
const rules = typeof ruleFn === 'function' ? ruleFn($v) : null
|
|
27
|
+
if (!rules) return [...mw, h]
|
|
28
|
+
|
|
29
|
+
return [...mw, ...$v.parse(rules), h]
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const verbAlias = {
|
|
2
|
+
get: 0,
|
|
3
|
+
post: 1,
|
|
4
|
+
put: 2,
|
|
5
|
+
patch: 3,
|
|
6
|
+
delete: 4,
|
|
7
|
+
head: 5,
|
|
8
|
+
options: 6,
|
|
9
|
+
connect: 7,
|
|
10
|
+
trace: 8,
|
|
11
|
+
} as Record<string, number>
|
|
12
|
+
|
|
13
|
+
export const getVerb = [
|
|
14
|
+
'get',
|
|
15
|
+
'post',
|
|
16
|
+
'put',
|
|
17
|
+
'patch',
|
|
18
|
+
'delete',
|
|
19
|
+
'head',
|
|
20
|
+
'options',
|
|
21
|
+
'connect',
|
|
22
|
+
'trace',
|
|
23
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Ability } from '$/auth'
|
|
2
|
+
import type { Req, Res, Next } from '@/.'
|
|
3
|
+
|
|
4
|
+
import { RegExpRouter } from 'hono/router/reg-exp-router'
|
|
5
|
+
import { SmartRouter } from 'hono/router/smart-router'
|
|
6
|
+
import { TrieRouter } from 'hono/router/trie-router'
|
|
7
|
+
|
|
8
|
+
let used = false
|
|
9
|
+
export const useAuth = () => used
|
|
10
|
+
|
|
11
|
+
const whiteList = new SmartRouter({
|
|
12
|
+
routers: [new RegExpRouter(), new TrieRouter()],
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export function autorizedPath(path: string, method = 'ALL'){
|
|
16
|
+
return used ? whiteList.match(method, path)[0][0]?.[0] !== undefined : true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function auth(publicPaths: (string | [string, string])[]) {
|
|
20
|
+
if (!used) {
|
|
21
|
+
used = true
|
|
22
|
+
for (const route of publicPaths) {
|
|
23
|
+
const str = typeof route === 'string'
|
|
24
|
+
const path = str ? route : route[1]
|
|
25
|
+
|
|
26
|
+
whiteList.add(str ? 'ALL' : route[0], path, path)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return async (req: Req, res: Res, next: Next) => {
|
|
31
|
+
if (!autorizedPath(req.path, req.method)) {
|
|
32
|
+
const ability = Ability.fromRequest(req)
|
|
33
|
+
|
|
34
|
+
if (!req?.user || !ability || req.cant(ability))
|
|
35
|
+
return res.unauthorized()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await next()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { logger } from 'hono/logger'
|
|
2
|
+
import { matchedRoutes } from 'hono/route'
|
|
3
|
+
import { Datte } from 't0n'
|
|
4
|
+
import { gray } from 't0n/color'
|
|
5
|
+
import type { Req, Res, Context, Next } from '@/.'
|
|
6
|
+
|
|
7
|
+
export const path = '*'
|
|
8
|
+
export async function handle(req: Req, res: Res, next: Next, c: Context) {
|
|
9
|
+
const method = c.req.method
|
|
10
|
+
const route = matchedRoutes(c).find(route => route.method === method)?.path
|
|
11
|
+
const logWithRoute = (args: string[]) => {
|
|
12
|
+
if (!route || !args.length) return args
|
|
13
|
+
return args.map(arg => {
|
|
14
|
+
if (!arg) return arg
|
|
15
|
+
const split = arg?.split(' ')
|
|
16
|
+
if (split.length < 3 || split[2] === route)
|
|
17
|
+
return arg
|
|
18
|
+
|
|
19
|
+
split.splice(Math.min(3, split.length), 0, gray(route))
|
|
20
|
+
return split.join(' ')
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const devLogger = logger((...args: any[]) => {
|
|
25
|
+
const timestamp = gray(Datte.dateTime())
|
|
26
|
+
console.log(timestamp, ...logWithRoute(args))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
await devLogger(c, next)
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { basicAuth } from 'hono/basic-auth'
|
|
2
|
+
import response from '$/response'
|
|
3
|
+
import Config from '$/config'
|
|
4
|
+
import type { Hono } from '$/types'
|
|
5
|
+
|
|
6
|
+
export function registerOpenAPI(app: Hono, opts: any) {
|
|
7
|
+
if (!opts.path) return
|
|
8
|
+
|
|
9
|
+
if (opts?.auth?.username && opts?.auth?.password) {
|
|
10
|
+
app.use(opts.path +'/*', async (c, next) => {
|
|
11
|
+
const realm = opts.auth?.realm || 'Docs'
|
|
12
|
+
const unauthorized = response.unauthorized(
|
|
13
|
+
null,
|
|
14
|
+
{'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
|
|
15
|
+
)
|
|
16
|
+
if (!c.req.raw.headers.get('Authorization')) return unauthorized
|
|
17
|
+
const auth = basicAuth({ username: opts.auth.username, password: opts.auth.password, realm })
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await auth(c, next)
|
|
21
|
+
} catch {
|
|
22
|
+
return unauthorized
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
app.get(opts.path, async () => response.html(Config.get('DUTO_OAC')))
|
|
28
|
+
app.get(opts.path +'/openapi', async () => response.raw(200, Config.get('DUTO_OAS'), 'application/json'))
|
|
29
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { STATUS_CODES } from 'node:http'
|
|
2
|
+
import { generateSpecs, resolver } from 'hono-openapi'
|
|
3
|
+
import { array, object, string } from 'zod/mini'
|
|
4
|
+
import { Envir } from 't0n'
|
|
5
|
+
import { Scalar } from '@scalar/hono-api-reference'
|
|
6
|
+
import { minify } from '@minify-html/node'
|
|
7
|
+
import type { Hono } from '$/types'
|
|
8
|
+
import { useAuth } from '$/middlewares/auth'
|
|
9
|
+
|
|
10
|
+
export function openAPIConfig(opts: any) {
|
|
11
|
+
const {
|
|
12
|
+
disable = false,
|
|
13
|
+
path = '/docs',
|
|
14
|
+
agent = false,
|
|
15
|
+
name = '',
|
|
16
|
+
title = '',
|
|
17
|
+
version = Envir.get('APP_VERSION') || Envir.get('VERSION_HASH') || '1.0.0',
|
|
18
|
+
description = Envir.get('APP_DESCRIPTION', ''),
|
|
19
|
+
auth = {},
|
|
20
|
+
...docs
|
|
21
|
+
} = opts?.docs ?? {}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
disable,
|
|
25
|
+
path,
|
|
26
|
+
auth,
|
|
27
|
+
agent: !!agent,
|
|
28
|
+
name: name || title || Envir.get('APP_NAME', 'API Docs'),
|
|
29
|
+
version,
|
|
30
|
+
desc: description,
|
|
31
|
+
...docs,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function generateOpenAPI(app: Hono, opts?: any) {
|
|
36
|
+
return await generateSpecs(app, {
|
|
37
|
+
documentation: {
|
|
38
|
+
info: {
|
|
39
|
+
title: opts.name,
|
|
40
|
+
version: opts.version,
|
|
41
|
+
description: opts.desc,
|
|
42
|
+
contact: opts.contact,
|
|
43
|
+
termsOfService: opts.terms || opts.termsOfService,
|
|
44
|
+
license: opts.license,
|
|
45
|
+
},
|
|
46
|
+
externalDocs: opts.externalDocs,
|
|
47
|
+
servers: opts.servers,
|
|
48
|
+
security: useAuth() ? [{'JWT':[]}] : opts.security,
|
|
49
|
+
tags: opts.tags,
|
|
50
|
+
components: {
|
|
51
|
+
...(useAuth() ? { securitySchemes: {
|
|
52
|
+
JWT: {
|
|
53
|
+
type: 'http',
|
|
54
|
+
scheme: 'bearer',
|
|
55
|
+
bearerFormat: 'JWT',
|
|
56
|
+
},
|
|
57
|
+
} } : {}),
|
|
58
|
+
responses: {
|
|
59
|
+
500: {
|
|
60
|
+
description: STATUS_CODES[500],
|
|
61
|
+
content: { // @ts-ignore
|
|
62
|
+
'application/json': await resolver(object({
|
|
63
|
+
m: array(string()),
|
|
64
|
+
})).toOpenAPISchema(),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
...opts?.responses,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function generateOpenAPIClient(app: Hono, opts?: any) {
|
|
75
|
+
const customCss = `[href="https://www.scalar.com"],.scalar-mcp-layer{display:none!important}`
|
|
76
|
+
app.get(
|
|
77
|
+
opts.path,
|
|
78
|
+
Scalar({
|
|
79
|
+
theme: 'saturn',
|
|
80
|
+
url: opts.path +'/openapi',
|
|
81
|
+
showDeveloperTools: 'never',
|
|
82
|
+
telemetry: false,
|
|
83
|
+
documentDownloadType: 'json', //'direct',
|
|
84
|
+
isLoading: true,
|
|
85
|
+
persistAuth: true,
|
|
86
|
+
hideClientButton: true,
|
|
87
|
+
pageTitle: opts.name,
|
|
88
|
+
agent: { disabled: !opts.agent },
|
|
89
|
+
slug: opts.name?.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s_-]/g, '').replace(/[\s_-]+/g, '_').replace(/[^\x00-\x7F]/g, '') +'_'+ opts.version,
|
|
90
|
+
customCss,
|
|
91
|
+
// onLoaded: () => document?.querySelectorAll('[href="https://www.scalar.com"]')?.forEach(el => el.remove()),
|
|
92
|
+
// favicon: 'https://example.com/favicon.png',
|
|
93
|
+
// hideDownloadButton: true,
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const html = await (await app.request('/docs')).text()
|
|
98
|
+
const result = minify(Buffer.from(html), {
|
|
99
|
+
minify_css: true,
|
|
100
|
+
minify_js: true,
|
|
101
|
+
keep_comments: false,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return result.toString()
|
|
105
|
+
.replace(',customCss:`' + customCss + '`', '')
|
|
106
|
+
.replace('_integration:`hono`,', '')
|
|
107
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { getCookie, getSignedCookie, setCookie, setSignedCookie, deleteCookie } from 'hono/cookie'
|
|
2
|
+
import { HTTPException } from 'hono/http-exception'
|
|
3
|
+
import { Authnz, Token } from './auth'
|
|
4
|
+
import { Envir } from 't0n'
|
|
5
|
+
|
|
6
|
+
import type { Context } from 'hono'
|
|
7
|
+
import { routePath, matchedRoutes } from 'hono/route'
|
|
8
|
+
import type { RouterRoute } from 'hono/types'
|
|
9
|
+
import type { CookieOptions, CookiePrefixOptions } from 'hono/utils/cookie'
|
|
10
|
+
import type { CustomHeader, RequestHeader } from 'hono/utils/headers'
|
|
11
|
+
import type { BodyData, ParseBodyOptions } from 'hono/utils/body'
|
|
12
|
+
|
|
13
|
+
const cookieWrapper = (c: Context) => ({
|
|
14
|
+
all: () => getCookie(c),
|
|
15
|
+
allSigned: (secret: string) => getSignedCookie(c, secret),
|
|
16
|
+
get: (name: string, prefixOptions?: CookiePrefixOptions) => prefixOptions ? getCookie(c, name, prefixOptions) : getCookie(c, name),
|
|
17
|
+
getSigned: (secret: string, name: string, prefixOptions?: CookiePrefixOptions) => prefixOptions ? getSignedCookie(c, secret, name, prefixOptions) : getSignedCookie(c, secret, name),
|
|
18
|
+
set: (name: string, value: string, opt?: CookieOptions) => setCookie(c, name, value, opt),
|
|
19
|
+
setSigned: (name: string, value: string, secret: string, opt?: CookieOptions) => setSignedCookie(c, name, value, secret, opt),
|
|
20
|
+
delete: (name: string, opt?: CookieOptions) => deleteCookie(c, name, opt)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export const GET_REQUEST: unique symbol = Symbol()
|
|
24
|
+
|
|
25
|
+
export default class $Request {
|
|
26
|
+
#c!: Context
|
|
27
|
+
#cookie: ReturnType<typeof cookieWrapper>
|
|
28
|
+
#u: Authnz<any> | null = null
|
|
29
|
+
|
|
30
|
+
#host: string
|
|
31
|
+
#protocol: string
|
|
32
|
+
#routePath?: string
|
|
33
|
+
#matchedRoutes: RouterRoute[]
|
|
34
|
+
|
|
35
|
+
constructor(c: Context) {
|
|
36
|
+
this.#c = c
|
|
37
|
+
this.#cookie = cookieWrapper(c)
|
|
38
|
+
|
|
39
|
+
const url = new URL(c.req.raw.url)
|
|
40
|
+
this.#host = url.protocol +'//'+ url.host
|
|
41
|
+
this.#protocol = url.protocol
|
|
42
|
+
|
|
43
|
+
this.#matchedRoutes = matchedRoutes(c)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static async toContext(c: Context) {
|
|
47
|
+
const req = new $Request(c)
|
|
48
|
+
await req.setAuth()
|
|
49
|
+
c.set(GET_REQUEST as unknown as string, req)
|
|
50
|
+
if (c.env) Envir.add(c.env)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async setAuth() {
|
|
54
|
+
this.#u = Authnz.fromToken(Token.fromRequest(this))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get user() {
|
|
58
|
+
return this.#u ? this.#u?.data : null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get auth() {
|
|
62
|
+
return this.#u
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
can(...abilities: string[]) {
|
|
66
|
+
return this.#u ? this.#u.can(...abilities) : false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cant(...abilities: string[]) {
|
|
70
|
+
return !this.can(...abilities)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
hasRole(...roles: string[]) {
|
|
74
|
+
return this.#u ? this.#u.hasRole(...roles) : false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// TODO: fazer funcionar dnv..
|
|
78
|
+
// has(prop: string, value: any = null) {
|
|
79
|
+
// return this.#u ? this.#u.has(prop, value) : false
|
|
80
|
+
// }
|
|
81
|
+
// hasValue(prop: string, value: any = null) {
|
|
82
|
+
// return this.has(prop, value)
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
// createToken(data: any, exp: number) {
|
|
86
|
+
// return Token.create(this, data, exp)
|
|
87
|
+
// }
|
|
88
|
+
// parseToken(token: string) {
|
|
89
|
+
// return Token.parse(this, token)
|
|
90
|
+
// }
|
|
91
|
+
|
|
92
|
+
get cx() {
|
|
93
|
+
return this.#c
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get cookie() {
|
|
97
|
+
return this.#cookie
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get ip(): string | undefined {
|
|
101
|
+
return this.#c.req.header('cf-connecting-ip') // cf
|
|
102
|
+
|| this.#c.req.header('x-forwarded-for')?.split(',').at(-1)?.trim() // aws lambda
|
|
103
|
+
|| this.#c.env?.aws?.lambda?.event?.requestContext?.identity?.sourceIp
|
|
104
|
+
|| this.#c.env?.event?.Records[0]?.cf?.request?.clientIp // aws lambda@edge
|
|
105
|
+
|| this.#c.req.header('x-real-ip') // vercel
|
|
106
|
+
|| this.#c.env?.context?.ip // netlify
|
|
107
|
+
|| this.#c.env?.remoteAddr?.hostname // deno
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get userAgent(): string | undefined {
|
|
111
|
+
return this.#c.req.header('user-agent')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get protocol() {
|
|
115
|
+
return this.#protocol
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get url() {
|
|
119
|
+
return this.#c.req.raw.url
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get host() {
|
|
123
|
+
return this.#host
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get path() {
|
|
127
|
+
return this.#c.req.path
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get fullPath() {
|
|
131
|
+
const url = this.url
|
|
132
|
+
return url.slice(url.indexOf('/', 8))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get method() {
|
|
136
|
+
return this.#c.req.raw.method
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get routePath() {
|
|
140
|
+
this.#routePath ??= routePath(this.#c)
|
|
141
|
+
return this.#routePath
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get routeIndex() {
|
|
145
|
+
return this.#c.req.routeIndex
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get matchedRoutes() {
|
|
149
|
+
return this.#matchedRoutes
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get raw() {
|
|
153
|
+
return this.#c.req.raw
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
header(name: RequestHeader): string | undefined
|
|
157
|
+
header(name: string): string | undefined
|
|
158
|
+
header(): Record<RequestHeader | (string & CustomHeader), string>
|
|
159
|
+
header(name?: string) { // @ts-ignore
|
|
160
|
+
return this.#c.req.header(name)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
param(key?: string) { // @ts-ignore
|
|
164
|
+
return this.#c.req.param(key)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
query(): Record<string, string>
|
|
168
|
+
query(key: string): string | undefined
|
|
169
|
+
query(key?: string) { // @ts-ignore
|
|
170
|
+
return this.#c.req.query(key)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
queries(): Record<string, string[]>
|
|
174
|
+
queries(key: string):string[] | undefined
|
|
175
|
+
queries(key?: string) { // @ts-ignore
|
|
176
|
+
return this.#c.req.queries(key)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async body<E>() {
|
|
180
|
+
const cType = this.#c.req.header('Content-Type')
|
|
181
|
+
if (!cType) return {} as E
|
|
182
|
+
|
|
183
|
+
if (/^application\/([a-z-\.]+\+)?json(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType))
|
|
184
|
+
return await this.json<E>()
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
cType?.startsWith('multipart/form-data')
|
|
188
|
+
|| cType?.startsWith('application/x-www-form-urlencoded')
|
|
189
|
+
) {
|
|
190
|
+
return await this.parseBody() as E
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {} as E
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async parseBody<Options extends Partial<ParseBodyOptions>, T extends BodyData<Options>>(
|
|
197
|
+
options?: Options
|
|
198
|
+
): Promise<T>
|
|
199
|
+
async parseBody<T extends BodyData>(options?: Partial<ParseBodyOptions>): Promise<T>
|
|
200
|
+
async parseBody(options?: Partial<ParseBodyOptions>) {
|
|
201
|
+
try {
|
|
202
|
+
return await this.#c.req.parseBody(options)
|
|
203
|
+
} catch (e) {
|
|
204
|
+
throw new HTTPException(400, {
|
|
205
|
+
message: 'Malformed FormData request.'+ (e instanceof Error ? ` ${e.message}` : ` ${String(e)}`)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async json<E>() {
|
|
211
|
+
try {
|
|
212
|
+
return await this.#c.req.json<E>()
|
|
213
|
+
} catch {
|
|
214
|
+
throw new HTTPException(400, { message: 'Malformed JSON in request body' })
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
text() {
|
|
219
|
+
return this.#c.req.text()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
arrayBuffer() {
|
|
223
|
+
return this.#c.req.arrayBuffer()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
blob() {
|
|
227
|
+
return this.#c.req.blob()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
formData() {
|
|
231
|
+
return this.#c.req.formData()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { HtmlEscapedCallbackPhase, resolveCallback } from 'hono/utils/html'
|
|
2
|
+
import type {
|
|
3
|
+
ContentfulStatusCode, RedirectStatusCode, StatusCode,
|
|
4
|
+
BaseMime, HeaderRecord,
|
|
5
|
+
ErrorResponse, Errors,
|
|
6
|
+
} from './types'
|
|
7
|
+
|
|
8
|
+
type RBag = {
|
|
9
|
+
status?: StatusCode,
|
|
10
|
+
headers?: HeaderRecord,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default class $Response {
|
|
14
|
+
static raw(
|
|
15
|
+
status?: StatusCode,
|
|
16
|
+
body?: any,
|
|
17
|
+
cType?: BaseMime,
|
|
18
|
+
headers?: HeaderRecord
|
|
19
|
+
) {
|
|
20
|
+
const b: RBag = { status: status || 200 }
|
|
21
|
+
|
|
22
|
+
if (cType || headers) {
|
|
23
|
+
headers ??= {}
|
|
24
|
+
if (cType) headers['Content-Type'] = cType
|
|
25
|
+
b.headers = headers
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return new Response(body ?? null, b)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static text(str?: string, status?: StatusCode, headers?: HeaderRecord) {
|
|
32
|
+
return this.raw(status, str, 'text/plain; charset=UTF-8' as BaseMime, headers)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static json<T>(data?: T, status?: StatusCode, headers?: HeaderRecord) {
|
|
36
|
+
if (data == null)
|
|
37
|
+
return this.raw(status, null, undefined, headers)
|
|
38
|
+
|
|
39
|
+
return this.raw(status, JSON.stringify(data), 'application/json', headers)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static redirect(
|
|
43
|
+
location: string | URL,
|
|
44
|
+
status?: RedirectStatusCode,
|
|
45
|
+
headers?: HeaderRecord
|
|
46
|
+
) {
|
|
47
|
+
const loc = String(location)
|
|
48
|
+
|
|
49
|
+
return new Response(null, {
|
|
50
|
+
status: status || 302,
|
|
51
|
+
headers: { ...headers, 'Location': /[^\x00-\xFF]/.test(loc) ? encodeURI(loc) : loc }
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static html(
|
|
56
|
+
html: string | Promise<string>,
|
|
57
|
+
status?: ContentfulStatusCode,
|
|
58
|
+
headers?: HeaderRecord
|
|
59
|
+
): Response | Promise<Response> {
|
|
60
|
+
const res = (html: string) => this.raw(status, html, 'text/html; charset=UTF-8' as BaseMime, headers)
|
|
61
|
+
return typeof html === 'string'
|
|
62
|
+
? res(html)
|
|
63
|
+
: resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then(res)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static ok(): Response
|
|
67
|
+
static ok<T>(data: T, headers?: HeaderRecord): Response
|
|
68
|
+
static ok<T>(data?: T, headers?: HeaderRecord) {
|
|
69
|
+
return this.json(data, 200, headers)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static created(): Response
|
|
73
|
+
static created<T>(data: T, headers?: HeaderRecord): Response
|
|
74
|
+
static created<T>(data?: T, headers?: HeaderRecord) {
|
|
75
|
+
return this.json(data, 201, headers)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static accepted(): Response
|
|
79
|
+
static accepted<T>(data: T, headers?: HeaderRecord): Response
|
|
80
|
+
static accepted<T>(data?: T, headers?: HeaderRecord) {
|
|
81
|
+
return this.json(data, 202, headers)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static deleted(headers?: HeaderRecord) {
|
|
85
|
+
return this.noContent(headers)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static noContent(headers?: HeaderRecord) {
|
|
89
|
+
return this.json(null, 204, headers)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static badRequest(): Response
|
|
93
|
+
static badRequest(errors?: Errors | null, msg?: string, headers?: HeaderRecord): Response
|
|
94
|
+
static badRequest(errors?: Errors | null, msg?: string, headers?: HeaderRecord) {
|
|
95
|
+
return this.error(errors, msg, 400, headers)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static unauthorized(): Response
|
|
99
|
+
static unauthorized<T>(data: T, headers?: HeaderRecord): Response
|
|
100
|
+
static unauthorized<T>(data?: T, headers?: HeaderRecord) {
|
|
101
|
+
return this.json(data, 401, headers)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static forbidden(): Response
|
|
105
|
+
static forbidden<T>(data: T, headers?: HeaderRecord): Response
|
|
106
|
+
static forbidden<T>(data?: T, headers?: HeaderRecord) {
|
|
107
|
+
return this.json(data, 403, headers)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static notFound(): Response
|
|
111
|
+
static notFound<T>(msg: T, headers?: HeaderRecord): Response
|
|
112
|
+
static notFound<T>(msg?: T, headers?: HeaderRecord) {
|
|
113
|
+
return this.json(msg, 404, headers)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static conflict(): Response
|
|
117
|
+
static conflict(errors?: Errors | null, msg?: string, headers?: HeaderRecord) {
|
|
118
|
+
return this.error(errors, msg, 409, headers)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static unsupportedMediaType(): Response
|
|
122
|
+
static unsupportedMediaType(errors?: Errors | null, msg?: string, headers?: HeaderRecord) {
|
|
123
|
+
return this.error(errors, msg, 415, headers)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static internalError(): Response
|
|
127
|
+
static internalError(errors?: Errors | null, msg?: string, headers?: HeaderRecord) {
|
|
128
|
+
return this.error(errors, msg, 500, headers)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static error(errors?: Errors | null, msg?: string, status?: ContentfulStatusCode, headers?: HeaderRecord) {
|
|
132
|
+
status ??= 500
|
|
133
|
+
if (!errors && !msg)
|
|
134
|
+
return this.raw(status, msg)
|
|
135
|
+
|
|
136
|
+
const resp: ErrorResponse = {}
|
|
137
|
+
if (msg) resp.m = msg
|
|
138
|
+
if (errors) resp.e = errors
|
|
139
|
+
|
|
140
|
+
return this.json(resp, status, headers)
|
|
141
|
+
}
|
|
142
|
+
}
|