rajt 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/package.json +45 -1
- package/src/action.ts +151 -0
- package/src/create-app.ts +124 -0
- package/src/dev.ts +22 -0
- package/src/exceptions.ts +15 -0
- package/src/http.ts +57 -0
- package/src/index.ts +3 -0
- package/src/middleware.ts +12 -0
- package/src/prod.ts +16 -0
- package/src/register.ts +25 -0
- package/src/response.ts +137 -0
- package/src/routes.ts +63 -0
- package/src/scripts/cache-routes.ts +49 -0
- package/src/types.ts +54 -0
- package/src/utils/ensuredir.ts +7 -0
- package/src/utils/environment.ts +22 -0
- package/src/utils/func.ts +8 -0
- package/src/utils/port.ts +30 -0
- package/src/utils/resolve.ts +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present, ZUNQ.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<p align="left">
|
|
2
|
+
<a href="https://npmjs.com/package/rajt"><img src="https://img.shields.io/npm/v/rajt.svg" alt="npm package"></a>
|
|
3
|
+
<a href="https://pr.new/attla/rajt"><img src="https://developer.stackblitz.com/img/start_pr_dark_small.svg" alt="Start new PR in StackBlitz Codeflow"></a>
|
|
4
|
+
</p>
|
|
5
|
+
<br/>
|
|
6
|
+
|
|
7
|
+
# Rajt λ
|
|
8
|
+
|
|
9
|
+
> This framework is fully geared towards the serverless world, specifically AWS Lambda (Node.js and LLRT) / Cloudflare Workers.
|
|
10
|
+
|
|
11
|
+
- 💡 Instant Server Start
|
|
12
|
+
- ⚡️ Fast Cold Start
|
|
13
|
+
- 📦 Optimized Build
|
|
14
|
+
|
|
15
|
+
[Read the Docs to Learn More](DOCS.md).
|
|
16
|
+
|
|
17
|
+
## Packages
|
|
18
|
+
|
|
19
|
+
| Package | Version (click for changelogs) |
|
|
20
|
+
| ----------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| [rajt](https://github.com/attla/rajt) | [](https://github.com/attla/rajt/CHANGELOG.md) |
|
|
22
|
+
| [create-rajt](https://github.com/attla/create-rajt) | [](https://github.com/attla/create-rajt/CHANGELOG.md) |
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
This package is licensed under the [MIT license](LICENSE) © [Zunq](https://zunq.com).
|
package/package.json
CHANGED
|
@@ -1 +1,45 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "rajt",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./http": "./src/http.ts",
|
|
9
|
+
"./types": "./src/types.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"sam": "sam local start-api --warm-containers LAZY --debug",
|
|
16
|
+
"dev": "tsx watch src/dev.ts",
|
|
17
|
+
"prod": "bun run build && bun run sam",
|
|
18
|
+
"build": "bun run cache:routes && bun run export && bun run clean:temp",
|
|
19
|
+
"build:watch": "chokidar \"{actions,src,models,utils,configs}/**/*.ts\" -c \"bun run build\" --initial",
|
|
20
|
+
"export": "esbuild --bundle --minify --outfile=dist/index.js --platform=node --target=node20 --format=esm --tree-shaking=true --legal-comments=none src/prod.ts",
|
|
21
|
+
"cache:routes": "tsx src/scripts/cache-routes.ts",
|
|
22
|
+
"ensure-dirs": "rm -rf dist tmp && mkdir -p ./tmp && chmod 755 ./tmp && mkdir -p ./dist && chmod 755 ./dist",
|
|
23
|
+
"clean": "rm -rf dist tmp",
|
|
24
|
+
"clean:build": "rm -rf dist",
|
|
25
|
+
"clean:temp": "rm -rf tmp",
|
|
26
|
+
"zip": "zip -j lambda.zip dist/index.js",
|
|
27
|
+
"update": "aws lambda update-function-code --zip-file fileb://lambda.zip --function-name hello",
|
|
28
|
+
"deploy": "run-s build zip update",
|
|
29
|
+
"start": "node dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@hono/zod-validator": "^0.4.3",
|
|
33
|
+
"dotenv": "^16.5.0",
|
|
34
|
+
"hono": "^4.7.6"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@hono/node-server": "^1.14.1",
|
|
38
|
+
"@types/node": "^20.11.0",
|
|
39
|
+
"chokidar-cli": "^3.0.0",
|
|
40
|
+
"esbuild": "^0.25.2",
|
|
41
|
+
"tsconfig-paths": "^4.2.0",
|
|
42
|
+
"tsx": "^4.19.3",
|
|
43
|
+
"typescript": "^5.8.3"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/action.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Context, Handler, HonoRequest, MiddlewareHandler, Next, ValidationTargets } from 'hono'
|
|
2
|
+
import { z, ZodObject, ZodRawShape } from 'zod'
|
|
3
|
+
import { zValidator } from '@hono/zod-validator'
|
|
4
|
+
// import { JSONValue } from 'hono/utils/types'
|
|
5
|
+
import json from './response'
|
|
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'
|
|
10
|
+
|
|
11
|
+
export type ActionType = Function | Handler | Action | (new () => Action)
|
|
12
|
+
|
|
13
|
+
type RuleDefinition = {
|
|
14
|
+
schema: z.ZodObject<any>
|
|
15
|
+
target: keyof ValidationTargets
|
|
16
|
+
eTarget?: 'fieldErrors' | 'formErrors'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default abstract class Action {
|
|
20
|
+
protected context!: Context
|
|
21
|
+
protected errorTarget!: string
|
|
22
|
+
protected rules(): RuleDefinition[] | RuleDefinition | null {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
rule<T extends keyof ValidationTargets>(target: T): { schema: (schema: ZodObject<any>) => RuleDefinition }
|
|
27
|
+
rule<T extends keyof ValidationTargets>(target: T, schema: ZodObject<any>): RuleDefinition
|
|
28
|
+
|
|
29
|
+
public rule<T extends keyof ValidationTargets>(target: T, schema?: ZodObject<any>):
|
|
30
|
+
| { schema: (schema: ZodObject<any>) => RuleDefinition }
|
|
31
|
+
| RuleDefinition
|
|
32
|
+
{
|
|
33
|
+
if (schema !== undefined) {
|
|
34
|
+
return {
|
|
35
|
+
target,
|
|
36
|
+
schema,
|
|
37
|
+
eTarget: 'fieldErrors' // | 'formErrors'
|
|
38
|
+
} satisfies RuleDefinition
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
schema: (schema: ZodObject<any>) => ({
|
|
43
|
+
target,
|
|
44
|
+
schema,
|
|
45
|
+
eTarget: 'fieldErrors' // | 'formErrors'
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public param(key: string) {
|
|
51
|
+
return this.context.req.param(key)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public query() {
|
|
55
|
+
return this.context.req.query()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async form(cType?: string) {
|
|
59
|
+
cType ??= this.context.req.header('Content-Type')
|
|
60
|
+
if (!cType) return {}
|
|
61
|
+
|
|
62
|
+
let formData: FormData
|
|
63
|
+
|
|
64
|
+
if (this.context.req.bodyCache.formData) {
|
|
65
|
+
formData = await this.context.req.bodyCache.formData
|
|
66
|
+
} else {
|
|
67
|
+
try {
|
|
68
|
+
const arrayBuffer = await this.context.req.arrayBuffer()
|
|
69
|
+
formData = await bufferToFormData(arrayBuffer, cType)
|
|
70
|
+
this.context.req.bodyCache.formData = formData
|
|
71
|
+
} catch (e) {
|
|
72
|
+
throw new HTTPException(400, {
|
|
73
|
+
message: 'Malformed FormData request.'
|
|
74
|
+
+ (e instanceof Error ? ` ${e.message}` : ` ${String(e)}`)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const form: BodyData<{ all: true }> = {}
|
|
80
|
+
formData.forEach((value, key) => {
|
|
81
|
+
if (key.endsWith('[]')) {
|
|
82
|
+
;((form[key] ??= []) as unknown[]).push(value)
|
|
83
|
+
} else if (Array.isArray(form[key])) {
|
|
84
|
+
;(form[key] as unknown[]).push(value)
|
|
85
|
+
} else if (key in form) {
|
|
86
|
+
form[key] = [form[key] as string | File, value]
|
|
87
|
+
} else {
|
|
88
|
+
form[key] = value
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return form
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public async json<E>() {
|
|
96
|
+
try {
|
|
97
|
+
return await this.context.req.json<E>()
|
|
98
|
+
} catch {
|
|
99
|
+
throw new HTTPException(400, { message: 'Malformed JSON in request body' })
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public async body<E>() {
|
|
104
|
+
const cType = this.context.req.header('Content-Type')
|
|
105
|
+
if (!cType) return {}
|
|
106
|
+
|
|
107
|
+
if (/^application\/([a-z-\.]+\+)?json(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType)) {
|
|
108
|
+
return await this.json<E>()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
/^multipart\/form-data(;\s?boundary=[a-zA-Z0-9'"()+_,\-./:=?]+)?$/.test(cType)
|
|
113
|
+
&& ! /^application\/x-www-form-urlencoded(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/.test(cType)
|
|
114
|
+
) {
|
|
115
|
+
return await this.form() as E
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public get response() {
|
|
122
|
+
return this.context ? JsonResponse.setContext(this.context) : JsonResponse
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public validate() {
|
|
126
|
+
const rules = this.rules()
|
|
127
|
+
const h = async (c: Context) => {
|
|
128
|
+
this.context = c
|
|
129
|
+
return await this.handle(c)
|
|
130
|
+
}
|
|
131
|
+
if (!rules) return [h]
|
|
132
|
+
|
|
133
|
+
const rulesArray = (Array.isArray(rules) ? rules : [rules])
|
|
134
|
+
.map(rule => zValidator(rule.target, rule.schema, (result, c) => {
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
// @ts-ignore
|
|
137
|
+
return json.badRequest({ ...result.error.flatten()[rule.eTarget] })
|
|
138
|
+
}
|
|
139
|
+
}))
|
|
140
|
+
|
|
141
|
+
rulesArray.push(h)
|
|
142
|
+
|
|
143
|
+
return rulesArray
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public run() {
|
|
147
|
+
return this.validate()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
abstract handle(c: Context): Promise<Response>
|
|
151
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Hono } from 'hono'
|
|
3
|
+
import type { Env, Context, ErrorHandler, NotFoundHandler, Next } from 'hono'
|
|
4
|
+
// import type { Env, Context, ErrorHandler, MiddlewareHandler, NotFoundHandler, Next } from 'hono'
|
|
5
|
+
// import { createMiddleware } from 'hono/factory'
|
|
6
|
+
// import type { H, Handler, HandlerResponse } from 'hono/types'
|
|
7
|
+
import { HTTPResponseError } from 'hono/types'
|
|
8
|
+
import { Routes } from './types'
|
|
9
|
+
import { BadRequest, Unauthorized } from './exceptions'
|
|
10
|
+
import response from './response'
|
|
11
|
+
import resolve from './utils/resolve'
|
|
12
|
+
import { getHandler } from './register'
|
|
13
|
+
import env from './utils/environment'
|
|
14
|
+
|
|
15
|
+
type InitFunction<E extends Env = Env> = (app: Hono<E>) => void
|
|
16
|
+
|
|
17
|
+
export type ServerOptions<E extends Env = Env> = Partial<{
|
|
18
|
+
routes: Routes,
|
|
19
|
+
notFound: NotFoundHandler<E>,
|
|
20
|
+
onError: ErrorHandler<E>,
|
|
21
|
+
root: string,
|
|
22
|
+
app?: Hono<E>,
|
|
23
|
+
init?: InitFunction<E>,
|
|
24
|
+
}>
|
|
25
|
+
|
|
26
|
+
const isDev = env() === 'dev'
|
|
27
|
+
const NFHandler = () => response.notFound()
|
|
28
|
+
const EHandler = async (e: Error | HTTPResponseError) => {
|
|
29
|
+
console.error(e)
|
|
30
|
+
|
|
31
|
+
switch (true) {
|
|
32
|
+
case e instanceof Unauthorized:
|
|
33
|
+
case 'status' in e && e.status === 401:
|
|
34
|
+
return response.unauthorized()
|
|
35
|
+
|
|
36
|
+
case e instanceof BadRequest:
|
|
37
|
+
case 'status' in e && e.status === 400:
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
return response.badRequest(undefined, e?.message)
|
|
40
|
+
|
|
41
|
+
// case e.message.includes('Not Found'):
|
|
42
|
+
// // @ts-ignore
|
|
43
|
+
// case e?.status === 404:
|
|
44
|
+
// return json.notFound();
|
|
45
|
+
|
|
46
|
+
default:
|
|
47
|
+
return response.internalError(
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
isDev
|
|
50
|
+
? e.stack?.split('\n').map(line =>
|
|
51
|
+
line.replace(
|
|
52
|
+
/at (.+ )?\(?([^)]+)\)?/g,
|
|
53
|
+
(match, method, path) => {
|
|
54
|
+
if (!path) return match
|
|
55
|
+
|
|
56
|
+
const nodeModulesIndex = path.indexOf('node_modules')
|
|
57
|
+
if (nodeModulesIndex > -1) {
|
|
58
|
+
return `${method || ''}(node_modules${path.slice(nodeModulesIndex + 'node_modules'.length)})`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const projectRoot = process.cwd()
|
|
62
|
+
const relativePath = path.startsWith(projectRoot)
|
|
63
|
+
? path.slice(projectRoot.length + 1)
|
|
64
|
+
: path
|
|
65
|
+
return `${method || ''}(${relativePath})`
|
|
66
|
+
}
|
|
67
|
+
).trim()
|
|
68
|
+
)
|
|
69
|
+
: undefined,
|
|
70
|
+
e.message || 'Internal Error'
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// return json.internalError(
|
|
75
|
+
// // @ts-ignore
|
|
76
|
+
// isDev ? e.stack?.split('\n at ').map() : undefined,
|
|
77
|
+
// e.message || 'Internal Error'
|
|
78
|
+
// )
|
|
79
|
+
// error: e.message,
|
|
80
|
+
// cause: e.cause || 'Desconhecido',
|
|
81
|
+
// stack: isDev ? e.stack : undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
|
|
85
|
+
// const root = options?.root ?? '/'
|
|
86
|
+
const app = options?.app ?? new Hono<E>()
|
|
87
|
+
|
|
88
|
+
const middlewares = [
|
|
89
|
+
async (c: Context, next: Next) => {
|
|
90
|
+
response.setContext(c)
|
|
91
|
+
await next()
|
|
92
|
+
},
|
|
93
|
+
]
|
|
94
|
+
middlewares.forEach(middleware => app.use(middleware))
|
|
95
|
+
|
|
96
|
+
app.onError(options?.onError || EHandler)
|
|
97
|
+
app.notFound(options?.notFound || NFHandler)
|
|
98
|
+
|
|
99
|
+
if (options?.init)
|
|
100
|
+
options.init(app)
|
|
101
|
+
|
|
102
|
+
const routes = options?.routes || []
|
|
103
|
+
for (const route of routes) {
|
|
104
|
+
if (Array.isArray(route)) {
|
|
105
|
+
const handle = getHandler(route[3])
|
|
106
|
+
// @ts-ignore
|
|
107
|
+
app[route[0]](route[1], ...mw(route[2], route[3]), ...resolve(handle))
|
|
108
|
+
} else {
|
|
109
|
+
// @ts-ignore
|
|
110
|
+
app[route.method](route.path, ...mw(route.middlewares, route.name), ...resolve(route.handle))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return app
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function mw(...objs: string[]): Function[] {
|
|
118
|
+
return objs.flatMap(obj => {
|
|
119
|
+
// @ts-ignore
|
|
120
|
+
return getHandler(obj)?.mw || null
|
|
121
|
+
}).flat().filter(Boolean)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default createApp
|
package/src/dev.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { config } from 'dotenv'
|
|
2
|
+
import { serve } from '@hono/node-server'
|
|
3
|
+
import createApp from './create-app'
|
|
4
|
+
import getRoutes from './routes'
|
|
5
|
+
import { getAvailablePort } from './utils/port'
|
|
6
|
+
|
|
7
|
+
config({ path: '.env.dev' })
|
|
8
|
+
|
|
9
|
+
const routes = await getRoutes()
|
|
10
|
+
const fetch = createApp({ routes }).fetch
|
|
11
|
+
|
|
12
|
+
const desiredPort = process.env?.PORT ? Number(process.env.PORT) : 3000
|
|
13
|
+
getAvailablePort(desiredPort)
|
|
14
|
+
.then(port => {
|
|
15
|
+
if (port != desiredPort)
|
|
16
|
+
console.log(`🟨 Port ${desiredPort} was in use, using ${port} as a fallback`)
|
|
17
|
+
|
|
18
|
+
console.log(`🚀 API running on http://localhost:${port}`)
|
|
19
|
+
serve({ fetch, port })
|
|
20
|
+
}).catch(err => {
|
|
21
|
+
console.error('Error finding available port:', err)
|
|
22
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class Unauthorized extends Error {
|
|
2
|
+
status = 401
|
|
3
|
+
constructor(message = 'Unauthorized') {
|
|
4
|
+
super(message)
|
|
5
|
+
this.name = 'UnauthorizedError'
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class BadRequest extends Error {
|
|
10
|
+
status = 400
|
|
11
|
+
constructor(message = 'Bad Request') {
|
|
12
|
+
super(message)
|
|
13
|
+
this.name = 'BadRequestError'
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/http.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono'
|
|
2
|
+
import BaseMiddleware, { MiddlewareType } from './middleware'
|
|
3
|
+
|
|
4
|
+
function method(method: string, path = '/') {
|
|
5
|
+
return function (target: any) {
|
|
6
|
+
target.m = method
|
|
7
|
+
target.p = path
|
|
8
|
+
target.mw = []
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Get(path = '/') {
|
|
13
|
+
return method('get', path)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Post(path = '/') {
|
|
17
|
+
return method('post', path)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Put(path = '/') {
|
|
21
|
+
return method('put', path)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Patch(path = '/') {
|
|
25
|
+
return method('patch', path)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Delete(path = '/') {
|
|
29
|
+
return method('delete', path)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Middleware(...handlers: MiddlewareType[]) {
|
|
33
|
+
return function (target: any) {
|
|
34
|
+
const middlewareHandlers = handlers.map(handler => {
|
|
35
|
+
if (typeof handler === 'function' && handler.length === 2)
|
|
36
|
+
return handler
|
|
37
|
+
|
|
38
|
+
if (handler instanceof BaseMiddleware)
|
|
39
|
+
return handler.handle
|
|
40
|
+
|
|
41
|
+
if (BaseMiddleware.isPrototypeOf(handler)) {
|
|
42
|
+
const instance = new (handler as new () => BaseMiddleware)()
|
|
43
|
+
return instance.handle
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw new Error('Invalid middleware provided. Must be a Hono middleware function or MiddlewareClass instance/constructor')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const existingMiddlewares: MiddlewareHandler[] = target?.mw || []
|
|
50
|
+
const allMiddlewares = [...existingMiddlewares, ...middlewareHandlers]
|
|
51
|
+
|
|
52
|
+
target.mw = allMiddlewares
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function Middlewares(...handlers: MiddlewareType[]) {
|
|
56
|
+
return Middleware(...handlers)
|
|
57
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Context, MiddlewareHandler, Next } from 'hono'
|
|
2
|
+
|
|
3
|
+
export type IMiddleware = {
|
|
4
|
+
handle(c: Context, next: Next): Promise<void> | void
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default abstract class Middleware implements IMiddleware {
|
|
8
|
+
public abstract handle(c: Context, next: Next): Promise<void> | void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// export type MiddlewareHandler = (c: Context, next: Next) => Promise<void> | void
|
|
12
|
+
export type MiddlewareType = MiddlewareHandler | Middleware | (new () => Middleware)
|
package/src/prod.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { config } from 'dotenv'
|
|
2
|
+
import { handle } from 'hono/aws-lambda'
|
|
3
|
+
import createApp from './create-app'
|
|
4
|
+
|
|
5
|
+
config({ path: '.env.prod' })
|
|
6
|
+
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
await import('../tmp/import-routes.mjs')
|
|
9
|
+
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
const routes = (await import('../tmp/routes.json')).default
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
const app = createApp({ routes })
|
|
14
|
+
|
|
15
|
+
// export default app // AWS Lambda & Cloudflare Workers
|
|
16
|
+
export const handler = handle(app) // AWS Lambda (LLRT)
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const handlers = {}
|
|
2
|
+
|
|
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
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getHandler(id: string): Function {
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
const handler = handlers[id] || null
|
|
23
|
+
if (!handler) throw new Error(`Handler ${id} not registered`)
|
|
24
|
+
return handler
|
|
25
|
+
}
|
package/src/response.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Context } from 'hono'
|
|
2
|
+
import { ContentfulStatusCode, StatusCode } from 'hono/utils/http-status'
|
|
3
|
+
import { ErrorResponse, Errors, LambdaResponse, ResponseOrInit } from './types'
|
|
4
|
+
import { InvalidJSONValue, JSONValue } from 'hono/utils/types'
|
|
5
|
+
|
|
6
|
+
class NullContext {
|
|
7
|
+
resp = new Response('Context not found. Use JsonResponse.setContext() first.', { status: 500 })
|
|
8
|
+
|
|
9
|
+
newResponse(body?: any, init?: ResponseOrInit<StatusCode>): Response {
|
|
10
|
+
return this.resp
|
|
11
|
+
}
|
|
12
|
+
json(json: JSONValue | {} | InvalidJSONValue, status: ContentfulStatusCode = 500): Response {
|
|
13
|
+
return this.resp
|
|
14
|
+
}
|
|
15
|
+
// overide do hono context
|
|
16
|
+
// newResponse(body?: any, init?: ResponseOrInit<StatusCode>): Response {
|
|
17
|
+
// return new Response(body ?? null, typeof init === 'number' ? {status: init} : init ?? {status: 500})
|
|
18
|
+
// }
|
|
19
|
+
// json(json: JSONValue | {} | InvalidJSONValue, status: ContentfulStatusCode = 500): Response {
|
|
20
|
+
// return new Response(JSON.stringify(json), {
|
|
21
|
+
// status,
|
|
22
|
+
// headers: { 'Content-Type': 'application/json' }
|
|
23
|
+
// })
|
|
24
|
+
// }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default class JsonResponse {
|
|
28
|
+
private static _c?: Context
|
|
29
|
+
|
|
30
|
+
static setContext(c: Context) {
|
|
31
|
+
this._c = c
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private static context(): Context | NullContext {
|
|
36
|
+
return this._c ?? new NullContext()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static raw(status?: StatusCode, body?: string) {
|
|
40
|
+
return this.context().newResponse(body ? body : null, { status })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static ok(): Response
|
|
44
|
+
static ok<T>(data: T): Response
|
|
45
|
+
static ok<T>(data?: T) {
|
|
46
|
+
if (data === undefined)
|
|
47
|
+
return this.raw(200)
|
|
48
|
+
|
|
49
|
+
return this.context().json(data, 200)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static created(): Response
|
|
53
|
+
static created<T>(data: T): Response
|
|
54
|
+
static created<T>(data?: T) {
|
|
55
|
+
if (data === undefined)
|
|
56
|
+
return this.raw(201)
|
|
57
|
+
|
|
58
|
+
return this.context().json(data, 201)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static accepted(): Response
|
|
62
|
+
static accepted<T>(data: T): Response
|
|
63
|
+
static accepted<T>(data?: T) {
|
|
64
|
+
if (data === undefined)
|
|
65
|
+
return this.raw(202)
|
|
66
|
+
|
|
67
|
+
return this.context().json(data, 202)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static deleted(): Response
|
|
71
|
+
static deleted<T>(data: T): Response
|
|
72
|
+
static deleted<T>(data?: T) {
|
|
73
|
+
return this.noContent()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static noContent() {
|
|
77
|
+
return this.raw(204)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static badRequest(): Response
|
|
81
|
+
static badRequest(errors?: Errors, msg?: string) {
|
|
82
|
+
return this.error(errors, msg, 400)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static unauthorized(): Response
|
|
86
|
+
static unauthorized<T>(data: T): Response
|
|
87
|
+
static unauthorized<T>(data?: T) {
|
|
88
|
+
if (data === undefined)
|
|
89
|
+
return this.raw(401)
|
|
90
|
+
|
|
91
|
+
return this.context().json(data, 401)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static forbidden(): Response
|
|
95
|
+
static forbidden<T>(data: T): Response
|
|
96
|
+
static forbidden<T>(data?: T) {
|
|
97
|
+
if (data === undefined)
|
|
98
|
+
return this.raw(403)
|
|
99
|
+
|
|
100
|
+
return this.context().json(data, 403)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static notFound(): Response
|
|
104
|
+
static notFound(msg: string): Response
|
|
105
|
+
static notFound(msg?: string) {
|
|
106
|
+
return this.raw(404, msg)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static conflict(): Response
|
|
110
|
+
static conflict(errors?: Errors, msg?: string) {
|
|
111
|
+
return this.error(errors, msg, 409)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static unsupportedMediaType(): Response
|
|
115
|
+
static unsupportedMediaType(errors?: Errors, msg?: string) {
|
|
116
|
+
return this.error(errors, msg, 415)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static internalError(): Response
|
|
120
|
+
static internalError(errors?: Errors, msg?: string) {
|
|
121
|
+
return this.error(errors, msg, 500)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static error(errors?: Errors, msg?: string, status?: ContentfulStatusCode) {
|
|
125
|
+
const context = this.context()
|
|
126
|
+
status ??= 500
|
|
127
|
+
|
|
128
|
+
if (!errors && !msg)
|
|
129
|
+
return this.raw(status, msg)
|
|
130
|
+
|
|
131
|
+
const resp: ErrorResponse = {}
|
|
132
|
+
if (msg) resp.m = msg
|
|
133
|
+
if (errors) resp.e = errors
|
|
134
|
+
|
|
135
|
+
return context.json(resp, status)
|
|
136
|
+
}
|
|
137
|
+
}
|
package/src/routes.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path, { relative } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { Route } from './types'
|
|
5
|
+
import { registerHandler } from './register'
|
|
6
|
+
import { isAnonFn } from './utils/func'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
export default async function getRoutes(all: boolean = false, baseDir: string = 'actions'): Promise<Route[]> {
|
|
12
|
+
const routes: Route[] = []
|
|
13
|
+
|
|
14
|
+
const walk = async (dir: string, middlewares: Function[] = []): Promise<void> => {
|
|
15
|
+
const files = fs.readdirSync(dir)
|
|
16
|
+
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const fullPath = path.join(dir, file)
|
|
19
|
+
const stat = fs.statSync(fullPath)
|
|
20
|
+
|
|
21
|
+
if (stat.isDirectory()) {
|
|
22
|
+
const indexFile = path.join(fullPath, 'index.ts')
|
|
23
|
+
|
|
24
|
+
if (fs.existsSync(indexFile)) {
|
|
25
|
+
const mod = await import(indexFile)
|
|
26
|
+
const group = mod.default
|
|
27
|
+
registerHandler(group.name, group)
|
|
28
|
+
!isAnonFn(group) && middlewares.push(group.name)
|
|
29
|
+
|
|
30
|
+
all && routes.push({
|
|
31
|
+
method: '',
|
|
32
|
+
path: '',
|
|
33
|
+
name: group.name.replace(/\.ts$/, ''),
|
|
34
|
+
file: relative(process.cwd(), indexFile),
|
|
35
|
+
middlewares,
|
|
36
|
+
handle: group,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await walk(fullPath, middlewares)
|
|
41
|
+
} else if (file.endsWith('.ts')) {
|
|
42
|
+
const mod = await import(fullPath)
|
|
43
|
+
const handle = mod.default
|
|
44
|
+
|
|
45
|
+
if (handle?.m) {
|
|
46
|
+
registerHandler(handle.name, handle)
|
|
47
|
+
|
|
48
|
+
routes.push({
|
|
49
|
+
method: handle.m.toLowerCase(),
|
|
50
|
+
path: handle.p,
|
|
51
|
+
name: handle.name.replace(/\.ts$/, ''),
|
|
52
|
+
file: relative(process.cwd(), fullPath),
|
|
53
|
+
middlewares,
|
|
54
|
+
handle,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await walk(path.resolve(__dirname, '..', baseDir))
|
|
62
|
+
return routes
|
|
63
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs'
|
|
2
|
+
import getRoutes from '../routes'
|
|
3
|
+
import ensureDir from '../utils/ensuredir'
|
|
4
|
+
|
|
5
|
+
async function cacheRoutes() {
|
|
6
|
+
const routes = await getRoutes(true)
|
|
7
|
+
|
|
8
|
+
const iPath = './tmp/import-routes.mjs'
|
|
9
|
+
ensureDir(iPath)
|
|
10
|
+
writeFileSync(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
|
|
11
|
+
import { registerHandler } from '../src/register'
|
|
12
|
+
|
|
13
|
+
${routes.map(r => `import ${r.name} from '../${normalizePath(r.file)}'`).join('\n')}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const handlers = {${routes.map(r => r.name).join()}}
|
|
17
|
+
|
|
18
|
+
for (const [name, handler] of Object.entries(handlers)) {
|
|
19
|
+
if (typeof handler === 'function' || handler.prototype?.handle) {
|
|
20
|
+
registerHandler(name, handler)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error('Failed to register handlers:', error)
|
|
25
|
+
}
|
|
26
|
+
`)
|
|
27
|
+
|
|
28
|
+
const rPath = './tmp/routes.json'
|
|
29
|
+
ensureDir(rPath)
|
|
30
|
+
writeFileSync(rPath, JSON.stringify(routes.filter(r => r.method && r.path).map(route => [
|
|
31
|
+
route.method,
|
|
32
|
+
route.path,
|
|
33
|
+
route.middlewares,
|
|
34
|
+
route.name,
|
|
35
|
+
])))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizePath(file: string) {
|
|
39
|
+
return file.replace(/\.tsx?$/i, '').replace(/(\/index)+$/i, '').replace(/\/+$/g, '')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
cacheRoutes()
|
|
43
|
+
.then(() => {
|
|
44
|
+
console.log('✅ Routes cached!')
|
|
45
|
+
process.exit(0)
|
|
46
|
+
}).catch(e => {
|
|
47
|
+
console.error('❌ Error: ', e)
|
|
48
|
+
process.exit(1)
|
|
49
|
+
})
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Handler } from 'hono'
|
|
2
|
+
import { ResponseHeader } from 'hono/utils/headers'
|
|
3
|
+
import { StatusCode } from 'hono/utils/http-status'
|
|
4
|
+
import { BaseMime } from 'hono/utils/mime'
|
|
5
|
+
import Action from './action'
|
|
6
|
+
|
|
7
|
+
export type Route = {
|
|
8
|
+
method: string,
|
|
9
|
+
path: string,
|
|
10
|
+
name: string,
|
|
11
|
+
file: string,
|
|
12
|
+
middlewares: Function[],
|
|
13
|
+
handle: Handlers,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type Handlers = (Function | Handler | (new () => Action))[]
|
|
17
|
+
|
|
18
|
+
export type Routes = Route[]
|
|
19
|
+
|
|
20
|
+
export type LambdaResponse = {
|
|
21
|
+
statusCode: StatusCode,
|
|
22
|
+
body: string,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type Errors = Record<string, string | string[]>
|
|
26
|
+
export type ErrorResponse = {
|
|
27
|
+
m?: string, // message
|
|
28
|
+
// c?: number, // http code
|
|
29
|
+
e?: Errors, // error bag
|
|
30
|
+
// e?: Record<string, string | string[]>, // error bag
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// export type Response<E> = E | ErrorResponse
|
|
34
|
+
|
|
35
|
+
export type ResponseHeadersInit = [
|
|
36
|
+
string,
|
|
37
|
+
string
|
|
38
|
+
][] | Record<"Content-Type", BaseMime> | Record<ResponseHeader, string> | Record<string, string> | Headers
|
|
39
|
+
export type ResponseInit<T extends StatusCode = StatusCode> = {
|
|
40
|
+
headers?: ResponseHeadersInit,
|
|
41
|
+
status?: T,
|
|
42
|
+
statusText?: string,
|
|
43
|
+
}
|
|
44
|
+
export type ResponseOrInit<T extends StatusCode = StatusCode> = ResponseInit<T> | Response
|
|
45
|
+
// export type JSONValue =
|
|
46
|
+
// | string
|
|
47
|
+
// | number
|
|
48
|
+
// | boolean
|
|
49
|
+
// | null
|
|
50
|
+
// | { [key: string]: JSONValue }
|
|
51
|
+
// | JSONValue[]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
export type { Context, Next } from 'hono'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default function getEnvironment() {
|
|
2
|
+
if (process.env?.npm_lifecycle_event === 'dev')
|
|
3
|
+
return 'dev'
|
|
4
|
+
|
|
5
|
+
if (
|
|
6
|
+
process.argv?.includes('--dev')
|
|
7
|
+
|| process.execArgv?.includes('--watch')
|
|
8
|
+
|| import.meta.url?.includes('localhost')
|
|
9
|
+
)
|
|
10
|
+
return 'dev'
|
|
11
|
+
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
if (typeof Bun === 'undefined') return 'prod'
|
|
14
|
+
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
if (Bun.argv.includes('--prod')) return 'prod'
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
if (Bun.argv.includes('--dev') || Bun.main.endsWith('.ts')) return 'dev'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
return 'prod'
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import net from 'node:net'
|
|
2
|
+
|
|
3
|
+
export async function isPortInUse(port: number) {
|
|
4
|
+
return new Promise(resolve => {
|
|
5
|
+
const server = net.createServer()
|
|
6
|
+
.once('error', () => resolve(true))
|
|
7
|
+
.once('listening', () => {
|
|
8
|
+
server.close(() => resolve(false))
|
|
9
|
+
})
|
|
10
|
+
.listen(port)
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function getAvailablePort(startPort: number, maxAttempts = 100) {
|
|
15
|
+
let port = startPort
|
|
16
|
+
let attempts = 0
|
|
17
|
+
|
|
18
|
+
while (attempts < maxAttempts) {
|
|
19
|
+
const inUse = await isPortInUse(port)
|
|
20
|
+
|
|
21
|
+
if (!inUse) {
|
|
22
|
+
return port
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
port++
|
|
26
|
+
attempts++
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
throw new Error(`No available ports found after ${maxAttempts} attempts`)
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Action, { ActionType } from '../action'
|
|
2
|
+
// import BaseMiddleware, { MiddlewareType } from '../middleware'
|
|
3
|
+
|
|
4
|
+
export default function resolve(obj: ActionType) {
|
|
5
|
+
if (typeof obj === 'function' && obj?.length === 2)
|
|
6
|
+
return [obj]
|
|
7
|
+
|
|
8
|
+
if (obj instanceof Action)
|
|
9
|
+
return obj.run()
|
|
10
|
+
|
|
11
|
+
const instance = new (obj as new () => Action)()
|
|
12
|
+
if (Action.isPrototypeOf(obj))
|
|
13
|
+
return instance.run()
|
|
14
|
+
|
|
15
|
+
if (obj?.prototype?.handle)
|
|
16
|
+
return [instance.handle]
|
|
17
|
+
|
|
18
|
+
throw new Error('Invalid action')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// export function resolveMiddleware(obj: MiddlewareType) {
|
|
22
|
+
// if (typeof obj === 'function' && obj.length === 2)
|
|
23
|
+
// return obj
|
|
24
|
+
|
|
25
|
+
// if (obj instanceof BaseMiddleware)
|
|
26
|
+
// return obj.handle
|
|
27
|
+
|
|
28
|
+
// if (BaseMiddleware.isPrototypeOf(obj)) {
|
|
29
|
+
// const instance = new (obj as new () => BaseMiddleware)()
|
|
30
|
+
// return instance.handle
|
|
31
|
+
// }
|
|
32
|
+
|
|
33
|
+
// throw new Error('Invalid middleware provided. Must be a Hono middleware function or MiddlewareClass instance/constructor')
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
// // import Action, { ActionType } from '../action'
|
|
37
|
+
|
|
38
|
+
// export default function resolve(obj: any) {
|
|
39
|
+
// if (typeof obj === 'function' && obj?.length === 2)
|
|
40
|
+
// return [obj]
|
|
41
|
+
|
|
42
|
+
// // if (obj instanceof Action)
|
|
43
|
+
// // return obj.run()
|
|
44
|
+
|
|
45
|
+
// // const instance = new (obj as new () => Action)()
|
|
46
|
+
// // @ts-ignore
|
|
47
|
+
// const instance = new obj()
|
|
48
|
+
// // if (Action.isPrototypeOf(obj))
|
|
49
|
+
// if (obj?.prototype?.run)
|
|
50
|
+
// return instance.run()
|
|
51
|
+
|
|
52
|
+
// if (obj?.prototype?.handle)
|
|
53
|
+
// return [instance.handle]
|
|
54
|
+
|
|
55
|
+
// throw new Error('Invalid action')
|
|
56
|
+
// }
|