rajt 0.0.58 → 0.0.60

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/src/request.ts CHANGED
@@ -1,98 +1,179 @@
1
- import { bufferToFormData } from 'hono/utils/buffer'
1
+ import { getCookie, getSignedCookie, setCookie, setSignedCookie, deleteCookie } from 'hono/cookie'
2
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
- }
3
+ import { Authnz, Token } from './auth'
4
+
5
+ import type { Context } from 'hono'
6
+ import type { CookieOptions, CookiePrefixOptions } from 'hono/utils/cookie'
7
+ import type { CustomHeader, RequestHeader } from 'hono/utils/headers'
8
+ import type { BodyData, ParseBodyOptions } from 'hono/utils/body'
9
+
10
+ const cookieWrapper = (c: Context) => ({
11
+ all: () => getCookie(c),
12
+ allSigned: (secret: string) => getSignedCookie(c, secret),
13
+ get: (name: string, prefixOptions?: CookiePrefixOptions) => prefixOptions ? getCookie(c, name, prefixOptions) : getCookie(c, name),
14
+ getSigned: (secret: string, name: string, prefixOptions?: CookiePrefixOptions) => prefixOptions ? getSignedCookie(c, secret, name, prefixOptions) : getSignedCookie(c, secret, name),
15
+ set: (name: string, value: string, opt?: CookieOptions) => setCookie(c, name, value, opt),
16
+ setSigned: (name: string, value: string, secret: string, opt?: CookieOptions) => setSignedCookie(c, name, value, secret, opt),
17
+ delete: (name: string, opt?: CookieOptions) => deleteCookie(c, name, opt)
18
+ })
19
+
20
+ export default class $Request {
21
+ #c!: Context
22
+ #cookie: ReturnType<typeof cookieWrapper>
23
+ #u: Authnz<any> | null = null
24
+
25
+ constructor(c: Context) {
26
+ this.#c = c
27
+ this.#cookie = cookieWrapper(c)
28
+ this.#u = Authnz.fromToken(Token.fromRequest(this))
29
+ }
36
30
 
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
- })
31
+ get user() {
32
+ return this.#u ? this.#u?.data : null
33
+ }
49
34
 
50
- return form
35
+ get auth() {
36
+ return this.#u
51
37
  }
52
38
 
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
- }
39
+ can(...abilities: string[]) {
40
+ return this.#u ? this.#u.can(...abilities) : false
41
+ }
42
+
43
+ cant(...abilities: string[]) {
44
+ return !this.can(...abilities)
45
+ }
46
+
47
+ hasRole(...roles: string[]) {
48
+ return this.#u ? this.#u.hasRole(...roles) : false
49
+ }
50
+
51
+ has(prop: string, value: any = null) {
52
+ return this.#u ? this.#u.has(prop, value) : false
53
+ }
54
+ hasValue(prop: string, value: any = null) {
55
+ return this.has(prop, value)
56
+ }
57
+
58
+ get cx() {
59
+ return this.#c
60
+ }
61
+
62
+ get cookie() {
63
+ return this.#cookie
64
+ }
65
+
66
+ get ip(): string | undefined {
67
+ return this.#c.req.header('cf-connecting-ip')
68
+ || this.#c.req.header('x-forwarded-for')?.split(',')[0]?.trim()
69
+ || this.#c.env?.aws?.lambda?.event?.requestContext?.identity?.sourceIp
70
+ || this.#c.req.header('x-real-ip')
71
+ || this.#c.env?.remoteAddr?.hostname
72
+ }
73
+
74
+ get userAgent(): string | undefined {
75
+ return this.#c.req.header('user-agent')
76
+ }
77
+
78
+ get routePath() {
79
+ return this.#c.req.routePath
80
+ }
81
+
82
+ get path() {
83
+ return this.#c.req.path
59
84
  }
60
85
 
