duto 0.260808.0 → 0.260906.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/README.md +1 -1
- package/package.json +51 -16
- package/src/cli.ts +42 -0
- package/src/enum.ts +2 -0
- package/src/index.d.ts +61 -0
- package/src/index.ts +15 -24
- package/src/middleware.ts +2 -0
- package/src/{compiler → pages/compiler}/vite-plugin.ts +1 -1
- package/src/{i18n → pages/i18n}/client.ts +1 -1
- package/src/pages/index.ts +28 -0
- package/src/server/adapter/aws.ts +4 -0
- package/src/server/adapter/workerd.ts +4 -0
- package/src/server/app.ts +96 -0
- package/src/server/auth/ability.ts +72 -0
- package/src/server/auth/authnz.ts +86 -0
- package/src/server/auth/index.ts +5 -0
- package/src/server/auth/token.ts +147 -0
- package/src/server/auth/types.ts +2 -0
- package/src/server/cli/commands/build.ts +41 -0
- package/src/server/cli/commands/deploy.ts +82 -0
- package/src/server/cli/commands/dev.ts +277 -0
- package/src/server/cli/commands/make.ts +98 -0
- package/src/server/cli/commands/migrate.ts +93 -0
- package/src/server/cli/commands/routes.ts +79 -0
- package/src/server/cli/index.ts +27 -0
- package/src/server/cli/stubs.ts +39 -0
- package/src/server/cli/types.ts +3 -0
- package/src/server/cli/utils.ts +443 -0
- package/src/server/config.ts +4 -0
- package/src/server/db/d1.d.ts +1 -0
- package/src/server/db/d1.ts +1 -0
- package/src/server/db/dynamodb.d.ts +1 -0
- package/src/server/db/dynamodb.ts +1 -0
- package/src/server/db/index.ts +1 -0
- package/src/server/entry.ts +19 -0
- package/src/server/handle.ts +30 -0
- package/src/server/http.ts +23 -0
- package/src/server/middlewares/auth.ts +40 -0
- package/src/server/middlewares/logger.ts +30 -0
- package/src/server/open-api/register.ts +29 -0
- package/src/server/open-api/spec.ts +107 -0
- package/src/server/request.ts +233 -0
- package/src/server/response.ts +142 -0
- package/src/server/routes.ts +474 -0
- package/src/server/types.ts +142 -0
- package/src/server/utils/environment.ts +17 -0
- package/src/server/utils.ts +46 -0
- package/src/server/validator.ts +70 -0
- package/src/utils.ts +5 -0
- package/src/cli/index.ts +0 -28
- package/src/cli/utils.ts +0 -146
- /package/src/{client → pages/client}/app.tsx +0 -0
- /package/src/{client → pages/client}/hmr.ts +0 -0
- /package/src/{client → pages/client}/island.ts +0 -0
- /package/src/{client → pages/client}/link.ts +0 -0
- /package/src/{client → pages/client}/router.ts +0 -0
- /package/src/{client → pages/client}/storage.ts +0 -0
- /package/src/{cli → pages}/commands/build.ts +0 -0
- /package/src/{cli → pages}/commands/dev-old.ts +0 -0
- /package/src/{cli → pages}/commands/dev.ts +0 -0
- /package/src/{cli → pages}/commands/preview.ts +0 -0
- /package/src/{compiler → pages/compiler}/aliases.ts +0 -0
- /package/src/{compiler → pages/compiler}/assets.ts +0 -0
- /package/src/{compiler → pages/compiler}/ast.ts +0 -0
- /package/src/{compiler → pages/compiler}/constants.ts +0 -0
- /package/src/{compiler → pages/compiler}/file.ts +0 -0
- /package/src/{compiler → pages/compiler}/index.ts +0 -0
- /package/src/{compiler → pages/compiler}/island.ts +0 -0
- /package/src/{compiler → pages/compiler}/meta.ts +0 -0
- /package/src/{compiler → pages/compiler}/routes.ts +0 -0
- /package/src/{compiler → pages/compiler}/types.ts +0 -0
- /package/src/{compiler → pages/compiler}/utils.ts +0 -0
- /package/src/{components → pages/components}/link.tsx +0 -0
- /package/src/{i18n → pages/i18n}/element.ts +0 -0
- /package/src/{i18n → pages/i18n}/server.ts +0 -0
- /package/src/{preact → pages/preact}/client-dev.ts +0 -0
- /package/src/{preact → pages/preact}/client.ts +0 -0
- /package/src/{preact → pages/preact}/context.ts +0 -0
- /package/src/{preact → pages/preact}/server.ts +0 -0
- /package/src/{preact → pages/preact}/signals.ts +0 -0
- /package/src/{preact → pages/preact}/static-html.ts +0 -0
- /package/src/{preact → pages/preact}/types.ts +0 -0
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2025-present,
|
|
3
|
+
Copyright (c) 2025-present, HUB.BI
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -23,4 +23,4 @@ This framework is fully geared towards the serverless world, specifically for st
|
|
|
23
23
|
|
|
24
24
|
## License
|
|
25
25
|
|
|
26
|
-
This package is licensed under the [MIT license](https://github.com/attla/duto/blob/main/LICENSE) © [
|
|
26
|
+
This package is licensed under the [MIT license](https://github.com/attla/duto/blob/main/LICENSE) © [HUB](https://hub.bi)
|
package/package.json
CHANGED
|
@@ -1,50 +1,84 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "duto",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.
|
|
3
|
+
"description": "All-in-one serverless framework for web artisans",
|
|
4
|
+
"version": "0.260906.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"bin": "./src/cli.ts",
|
|
7
9
|
"files": ["src"],
|
|
8
|
-
"bin": "./src/cli/index.ts",
|
|
9
10
|
"exports": {
|
|
10
|
-
".": "./src/index.ts"
|
|
11
|
+
".": "./src/index.ts",
|
|
12
|
+
"./auth": "./src/server/auth/index.ts",
|
|
13
|
+
"./db": "./src/db/index.ts",
|
|
14
|
+
"./database": "./src/db/index.ts",
|
|
15
|
+
"./d1": "./src/server/db/d1.ts",
|
|
16
|
+
"./d1/types": "./src/server/db/d1.d.ts",
|
|
17
|
+
"./dynamodb": "./src/server/db/dynamodb.ts",
|
|
18
|
+
"./dynamodb/types": "./src/server/db/dynamodb.d.ts",
|
|
19
|
+
"./enum": "./src/enum.ts",
|
|
20
|
+
"./http": "./src/server/http.ts",
|
|
21
|
+
"./middleware": "./src/middleware.ts",
|
|
22
|
+
"./types": "./src/server/types.ts",
|
|
23
|
+
"./types/global": "./global.d.ts",
|
|
24
|
+
"./env": "./src/server/utils/environment.ts",
|
|
25
|
+
"./length": "./src/server/utils/length.ts"
|
|
11
26
|
},
|
|
12
27
|
"dependencies": {
|
|
28
|
+
"@hono/node-server": "^1.19.11",
|
|
29
|
+
"@hono/standard-validator": "^0.2.2",
|
|
30
|
+
"@hono/zod-validator": "^0.7.6",
|
|
31
|
+
"@scalar/hono-api-reference": "^0.12.0",
|
|
32
|
+
"@minify-html/node": "^0.18.1",
|
|
13
33
|
"@preact/signals": "^2.9.2",
|
|
14
34
|
"@swc/core": "^1.15.21",
|
|
15
35
|
"chokidar": "^3.5.2",
|
|
16
36
|
"citty": "^0.2.2",
|
|
17
37
|
"consola": "^3.4.2",
|
|
38
|
+
"cripta": "0.x",
|
|
39
|
+
"dotenv": "^16.5.0",
|
|
40
|
+
"esbuild": "^0.25.2",
|
|
18
41
|
"filesize": "^11.0",
|
|
42
|
+
"forj": "0.x",
|
|
43
|
+
"hono": "^4.13.7",
|
|
44
|
+
"hono-openapi": "^1.3.0",
|
|
45
|
+
"localflare-api": "^0.5.0",
|
|
46
|
+
"localflare-core": "^0.5.0",
|
|
47
|
+
"miniflare": "^4.20260730.0",
|
|
19
48
|
"pathe": "^2.0",
|
|
20
49
|
"preact": "^10.29.7",
|
|
21
50
|
"preact-render-to-string": "^6.6.6",
|
|
51
|
+
"quansync": "^0.2.11",
|
|
22
52
|
"t0n": "0.x",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
53
|
+
"wouter-preact": "^3.10.0",
|
|
54
|
+
"wrangler": "^4.128.0",
|
|
55
|
+
"zod": "^4.3.6",
|
|
56
|
+
"zod-openapi": "^5.4.6"
|
|
25
57
|
},
|
|
26
58
|
"devDependencies": {
|
|
27
|
-
"@
|
|
28
|
-
"@
|
|
29
|
-
"
|
|
30
|
-
"@types/node": "^25.9.0",
|
|
31
|
-
"bun-types": "^1.3.14",
|
|
59
|
+
"@cloudflare/workers-types": "^4.20260113.0",
|
|
60
|
+
"@types/node": "^26.4.1",
|
|
61
|
+
"bun-types": "^1.4.0",
|
|
32
62
|
"typescript": "^5.9.3"
|
|
33
63
|
},
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
"
|
|
64
|
+
"resolutions": {
|
|
65
|
+
"@smithy/types": "^4.3.0",
|
|
66
|
+
"zod": "^4.3.6"
|
|
37
67
|
},
|
|
68
|
+
"sideEffects": false,
|
|
69
|
+
"packageManager": "bun@1.4.0",
|
|
38
70
|
"publishConfig": {
|
|
39
71
|
"registry": "https://registry.npmjs.org"
|
|
40
72
|
},
|
|
41
|
-
"author": "
|
|
73
|
+
"author": "HUB <foss@hub.bi>",
|
|
42
74
|
"license": "MIT",
|
|
43
|
-
"homepage": "https://
|
|
75
|
+
"homepage": "https://hub.bi/docs/duto",
|
|
44
76
|
"repository": "git://github.com/attla/duto",
|
|
45
77
|
"bugs": "https://github.com/attla/duto/issues",
|
|
46
78
|
"keywords": [
|
|
47
79
|
"SSG",
|
|
80
|
+
"app",
|
|
81
|
+
"http",
|
|
48
82
|
"application",
|
|
49
83
|
"framework",
|
|
50
84
|
"router",
|
|
@@ -52,6 +86,7 @@
|
|
|
52
86
|
"typescript",
|
|
53
87
|
"cloudflare",
|
|
54
88
|
"workers",
|
|
89
|
+
"workerd",
|
|
55
90
|
"vercel",
|
|
56
91
|
"bun"
|
|
57
92
|
]
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
function exit(msg?: string, desc?: string) {
|
|
4
|
+
msg && console.error(msg)
|
|
5
|
+
desc && console.log(desc)
|
|
6
|
+
process.exit(0)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!import.meta.main)
|
|
10
|
+
exit('Error: Duto must be used directly as CLI command.')
|
|
11
|
+
|
|
12
|
+
if (!process?.isBun || typeof Bun === 'undefined')
|
|
13
|
+
exit('Error: "Bun" is not available.', ' Try install: https://bun.sh/get')
|
|
14
|
+
|
|
15
|
+
global.__st = performance.now()
|
|
16
|
+
|
|
17
|
+
import { join } from 'pathe'
|
|
18
|
+
import { cli } from 't0n/cli'
|
|
19
|
+
import { Envir } from 't0n'
|
|
20
|
+
import { version } from '!/package.json'
|
|
21
|
+
import { _root, _duto } from './utils'
|
|
22
|
+
|
|
23
|
+
let commands = {
|
|
24
|
+
// new:
|
|
25
|
+
// upgrade:
|
|
26
|
+
}
|
|
27
|
+
let pkg
|
|
28
|
+
try {
|
|
29
|
+
pkg = await import(join(_root, 'package.json'))
|
|
30
|
+
if (!pkg || !pkg.duto || !pkg?.duto?.type || !['pages', 'server'].includes(pkg.duto.type))
|
|
31
|
+
exit('Error: invalid app type.')
|
|
32
|
+
|
|
33
|
+
Envir.set('duto', pkg.duto)
|
|
34
|
+
commands = (await import(join(_duto, pkg.duto.type, 'cli')))?.default
|
|
35
|
+
} catch {}
|
|
36
|
+
|
|
37
|
+
cli({
|
|
38
|
+
name: 'duto',
|
|
39
|
+
description: 'Duto',
|
|
40
|
+
version,
|
|
41
|
+
commands,
|
|
42
|
+
})
|
package/src/enum.ts
ADDED
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/// <reference types='bun-types' />
|
|
2
|
+
/// <reference types='node' />
|
|
3
|
+
|
|
4
|
+
// export { }
|
|
5
|
+
|
|
6
|
+
import * as Duto from './'
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
// Duto
|
|
10
|
+
var __st: number
|
|
11
|
+
var __hmr: boolean
|
|
12
|
+
|
|
13
|
+
type Req = Duto.Req
|
|
14
|
+
type Res = Duto.Res
|
|
15
|
+
type $v = Duto.$v
|
|
16
|
+
type IRequest = Duto.IRequest
|
|
17
|
+
type IResponse = Duto.IResponse
|
|
18
|
+
type IValidator = Duto.IValidator
|
|
19
|
+
|
|
20
|
+
type Context = Duto.Context
|
|
21
|
+
type Next = Duto.Next
|
|
22
|
+
|
|
23
|
+
type Abilities = Duto.Abilities
|
|
24
|
+
type Roles = Duto.Roles
|
|
25
|
+
|
|
26
|
+
type EnumStatic = Duto.EnumStatic
|
|
27
|
+
type EnumValue = Duto.EnumValue
|
|
28
|
+
type EnumType = Duto.EnumType
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// Bun:test
|
|
32
|
+
var test: typeof import('bun:test').test
|
|
33
|
+
var describe: typeof import('bun:test').describe
|
|
34
|
+
var it: typeof import('bun:test').it
|
|
35
|
+
var expect: typeof import('bun:test').expect
|
|
36
|
+
// Hooks
|
|
37
|
+
var beforeAll: typeof import('bun:test').beforeAll
|
|
38
|
+
var afterAll: typeof import('bun:test').afterAll
|
|
39
|
+
var beforeEach: typeof import('bun:test').beforeEach
|
|
40
|
+
var afterEach: typeof import('bun:test').afterEach
|
|
41
|
+
// Mocking
|
|
42
|
+
var mock: typeof import('bun:test').mock
|
|
43
|
+
var vi: typeof import('bun:test').vi
|
|
44
|
+
var jest: typeof import('bun:test').jest
|
|
45
|
+
var spyOn: typeof import('bun:test').spyOn
|
|
46
|
+
// Utils
|
|
47
|
+
var setSystemTime: typeof import('bun:test').setSystemTime
|
|
48
|
+
var setDefaultTimeout: typeof import('bun:test').setDefaultTimeout
|
|
49
|
+
// Types
|
|
50
|
+
var Test: typeof import('bun:test').Test
|
|
51
|
+
var Describe: typeof import('bun:test').Describe
|
|
52
|
+
var Expect: typeof import('bun:test').Expect
|
|
53
|
+
var Mock: typeof import('bun:test').Mock
|
|
54
|
+
var TestOptions: typeof import('bun:test').TestOptions
|
|
55
|
+
var Matchers: typeof import('bun:test').Matchers
|
|
56
|
+
// Namespace
|
|
57
|
+
namespace jest {
|
|
58
|
+
var fn: typeof import('bun:test').vi.fn
|
|
59
|
+
var mock: typeof import('bun:test').vi.mock
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
useLocation, useParams, useSearch, useSearchParams,
|
|
4
|
-
matchRoute,
|
|
5
|
-
} from 'wouter-preact'
|
|
1
|
+
export { default as Response } from './server/response'
|
|
2
|
+
export { default as Config } from './server/config'
|
|
6
3
|
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
getLocale, setLocale,
|
|
10
|
-
isPrefixed,
|
|
11
|
-
} from './i18n/client'
|
|
4
|
+
export { Enum, Envir } from 't0n'
|
|
5
|
+
export type { EnumStatic, EnumValue, EnumType } from 't0n'
|
|
12
6
|
|
|
13
|
-
export {
|
|
7
|
+
export type {
|
|
8
|
+
IRequest, IRequest as Req, IRequest as $req,
|
|
9
|
+
IResponse, IResponse as Res, IResponse as $res,
|
|
10
|
+
IValidator, IValidator as $v,
|
|
11
|
+
Context, Next,
|
|
12
|
+
} from './server/types'
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (window?.$duto?.sroutes?.has(to)) {
|
|
20
|
-
location.pathname = to
|
|
21
|
-
return
|
|
22
|
-
}
|
|
14
|
+
// Auth
|
|
15
|
+
export { Ability } from './server/auth/ability'
|
|
16
|
+
export { Authnz } from './server/auth/authnz'
|
|
17
|
+
export { Token } from './server/auth/token'
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
n(to, options)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export { Link } from './components/link'
|
|
19
|
+
export type { Abilities, Roles } from './server/auth/types'
|
|
@@ -9,7 +9,7 @@ import tailwind from '@tailwindcss/vite'
|
|
|
9
9
|
|
|
10
10
|
import { EXT_RE } from './constants'
|
|
11
11
|
import { setAsset, setChunk, setEntry } from './assets'
|
|
12
|
-
import { renderPage } from '
|
|
12
|
+
import { renderPage } from '.'
|
|
13
13
|
import { removeExports, resolveImport } from './ast'
|
|
14
14
|
import { getMetadata } from './meta'
|
|
15
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { h, type JSX } from 'preact'
|
|
2
2
|
import { local } from '#/storage'
|
|
3
|
-
import { navigate } from '
|
|
3
|
+
import { navigate } from '..'
|
|
4
4
|
|
|
5
5
|
const accepted = (lang: string | undefined, locales: string[] = [], fallback: string) => lang && locales.includes(lang) ? lang : fallback
|
|
6
6
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Redirect,
|
|
3
|
+
useLocation, useParams, useSearch, useSearchParams,
|
|
4
|
+
matchRoute,
|
|
5
|
+
} from 'wouter-preact'
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
t, r, d,
|
|
9
|
+
getLocale, setLocale,
|
|
10
|
+
isPrefixed,
|
|
11
|
+
} from './i18n/client'
|
|
12
|
+
|
|
13
|
+
export { local } from './client/storage'
|
|
14
|
+
|
|
15
|
+
export const navigate = <S = any>(
|
|
16
|
+
to: string | URL,
|
|
17
|
+
options?: { replace?: boolean; state?: S; transition?: boolean }
|
|
18
|
+
) => {
|
|
19
|
+
if (window?.$duto?.sroutes?.has(to)) {
|
|
20
|
+
location.pathname = to
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const n = window?.$duto?.navigate || useLocation()[1]
|
|
25
|
+
n(to, options)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Link } from './components/link'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { Envir } from 't0n'
|
|
3
|
+
import type {
|
|
4
|
+
Env, Context, Next,
|
|
5
|
+
HTTPResponseError,
|
|
6
|
+
ServerOptions, Route
|
|
7
|
+
} from './types'
|
|
8
|
+
import request, { GET_REQUEST } from './request'
|
|
9
|
+
import response from './response'
|
|
10
|
+
import { isDev } from './utils/environment'
|
|
11
|
+
import { getVerb } from './http'
|
|
12
|
+
import { registerMiddleware, resolveHandle, resolveMw } from './handle'
|
|
13
|
+
|
|
14
|
+
const NFHandler = () => response.notFound()
|
|
15
|
+
const EHandler = async (e: Error | HTTPResponseError) => {
|
|
16
|
+
console.error(e)
|
|
17
|
+
|
|
18
|
+
switch (true) {
|
|
19
|
+
// case 'status' in e && e.status === 401:
|
|
20
|
+
case e?.status === 401:
|
|
21
|
+
return response.unauthorized()
|
|
22
|
+
|
|
23
|
+
// case 'status' in e && e.status === 400: // @ts-ignore
|
|
24
|
+
case e?.status === 400:
|
|
25
|
+
return response.badRequest(null, e?.message)
|
|
26
|
+
|
|
27
|
+
default:
|
|
28
|
+
return response.internalError(
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
isDev() // TODO: remover ou migrar para um middleware / melhorar
|
|
31
|
+
? e.stack?.split('\n').map(line =>
|
|
32
|
+
line.replace(
|
|
33
|
+
/at (.+ )?\(?([^)]+)\)?/g,
|
|
34
|
+
(match, method, path) => {
|
|
35
|
+
if (!path) return match
|
|
36
|
+
|
|
37
|
+
const nodeModulesIndex = path.indexOf('node_modules')
|
|
38
|
+
if (nodeModulesIndex > -1)
|
|
39
|
+
return `${method || ''}(node_modules${path.slice(nodeModulesIndex + 'node_modules'.length)})`
|
|
40
|
+
|
|
41
|
+
const projectRoot = process.cwd()
|
|
42
|
+
const relativePath = path.startsWith(projectRoot) ? path.slice(projectRoot.length + 1) : path
|
|
43
|
+
return `${method || ''}(${relativePath})`
|
|
44
|
+
}
|
|
45
|
+
).trim()
|
|
46
|
+
)
|
|
47
|
+
: undefined,
|
|
48
|
+
e.message || 'Internal Error'
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// return json.internalError(
|
|
53
|
+
// // @ts-ignore
|
|
54
|
+
// isDev() ? e.stack?.split('\n at ').map() : undefined,
|
|
55
|
+
// e.message || 'Internal Error'
|
|
56
|
+
// )
|
|
57
|
+
// error: e.message,
|
|
58
|
+
// cause: e.cause || '???',
|
|
59
|
+
// stack: isDev (? e.stack : undefined
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
|
|
63
|
+
// const root = options?.root ?? '/'
|
|
64
|
+
const app = options?.app ?? new Hono<E>()
|
|
65
|
+
|
|
66
|
+
app.use(async (c: Context, next: Next) => {
|
|
67
|
+
await request.toContext(c)
|
|
68
|
+
await next()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const middlewares = options?.middlewares || []
|
|
72
|
+
for (const mw of middlewares) { // @ts-ignore
|
|
73
|
+
registerMiddleware(mw[2], mw[1]) // @ts-ignore
|
|
74
|
+
mw[0] ? app.use(String(mw[0]), resolveMw(mw[1])) : app.use(resolveMw(mw[1]))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
app.onError(options?.onError || EHandler)
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
app.notFound(options?.notFound || NFHandler)
|
|
81
|
+
|
|
82
|
+
if (options?.init) options.init(app)
|
|
83
|
+
|
|
84
|
+
const routes = options?.routes || [] // @ts-ignore
|
|
85
|
+
const routeRegister = options?.routeRegister ? options.routeRegister : (_: Hono, route: Route) => {
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
_[getVerb[route[0]]](route[1], ...resolveHandle(route[2], route[3], route[4]))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const route of routes)
|
|
91
|
+
routeRegister(app, route)
|
|
92
|
+
|
|
93
|
+
return app
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default createApp
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { verbAlias } from '$/http'
|
|
2
|
+
import type { IRequest, Routes } from '$/types'
|
|
3
|
+
import type { Roles, Abilities } from './types'
|
|
4
|
+
|
|
5
|
+
export class Ability {
|
|
6
|
+
static #roles: Roles = {}
|
|
7
|
+
static #abilities: Abilities = []
|
|
8
|
+
static #actions: Record<string, string> = {}
|
|
9
|
+
|
|
10
|
+
static empty() {
|
|
11
|
+
this.#roles = {}
|
|
12
|
+
this.#abilities = []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static fromRoutes(routes: Routes) {
|
|
16
|
+
if (!routes?.length) return
|
|
17
|
+
|
|
18
|
+
const actions: Record<string, string> = {}
|
|
19
|
+
const abilities = new Set()
|
|
20
|
+
for (const route of routes) {
|
|
21
|
+
const isArr = Array.isArray(route)
|
|
22
|
+
const name = isArr ? route[route.length-1] : route.name
|
|
23
|
+
if (!name || abilities.has(name)) continue
|
|
24
|
+
|
|
25
|
+
abilities.add(name)
|
|
26
|
+
actions[(isArr ? route[1] : route.path) +'$'+ verbAlias[isArr ? route[0] : route.method]] = name
|
|
27
|
+
const formatted = this.format(name)
|
|
28
|
+
|
|
29
|
+
if (formatted)
|
|
30
|
+
this.#abilities.push(formatted)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.#actions = actions
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static fromRequest(req: IRequest) {
|
|
37
|
+
const ability = this.#actions[`${req.routePath}$` + verbAlias[req.method.toLowerCase()]]
|
|
38
|
+
return ability ? this.format(ability) : ''
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static fromAction(target: any): string {
|
|
42
|
+
return !target ? '' : this.format(typeof target === 'string' ? target : (target.name.length > 3 ? target.name : (target?.p || '')))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static format(path: string) {
|
|
46
|
+
return path === '/'
|
|
47
|
+
? 'index'
|
|
48
|
+
: path.normalize('NFD')
|
|
49
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
50
|
+
.replace(/^\/*/, '')
|
|
51
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
52
|
+
.replace(/[^a-zA-Z0-9/]|[\s\-.]/g, '_')
|
|
53
|
+
.replace(/_+/g, '_')
|
|
54
|
+
.replace(/\//g, '.')
|
|
55
|
+
.replace(/\._/g, '.')
|
|
56
|
+
.replace(/^[._-]+/, '')
|
|
57
|
+
.replace(/[._-]+$/, '')
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static get abilities() {
|
|
62
|
+
return this.#abilities
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static get roles() {
|
|
66
|
+
return this.#roles
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static set roles(roles: Roles) {
|
|
70
|
+
this.#roles = roles
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Ability } from './ability'
|
|
2
|
+
import type { Token } from 'cripta/token'
|
|
3
|
+
|
|
4
|
+
export class Authnz<T extends object> {
|
|
5
|
+
#abilities: string[]
|
|
6
|
+
#roles: string[]
|
|
7
|
+
#data: T
|
|
8
|
+
#token: Token
|
|
9
|
+
|
|
10
|
+
constructor(token: Token, data: T, abilities: string[], roles: string[]) {
|
|
11
|
+
this.#abilities = abilities
|
|
12
|
+
this.#roles = roles
|
|
13
|
+
this.#token = token
|
|
14
|
+
this.#data = data
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
can(...abilities: string[]): boolean {
|
|
18
|
+
if (this.#abilities.includes('*')) return true
|
|
19
|
+
|
|
20
|
+
return abilities.flat().every(ability => {
|
|
21
|
+
if (this.#roles.includes(ability)) return true
|
|
22
|
+
return this.#abilities.some(rule => this.#match(rule, ability))
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
cant(...abilities: string[]): boolean {
|
|
26
|
+
return !this.can(...abilities)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
hasRole(...roles: string[]): boolean {
|
|
30
|
+
return roles.flat().every(role => this.#roles.includes(role))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// has(prop: string, value: any = null): boolean {
|
|
34
|
+
// return this.#token?.hasValue(prop, value) || false
|
|
35
|
+
// }
|
|
36
|
+
// hasValue(prop: string, value: any = null): boolean {
|
|
37
|
+
// return this.has(prop, value)
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
static fromToken<T extends object>(token: Token): Authnz<T> | null {
|
|
41
|
+
if (!token || !token?.valid) return null
|
|
42
|
+
const user = token.body
|
|
43
|
+
const roles = [...(user?.role ? [user.role] : []), ...(user?.roles ?? [])]
|
|
44
|
+
|
|
45
|
+
const combined = [...(user?.perms ?? []), ...roles.flatMap(role => {
|
|
46
|
+
const perms = Ability.roles[role]
|
|
47
|
+
if (!perms) return []
|
|
48
|
+
return perms === '*' ? ['*'] : perms
|
|
49
|
+
})]
|
|
50
|
+
|
|
51
|
+
const abilities = combined.includes('*') ? ['*'] : Array.from(new Set(combined))
|
|
52
|
+
|
|
53
|
+
return new Authnz<T>(token, user, abilities, roles)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#match(rule: string, ability: string): boolean {
|
|
57
|
+
if (rule === ability) return true
|
|
58
|
+
if (
|
|
59
|
+
this.#wildcardMatch(rule, ability, '_*')
|
|
60
|
+
|| this.#wildcardMatch(rule, ability, '-*')
|
|
61
|
+
|| this.#wildcardMatch(rule, ability, '.*')
|
|
62
|
+
) return true
|
|
63
|
+
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#wildcardMatch(rule: string, ability: string, suffix: string) {
|
|
68
|
+
return rule.endsWith(suffix)
|
|
69
|
+
&& (ability.startsWith(rule.slice(0, -2) + suffix[0]) || ability === rule.slice(0, -2))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get abilities() {
|
|
73
|
+
return this.#abilities
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get roles() {
|
|
77
|
+
return this.#roles
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get data() {
|
|
81
|
+
return this.#data
|
|
82
|
+
}
|
|
83
|
+
get user() {
|
|
84
|
+
return this.#data
|
|
85
|
+
}
|
|
86
|
+
}
|