jax-hono 1.0.1 → 1.0.3
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 +1 -1
- package/bin/jax.js +66 -0
- package/bin/jax.ts +82 -24
- package/build/generate-config.ts +4 -5
- package/build/generate-registry.ts +4 -5
- package/build/generated-path.ts +57 -0
- package/docs/jax.md +7 -7
- package/index.ts +2 -0
- package/middleware/jwt.ts +30 -25
- package/package.json +7 -5
- package/plugins/mongoose/model.ts +1 -1
- package/plugins/session/index.ts +84 -18
- package/types/config.ts +3 -7
package/README.md
CHANGED
package/bin/jax.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const cliPath = path.join(__dirname, "jax.ts");
|
|
11
|
+
|
|
12
|
+
function getBunCommand() {
|
|
13
|
+
const candidates = [];
|
|
14
|
+
|
|
15
|
+
if (process.env.BUN_INSTALL) {
|
|
16
|
+
candidates.push(
|
|
17
|
+
path.join(
|
|
18
|
+
process.env.BUN_INSTALL,
|
|
19
|
+
"bin",
|
|
20
|
+
process.platform === "win32" ? "bun.exe" : "bun",
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (process.platform === "win32") {
|
|
26
|
+
const nodeDir = path.dirname(process.execPath);
|
|
27
|
+
candidates.push(
|
|
28
|
+
path.join(nodeDir, "bun.exe"),
|
|
29
|
+
path.join(nodeDir, "node_modules", "bun", "bin", "bun.exe"),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
if (existsSync(candidate)) {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return "bun";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const child = spawn(getBunCommand(), [cliPath, ...process.argv.slice(2)], {
|
|
43
|
+
stdio: "inherit",
|
|
44
|
+
windowsHide: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on("error", (error) => {
|
|
48
|
+
if (error.code === "ENOENT") {
|
|
49
|
+
console.error(
|
|
50
|
+
"未找到 Bun。请从 https://bun.sh 安装 Bun,或确认 bun 已加入 PATH。",
|
|
51
|
+
);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.error(error.message);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
child.on("exit", (code, signal) => {
|
|
60
|
+
if (signal) {
|
|
61
|
+
process.kill(process.pid, signal);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.exit(code ?? 0);
|
|
66
|
+
});
|
package/bin/jax.ts
CHANGED
|
@@ -4,18 +4,40 @@ import { cac } from "cac";
|
|
|
4
4
|
import { $ } from "bun";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
|
-
import { mkdir } from "node:fs/promises";
|
|
7
|
+
import { lstat, mkdir, rm } from "node:fs/promises";
|
|
8
8
|
|
|
9
9
|
const cli = cac("jax");
|
|
10
10
|
|
|
11
|
+
async function removeExistingTarget(
|
|
12
|
+
targetDir: string,
|
|
13
|
+
allowRegularDirectory: boolean,
|
|
14
|
+
) {
|
|
15
|
+
if (!existsSync(targetDir)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const stats = await lstat(targetDir);
|
|
20
|
+
if (stats.isSymbolicLink() || allowRegularDirectory) {
|
|
21
|
+
await rm(targetDir, { recursive: true, force: true });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.error(
|
|
26
|
+
`目标已存在且不是符号链接:${targetDir}\n请手动删除后再执行 jax link。`,
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
cli
|
|
12
|
-
.command(
|
|
13
|
-
"link",
|
|
14
|
-
"Create a symlink to the jax-hono installation directory",
|
|
15
|
-
)
|
|
32
|
+
.command("link", "创建指向 jax-hono 安装目录的链接")
|
|
16
33
|
.action(async () => {
|
|
17
34
|
const cwd = process.cwd();
|
|
18
35
|
const sourceDir = path.resolve(import.meta.dir, "..");
|
|
36
|
+
if (path.resolve(cwd) === sourceDir) {
|
|
37
|
+
console.error("不能在 jax-hono 安装目录内执行 jax link。");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
const shouldUseNodeModules =
|
|
20
42
|
existsSync(path.join(cwd, "package.json")) ||
|
|
21
43
|
existsSync(path.join(cwd, "node_modules"));
|
|
@@ -29,54 +51,90 @@ cli
|
|
|
29
51
|
await mkdir(targetRoot, { recursive: true });
|
|
30
52
|
}
|
|
31
53
|
|
|
54
|
+
await removeExistingTarget(targetDir, shouldUseNodeModules);
|
|
55
|
+
|
|
32
56
|
if (process.platform === "win32") {
|
|
33
57
|
const result =
|
|
34
|
-
await $`cmd /c mklink /
|
|
58
|
+
await $`cmd /c mklink /J ${targetDir} ${sourceDir}`.nothrow().quiet();
|
|
35
59
|
if (result.exitCode !== 0) {
|
|
36
60
|
console.error(
|
|
37
|
-
|
|
61
|
+
`创建目录联接失败:${result.stderr.toString()}`,
|
|
38
62
|
);
|
|
39
63
|
process.exit(1);
|
|
40
64
|
}
|
|
41
|
-
console.log(
|
|
65
|
+
console.log(`目录联接已创建:${targetDir} <<===>> ${sourceDir}`);
|
|
42
66
|
} else {
|
|
43
67
|
const relativeSource = path.relative(path.dirname(targetDir), sourceDir);
|
|
44
|
-
const result = await $`ln -sf ${relativeSource} ${targetDir}`.nothrow();
|
|
68
|
+
const result = await $`ln -sf ${relativeSource} ${targetDir}`.nothrow().quiet();
|
|
45
69
|
if (result.exitCode !== 0) {
|
|
46
|
-
console.error(
|
|
70
|
+
console.error(`创建符号链接失败:${result.stderr.toString()}`);
|
|
47
71
|
process.exit(1);
|
|
48
72
|
}
|
|
49
|
-
console.log(
|
|
73
|
+
console.log(`符号链接已创建:${targetDir} -> ${relativeSource}`);
|
|
50
74
|
}
|
|
51
75
|
});
|
|
52
76
|
|
|
53
77
|
cli
|
|
54
|
-
.command("open", "
|
|
78
|
+
.command("open", "在文件管理器中打开 jax-hono 安装目录")
|
|
55
79
|
.action(async () => {
|
|
56
80
|
const sourceDir = path.resolve(import.meta.dir, "..");
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
if (process.platform === "win32") {
|
|
83
|
+
const escapedSourceDir = sourceDir.replace(/'/g, "''");
|
|
84
|
+
const proc = Bun.spawn([
|
|
85
|
+
"powershell",
|
|
86
|
+
"-NoProfile",
|
|
87
|
+
"-Command",
|
|
88
|
+
`Start-Process -FilePath explorer.exe -ArgumentList '${escapedSourceDir}'`,
|
|
89
|
+
], {
|
|
90
|
+
stdout: "pipe",
|
|
91
|
+
stderr: "pipe",
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
});
|
|
94
|
+
const exitCode = await proc.exited;
|
|
95
|
+
const stderr = await new Response(proc.stderr).text();
|
|
96
|
+
|
|
97
|
+
if (exitCode !== 0) {
|
|
98
|
+
console.error(`打开目录失败:${stderr || "未知错误"}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`已打开:${sourceDir}`);
|
|
103
|
+
return;
|
|
65
104
|
}
|
|
66
105
|
|
|
106
|
+
const command =
|
|
107
|
+
process.platform === "darwin" ? ["open", sourceDir] : ["xdg-open", sourceDir];
|
|
67
108
|
const [cmd, ...args] = command;
|
|
68
|
-
const result = await $`${cmd} ${args}`.nothrow();
|
|
109
|
+
const result = await $`${cmd} ${args}`.nothrow().quiet();
|
|
69
110
|
if (result.exitCode !== 0) {
|
|
70
|
-
console.error(
|
|
111
|
+
console.error(`打开目录失败:${result.stderr.toString() || "未知错误"}`);
|
|
71
112
|
process.exit(1);
|
|
72
113
|
}
|
|
73
|
-
console.log(
|
|
114
|
+
console.log(`已打开:${sourceDir}`);
|
|
74
115
|
});
|
|
75
116
|
|
|
76
|
-
cli.help()
|
|
117
|
+
cli.help((sections) =>
|
|
118
|
+
sections.map((section) => {
|
|
119
|
+
const titleMap: Record<string, string> = {
|
|
120
|
+
Usage: "用法",
|
|
121
|
+
Commands: "命令",
|
|
122
|
+
"For more info, run any command with the `--help` flag":
|
|
123
|
+
"查看更多信息,请在任意命令后添加 `--help`",
|
|
124
|
+
Options: "选项",
|
|
125
|
+
Examples: "示例",
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...section,
|
|
130
|
+
title: section.title ? (titleMap[section.title] ?? section.title) : section.title,
|
|
131
|
+
body: section.body.replace("Display this message", "显示帮助信息"),
|
|
132
|
+
};
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
77
135
|
const parsed = cli.parse();
|
|
78
136
|
|
|
79
|
-
//
|
|
137
|
+
// 未匹配到命令时显示帮助,例如直接执行 `jax` 或输入未知命令。
|
|
80
138
|
if (!cli.matchedCommand && !parsed.options.help) {
|
|
81
139
|
cli.outputHelp();
|
|
82
140
|
}
|
package/build/generate-config.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { cwd } from 'node:process'
|
|
3
3
|
import { join, parse, relative } from 'node:path'
|
|
4
|
+
import { ensureGeneratedDir, generatedDir, projectDir } from './generated-path'
|
|
4
5
|
|
|
5
|
-
const packageDir = join(import.meta.dir, '..')
|
|
6
6
|
const srcDir = join(cwd(), 'src')
|
|
7
7
|
const configDir = join(srcDir, 'config')
|
|
8
8
|
const typesFile = join(srcDir, 'types/index.ts')
|
|
9
|
-
const generatedDir = join(packageDir, 'generated')
|
|
10
9
|
const outputFile = join(generatedDir, 'config.ts')
|
|
11
10
|
const envStages = new Set([
|
|
12
11
|
'default',
|
|
@@ -102,14 +101,14 @@ ${projectConfigType}
|
|
|
102
101
|
export default configModules
|
|
103
102
|
`
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
ensureGeneratedDir()
|
|
106
105
|
|
|
107
106
|
if (existsSync(outputFile) && readFileSync(outputFile, 'utf8') === content) {
|
|
108
107
|
return
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
writeFileSync(outputFile, content)
|
|
112
|
-
console.log(`Generated ${relative(
|
|
111
|
+
console.log(`Generated ${relative(projectDir, outputFile)} with ${files.length} config file(s).`)
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
if (import.meta.main) {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { cwd } from 'node:process'
|
|
3
3
|
import { basename, join, parse, relative } from 'node:path'
|
|
4
|
+
import { ensureGeneratedDir, generatedDir, projectDir } from './generated-path'
|
|
4
5
|
|
|
5
|
-
const packageDir = join(import.meta.dir, '..')
|
|
6
6
|
const srcDir = join(cwd(), 'src')
|
|
7
|
-
const generatedDir = join(packageDir, 'generated')
|
|
8
7
|
const moduleExtensions = new Set(['.ts', '.js', '.mjs', '.cjs'])
|
|
9
8
|
const generatedHeader = `// This file is generated by jax-hono/build/generate-registry.ts.
|
|
10
9
|
// Do not edit it directly.
|
|
@@ -66,14 +65,14 @@ function walkFiles(dir: string): string[] {
|
|
|
66
65
|
function writeGeneratedFile(fileName: string, content: string) {
|
|
67
66
|
const outputFile = join(generatedDir, fileName)
|
|
68
67
|
|
|
69
|
-
|
|
68
|
+
ensureGeneratedDir()
|
|
70
69
|
|
|
71
70
|
if (existsSync(outputFile) && readFileSync(outputFile, 'utf8') === content) {
|
|
72
71
|
return
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
writeFileSync(outputFile, content)
|
|
76
|
-
console.log(`Generated ${relative(
|
|
75
|
+
console.log(`Generated ${relative(projectDir, outputFile)}.`)
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
function toImportPath(filePath: string) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
realpathSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
symlinkSync,
|
|
8
|
+
} from 'node:fs'
|
|
9
|
+
import { cwd } from 'node:process'
|
|
10
|
+
import { join, resolve } from 'node:path'
|
|
11
|
+
|
|
12
|
+
export const packageDir = join(import.meta.dir, '..')
|
|
13
|
+
export const projectDir = cwd()
|
|
14
|
+
|
|
15
|
+
export function getGeneratedDir() {
|
|
16
|
+
const configuredDir = Bun.env.JAX_HONO_GENERATED_DIR
|
|
17
|
+
|
|
18
|
+
return configuredDir
|
|
19
|
+
? resolve(projectDir, configuredDir)
|
|
20
|
+
: join(projectDir, 'generated')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const generatedDir = getGeneratedDir()
|
|
24
|
+
|
|
25
|
+
export function ensureGeneratedDir() {
|
|
26
|
+
mkdirSync(generatedDir, { recursive: true })
|
|
27
|
+
ensurePackageGeneratedLink()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensurePackageGeneratedLink() {
|
|
31
|
+
const packageGeneratedDir = join(packageDir, 'generated')
|
|
32
|
+
|
|
33
|
+
if (resolve(packageGeneratedDir) === resolve(generatedDir)) {
|
|
34
|
+
if (existsSync(packageGeneratedDir) && lstatSync(packageGeneratedDir).isSymbolicLink()) {
|
|
35
|
+
rmSync(packageGeneratedDir, { recursive: true, force: true })
|
|
36
|
+
mkdirSync(generatedDir, { recursive: true })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (existsSync(packageGeneratedDir)) {
|
|
43
|
+
const stats = lstatSync(packageGeneratedDir)
|
|
44
|
+
|
|
45
|
+
if (stats.isSymbolicLink()) {
|
|
46
|
+
const currentTarget = realpathSync(packageGeneratedDir)
|
|
47
|
+
|
|
48
|
+
if (resolve(currentTarget) === resolve(generatedDir)) {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
rmSync(packageGeneratedDir, { recursive: true, force: true })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
symlinkSync(generatedDir, packageGeneratedDir, 'junction')
|
|
57
|
+
}
|
package/docs/jax.md
CHANGED
|
@@ -80,7 +80,7 @@ export default definePlugin({
|
|
|
80
80
|
});
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
未启用的插件不会写入调用项目的 `generated/plugins.generated.ts`,因此 `bun build` 不会解析它的运行时依赖。
|
|
84
84
|
|
|
85
85
|
### `build`
|
|
86
86
|
|
|
@@ -88,12 +88,12 @@ export default definePlugin({
|
|
|
88
88
|
|
|
89
89
|
当前 `bun run setup` 会生成:
|
|
90
90
|
|
|
91
|
-
- `
|
|
92
|
-
- `
|
|
93
|
-
- `
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
96
|
-
- `
|
|
91
|
+
- `generated/config.ts`
|
|
92
|
+
- `generated/controllers.generated.ts`
|
|
93
|
+
- `generated/middlewares.generated.ts`
|
|
94
|
+
- `generated/models.generated.ts`
|
|
95
|
+
- `generated/plugins.generated.ts`
|
|
96
|
+
- `generated/services.generated.ts`
|
|
97
97
|
|
|
98
98
|
业务代码不要手动编辑 `generated` 目录。
|
|
99
99
|
|
package/index.ts
CHANGED
package/middleware/jwt.ts
CHANGED
|
@@ -1,62 +1,67 @@
|
|
|
1
|
-
import config from
|
|
2
|
-
import jwt from
|
|
3
|
-
import type { Context, MiddlewareHandler, Next } from
|
|
1
|
+
import config from "../config";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import type { Context, MiddlewareHandler, Next } from "hono";
|
|
4
4
|
|
|
5
|
-
type JwtAuthOptions<
|
|
5
|
+
type JwtAuthOptions<
|
|
6
|
+
TPayload extends Record<string, any> = Record<string, any>,
|
|
7
|
+
> = {
|
|
6
8
|
secretSuffix: string;
|
|
7
9
|
stateKey: string;
|
|
8
10
|
validate?: (payload: TPayload, c: Context) => boolean | Promise<boolean>;
|
|
9
11
|
resolveState?: (payload: TPayload, c: Context) => unknown | Promise<unknown>;
|
|
10
12
|
};
|
|
11
13
|
|
|
12
|
-
function createJwtAuth<
|
|
13
|
-
|
|
14
|
-
): MiddlewareHandler {
|
|
14
|
+
function createJwtAuth<
|
|
15
|
+
TPayload extends Record<string, any> = Record<string, any>,
|
|
16
|
+
>(options: JwtAuthOptions<TPayload>): MiddlewareHandler {
|
|
15
17
|
return async (c, next) => {
|
|
16
|
-
const session = c.get('session');
|
|
17
|
-
const sessionData = session.get(options.stateKey);
|
|
18
|
+
// const session = c.get('session');
|
|
19
|
+
// const sessionData = session.get(options.stateKey);
|
|
18
20
|
|
|
19
|
-
if (sessionData) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
21
|
+
// if (sessionData) {
|
|
22
|
+
// c.state[options.stateKey] = sessionData;
|
|
23
|
+
// await next();
|
|
24
|
+
// }
|
|
24
25
|
|
|
25
26
|
const token = getAuthorizationToken(c);
|
|
26
27
|
|
|
27
28
|
if (!token) {
|
|
28
|
-
return c.json({ code: 401, msg:
|
|
29
|
+
return c.json({ code: 401, msg: "请先登录" });
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
try {
|
|
32
33
|
const payload = jwt.verify(
|
|
33
34
|
token,
|
|
34
|
-
(config as any).jwt.secret + options.secretSuffix
|
|
35
|
+
(config as any).jwt.secret + options.secretSuffix,
|
|
35
36
|
) as TPayload;
|
|
36
|
-
const valid = options.validate
|
|
37
|
+
const valid = options.validate
|
|
38
|
+
? await options.validate(payload, c)
|
|
39
|
+
: true;
|
|
37
40
|
|
|
38
41
|
if (!valid) {
|
|
39
|
-
return c.json({ code: 401, msg:
|
|
42
|
+
return c.json({ code: 401, msg: "请重新登录" });
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
const stateValue = options.resolveState
|
|
45
|
+
const stateValue = options.resolveState
|
|
46
|
+
? await options.resolveState(payload, c)
|
|
47
|
+
: payload;
|
|
43
48
|
|
|
44
49
|
if (!stateValue) {
|
|
45
|
-
return c.json({ code: 401, msg:
|
|
50
|
+
return c.json({ code: 401, msg: "请重新登录" });
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
c.state[options.stateKey] = stateValue;
|
|
49
54
|
|
|
50
55
|
await next();
|
|
51
56
|
} catch {
|
|
52
|
-
return c.json({ code: 401, msg:
|
|
57
|
+
return c.json({ code: 401, msg: "请重新登录" });
|
|
53
58
|
}
|
|
54
59
|
};
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
function createOptionalAuth(auth: MiddlewareHandler): MiddlewareHandler {
|
|
58
63
|
return async (c, next) => {
|
|
59
|
-
if (!c.req.header(
|
|
64
|
+
if (!c.req.header("authorization")) {
|
|
60
65
|
await next();
|
|
61
66
|
return;
|
|
62
67
|
}
|
|
@@ -66,10 +71,10 @@ function createOptionalAuth(auth: MiddlewareHandler): MiddlewareHandler {
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
function getAuthorizationToken(c: Context) {
|
|
69
|
-
const authorization = c.req.header(
|
|
74
|
+
const authorization = c.req.header("authorization") ?? "";
|
|
70
75
|
|
|
71
|
-
return authorization.startsWith(
|
|
72
|
-
? authorization.slice(
|
|
76
|
+
return authorization.startsWith("Bearer ")
|
|
77
|
+
? authorization.slice("Bearer ".length)
|
|
73
78
|
: authorization;
|
|
74
79
|
}
|
|
75
80
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jax-hono",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Lightweight framework layer on top of Hono, built for Bun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"bin": {
|
|
63
|
-
"jax": "./bin/jax.
|
|
63
|
+
"jax": "./bin/jax.js"
|
|
64
64
|
},
|
|
65
65
|
"files": [
|
|
66
66
|
"index.ts",
|
|
@@ -82,10 +82,10 @@
|
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@hono/session": "^0.2.1",
|
|
85
|
-
"cac": "^7.0.0"
|
|
86
|
-
"hono": "^4.12.27"
|
|
85
|
+
"cac": "^7.0.0"
|
|
87
86
|
},
|
|
88
87
|
"peerDependencies": {
|
|
88
|
+
"hono": "^4.12.27",
|
|
89
89
|
"dayjs": "*",
|
|
90
90
|
"deepmerge": "^4.3.1",
|
|
91
91
|
"dotenv": "^17.4.2",
|
|
@@ -94,9 +94,11 @@
|
|
|
94
94
|
"mongoose": "*"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
+
"@types/bun": "^1.3.14",
|
|
97
98
|
"dayjs": "^1.11.21",
|
|
98
99
|
"deepmerge": "^4.3.1",
|
|
99
100
|
"dotenv": "^17.4.2",
|
|
101
|
+
"hono": "^4.12.27",
|
|
100
102
|
"jsonwebtoken": "^9.0.3",
|
|
101
103
|
"minimist": "^1.2.8",
|
|
102
104
|
"mongoose": "^8.24.1"
|
|
@@ -116,4 +118,4 @@
|
|
|
116
118
|
"bun": ">=1.0.0"
|
|
117
119
|
},
|
|
118
120
|
"license": "MIT"
|
|
119
|
-
}
|
|
121
|
+
}
|
package/plugins/session/index.ts
CHANGED
|
@@ -1,20 +1,86 @@
|
|
|
1
|
-
import { useSession, useSessionStorage } from '@hono/session'
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import { definePlugin } from '../define'
|
|
1
|
+
import { useSession, useSessionStorage } from '@hono/session';
|
|
2
|
+
import type { Session, SessionData, SessionOptions, Storage } from '@hono/session';
|
|
3
|
+
import type { Context } from 'hono';
|
|
4
|
+
import { definePlugin } from '../define';
|
|
5
|
+
|
|
6
|
+
export type JaxSessionData = SessionData;
|
|
7
|
+
|
|
8
|
+
export type SessionStorageFactory<Data extends SessionData = JaxSessionData> = (
|
|
9
|
+
c: Context
|
|
10
|
+
) => Storage<Data>;
|
|
11
|
+
|
|
12
|
+
export type SessionPluginOptions<Data extends SessionData = JaxSessionData> =
|
|
13
|
+
SessionOptions<Data> & {
|
|
14
|
+
/**
|
|
15
|
+
* Optional backing storage. When omitted, @hono/session keeps session data
|
|
16
|
+
* inside the encrypted cookie.
|
|
17
|
+
*/
|
|
18
|
+
storage?: Storage<Data> | SessionStorageFactory<Data>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
declare module 'hono' {
|
|
22
|
+
interface ContextVariableMap {
|
|
23
|
+
session: Session<JaxSessionData>;
|
|
24
|
+
sessionStorage?: Storage<JaxSessionData>;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULT_SESSION_DURATION = {
|
|
29
|
+
// 会话的有效时间以秒为单位。超过该时间后,会话将失效,需要重新认证才能继续使用。
|
|
30
|
+
absolute: 60 * 60 * 24 * 7 // 7天
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const DEFAULT_SESSION_OPTIONS: SessionPluginOptions = {
|
|
34
|
+
secret: 'jax-session',
|
|
35
|
+
duration: DEFAULT_SESSION_DURATION
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function isSessionPluginOptions(options: unknown): options is SessionPluginOptions {
|
|
39
|
+
return Boolean(options && typeof options === 'object' && !Array.isArray(options));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveSessionOptions(config: unknown, options: unknown): SessionPluginOptions {
|
|
43
|
+
const configOptions = readConfigSessionOptions(config);
|
|
44
|
+
const pluginOptions = isSessionPluginOptions(options) ? options : {};
|
|
45
|
+
const duration: NonNullable<SessionPluginOptions['duration']> = {
|
|
46
|
+
absolute: DEFAULT_SESSION_DURATION.absolute,
|
|
47
|
+
...configOptions.duration,
|
|
48
|
+
...pluginOptions.duration
|
|
49
|
+
};
|
|
50
|
+
const { storage, ...sessionOptions } = {
|
|
51
|
+
...DEFAULT_SESSION_OPTIONS,
|
|
52
|
+
...configOptions,
|
|
53
|
+
...pluginOptions,
|
|
54
|
+
duration
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...sessionOptions,
|
|
59
|
+
storage
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readConfigSessionOptions(config: unknown): SessionPluginOptions {
|
|
64
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const session = (config as { session?: unknown }).session;
|
|
69
|
+
|
|
70
|
+
return isSessionPluginOptions(session) ? session : {};
|
|
71
|
+
}
|
|
5
72
|
|
|
6
73
|
export default definePlugin({
|
|
7
|
-
setup(ctx) {
|
|
8
|
-
ctx.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
})
|
|
74
|
+
setup(ctx, options) {
|
|
75
|
+
const { storage, ...sessionOptions } = resolveSessionOptions(ctx.config, options);
|
|
76
|
+
|
|
77
|
+
if (storage) {
|
|
78
|
+
ctx.app.use(useSessionStorage(storage));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ctx.app.use(useSession(sessionOptions));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export type { Session, SessionData, SessionOptions, Storage };
|
|
86
|
+
export { useSession, useSessionStorage };
|
package/types/config.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
+
import type { SessionPluginOptions } from '../plugins/session';
|
|
2
|
+
|
|
1
3
|
export type JaxConfig = {
|
|
2
4
|
rootDir: string;
|
|
3
5
|
port: number;
|
|
4
6
|
env: string;
|
|
5
7
|
|
|
6
|
-
session?:
|
|
7
|
-
secret?: string;
|
|
8
|
-
duration?: {
|
|
9
|
-
absolute: number;
|
|
10
|
-
inactivity?: number;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
8
|
+
session?: SessionPluginOptions;
|
|
13
9
|
};
|