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 CHANGED
@@ -13,5 +13,5 @@ helpers/ 框架辅助函数
13
13
  utils/ 纯工具函数
14
14
  errors/ 统一错误类型
15
15
  types/ 框架公共类型
16
- generated/ 自动生成的注册表
16
+ generated/ 调用项目中自动生成的注册表,包内同名目录会链接到这里
17
17
  ```
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
- const targetDir = path.resolve(process.cwd(), "jax");
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 /D ${targetDir} ${sourceDir}`.nothrow();
58
+ await $`cmd /c mklink /J ${targetDir} ${sourceDir}`.nothrow().quiet();
21
59
  if (result.exitCode !== 0) {
22
60
  console.error(
23
- "Failed to create symlink. Make sure you run as Administrator.",
61
+ `创建目录联接失败:${result.stderr.toString()}`,
24
62
  );
25
63
  process.exit(1);
26
64
  }
27
- console.log(`Symlink created: ${targetDir} <<===>> ${sourceDir}`);
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(`Failed to create symlink: ${result.stderr.toString()}`);
70
+ console.error(`创建符号链接失败:${result.stderr.toString()}`);
33
71
  process.exit(1);
34
72
  }
35
- console.log(`Symlink created: ${targetDir} -> ${relativeSource}`);
73
+ console.log(`符号链接已创建:${targetDir} -> ${relativeSource}`);
36
74
  }
37
75
  });
38
76
 
39
77
  cli
40
- .command("open", "Open jax-hono installation directory in file manager")
78
+ .command("open", "在文件管理器中打开 jax-hono 安装目录")
41
79
  .action(async () => {
42
80
  const sourceDir = path.resolve(import.meta.dir, "..");
43
81
 
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];
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(`Failed to open directory: ${result.stderr.toString()}`);
111
+ console.error(`打开目录失败:${result.stderr.toString() || "未知错误"}`);
57
112
  process.exit(1);
58
113
  }
59
- console.log(`Opened: ${sourceDir}`);
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
- // Show help when no command is matched (e.g. bare `jax` or unknown command)
137
+ // 未匹配到命令时显示帮助,例如直接执行 `jax` 或输入未知命令。
66
138
  if (!cli.matchedCommand && !parsed.options.help) {
67
139
  cli.outputHelp();
68
140
  }
@@ -1,10 +1,11 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
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(import.meta.dir, '../..')
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('../../config', dir, name).replaceAll('\\', '/')
39
+ const path = join(configDir, dir, name).replaceAll('\\', '/')
39
40
 
40
- return path.startsWith('.') ? path : `./${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 '../../types'`] : []
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
- mkdirSync(generatedDir, { recursive: true })
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(srcDir, outputFile)} with ${files.length} config file(s).`)
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, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
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(import.meta.dir, '../..')
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 src/jax/build/generate-registry.ts.
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
- mkdirSync(generatedDir, { recursive: true })
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(srcDir, outputFile)}.`)
75
+ console.log(`Generated ${relative(projectDir, outputFile)}.`)
75
76
  }
76
77
 
77
78
  function toImportPath(filePath: string) {
78
- const path = relative(srcDir, filePath)
79
+ const path = filePath
79
80
  .replace(parse(filePath).ext, '')
80
81
  .replaceAll('\\', '/')
81
82
 
82
- return `@/${path}`
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 '@/jax/core/controller'\n`
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 = relative(srcDir, join(configFile, '..', packageName))
535
+ const normalized = join(configFile, '..', packageName)
535
536
  .replaceAll('\\', '/')
536
537
  .replace(/\/index$/, '')
537
538
 
538
- if (normalized.startsWith('jax/')) {
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
- 未启用的插件不会写入 `src/jax/generated/plugins.generated.ts`,因此 `bun build` 不会解析它的运行时依赖。
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
- - `src/jax/generated/config.ts`
92
- - `src/jax/generated/controllers.generated.ts`
93
- - `src/jax/generated/middlewares.generated.ts`
94
- - `src/jax/generated/models.generated.ts`
95
- - `src/jax/generated/plugins.generated.ts`
96
- - `src/jax/generated/services.generated.ts`
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.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.ts"
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 'node:path';
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 'mongoose';
9
- import config from '@/jax/config';
10
- import { rankSetter } from './schema';
11
- import dayjs from 'dayjs';
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] ?? mongooseModel(name, schema, collection ?? getCollectionName(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('/') || 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 = (config as any).mongoose?.prefix ?? (config as any).mongo?.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('YYYY-MM-DD HH:mm:ss');
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('YYYY-MM-DD HH:mm:ss');
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('timestamps', defaults.timestamps);
131
+ schema.set("timestamps", defaults.timestamps);
130
132
  }
131
133
 
132
- if (schema.options.versionKey === undefined || schema.options.versionKey === '__v') {
133
- schema.set('versionKey', defaults.versionKey);
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('toJSON', toJSON);
142
- schema.set('toObject', {
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(['updateOne', 'findOneAndUpdate', 'updateMany'], function () {
154
+ schema.pre(["updateOne", "findOneAndUpdate", "updateMany"], function () {
150
155
  normalizeRank(this.getUpdate());
151
156
  });
152
157
 
153
- schema.pre('save', function () {
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('save', async function () {
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('findPage', async function findPage(filter = {}, options: PageOptions = {}) {
173
- const page = Number(options.page || 1);
174
- const pageSize = Number(options.pageSize || 20);
175
- const sort = (options.sort ?? { _id: -1 }) as any;
176
- const total = await this.countDocuments(filter);
177
- let query = this.find(filter).sort(sort);
178
-
179
- if (pageSize !== -1) {
180
- query = query.skip((page - 1) * pageSize).limit(pageSize);
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
- const list = await query;
190
+ const list = await query;
184
191
 
185
- return {
186
- total,
187
- list,
188
- maxPage: pageSize === -1 ? 1 : Math.ceil(total / pageSize),
189
- page,
190
- pageSize
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('tree', async function tree(options: any = {}) {
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('flatTree', async function flatTree(this: any, options: any = {}) {
221
- const tree = await this.tree(options);
222
- const list: any[] = [];
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
- walkTree(tree, 1, list);
234
+ walkTree(tree, 1, list);
225
235
 
226
- return list;
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('isDelete')) {
244
+ if (!schema.path("isDelete")) {
234
245
  fields.isDelete = { type: Boolean, default: false };
235
246
  }
236
247
 
237
- if (!schema.path('isOpen')) {
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, '$1_$2')
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);