rajt 0.0.29 → 0.0.31

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rajt",
3
3
  "description": "A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.",
4
- "version": "0.0.29",
4
+ "version": "0.0.31",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "exports": {
package/src/action.ts CHANGED
@@ -3,10 +3,8 @@
3
3
  import { Context, Handler, ValidationTargets } from 'hono'
4
4
  import { z, ZodObject } from 'zod'
5
5
  import { zValidator } from '@hono/zod-validator'
6
- import JsonResponse from './response'
7
- import { bufferToFormData } from 'hono/utils/buffer'
8
- import { HTTPException } from 'hono/http-exception'
9
- import { BodyData } from 'hono/utils/body'
6
+ import Response from './response'
7
+ import cx from './context'
10
8
 
11
9
  export type ActionType = Function | Handler | Action | (new () => Action)
12
10
 
@@ -16,10 +14,10 @@ type RuleDefinition = {
16
14
  eTarget?: 'fieldErrors' | 'formErrors'
17
15
  }
18
16
 
19
- export default abstract class Action {
20
- rule<T extends keyof ValidationTargets>(target: T): { schema: (schema: ZodObject<any>) => RuleDefinition }
21
- rule<T extends keyof ValidationTargets>(target: T, schema: ZodObject<any>): RuleDefinition
22
- rule<T extends keyof ValidationTargets>(target: T, schema?: ZodObject<any>):
17
+ export default class Action {
18
+ static rule<T extends keyof ValidationTargets>(target: T): { schema: (schema: ZodObject<any>) => RuleDefinition }
19
+ static rule<T extends keyof ValidationTargets>(target: T, schema: ZodObject<any>): RuleDefinition
20
+ static rule<T extends keyof ValidationTargets>(target: T, schema?: ZodObject<any>):
23
21
  | { schema: (schema: ZodObject<any>) => RuleDefinition }
24
22
  | RuleDefinition
25
23
  {
@@ -40,101 +38,19 @@ export default abstract class Action {
40
38
  }
41
39
  }
42
40
 
43
- param(key: string) {
44
- return this.cx.req.param(key)
45
- }
46
-
47
- query() {
48
- return this.cx.req.query()
49
- }
50
-
51
- async form(cType?: string) {
52
- cType ??= this.cx.req.header('Content-Type')
53
- if (!cType) return {}
54
-
55
- let formData: FormData
56
-
57
- if (this.cx.req.bodyCache.formData) {
58
- formData = await this.cx.req.bodyCache.formData
59
- } else {
60
- try {
61
- const arrayBuffer = await this.cx.req.arrayBuffer()
62
- formData = await bufferToFormData(arrayBuffer, cType)
63
- this.cx.req.bodyCache.formData = formData
64
- } catch (e) {
65
- throw new HTTPException(400, {
66
- message: 'Malformed FormData request.'
67
- + (e instanceof Error ? ` ${e.message}` : ` ${String(e)}`)
68
- })
69
- }
70
- }
71
-
72
- const form: BodyData<{ all: true }> = {}
73
- formData.forEach((value, key) => {
74
- if (key.endsWith('[]')) {
75
- ;((form[key] ??= []) as unknown[]).push(value)
76
- } else if (Array.isArray(form[key])) {
77
- ;(form[key] as unknown[]).push(value)
78
- } else if (key in form) {
79
- form[key] = [form[key] as string | File, value]
80
- } else {
81
- form[key] = value
82
- }
83
- })
84
-
85
- return form
86
- }
87
-
88
- async json<E>() {
89
- try {
90
- return await this.cx.req.json<E>()
91
- } catch {
92
- throw new HTTPException(400, { message: 'Malformed JSON in request body' })
93
- }
94
- }
95
-
96
- async body<E>() {
97
- const cType = this.cx.req.header('Content-Type')
98
- if (!cType) return {} as E
99
-
100
- if (/^application\/([a-z-\.]+\+)?json(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType)) {
101
- return await this.json<E>()
102
- }
103
-
104
- if (
105
- /^multipart\/form-data(;\s?boundary=[a-zA-Z0-9'"()+_,\-./:=?]+)?$/.test(cType)
106
- && ! /^application\/x-www-form-urlencoded(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType)
107
- ) {
108
- return await this.form() as E
109
- }
110
-
111
- return {} as E
112
- }
113
-
114
- get response() {
115
- return JsonResponse
116
- }
117
-
118
- get cx() {
119
- return JsonResponse.cx
120
- }
121
-
122
- get cookie() {
123
- return JsonResponse.cookie
124
- }
125
-
126
- validate() {
41
+ static validate() {
127
42
  const rules = this.rules()
128
43
  const h = async (c: Context) => {
129
- return await this.handle(this.cx)
44
+ return await this.handle(cx.cx)
130
45
  }
131
46
  if (!rules) return [h]
132
47
 
133
- const rulesArray = (Array.isArray(rules) ? rules : [rules])
48
+ const rulesArray: Function[] = (Array.isArray(rules) ? rules : [rules])
49
+ // @ts-ignore
134
50
  .map(rule => zValidator(rule.target, rule.schema, (result, c) => {
135
51
  if (!result.success) {
136
52
  // @ts-ignore
137
- return JsonResponse.badRequest({ ...result.error.flatten()[rule.eTarget] })
53
+ return Response.badRequest({ ...result.error.flatten()[rule.eTarget] })
138
54
  }
139
55
  }))
140
56
 
@@ -142,34 +58,15 @@ export default abstract class Action {
142
58
  return rulesArray
143
59
  }
144
60
 
145
- run() {
61
+ static run() {
146
62
  return this.validate()
147
63
  }
148
64
 
149
- // PUBLIC API
150
-
151
- get auth() {
152
- const auth = this.cx.get('#auth')
153
- return auth ? auth?.data : null
154
- }
155
-
156
- can(...abilities: string[]): boolean {
157
- const auth = this.cx.get('#auth')
158
- return auth ? auth.can(...abilities) : false
159
- }
160
-
161
- cant(...abilities: string[]): boolean {
162
- return !this.can(...abilities)
163
- }
164
-
165
- hasRole(...roles: string[]): boolean {
166
- const auth = this.cx.get('#auth')
167
- return auth ? auth.hasRole(...roles) : false
168
- }
169
-
170
- rules(): RuleDefinition[] | RuleDefinition | null {
65
+ static rules(): RuleDefinition[] | RuleDefinition | null {
171
66
  return null
172
67
  }
173
68
 
174
- abstract handle(c: Context): Promise<Response>
69
+ static async handle(c: Context): Promise<Response> {
70
+ return Promise.resolve(Response.raw(200, 'Action handle not implemented'))
71
+ }
175
72
  }
package/src/auth/auth.ts CHANGED
@@ -1,66 +1,26 @@
1
- import { Ability } from './ability'
1
+ import { Authnz } from './authnz'
2
+ import { Token } from './token'
2
3
 
3
- export class Authnz<T extends object> {
4
- #abilities: string[]
5
- #roles: string[]
6
- #data: T
4
+ export class Auth {
5
+ static #u: Authnz<any> | null = null
7
6
 
8
- constructor(data: T, abilities: string[], roles: string[]) {
9
- this.#abilities = abilities
10
- this.#roles = roles
11
- this.#data = data
7
+ static resolve() {
8
+ this.#u = Authnz.fromToken(Token.fromRequest())
12
9
  }
13
10
 
14
- can(...abilities: string[]): boolean {
15
- if (this.#abilities.includes('*')) return true
16
-
17
- return abilities.flat().every(ability => {
18
- if (this.#roles.includes(ability)) return true
19
- return this.#abilities.some(rule => this.#match(rule, ability))
20
- })
21
- }
22
- cant(...abilities: string[]): boolean {
23
- return !this.can(...abilities)
11
+ static get user() {
12
+ return this.#u ? this.#u?.data : null
24
13
  }
25
14
 
26
- hasRole(...roles: string[]): boolean {
27
- return roles.flat().every(role => this.#roles.includes(role))
15
+ static can(...abilities: string[]): boolean {
16
+ return this.#u ? this.#u.can(...abilities) : false
28
17
  }
29
18
 
30
- static fromToken<T extends object>(user: any): Authnz<T> | null {
31
- if (!user || user?.isInvalid()) return null
32
- user = user.get()
33
- const roles = [...(user?.role ? [user.role] : []), ...(user?.roles ?? [])]
34
-
35
- const combined = [...(user?.perms ?? []), ...roles.flatMap(role => {
36
- const perms = Ability.roles[role]
37
- if (!perms) return []
38
- return perms === '*' ? ['*'] : perms;
39
- })]
40
-
41
- const abilities = combined.includes('*') ? ['*'] : Array.from(new Set(combined))
42
-
43
- return new Authnz(user as T, abilities, roles)
44
- }
45
-
46
- #match(rule: string, ability: string): boolean {
47
- if (rule === ability) return true
48
- if (rule.endsWith('.*')) {
49
- const prefix = rule.slice(0, -2)
50
- return ability.startsWith(`${prefix}.`) || ability === prefix
51
- }
52
- return false
53
- }
54
-
55
- get abilities() {
56
- return this.#abilities
57
- }
58
-
59
- get roles() {
60
- return this.#roles
19
+ static cant(...abilities: string[]): boolean {
20
+ return !this.can(...abilities)
61
21
  }
62
22
 
63
- get data() {
64
- return this.#data
23
+ static hasRole(...roles: string[]): boolean {
24
+ return this.#u ? this.#u.hasRole(...roles) : false
65
25
  }
66
26
  }
@@ -0,0 +1,66 @@
1
+ import { Ability } from './ability'
2
+
3
+ export class Authnz<T extends object> {
4
+ #abilities: string[]
5
+ #roles: string[]
6
+ #data: T
7
+
8
+ constructor(data: T, abilities: string[], roles: string[]) {
9
+ this.#abilities = abilities
10
+ this.#roles = roles
11
+ this.#data = data
12
+ }
13
+
14
+ can(...abilities: string[]): boolean {
15
+ if (this.#abilities.includes('*')) return true
16
+
17
+ return abilities.flat().every(ability => {
18
+ if (this.#roles.includes(ability)) return true
19
+ return this.#abilities.some(rule => this.#match(rule, ability))
20
+ })
21
+ }
22
+ cant(...abilities: string[]): boolean {
23
+ return !this.can(...abilities)
24
+ }
25
+
26
+ hasRole(...roles: string[]): boolean {
27
+ return roles.flat().every(role => this.#roles.includes(role))
28
+ }
29
+
30
+ static fromToken<T extends object>(user: any): Authnz<T> | null {
31
+ if (!user || user?.isInvalid()) return null
32
+ user = user.get()
33
+ const roles = [...(user?.role ? [user.role] : []), ...(user?.roles ?? [])]
34
+
35
+ const combined = [...(user?.perms ?? []), ...roles.flatMap(role => {
36
+ const perms = Ability.roles[role]
37
+ if (!perms) return []
38
+ return perms === '*' ? ['*'] : perms;
39
+ })]
40
+
41
+ const abilities = combined.includes('*') ? ['*'] : Array.from(new Set(combined))
42
+
43
+ return new Authnz(user as T, abilities, roles)
44
+ }
45
+
46
+ #match(rule: string, ability: string): boolean {
47
+ if (rule === ability) return true
48
+ if (rule.endsWith('.*')) {
49
+ const prefix = rule.slice(0, -2)
50
+ return ability.startsWith(`${prefix}.`) || ability === prefix
51
+ }
52
+ return false
53
+ }
54
+
55
+ get abilities() {
56
+ return this.#abilities
57
+ }
58
+
59
+ get roles() {
60
+ return this.#roles
61
+ }
62
+
63
+ get data() {
64
+ return this.#data
65
+ }
66
+ }
package/src/auth/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { Ability } from './ability'
2
- export { Authnz } from './auth'
2
+ export { Auth } from './auth'
3
+ export { Authnz } from './authnz'
3
4
  export { Token } from './token'
4
5
 
5
6
  export * from './types'
package/src/auth/token.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  import { Envir } from 't0n'
2
2
  import { Token as Factory } from 'cripta'
3
- import type { Context, HonoRequest, Next } from 'hono'
3
+ import c from '../context'
4
4
 
5
5
  export class Token {
6
6
  static #name: string = 'Authorization'
7
7
  static #prefix: string = 'bearer'
8
8
 
9
- static fromRequest(c: Context) {
10
- const token = this.fromHeader(c.req)
11
- return token ? this.parse(c.req, token) : null
9
+ static fromRequest() {
10
+ const token = this.fromCookie() || this.fromHeader()
11
+ return token ? this.parse(token) : null
12
12
  }
13
13
 
14
- static fromHeader(req: HonoRequest): string | null {
15
- const header = req.header(this.#name) || req.header('HTTP_AUTHORIZATION') || req.header('REDIRECT_HTTP_AUTHORIZATION') || null
14
+ static fromHeader(): string | null {
15
+ const header = c.cx.req.header(this.#name) || c.cx.req.header('HTTP_AUTHORIZATION') || c.cx.req.header('REDIRECT_HTTP_AUTHORIZATION') || null
16
16
 
17
17
  if (header) {
18
18
  const position = header.toLowerCase().indexOf(this.#prefix.toLowerCase())
@@ -28,17 +28,28 @@ export class Token {
28
28
  return null
29
29
  }
30
30
 
31
- static parse(req: HonoRequest, token: string) {
32
- const host = this.host(Envir.get('FLOW_SERVER') || req.header('host') || '')
31
+ static fromCookie(): string | null {
32
+ const uid = c.cx.req.header('uid')
33
+
34
+ if (uid) {
35
+ const auth = c.cookie.get('__auth_' + uid)
36
+ return auth ? auth : null
37
+ }
38
+
39
+ return null
40
+ }
41
+
42
+ static parse(token: string) {
43
+ const host = this.host(Envir.get('FLOW_SERVER') || c.cx.req.header('host') || '')
33
44
 
34
45
  return Factory.parse(token)
35
46
  .issuedBy(host)
36
47
  .permittedFor(host)
37
48
  }
38
49
 
39
- static create(req: HonoRequest, user: any, exp: number = 7200) {
50
+ static create(user: any, exp: number = 7200) {
40
51
  const time = Math.floor(Date.now() / 1000)
41
- const host = this.host(req.header('host') || '')
52
+ const host = this.host(c.cx.req.header('host') || '')
42
53
 
43
54
  return Factory.create()
44
55
  .issuedBy(host)
package/src/context.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { Context } from 'hono'
2
+ import { getCookie, getSignedCookie, setCookie, setSignedCookie, deleteCookie } from 'hono/cookie'
3
+ import type { CookieOptions, CookiePrefixOptions } from 'hono/utils/cookie'
4
+
5
+ const cookieWrapper = (c: Context) => ({
6
+ all: () => getCookie(c),
7
+ allSigned: (secret: string) => getSignedCookie(c, secret),
8
+ get: (name: string, prefixOptions?: CookiePrefixOptions) => prefixOptions ? getCookie(c, name, prefixOptions) : getCookie(c, name),
9
+ getSigned: (secret: string, name: string, prefixOptions?: CookiePrefixOptions) => prefixOptions ? getSignedCookie(c, secret, name, prefixOptions) : getSignedCookie(c, secret, name),
10
+ set: (name: string, value: string, opt?: CookieOptions) => setCookie(c, name, value, opt),
11
+ setSigned: (name: string, value: string, secret: string, opt?: CookieOptions) => setSignedCookie(c, name, value, secret, opt),
12
+ delete: (name: string, opt?: CookieOptions) => deleteCookie(c, name, opt)
13
+ })
14
+
15
+ export default class CX {
16
+ static #c: Context
17
+ static #cookie: ReturnType<typeof cookieWrapper>
18
+
19
+ static setContext(c: Context) {
20
+ this.#c = c
21
+ this.#cookie = cookieWrapper(c)
22
+ }
23
+
24
+ static get cx(): Context {
25
+ return this.#c
26
+ }
27
+
28
+ static get cookie() {
29
+ return this.#cookie
30
+ }
31
+ }
package/src/create-app.ts CHANGED
@@ -4,13 +4,15 @@ import type { Env, Context, ErrorHandler, NotFoundHandler, Next } from 'hono'
4
4
  // import type { MiddlewareHandler } from 'hono'
5
5
  // import { createMiddleware } from 'hono/factory'
6
6
  // import type { H, Handler, HandlerResponse } from 'hono/types'
7
- import { HTTPResponseError } from 'hono/types'
8
- import { Routes } from './types'
7
+ import type { HTTPResponseError } from 'hono/types'
8
+ import type { Routes } from './types'
9
9
  import { BadRequest, Unauthorized } from './exceptions'
10
- import response from './response'
11
10
  import { resolve, resolveMiddleware } from './utils/resolve'
12
11
  import { getMiddlewares, getHandler } from './register'
13
12
  import env from './utils/environment'
13
+ import { Auth } from './auth'
14
+ import response from './response'
15
+ import cx from './context'
14
16
 
15
17
  type InitFunction<E extends Env = Env> = (app: Hono<E>) => void
16
18
 
@@ -78,16 +80,18 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
78
80
  const app = options?.app ?? new Hono<E>()
79
81
 
80
82
  app.use(async (c: Context, next: Next) => {
81
- response.setContext(c)
83
+ cx.setContext(c)
84
+ Auth.resolve()
82
85
  await next()
83
86
  })
84
87
  getMiddlewares().forEach(mw => {
85
- const h = async (c: Context, next: Next) => await resolveMiddleware(mw)(response.cx, next)
88
+ const h = async (c: Context, next: Next) => await resolveMiddleware(mw)(cx.cx, next)
86
89
  // @ts-ignore
87
90
  mw?.path ? app.use(String(mw.path), h) : app.use(h)
88
91
  })
89
-
92
+ // @ts-ignore
90
93
  app.onError(options?.onError || EHandler)
94
+ // @ts-ignore
91
95
  app.notFound(options?.notFound || NFHandler)
92
96
 
93
97
  if (options?.init) options.init(app)
@@ -95,12 +99,11 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
95
99
  const routes = options?.routes || []
96
100
  for (const route of routes) {
97
101
  if (Array.isArray(route)) {
98
- const handle = getHandler(route[3])
99
102
  // @ts-ignore
100
- app[route[0]](route[1], ...mw(route[2], route[3]), ...resolve(handle))
103
+ app[route[0]](route[1], ...mw(route[2], route[3]), ...resolve(getHandler(route[3]), route[3]))
101
104
  } else {
102
105
  // @ts-ignore
103
- app[route.method](route.path, ...mw(route.middlewares, route.name), ...resolve(route.handle))
106
+ app[route.method](route.path, ...mw(route.middlewares, route.name), ...resolve(route.handle, route.name))
104
107
  }
105
108
  }
106
109
 
package/src/http.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Context, Next } from 'hono'
2
2
  import { MiddlewareType } from './middleware'
3
- import JsonResponse from './response'
4
- import { Ability, Authnz, Token } from './auth'
3
+ import Response from './response'
4
+ import { Ability, Auth as Gate } from './auth'
5
5
  import mergeMiddleware from './utils/merge-middleware'
6
6
 
7
7
  function method(method: string, path = '/') {
@@ -52,15 +52,12 @@ export function Auth(...args: any[]): void | ClassDecorator {
52
52
 
53
53
  function _auth(target: Function | any) {
54
54
  mergeMiddleware(target, async (c: Context, next: Next) => {
55
- const unauthorized = JsonResponse.unauthorized()
56
-
57
- const auth = Authnz.fromToken(Token.fromRequest(c))
55
+ const auth = Gate.user()
58
56
  const ability = Ability.fromAction(target)
59
57
 
60
58
  if (!auth || !ability || auth.cant(ability))
61
- return unauthorized
59
+ return Response.unauthorized()
62
60
 
63
- c.set('#auth', auth)
64
61
  await next()
65
62
  })
66
63
  }
package/src/index.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { default as Action } from './action'
2
+ export { Auth } from './auth/auth'
2
3
  export { default as Middleware } from './middleware'
3
- export { default as JsonResponse } from './response'
4
+ export { default as Request } from './request'
5
+ export { default as Response } from './response'
4
6
  export { default as Enum } from './enum'
package/src/middleware.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Context, MiddlewareHandler, Next } from 'hono'
2
2
 
3
3
  export type MiddlewareType = MiddlewareHandler | Middleware | (new () => Middleware)
4
- export default abstract class Middleware {
4
+ export default class Middleware {
5
5
  static factory?: Function
6
6
  static opts?: object | any[]
7
7
  static path: string = '*'
package/src/register.ts CHANGED
@@ -1,24 +1,13 @@
1
- export const handlers = {}
1
+ export const handlers: Record<string, Function> = {}
2
2
 
3
3
  export function registerHandler(id: string, handler: any) {
4
- if (typeof handler === 'function') {
5
- // @ts-ignore
6
- handlers[id] = handler
7
- } else if (handler.prototype?.handle) {
8
- const instance = new handler()
9
- // @ts-ignore
10
- handlers[id] = instance.handle.bind(instance)
11
- } else if (handler.run) {
12
- const instance = new handler()
13
- // @ts-ignore
14
- handlers[id] = instance.run.bind(instance)
15
- } else {
16
- console.warn(`Handler ${id} could not be registered - unsupported type`)
17
- }
4
+ if (id in handlers)
5
+ console.warn(`Handler "${id}" has already been registered`)
6
+
7
+ handlers[id] = handler
18
8
  }
19
9
 
20
10
  export function getHandler(id: string): Function {
21
- // @ts-ignore
22
11
  const handler = handlers[id] || null
23
12
  if (!handler) throw new Error(`Handler ${id} not registered`)
24
13
  return handler
package/src/request.ts ADDED
@@ -0,0 +1,90 @@
1
+ import { bufferToFormData } from 'hono/utils/buffer'
2
+ import { HTTPException } from 'hono/http-exception'
3
+ import type { BodyData } from 'hono/utils/body'
4
+ import Response from './response'
5
+ import c from './context'
6
+
7
+ export default class Request {
8
+ static param(key: string) {
9
+ return c.cx.req.param(key)
10
+ }
11
+
12
+ static query() {
13
+ return c.cx.req.query()
14
+ }
15
+
16
+ static async form(cType?: string) {
17
+ cType ??= c.cx.req.header('Content-Type')
18
+ if (!cType) return {}
19
+
20
+ let formData: FormData
21
+
22
+ if (c.cx.req.bodyCache.formData) {
23
+ formData = await c.cx.req.bodyCache.formData
24
+ } else {
25
+ try {
26
+ const arrayBuffer = await c.cx.req.arrayBuffer()
27
+ formData = await bufferToFormData(arrayBuffer, cType)
28
+ c.cx.req.bodyCache.formData = formData
29
+ } catch (e) {
30
+ throw new HTTPException(400, {
31
+ message: 'Malformed FormData request.'
32
+ + (e instanceof Error ? ` ${e.message}` : ` ${String(e)}`)
33
+ })
34
+ }
35
+ }
36
+
37
+ const form: BodyData<{ all: true }> = {}
38
+ formData.forEach((value, key) => {
39
+ if (key.endsWith('[]')) {
40
+ ;((form[key] ??= []) as unknown[]).push(value)
41
+ } else if (Array.isArray(form[key])) {
42
+ ;(form[key] as unknown[]).push(value)
43
+ } else if (key in form) {
44
+ form[key] = [form[key] as string | File, value]
45
+ } else {
46
+ form[key] = value
47
+ }
48
+ })
49
+
50
+ return form
51
+ }
52
+
53
+ static async json<E>() {
54
+ try {
55
+ return await c.cx.req.json<E>()
56
+ } catch {
57
+ throw new HTTPException(400, { message: 'Malformed JSON in request body' })
58
+ }
59
+ }
60
+
61
+ static async body<E>() {
62
+ const cType = c.cx.req.header('Content-Type')
63
+ if (!cType) return {} as E
64
+
65
+ if (/^application\/([a-z-\.]+\+)?json(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType)) {
66
+ return await Request.json<E>()
67
+ }
68
+
69
+ if (
70
+ /^multipart\/form-data(;\s?boundary=[a-zA-Z0-9'"()+_,\-./:=?]+)?$/.test(cType)
71
+ && ! /^application\/x-www-form-urlencoded(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType)
72
+ ) {
73
+ return await Request.form() as E
74
+ }
75
+
76
+ return {} as E
77
+ }
78
+
79
+ static get response() {
80
+ return Response
81
+ }
82
+
83
+ static get cx() {
84
+ return c.cx
85
+ }
86
+
87
+ static get cookie() {
88
+ return c.cookie
89
+ }
90
+ }
package/src/response.ts CHANGED
@@ -1,39 +1,10 @@
1
- import { Context } from 'hono'
2
1
  import type { ContentfulStatusCode, StatusCode } from 'hono/utils/http-status'
3
- import { ErrorResponse, Errors } from './types'
4
- import { getCookie, getSignedCookie, setCookie, setSignedCookie, deleteCookie } from 'hono/cookie'
5
- import type { CookieOptions, CookiePrefixOptions } from 'hono/utils/cookie'
6
-
7
- const cookieWrapper = (c: Context) => ({
8
- all: () => getCookie(c),
9
- allSigned: (secret: string) => getSignedCookie(c, secret),
10
- get: (name: string, prefixOptions?: CookiePrefixOptions) => prefixOptions ? getCookie(c, name, prefixOptions) : getCookie(c, name),
11
- getSigned: (secret: string, name: string, prefixOptions?: CookiePrefixOptions) => prefixOptions ? getSignedCookie(c, secret, name, prefixOptions) : getSignedCookie(c, secret, name),
12
- set: (name: string, value: string, opt?: CookieOptions) => setCookie(c, name, value, opt),
13
- setSigned: (name: string, value: string, secret: string, opt?: CookieOptions) => setSignedCookie(c, name, value, secret, opt),
14
- delete: (name: string, opt?: CookieOptions) => deleteCookie(c, name, opt)
15
- })
16
-
17
- export default class JsonResponse {
18
- static #c: Context
19
- static #cookie: ReturnType<typeof cookieWrapper>
20
-
21
- static setContext(c: Context) {
22
- this.#c = c
23
- this.#cookie = cookieWrapper(c)
24
- return this
25
- }
26
-
27
- static get cx(): Context {
28
- return this.#c
29
- }
30
-
31
- static get cookie() {
32
- return this.#cookie
33
- }
2
+ import type { ErrorResponse, Errors } from './types'
3
+ import c from './context'
34
4
 
5
+ export default class Response {
35
6
  static raw(status?: StatusCode, body?: string) {
36
- return this.cx.newResponse(body ? body : null, { status })
7
+ return c.cx.newResponse(body ? body : null, { status })
37
8
  }
38
9
 
39
10
  static ok(): Response
@@ -42,7 +13,7 @@ export default class JsonResponse {
42
13
  if (data === undefined)
43
14
  return this.raw(200)
44
15
 
45
- return this.cx.json(data, 200)
16
+ return c.cx.json(data, 200)
46
17
  }
47
18
 
48
19
  static created(): Response
@@ -51,7 +22,7 @@ export default class JsonResponse {
51
22
  if (data === undefined)
52
23
  return this.raw(201)
53
24
 
54
- return this.cx.json(data, 201)
25
+ return c.cx.json(data, 201)
55
26
  }
56
27
 
57
28
  static accepted(): Response
@@ -60,7 +31,7 @@ export default class JsonResponse {
60
31
  if (data === undefined)
61
32
  return this.raw(202)
62
33
 
63
- return this.cx.json(data, 202)
34
+ return c.cx.json(data, 202)
64
35
  }
65
36
 
66
37
  static deleted() {
@@ -82,7 +53,7 @@ export default class JsonResponse {
82
53
  if (data === undefined)
83
54
  return this.raw(401)
84
55
 
85
- return this.cx.json(data, 401)
56
+ return c.cx.json(data, 401)
86
57
  }
87
58
 
88
59
  static forbidden(): Response
@@ -91,7 +62,7 @@ export default class JsonResponse {
91
62
  if (data === undefined)
92
63
  return this.raw(403)
93
64
 
94
- return this.cx.json(data, 403)
65
+ return c.cx.json(data, 403)
95
66
  }
96
67
 
97
68
  static notFound(): Response
@@ -124,6 +95,6 @@ export default class JsonResponse {
124
95
  if (msg) resp.m = msg
125
96
  if (errors) resp.e = errors
126
97
 
127
- return this.cx.json(resp, status)
98
+ return c.cx.json(resp, status)
128
99
  }
129
100
  }
package/src/routes.ts CHANGED
@@ -62,7 +62,41 @@ export async function getRoutes(
62
62
  }
63
63
  )))
64
64
 
65
- return routes
65
+ return sortRoutes(routes)
66
+ }
67
+
68
+ function sortRoutes(routes: Route[]) {
69
+ const metas = new Map<string, { score: number; segmentsCount: number }>()
70
+
71
+ for (const route of routes)
72
+ metas.set(route.path, computeRouteMeta(route.path))
73
+
74
+ return routes.sort((a, b) => {
75
+ const metaA = metas.get(a.path)!
76
+ const metaB = metas.get(b.path)!
77
+
78
+ if (metaA.score === metaB.score)
79
+ return metaB.segmentsCount - metaA.segmentsCount
80
+
81
+ return metaB.score - metaA.score
82
+ })
83
+ }
84
+
85
+ function computeRouteMeta(path: string) {
86
+ const segments = path.split('/').filter(Boolean)
87
+
88
+ let score = 0
89
+ for (const segment of segments) {
90
+ if (segment === '*') {
91
+ score += 0
92
+ } else if (segment.startsWith(':')) {
93
+ score += 1
94
+ } else {
95
+ score += 10
96
+ }
97
+ }
98
+
99
+ return { score, segmentsCount: segments.length }
66
100
  }
67
101
 
68
102
  export async function getMiddlewares(
@@ -1,21 +1,24 @@
1
1
  import Action, { ActionType } from '../action'
2
2
  import { MiddlewareType } from '../middleware'
3
3
 
4
- export function resolve(obj: ActionType) {
4
+ export function resolve(obj: ActionType, id: string) {
5
5
  if (typeof obj === 'function' && obj?.length === 2)
6
6
  return [obj]
7
7
 
8
- if (obj instanceof Action)
8
+ if (obj?.run)
9
9
  return obj.run()
10
10
 
11
+ if (obj?.handle)
12
+ return obj.handle()
13
+
11
14
  const instance = new (obj as new () => Action)()
12
- if (Action.isPrototypeOf(obj))
15
+ if (obj?.prototype?.run)
13
16
  return instance.run()
14
17
 
15
18
  if (obj?.prototype?.handle)
16
19
  return [instance.handle]
17
20
 
18
- throw new Error('Invalid action')
21
+ throw new Error(`Invalid action "${id}" - unsupported type`)
19
22
  }
20
23
 
21
24
  export function resolveMiddleware(obj: MiddlewareType) {