jax-hono 1.0.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/README.md +17 -0
- package/bin/jax.ts +68 -0
- package/build/build.ts +19 -0
- package/build/generate-config.ts +115 -0
- package/build/generate-registry.ts +615 -0
- package/config.ts +76 -0
- package/core/api-controller.ts +244 -0
- package/core/app.ts +199 -0
- package/core/controller.ts +86 -0
- package/core/hono.ts +64 -0
- package/core/service.ts +46 -0
- package/docs/jax.md +272 -0
- package/helpers/config.ts +23 -0
- package/helpers/crud.ts +30 -0
- package/helpers/index.ts +4 -0
- package/helpers/route.ts +7 -0
- package/index.ts +114 -0
- package/middleware/api-response.ts +44 -0
- package/middleware/jwt.ts +76 -0
- package/middleware/request-log.ts +3 -0
- package/package.json +64 -0
- package/plugins/config.ts +8 -0
- package/plugins/define.ts +5 -0
- package/plugins/mongoose/index.ts +57 -0
- package/plugins/mongoose/model.ts +281 -0
- package/plugins/mongoose/schema.ts +27 -0
- package/plugins/session/index.ts +20 -0
- package/setup.ts +11 -0
- package/types/config.ts +13 -0
- package/types/context.ts +10 -0
- package/types/index.ts +2 -0
- package/utils/array.ts +7 -0
- package/utils/crypto.ts +9 -0
- package/utils/index.ts +3 -0
- package/utils/regexp.ts +17 -0
- package/utils/transform.ts +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Jax-hono
|
|
2
|
+
|
|
3
|
+
Jax 是本项目的轻量框架层。完整说明见 [docs/jax.md](docs/jax.md)。
|
|
4
|
+
|
|
5
|
+
维护 Jax 内部代码时按目录职责放置文件:
|
|
6
|
+
|
|
7
|
+
```txt
|
|
8
|
+
core/ Hono runtime、生命周期、Controller/Service
|
|
9
|
+
middleware/ 框架中间件
|
|
10
|
+
plugins/ 插件定义、内置插件配置、内置插件实现
|
|
11
|
+
build/ 代码生成/扫描注册
|
|
12
|
+
helpers/ 框架辅助函数
|
|
13
|
+
utils/ 纯工具函数
|
|
14
|
+
errors/ 统一错误类型
|
|
15
|
+
types/ 框架公共类型
|
|
16
|
+
generated/ 自动生成的注册表
|
|
17
|
+
```
|
package/bin/jax.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { cac } from "cac";
|
|
4
|
+
import { $ } from "bun";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
const cli = cac("jax");
|
|
8
|
+
|
|
9
|
+
cli
|
|
10
|
+
.command(
|
|
11
|
+
"link",
|
|
12
|
+
"Create a symlink (./jax -> jax-hono installation directory)",
|
|
13
|
+
)
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const sourceDir = path.resolve(import.meta.dir, "..");
|
|
16
|
+
const targetDir = path.resolve(process.cwd(), "jax");
|
|
17
|
+
|
|
18
|
+
if (process.platform === "win32") {
|
|
19
|
+
const result =
|
|
20
|
+
await $`cmd /c mklink /D ${targetDir} ${sourceDir}`.nothrow();
|
|
21
|
+
if (result.exitCode !== 0) {
|
|
22
|
+
console.error(
|
|
23
|
+
"Failed to create symlink. Make sure you run as Administrator.",
|
|
24
|
+
);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
console.log(`Symlink created: ${targetDir} <<===>> ${sourceDir}`);
|
|
28
|
+
} else {
|
|
29
|
+
const relativeSource = path.relative(path.dirname(targetDir), sourceDir);
|
|
30
|
+
const result = await $`ln -sf ${relativeSource} ${targetDir}`.nothrow();
|
|
31
|
+
if (result.exitCode !== 0) {
|
|
32
|
+
console.error(`Failed to create symlink: ${result.stderr.toString()}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
console.log(`Symlink created: ${targetDir} -> ${relativeSource}`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
cli
|
|
40
|
+
.command("open", "Open jax-hono installation directory in file manager")
|
|
41
|
+
.action(async () => {
|
|
42
|
+
const sourceDir = path.resolve(import.meta.dir, "..");
|
|
43
|
+
|
|
44
|
+
let command: string[];
|
|
45
|
+
if (process.platform === "darwin") {
|
|
46
|
+
command = ["open", sourceDir];
|
|
47
|
+
} else if (process.platform === "win32") {
|
|
48
|
+
command = ["explorer", sourceDir];
|
|
49
|
+
} else {
|
|
50
|
+
command = ["xdg-open", sourceDir];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const [cmd, ...args] = command;
|
|
54
|
+
const result = await $`${cmd} ${args}`.nothrow();
|
|
55
|
+
if (result.exitCode !== 0) {
|
|
56
|
+
console.error(`Failed to open directory: ${result.stderr.toString()}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
console.log(`Opened: ${sourceDir}`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
cli.help();
|
|
63
|
+
const parsed = cli.parse();
|
|
64
|
+
|
|
65
|
+
// Show help when no command is matched (e.g. bare `jax` or unknown command)
|
|
66
|
+
if (!cli.matchedCommand && !parsed.options.help) {
|
|
67
|
+
cli.outputHelp();
|
|
68
|
+
}
|
package/build/build.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { setupJax } from '../setup'
|
|
2
|
+
|
|
3
|
+
setupJax()
|
|
4
|
+
|
|
5
|
+
const result = await Bun.build({
|
|
6
|
+
entrypoints: ['src/index.ts'],
|
|
7
|
+
outdir: 'dist',
|
|
8
|
+
target: 'bun',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
if (!result.success) {
|
|
12
|
+
for (const log of result.logs) {
|
|
13
|
+
console.error(log)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log('Build completed.')
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join, parse, relative } from 'node:path'
|
|
3
|
+
|
|
4
|
+
const srcDir = join(import.meta.dir, '../..')
|
|
5
|
+
const configDir = join(srcDir, 'config')
|
|
6
|
+
const typesFile = join(srcDir, 'types/index.ts')
|
|
7
|
+
const generatedDir = join(srcDir, 'jax/generated')
|
|
8
|
+
const outputFile = join(generatedDir, 'config.ts')
|
|
9
|
+
const envStages = new Set([
|
|
10
|
+
'default',
|
|
11
|
+
'local',
|
|
12
|
+
'dev',
|
|
13
|
+
'development',
|
|
14
|
+
'test',
|
|
15
|
+
'staging',
|
|
16
|
+
'pre',
|
|
17
|
+
'uat',
|
|
18
|
+
'prod',
|
|
19
|
+
'production',
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
function toImportName(fileName: string, index: number) {
|
|
23
|
+
return `config${index}_${parse(fileName).name.replace(/[^a-zA-Z0-9_$]/g, '_')}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getStage(fileName: string) {
|
|
27
|
+
const stage = parse(fileName).name.split('.').at(-1)
|
|
28
|
+
|
|
29
|
+
if (stage && envStages.has(stage)) {
|
|
30
|
+
return stage
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return 'base'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getImportPath(fileName: string) {
|
|
37
|
+
const { dir, name } = parse(fileName)
|
|
38
|
+
const path = join('../../config', dir, name).replaceAll('\\', '/')
|
|
39
|
+
|
|
40
|
+
return path.startsWith('.') ? path : `./${path}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getConfigFiles() {
|
|
44
|
+
if (!existsSync(configDir)) {
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return readdirSync(configDir)
|
|
49
|
+
.filter((fileName) => /^config\.[^.]+\.ts$/.test(fileName))
|
|
50
|
+
.sort((left, right) => {
|
|
51
|
+
const stageWeight: Record<string, number> = {
|
|
52
|
+
base: 0,
|
|
53
|
+
default: 1,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const diff = (stageWeight[getStage(left)] ?? 2) - (stageWeight[getStage(right)] ?? 2)
|
|
57
|
+
|
|
58
|
+
if (diff !== 0) {
|
|
59
|
+
return diff
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return left.localeCompare(right)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasProjectConfigType() {
|
|
67
|
+
if (!existsSync(typesFile)) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const source = readFileSync(typesFile, 'utf8')
|
|
72
|
+
|
|
73
|
+
return /\bexport\s+(?:type|interface)\s+ProjectConfig\b/.test(source)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function generateConfig() {
|
|
77
|
+
const files = getConfigFiles()
|
|
78
|
+
const defaultConfigIndex = files.findIndex((fileName) => getStage(fileName) === 'default')
|
|
79
|
+
const imports = files.map((fileName, index) => {
|
|
80
|
+
return `import ${toImportName(fileName, index)} from '${getImportPath(fileName)}'`
|
|
81
|
+
})
|
|
82
|
+
const modules = files.map((fileName, index) => {
|
|
83
|
+
return ` { name: '${fileName}', stage: '${getStage(fileName)}', config: ${toImportName(fileName, index)} },`
|
|
84
|
+
})
|
|
85
|
+
const hasProjectTypes = hasProjectConfigType()
|
|
86
|
+
const typeImports = hasProjectTypes ? [`import type { ProjectConfig } from '../../types'`] : []
|
|
87
|
+
const projectConfigType = hasProjectTypes
|
|
88
|
+
? 'export type { ProjectConfig }'
|
|
89
|
+
: defaultConfigIndex === -1
|
|
90
|
+
? 'export type ProjectConfig = Record<string, unknown>'
|
|
91
|
+
: `export type ProjectConfig = typeof ${toImportName(files[defaultConfigIndex], defaultConfigIndex)}`
|
|
92
|
+
const content = `${[...typeImports, ...imports].join('\n')}
|
|
93
|
+
|
|
94
|
+
export const configModules = [
|
|
95
|
+
${modules.join('\n')}
|
|
96
|
+
] as const
|
|
97
|
+
|
|
98
|
+
${projectConfigType}
|
|
99
|
+
|
|
100
|
+
export default configModules
|
|
101
|
+
`
|
|
102
|
+
|
|
103
|
+
mkdirSync(generatedDir, { recursive: true })
|
|
104
|
+
|
|
105
|
+
if (existsSync(outputFile) && readFileSync(outputFile, 'utf8') === content) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
writeFileSync(outputFile, content)
|
|
110
|
+
console.log(`Generated ${relative(srcDir, outputFile)} with ${files.length} config file(s).`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (import.meta.main) {
|
|
114
|
+
generateConfig()
|
|
115
|
+
}
|