jax-hono 1.0.0 → 1.0.2
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 +96 -24
- package/build/generate-config.ts +9 -8
- package/build/generate-registry.ts +12 -16
- package/build/generated-path.ts +57 -0
- package/docs/jax.md +7 -7
- package/package.json +63 -7
- package/plugins/mongoose/model.ts +71 -60
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
|
@@ -3,66 +3,138 @@
|
|
|
3
3
|
import { cac } from "cac";
|
|
4
4
|
import { $ } from "bun";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { lstat, mkdir, rm } from "node:fs/promises";
|
|
6
8
|
|
|
7
9
|
const cli = cac("jax");
|
|
8
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
|
+
|
|
9
31
|
cli
|
|
10
|
-
.command(
|
|
11
|
-
"link",
|
|
12
|
-
"Create a symlink (./jax -> jax-hono installation directory)",
|
|
13
|
-
)
|
|
32
|
+
.command("link", "创建指向 jax-hono 安装目录的链接")
|
|
14
33
|
.action(async () => {
|
|
34
|
+
const cwd = process.cwd();
|
|
15
35
|
const sourceDir = path.resolve(import.meta.dir, "..");
|
|
16
|
-
|
|
36
|
+
if (path.resolve(cwd) === sourceDir) {
|
|
37
|
+
console.error("不能在 jax-hono 安装目录内执行 jax link。");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const shouldUseNodeModules =
|
|
42
|
+
existsSync(path.join(cwd, "package.json")) ||
|
|
43
|
+
existsSync(path.join(cwd, "node_modules"));
|
|
44
|
+
const targetRoot = shouldUseNodeModules
|
|
45
|
+
? path.join(cwd, "node_modules")
|
|
46
|
+
: cwd;
|
|
47
|
+
const targetName = shouldUseNodeModules ? "jax-hono" : "jax";
|
|
48
|
+
const targetDir = path.resolve(targetRoot, targetName);
|
|
49
|
+
|
|
50
|
+
if (shouldUseNodeModules) {
|
|
51
|
+
await mkdir(targetRoot, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await removeExistingTarget(targetDir, shouldUseNodeModules);
|
|
17
55
|
|
|
18
56
|
if (process.platform === "win32") {
|
|
19
57
|
const result =
|
|
20
|
-
await $`cmd /c mklink /
|
|
58
|
+
await $`cmd /c mklink /J ${targetDir} ${sourceDir}`.nothrow().quiet();
|
|
21
59
|
if (result.exitCode !== 0) {
|
|
22
60
|
console.error(
|
|
23
|
-
|
|
61
|
+
`创建目录联接失败:${result.stderr.toString()}`,
|
|
24
62
|
);
|
|
25
63
|
process.exit(1);
|
|
26
64
|
}
|
|
27
|
-
console.log(
|
|
65
|
+
console.log(`目录联接已创建:${targetDir} <<===>> ${sourceDir}`);
|
|
28
66
|
} else {
|
|
29
67
|
const relativeSource = path.relative(path.dirname(targetDir), sourceDir);
|
|
30
|
-
const result = await $`ln -sf ${relativeSource} ${targetDir}`.nothrow();
|
|
68
|
+
const result = await $`ln -sf ${relativeSource} ${targetDir}`.nothrow().quiet();
|
|
31
69
|
if (result.exitCode !== 0) {
|
|
32
|
-
console.error(
|
|
70
|
+
console.error(`创建符号链接失败:${result.stderr.toString()}`);
|
|
33
71
|
process.exit(1);
|
|
34
72
|
}
|
|
35
|
-
console.log(
|
|
73
|
+
console.log(`符号链接已创建:${targetDir} -> ${relativeSource}`);
|
|
36
74
|
}
|
|
37
75
|
});
|
|
38
76
|
|
|
39
77
|
cli
|
|
40
|
-
.command("open", "
|
|
78
|
+
.command("open", "在文件管理器中打开 jax-hono 安装目录")
|
|
41
79
|
.action(async () => {
|
|
42
80
|
const sourceDir = path.resolve(import.meta.dir, "..");
|
|
43
81
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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;
|
|
51
104
|
}
|
|
52
105
|
|
|
106
|
+
const command =
|
|
107
|
+
process.platform === "darwin" ? ["open", sourceDir] : ["xdg-open", sourceDir];
|
|
53
108
|
const [cmd, ...args] = command;
|
|
54
|
-
const result = await $`${cmd} ${args}`.nothrow();
|
|
109
|
+
const result = await $`${cmd} ${args}`.nothrow().quiet();
|
|
55
110
|
if (result.exitCode !== 0) {
|
|
56
|
-
console.error(
|
|
111
|
+
console.error(`打开目录失败:${result.stderr.toString() || "未知错误"}`);
|
|
57
112
|
process.exit(1);
|
|
58
113
|
}
|
|
59
|
-
console.log(
|
|
114
|
+
console.log(`已打开:${sourceDir}`);
|
|
60
115
|
});
|
|
61
116
|
|
|
62
|
-
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
|
+
);
|
|
63
135
|
const parsed = cli.parse();
|
|
64
136
|
|
|
65
|
-
//
|
|
137
|
+
// 未匹配到命令时显示帮助,例如直接执行 `jax` 或输入未知命令。
|
|
66
138
|
if (!cli.matchedCommand && !parsed.options.help) {
|
|
67
139
|
cli.outputHelp();
|
|
68
140
|
}
|
package/build/generate-config.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { cwd } from 'node:process'
|
|
2
3
|
import { join, parse, relative } from 'node:path'
|
|
4
|
+
import { ensureGeneratedDir, generatedDir, projectDir } from './generated-path'
|
|
3
5
|
|
|
4
|
-
const srcDir = join(
|
|
6
|
+
const srcDir = join(cwd(), 'src')
|
|
5
7
|
const configDir = join(srcDir, 'config')
|
|
6
8
|
const typesFile = join(srcDir, 'types/index.ts')
|
|
7
|
-
const generatedDir = join(srcDir, 'jax/generated')
|
|
8
9
|
const outputFile = join(generatedDir, 'config.ts')
|
|
9
10
|
const envStages = new Set([
|
|
10
11
|
'default',
|
|
@@ -35,9 +36,9 @@ function getStage(fileName: string) {
|
|
|
35
36
|
|
|
36
37
|
function getImportPath(fileName: string) {
|
|
37
38
|
const { dir, name } = parse(fileName)
|
|
38
|
-
const path = join(
|
|
39
|
+
const path = join(configDir, dir, name).replaceAll('\\', '/')
|
|
39
40
|
|
|
40
|
-
return path
|
|
41
|
+
return path
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function getConfigFiles() {
|
|
@@ -83,7 +84,7 @@ export function generateConfig() {
|
|
|
83
84
|
return ` { name: '${fileName}', stage: '${getStage(fileName)}', config: ${toImportName(fileName, index)} },`
|
|
84
85
|
})
|
|
85
86
|
const hasProjectTypes = hasProjectConfigType()
|
|
86
|
-
const typeImports = hasProjectTypes ? [`import type { ProjectConfig } from '
|
|
87
|
+
const typeImports = hasProjectTypes ? [`import type { ProjectConfig } from '${join(srcDir, 'types').replaceAll('\\', '/')}'`] : []
|
|
87
88
|
const projectConfigType = hasProjectTypes
|
|
88
89
|
? 'export type { ProjectConfig }'
|
|
89
90
|
: defaultConfigIndex === -1
|
|
@@ -100,14 +101,14 @@ ${projectConfigType}
|
|
|
100
101
|
export default configModules
|
|
101
102
|
`
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
ensureGeneratedDir()
|
|
104
105
|
|
|
105
106
|
if (existsSync(outputFile) && readFileSync(outputFile, 'utf8') === content) {
|
|
106
107
|
return
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
writeFileSync(outputFile, content)
|
|
110
|
-
console.log(`Generated ${relative(
|
|
111
|
+
console.log(`Generated ${relative(projectDir, outputFile)} with ${files.length} config file(s).`)
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
if (import.meta.main) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { cwd } from 'node:process'
|
|
2
3
|
import { basename, join, parse, relative } from 'node:path'
|
|
4
|
+
import { ensureGeneratedDir, generatedDir, projectDir } from './generated-path'
|
|
3
5
|
|
|
4
|
-
const srcDir = join(
|
|
5
|
-
const generatedDir = join(srcDir, 'jax/generated')
|
|
6
|
+
const srcDir = join(cwd(), 'src')
|
|
6
7
|
const moduleExtensions = new Set(['.ts', '.js', '.mjs', '.cjs'])
|
|
7
|
-
const generatedHeader = `// This file is generated by
|
|
8
|
+
const generatedHeader = `// This file is generated by jax-hono/build/generate-registry.ts.
|
|
8
9
|
// Do not edit it directly.
|
|
9
10
|
`
|
|
10
11
|
|
|
@@ -64,22 +65,22 @@ function walkFiles(dir: string): string[] {
|
|
|
64
65
|
function writeGeneratedFile(fileName: string, content: string) {
|
|
65
66
|
const outputFile = join(generatedDir, fileName)
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
ensureGeneratedDir()
|
|
68
69
|
|
|
69
70
|
if (existsSync(outputFile) && readFileSync(outputFile, 'utf8') === content) {
|
|
70
71
|
return
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
writeFileSync(outputFile, content)
|
|
74
|
-
console.log(`Generated ${relative(
|
|
75
|
+
console.log(`Generated ${relative(projectDir, outputFile)}.`)
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
function toImportPath(filePath: string) {
|
|
78
|
-
const path =
|
|
79
|
+
const path = filePath
|
|
79
80
|
.replace(parse(filePath).ext, '')
|
|
80
81
|
.replaceAll('\\', '/')
|
|
81
82
|
|
|
82
|
-
return
|
|
83
|
+
return path
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
function toIdentifier(parts: string[]) {
|
|
@@ -277,7 +278,7 @@ function generateControllersRegistry() {
|
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
const helperImport = imports.length > 0
|
|
280
|
-
? `import { createController } from '
|
|
281
|
+
? `import { createController } from 'jax-hono/core/controller'\n`
|
|
281
282
|
: ''
|
|
282
283
|
const content = `${generatedHeader}
|
|
283
284
|
${helperImport}${imports.join('\n')}
|
|
@@ -531,15 +532,11 @@ function normalizePluginPackage(packageName: string, configFile: string) {
|
|
|
531
532
|
return packageName
|
|
532
533
|
}
|
|
533
534
|
|
|
534
|
-
const normalized =
|
|
535
|
+
const normalized = join(configFile, '..', packageName)
|
|
535
536
|
.replaceAll('\\', '/')
|
|
536
537
|
.replace(/\/index$/, '')
|
|
537
538
|
|
|
538
|
-
|
|
539
|
-
return normalized
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return `@/${normalized}`
|
|
539
|
+
return normalized
|
|
543
540
|
}
|
|
544
541
|
|
|
545
542
|
function mergePluginConfig(configs: { filePath: string; config: PluginConfig }[]) {
|
|
@@ -578,7 +575,6 @@ function serializePluginOptions(options: unknown) {
|
|
|
578
575
|
|
|
579
576
|
function generatePluginContextRegistry() {
|
|
580
577
|
const enabledPlugins = mergePluginConfig([
|
|
581
|
-
{ filePath: join(srcDir, 'jax/plugins/config.ts'), config: readPluginConfig(join(srcDir, 'jax/plugins/config.ts')) },
|
|
582
578
|
{ filePath: join(srcDir, 'config/plugin.ts'), config: readPluginConfig(join(srcDir, 'config/plugin.ts')) },
|
|
583
579
|
])
|
|
584
580
|
const imports = enabledPlugins.map(([name, item]) => {
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jax-hono",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Lightweight framework layer on top of Hono, built for Bun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -10,10 +10,57 @@
|
|
|
10
10
|
"import": "./index.ts",
|
|
11
11
|
"types": "./index.ts"
|
|
12
12
|
},
|
|
13
|
-
"
|
|
13
|
+
"./setup": {
|
|
14
|
+
"import": "./setup.ts",
|
|
15
|
+
"types": "./setup.ts"
|
|
16
|
+
},
|
|
17
|
+
"./config": {
|
|
18
|
+
"import": "./config.ts",
|
|
19
|
+
"types": "./config.ts"
|
|
20
|
+
},
|
|
21
|
+
"./core/*": {
|
|
22
|
+
"import": "./core/*.ts",
|
|
23
|
+
"types": "./core/*.ts"
|
|
24
|
+
},
|
|
25
|
+
"./helpers": {
|
|
26
|
+
"import": "./helpers/index.ts",
|
|
27
|
+
"types": "./helpers/index.ts"
|
|
28
|
+
},
|
|
29
|
+
"./helpers/*": {
|
|
30
|
+
"import": "./helpers/*.ts",
|
|
31
|
+
"types": "./helpers/*.ts"
|
|
32
|
+
},
|
|
33
|
+
"./middleware/*": {
|
|
34
|
+
"import": "./middleware/*.ts",
|
|
35
|
+
"types": "./middleware/*.ts"
|
|
36
|
+
},
|
|
37
|
+
"./plugins/*": {
|
|
38
|
+
"import": "./plugins/*/index.ts",
|
|
39
|
+
"types": "./plugins/*/index.ts"
|
|
40
|
+
},
|
|
41
|
+
"./plugins/*/*": {
|
|
42
|
+
"import": "./plugins/*/*.ts",
|
|
43
|
+
"types": "./plugins/*/*.ts"
|
|
44
|
+
},
|
|
45
|
+
"./utils": {
|
|
46
|
+
"import": "./utils/index.ts",
|
|
47
|
+
"types": "./utils/index.ts"
|
|
48
|
+
},
|
|
49
|
+
"./utils/*": {
|
|
50
|
+
"import": "./utils/*.ts",
|
|
51
|
+
"types": "./utils/*.ts"
|
|
52
|
+
},
|
|
53
|
+
"./types": {
|
|
54
|
+
"import": "./types/index.ts",
|
|
55
|
+
"types": "./types/index.ts"
|
|
56
|
+
},
|
|
57
|
+
"./types/*": {
|
|
58
|
+
"import": "./types/*.ts",
|
|
59
|
+
"types": "./types/*.ts"
|
|
60
|
+
}
|
|
14
61
|
},
|
|
15
62
|
"bin": {
|
|
16
|
-
"jax": "./bin/jax.
|
|
63
|
+
"jax": "./bin/jax.js"
|
|
17
64
|
},
|
|
18
65
|
"files": [
|
|
19
66
|
"index.ts",
|
|
@@ -40,12 +87,21 @@
|
|
|
40
87
|
},
|
|
41
88
|
"peerDependencies": {
|
|
42
89
|
"dayjs": "*",
|
|
43
|
-
"deepmerge": "
|
|
44
|
-
"dotenv": "
|
|
90
|
+
"deepmerge": "^4.3.1",
|
|
91
|
+
"dotenv": "^17.4.2",
|
|
45
92
|
"jsonwebtoken": "*",
|
|
46
|
-
"minimist": "
|
|
93
|
+
"minimist": "^1.2.8",
|
|
47
94
|
"mongoose": "*"
|
|
48
95
|
},
|
|
96
|
+
"devDependencies": {
|
|
97
|
+
"@types/bun": "^1.3.14",
|
|
98
|
+
"dayjs": "^1.11.21",
|
|
99
|
+
"deepmerge": "^4.3.1",
|
|
100
|
+
"dotenv": "^17.4.2",
|
|
101
|
+
"jsonwebtoken": "^9.0.3",
|
|
102
|
+
"minimist": "^1.2.8",
|
|
103
|
+
"mongoose": "^8.24.1"
|
|
104
|
+
},
|
|
49
105
|
"peerDependenciesMeta": {
|
|
50
106
|
"dayjs": {
|
|
51
107
|
"optional": true
|
|
@@ -61,4 +117,4 @@
|
|
|
61
117
|
"bun": ">=1.0.0"
|
|
62
118
|
},
|
|
63
119
|
"license": "MIT"
|
|
64
|
-
}
|
|
120
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { basename, parse } from
|
|
1
|
+
import { basename, parse } from "node:path";
|
|
2
2
|
import mongoose, {
|
|
3
3
|
Schema,
|
|
4
4
|
model as mongooseModel,
|
|
5
5
|
type InferSchemaType,
|
|
6
6
|
type Model,
|
|
7
|
-
type SchemaOptions
|
|
8
|
-
} from
|
|
9
|
-
import config from
|
|
10
|
-
import { rankSetter } from
|
|
11
|
-
import dayjs from
|
|
7
|
+
type SchemaOptions,
|
|
8
|
+
} from "mongoose";
|
|
9
|
+
import config from "../../config";
|
|
10
|
+
import { rankSetter } from "./schema";
|
|
11
|
+
import dayjs from "dayjs";
|
|
12
12
|
|
|
13
13
|
export { rankSetter };
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ type PageOptions = {
|
|
|
21
21
|
export type ModelStatics = {
|
|
22
22
|
findPage(
|
|
23
23
|
filter?: Record<string, unknown>,
|
|
24
|
-
options?: PageOptions
|
|
24
|
+
options?: PageOptions,
|
|
25
25
|
): Promise<{
|
|
26
26
|
total: number;
|
|
27
27
|
list: unknown[];
|
|
@@ -48,14 +48,15 @@ export function createLooseModel(filePathOrName: string, collection?: string) {
|
|
|
48
48
|
installModelStatics(schema);
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
|
-
mongoose.models[name] ??
|
|
51
|
+
mongoose.models[name] ??
|
|
52
|
+
mongooseModel(name, schema, collection ?? getCollectionName(name))
|
|
52
53
|
);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
export function createSchemaModel<TSchema extends Schema>(
|
|
56
57
|
filePathOrName: string,
|
|
57
58
|
schema: TSchema,
|
|
58
|
-
collection?: string
|
|
59
|
+
collection?: string,
|
|
59
60
|
): Model<InferSchemaType<TSchema>> & ModelStatics {
|
|
60
61
|
const name = getModelName(filePathOrName);
|
|
61
62
|
|
|
@@ -67,7 +68,7 @@ export function createSchemaModel<TSchema extends Schema>(
|
|
|
67
68
|
mongooseModel<InferSchemaType<TSchema>>(
|
|
68
69
|
name,
|
|
69
70
|
schema as any,
|
|
70
|
-
collection ?? getCollectionName(name)
|
|
71
|
+
collection ?? getCollectionName(name),
|
|
71
72
|
)) as Model<InferSchemaType<TSchema>> & ModelStatics;
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -75,15 +76,16 @@ export const createModel = createSchemaModel;
|
|
|
75
76
|
|
|
76
77
|
export function getModelName(filePathOrName: string) {
|
|
77
78
|
const baseName =
|
|
78
|
-
filePathOrName.includes(
|
|
79
|
-
? parse(basename(filePathOrName)).name.replace(/\.model$/,
|
|
79
|
+
filePathOrName.includes("/") || filePathOrName.includes("\\")
|
|
80
|
+
? parse(basename(filePathOrName)).name.replace(/\.model$/, "")
|
|
80
81
|
: filePathOrName;
|
|
81
82
|
|
|
82
83
|
return toPascalCase(baseName);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
export function getCollectionName(modelName: string) {
|
|
86
|
-
const prefix =
|
|
87
|
+
const prefix =
|
|
88
|
+
(config as any).mongoose?.prefix ?? (config as any).mongo?.prefix ?? "";
|
|
87
89
|
const name = toSnakeCase(modelName);
|
|
88
90
|
|
|
89
91
|
return prefix ? `${prefix}_${name}` : name;
|
|
@@ -102,23 +104,23 @@ export function schemaOptions(): SchemaOptions {
|
|
|
102
104
|
// }
|
|
103
105
|
|
|
104
106
|
if (ret.createdAt) {
|
|
105
|
-
ret.createdAt = dayjs(ret.createdAt).format(
|
|
107
|
+
ret.createdAt = dayjs(ret.createdAt).format("YYYY-MM-DD HH:mm:ss");
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
if (ret.updatedAt) {
|
|
109
|
-
ret.updatedAt = dayjs(ret.updatedAt).format(
|
|
111
|
+
ret.updatedAt = dayjs(ret.updatedAt).format("YYYY-MM-DD HH:mm:ss");
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
delete ret.__v;
|
|
113
115
|
delete ret._id;
|
|
114
116
|
|
|
115
117
|
return ret;
|
|
116
|
-
}
|
|
118
|
+
},
|
|
117
119
|
},
|
|
118
120
|
toObject: {
|
|
119
121
|
virtuals: true,
|
|
120
|
-
getters: true
|
|
121
|
-
}
|
|
122
|
+
getters: true,
|
|
123
|
+
},
|
|
122
124
|
};
|
|
123
125
|
}
|
|
124
126
|
|
|
@@ -126,37 +128,40 @@ export function applyDefaultSchemaOptions(schema: Schema) {
|
|
|
126
128
|
const defaults = schemaOptions();
|
|
127
129
|
|
|
128
130
|
if (schema.options.timestamps === undefined) {
|
|
129
|
-
schema.set(
|
|
131
|
+
schema.set("timestamps", defaults.timestamps);
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
if (
|
|
133
|
-
schema.
|
|
134
|
+
if (
|
|
135
|
+
schema.options.versionKey === undefined ||
|
|
136
|
+
schema.options.versionKey === "__v"
|
|
137
|
+
) {
|
|
138
|
+
schema.set("versionKey", defaults.versionKey);
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
const toJSON = {
|
|
137
142
|
...defaults.toJSON,
|
|
138
|
-
...(schema.options.toJSON ?? {})
|
|
143
|
+
...(schema.options.toJSON ?? {}),
|
|
139
144
|
} as any;
|
|
140
145
|
|
|
141
|
-
schema.set(
|
|
142
|
-
schema.set(
|
|
146
|
+
schema.set("toJSON", toJSON);
|
|
147
|
+
schema.set("toObject", {
|
|
143
148
|
...defaults.toObject,
|
|
144
|
-
...(schema.options.toObject ?? {})
|
|
149
|
+
...(schema.options.toObject ?? {}),
|
|
145
150
|
} as any);
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
export function installRankHooks(schema: Schema) {
|
|
149
|
-
schema.pre([
|
|
154
|
+
schema.pre(["updateOne", "findOneAndUpdate", "updateMany"], function () {
|
|
150
155
|
normalizeRank(this.getUpdate());
|
|
151
156
|
});
|
|
152
157
|
|
|
153
|
-
schema.pre(
|
|
158
|
+
schema.pre("save", function () {
|
|
154
159
|
normalizeRank(this);
|
|
155
160
|
});
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
export function installAutoNo(schema: Schema, field: string, start = 0) {
|
|
159
|
-
schema.pre(
|
|
164
|
+
schema.pre("save", async function () {
|
|
160
165
|
if (!this.isNew || this.get(field)) {
|
|
161
166
|
return;
|
|
162
167
|
}
|
|
@@ -169,29 +174,32 @@ export function installAutoNo(schema: Schema, field: string, start = 0) {
|
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
export function installModelStatics(schema: Schema) {
|
|
172
|
-
schema.static(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
schema.static(
|
|
178
|
+
"findPage",
|
|
179
|
+
async function findPage(filter = {}, options: PageOptions = {}) {
|
|
180
|
+
const page = Number(options.page || 1);
|
|
181
|
+
const pageSize = Number(options.pageSize || 20);
|
|
182
|
+
const sort = (options.sort ?? { _id: -1 }) as any;
|
|
183
|
+
const total = await this.countDocuments(filter);
|
|
184
|
+
let query = this.find(filter).sort(sort);
|
|
185
|
+
|
|
186
|
+
if (pageSize !== -1) {
|
|
187
|
+
query = query.skip((page - 1) * pageSize).limit(pageSize);
|
|
188
|
+
}
|
|
182
189
|
|
|
183
|
-
|
|
190
|
+
const list = await query;
|
|
184
191
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
return {
|
|
193
|
+
total,
|
|
194
|
+
list,
|
|
195
|
+
maxPage: pageSize === -1 ? 1 : Math.ceil(total / pageSize),
|
|
196
|
+
page,
|
|
197
|
+
pageSize,
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
);
|
|
193
201
|
|
|
194
|
-
schema.static(
|
|
202
|
+
schema.static("tree", async function tree(options: any = {}) {
|
|
195
203
|
const docs = await this.find(options.filter ?? {})
|
|
196
204
|
.sort(options.sort ?? {})
|
|
197
205
|
.lean({ virtuals: true });
|
|
@@ -204,7 +212,7 @@ export function installModelStatics(schema: Schema) {
|
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
for (const item of byId.values()) {
|
|
207
|
-
const parentId = item.parentId ? String(item.parentId) :
|
|
215
|
+
const parentId = item.parentId ? String(item.parentId) : "";
|
|
208
216
|
const parent = parentId ? byId.get(parentId) : undefined;
|
|
209
217
|
|
|
210
218
|
if (parent) {
|
|
@@ -217,24 +225,27 @@ export function installModelStatics(schema: Schema) {
|
|
|
217
225
|
return tree;
|
|
218
226
|
});
|
|
219
227
|
|
|
220
|
-
schema.static(
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
schema.static(
|
|
229
|
+
"flatTree",
|
|
230
|
+
async function flatTree(this: any, options: any = {}) {
|
|
231
|
+
const tree = await this.tree(options);
|
|
232
|
+
const list: any[] = [];
|
|
223
233
|
|
|
224
|
-
|
|
234
|
+
walkTree(tree, 1, list);
|
|
225
235
|
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
return list;
|
|
237
|
+
},
|
|
238
|
+
);
|
|
228
239
|
}
|
|
229
240
|
|
|
230
241
|
export function installCommonFields(schema: Schema) {
|
|
231
242
|
const fields: Record<string, unknown> = {};
|
|
232
243
|
|
|
233
|
-
if (!schema.path(
|
|
244
|
+
if (!schema.path("isDelete")) {
|
|
234
245
|
fields.isDelete = { type: Boolean, default: false };
|
|
235
246
|
}
|
|
236
247
|
|
|
237
|
-
if (!schema.path(
|
|
248
|
+
if (!schema.path("isOpen")) {
|
|
238
249
|
fields.isOpen = { type: Boolean, default: false };
|
|
239
250
|
}
|
|
240
251
|
|
|
@@ -245,14 +256,14 @@ export function installCommonFields(schema: Schema) {
|
|
|
245
256
|
|
|
246
257
|
export function toSnakeCase(value: string) {
|
|
247
258
|
return value
|
|
248
|
-
.replace(/([a-z0-9])([A-Z])/g,
|
|
249
|
-
.replace(/[-\s]+/g,
|
|
259
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
260
|
+
.replace(/[-\s]+/g, "_")
|
|
250
261
|
.toLowerCase();
|
|
251
262
|
}
|
|
252
263
|
|
|
253
264
|
function toPascalCase(value: string) {
|
|
254
265
|
const camel = value.replace(/[-_\s]+([a-zA-Z0-9])/g, (_match, char: string) =>
|
|
255
|
-
char.toUpperCase()
|
|
266
|
+
char.toUpperCase(),
|
|
256
267
|
);
|
|
257
268
|
|
|
258
269
|
return camel.charAt(0).toUpperCase() + camel.slice(1);
|