61
- static async body<E>() {
62
- const cType = c.cx.req.header('Content-Type')
86
+ get url() {
87
+ return this.#c.req.raw.url
88
+ }
89
+
90
+ get method() {
91
+ return this.#c.req.raw.method
92
+ }
93
+
94
+ get matchedRoutes() {
95
+ return this.#c.req.matchedRoutes
96
+ }
97
+
98
+ get raw() {
99
+ return this.#c.req.raw
100
+ }
101
+
102
+ header(name: RequestHeader): string | undefined
103
+ header(name: string): string | undefined
104
+ header(): Record<RequestHeader | (string & CustomHeader), string>
105
+ header(name?: string) { // @ts-ignore
106
+ return this.#c.req.header(name)
107
+ }
108
+
109
+ param(key?: string) { // @ts-ignore
110
+ return this.#c.req.param(key)
111
+ }
112
+
113
+ query(): Record<string, string>
114
+ query(key: string): string | undefined
115
+ query(key?: string) { // @ts-ignore
116
+ return this.#c.req.query(key)
117
+ }
118
+
119
+ queries(): Record<string, string[]>
120
+ queries(key: string):string[] | undefined
121
+ queries(key?: string) { // @ts-ignore
122
+ return this.#c.req.queries(key)
123
+ }
124
+
125
+ async body<E>() {
126
+ const cType = this.#c.req.header('Content-Type')
63
127
  if (!cType) return {} as E
64
128
 
65
- if (/^application\/([a-z-\.]+\+)?json(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType)) {
66
- return await Request.json<E>()
67
- }
129
+ if (/^application\/([a-z-\.]+\+)?json(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType))
130
+ return await this.json<E>()
68
131
 
69
132
  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)
133
+ cType?.startsWith('multipart/form-data')
134
+ || cType?.startsWith('application/x-www-form-urlencoded')
72
135
  ) {
73
- return await Request.form() as E
136
+ return await this.parseBody() as E
74
137
  }
75
138
 
76
139
  return {} as E
77
140
  }
78
141
 
