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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -12
  3. package/package.json +91 -2
  4. package/src/cli.ts +42 -0
  5. package/src/enum.ts +2 -0
  6. package/src/index.d.ts +61 -0
  7. package/src/index.ts +19 -0
  8. package/src/middleware.ts +2 -0
  9. package/src/pages/client/app.tsx +123 -0
  10. package/src/pages/client/hmr.ts +13 -0
  11. package/src/pages/client/island.ts +136 -0
  12. package/src/pages/client/link.ts +70 -0
  13. package/src/pages/client/router.ts +329 -0
  14. package/src/pages/client/storage.ts +95 -0
  15. package/src/pages/commands/build.ts +74 -0
  16. package/src/pages/commands/dev-old.ts +213 -0
  17. package/src/pages/commands/dev.ts +87 -0
  18. package/src/pages/commands/preview.ts +33 -0
  19. package/src/pages/compiler/aliases.ts +36 -0
  20. package/src/pages/compiler/assets.ts +43 -0
  21. package/src/pages/compiler/ast.ts +764 -0
  22. package/src/pages/compiler/constants.ts +24 -0
  23. package/src/pages/compiler/file.ts +60 -0
  24. package/src/pages/compiler/index.ts +307 -0
  25. package/src/pages/compiler/island.ts +108 -0
  26. package/src/pages/compiler/meta.ts +601 -0
  27. package/src/pages/compiler/routes.ts +29 -0
  28. package/src/pages/compiler/types.ts +209 -0
  29. package/src/pages/compiler/utils.ts +5 -0
  30. package/src/pages/compiler/vite-plugin.ts +324 -0
  31. package/src/pages/components/link.tsx +38 -0
  32. package/src/pages/i18n/client.ts +403 -0
  33. package/src/pages/i18n/element.ts +44 -0
  34. package/src/pages/i18n/server.ts +56 -0
  35. package/src/pages/index.ts +28 -0
  36. package/src/pages/preact/client-dev.ts +5 -0
  37. package/src/pages/preact/client.ts +87 -0
  38. package/src/pages/preact/context.ts +41 -0
  39. package/src/pages/preact/server.ts +186 -0
  40. package/src/pages/preact/signals.ts +89 -0
  41. package/src/pages/preact/static-html.ts +45 -0
  42. package/src/pages/preact/types.ts +26 -0
  43. package/src/server/adapter/aws.ts +4 -0
  44. package/src/server/adapter/workerd.ts +4 -0
  45. package/src/server/app.ts +96 -0
  46. package/src/server/auth/ability.ts +72 -0
  47. package/src/server/auth/authnz.ts +86 -0
  48. package/src/server/auth/index.ts +5 -0
  49. package/src/server/auth/token.ts +147 -0
  50. package/src/server/auth/types.ts +2 -0
  51. package/src/server/cli/commands/build.ts +41 -0
  52. package/src/server/cli/commands/deploy.ts +82 -0
  53. package/src/server/cli/commands/dev.ts +277 -0
  54. package/src/server/cli/commands/make.ts +98 -0
  55. package/src/server/cli/commands/migrate.ts +93 -0
  56. package/src/server/cli/commands/routes.ts +79 -0
  57. package/src/server/cli/index.ts +27 -0
  58. package/src/server/cli/stubs.ts +39 -0
  59. package/src/server/cli/types.ts +3 -0
  60. package/src/server/cli/utils.ts +443 -0
  61. package/src/server/config.ts +4 -0
  62. package/src/server/db/d1.d.ts +1 -0
  63. package/src/server/db/d1.ts +1 -0
  64. package/src/server/db/dynamodb.d.ts +1 -0
  65. package/src/server/db/dynamodb.ts +1 -0
  66. package/src/server/db/index.ts +1 -0
  67. package/src/server/entry.ts +19 -0
  68. package/src/server/handle.ts +30 -0
  69. package/src/server/http.ts +23 -0
  70. package/src/server/middlewares/auth.ts +40 -0
  71. package/src/server/middlewares/logger.ts +30 -0
  72. package/src/server/open-api/register.ts +29 -0
  73. package/src/server/open-api/spec.ts +107 -0
  74. package/src/server/request.ts +233 -0
  75. package/src/server/response.ts +142 -0
  76. package/src/server/routes.ts +474 -0
  77. package/src/server/types.ts +142 -0
  78. package/src/server/utils/environment.ts +17 -0
  79. package/src/server/utils.ts +46 -0
  80. package/src/server/validator.ts +70 -0
  81. 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
+ }