79
- static get response() {
80
- return Response
142
+ async parseBody<Options extends Partial<ParseBodyOptions>, T extends BodyData<Options>>(
143
+ options?: Options
144
+ ): Promise<T>
145
+ async parseBody<T extends BodyData>(options?: Partial<ParseBodyOptions>): Promise<T>
146
+ async parseBody(options?: Partial<ParseBodyOptions>) {
147
+ try {
148
+ return await this.#c.req.parseBody(options)
149
+ } catch (e) {
150
+ throw new HTTPException(400, {
151
+ message: 'Malformed FormData request.'+ (e instanceof Error ? ` ${e.message}` : ` ${String(e)}`)
152
+ })
153
+ }
154
+ }
155
+
156
+ async json<E>() {
157
+ try {
158
+ return await this.#c.req.json<E>()
159
+ } catch {
160
+ throw new HTTPException(400, { message: 'Malformed JSON in request body' })
161
+ }
81
162
  }
82
163
 
83
- static get cx() {
84
- return c.cx
164
+ text() {
165
+ return this.#c.req.text()
85
166
  }
86
167
 
87
- static get cookie() {
88
- return c.cookie
168
+ arrayBuffer() {
169
+ return this.#c.req.arrayBuffer()
89
170
  }
90
171
 
91
- static get ip() {
92
- return c.ip
172
+ blob() {
173
+ return this.#c.req.blob()
93
174
  }
94
175
 
95
- static get userAgent() {
96
- return c.userAgent
176
+ formData() {
177
+ return this.#c.req.formData()
97
178
  }
98
179
  }
package/src/response.ts CHANGED
@@ -1,37 +1,88 @@
1
- import type { ContentfulStatusCode, StatusCode } from 'hono/utils/http-status'
1
+ import type { ContentfulStatusCode, RedirectStatusCode, StatusCode } from 'hono/utils/http-status'
2
+ import type { BaseMime } from 'hono/utils/mime'
3
+ import type { ResponseHeader } from 'hono/utils/headers'
2
4
  import type { ErrorResponse, Errors } from './types'
3
- import c from './context'
5
+ import { HtmlEscapedCallbackPhase, resolveCallback } from 'hono/utils/html'
4
6
 
5
- export default class Response {
6
- static raw(status?: StatusCode, body?: string) {
7
- return c.cx.newResponse(body ? body : null, { status })
7
+ type HeaderRecord =
8
+ | Record<'Content-Type', BaseMime>
9
+ | Record<ResponseHeader, string | string[]>
10
+ | Record<string, string | string[]>
11
+
12
+ type RBag = {
13
+ status?: StatusCode,
14
+ headers?: HeaderRecord,
15
+ }
16
+
17
+ export default class $Response {
18
+ static raw(
19
+ status?: StatusCode,
20
+ body?: any,
21
+ cType?: BaseMime,
22
+ headers?: HeaderRecord
23
+ ) {
24
+ const b: RBag = { status: status || 200 }
25
+
26
+ if (cType || headers) {
27
+ headers ??= {}
28
+ if (cType) headers['Content-Type'] = cType
29
+ b.headers = headers
30
+ }
31
+
32
+ return new Response(body ?? null, b)
33
+ }
34
+
35
+ static text(data?: string, status?: StatusCode) {
36
+ return this.raw(status, data, 'text/plain; charset=UTF-8' as BaseMime)
37
+ }
38
+
39
+ static json<T>(data?: T, status?: StatusCode, headers?: HeaderRecord) {
40
+ if (data == null)
41
+ return this.raw(status)
42
+
43
+ return this.raw(status, JSON.stringify(data), 'application/json', headers)
44
+ }
45
+
46
+ static redirect(
47
+ location: string | URL,
48
+ status?: RedirectStatusCode,
49
+ headers?: HeaderRecord
50
+ ) {
51
+ const loc = String(location)
52
+
53
+ return new Response(null, {
54
+ status: status || 302,
55
+ headers: { ...headers, 'Location': /[^\x00-\xFF]/.test(loc) ? encodeURI(loc) : loc }
56
+ })
57
+ }
58
+
59
+ static html(
60
+ html: string | Promise<string>,
61
+ status?: ContentfulStatusCode,
62
+ headers?: HeaderRecord
63
+ ): Response | Promise<Response> {
64
+ const res = (html: string) => this.raw(status, html, 'text/html; charset=UTF-8' as BaseMime, headers)
65
+ return typeof html == 'string'
66
+ ? res(html)
67
+ : resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then(res)
8
68
  }
9
69
 
10
70
  static ok(): Response
11
71
  static ok<T>(data: T): Response
12
72
  static ok<T>(data?: T) {
13
- if (data === undefined)
14
- return this.raw(200)
15
-
16
- return c.cx.json(data, 200)
73
+ return this.json(data, 200)
17
74
  }
18
75
 
19
76
  static created(): Response
20
77
  static created<T>(data: T): Response
21
78
  static created<T>(data?: T) {
22
- if (data === undefined)
23
- return this.raw(201)
24
-
25
- return c.cx.json(data, 201)
79
+ return this.json(data, 201)
26
80
  }
27
81
 
28
82
  static accepted(): Response
29
83
  static accepted<T>(data: T): Response
30
84
  static accepted<T>(data?: T) {
31
- if (data === undefined)
32
- return this.raw(202)
33
-
34
- return c.cx.json(data, 202)
85
+ return this.json(data, 202)
35
86
  }
36
87
 
37
88
  static deleted() {
@@ -50,19 +101,13 @@ export default class Response {
50
101
  static unauthorized(): Response
51
102
  static unauthorized<T>(data: T): Response
52
103
  static unauthorized<T>(data?: T) {
53
- if (data === undefined)
54
- return this.raw(401)
55
-
56
- return c.cx.json(data, 401)
104
+ return this.json(data, 401)
57
105
  }
58
106
 
59
107
  static forbidden(): Response
60
108
  static forbidden<T>(data: T): Response
61
109
  static forbidden<T>(data?: T) {
62
- if (data === undefined)
63
- return this.raw(403)
64
-
65
- return c.cx.json(data, 403)
110
+ return this.json(data, 403)
66
111
  }
67
112
 
68
113
  static notFound(): Response
@@ -95,6 +140,6 @@ export default class Response {
95
140
  if (msg) resp.m = msg
96
141
  if (errors) resp.e = errors
97
142
 
98
- return c.cx.json(resp, status)
143
+ return this.json(resp, status)
99
144
  }
100
145
  }
package/src/routes.ts CHANGED
@@ -4,6 +4,11 @@ import { fileURLToPath } from 'node:url'
4
4
  import { Route } from './types'
5
5
  import { isAnonFn } from './utils/func'
6
6
 
7
+ import { writeFileSync } from 'node:fs'
8
+ import { config } from 'dotenv'
9
+ import ensureDir from './utils/ensuredir'
10
+ import versionSHA from './utils/version-sha'
11
+
7
12
  const __filename = fileURLToPath(import.meta.url)
8
13
  const __dirname = dirname(__filename)
9
14
 
@@ -29,7 +34,7 @@ const walk = async (dir: string, baseDir: string, fn: Function, parentMw: string
29
34
 
30
35
  if (stat.isDirectory()) {
31
36
  await walk(fullPath, baseDir, fn, currentMw)
32
- } else if (file !== 'index.ts' && file.endsWith('.ts') && !file.endsWith('.d.ts')) {
37
+ } else if (file != 'index.ts' && file.endsWith('.ts') && !file.endsWith('.d.ts')) {
33
38
  const mod = await import(fullPath)
34
39
  fn(fullPath, baseDir, mod.default, currentMw)
35
40
  }
@@ -65,7 +70,7 @@ export async function getRoutes(
65
70
  }
66
71
 
67
72
  function sortRoutes(routes: Route[]) {
68
- const metas = new Map<string, { score: number; segmentsCount: number }>()
73
+ const metas = new Map<string, { score: number, segmentsCount: number }>()
69
74
 
70
75
  for (const route of routes)
71
76
  metas.set(route.path, computeRouteMeta(route.path))
@@ -118,3 +123,62 @@ export async function getMiddlewares(
118
123
 
119
124
  return mw
120
125
  }
126
+
127
+ const env = Object.entries(
128
+ config({ path: '../../.env.prod' })?.parsed || {}
129
+ ).filter(([key, val]) => key?.toLowerCase().indexOf('aws') != 0) // prevent AWS credentials
130
+
131
+ const version = versionSHA('../../.git') // @ts-ignore
132
+ env.push(['VERSION_SHA', process.env['VERSION_SHA'] = version]) // @ts-ignore
133
+ env.push(['VERSION_HASH', process.env['VERSION_HASH'] = version?.substring(0, 7)])
134
+
135
+ export async function cacheRoutes() {
136
+ const rolePath = join(__dirname, '../../../roles.json')
137
+ if (!existsSync(rolePath))
138
+ writeFileSync(rolePath, '{}')
139
+
140
+ const routes = await getRoutes()
141
+ const middlewares = await getMiddlewares()
142
+
143
+ const iPath = join(__dirname, '../../../tmp/import-routes.mjs')
144
+ ensureDir(iPath)
145
+ writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
146
+ ${env?.length ? `import { Envir } from '../node_modules/t0n/dist/index'\nEnvir.add({${env.map(([key, val]) => key + ':' + JSON.stringify(val)).join(',')}})` : ''}
147
+
148
+ import { registerHandler, registerMiddleware } from '../node_modules/rajt/src/register'
149
+
150
+ ${routes.map(r => `import ${r.name} from '../${normalizePath(r.file)}'`).join('\n')}
151
+ ${middlewares.map(r => `import ${r.name} from '../${normalizePath(r.file)}'`).join('\n')}
152
+
153
+ try {
154
+ const handlers = {${routes.map(r => r.name).join()}}
155
+
156
+ for (const [name, handler] of Object.entries(handlers)) {
157
+ if (typeof handler == 'function' || handler.prototype?.handle) {
158
+ registerHandler(name, handler)
159
+ }
160
+ }
161
+
162
+ const middlewares = {${middlewares.map(r => r.name).join()}}
163
+
164
+ for (const [name, mw] of Object.entries(middlewares)) {
165
+ registerMiddleware(mw)
166
+ }
167
+ } catch (e) {
168
+ console.error('Failed to register handlers:', e)
169
+ }
170
+ `)
171
+
172
+ const rPath = join(__dirname, '../../../tmp/routes.json')
173
+ ensureDir(rPath)
174
+ writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
175
+ route.method,
176
+ route.path,
177
+ route.middlewares,
178
+ route.name,
179
+ ])))
180
+ }
181
+
182
+ function normalizePath(file: string) {
183
+ return file.replace(/\.tsx?$/i, '').replace(/(\/index)+$/i, '').replace(/\/+$/g, '')
184
+ }
@@ -1,67 +1,4 @@
1
- import { existsSync, writeFileSync } from 'node:fs'
2
- import { config } from 'dotenv'
3
- import { getRoutes, getMiddlewares } from '../routes'
4
- import ensureDir from '../utils/ensuredir'
5
- import versionSHA from '../utils/version-sha'
6
-
7
- const env = Object.entries(
8
- config({ path: '../../.env.prod' })?.parsed || {}
9
- ).filter(([key, val]) => key?.toLowerCase().indexOf('aws') != 0) // prevent AWS credentials
10
-
11
- const version = versionSHA('../../.git')
12
- env.push(['VERSION_SHA', process.env['VERSION_SHA'] = version])
13
- env.push(['VERSION_HASH', process.env['VERSION_HASH'] = version?.substring(0, 7)])
14
-
15
- async function cacheRoutes() {
16
- const rolePath = '../../roles.json'
17
- if (!existsSync(rolePath))
18
- writeFileSync(rolePath, '{}')
19
-
20
- const routes = await getRoutes()
21
- const middlewares = await getMiddlewares()
22
-
23
- const iPath = '../../tmp/import-routes.mjs'
24
- ensureDir(iPath)
25
- writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
26
- ${env?.length ? `import { Envir } from '../node_modules/t0n/dist/index'\nEnvir.add({${env.map(([key, val]) => key + ':' + JSON.stringify(val)).join(',')}})` : ''}
27
-
28
- import { registerHandler, registerMiddleware } from '../node_modules/rajt/src/register'
29
-
30
- ${routes.map(r => `import ${r.name} from '../${normalizePath(r.file)}'`).join('\n')}
31
- ${middlewares.map(r => `import ${r.name} from '../${normalizePath(r.file)}'`).join('\n')}
32
-
33
- try {
34
- const handlers = {${routes.map(r => r.name).join()}}
35
-
36
- for (const [name, handler] of Object.entries(handlers)) {
37
- if (typeof handler == 'function' || handler.prototype?.handle) {
38
- registerHandler(name, handler)
39
- }
40
- }
41
-
42
- const middlewares = {${middlewares.map(r => r.name).join()}}
43
-
44
- for (const [name, mw] of Object.entries(middlewares)) {
45
- registerMiddleware(mw)
46
- }
47
- } catch (e) {
48
- console.error('Failed to register handlers:', e)
49
- }
50
- `)
51
-
52
- const rPath = '../../tmp/routes.json'
53
- ensureDir(rPath)
54
- writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
55
- route.method,
56
- route.path,
57
- route.middlewares,
58
- route.name,
59
- ])))
60
- }
61
-
62
- function normalizePath(file: string) {
63
- return file.replace(/\.tsx?$/i, '').replace(/(\/index)+$/i, '').replace(/\/+$/g, '')
64
- }
1
+ import { cacheRoutes } from '../routes'
65
2
 
66
3
  cacheRoutes()
67
4
  .then(() => {
package/src/types.ts CHANGED
@@ -1,8 +1,29 @@
1
- import { Handler } from 'hono'
1
+ import type { Handler, ValidationTargets } from 'hono'
2
2
  import { ResponseHeader } from 'hono/utils/headers'
3
3
  import { StatusCode } from 'hono/utils/http-status'
4
4
  import { BaseMime } from 'hono/utils/mime'
5
+ import z from 'zod'
5
6
  import Action from './action'
7
+ import request from './request'
8
+ import response from './response'
9
+ import validator from './validator'
10
+
11
+ export type { Context, Next } from 'hono'
12
+
13
+ type PublicMethods<T> = {
14
+ [K in keyof T]: K extends `#${string}` | `$${string}` | symbol | 'prototype' ? never : K
15
+ }[keyof T]
16
+
17
+ export type IRequest = Pick<request, PublicMethods<request>>
18
+ export type IResponse = Pick<typeof response, PublicMethods<typeof response>>
19
+
20
+ export type IValidator = Pick<typeof validator, PublicMethods<typeof validator>>
21
+ export type Rule = {
22
+ schema: z.ZodObject<any>
23
+ target: keyof ValidationTargets
24
+ eTarget?: 'fieldErrors' | 'formErrors'
25
+ }
26
+ export type Rules = Rule[] | Rule | null
6
27
 
7
28
  export type Route = {
8
29
  method: string,
@@ -13,6 +34,8 @@ export type Route = {
13
34
  handle: Handlers,
14
35
  }
15
36
 
37
+ export type ActionType = Function | Handler | Action | (new () => Action)
38
+
16
39
  export type Handlers = (Function | Handler | (new () => Action))[]
17
40
 
18
41
  export type Routes = Route[]
@@ -45,6 +68,3 @@ export type ResponseOrInit<T extends StatusCode = StatusCode> = ResponseInit<T>
45
68
  // | null
46
69
  // | { [key: string]: JSONValue }
47
70
  // | JSONValue[]
48
-
49
-
50
- export type { Context, Next } from 'hono'
package/src/utils/func.ts CHANGED
@@ -4,5 +4,5 @@ export const isAsyncFn = (fn: any) => {
4
4
  }
5
5
 
6
6
  export const isAnonFn = (fn: any) => {
7
- return fn?.name === '' || fn?.name == 'anonymous'
7
+ return fn?.name == '' || fn?.name == 'anonymous'
8
8
  }
@@ -1,5 +1,6 @@
1
- import Action, { ActionType } from '../action'
1
+ import Action from '../action'
2
2
  import { MiddlewareType } from '../middleware'
3
+ import type { ActionType } from '../types'
3
4
 
4
5
  export function resolve(obj: ActionType, id: string) {
5
6
  if (typeof obj == 'function' && obj?.length == 2)
@@ -0,0 +1,68 @@
1
+ import { ZodObject } from 'zod'
2
+ import { zValidator } from '@hono/zod-validator'
3
+ import type { ValidationTargets } from 'hono'
4
+ import type { Rule, Rules } from './types'
5
+ import response from './response'
6
+
7
+ export default class $Validator {
8
+ private static cache = new Map<string, any>()
9
+
10
+ private static createRule<T extends keyof ValidationTargets>(
11
+ target: T,
12
+ schema?: ZodObject<any>
13
+ ):
14
+ | { schema: (schema: ZodObject<any>) => Rule }
15
+ | Rule
16
+ {
17
+ if (schema != null) {
18
+ return {
19
+ target,
20
+ schema,
21
+ eTarget: 'fieldErrors'
22
+ } satisfies Rule
23
+ }
24
+
25
+ return {
26
+ schema: (schema: ZodObject<any>) => ({
27
+ target,
28
+ schema,
29
+ eTarget: 'fieldErrors'
30
+ })
31
+ }
32
+ }
33
+
34
+ private static getOrCreateAlias<T extends keyof ValidationTargets>(target: T) {
35
+ if (this.cache.has(target))
36
+ return this.cache.get(target)
37
+
38
+ const aliasFunc = (schema?: ZodObject<any>) => {
39
+ if (schema != null)
40
+ return this.createRule(target, schema)
41
+
42
+ return this.createRule(target)
43
+ }
44
+
45
+ const typedAlias = aliasFunc as {
46
+ (): { schema: (schema: ZodObject<any>) => Rule },
47
+ (schema: ZodObject<any>): Rule,
48
+ }
49
+
50
+ this.cache.set(target, typedAlias)
51
+ return typedAlias
52
+ }
53
+
54
+ static readonly json = $Validator.getOrCreateAlias('json')
55
+ static readonly form = $Validator.getOrCreateAlias('form')
56
+ static readonly query = $Validator.getOrCreateAlias('query')
57
+ static readonly param = $Validator.getOrCreateAlias('param')
58
+ static readonly header = $Validator.getOrCreateAlias('header')
59
+ static readonly cookie = $Validator.getOrCreateAlias('cookie')
60
+
61
+ static parse(rules: Rules): Function[] {
62
+ return (Array.isArray(rules) ? rules : [rules]) // @ts-ignore
63
+ .flatMap(rule => zValidator(rule.target, rule.schema, (result, c) => {
64
+ if (!result.success) // @ts-ignore
65
+ return response.badRequest({ ...result.error.flatten()[rule.eTarget] })
66
+ }))
67
+ }
68
+ }